| Current Path : /var/www/html/administrator/components/com_jdownloads/src/Table/ |
| Current File : /var/www/html/administrator/components/com_jdownloads/src/Table/JDCategoryTable.php |
<?php
/**
* @package jDownloads
* @version 4.0
* @copyright (C) 2007 - 2022 Arno Betz - www.jdownloads.com
* @license http://www.gnu.org/copyleft/gpl.html GNU/GPL, see LICENSE.txt
*
* jDownloads is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
namespace JDownloads\Component\JDownloads\Administrator\Table;
\defined('_JEXEC') or die;
use Joomla\Utilities\ArrayHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Table\Table;
use Joomla\Filesystem\File;
use Joomla\Filesystem\Folder;
use Joomla\Registry\Registry;
use Joomla\Database\DatabaseDriver;
use Joomla\Input\Input;
use Joomla\CMS\Access\Rules;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\CMS\Event\AbstractEvent;
use Joomla\Event\Dispatcher;
use Joomla\Event\Event;
use Joomla\CMS\Tag\TaggableTableTrait;
use Joomla\CMS\Tag\TaggableTableInterface;
use Joomla\Database\ParameterType;
use Joomla\Database\DatabaseInterface;
use JDownloads\Component\JDownloads\Administrator\Helper\JDownloadsHelper;
Table::addIncludePath(JPATH_ADMINISTRATOR.'/components/com_jdownloads/src/Table');
/**
* Categories Table class
*/
class JDCategoryTable extends Table implements TaggableTableInterface
{
use TaggableTableTrait;
/**
* Object property holding the primary key of the parent node. Provides
* adjacency list data for nodes.
*
* @var integer
* @since 11.1
*/
public $parent_id;
/**
* Object property holding the depth level of the node in the tree.
*
* @var integer
* @since 11.1
*/
public $level;
/**
* Object property holding the left value of the node for managing its
* placement in the nested sets tree.
*
* @var integer
* @since 11.1
*/
public $lft;
/**
* Object property holding the right value of the node for managing its
* placement in the nested sets tree.
*
* @var integer
* @since 11.1
*/
public $rgt;
/**
* Object property holding the alias of this node used to constuct the
* full text path, forward-slash delimited.
*
* @var string
* @since 11.1
*/
public $alias;
/**
* Object property to hold the location type to use when storing the row.
* Possible values are: ['before', 'after', 'first-child', 'last-child'].
*
* @var string
* @since 11.1
*/
protected $_location;
/**
* Object property to hold the primary key of the location reference node to
* use when storing the row. A combination of location type and reference
* node describes where to store the current node in the tree.
*
* @var integer
* @since 11.1
*/
protected $_location_id;
/**
* An array to cache values in recursive processes.
*
* @var array
* @since 11.1
*/
protected $_cache = array();
/**
* Debug level
*
* @var integer
* @since 11.1
*/
protected $_debug = 0;
/**
* Cache for the root ID
*
* @var integer
* @since 3.3
*/
protected static $root_id = 0;
/**
* Array declaring the valid location values for moving a node
*
* @var array
* @since 3.7.0
*/
private $_validLocations = array('before', 'after', 'first-child', 'last-child');
/**
* Sets the debug level on or off
*
* @param integer $level 0 = off, 1 = on
*/
public function debug($level)
{
$this->_debug = (int)$level;
}
/**
* Constructor
*
* @param object Database connector object
*/
public function __construct(DatabaseDriver $db)
{
parent::__construct('#__jdownloads_categories', 'id', $db);
}
/**
* Overloaded check method to ensure data integrity.
*
* @param boolean $isNew true when the category is new
*
* @return boolean True on success.
*/
public function checkData($isNew, $auto_added = false)
{
$date = Factory::getDate()->toSql();
$user = Factory::getApplication()->getIdentity();
$db = Factory::getContainer()->get(DatabaseInterface::class);
$root_id = (int) $this->getRootId();
$jinput = Factory::getApplication()->getInput();
// Verify that the alias is unique
$jDownloadsComponent = Factory::getApplication()->bootComponent('com_jdownloads');
$table = $jDownloadsComponent->getMVCFactory()->createTable('JDCategory', 'Table', []);
if ($table->load(array('alias' => $this->alias, 'parent_id' => (int) $this->parent_id))
&& ($table->id != $this->id || $this->id == 0)){
if ($auto_added){
// When automatically added make it automatically unique
$this->alias .= mt_rand(100,999);
} else {
$this->setError(Text::_('COM_JDOWNLOADS_BACKEND_ERROR_CATEGORY_UNIQUE_ALIAS'));
return false;
}
}
/**
* @desc check icon upload field
* if pic selected for upload:
* - check image typ
* - check whether filename exists. If so, rename the new file.
* - move new file to catimages
*/
if (!$auto_added){
// We neeed also the jform files data
$jFileInput = new Input($_FILES);
$file = $jFileInput->get('jform',array(),'array');
if (isset($file['tmp_name']['picnew'])){
if ($file['tmp_name']['picnew'] != ''){
$pic['tmp_name'] = $file['tmp_name']['picnew'];
$pic['name'] = $file['name']['picnew'];
$pic['type'] = $file['type']['picnew'];
$pic['size'] = $file['size']['picnew'];
if (JDownloadsHelper::fileIsPicture($pic['name'])){
$upload_dir = JPATH_SITE.'/images/jdownloads/catimages/';
$pic['name'] = File::makeSafe($pic['name']);
// Replace all spaces with underscores to prevent problems
$pic['name'] = str_replace(' ', '_', $pic['name']);
if (!File::upload($pic['tmp_name'], $upload_dir.$pic['name'], false, true)){
$this->setError(Text::_('COM_JDOWNLOADS_ERROR_CAN_NOT_MOVE_UPLOADED_IMAGE'));
return false;
} else {
// Move ok - set new file name as selected
$this->pic = $pic['name'];
}
}
} else {
// Check selected pic
$picname = $jinput->get('pic');
if (isset($picname)){
$this->pic = $jinput->get('pic');
}
}
}
}
// If the category is below the real root node, inherit structural data from its parent category.
if ($root_id > 0 && (int) $this->parent_id !== $root_id){
if ($isNew){
$query = "SELECT * FROM #__jdownloads_categories WHERE id = '$this->parent_id'";
$db->setQuery( $query );
$parent_cat = $db->loadObject();
$this->level = $parent_cat->level + 1;
if ($parent_cat->cat_dir_parent != ''){
$this->cat_dir_parent = $parent_cat->cat_dir_parent.'/'.$parent_cat->cat_dir;
} else {
$this->cat_dir_parent = $parent_cat->cat_dir;
}
if (!$auto_added){
// Use access value from parent category
$this->access = $parent_cat->access;
}
}
} else {
// Has no parent category so we must delete the cat_dir_parent field
$this->cat_dir_parent = '';
}
// Check date and user id fields
if (!$isNew){
// Set user id in modified field
$this->modified_user_id = $user->id;
// Fill out modified date field
$this->modified_time = $date;
} else {
// Fill out created date field
$this->created_time = $date;
if (!$this->created_user_id && !$auto_added){
$this->created_user_id = $user->id;
}
}
if ($this->robots == null) $this->robots = '';
if ($this->metakey == null) $this->metakey = '';
if ($this->metadesc == null) $this->metadesc = '';
if ($this->notes == null) $this->notes = '';
if ($this->params == null) $this->params = '';
if ($this->description == null) $this->description = '';
if ($this->password == null) $this->password = '';
if ($this->password_md5 == null) $this->password_md5 = '';
if ($this->pic == null) $this->pic = '';
return true;
}
/**
* Method to set the location of a node in the tree object. This method does not
* save the new location to the database, but will set it in the object so
* that when the node is stored it will be stored in the new location.
*
* @param integer $referenceId The primary key of the node to reference new location by.
* @param string $position Location type string. ['before', 'after', 'first-child', 'last-child']
*
* @return boolean True on success.
*
* @link http://docs.joomla.org/TableNested/setLocation
* @since 11.1
*/
public function setLocation($referenceId, $position = 'after')
{
// Make sure the location is valid.
if (!\in_array($position, $this->_validLocations)){
throw new \InvalidArgumentException(
sprintf('Invalid location "%1$s" given, valid values are %2$s', $position, implode(', ', $this->_validLocations))
);
}
// Set the location properties.
$this->_location = $position;
$this->_location_id = $referenceId;
}
/**
* Method to rebuild the node's path field from the alias values of the
* nodes from the current node to the root node of the tree.
*
* @param integer $pk Primary key of the node for which to get the path.
*
* @return boolean True on success.
*
* @link http://docs.joomla.org/TableNested/rebuildPath
* @since 11.1
*/
public function rebuildPath($pk = null)
{
$fields = $this->getFields();
// If there is no alias or path field, just return true.
if (!\array_key_exists('alias', $fields) || !\array_key_exists('path', $fields))
{
return true;
}
$k = $this->_tbl_key;
$pk = (\is_null($pk)) ? $this->$k : $pk;
// Get the aliases for the path from the node to the root node.
$query = $this->_db->getQuery(true);
$query->select('p.cat_dir');
$query->from($this->_tbl.' AS n, '.$this->_tbl.' AS p');
$query->where('n.lft BETWEEN p.lft AND p.rgt');
$query->where('n.'.$this->_tbl_key.' = '. (int) $pk);
$query->order('p.lft');
$this->_db->setQuery($query);
$segments = $this->_db->loadColumn();
// Make sure to remove the root path if it exists in the list.
if ($segments[0] == ''){
array_shift($segments);
}
// remove the cat dir from current sub cat in the list.
array_pop($segments);
// Build the path.
$path = trim(implode('/', $segments), ' /\\');
// Update the path field for the node.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('cat_dir_parent = '.$this->_db->quote($path));
$query->where($this->_tbl_key.' = '.(int) $pk);
$this->_db->setQuery($query);
// Check for a database error.
if (!$this->_db->execute()){
$e = new JException(Text::sprintf('JLIB_DATABASE_ERROR_REBUILDPATH_FAILED', get_class($this), $this->_db->getErrorMsg()));
$this->setError($e);
return false;
}
// Update the current record's path to the new one:
$this->cat_dir_parent = $path;
return true;
}
public function rebuild($parentId = null, $leftId = 0, $level = 0, $path = '')
{
// If no parent is provided, try to find it.
if ($parentId === null){
// Get the root item.
$parentId = $this->getRootId();
if ($parentId === false){
return false;
}
}
$query = $this->_db->getQuery(true);
// Build the structure of the recursive query.
if (!isset($this->_cache['rebuild.sql'])){
$query->select($this->_tbl_key.', cat_dir, cat_dir_parent');
//$query->select($this->_tbl_key.', alias');
$query->from($this->_tbl);
$query->where('parent_id = %d');
$query->order('parent_id, lft');
// If the table has an ordering field, use that for ordering.
/*if (property_exists($this, 'ordering')) {
$query->order('parent_id, ordering, lft');
} else {
$query->order('parent_id, lft');
} */
$this->_cache['rebuild.sql'] = (string) $query;
}
// Make a shortcut to database object.
// Assemble the query to find all children of this node.
$this->_db->setQuery(sprintf($this->_cache['rebuild.sql'], (int) $parentId));
$children = $this->_db->loadObjectList();
// The right value of this node is the left value + 1
$rightId = $leftId + 1;
// Execute this function recursively over all children
foreach ($children as $node){
/*
* $rightId is the current right value, which is incremented on recursion return.
* Increment the level for the children.
* Add this item's alias to the path (but avoid a leading /)
*/
$rightId = $this->rebuild($node->{$this->_tbl_key}, $rightId, $level + 1, $path . (empty($path) ? '' : '/') . $node->cat_dir);
// If there is an update failure, return false to break out of the recursion.
if ($rightId === false){
return false;
}
}
// We've got the left value, and now that we've processed
// The children of this node we also know the right value.
$path = substr($path, 0, strrpos($path, '/') );
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('lft = '. (int) $leftId);
$query->set('rgt = '. (int) $rightId);
$query->set('level = '.(int) $level);
$query->set('cat_dir_parent = '.$this->_db->quote($path));
$query->where($this->_tbl_key.' = '. (int)$parentId);
$this->_db->setQuery($query);
// If there is an update failure, return false to break out of the recursion.
if (!$this->_db->execute()){
$e = new JException(Text::sprintf('JLIB_DATABASE_ERROR_REBUILD_FAILED', get_class($this), $this->_db->getErrorMsg()));
$this->setError($e);
return false;
}
// Return the right value of this node + 1.
return $rightId + 1;
}
/**
* Method to store a node in the database table.
*
* @param boolean True to update null values as well.
*
* @return boolean True on success.
*
* @link http://docs.joomla.org/TableNested/store
* @since 11.1
*/
public function store($updateNulls = false)
{
$date = Factory::getDate()->toSql();
$user = Factory::getApplication()->getIdentity();
// Set created date if not set.
if (!(int) $this->created_time){
$this->created_time = $date;
}
if ($this->id){
// Existing category
$this->modified_user_id = $user->get('id');
$this->modified_time = $date;
} else {
if (!(int) ($this->modified_time)){
$this->modified_time = $this->created_time;
}
// Field created_user_id can be set by the user, so we don't touch it if it's set.
if (empty($this->created_user_id)){
$this->created_user_id = $user->get('id');
}
if (empty($this->modified_user_id)){
$this->modified_user_id = $this->created_user_id;
}
}
// Initialise variables.
$k = $this->_tbl_key;
if ($this->_debug){
echo "\n".get_class($this)."::store\n";
$this->_logtable(true, false);
}
/*
* If the primary key is empty, then we assume we are inserting a new node into the
* tree. From this point we would need to determine where in the tree to insert it.
*/
if (empty($this->$k)){
/*
* We are inserting a node somewhere in the tree with a known reference
* node. We have to make room for the new node and set the left and right
* values before we insert the row.
*/
if ($this->_location_id >= 0){
// Lock the table for writing.
if (!$this->_lock()){
// Error message set in lock method.
return false;
}
// We are inserting a node relative to the last root node.
if ($this->_location_id == 0){
// Get the last root node as the reference node.
$query = $this->_db->getQuery(true);
$query->select($this->_tbl_key.', parent_id, level, lft, rgt');
$query->from($this->_tbl);
$query->where('parent_id = 0');
$query->order('lft DESC');
$this->_db->setQuery($query, 0, 1);
// Check for a database error.
try
{
$reference = $this->_db->loadObject();
}
catch (\RuntimeException $e)
{
Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
$this->_unlock();
return false;
}
if ($this->_debug){
$this->_logtable(false);
}
} else {
// We have a real node set as a location reference.
// Get the reference node by primary key.
if (!$reference = $this->_getNode($this->_location_id)){
// Error message set in getNode method.
$this->_unlock();
return false;
}
}
// Get the reposition data for shifting the tree and re-inserting the node.
if (!($repositionData = $this->_getTreeRepositionData($reference, 2, $this->_location))){
// Error message set in getNode method.
$this->_unlock();
return false;
}
// Create space in the tree at the new location for the new node in left ids.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('lft = lft + 2');
$query->where($repositionData->left_where);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_STORE_FAILED');
// Create space in the tree at the new location for the new node in right ids.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('rgt = rgt + 2');
$query->where($repositionData->right_where);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_STORE_FAILED');
// Set the object values.
$this->parent_id = $repositionData->new_parent_id;
$this->level = $repositionData->new_level;
$this->lft = $repositionData->new_lft;
$this->rgt = $repositionData->new_rgt;
} else {
// Negative parent ids are invalid
$e = new JException(Text::_('JLIB_DATABASE_ERROR_INVALID_PARENT_ID'));
$this->setError($e);
return false;
}
} else {
/*
* If we have a given primary key then we assume we are simply updating this
* node in the tree. We should assess whether or not we are moving the node
* or just updating its data fields.
*/
// If the location has been set, move the node to its new location.
if ($this->_location_id > 0){
if (!$this->moveByReference($this->_location_id, $this->_location, $this->$k)){
// Error message set in move method.
return false;
}
}
// Lock the table for writing.
/* if (!$this->_lock()) {
// Error message set in lock method.
return false;
} */
}
// Store the row to the database.
if (!parent::store($updateNulls)){
$this->_unlock();
return false;
}
if ($this->_debug){
$this->_logtable();
}
// Unlock the table for writing.
$this->_unlock();
return true;
}
/**
* Method to get nested set properties for a node in the tree.
*
* @param integer $id Value to look up the node by.
* @param string $key Key to look up the node by.
*
* @return mixed Boolean false on failure or node object on success.
*
* @since 11.1
*/
protected function _getNode($id, $key = null)
{
// Determine which key to get the node base on.
switch ($key)
{
case 'parent':
$k = 'parent_id';
break;
case 'left':
$k = 'lft';
break;
case 'right':
$k = 'rgt';
break;
default:
$k = $this->_tbl_key;
break;
}
// Get the node data.
$query = $this->_db->getQuery(true)
->select($this->_tbl_key . ', parent_id, level, lft, rgt')
->from($this->_tbl)
->where($k . ' = ' . (int) $id);
$row = $this->_db->setQuery($query, 0, 1)->loadObject();
// Check for no $row returned
if (empty($row)){
$e = new JException(Text::sprintf('JLIB_DATABASE_ERROR_GETNODE_FAILED', get_class($this), $this->_db->getErrorMsg()));
$this->setError($e);
return false;
}
// Do some simple calculations.
$row->numChildren = (int) ($row->rgt - $row->lft - 1) / 2;
$row->width = (int) $row->rgt - $row->lft + 1;
return $row;
}
/**
* Method to get various data necessary to make room in the tree at a location
* for a node and its children. The returned data object includes conditions
* for SQL WHERE clauses for updating left and right id values to make room for
* the node as well as the new left and right ids for the node.
*
* @param object $referenceNode A node object with at least a 'lft' and 'rgt' with
* which to make room in the tree around for a new node.
* @param integer $nodeWidth The width of the node for which to make room in the tree.
* @param string $position The position relative to the reference node where the room
* should be made.
*
* @return mixed Boolean false on failure or data object on success.
*
* @since 11.1
*/
protected function _getTreeRepositionData($referenceNode, $nodeWidth, $position = 'before')
{
// Make sure the reference an object with a left and right id.
if (!is_object($referenceNode) && isset($referenceNode->lft) && isset($referenceNode->rgt)){
return false;
}
// A valid node cannot have a width less than 2.
if ($nodeWidth < 2) return false;
// Initialise variables.
$k = $this->_tbl_key;
$data = new \stdClass;
// Run the calculations and build the data object by reference position.
switch ($position)
{
case 'first-child':
$data->left_where = 'lft > '.$referenceNode->lft;
$data->right_where = 'rgt >= '.$referenceNode->lft;
$data->new_lft = $referenceNode->lft + 1;
$data->new_rgt = $referenceNode->lft + $nodeWidth;
$data->new_parent_id = $referenceNode->$k;
$data->new_level = $referenceNode->level + 1;
break;
case 'last-child':
$data->left_where = 'lft > '.($referenceNode->rgt);
$data->right_where = 'rgt >= '.($referenceNode->rgt);
$data->new_lft = $referenceNode->rgt;
$data->new_rgt = $referenceNode->rgt + $nodeWidth - 1;
$data->new_parent_id = $referenceNode->$k;
$data->new_level = $referenceNode->level + 1;
break;
case 'before':
$data->left_where = 'lft >= '.$referenceNode->lft;
$data->right_where = 'rgt >= '.$referenceNode->lft;
$data->new_lft = $referenceNode->lft;
$data->new_rgt = $referenceNode->lft + $nodeWidth - 1;
$data->new_parent_id = $referenceNode->parent_id;
$data->new_level = $referenceNode->level;
break;
default:
case 'after':
$data->left_where = 'lft > '.$referenceNode->rgt;
$data->right_where = 'rgt > '.$referenceNode->rgt;
$data->new_lft = $referenceNode->rgt + 1;
$data->new_rgt = $referenceNode->rgt + $nodeWidth;
$data->new_parent_id = $referenceNode->parent_id;
$data->new_level = $referenceNode->level;
break;
}
if ($this->_debug){
echo "\nRepositioning Data for $position" .
"\n-----------------------------------" .
"\nLeft Where: $data->left_where" .
"\nRight Where: $data->right_where" .
"\nNew Lft: $data->new_lft" .
"\nNew Rgt: $data->new_rgt".
"\nNew Parent ID: $data->new_parent_id".
"\nNew Level: $data->new_level" .
"\n";
}
return $data;
}
/**
* Method to move a row in the ordering sequence of a group of rows defined by an SQL WHERE clause.
* Negative numbers move the row up in the sequence and positive numbers move it down.
*
* @param integer $delta The direction and magnitude to move the row in the ordering sequence.
* @param string $where WHERE clause to use for limiting the selection of rows to compact the
* ordering values.
*
* @return mixed Boolean true on success.
*
* @link http://docs.joomla.org/Table/move
* @since 11.1
*/
public function move($delta, $where = '')
{
// Initialise variables.
$k = $this->_tbl_key;
$pk = $this->$k;
$query = $this->_db->getQuery(true);
$query->select($k);
$query->from($this->_tbl);
$query->where('parent_id = '.$this->parent_id);
if ($where) {
$query->where($where);
}
$position = 'after';
if ($delta > 0){
$query->where('rgt > '.$this->rgt);
$query->order('rgt ASC');
$position = 'after';
} else {
$query->where('lft < '.$this->lft);
$query->order('lft DESC');
$position = 'before';
}
$this->_db->setQuery($query);
$referenceId = $this->_db->loadResult();
if ($referenceId){
return $this->moveByReference($referenceId, $position, $pk);
} else {
return false;
}
}
/**
* Method to move a node and its children to a new location in the tree.
*
* @param integer $referenceId The primary key of the node to reference new location by.
* @param string $position Location type string. ['before', 'after', 'first-child', 'last-child']
* @param integer $pk The primary key of the node to move.
*
* @return boolean True on success.
*
* @link http://docs.joomla.org/TableNested/moveByReference
* @since 11.1
*/
public function moveByReference($referenceId, $position = 'after', $pk = null)
{
if ($this->_debug){
echo "\nMoving ReferenceId:$referenceId, Position:$position, PK:$pk";
}
// Initialise variables.
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
// Get the node by id.
if (!$node = $this->_getNode($pk)){
// Error message set in getNode method.
return false;
}
// Get the ids of child nodes.
$query = $this->_db->getQuery(true);
$query->select($k);
$query->from($this->_tbl);
$query->where('lft BETWEEN '.(int) $node->lft.' AND '.(int) $node->rgt);
$this->_db->setQuery($query);
// Check for a database error.
try
{
$children = $this->_db->loadColumn();
}
catch (\RuntimeException $e)
{
Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
return false;
}
if ($this->_debug){
$this->_logtable(false);
}
// Cannot move the node to be a child of itself.
if (in_array($referenceId, $children)){
$e = new JException(Text::sprintf('JLIB_DATABASE_ERROR_INVALID_NODE_RECURSION', get_class($this)));
$this->setError($e);
return false;
}
// Lock the table for writing.
if (!$this->_lock()){
return false;
}
/*
* Move the sub-tree out of the nested sets by negating its left and right values.
*/
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('lft = lft * (-1), rgt = rgt * (-1)');
$query->where('lft BETWEEN '.(int) $node->lft.' AND '.(int) $node->rgt);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
/*
* Close the hole in the tree that was opened by removing the sub-tree from the nested sets.
*/
// Compress the left values.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('lft = lft - '.(int) $node->width);
$query->where('lft > '.(int) $node->rgt);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
// Compress the right values.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('rgt = rgt - '.(int) $node->width);
$query->where('rgt > '.(int) $node->rgt);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
// We are moving the tree relative to a reference node.
if ($referenceId){
// Get the reference node by primary key.
if (!$reference = $this->_getNode($referenceId)){
// Error message set in getNode method.
$this->_unlock();
return false;
}
// Get the reposition data for shifting the tree and re-inserting the node.
if (!$repositionData = $this->_getTreeRepositionData($reference, $node->width, $position)){
// Error message set in getNode method.
$this->_unlock();
return false;
}
} else {
// We are moving the tree to be the last child of the root node
// Get the last root node as the reference node.
$query = $this->_db->getQuery(true);
$query->select($this->_tbl_key.', parent_id, level, lft, rgt');
$query->from($this->_tbl);
$query->where('parent_id = 0');
$query->order('lft DESC');
$this->_db->setQuery($query, 0, 1);
// Check for a database error.
try
{
$reference = $this->_db->loadObject();
}
catch (\RuntimeException $e)
{
Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
$this->_unlock();
return false;
}
if ($this->_debug){
$this->_logtable(false);
}
// Get the reposition data for re-inserting the node after the found root.
if (!$repositionData = $this->_getTreeRepositionData($reference, $node->width, 'last-child')){
// Error message set in getNode method.
$this->_unlock();
return false;
}
}
/*
* Create space in the nested sets at the new location for the moved sub-tree.
*/
// Shift left values.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('lft = lft + '.(int) $node->width);
$query->where($repositionData->left_where);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
// Shift right values.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('rgt = rgt + '.(int) $node->width);
$query->where($repositionData->right_where);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
/*
* Calculate the offset between where the node used to be in the tree and
* where it needs to be in the tree for left ids (also works for right ids).
*/
$offset = $repositionData->new_lft - $node->lft;
$levelOffset = $repositionData->new_level - $node->level;
// Move the nodes back into position in the tree using the calculated offsets.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('rgt = '.(int) $offset.' - rgt');
$query->set('lft = '.(int) $offset.' - lft');
$query->set('level = level + '.(int) $levelOffset);
$query->where('lft < 0');
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
// Set the correct parent id for the moved node if required.
if ($node->parent_id != $repositionData->new_parent_id){
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
// Update the title and alias fields if they exist for the table.
if (property_exists($this, 'title') && $this->title !== null){
$query->set('title = '.$this->_db->Quote($this->title));
}
if (property_exists($this, 'alias') && $this->alias !== null){
$query->set('alias = '.$this->_db->Quote($this->alias));
}
$query->set('parent_id = '.(int) $repositionData->new_parent_id);
$query->where($this->_tbl_key.' = '.(int) $node->$k);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
}
// Unlock the table for writing.
$this->_unlock();
// Set the object values.
$this->parent_id = $repositionData->new_parent_id;
$this->level = $repositionData->new_level;
$this->lft = $repositionData->new_lft;
$this->rgt = $repositionData->new_rgt;
return true;
}
/**
* Method to delete a node and, optionally, its child nodes from the table.
*
* @param integer $pk The primary key of the node to delete.
* @param boolean $children True to delete child nodes, false to move them up a level.
*
* @return boolean True on success.
*
* @link http://docs.joomla.org/TableNested/delete
*/
public function delete($pk = null, $children = true)
{
// Initialise variables.
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
if ($this->hasChildren($pk)){
// category has children so we can not delete it
Factory::getApplication()->enqueueMessage( Text::_('COM_JDOWNLOADS_BE_NO_DEL_SUBCATS_EXISTS'), 'warning');
return false;
}
if ($this->hasDownloads($pk)){
// category has downloads so we can not delete it
Factory::getApplication()->enqueueMessage( Text::_('COM_JDOWNLOADS_BE_NO_DEL_FILES_EXISTS'), 'notice');
return false;
}
// Pre-processing by observers
$event = AbstractEvent::create(
'onTableBeforeDelete',
[
'subject' => $this,
'pk' => $pk,
]
);
$this->getDispatcher()->dispatch('onTableBeforeDelete', $event);
// Lock the table for writing.
if (!$this->_lock()){
// Error message set in lock method.
return false;
}
// If tracking assets, remove the asset first.
if ($this->_trackAssets){
$name = $this->_getAssetName();
$asset = Table::getInstance('Asset');
// Lock the table for writing.
if (!$asset->_lock()){
// Error message set in lock method.
return false;
}
if ($asset->loadByName($name)){
// Delete the node in assets table.
if (!$asset->delete(null, $children)){
$this->setError($asset->getError());
$asset->_unlock();
return false;
}
$asset->_unlock();
} else {
$this->setError($asset->getError());
$asset->_unlock();
return false;
}
}
// Get the node by id.
if (!$node = $this->_getNode($pk)){
// Error message set in getNode method.
$this->_unlock();
return false;
}
// get first the folder name, so we can later delete this folder
$query = $this->_db->getQuery(true);
$query->select('cat_dir, cat_dir_parent');
$query->from($this->_tbl);
$query->where('id = '.(int)$pk);
$this->_db->setQuery($query);
$cat_dirs = $this->_db->loadObject();
// Should we delete all children along with the node?
if ($children){
// Delete the node and all of its children.
$query = $this->_db->getQuery(true);
$query->delete();
$query->from($this->_tbl);
$query->where('lft BETWEEN '.(int) $node->lft.' AND '.(int) $node->rgt);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
// Compress the left values.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('lft = lft - '.(int) $node->width);
$query->where('lft > '.(int) $node->rgt);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
// Compress the right values.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('rgt = rgt - '.(int) $node->width);
$query->where('rgt > '.(int) $node->rgt);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
} else {
// Leave the children and move them up a level.
// Delete the node.
$query = $this->_db->getQuery(true);
$query->delete();
$query->from($this->_tbl);
$query->where('lft = '.(int) $node->lft);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
// Shift all node's children up a level.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('lft = lft - 1');
$query->set('rgt = rgt - 1');
$query->set('level = level - 1');
$query->where('lft BETWEEN '.(int) $node->lft.' AND '.(int) $node->rgt);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
// Adjust all the parent values for direct children of the deleted node.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('parent_id = '.(int) $node->parent_id);
$query->where('parent_id = '.(int) $node->$k);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
// Shift all of the left values that are right of the node.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('lft = lft - 2');
$query->where('lft > '.(int) $node->rgt);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
// Shift all of the right values that are right of the node.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('rgt = rgt - 2');
$query->where('rgt > '.(int) $node->rgt);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
}
// delete now the folder
if ($cat_dirs){
if ($cat_dirs->cat_dir_parent != ''){
$cat_dir = $cat_dirs->cat_dir_parent.'/'.$cat_dirs->cat_dir;
} else {
$cat_dir = $cat_dirs->cat_dir;
}
JDownloadsHelper::deleteCategoryFolder($cat_dir);
}
// Unlock the table for writing.
$this->_unlock();
// Post-processing by observers
$event = AbstractEvent::create(
'onTableAfterDelete',
[
'subject' => $this,
'pk' => $pk,
]
);
$this->getDispatcher()->dispatch('onTableAfterDelete', $event);
return true;
}
/**
* Method to move a node one position to the left in the same level.
*
* @param integer $pk Primary key of the node to move.
*
* @return boolean True on success.
*
* @link http://docs.joomla.org/TableNested/orderUp
*/
public function orderUp($pk)
{
// Initialise variables.
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
// Lock the table for writing.
if (!$this->_lock()){
// Error message set in lock method.
return false;
}
// Get the node by primary key.
if (!$node = $this->_getNode($pk)){
// Error message set in getNode method.
$this->_unlock();
return false;
}
// Get the left sibling node.
if (!$sibling = $this->_getNode($node->lft - 1, 'right')){
// Error message set in getNode method.
$this->_unlock();
return false;
}
// Get the primary keys of child nodes.
$query = $this->_db->getQuery(true);
$query->select($this->_tbl_key);
$query->from($this->_tbl);
$query->where('lft BETWEEN '.(int) $node->lft.' AND '.(int) $node->rgt);
$this->_db->setQuery($query);
// Check for a database error.
try
{
$children = $this->_db->loadColumn();
}
catch (\RuntimeException $e)
{
Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
$this->_unlock();
return false;
}
// Shift left and right values for the node and it's children.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('lft = lft - '.(int) $sibling->width);
$query->set('rgt = rgt - '.(int) $sibling->width);
$query->where('lft BETWEEN '.(int) $node->lft.' AND '.(int) $node->rgt);
$this->_db->setQuery($query);
// Check for a database error.
if (!$this->_db->execute()){
$e = new JException(Text::sprintf('JLIB_DATABASE_ERROR_ORDERUP_FAILED', get_class($this), $this->_db->getErrorMsg()));
$this->setError($e);
$this->_unlock();
return false;
}
// Shift left and right values for the sibling and it's children.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('lft = lft + '.(int) $node->width);
$query->set('rgt = rgt + '.(int) $node->width);
$query->where('lft BETWEEN '.(int) $sibling->lft.' AND '.(int) $sibling->rgt);
$query->where($this->_tbl_key.' NOT IN ('.implode(',', $children).')');
$this->_db->setQuery($query);
// Check for a database error.
if (!$this->_db->execute()){
$e = new JException(Text::sprintf('JLIB_DATABASE_ERROR_ORDERUP_FAILED', get_class($this), $this->_db->getErrorMsg()));
$this->setError($e);
$this->_unlock();
return false;
}
// Unlock the table for writing.
$this->_unlock();
return true;
}
/**
* Method to move a node one position to the right in the same level.
*
* @param integer $pk Primary key of the node to move.
*
* @return boolean True on success.
*
* @link http://docs.joomla.org/TableNested/orderDown
*/
public function orderDown($pk)
{
// Initialise variables.
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
// Lock the table for writing.
if (!$this->_lock()){
// Error message set in lock method.
return false;
}
// Get the node by primary key.
if (!$node = $this->_getNode($pk)){
// Error message set in getNode method.
$this->_unlock();
return false;
}
// Get the right sibling node.
if (!$sibling = $this->_getNode($node->rgt + 1, 'left')){
// Error message set in getNode method.
$query->unlock($this->_db);
$this->_locked=false;
return false;
}
// Get the primary keys of child nodes.
$query = $this->_db->getQuery(true);
$query->select($this->_tbl_key);
$query->from($this->_tbl);
$query->where('lft BETWEEN '.(int) $node->lft.' AND '.(int) $node->rgt);
$this->_db->setQuery($query);
// Check for a database error.
try
{
$children = $this->_db->loadColumn();
}
catch (\RuntimeException $e)
{
Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
$this->_unlock();
return false;
}
// Shift left and right values for the node and it's children.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('lft = lft + '.(int) $sibling->width);
$query->set('rgt = rgt + '.(int) $sibling->width);
$query->where('lft BETWEEN '.(int) $node->lft.' AND '.(int) $node->rgt);
$this->_db->setQuery($query);
// Check for a database error.
if (!$this->_db->execute()){
$e = new JException(Text::sprintf('JLIB_DATABASE_ERROR_ORDERDOWN_FAILED', get_class($this), $this->_db->getErrorMsg()));
$this->setError($e);
$this->_unlock();
return false;
}
// Shift left and right values for the sibling and it's children.
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->set('lft = lft - '.(int) $node->width);
$query->set('rgt = rgt - '.(int) $node->width);
$query->where('lft BETWEEN '.(int) $sibling->lft.' AND '.(int) $sibling->rgt);
$query->where($this->_tbl_key.' NOT IN ('.implode(',', $children).')');
$this->_db->setQuery($query);
// Check for a database error.
if (!$this->_db->execute()){
$e = new JException(Text::sprintf('JLIB_DATABASE_ERROR_ORDERDOWN_FAILED', get_class($this), $this->_db->getErrorMsg()));
$this->setError($e);
$this->_unlock();
return false;
}
// Unlock the table for writing.
$this->_unlock();
return true;
}
/**
* Method to update order of table rows
*
* @param array $idArray id numbers of rows to be reordered
* @param array $lft_array lft values of rows to be reordered
*
* @return integer 1 + value of root rgt on success, false on failure
*/
public function saveorder($idArray = null, $lft_array = null)
{
// Validate arguments
if (is_array($idArray) && is_array($lft_array) && count($idArray) == count($lft_array)){
for ($i = 0, $count = count($idArray); $i < $count; $i++){
// Do an update to change the lft values in the table for each id
$query = $this->_db->getQuery(true);
$query->update($this->_tbl);
$query->where($this->_tbl_key . ' = ' . (int) $idArray[$i]);
$query->set('lft = ' . (int) $lft_array[$i]);
$this->_db->setQuery($query);
// Check for a database error.
if (!$this->_db->execute()){
$e = new JException(Text::sprintf('JLIB_DATABASE_ERROR_REORDER_FAILED', get_class($this), $this->_db->getErrorMsg()));
$this->setError($e);
$this->_unlock();
return false;
}
if ($this->_debug){
$this->_logtable();
}
}
return $this->rebuild();
} else {
return false;
}
}
/**
* Gets the ID of the root item in the tree
*
* @return mixed The ID of the root row, or false and the internal error is set.
*
*/
public function getRootId()
{
if ((int) self::$root_id > 0){
return self::$root_id;
}
// Get the root item.
$k = $this->_tbl_key;
// Test for a unique record with parent_id = 0
$query = $this->_db->getQuery(true)
->select($k)
->from($this->_tbl)
->where('parent_id = 0');
$result = $this->_db->setQuery($query)->loadColumn();
if (\count($result) == 1){
self::$root_id = $result[0];
return self::$root_id;
}
// Test for a unique record with lft = 0
$query->clear()
->select($k)
->from($this->_tbl)
->where('lft = 0');
$result = $this->_db->setQuery($query)->loadColumn();
if (\count($result) == 1){
self::$root_id = $result[0];
return self::$root_id;
}
$fields = $this->getFields();
if (\array_key_exists('alias', $fields)){
// Test for a unique record alias = root
$query->clear()
->select($k)
->from($this->_tbl)
->where('alias = ' . $this->_db->quote('root'));
$result = $this->_db->setQuery($query)->loadColumn();
if (\count($result) == 1){
self::$root_id = $result[0];
return self::$root_id;
}
}
$e = new \UnexpectedValueException(sprintf('%s::getRootId', \get_class($this)));
$this->setError($e);
self::$root_id = false;
return false;
}
/**
* Method to create a log table in the buffer optionally showing the query and/or data.
*
* @param boolean $showData True to show data
* @param boolean $showQuery True to show query
*
* @return void
*
* @since 11.1
*/
protected function _logtable($showData = true, $showQuery = true)
{
$sep = "\n".str_pad('', 40, '-');
$buffer = '';
if ($showQuery){
$buffer .= "\n".$this->_db->getQuery().$sep;
}
if ($showData){
$query = $this->_db->getQuery(true);
$query->select($this->_tbl_key.', parent_id, lft, rgt, level');
$query->from($this->_tbl);
$query->order($this->_tbl_key);
$this->_db->setQuery($query);
$rows = $this->_db->loadRowList();
$buffer .= sprintf("\n| %4s | %4s | %4s | %4s |", $this->_tbl_key, 'par', 'lft', 'rgt');
$buffer .= $sep;
foreach ($rows as $row){
$buffer .= sprintf("\n| %4s | %4s | %4s | %4s |", $row[0], $row[1], $row[2], $row[3]);
}
$buffer .= $sep;
}
echo $buffer;
}
/**
* Method to determine if a node is a leaf node in the tree (has no children).
*
* @param integer $pk Primary key of the node to check.
*
* @return boolean True if a leaf node.
*
* @link http://docs.joomla.org/TableNested/isLeaf
* @since 11.1
*/
public function isLeaf($pk = null)
{
// Initialise variables.
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
// Get the node by primary key.
if (!$node = $this->_getNode($pk)){
// Error message set in getNode method.
return false;
}
// The node is a leaf node.
return (($node->rgt - $node->lft) == 1);
}
/**
* Method to run an update query and check for a database error
*
* @params string $query
* @param string $errorMessage
*
* @return boolean False on exception
*
*/
protected function _runQuery($query, $errorMessage)
{
$this->_db->setQuery($query);
// Check for a database error.
if (!$this->_db->execute()){
$e = new JException(Text::sprintf('$errorMessage', get_class($this), $this->_db->getErrorMsg()));
$this->setError($e);
$this->_unlock();
return false;
}
if ($this->_debug){
$this->_logtable();
}
}
/**
* Method to handle the category folder actions (create/move/rename)
*
* @param string $isNew true when used
* @param string $catChanged true when used
* @param string $titleChanged true when used
* @param string $checked_cat_title the new/changed and checked category folder name
*
* @return boolean $result True on success
*/
public function checkCategoryFolder($isNew, $catChanged, $titleChanged, $checked_cat_title, $cat_dir_changed_manually)
{
$params = ComponentHelper::getParams('com_jdownloads');
$root_id = (int) $this->getRootId();
$jinput = Factory::getApplication()->getInput();
$root_dir_path = $params->get('files_uploaddir');
if (!$isNew && !$catChanged && !$titleChanged && !$cat_dir_changed_manually){
// Nothing to do
return true;
}
if ($isNew){
// Get parent dir when selected
if ($root_id > 0 && (int) $this->parent_id !== $root_id){
$this->cat_dir_parent = $this->getParentCategoryPath($this->parent_id);
}
if ($this->cat_dir_parent != ''){
$cat_dir_path = $root_dir_path.'/'.$this->cat_dir_parent.'/'.$this->cat_dir;
} else {
$cat_dir_path = $root_dir_path.'/'.$this->cat_dir;
}
// Create the new folder when he not exists
if (!Folder::exists($cat_dir_path)){
$result = Folder::create($cat_dir_path);
} else {
// New category but the given cat_dir exists always...
// TODO: problem, we have stored the new category in DB but can not create the new folder - so we have now two categories with the same folder path?
$result = false;
}
} else {
// Build the new folder path to move or rename the folder when needed
if ($root_id > 0 && (int) $this->parent_id !== $root_id){
$this->cat_dir_parent = $this->getParentCategoryPath($this->parent_id);
} else {
$this->cat_dir_parent = '';
}
if ($this->cat_dir_parent != ''){
$new_cat_dir_path = $root_dir_path.'/'.$this->cat_dir_parent.'/'.$this->cat_dir;
} else {
$new_cat_dir_path = $root_dir_path.'/'.$this->cat_dir;
}
// We need also the old folder path
$old_parent = $jinput->get('cat_dir_parent_org', '', 'string');
$old_dir = $jinput->get('cat_dir_org', '', 'string');
if ($old_parent != ''){
$old_cat_dir_path = $root_dir_path.'/'.$old_parent.'/'.$old_dir;
} else {
$old_cat_dir_path = $root_dir_path.'/'.$old_dir;
}
// Category is not new - so we must at first check, whether the title is changed.
if ($titleChanged || $cat_dir_changed_manually){
// Get the old and new cat dir and rename it
if (Folder::exists($old_cat_dir_path)){
$result = Folder::move($old_cat_dir_path, $new_cat_dir_path);
} else {
Factory::getApplication()->enqueueMessage( Text::sprintf('COM_JDOWNLOADS_CATSEDIT_ERROR_CHECK_FOLDER', $old_cat_dir_path ), 'warning');
$result = false;
}
}
// We must only check this, when the user have not changed the category title
// If so, we must move the category folder complete to the new position
if ($catChanged && !$titleChanged){
// Move it to the new location when exists
if (Folder::exists($old_cat_dir_path)){
$result = JDownloadsHelper::moveDirs($old_cat_dir_path.'/', $new_cat_dir_path.'/', $msg, true, true, false, false);
if ($result !== true) {
// $result has a error message from file/folder operations
Factory::getApplication()->enqueueMessage( $result, 'warning');
$result = false;
}
} else {
$result = false;
}
}
}
return $result;
}
// Check whether a category has children
// @return boolean True on success.
public function hasChildren($pk)
{
$query = $this->_db->getQuery(true);
$query->select('count(*)');
$query->from('#__jdownloads_categories');
$query->where('parent_id = '.(int)$pk);
$this->_db->setQuery($query);
if ($this->_db->loadResult() > 0){
return true;
} else {
return false;
}
}
// Check whether a category has downloads
// @return boolean True on success.
public function hasDownloads($pk)
{
$query = $this->_db->getQuery(true);
$query->select('count(*)');
$query->from('#__jdownloads_files');
$query->where('catid = '.(int)$pk);
$this->_db->setQuery($query);
if ($this->_db->loadResult() > 0){
return true;
} else {
return false;
}
}
// Get the path from a given parent_id
// @return path The folder path from the parent category
public function getParentCategoryPath($parent_id)
{
$catpath = '';
$query = $this->_db->getQuery(true);
$query->select('cat_dir, cat_dir_parent');
$query->from('#__jdownloads_categories');
$query->where('id = '.(int)$parent_id);
$this->_db->setQuery($query);
$path = $this->_db->loadObject();
if ($path->cat_dir_parent != ''){
$catpath = $path->cat_dir_parent.'/'.$path->cat_dir;
} else {
$catpath = $path->cat_dir;
}
return $catpath;
}
/**
* Overloaded bind function.
*
* @param array $array named array
* @param string $ignore An optional array or space separated list of properties
* to ignore while binding.
*
* @return mixed Null if operation was satisfactory, otherwise returns an error
*
* @see Table::bind
*/
public function bind($array, $ignore = '')
{
if (isset($array['params']) && is_array($array['params'])){
$registry = new Registry;
$registry->loadArray($array['params']);
$array['params'] = (string) $registry;
}
// Bind the rules.
if (isset($array['rules']) && is_array($array['rules'])){
$rules = new Rules($array['rules']);
$this->setRules($rules);
}
return parent::bind($array, $ignore);
}
/**
* Method to compute the default name of the asset.
* The default name is in the form `table_name.id`
* where id is the value of the primary key of the table.
*
* @return string
* @since 1.6
*/
protected function _getAssetName()
{
$k = $this->_tbl_key;
return 'com_jdownloads.category.'.(int) $this->$k;
}
/**
* Method to return the title to use for the asset table.
*
* @return string
* @since 1.6
*/
protected function _getAssetTitle()
{
return $this->title;
}
/**
* Get the parent asset id for the current category
* @param Table $table A Table object for the asset parent.
* @param integer $id Id to look up
*
* @return int The parent asset id for the category
*/
protected function _getAssetParentId(?Table $table = null, $id = null)
{
$assetId = null;
$root_id = (int) $this->getRootId();
$this->extension = 'com_jdownloads';
// This is a category under a category.
if ($root_id > 0 && (int) $this->parent_id !== $root_id){
// Build the query to get the asset id for the parent category.
$query = $this->_db->getQuery(true)
->select($this->_db->quoteName('asset_id'))
->from($this->_db->quoteName('#__jdownloads_categories'))
->where($this->_db->quoteName('id') . ' = :parentId')
->bind(':parentId', $this->parent_id, ParameterType::INTEGER);
// Get the asset id from the database.
$this->_db->setQuery($query);
if ($result = $this->_db->loadResult()){
$assetId = (int) $result;
}
} elseif ($assetId === null){
// This is a category that needs to parent with the extension.
// Build the query to get the asset id for the parent category.
$query = $this->_db->getQuery(true)
->select($this->_db->quoteName('id'))
->from($this->_db->quoteName('#__assets'))
->where($this->_db->quoteName('name') . ' = :extension')
->bind(':extension', $this->extension);
// Get the asset id from the database.
$this->_db->setQuery($query);
if ($result = $this->_db->loadResult()){
$assetId = (int) $result;
}
}
// Return the asset id.
if ($assetId){
return $assetId;
} else {
return parent::_getAssetParentId($table, $id);
}
}
/**
* Method to set the publishing state for a row or list of rows in the database
* table. The method respects checked out rows by other users and will attempt
* to checkin rows that it can after adjustments are made.
*
* @param mixed $pks An optional array of primary key values to update. If not set the instance property value is used.
* @param integer $state The publishing state. eg. [0 = unpublished, 1 = published]
* @param integer $userId The user id of the user performing the operation.
*
* @return boolean True on success.
*
*/
public function publish($pks = null, $state = 1, $userId = 0)
{
// Initialise variables.
$k = $this->_tbl_key;
$query = $this->_db->getQuery(true);
// Sanitize input.
$pks = ArrayHelper::toInteger($pks);
$userId = (int) $userId;
$state = (int) $state;
// If $state > 1, then we allow state changes even if an ancestor has lower state
// (for example, can change a child state to Archived (2) if an ancestor is Published (1) - not use in jD now
$compareState = ($state > 1) ? 1 : $state;
// If there are no primary keys set check to see if the instance key is set.
if (empty($pks)){
if ($this->$k){
$pks = explode(',', $this->$k);
} else {
// Nothing to set publishing state on, return false.
$e = new JException(Text::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));
$this->setError($e);
return false;
}
}
// Determine if there is checkout support for the table.
$checkoutSupport = (property_exists($this, 'checked_out') || property_exists($this, 'checked_out_time'));
// Iterate over the primary keys to execute the publish action if possible.
foreach ($pks as $pk){
if (!$node = $this->_getNode($pk)){
// Error message set in getNode method.
return false;
}
// If the table has checkout support, verify no children are checked out.
if ($checkoutSupport){
// Ensure that children are not checked out.
$query->clear()
->select('COUNT(' . $k . ')')
->from($this->_tbl)
->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt)
->where('(checked_out <> 0 AND checked_out <> ' . (int) $userId . ')');
$this->_db->setQuery($query);
// Check for checked out children.
if ($this->_db->loadResult()){
// TODO Convert to a conflict exception when available.
$e = new RuntimeException(sprintf('%s::publish(%s, %d, %d) checked-out conflict.', get_class($this), $pks, $state, $userId));
$this->setError($e);
return false;
}
}
// If any parent nodes have lower published state values, we cannot continue.
if ($node->parent_id){
// Get any ancestor nodes that have a lower publishing state.
$query->clear()
->select('n.' . $k)
->from($this->_db->quoteName($this->_tbl) . ' AS n')
->where('n.lft < ' . (int) $node->lft)
->where('n.rgt > ' . (int) $node->rgt)
->where('n.parent_id > 0')
->where('n.published < ' . (int) $compareState);
// Just fetch one row (one is one too many).
$this->_db->setQuery($query, 0, 1);
$rows = $this->_db->loadColumn();
if (!empty($rows)){
$e = new \UnexpectedValueException(
sprintf('%s::publish(%s, %d, %d) ancestors have lower state.', get_class($this), $pks, $state, $userId)
);
$this->setError($e);
return false;
}
}
// Update and cascade the publishing state.
$query->clear()
->update($this->_db->quoteName($this->_tbl))
->set('published = ' . (int) $state)
->where('(lft > ' . (int) $node->lft . ' AND rgt < ' . (int) $node->rgt . ') OR ' . $k . ' = ' . (int) $pk);
$this->_db->setQuery($query)->execute();
// If checkout support exists for the object, check the row in.
if ($checkoutSupport){
$this->checkin($pk);
}
}
// If the Table instance value is in the list of primary keys that were set, set the instance.
if (in_array($this->$k, $pks)){
$this->published = $state;
}
$this->setError('');
return true;
}
/**
* Get the type alias for the history table
*
* @return string The alias as described above
*
* @since 4.0.0
*/
public function getTypeAlias()
{
return 'com_jdownloads.category';
}
}
?>