Source for file NestedSet.php
Documentation is available at NestedSet.php
// +----------------------------------------------------------------------+
// | PEAR :: DB_NestedSet |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Daniel Khan <dk@webcluster.at> |
// | Jason Rust <jason@rustyparts.com> |
// +----------------------------------------------------------------------+
// $Id: NestedSet.php,v 1.79 2004/04/02 00:15:05 datenpunk Exp $
// - Thanks to Kristian Koehntopp for publishing an explanation of the Nested Set
// technique and for the great work he did and does for the php community
// - Thanks to Daniel T. Gorski for his great tutorial on www.develnet.org
// - Thanks to my parents for ... just kidding :]
// Error and message codes
define('NESE_ERROR_RECURSION', 'E100');
define('NESE_ERROR_NODRIVER', 'E200');
define('NESE_ERROR_NOHANDLER', 'E300');
define('NESE_ERROR_TBLOCKED', 'E010');
define('NESE_MESSAGE_UNKNOWN', 'E0');
define('NESE_ERROR_NOTSUPPORTED', 'E1');
define('NESE_ERROR_PARAM_MISSING', 'E400');
define('NESE_ERROR_NOT_FOUND', 'E500');
define('NESE_ERROR_WRONG_MPARAM', 'E2');
// for moving a node before another
define('NESE_MOVE_BEFORE', 'BE');
// for moving a node after another
define('NESE_MOVE_AFTER', 'AF');
// for moving a node below another
define('NESE_MOVE_BELOW', 'SUB');
define('NESE_SORT_LEVEL', 'SLV');
define('NESE_SORT_PREORDER', 'SPO');
// {{{ DB_NestedSet:: class
* DB_NestedSet is a class for handling nested sets
* @author Daniel Khan <dk@webcluster.at>
* @version $Revision: 1.79 $
* @var array The field parameters of the table with the nested set. Format: 'realFieldName' => 'fieldId'
var $params = array ('STRID' => 'id',
// 'parent'=>'parent', // Optional but very useful
// To be used with 2.0 - would be an api break atm
// var $quotedParams = array('name');
* @var string The table with the actual tree data
* @var string The table to handle locking
* @var string The table used for sequences
* Secondary order field. Normally this is the order field, but can be changed to
* something else (i.e. the name field so that the tree can be shown alphabetically)
* Used to store the secondary sort method set by the user while doing manipulative queries
var $_userSecondarySort = false;
* The default sorting field - will be set to the table column inside the constructor
var $_defaultSecondarySort = 'norder';
* @var int The time to live of the lock
* @var bool Enable debugging statements?
* @var bool Lock the structure of the table?
var $_structureTableLock = false;
* @var bool Don't allow unlocking (used inside of moves)
var $_lockExclusive = false;
* @var object cache Optional PEAR::Cache object
* Specify the sortMode of the query methods
* NESE_SORT_LEVEL is the 'old' sorting method and sorts a tree by level
* all nodes of level 1, all nodes of level 2,...
* NESE_SORT_PREORDER will sort doing a preorder walk.
* So all children of node x will come right after it
* Note that moving a node within it's siblings will obviously not change the output
* @var constant Order method (NESE_SORT_LEVEL|NESE_SORT_PREORDER)
var $_sortMode = NESE_SORT_LEVEL;
* @var array Available sortModes
var $_sortModes = array (NESE_SORT_LEVEL , NESE_SORT_PREORDER );
* @var array An array of field ids that must exist in the table
var $_requiredParams = array ('id', 'rootid', 'l', 'r', 'norder', 'level');
* @var bool Skip the callback events?
var $_skipCallbacks = false;
* @var bool Do we want to use caching
* @var array The above parameters flipped for easy access
* @var bool Temporary switch for cache
* Used to determine the presence of listeners for an event in triggerEvent()
* If any event listeners are registered for an event, the event name will
* have a key set in this array, otherwise, it will not be set.
var $_hasListeners = array ();
* @var string packagename
var $_packagename = 'DB_NestedSet';
* @var string Minorversion
var $_minorversion = '3';
* @var array Used for mapping a cloned tree to the real tree for move_* operations
var $_relations = array ();
* Used for _internal_ tree conversion
* @var bool Turn off user param verification and id generation
* @var array Map of error messages to their descriptions
NESE_ERROR_RECURSION => '%s: This operation would lead to a recursion',
NESE_ERROR_TBLOCKED => 'The structure Table is locked for another database operation, please retry.',
NESE_ERROR_NODRIVER => 'The selected database driver %s wasn\'t found',
NESE_ERROR_NOTSUPPORTED => 'Method not supported yet',
NESE_ERROR_NOHANDLER => 'Event handler not found',
NESE_ERROR_PARAM_MISSING => 'Parameter missing',
NESE_MESSAGE_UNKNOWN => 'Unknown error or message',
NESE_ERROR_NOT_FOUND => '%s: Node %s not found',
NESE_ERROR_WRONG_MPARAM => '%s: %s'
* @var array The array of event listeners
var $eventListeners = array ();
// +---------------------------------------+
// +---------------------------------------+
* @param array $params Database column fields which should be returned
function DB_NestedSet ($params) {
$this->_debugMessage ('DB_NestedSet()');
$this->secondarySort = $this->flparams[$this->_defaultSecondarySort];
* Closes open database connections
function _DB_NestedSet () {
$this->_debugMessage ('_DB_NestedSet()');
$this->_releaseLock (true );
* Handles the returning of a concrete instance of DB_NestedSet based on the driver.
* If the class given by $driver allready exists it will be used.
* If not the driver will be searched inside the default path ./NestedSet/
* @param string $driver The driver, such as DB or MDB
* @param string $dsn The dsn for connecting to the database
* @param array $params The field name params for the node table
* @return object The DB_NestedSet object
function & factory($driver, $dsn, $params = array ()) {
$classname = 'DB_NestedSet_' . $driver;
$driverpath = dirname(__FILE__ ) . '/NestedSet/' . $driver . '.php';
return PEAR ::raiseError (" factory(): The database driver '$driver' wasn't found" , NESE_ERROR_NODRIVER, PEAR_ERROR_TRIGGER , E_USER_ERROR );
include_once($driverpath);
$c = & new $classname($dsn, $params);
// +----------------------------------------------+
// | NestedSet manipulation and query methods |
// |----------------------------------------------+
// +----------------------------------------------+
* Fetch the whole NestedSet
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
* @return mixed False on error, or an array of nodes
function getAllNodes($keepAsArray = false , $aliasFields = true , $addSQL = array ()) {
$this->_debugMessage ('getAllNodes()');
$sql = sprintf('SELECT %s %s FROM %s %s %s %s ORDER BY %s.%s, %s.%s ASC',
$this->_getSelectFields ($aliasFields),
$this->_addSQL ($addSQL, 'cols'),
$this->_addSQL ($addSQL, 'join'),
$this->_addSQL ($addSQL, 'where', 'WHERE'),
$this->_addSQL ($addSQL, 'append'),
$this->flparams['level'],
foreach($rootnodes AS $rid => $rootnode) {
$nodeSet = $nodeSet + $this->getBranch($rootnode, $keepAsArray, $aliasFields, $addSQL);
$nodeSet = $this->_processResultSet ($sql, $keepAsArray, $aliasFields);
$nodeSet = $this->cache->call ('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeLoad'])) {
* Fetches the first level (the rootnodes) of the NestedSet
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
* @return mixed False on error, or an array of nodes
function getRootNodes($keepAsArray = false , $aliasFields = true , $addSQL = array ()) {
$this->_debugMessage ('getRootNodes()');
$sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s.%s %s %s ORDER BY %s.%s ASC',
$this->_getSelectFields ($aliasFields),
$this->_addSQL ($addSQL, 'cols'),
$this->_addSQL ($addSQL, 'join'),
$this->flparams['rootid'],
$this->_addSQL ($addSQL, 'where', 'AND'),
$this->_addSQL ($addSQL, 'append'),
$nodeSet = $this->_processResultSet ($sql, $keepAsArray, $aliasFields);
$nodeSet = $this->cache->call ('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeLoad'])) {
* Fetch the whole branch where a given node id is in
* @param int $id The node ID
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
* @return mixed False on error, or an array of nodes
function getBranch($id, $keepAsArray = false , $aliasFields = true , $addSQL = array ()) {
$this->_debugMessage ('getBranch($id)');
if (!($thisnode = $this->pickNode($id, true ))) {
$epr = array ('getBranch()', $id);
$firstsort = $this->flparams['level'];
$sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s %s %s ORDER BY %s.%s, %s.%s ASC',
$this->_getSelectFields ($aliasFields),
$this->_addSQL ($addSQL, 'cols'),
$this->_addSQL ($addSQL, 'join'),
$this->flparams['rootid'],
$this->_addSQL ($addSQL, 'where', 'AND'),
$this->_addSQL ($addSQL, 'append'),
$firstsort = $this->flparams['l'];
$sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s %s %s ORDER BY %s.%s ASC',
$this->_getSelectFields ($aliasFields),
$this->_addSQL ($addSQL, 'cols'),
$this->_addSQL ($addSQL, 'join'),
$this->flparams['rootid'],
$this->_addSQL ($addSQL, 'where', 'AND'),
$this->_addSQL ($addSQL, 'append'),
$nodeSet = $this->_processResultSet ($sql, $keepAsArray, $aliasFields);
$nodeSet = $this->cache->call ('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeLoad'])) {
uasort($nodeSet, array ($this, '_secSort'));
* Fetch the parents of a node given by id
* @param int $id The node ID
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
* @return mixed False on error, or an array of nodes
function getParents($id, $keepAsArray = false , $aliasFields = true , $addSQL = array ()) {
$this->_debugMessage ('getParents($id)');
if (!($child = $this->pickNode($id, true ))) {
$epr = array ('getParents()', $id);
$sql = sprintf('SELECT %s %s FROM %s %s
WHERE %s.%s=%s AND %s.%s<%s AND %s.%s<%s AND %s.%s>%s %s %s
$this->_getSelectFields ($aliasFields),
$this->_addSQL ($addSQL, 'cols'),
$this->_addSQL ($addSQL, 'join'),
$this->flparams['rootid'],
$this->flparams['level'],
$this->_addSQL ($addSQL, 'where', 'AND'),
$this->_addSQL ($addSQL, 'append'),
$this->flparams['level']);
$nodeSet = $this->_processResultSet ($sql, $keepAsArray, $aliasFields);
$nodeSet = $this->cache->call ('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeLoad'])) {
* Fetch the immediate parent of a node given by id
* @param int $id The node ID
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
* @return mixed False on error, or the parent node
function getParent($id, $keepAsArray = false , $aliasFields = true , $addSQL = array (), $useDB = true ) {
$this->_debugMessage ('getParent($id)');
if (!($child = $this->pickNode($id, true ))) {
$epr = array ('getParent()', $id);
if ($child['id'] == $child['rootid']) {
// If parent node is set inside the db simply return it
if (isset ($child['parent']) && !empty ($child['parent']) && ($useDB == true )) {
return $this->pickNode($child['parent'], $keepAsArray, $aliasFields, 'id', $addSQL);
$addSQL['where'] = sprintf('%s.%s = %s',
$this->flparams['level'],
$nodeSet = $this->getParents($id, $keepAsArray, $aliasFields, $addSQL);
return $nodeSet[$keys[0 ]];
* Fetch all siblings of the node given by id
* Important: The node given by ID will also be returned
* Do a unset($array[$id]) on the result if you don't want that
* @param int $id The node ID
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
* @return mixed False on error, or the parent node
function getSiblings($id, $keepAsArray = false , $aliasFields = true , $addSQL = array ()) {
$this->_debugMessage ('getSiblings($id)');
if (!($sibling = $this->pickNode($id, true ))) {
$epr = array ('getSibling()', $id);
return $this->getChildren($parent, $keepAsArray, $aliasFields, false , $addSQL);
* Fetch the children _one level_ after of a node given by id
* @param int $id The node ID
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param bool $forceNorder (optional) Force the result to be ordered by the norder
* param (as opposed to the value of secondary sort). Used by the move and
* @param array $addSQL (optional) Array of additional params to pass to the query.
* @return mixed False on error, or an array of nodes
function getChildren($id, $keepAsArray = false , $aliasFields = true , $forceNorder = false , $addSQL = array ()) {
$this->_debugMessage ('getChildren($id)');
if (!($parent = $this->pickNode($id, true ))) {
$epr = array ('getChildren()', $id);
if (!$parent || $parent['l'] == ($parent['r'] - 1 )) {
$sql = sprintf('SELECT %s %s FROM %s %s
WHERE %s.%s=%s AND %s.%s=%s+1 AND %s.%s BETWEEN %s AND %s %s %s
$this->_getSelectFields ($aliasFields), $this->_addSQL ($addSQL, 'cols'),
$this->node_table, $this->_addSQL ($addSQL, 'join'),
$this->node_table, $this->flparams['rootid'], $parent['rootid'],
$this->node_table, $this->flparams['level'], $parent['level'],
$this->node_table, $this->flparams['l'], $parent['l'], $parent['r'],
$this->_addSQL ($addSQL, 'where', 'AND'),
$this->_addSQL ($addSQL, 'append'),
$nodeSet = $this->_processResultSet ($sql, $keepAsArray, $aliasFields);
$nodeSet = $this->cache->call ('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeLoad'])) {
* Fetch all the children of a node given by id
* getChildren only queries the immediate children
* getSubBranch returns all nodes below the given node
* @param string $id The node ID
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
* @return mixed False on error, or an array of nodes
function getSubBranch($id, $keepAsArray = false , $aliasFields = true , $addSQL = array ()) {
$this->_debugMessage ('getSubBranch($id)');
if (!($parent = $this->pickNode($id, true ))) {
$epr = array ('getSubBranch()', $id);
$firstsort = $this->flparams['level'];
$sql = sprintf('SELECT %s %s FROM %s %s
WHERE %s.%s BETWEEN %s AND %s AND %s.%s=%s AND %s.%s!=%s %s %s
ORDER BY %s.%s, %s.%s ASC',
$this->_getSelectFields ($aliasFields), $this->_addSQL ($addSQL, 'cols'),
$this->node_table, $this->_addSQL ($addSQL, 'join'),
$this->node_table, $this->flparams['l'], $parent['l'], $parent['r'],
$this->node_table, $this->flparams['rootid'], $parent['rootid'],
$this->node_table, $this->flparams['id'], $id, $this->_addSQL ($addSQL, 'where', 'AND'), $this->_addSQL ($addSQL, 'append'),
$firstsort = $this->flparams['l'];
$sql = sprintf('SELECT %s %s FROM %s %s
WHERE %s.%s BETWEEN %s AND %s AND %s.%s=%s AND %s.%s!=%s %s %s
$this->_getSelectFields ($aliasFields), $this->_addSQL ($addSQL, 'cols'),
$this->_addSQL ($addSQL, 'join'),
$this->flparams['rootid'],
$this->_addSQL ($addSQL, 'where', 'AND'),
$this->_addSQL ($addSQL, 'append'),
$nodeSet = $this->_processResultSet ($sql, $keepAsArray, $aliasFields);
$nodeSet = $this->cache->call ('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeLoad'])) {
uasort($nodeSet, array ($this, '_secSort'));
* Fetch the data of a node with the given id
* @param int $id The node id of the node to fetch
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param string $idfield (optional) Which field has to be compared with $id?
* This is can be used to pick a node by other values (e.g. it's name).
* @param array $addSQL (optional) Array of additional params to pass to the query.
* @return mixed False on error, or an array of nodes
function pickNode($id, $keepAsArray = false , $aliasFields = true , $idfield = 'id', $addSQL = array ()) {
$this->_debugMessage ('pickNode($id)');
} elseif (is_array($id) && isset ($id['id'])) {
$sql = sprintf("SELECT %s %s FROM %s %s WHERE %s.%s=%s %s %s",
$this->_getSelectFields ($aliasFields), $this->_addSQL ($addSQL, 'cols'),
$this->node_table, $this->_addSQL ($addSQL, 'join'),
$this->node_table, $this->flparams[$idfield], $this->_quote ($id),
$this->_addSQL ($addSQL, 'where', 'AND'),
$this->_addSQL ($addSQL, 'append'));
$nodeSet = $this->_processResultSet ($sql, $keepAsArray, $aliasFields);
$nodeSet = $this->cache->call ('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeLoad'])) {
if (is_array($nodeSet) && $idfield != 'id') {
return isset ($nodeSet[$id]) ? $nodeSet[$id] : false;
* See if a given node is a parent of another given node
* A node is considered to be a parent if it resides above the child
* So it doesn't mean that the node has to be an immediate parent.
* To get this information simply compare the levels of the two nodes
* after you know that you have a parent relation.
* @param mixed $parent The parent node as array or object
* @param mixed $child The child node as array or object
* @return bool True if it's a parent
$this->_debugMessage ('isParent($parent, $child)');
if (!isset ($parent) || !isset ($child)) {
$p_rootid = $parent['rootid'];
$p_rootid = $parent->rootid;
$c_rootid = $child['rootid'];
$c_rootid = $child->rootid;
if (($p_rootid == $c_rootid) && ($p_l < $c_l && $p_r > $c_r)) {
// +----------------------------------------------+
// | NestedSet manipulation and query methods |
// |----------------------------------------------+
// | insert / delete / update of nodes |
// +----------------------------------------------+
// +----------------------------------------------+
* Creates a new root node. If no id is specified then it is either
* added to the beginning/end of the tree based on the $pos.
* Optionally it deletes the whole tree and creates one initial rootnode
* @param array $values Hash with param => value pairs of the node (see $this->params)
* @param integer $id ID of target node (the rootnode after which the node should be inserted)
* @param bool $first Danger: Deletes and (re)init's the hole tree - sequences are reset
* @param string $pos The position in which to insert the new node.
* @return mixed The node id or false on error
function createRootNode($values, $id = false , $first = false , $pos = NESE_MOVE_AFTER ) {
$this->_debugMessage ('createRootNode($values, $id = false, $first = false, $pos = \'AF\')');
$this->_verifyUserValues ('createRootNode()', $values);
// If they specified an id, see if the parent is valid
if (!$first && ($id && !$parent = $this->pickNode($id, true ))) {
$epr = array ('createRootNode()', $id);
} elseif ($first && $id) {
// No notice for now. But these 2 params don't make sense together
$epr = array ('createRootNode()', '[id] AND [first] were passed - that doesn\'t make sense');
// $this->_raiseError(NESE_ERROR_WRONG_MPARAM, E_USER_WARNING, $epr);
} elseif (!$first && !$id) {
// If no id was specified, then determine order
// Put it at the end of the tree
$qry = sprintf('SELECT MAX(%s) FROM %s WHERE %s=1',
$this->flparams['norder'],
$tmp_order = $this->db->getOne ($qry);
// If null, then it's the first one
$parent['norder'] = is_null($tmp_order) ? 0 : $tmp_order;
// Try to aquire a table lock
if (PEAR ::isError ($lock = $this->_setLock ())) {
$addval[$this->flparams['level']] = 1;
// Shall we delete the existing tree (reinit)
// New order of the new node will be 1
$addval[$this->flparams['norder']] = 1;
// Let's open a gap for the new node
$addval[$this->flparams['norder']] = $parent['norder'] + 1;
$sql[] = sprintf('UPDATE %s SET %s=%s+1 WHERE %s=1 AND %s > %s',
$this->flparams['norder'], $this->flparams['norder'],
$this->flparams['norder'], $parent['norder']);
$addval[$this->flparams['norder']] = $parent['norder'];
$sql[] = sprintf('UPDATE %s SET %s=%s+1 WHERE %s=1 AND %s >= %s',
$this->flparams['norder'], $this->flparams['norder'],
$this->flparams['norder'], $parent['norder']);
if (isset ($this->flparams['parent'])) {
$addval[$this->flparams['parent']] = 0;
// Sequence of node id (equals to root id in this case
if (!$this->_dumbmode || !$node_id = isset ($values[$this->flparams['id']]) || !isset ($values[$this->flparams['rootid']])) {
$addval[$this->flparams['rootid']] = $node_id = $addval[$this->flparams['id']] = $this->db->nextId ($this->sequence_table);
$node_id = $values[$this->flparams['id']];
// Left/Right values for rootnodes
$addval[$this->flparams['l']] = 1;
$addval[$this->flparams['r']] = 2;
// Transform the node data hash to a query
if (!$qr = $this->_values2InsertQuery ($values, $addval)) {
$res = $this->db->query ($qry);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeCreate'])) {
* @param integer $id Parent node ID
* @param array $values Hash with param => value pairs of the node (see $this->params)
* @return mixed The node id or false on error
$this->_debugMessage ('createSubNode($id, $values)');
// invalid parent id, bail out
if (!($thisnode = $this->pickNode($id, true ))) {
$epr = array ('createSubNode()', $id);
// Try to aquire a table lock
if (PEAR ::isError ($lock = $this->_setLock ())) {
$this->_verifyUserValues ('createRootNode()', $values);
// Get the children of the target node
if ($thisnode['r']-1 != $thisnode['l']) {
// What we have to do is virtually an insert of a node after the last child
// So we don't have to proceed creating a subnode
%s=CASE WHEN %s>%s THEN %s+2 ELSE %s END,
%s=CASE WHEN (%s>%s OR %s>=%s) THEN %s+2 ELSE %s END
$this->flparams['l'], $thisnode['l'],
$this->flparams['l'], $this->flparams['l'],
$this->flparams['l'], $thisnode['l'],
$this->flparams['r'], $thisnode['r'],
$this->flparams['r'], $this->flparams['r'],
$this->flparams['rootid'], $thisnode['rootid']);
if (isset ($this->flparams['parent'])) {
$addval[$this->flparams['parent']] = $thisnode['id'];
$addval[$this->flparams['l']] = $thisnode['r'];
$addval[$this->flparams['r']] = $thisnode['r'] + 1;
$addval[$this->flparams['rootid']] = $thisnode['rootid'];
$addval[$this->flparams['norder']] = 1;
$addval[$this->flparams['level']] = $thisnode['level'] + 1;
if (!$this->_dumbmode || !$node_id = isset ($values[$this->flparams['id']])) {
$node_id = $addval[$this->flparams['id']] = $this->db->nextId ($this->sequence_table);
$node_id = $values[$this->flparams['id']];
if (!$qr = $this->_values2InsertQuery ($values, $addval)) {
$res = $this->db->query ($qry);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeCreate'])) {
* Creates a node before a given node
* | |-- subnode1 [target]
* @param int $id Target node ID
* @param array $values Hash with param => value pairs of the node (see $this->params)
* @param bool $returnID Tell the method to return a node id instead of an object.
* ATTENTION: That the method defaults to return an object instead of the node id
* has been overseen and is basically a bug. We have to keep this to maintain BC.
* You will have to set $returnID to true to make it behave like the other creation methods.
* This flaw will get fixed with the next major version.
* @return mixed The node id or false on error
$this->_debugMessage ('createLeftNode($target, $values)');
$this->_verifyUserValues ('createLeftode()', $values);
// invalid target node, bail out
if (!($thisnode = $this->pickNode($id, true ))) {
$epr = array ('createLeftNode()', $id);
if (PEAR ::isError ($lock = $this->_setLock ())) {
// If the target node is a rootnode we virtually want to create a new root node
if ($thisnode['rootid'] == $thisnode['id']) {
if (isset ($this->flparams['parent'])) {
$addval[$this->flparams['parent']] = $parent['id'];
$sql[] = sprintf('UPDATE %s SET %s=%s+1
WHERE %s=%s AND %s>=%s AND %s=%s AND %s BETWEEN %s AND %s',
$this->flparams['norder'], $this->flparams['norder'],
$this->flparams['rootid'], $thisnode['rootid'],
$this->flparams['norder'], $thisnode['norder'],
$this->flparams['level'], $thisnode['level'],
$this->flparams['l'], $parent['l'], $parent['r']);
// Update all nodes which have dependent left and right values
%s=CASE WHEN %s>=%s THEN %s+2 ELSE %s END,
%s=CASE WHEN (%s>=%s OR %s>=%s) THEN %s+2 ELSE %s END
$this->flparams['l'], $thisnode['l'],
$this->flparams['l'], $this->flparams['l'],
$this->flparams['r'], $thisnode['r'],
$this->flparams['l'], $thisnode['l'],
$this->flparams['r'], $this->flparams['r'],
$this->flparams['rootid'], $thisnode['rootid']);
$addval[$this->flparams['norder']] = $thisnode['norder'];
$addval[$this->flparams['l']] = $thisnode['l'];
$addval[$this->flparams['r']] = $thisnode['l'] + 1;
$addval[$this->flparams['rootid']] = $thisnode['rootid'];
$addval[$this->flparams['level']] = $thisnode['level'];
if (!$this->_dumbmode || !$node_id = isset ($values[$this->flparams['id']])) {
$node_id = $addval[$this->flparams['id']] = $this->db->nextId ($this->sequence_table);
$node_id = $values[$this->flparams['id']];
if (!$qr = $this->_values2InsertQuery ($values, $addval)) {
$res = $this->db->query ($qry);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeCreate'])) {
* Creates a node after a given node
* | |-- subnode1 [target]
* @param int $id Target node ID
* @param array $values Hash with param => value pairs of the node (see $this->params)
* @param bool $returnID Tell the method to return a node id instead of an object.
* ATTENTION: That the method defaults to return an object instead of the node id
* has been overseen and is basically a bug. We have to keep this to maintain BC.
* You will have to set $returnID to true to make it behave like the other creation methods.
* This flaw will get fixed with the next major version.
* @return mixed The node id or false on error
$this->_debugMessage ('createRightNode($target, $values)');
$this->_verifyUserValues ('createRootNode()', $values);
// invalid target node, bail out
if (!($thisnode = $this->pickNode($id, true ))) {
$epr = array ('createRightNode()', $id);
if (PEAR ::isError ($lock = $this->_setLock ())) {
// If the target node is a rootnode we virtually want to create a new root node
if ($thisnode['rootid'] == $thisnode['id']) {
if (isset ($this->flparams['parent'])) {
$addval[$this->flparams['parent']] = $parent['id'];
$sql[] = sprintf('UPDATE %s SET %s=%s+1
WHERE %s=%s AND %s>%s AND %s=%s AND %s BETWEEN %s AND %s',
$this->flparams['norder'], $this->flparams['norder'],
$this->flparams['rootid'], $thisnode['rootid'],
$this->flparams['norder'], $thisnode['norder'],
$this->flparams['level'], $thisnode['level'],
$this->flparams['l'], $parent['l'], $parent['r']);
// Update all nodes which have dependent left and right values
%s=CASE WHEN (%s>%s AND %s>%s) THEN %s+2 ELSE %s END,
%s=CASE WHEN %s>%s THEN %s+2 ELSE %s END
$this->flparams['l'], $thisnode['l'],
$this->flparams['r'], $thisnode['r'],
$this->flparams['l'], $this->flparams['l'],
$this->flparams['r'], $thisnode['r'],
$this->flparams['r'], $this->flparams['r'],
$this->flparams['rootid'], $thisnode['rootid']);
$addval[$this->flparams['norder']] = $thisnode['norder'] + 1;
$addval[$this->flparams['l']] = $thisnode['r'] + 1;
$addval[$this->flparams['r']] = $thisnode['r'] + 2;
$addval[$this->flparams['rootid']] = $thisnode['rootid'];
$addval[$this->flparams['level']] = $thisnode['level'];
if (!$this->_dumbmode || !isset ($values[$this->flparams['id']])) {
$node_id = $addval[$this->flparams['id']] = $this->db->nextId ($this->sequence_table);
$node_id = $values[$this->flparams['id']];
if (!$qr = $this->_values2InsertQuery ($values, $addval)) {
$res = $this->db->query ($qry);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeCreate'])) {
* @param int $id ID of the node to be deleted
* @return bool True if the delete succeeds
$this->_debugMessage (" deleteNode($id)" );
// invalid target node, bail out
if (!($thisnode = $this->pickNode($id, true ))) {
$epr = array ('deleteNode()', $id);
if (PEAR ::isError ($lock = $this->_setLock ())) {
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeDelete'])) {
$len = $thisnode['r'] - $thisnode['l'] + 1;
$sql[] = sprintf('DELETE FROM %s WHERE %s BETWEEN %s AND %s AND %s=%s',
$this->flparams['l'], $thisnode['l'], $thisnode['r'],
$this->flparams['rootid'], $thisnode['rootid']);
if ($thisnode['id'] != $thisnode['rootid']) {
// The node isn't a rootnode so close the gap
%s=CASE WHEN %s>%s THEN %s-%s ELSE %s END,
%s=CASE WHEN %s>%s THEN %s-%s ELSE %s END
WHERE %s=%s AND (%s>%s OR %s>%s)',
$this->flparams['l'], $thisnode['l'],
$this->flparams['l'], $len, $this->flparams['l'],
$this->flparams['r'], $thisnode['l'],
$this->flparams['r'], $len, $this->flparams['r'],
$this->flparams['rootid'], $thisnode['rootid'],
$this->flparams['l'], $thisnode['l'],
$this->flparams['r'], $thisnode['r']);
$sql[] = sprintf('UPDATE %s SET %s=%s-1
WHERE %s=%s AND %s=%s AND %s>%s AND %s BETWEEN %s AND %s',
$this->flparams['norder'], $this->flparams['norder'],
$this->flparams['rootid'], $thisnode['rootid'],
$this->flparams['level'], $thisnode['level'],
$this->flparams['norder'], $thisnode['norder'],
$this->flparams['l'], $parent['l'], $parent['r']);
// A rootnode was deleted and we only have to close the gap inside the order
$sql[] = sprintf('UPDATE %s SET %s=%s-1 WHERE %s=%s AND %s > %s',
$this->flparams['norder'], $this->flparams['norder'],
$this->flparams['rootid'], $this->flparams['id'],
$this->flparams['norder'], $thisnode['norder']);
$res = $this->db->query ($qry);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
* Changes the payload of a node
* @param array $values Hash with param => value pairs of the node (see $this->params)
* @param bool $_intermal Internal use only. Used to skip value validation. Leave this as it is.
* @return bool True if the update is successful
function updateNode($id, $values, $_internal = false ) {
$this->_debugMessage ('updateNode($id, $values)');
if (PEAR ::isError ($lock = $this->_setLock ())) {
$this->_verifyUserValues ('createRootNode()', $values);
$eparams = array ('values' => $values);
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeUpdate'])) {
if (!$qr = $this->_values2UpdateQuery ($values, $addvalues)) {
$sql = sprintf('UPDATE %s SET %s WHERE %s=%s',
$this->flparams['id'], $id);
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
// +----------------------------------------------+
// | Moving and copying |
// |----------------------------------------------+
// +----------------------------------------------+
* Wrapper for node moving and copying
* @param int $id Source ID
* @param int $target Target ID
* @param constant $pos Position (use one of the NESE_MOVE_* constants)
* @param bool $copy Shall we create a copy
* @return int ID of the moved node or false on error
function moveTree($id, $targetid, $pos, $copy = false ) {
$this->_debugMessage ('moveTree($id, $target, $pos, $copy = false)');
if ($id == $targetid && !$copy) {
$epr = array ('moveTree()');
// Get information about source and target
if (!($source = $this->pickNode($id, true ))) {
$epr = array ('moveTree()', $id);
if (!($target = $this->pickNode($targetid, true ))) {
$epr = array ('moveTree()', $targetid);
if (PEAR ::isError ($lock = $this->_setLock (true ))) {
$this->_relations = array ();
// This operations don't need callbacks except the copy handler
// which ignores this setting
$this->_skipCallbacks = true;
// We have a recursion - let's stop
if (($target['rootid'] == $source['rootid']) &&
(($source['l'] <= $target['l']) &&
($source['r'] >= $target['r']))) {
$this->_releaseLock (true );
$epr = array ('moveTree()');
// Insert/move before or after
if (($source['rootid'] == $source['id']) &&
// We have to move a rootnode which is different from moving inside a tree
$nid = $this->_moveRoot2Root ($source, $target, $pos);
$this->_releaseLock (true );
} elseif (($target['rootid'] == $source['rootid']) &&
(($source['l'] < $target['l']) &&
($source['r'] > $target['r']))) {
$this->_releaseLock (true );
$epr = array ('moveTree()');
// We have to move between different levels and maybe subtrees - let's rock ;)
$moveID = $this->_moveAcross ($source, $target, $pos, true );
$this->_moveCleanup ($copy);
$this->_releaseLock (true );
* Moves nodes and trees to other subtrees or levels
* [+] <--------------------------------+
* <-------------------------+ |p
* | |-- subnode1 [target] | |B
* | |-- subnode2 [new] |S |E
* |-\ subnode 3.2 [source] >--+------+
* @param object $ NodeCT $source Source node
* @param object $ NodeCT $target Target node
* @param string $pos Position [SUBnode/BEfore]
* @param bool $copy Shall we create a copy
function _moveAcross ($source, $target, $pos, $first = false ) {
$this->_debugMessage ('_moveAcross($source, $target, $pos, $copy = false)');
// Get the current data from a node and exclude the id params which will be changed
// because of the node move
foreach($this->params as $key => $val) {
if ($source[$val] && $val != 'parent' && !in_array($val, $this->_requiredParams)) {
$values[$key] = trim($source[$val]);
if ($first && isset ($this->flparams['parent'])) {
$t_parent = $this->getParent($clone_id, true , true , array (), false );
$t_parent_id = $t_parent['id'];
} elseif (isset ($this->flparams['parent'])) {
$t_parent_id = $source['parent'];
$children = $this->getChildren($source['id'], true , true , true );
// Recurse through the child nodes
foreach($children AS $cid => $child) {
$sclone = $this->pickNode($sclone_id, true );
$sclone_id = $this->_moveAcross ($child, $sclone, $pos);
$this->_relations[$source['id']]['clone'] = $clone_id;
$this->_relations[$source['id']]['parent'] = $t_parent_id;
* Deletes the old subtree (node) and writes the node id's into the cloned tree
* @param array $relations Hash in der Form $h[alteid]=neueid
* @param array $copy Are we in copy mode?
function _moveCleanup ($copy = false ) {
$relations = $this->_relations;
$this->_debugMessage ('_moveCleanup($relations, $copy = false)');
$fid = $this->flparams['id'];
$froot = $this->flparams['rootid'];
foreach($relations AS $key => $val) {
$cloneid = $val['clone'];
$parentID = $val['parent'];
$eparams = array ('clone' => $clone);
if (!$this->_skipCallbacks && isset ($this->_hasListeners['nodeCopy'])) {
// No callbacks here because the node itself doesn't get changed
// If one needs a callback here please let me know
$sql = sprintf('UPDATE %s SET %s=%s WHERE %s=%s',
$this->flparams['parent'],
if ($clone->id != $clone->rootid ) {
$sql = sprintf('UPDATE %s SET %s=%s WHERE %s=%s',
$sql = sprintf('UPDATE %s SET %s=%s, %s=%s WHERE %s=%s',
$orootid = $clone->rootid;
$sql = sprintf('UPDATE %s SET %s=%s WHERE %s=%s',
$this->_skipCallbacks = false;
foreach ($deletes as $delete) {
for($i = 0; $i < count($updates); $i++ ) {
$res = $this->db->query ($updates[$i]);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
for($i = 0; $i < count($pupdates); $i++ ) {
$res = $this->db->query ($pupdates[$i]);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
* | |-- subnode1 [target]
* [|] <-----------------------+
* |-- subnode 3.1 [target] |
* |-\ subnode 3.2 [source] >--+
* @param object $ NodeCT $source Source
* @param object $ NodeCT $target Target
* @param string $pos BEfore | AFter
function _moveRoot2Root ($source, $target, $pos) {
$this->_debugMessage ('_moveRoot2Root($source, $target, $pos, $copy)');
if (PEAR ::isError ($lock = $this->_setLock ())) {
$fid = $this->flparams['id'];
$froot = $this->flparams['rootid'];
$freh = $this->flparams['norder'];
$s_order = $source['norder'];
$t_order = $target['norder'];
if ($s_order < $t_order) {
$sql = " UPDATE $tb SET $freh=$freh-1
WHERE $freh BETWEEN $s_order AND $t_order AND
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
$sql = " UPDATE $tb SET $freh=$t_order -1 WHERE $fid=$s_id";
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
$sql = " UPDATE $tb SET $freh=$freh-1
WHERE $freh BETWEEN $s_order AND $t_order AND
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
$sql = " UPDATE $tb SET $freh=$t_order WHERE $fid=$s_id";
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
if ($s_order > $t_order) {
$sql = " UPDATE $tb SET $freh=$freh+1
WHERE $freh BETWEEN $t_order AND $s_order AND
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
$sql = " UPDATE $tb SET $freh=$t_order WHERE $fid=$s_id";
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
$sql = " UPDATE $tb SET $freh=$freh+1
WHERE $freh BETWEEN $t_order AND $s_order AND
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
$sql = " UPDATE $tb SET $freh=$t_order+1 WHERE $fid = $s_id";
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
// +-----------------------+
// +-----------------------+
* Callback for uasort used to sort siblings
function _secSort ($node1, $node2) {
// Within the same level?
if ($node1['level'] != $node2['level']) {
if ($p1['id'] != $p2['id']) {
// Same field value? Use the lft value then
if ($node1[$field] == $node2[$field]) {
// Compare between siblings with different field value
return strnatcmp($node1[$field], $node2[$field]);
* Adds a specific type of SQL to a query string
* @param array $addSQL The array of SQL strings to add. Example value:
* 'cols' => 'tb2.col2, tb2.col3', // Additional tables/columns
* 'join' => 'LEFT JOIN tb1 USING(STRID)', // Join statement
* 'where' => 'A='B' AND C='D', // Where statement without 'WHERE' OR 'AND' in front
* 'append' => 'GROUP by tb1.STRID'); // Group condition
* @param string $type The type of SQL. Can be 'cols', 'join', or 'append'.
* @return string The SQL, properly formatted
function _addSQL ($addSQL, $type, $prefix = false ) {
if (!isset ($addSQL[$type])) {
return ', ' . $addSQL[$type];
return $prefix . ' (' . $addSQL[$type] . ')';
// {{{ _getSelectFields()
* Gets the select fields based on the params
* @param bool $aliasFields Should we alias the fields so they are the names of the
* parameter keys, or leave them as is?
* @return string A string of query fields to select
function _getSelectFields ($aliasFields) {
foreach ($this->params as $key => $val) {
$tmp_field .= ' AS ' . $this->_quoteIdentifier ($val);
$queryFields[] = $tmp_field;
$fields = implode(', ', $queryFields);
// {{{ _processResultSet()
* Processes a DB result set by checking for a DB error and then transforming the result
* into a set of DB_NestedSet_Node objects or leaving it as an array.
* @param string $sql The sql query to be done
* @param bool $keepAsArray Keep the result as an array or transform it into a set of
* DB_NestedSet_Node objects?
* @param bool $fieldsAreAliased Are the fields aliased?
* @return mixed False on error or the transformed node set.
function _processResultSet ($sql, $keepAsArray, $fieldsAreAliased) {
$result = $this->db->getAll ($sql);
if ($this->_testFatalAbort ($result, __FILE__ , __LINE__ )) {
$idKey = $fieldsAreAliased ? 'id' : $this->flparams['id'];
foreach ($result as $row) {
// Create an instance of the node container
$nodes[$node_id] = & new DB_NestedSet_Node ($row);
* Tests if a given ressource is a PEAR error object
* ans raises a fatal error in case of an error object
* @param object $ PEAR::Error $errobj The object to test
* @param string $file The filename wher the error occured
* @param int $line The line number of the error
function _testFatalAbort ($errobj, $file, $line) {
if (!$this->_isDBError ($errobj)) {
$this->_debugMessage ('_testFatalAbort($errobj, $file, $line)');
$message = $errobj->getUserInfo ();
$code = $errobj->getCode ();
$msg = " $message ($code) in file $file at line $line";
$msg = $errobj->getMessage ();
$code = $errobj->getCode ();
PEAR ::raiseError ($msg, $code, PEAR_ERROR_TRIGGER , E_USER_ERROR );
function _raiseError ($code, $mode, $option, $epr = array ()) {
$message = vsprintf($this->_getMessage ($code), $epr);
return PEAR ::raiseError ($message, $code, $mode, $option);
* Adds an event listener and returns an ID for it
* @param string $event The ivent name
* @param string $listener The listener object
$this->eventListeners[$event][$listenerID] = & $listener;
$this->_hasListeners[$event] = true;
* Removes an event listener
* Removes the event listener with the given ID
* @param string $event The ivent name
* @param string $listenerID The listener's ID
unset ($this->eventListeners[$event][$listenerID]);
if (!isset ($this->eventListeners[$event]) || !is_array($this->eventListeners[$event]) ||
count($this->eventListeners[$event]) == 0 ) {
unset ($this->_hasListeners[$event]);
* Triggers and event an calls the event listeners
* @param string $event The Event that occured
* @param object $ node $node A Reference to the node object which was subject to changes
* @param array $eparams A associative array of params which may be needed by the handler
if ($this->_skipCallbacks || !isset ($this->_hasListeners[$event])) {
foreach($this->eventListeners[$event] as $key => $val) {
$val->callEvent ($event, $node, $eparams);
return array ('package:' => $this->_packagename,
'majorversion' => $this->_majorversion,
'minorversion' => $this->_minorversion,
'version' => sprintf('%s.%s', $this->_majorversion, $this->_minorversion),
'revision' => str_replace('$', '', " $Revision: 1.79 $" )
* Sets an object attribute
* @param array $attr An associative array with attributes
if (!isset ($hasSetSequence)) {
foreach ($attr as $key => $val) {
if ($key == 'sequence_table') {
// only update sequence to reflect new table if they haven't set it manually
if (!$hasSetSequence && $key == 'node_table') {
$GLOBALS['DB_NestedSet'] = & $this;
* This enables you to set specific options for each output method
* @param constant $sortMode
* @return Current sortMode
if ($sortMode && in_array($sortMode, $this->_sortModes)) {
$this->_sortMode = $sortMode;
* Sets a db option. Example, setting the sequence table format
* @var string $option The option to set
* @var string $val The value of the option
$this->db->setOption ($option, $val);
* Tests if a database lock is set
$this->_debugMessage ('testLock()');
if ($lockID = $this->_structureTableLock) {
$sql = sprintf('SELECT lockID FROM %s WHERE lockTable=%s',
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
if ($this->_numRows ($res)) {
function _setLock ($exclusive = false ) {
if (PEAR ::isError ($lock)) {
$this->_debugMessage ('_setLock()');
@$this->cache->flush ('function_cache');
$this->_restcache = true;
if (!$lockID = $this->_structureTableLock) {
$lockID = $this->_structureTableLock = uniqid('lck-');
$sql = sprintf('INSERT INTO %s (lockID, lockTable, lockStamp) VALUES (%s, %s, %s)',
$sql = sprintf('UPDATE %s SET lockStamp=%s WHERE lockID=%s AND lockTable=%s',
$this->_quote ($lockID), $this->_quote ($this->node_table));
$this->_lockExclusive = true;
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
function _releaseLock ($exclusive = false ) {
$this->_debugMessage ('_releaseLock()');
$this->_lockExclusive = false;
if ((!$lockID = $this->_structureTableLock) || $this->_lockExclusive) {
WHERE lockTable=" . $this->_quote ($stb) . " AND
lockID=" . $this->_quote ($lockID);
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
$this->_structureTableLock = false;
$this->_restcache = false;
$this->_debugMessage ('_lockGC()');
WHERE lockTable=" . $this->_quote ($stb) . " AND
$res = $this->db->query ($sql);
$this->_testFatalAbort ($res, __FILE__ , __LINE__ );
// {{{ _values2UpdateQuery()
function _values2UpdateQuery ($values, $addval = false ) {
$this->_debugMessage ('_values2UpdateQuery($values, $addval = false)');
$values = $values + $addval;
foreach($values AS $key => $val) {
$k = $this->_quoteIdentifier (trim($key));
// To be used with the next major version
// $iv = in_array($this->params[$k], $this->_quotedParams) ? $this->_quote($v) : $v;
$iv = $this->_quote (trim($val));
// {{{ _values2UpdateQuery()
function _values2InsertQuery ($values, $addval = false ) {
$this->_debugMessage ('_values2InsertQuery($values, $addval = false)');
$values = $values + $addval;
foreach($values AS $key => $val) {
$k = $this->_quoteIdentifier (trim($key));
// To be used with the next major version
// $iv = in_array($this->params[$k], $this->_quotedParams) ? $this->_quote($v) : $v;
$iv = $this->_quote (trim($val));
// {{{ _verifyUserValues()
* Clean values from protected or unknown columns
* @var string $caller The calling method
* @var string $values The values array
function _verifyUserValues ($caller, & $values) {
foreach($values as $field => $value) {
if (!isset ($this->params[$field])) {
$epr = array ($caller, sprintf('Unknown column/param \'%s\'', $field));
$flip = $this->params[$field];
if (in_array($flip, $this->_requiredParams)) {
$epr = array ($caller, sprintf('\'%s\' is autogenerated and can\'t be passed - it will be ignored', $field));
function _debugMessage ($msg) {
$time = ((float) $usec + (float) $sec);
echo " $time::Debug:: $msg<br />\n";
function _getMessage ($code) {
$this->_debugMessage ('_getMessage($code)');
// {{{ convertTreeModel()
* Convert a <1.3 tree into a 1.3 tree format
* This will convert the tree into a format needed for some new features in
* 1.3. Your <1.3 tree will still work without converting but some new features
* like preorder sorting won't work as expected.
* - Create a new node table (tb_nodes2) from the current node table (tb_nodes1) (only copy the structure).
* - Create a nested set instance of the 'old' set (NeSe1) and one of the new set (NeSe2)
* - Now you have 2 identical objects where only node_table differs
* - Call DB_NestedSet::convertTreeModel(&$orig, &$copy);
* - After that you have a cleaned up copy of tb_nodes1 inside tb_nodes2
* @param object $ DB_NestedSet $orig Nested set we want to copy
* @param object $ DB_NestedSet $copy Object where the new tree is copied to
* @param integer $_parent ID of the parent node (private)
* @return bool True uns success
if ($orig->node_table == $copy->node_table ) {
$sibl = $orig->getRootNodes (true );
$sibl = $orig->getChildren ($_parent, true );
foreach($sibl AS $sid => $sibling) {
unset ($sibling['norder']);
foreach($sibling AS $key => $val) {
if (!isset ($copy->flparams [$key])) {
$values[$copy->flparams [$key]] = $val;
$psid = $copy->createRootNode ($values, false , true );
$psid = $copy->createRightNode ($psid, $values);
$copy->createSubNode ($_parent, $values);
// {{{ DB_NestedSet_Node:: class
* Generic class for node objects
* @autor Daniel Khan <dk@webcluster.at>;
* @version $Revision: 1.79 $
class DB_NestedSet_Node {
function DB_NestedSet_Node ($data) {
function setAttr ($data) {
foreach ($data as $key => $val) {
Documentation generated on Mon, 11 Mar 2019 10:14:33 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|