public bool
moveByReference
(mixed $referenceId, mixed $position = 'after', mixed $pk = null, mixed $recursiveUpdate = true)
/**
* 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.
* @param boolean $recursiveUpdate Flag indicate that method recursiveUpdatePublishedColumn should be call.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \RuntimeException on database error.
*/
public function moveByReference($referenceId, $position = 'after', $pk = null, $recursiveUpdate = true)
{
if ($this->_debug) {
echo "\nMoving ReferenceId:{$referenceId}, Position:{$position}, PK:{$pk}";
}
$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)->select($k)->from($this->_tbl)->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
$children = $this->_db->setQuery($query)->loadColumn();
if ($this->_debug) {
$this->_logtable(false);
}
// Cannot move the node to be a child of itself.
if (\in_array($referenceId, $children)) {
$this->setError(new \UnexpectedValueException(sprintf('%1$s::moveByReference() is trying to make record ID %2$d a child of itself.', \get_class($this), $pk)));
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->clear()->update($this->_tbl)->set('lft = lft * (-1), rgt = rgt * (-1)')->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->clear()->update($this->_tbl)->set('lft = lft - ' . (int) $node->width)->where('lft > ' . (int) $node->rgt);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
// Compress the right values.
$query->clear()->update($this->_tbl)->set('rgt = rgt - ' . (int) $node->width)->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 {
// Get the last root node as the reference node.
$query->clear()->select($this->_tbl_key . ', parent_id, level, lft, rgt')->from($this->_tbl)->where('parent_id = 0')->order('lft DESC');
$query->setLimit(1);
$this->_db->setQuery($query);
$reference = $this->_db->loadObject();
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->clear()->update($this->_tbl)->set('lft = lft + ' . (int) $node->width)->where($repositionData->left_where);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
// Shift right values.
$query->clear()->update($this->_tbl)->set('rgt = rgt + ' . (int) $node->width)->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->clear()->update($this->_tbl)->set('rgt = ' . (int) $offset . ' - rgt')->set('lft = ' . (int) $offset . ' - lft')->set('level = level + ' . (int) $levelOffset)->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)->update($this->_tbl);
// Update the title and alias fields if they exist for the table.
$fields = $this->getFields();
if ($this->hasField('title') && $this->title !== null) {
$query->set('title = ' . $this->_db->quote($this->title));
}
if (\array_key_exists('alias', $fields) && $this->alias !== null) {
$query->set('alias = ' . $this->_db->quote($this->alias));
}
$query->set('parent_id = ' . (int) $repositionData->new_parent_id)->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();
if ($this->hasField('published') && $recursiveUpdate) {
$this->recursiveUpdatePublishedColumn($node->{$k});
}
// 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;
}