cvs: pear /DB_NestedSet NestedSet.php
| From: | Daniel Khan | Date: | Mon, 06 Oct 2003 20:05:15 +0000 |
| Subject: | cvs: pear /DB_NestedSet NestedSet.php | ||
| Groups: | php.pear.cvs | ||
| Request: | Send a blank email to pear-cvs+get-13443@lists.php.net to get a copy of this message | ||
datenpunk Mon Oct 6 16:05:15 2003 EDT
Modified files:
/pear/DB_NestedSet NestedSet.php
Log:
- Cleanup work
Index: pear/DB_NestedSet/NestedSet.php diff -u pear/DB_NestedSet/NestedSet.php:1.53 pear/DB_NestedSet/NestedSet.php:1.54 --- pear/DB_NestedSet/NestedSet.php:1.53 Mon Oct 6 11:16:24 2003 +++ pear/DB_NestedSet/NestedSet.php Mon Oct 6 16:05:15 2003 @@ -16,18 +16,15 @@ // | Authors: Daniel Khan <dk@webcluster.at> | // | Jason Rust <jason@rustyparts.com> | // +----------------------------------------------------------------------+ -// $Id: NestedSet.php,v 1.53 2003/10/06 15:16:24 datenpunk Exp $ +// $Id: NestedSet.php,v 1.54 2003/10/06 20:05:15 datenpunk Exp $ // // CREDITS: // -------- -// - Many thanks to Jason Rust for doing great improvements and cleanup work for the current release // - 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 Hans Lellelid for suggesting support for MDB and for helping me with the -// implementation -// ... +// // - Thanks to my parents for ... just kidding :] require_once 'PEAR.php'; @@ -64,7 +61,7 @@ * * @author Daniel Khan <dk@webcluster.at> * @package DB_NestedSet -* @version $Revision: 1.53 $ +* @version $Revision: 1.54 $ * @access public */ @@ -87,7 +84,8 @@ 'STRNA' => 'name' ); - var $quotedParams = array('name'); + // 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 @@ -848,6 +846,10 @@ return $id; } + if(!$id) { + return false; + } + $sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s %s', $this->_getSelectFields($aliasFields), $this->_addSQL($addSQL, 'cols'), @@ -1023,7 +1025,7 @@ } elseif($_pos == NESE_MOVE_BEFORE) { $addval[$this->_flparams['norder']] = $parent['norder']; $sql[] = sprintf('UPDATE %s SET %s=%s+1 WHERE %s=%s AND %s >= %s', - $this->node_table, + $this->node_table, $this->_flparams['norder'], $this->_flparams['norder'], $this->_flparams['id'], @@ -1485,18 +1487,10 @@ $this->triggerEvent('nodeDelete', $this->pickNode($id)); } - $parents = $this->getParents($id, true); - $parent = array_pop($parents); + $parent = $this->getParent($id, true); $plft = $parent['l']; $prgt = $parent['r']; - - $tb = $this->node_table; - $flft = $this->_flparams['l']; - $frgt = $this->_flparams['r']; - $fid = $this->_flparams['id']; - $froot = $this->_flparams['rootid']; - $freh = $this->_flparams['norder']; - $flevel = $this->_flparams['level']; + $len = $thisnode['r'] - $thisnode['l'] + 1; @@ -1505,1142 +1499,9 @@ // Delete the node $sql[] = sprintf('DELETE FROM %s WHERE %s BETWEEN %s AND %s AND %s=%s', - $tb, - $flft, - $thisnode['l'], - $thisnode['r'], - $froot, - $thisnode['rootid'] - ); - - $sql[] = sprintf('DELETE FROM %s WHERE %s BETWEEN %s AND %s AND %s = %s', - $tb, - $flft, + $this->node_table, + $this->_flparams['l'], $thisnode['l'], $thisnode['r'], - $froot, - $thisnode['rootid'] - ); - - - if ($thisnode['id'] != $thisnode['rootid']) { - - - // The node isn't a rootnode so close the gap - - $sql[] = sprintf('UPDATE %s SET - %s=IF(%s>%s, %s-%s, %s), - %s=IF(%s>%s, %s-%s, %s) - WHERE %s=%s AND - (%s>%s OR %s>%s)', - $tb, - $flft, - $flft, - $thisnode['l'], - $flft, - $len, - $flft, - $frgt, - $frgt, - $thisnode['l'], - $frgt, - $len, - $frgt, - $froot, - $thisnode['rootid'], - $flft, - $thisnode['l'], - $frgt, - $thisnode['r'] - ); - - // Re-order - - $sql[] = sprintf('UPDATE %s SET %s=%s-1 WHERE %s=%s AND %s=%s AND %s>%s AND %s BETWEEN %s AND %s', - $tb, - $freh, - $freh, - $froot, - $thisnode['rootid'], - $flevel, - $thisnode['level'], - $freh, - $thisnode['norder'], - $flft, - $parent['l'], - $parent['r']); - - } else { - // 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', - $tb, - $freh, - $freh, - $froot, - $fid, - $freh, - $thisnode['norder']); - } - for($i=0;$i<count($sql);$i++) { - $res = $this->db->query($sql[$i]); - $this->_testFatalAbort($res, __FILE__, __LINE__); - } - $this->_releaseLock(); - return true; - } - - // }}} - // {{{ updateNode() - - /** - * Changes the payload of a node - * - * @param int $id Node ID - * @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. - * - * @access public - * @return bool True if the update is successful - */ - function updateNode($id, $values, $_internal=false) { - if ($this->debug) { - $this->_debugMessage('updateNode($id, $values)'); - } - if (!($thisnode =& $this->pickNode($id, true))) { - return false; - } - - if (PEAR::isError($lock = $this->_setLock())) { - return $lock; - } - - if(!$_internal) { - $this->_verifyUserValues('createRootNode()', $values); - } - - - - $eparams = array('values' => $values); - if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeUpdate'])) { - // EVENT (NodeUpdate) - $this->triggerEvent('nodeUpdate', $this->pickNode($id), $eparams); - } - $fid = $this->_flparams['id']; - $addvalues = array(); - if (!$qr = $this->_values2Query($values, $addvalues)) { - $this->_releaseLock(); - return false; - } - - $sql = sprintf('UPDATE %s SET %s WHERE %s = %s', - $this->node_table, - $qr, - $fid, - $id); - $res = $this->db->query($sql); - $this->_testFatalAbort($res, __FILE__, __LINE__); - $this->_releaseLock(); - return true; - } - - - - // }}} - // +----------------------------------------------+ - // | Moving and copying | - // |----------------------------------------------+ - // | [PUBLIC] | - // +----------------------------------------------+ - // {{{ moveTree() - - /** - * Wrapper for node moving and copying - * - * @param int $id Source ID - * @param int $target Target ID - * @param array $pos Position (use one of the NESE_MOVE_* constants) - * @param bool $copy Shall we create a copy - * - * @see _moveInsideLevel - * @see _moveAcross - * @see _moveRoot2Root - * @access public - * @return int ID of the moved node or false on error - */ - function moveTree($id, $targetid, $pos, $copy = false) { - - if ($this->debug) { - $this->_debugMessage('moveTree($id, $target, $pos, $copy = false)'); - } - if($id == $targetid && !$copy) { - // TRIGGER BOGUS MESSAGE - return false; - } - - // Get information about source and target - if (!($source = $this->pickNode($id, true))) { - $epr = array('moveTree()', $id); - return $this->_raiseError(NESE_ERROR_NOT_FOUND, E_USER_ERROR, $epr); - } - - if (!($target = $this->pickNode($targetid, true))) { - $epr = array('moveTree()', $targetid); - return $this->_raiseError(NESE_ERROR_NOT_FOUND, E_USER_ERROR, $epr); - } - - if (PEAR::isError($lock = $this->_setLock(true))) { - return $lock; - } - - $this->_relations = array(); - // This operations don't need callbacks except the copy handler - // which ignores this setting - $this->_skipCallbacks = true; - - if(!$copy) { - // 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()'); - return $this->_raiseError(NESE_ERROR_RECURSION, E_USER_NOTICE, $epr); - } - - // Insert/move before or after - - if (($source['rootid'] == $source['id']) && - ($target['rootid'] == $target['id'])) { - // We have to move a rootnode which is different from moving inside a tree - $nid = $this->_moveRoot2Root($source, $target, $pos, $copy); - $this->_releaseLock(true); - return $nid; - } - } elseif(($target['rootid'] == $source['rootid']) && - (($source['l'] < $target['l']) && - ($source['r'] > $target['r']))) { - $this->_releaseLock(true); - $epr = array('moveTree()'); - return $this->_raiseError(NESE_ERROR_RECURSION, E_USER_NOTICE, $epr); - } - - - // We have to move between different levels and maybe subtrees - let's rock ;) - $this->_moveAcross($source, $target, $pos); - $this->_moveCleanup($copy); - $this->_releaseLock(true); - } - - // }}} - // {{{ _moveAcross() - - /** - * Moves nodes and trees to other subtrees or levels - * - * <pre> - * [+] <--------------------------------+ - * +-[\] root1 [target] | - * <-------------------------+ |p - * +-\ root2 | | - * | | | | - * | |-- subnode1 [target] | |B - * | |-- subnode2 [new] |S |E - * | |-- subnode3 |U |F - * | |B |O - * +-\ root3 | |R - * |-- subnode 3.1 | |E - * |-\ subnode 3.2 [source] >--+------+ - * |-- subnode 3.2.1 - *</pre> - * - * @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 - * - * @access private - * @see moveTree - * @see _r_moveAcross - * @see _moveCleanup - */ - function _moveAcross($source, $target, $pos) { - if ($this->debug) { - $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 - $values = array(); - foreach($this->params as $key => $val) { - if ($source[$val] && !in_array($val, $this->_requiredParams)) { - $values[$key] = trim($source[$val]); - } - } - - switch($pos) { - - case NESE_MOVE_BEFORE: - $clone_id = $this->createLeftNode($target['id'], $values); - break; - - case NESE_MOVE_AFTER: - $clone_id = $this->createRightNode($target['id'], $values); - break; - - case NESE_MOVE_BELOW: - $clone_id = $this->createSubNode($target['id'], $values); - break; - } - - - $children = $this->getChildren($source['id'], true, true, true); - - - if ($children) { - $pos = NESE_MOVE_BELOW; - $sclone_id = $clone_id; - // Recurse through the child nodes - foreach($children AS $cid => $child) { - $sclone = $this->pickNode($sclone_id, true); - $sclone_id = $this->_moveAcross($child, $sclone, $pos); - - $pos = NESE_MOVE_AFTER; - } - } - - $this->_relations[$source['id']] = $clone_id; - return $clone_id; - } - - // }}} - // {{{ _moveCleanup() - - /** - * 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? - * @access private - */ - function _moveCleanup($copy = false) { - - $relations = $this->_relations; - if ($this->debug) { - $this->_debugMessage('_moveCleanup($relations, $copy = false)'); - } - - $deletes = array(); - $updates = array(); - $tb = $this->node_table; - $fid = $this->_flparams['id']; - $froot = $this->_flparams['rootid']; - foreach($relations AS $key => $val) { - $clone = $this->pickNode($val); - if ($copy) { - // EVENT (NodeCopy) - - $eparams = array('clone' => $clone); - - if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeCopy'])) { - $thisnode =& $this->pickNode($key); - $this->triggerEvent('nodeCopy', $this->pickNode($id), $eparams); - } - continue; - } - - // No callbacks here because the node itself doesn't get changed - // Only it's position - // If one needs a callback here please let me know - - $deletes[] = $key; - // It's isn't a rootnode - if ($clone->id != $clone->rootid) { - - - $sql = sprintf('UPDATE %s SET %s=%s WHERE %s = %s', - $this->node_table, - $fid, - $key, - $fid, - $val); - $updates[] = $sql; - } else { - - $sql = "UPDATE $tb SET - $fid=" . $this->quote($key) . ", - $froot=" . $this->quote($key) . " - WHERE $fid=" . $this->quote($val); - $updates[] = $sql; - $orootid = $clone->rootid; - $sql = "UPDATE $tb - SET $froot=" . $this->quote($key) . " - WHERE $froot=" . $this->quote($orootid); - $updates[] = $sql; - } - $this->_skipCallbacks = false; - } - - if(!empty($deletes)) { - for($i=0;$i<count($deletes);$i++) { - $this->deleteNode($deletes[$i]); - } - } - - if(!empty($updates)) { - for($i=0;$i<count($updates);$i++) { - $res = $this->db->query($updates[$i]); - $this->_testFatalAbort($res, __FILE__, __LINE__); - } - } - - return true; - } - - // }}} - // {{{ _moveInsideLevel() - - // }}} - // {{{ _moveRoot2Root() - - /** - * Moves rootnodes - * - * <pre> - * +-- root1 - * | - * +-\ root2 - * | | - * | |-- subnode1 [target] - * | |-- subnode2 [new] - * | |-- subnode3 - * | - * +-\ root3 - * [|] <-----------------------+ - * |-- subnode 3.1 [target] | - * |-\ subnode 3.2 [source] >--+ - * |-- subnode 3.2.1 - * </pre> - * - * @param object NodeCT $source Source - * @param object NodeCT $target Target - * @param object NodeCT $target Parent - * @param string $pos BEfore | AFter - * @param string $copy Copy mode? - * @access private - * @see moveTree - */ - function _moveRoot2Root($source, $target, $pos, $copy) { - - if ($this->debug) { - $this->_debugMessage('_moveRoot2Root($source, $target, $pos, $copy)'); - } - if(PEAR::isError($lock=$this->_setLock())) { - return $lock; - } - - $tb = $this->node_table; - $fid = $this->_flparams['id']; - $froot = $this->_flparams['rootid']; - $freh = $this->_flparams['norder']; - $s_order = $source['norder']; - $t_order = $target['norder']; - $s_id = $source['id']; - $t_id = $target['id']; - - - if ($s_order < $t_order) { - if ($pos == NESE_MOVE_BEFORE) { - $sql = "UPDATE $tb SET $freh=$freh-1 - WHERE $freh BETWEEN $s_order AND $t_order AND - $fid!=$t_id AND - $fid!=$s_id AND - $froot=$fid"; - $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__); - } - elseif($pos == NESE_MOVE_AFTER) { - - $sql = "UPDATE $tb SET $freh=$freh-1 - WHERE $freh BETWEEN $s_order AND $t_order AND - $fid!=$s_id AND - $froot=$fid"; - $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) { - if ($pos == NESE_MOVE_BEFORE) { - $sql = "UPDATE $tb SET $freh=$freh+1 - WHERE $freh BETWEEN $t_order AND $s_order AND - $fid != $s_id AND - $froot=$fid"; - $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__); - } - elseif ($pos == NESE_MOVE_AFTER) { - $sql = "UPDATE $tb SET $freh=$freh+1 - WHERE $freh BETWEEN $t_order AND $s_order AND - $fid!=$t_id AND - $fid!=$s_id AND - $froot=$fid"; - $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__); - } - } - $this->_releaseLock(); - return $source->id; - } - - // }}} - // +-----------------------+ - // | Helper methods | - // +-----------------------+ - - function _secSort($node1, $node2) { - - // Within the same level? - if($node1['level'] != $node2['level']) { - return strnatcmp($node1['l'], $node2['l']); - } - - // Are they siblings? - $p1 = $this->getParent($node1); - $p2 = $this->getParent($node2); - if($p1['id'] != $p2['id']) { - return strnatcmp($node1['l'], $node2['l']); - } - - // Same field value? Use the lft value then - $field = $this->params[$this->secondarySort]; - if($node1[$field] == $node2[$field]) { - return strnatcmp($node1['l'], $node2[l]); - } - - // Compare between siblings with different field value - return strnatcmp($node1[$field], $node2[$field]); - } - - // {{{ _addSQL() - /** - * Adds a specific type of SQL to a query string - * - * @param array $addSQL The array of SQL strings to add. Example value: - * $addSQL = array( - * 'cols' => 'tb2.col2, tb2.col3', // Additional tables/columns - * 'join' => 'LEFT JOIN tb1 USING(STRID)', // Join statement - * 'append' => 'GROUP by tb1.STRID'); // Group condition - * @param string $type The type of SQL. Can be 'cols', 'join', or 'append'. - * - * @access private - * @return string The SQL, properly formatted - */ - function _addSQL($addSQL, $type) { - if (!isset($addSQL[$type])) { - return ''; - } - - switch($type) { - case 'cols': - return ', ' . $addSQL[$type]; - default: - return $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? - * - * @access private - * @return string A string of query fields to select - */ - function _getSelectFields($aliasFields) { - $queryFields = array(); - foreach ($this->params as $key => $val) { - $tmp_field = $this->node_table . '.' . $key; - if ($aliasFields) { - $tmp_field .= ' AS ' . $val; - } - $queryFields[] = $tmp_field; - } - - $fields = implode(', ', $queryFields); - return $fields; - } - - // }}} - // {{{ _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? - * - * @access private - * @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__)) { - return false; - } - - $nodes = array(); - $idKey = $fieldsAreAliased ? 'id' : $this->_flparams['id']; - foreach ($result as $row) { - $node_id = $row[$idKey]; - if ($keepAsArray) { - $nodes[$node_id] = $row; - } else { - // Create an instance of the node container - $nodes[$node_id] =& new DB_NestedSet_Node($row); - } - - } - return $nodes; - } - - // }}} - // {{{ _testFatalAbort() - /** - * Error Handler - * - * 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 - * @return void - * @access private - */ - function _testFatalAbort($errobj, $file, $line) { - if (!$this->_isDBError($errobj)) { - return false; - } - - if ($this->debug) { - $this->_debugMessage('_testFatalAbort($errobj, $file, $line)'); - } - if ($this->debug) { - $message = $errobj->getUserInfo(); - $code = $errobj->getCode(); - $msg = "$message ($code) in file $file at line $line"; - } else { - $msg = $errobj->getMessage(); - $code = $errobj->getCode(); } - - PEAR::raiseError($msg, $code, PEAR_ERROR_TRIGGER, E_USER_ERROR); - } - - // }}} - // {{{ addListener() - - /** - * Add an event listener - * - * Adds an event listener and returns an ID for it - * - * @param string $event The ivent name - * @param string $listener The listener object - * @return string - * @access public - */ - function addListener($event, &$listener) { - $listenerID = uniqid('el'); - $this->eventListeners[$event][$listenerID] =& $listener; - $this->_hasListeners[$event] = true; - return $listenerID; - } - - // }}} - // {{{ removeListener() - - /** - * 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 - * @return bool - * @access public - */ - function removeListener($event, $listenerID) { - unset($this->eventListeners[$event][$listenerID]); - if (!isset($this->eventListeners[$event]) || - !is_array($this->eventListeners[$event]) || - count($this->eventListeners[$event]) == 0) { - unset($this->_hasListeners[$event]); - } - return true; - } - - // }}} - // {{{ triggerEvent() - - /** - * 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 - * @return bool - * @access public - */ - function triggerEvent($event, &$node, $eparams = false) { - if ($this->_skipCallbacks || !isset($this->_hasListeners[$event])) { - return false; - } - - foreach($this->eventListeners[$event] as $key => $val) { - if (!method_exists($val, 'callEvent')) { - return new PEAR_Error($this->_getMessage(NESE_ERROR_NOHANDLER), NESE_ERROR_NOHANDLER); - } - - $val->callEvent($event, $node, $eparams); - } - - return true; - } - - // }}} - - - function apiVersion() { - return array( - 'package:'=>$this->_packagename, - 'majorversion'=>$this->_majorversion, - 'minorversion'=>$this->_minorversion, - 'version'=>sprintf('%s.%s',$this->_majorversion, $this->_minorversion), - 'revision'=>str_replace('$', '',"$Revision: 1.53 $") - ); - } - - - // {{{ setAttr() - - /** - * Sets an object attribute - * - * @param array $attr An associative array with attributes - * - * @return bool - * @access public - */ - function setAttr($attr) { - static $hasSetSequence; - if (!isset($hasSetSequence)) { - $hasSetSequence = false; - } - - if (!is_array($attr) || count($attr) == 0) { - return false; - } - - foreach ($attr as $key => $val) { - $this->$key = $val; - if ($key == 'sequence_table') { - $hasSetSequence = true; - } - - // only update sequence to reflect new table if they haven't set it manually - if (!$hasSetSequence && $key == 'node_table') { - $this->sequence_table = $this->node_table . '_' . $this->_flparams['id']; - } - if($key == 'cache' && is_object($val)) { - $this->_caching = true; - $GLOBALS['DB_NestedSet'] = & $this; - } - } - - return true; - } - - // }}} - // {{{ setDbOption() - - /** - * 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 - * - * @access public - * @return void - */ - function setDbOption($option, $val) { - $this->db->setOption($option, $val); - } - - // }}} - // {{{ testLock() - - /** - * Tests if a database lock is set - * - * @access public - */ - function testLock() { - if ($this->debug) { - $this->_debugMessage('testLock()'); - } - - if($lockID = $this->_structureTableLock) { - return $lockID; - } - $this->_lockGC(); - $sql = sprintf('SELECT lockID FROM %s WHERE lockTable=%s', - $this->lock_table, - $this->quote($this->node_table)) ; - - $res = $this->db->query($sql); - $this->_testFatalAbort($res, __FILE__, __LINE__); - - if ($this->_numRows($res)) { - return new PEAR_Error($this->_getMessage(NESE_ERROR_TBLOCKED),NESE_ERROR_TBLOCKED); - } - - return false; - } - - // }}} - // {{{ _setLock() - - /** - * @access private - */ - function _setLock($exclusive=false) { - $lock = $this->testLock(); - if(PEAR::isError($lock)) { - return $lock; - } - - if ($this->debug) { - $this->_debugMessage('_setLock()'); - } - if($this->_caching) { - @$this->cache->flush('function_cache'); - $this->_caching = false; - $this->_restcache = true; - } - - if (!$lockID = $this->_structureTableLock) { - $lockID = $this->_structureTableLock = uniqid('lck-'); - - $sql = sprintf('INSERT INTO %s SET lockID=%s, lockTable=%s, lockStamp=%s', - $this->lock_table, - $this->quote($lockID), - $this->quote($this->node_table), - time()); - - } else { - $sql = sprintf('UPDATE %s set lockStamp=%s WHERE lockID=%s AND lockTable=%s', - $this->lock_table, - time(), - $this->quote($lockID), - $this->quote($this->node_table)); - } - if($exclusive) { - $this->_lockExclusive = true; - } - - $res = $this->db->query($sql); - $this->_testFatalAbort($res, __FILE__, __LINE__); - return $lockID; - } - - // }}} - // {{{ _releaseLock() - - /** - * @access private - */ - function _releaseLock($exclusive=false) { - if ($this->debug) { - $this->_debugMessage('_releaseLock()'); - } - - if($exclusive) { - $this->_lockExclusive = false; - } - - if ((!$lockID = $this->_structureTableLock) || $this->_lockExclusive) { - return false; - } - - $tb = $this->lock_table; - $stb = $this->node_table; - $sql = "DELETE FROM $tb - WHERE lockTable=" . $this->quote($stb) . " AND - lockID=" . $this->quote($lockID); - - $res = $this->db->query($sql); - $this->_testFatalAbort($res, __FILE__, __LINE__); - $this->_structureTableLock = false; - if($this->_restcache) { - $this->_caching = true; - $this->_restcache = false; - } - return true; - } - - // }}} - // {{{ _lockGC() - - /** - * @access private - */ - function _lockGC() { - if ($this->debug) { - $this->_debugMessage('_lockGC()'); - } - $tb = $this->lock_table; - $stb = $this->node_table; - $lockTTL = time() - $this->lockTTL; - $sql = "DELETE FROM $tb - WHERE lockTable=" . $this->quote($stb) . " AND - lockStamp < $lockTTL"; - - $res = $this->db->query($sql); - $this->_testFatalAbort($res, __FILE__, __LINE__); - } - - // }}} - // {{{ _values2Query() - - /** - * @access private - */ - function _values2Query($values, $addval = false) { - if ($this->debug) { - $this->_debugMessage('_values2Query($values, $addval = false)'); - } - if (is_array($addval)) { - $values = $values + $addval; - } - - $arq = array(); - foreach($values AS $key => $val) { - $k = trim($key); - $v = trim($val); - if ($k) { - - $arq[] = "$k=" . $this->quote($v); - } - } - - if (!is_array($arq) || count($arq) == 0) { - return false; - } - - $query = implode(', ', $arq); - return $query; - } - - // }}} - // {{{ _verifyUserValues() - - /** - * Clean values from protected or unknown columns - * - * @var string $caller The calling method - * @var string $values The values array - * - * @access private - * @return void - */ - function _verifyUserValues($caller, &$values) { - - if($this->_dumbmode) { - return true; - } - foreach($values AS $field=>$value) { - if(!isset($this->params[$field])) { - $epr = array( - $caller, - sprintf('Unknown column/param \'%s\'', $field)); - $this->_raiseError(NESE_ERROR_WRONG_MPARAM, E_USER_NOTICE, $epr); - unset($values[$field]); - } else { - $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)); - $this->_raiseError(NESE_ERROR_WRONG_MPARAM, E_USER_NOTICE, $epr); - unset($values[$field]); - } - } - } - } - - // }}} - // {{{ _debugMessage() - - /** - * @access private - */ - function _debugMessage($msg) { - if ($this->debug) { - $time = $this->_getmicrotime(); - echo "$time::Debug:: $msg<br />\n"; - } - } - - // }}} - // {{{ _getMessage() - - /** - * @access private - */ - function _getMessage($code) { - if ($this->debug) { - $this->_debugMessage('_getMessage($code)'); - } - return isset($this->messages[$code]) ? $this->messages[$code] : $this->messages[NESE_MESSAGE_UNKNOWN]; - - } - - function _raiseError($code, $option, $epr=array()) { - $message = vsprintf($this->_getMessage($code), $epr); - return PEAR::raiseError($message, $code, PEAR_ERROR_TRIGGER, $option); - } - - // }}} - // {{{ _getmicrotime() - - /** - * @access private - */ - function _getmicrotime() { - list($usec, $sec) = explode(' ', microtime()); - return ((float)$usec + (float)$sec); - } - // }}} - - function convertTreeModel(&$orig, &$copy, $parent=false) { - - static $firstSet; - - $isRoot = false; - if(!$parent) { - if(!is_object($orig) || !is_object($copy)) { - return false; - } - if($orig->node_table == $copy->node_table) { - return false; - } - $copy->_dumbmode = true; - $orig->sortmode = NESE_SORT_LEVEL; - $copy->sortmode = NESE_SORT_LEVEL; - $sibl = $orig->getRootNodes(true); - $isRoot = true; - } else { - $sibl = $orig->getChildren($parent, true); - } - - if(empty($sibl)) { - return false; - } - - foreach($sibl AS $sid=>$sibling) { - unset($sibling['l']); - unset($sibling['r']); - unset($sibling['norder']); - - $values = array(); - foreach($sibling AS $key=>$val) { - if(!isset($copy->_flparams[$key])) { - continue; - } - $values[$copy->_flparams[$key]] = $val; - } - - if(!$firstSet) { - $psid = $copy->createRootNode($values, false, true); - $firstSet = true; - } elseif($isRoot) { - $psid = $copy->createRightNode($psid, $values); - } else { - $copy->createSubNode($parent, $values); - } - - DB_NestedSet::convertTreeModel($orig, $copy, $sid); - } - - } -} - -// {{{ DB_NestedSet_Node:: class - -/** -* Generic class for node objects -* -* @autor Daniel Khan <dk@webcluster.at>; -* @version $Revision: 1.53 $ -* @package DB_NestedSet -* -* @access private -*/ - -class DB_NestedSet_Node { - // {{{ constructor - - /** - * Constructor - */ - function DB_NestedSet_Node($data) { - if (!is_array($data) || count($data) == 0) { - return new PEAR_ERROR($data, NESE_ERROR_PARAM_MISSING); - } - - $this->setAttr($data); - return true; - } - - // }}} - // {{{ setAttr() - - function setAttr($data) { - if(!is_array($data) || count($data) == 0) { - return false; - } - - foreach ($data as $key => $val) { - $this->$key = $val; - } - } - - // }}} - -} -// }}} - - -?> \ No newline at end of file + $this->_flparams['rootid'], + \ No newline at end of file
Index: pear/DB_NestedSet/NestedSet.php diff -u pear/DB_NestedSet/NestedSet.php:1.53 pear/DB_NestedSet/NestedSet.php:1.54 --- pear/DB_NestedSet/NestedSet.php:1.53 Mon Oct 6 11:16:24 2003 +++ pear/DB_NestedSet/NestedSet.php Mon Oct 6 16:05:15 2003 @@ -16,18 +16,15 @@ // | Authors: Daniel Khan <dk@webcluster.at> | // | Jason Rust <jason@rustyparts.com> | // +----------------------------------------------------------------------+ -// $Id: NestedSet.php,v 1.53 2003/10/06 15:16:24 datenpunk Exp $ +// $Id: NestedSet.php,v 1.54 2003/10/06 20:05:15 datenpunk Exp $ // // CREDITS: // -------- -// - Many thanks to Jason Rust for doing great improvements and cleanup work for the current release // - 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 Hans Lellelid for suggesting support for MDB and for helping me with the -// implementation -// ... +// // - Thanks to my parents for ... just kidding :] require_once 'PEAR.php'; @@ -64,7 +61,7 @@ * * @author Daniel Khan <dk@webcluster.at> * @package DB_NestedSet -* @version $Revision: 1.53 $ +* @version $Revision: 1.54 $ * @access public */ @@ -87,7 +84,8 @@ 'STRNA' => 'name' ); - var $quotedParams = array('name'); + // 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 @@ -848,6 +846,10 @@ return $id; } + if(!$id) { + return false; + } + $sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s %s', $this->_getSelectFields($aliasFields), $this->_addSQL($addSQL, 'cols'), @@ -1023,7 +1025,7 @@ } elseif($_pos == NESE_MOVE_BEFORE) { $addval[$this->_flparams['norder']] = $parent['norder']; $sql[] = sprintf('UPDATE %s SET %s=%s+1 WHERE %s=%s AND %s >= %s', - $this->node_table, + $this->node_table, $this->_flparams['norder'], $this->_flparams['norder'], $this->_flparams['id'], @@ -1485,18 +1487,10 @@ $this->triggerEvent('nodeDelete', $this->pickNode($id)); } - $parents = $this->getParents($id, true); - $parent = array_pop($parents); + $parent = $this->getParent($id, true); $plft = $parent['l']; $prgt = $parent['r']; - - $tb = $this->node_table; - $flft = $this->_flparams['l']; - $frgt = $this->_flparams['r']; - $fid = $this->_flparams['id']; - $froot = $this->_flparams['rootid']; - $freh = $this->_flparams['norder']; - $flevel = $this->_flparams['level']; + $len = $thisnode['r'] - $thisnode['l'] + 1; @@ -1505,1142 +1499,9 @@ // Delete the node $sql[] = sprintf('DELETE FROM %s WHERE %s BETWEEN %s AND %s AND %s=%s', - $tb, - $flft, - $thisnode['l'], - $thisnode['r'], - $froot, - $thisnode['rootid'] - ); - - $sql[] = sprintf('DELETE FROM %s WHERE %s BETWEEN %s AND %s AND %s = %s', - $tb, - $flft, + $this->node_table, + $this->_flparams['l'], $thisnode['l'], $thisnode['r'], - $froot, - $thisnode['rootid'] - ); - - - if ($thisnode['id'] != $thisnode['rootid']) { - - - // The node isn't a rootnode so close the gap - - $sql[] = sprintf('UPDATE %s SET - %s=IF(%s>%s, %s-%s, %s), - %s=IF(%s>%s, %s-%s, %s) - WHERE %s=%s AND - (%s>%s OR %s>%s)', - $tb, - $flft, - $flft, - $thisnode['l'], - $flft, - $len, - $flft, - $frgt, - $frgt, - $thisnode['l'], - $frgt, - $len, - $frgt, - $froot, - $thisnode['rootid'], - $flft, - $thisnode['l'], - $frgt, - $thisnode['r'] - ); - - // Re-order - - $sql[] = sprintf('UPDATE %s SET %s=%s-1 WHERE %s=%s AND %s=%s AND %s>%s AND %s BETWEEN %s AND %s', - $tb, - $freh, - $freh, - $froot, - $thisnode['rootid'], - $flevel, - $thisnode['level'], - $freh, - $thisnode['norder'], - $flft, - $parent['l'], - $parent['r']); - - } else { - // 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', - $tb, - $freh, - $freh, - $froot, - $fid, - $freh, - $thisnode['norder']); - } - for($i=0;$i<count($sql);$i++) { - $res = $this->db->query($sql[$i]); - $this->_testFatalAbort($res, __FILE__, __LINE__); - } - $this->_releaseLock(); - return true; - } - - // }}} - // {{{ updateNode() - - /** - * Changes the payload of a node - * - * @param int $id Node ID - * @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. - * - * @access public - * @return bool True if the update is successful - */ - function updateNode($id, $values, $_internal=false) { - if ($this->debug) { - $this->_debugMessage('updateNode($id, $values)'); - } - if (!($thisnode =& $this->pickNode($id, true))) { - return false; - } - - if (PEAR::isError($lock = $this->_setLock())) { - return $lock; - } - - if(!$_internal) { - $this->_verifyUserValues('createRootNode()', $values); - } - - - - $eparams = array('values' => $values); - if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeUpdate'])) { - // EVENT (NodeUpdate) - $this->triggerEvent('nodeUpdate', $this->pickNode($id), $eparams); - } - $fid = $this->_flparams['id']; - $addvalues = array(); - if (!$qr = $this->_values2Query($values, $addvalues)) { - $this->_releaseLock(); - return false; - } - - $sql = sprintf('UPDATE %s SET %s WHERE %s = %s', - $this->node_table, - $qr, - $fid, - $id); - $res = $this->db->query($sql); - $this->_testFatalAbort($res, __FILE__, __LINE__); - $this->_releaseLock(); - return true; - } - - - - // }}} - // +----------------------------------------------+ - // | Moving and copying | - // |----------------------------------------------+ - // | [PUBLIC] | - // +----------------------------------------------+ - // {{{ moveTree() - - /** - * Wrapper for node moving and copying - * - * @param int $id Source ID - * @param int $target Target ID - * @param array $pos Position (use one of the NESE_MOVE_* constants) - * @param bool $copy Shall we create a copy - * - * @see _moveInsideLevel - * @see _moveAcross - * @see _moveRoot2Root - * @access public - * @return int ID of the moved node or false on error - */ - function moveTree($id, $targetid, $pos, $copy = false) { - - if ($this->debug) { - $this->_debugMessage('moveTree($id, $target, $pos, $copy = false)'); - } - if($id == $targetid && !$copy) { - // TRIGGER BOGUS MESSAGE - return false; - } - - // Get information about source and target - if (!($source = $this->pickNode($id, true))) { - $epr = array('moveTree()', $id); - return $this->_raiseError(NESE_ERROR_NOT_FOUND, E_USER_ERROR, $epr); - } - - if (!($target = $this->pickNode($targetid, true))) { - $epr = array('moveTree()', $targetid); - return $this->_raiseError(NESE_ERROR_NOT_FOUND, E_USER_ERROR, $epr); - } - - if (PEAR::isError($lock = $this->_setLock(true))) { - return $lock; - } - - $this->_relations = array(); - // This operations don't need callbacks except the copy handler - // which ignores this setting - $this->_skipCallbacks = true; - - if(!$copy) { - // 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()'); - return $this->_raiseError(NESE_ERROR_RECURSION, E_USER_NOTICE, $epr); - } - - // Insert/move before or after - - if (($source['rootid'] == $source['id']) && - ($target['rootid'] == $target['id'])) { - // We have to move a rootnode which is different from moving inside a tree - $nid = $this->_moveRoot2Root($source, $target, $pos, $copy); - $this->_releaseLock(true); - return $nid; - } - } elseif(($target['rootid'] == $source['rootid']) && - (($source['l'] < $target['l']) && - ($source['r'] > $target['r']))) { - $this->_releaseLock(true); - $epr = array('moveTree()'); - return $this->_raiseError(NESE_ERROR_RECURSION, E_USER_NOTICE, $epr); - } - - - // We have to move between different levels and maybe subtrees - let's rock ;) - $this->_moveAcross($source, $target, $pos); - $this->_moveCleanup($copy); - $this->_releaseLock(true); - } - - // }}} - // {{{ _moveAcross() - - /** - * Moves nodes and trees to other subtrees or levels - * - * <pre> - * [+] <--------------------------------+ - * +-[\] root1 [target] | - * <-------------------------+ |p - * +-\ root2 | | - * | | | | - * | |-- subnode1 [target] | |B - * | |-- subnode2 [new] |S |E - * | |-- subnode3 |U |F - * | |B |O - * +-\ root3 | |R - * |-- subnode 3.1 | |E - * |-\ subnode 3.2 [source] >--+------+ - * |-- subnode 3.2.1 - *</pre> - * - * @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 - * - * @access private - * @see moveTree - * @see _r_moveAcross - * @see _moveCleanup - */ - function _moveAcross($source, $target, $pos) { - if ($this->debug) { - $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 - $values = array(); - foreach($this->params as $key => $val) { - if ($source[$val] && !in_array($val, $this->_requiredParams)) { - $values[$key] = trim($source[$val]); - } - } - - switch($pos) { - - case NESE_MOVE_BEFORE: - $clone_id = $this->createLeftNode($target['id'], $values); - break; - - case NESE_MOVE_AFTER: - $clone_id = $this->createRightNode($target['id'], $values); - break; - - case NESE_MOVE_BELOW: - $clone_id = $this->createSubNode($target['id'], $values); - break; - } - - - $children = $this->getChildren($source['id'], true, true, true); - - - if ($children) { - $pos = NESE_MOVE_BELOW; - $sclone_id = $clone_id; - // Recurse through the child nodes - foreach($children AS $cid => $child) { - $sclone = $this->pickNode($sclone_id, true); - $sclone_id = $this->_moveAcross($child, $sclone, $pos); - - $pos = NESE_MOVE_AFTER; - } - } - - $this->_relations[$source['id']] = $clone_id; - return $clone_id; - } - - // }}} - // {{{ _moveCleanup() - - /** - * 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? - * @access private - */ - function _moveCleanup($copy = false) { - - $relations = $this->_relations; - if ($this->debug) { - $this->_debugMessage('_moveCleanup($relations, $copy = false)'); - } - - $deletes = array(); - $updates = array(); - $tb = $this->node_table; - $fid = $this->_flparams['id']; - $froot = $this->_flparams['rootid']; - foreach($relations AS $key => $val) { - $clone = $this->pickNode($val); - if ($copy) { - // EVENT (NodeCopy) - - $eparams = array('clone' => $clone); - - if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeCopy'])) { - $thisnode =& $this->pickNode($key); - $this->triggerEvent('nodeCopy', $this->pickNode($id), $eparams); - } - continue; - } - - // No callbacks here because the node itself doesn't get changed - // Only it's position - // If one needs a callback here please let me know - - $deletes[] = $key; - // It's isn't a rootnode - if ($clone->id != $clone->rootid) { - - - $sql = sprintf('UPDATE %s SET %s=%s WHERE %s = %s', - $this->node_table, - $fid, - $key, - $fid, - $val); - $updates[] = $sql; - } else { - - $sql = "UPDATE $tb SET - $fid=" . $this->quote($key) . ", - $froot=" . $this->quote($key) . " - WHERE $fid=" . $this->quote($val); - $updates[] = $sql; - $orootid = $clone->rootid; - $sql = "UPDATE $tb - SET $froot=" . $this->quote($key) . " - WHERE $froot=" . $this->quote($orootid); - $updates[] = $sql; - } - $this->_skipCallbacks = false; - } - - if(!empty($deletes)) { - for($i=0;$i<count($deletes);$i++) { - $this->deleteNode($deletes[$i]); - } - } - - if(!empty($updates)) { - for($i=0;$i<count($updates);$i++) { - $res = $this->db->query($updates[$i]); - $this->_testFatalAbort($res, __FILE__, __LINE__); - } - } - - return true; - } - - // }}} - // {{{ _moveInsideLevel() - - // }}} - // {{{ _moveRoot2Root() - - /** - * Moves rootnodes - * - * <pre> - * +-- root1 - * | - * +-\ root2 - * | | - * | |-- subnode1 [target] - * | |-- subnode2 [new] - * | |-- subnode3 - * | - * +-\ root3 - * [|] <-----------------------+ - * |-- subnode 3.1 [target] | - * |-\ subnode 3.2 [source] >--+ - * |-- subnode 3.2.1 - * </pre> - * - * @param object NodeCT $source Source - * @param object NodeCT $target Target - * @param object NodeCT $target Parent - * @param string $pos BEfore | AFter - * @param string $copy Copy mode? - * @access private - * @see moveTree - */ - function _moveRoot2Root($source, $target, $pos, $copy) { - - if ($this->debug) { - $this->_debugMessage('_moveRoot2Root($source, $target, $pos, $copy)'); - } - if(PEAR::isError($lock=$this->_setLock())) { - return $lock; - } - - $tb = $this->node_table; - $fid = $this->_flparams['id']; - $froot = $this->_flparams['rootid']; - $freh = $this->_flparams['norder']; - $s_order = $source['norder']; - $t_order = $target['norder']; - $s_id = $source['id']; - $t_id = $target['id']; - - - if ($s_order < $t_order) { - if ($pos == NESE_MOVE_BEFORE) { - $sql = "UPDATE $tb SET $freh=$freh-1 - WHERE $freh BETWEEN $s_order AND $t_order AND - $fid!=$t_id AND - $fid!=$s_id AND - $froot=$fid"; - $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__); - } - elseif($pos == NESE_MOVE_AFTER) { - - $sql = "UPDATE $tb SET $freh=$freh-1 - WHERE $freh BETWEEN $s_order AND $t_order AND - $fid!=$s_id AND - $froot=$fid"; - $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) { - if ($pos == NESE_MOVE_BEFORE) { - $sql = "UPDATE $tb SET $freh=$freh+1 - WHERE $freh BETWEEN $t_order AND $s_order AND - $fid != $s_id AND - $froot=$fid"; - $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__); - } - elseif ($pos == NESE_MOVE_AFTER) { - $sql = "UPDATE $tb SET $freh=$freh+1 - WHERE $freh BETWEEN $t_order AND $s_order AND - $fid!=$t_id AND - $fid!=$s_id AND - $froot=$fid"; - $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__); - } - } - $this->_releaseLock(); - return $source->id; - } - - // }}} - // +-----------------------+ - // | Helper methods | - // +-----------------------+ - - function _secSort($node1, $node2) { - - // Within the same level? - if($node1['level'] != $node2['level']) { - return strnatcmp($node1['l'], $node2['l']); - } - - // Are they siblings? - $p1 = $this->getParent($node1); - $p2 = $this->getParent($node2); - if($p1['id'] != $p2['id']) { - return strnatcmp($node1['l'], $node2['l']); - } - - // Same field value? Use the lft value then - $field = $this->params[$this->secondarySort]; - if($node1[$field] == $node2[$field]) { - return strnatcmp($node1['l'], $node2[l]); - } - - // Compare between siblings with different field value - return strnatcmp($node1[$field], $node2[$field]); - } - - // {{{ _addSQL() - /** - * Adds a specific type of SQL to a query string - * - * @param array $addSQL The array of SQL strings to add. Example value: - * $addSQL = array( - * 'cols' => 'tb2.col2, tb2.col3', // Additional tables/columns - * 'join' => 'LEFT JOIN tb1 USING(STRID)', // Join statement - * 'append' => 'GROUP by tb1.STRID'); // Group condition - * @param string $type The type of SQL. Can be 'cols', 'join', or 'append'. - * - * @access private - * @return string The SQL, properly formatted - */ - function _addSQL($addSQL, $type) { - if (!isset($addSQL[$type])) { - return ''; - } - - switch($type) { - case 'cols': - return ', ' . $addSQL[$type]; - default: - return $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? - * - * @access private - * @return string A string of query fields to select - */ - function _getSelectFields($aliasFields) { - $queryFields = array(); - foreach ($this->params as $key => $val) { - $tmp_field = $this->node_table . '.' . $key; - if ($aliasFields) { - $tmp_field .= ' AS ' . $val; - } - $queryFields[] = $tmp_field; - } - - $fields = implode(', ', $queryFields); - return $fields; - } - - // }}} - // {{{ _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? - * - * @access private - * @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__)) { - return false; - } - - $nodes = array(); - $idKey = $fieldsAreAliased ? 'id' : $this->_flparams['id']; - foreach ($result as $row) { - $node_id = $row[$idKey]; - if ($keepAsArray) { - $nodes[$node_id] = $row; - } else { - // Create an instance of the node container - $nodes[$node_id] =& new DB_NestedSet_Node($row); - } - - } - return $nodes; - } - - // }}} - // {{{ _testFatalAbort() - /** - * Error Handler - * - * 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 - * @return void - * @access private - */ - function _testFatalAbort($errobj, $file, $line) { - if (!$this->_isDBError($errobj)) { - return false; - } - - if ($this->debug) { - $this->_debugMessage('_testFatalAbort($errobj, $file, $line)'); - } - if ($this->debug) { - $message = $errobj->getUserInfo(); - $code = $errobj->getCode(); - $msg = "$message ($code) in file $file at line $line"; - } else { - $msg = $errobj->getMessage(); - $code = $errobj->getCode(); } - - PEAR::raiseError($msg, $code, PEAR_ERROR_TRIGGER, E_USER_ERROR); - } - - // }}} - // {{{ addListener() - - /** - * Add an event listener - * - * Adds an event listener and returns an ID for it - * - * @param string $event The ivent name - * @param string $listener The listener object - * @return string - * @access public - */ - function addListener($event, &$listener) { - $listenerID = uniqid('el'); - $this->eventListeners[$event][$listenerID] =& $listener; - $this->_hasListeners[$event] = true; - return $listenerID; - } - - // }}} - // {{{ removeListener() - - /** - * 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 - * @return bool - * @access public - */ - function removeListener($event, $listenerID) { - unset($this->eventListeners[$event][$listenerID]); - if (!isset($this->eventListeners[$event]) || - !is_array($this->eventListeners[$event]) || - count($this->eventListeners[$event]) == 0) { - unset($this->_hasListeners[$event]); - } - return true; - } - - // }}} - // {{{ triggerEvent() - - /** - * 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 - * @return bool - * @access public - */ - function triggerEvent($event, &$node, $eparams = false) { - if ($this->_skipCallbacks || !isset($this->_hasListeners[$event])) { - return false; - } - - foreach($this->eventListeners[$event] as $key => $val) { - if (!method_exists($val, 'callEvent')) { - return new PEAR_Error($this->_getMessage(NESE_ERROR_NOHANDLER), NESE_ERROR_NOHANDLER); - } - - $val->callEvent($event, $node, $eparams); - } - - return true; - } - - // }}} - - - function apiVersion() { - return array( - 'package:'=>$this->_packagename, - 'majorversion'=>$this->_majorversion, - 'minorversion'=>$this->_minorversion, - 'version'=>sprintf('%s.%s',$this->_majorversion, $this->_minorversion), - 'revision'=>str_replace('$', '',"$Revision: 1.53 $") - ); - } - - - // {{{ setAttr() - - /** - * Sets an object attribute - * - * @param array $attr An associative array with attributes - * - * @return bool - * @access public - */ - function setAttr($attr) { - static $hasSetSequence; - if (!isset($hasSetSequence)) { - $hasSetSequence = false; - } - - if (!is_array($attr) || count($attr) == 0) { - return false; - } - - foreach ($attr as $key => $val) { - $this->$key = $val; - if ($key == 'sequence_table') { - $hasSetSequence = true; - } - - // only update sequence to reflect new table if they haven't set it manually - if (!$hasSetSequence && $key == 'node_table') { - $this->sequence_table = $this->node_table . '_' . $this->_flparams['id']; - } - if($key == 'cache' && is_object($val)) { - $this->_caching = true; - $GLOBALS['DB_NestedSet'] = & $this; - } - } - - return true; - } - - // }}} - // {{{ setDbOption() - - /** - * 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 - * - * @access public - * @return void - */ - function setDbOption($option, $val) { - $this->db->setOption($option, $val); - } - - // }}} - // {{{ testLock() - - /** - * Tests if a database lock is set - * - * @access public - */ - function testLock() { - if ($this->debug) { - $this->_debugMessage('testLock()'); - } - - if($lockID = $this->_structureTableLock) { - return $lockID; - } - $this->_lockGC(); - $sql = sprintf('SELECT lockID FROM %s WHERE lockTable=%s', - $this->lock_table, - $this->quote($this->node_table)) ; - - $res = $this->db->query($sql); - $this->_testFatalAbort($res, __FILE__, __LINE__); - - if ($this->_numRows($res)) { - return new PEAR_Error($this->_getMessage(NESE_ERROR_TBLOCKED),NESE_ERROR_TBLOCKED); - } - - return false; - } - - // }}} - // {{{ _setLock() - - /** - * @access private - */ - function _setLock($exclusive=false) { - $lock = $this->testLock(); - if(PEAR::isError($lock)) { - return $lock; - } - - if ($this->debug) { - $this->_debugMessage('_setLock()'); - } - if($this->_caching) { - @$this->cache->flush('function_cache'); - $this->_caching = false; - $this->_restcache = true; - } - - if (!$lockID = $this->_structureTableLock) { - $lockID = $this->_structureTableLock = uniqid('lck-'); - - $sql = sprintf('INSERT INTO %s SET lockID=%s, lockTable=%s, lockStamp=%s', - $this->lock_table, - $this->quote($lockID), - $this->quote($this->node_table), - time()); - - } else { - $sql = sprintf('UPDATE %s set lockStamp=%s WHERE lockID=%s AND lockTable=%s', - $this->lock_table, - time(), - $this->quote($lockID), - $this->quote($this->node_table)); - } - if($exclusive) { - $this->_lockExclusive = true; - } - - $res = $this->db->query($sql); - $this->_testFatalAbort($res, __FILE__, __LINE__); - return $lockID; - } - - // }}} - // {{{ _releaseLock() - - /** - * @access private - */ - function _releaseLock($exclusive=false) { - if ($this->debug) { - $this->_debugMessage('_releaseLock()'); - } - - if($exclusive) { - $this->_lockExclusive = false; - } - - if ((!$lockID = $this->_structureTableLock) || $this->_lockExclusive) { - return false; - } - - $tb = $this->lock_table; - $stb = $this->node_table; - $sql = "DELETE FROM $tb - WHERE lockTable=" . $this->quote($stb) . " AND - lockID=" . $this->quote($lockID); - - $res = $this->db->query($sql); - $this->_testFatalAbort($res, __FILE__, __LINE__); - $this->_structureTableLock = false; - if($this->_restcache) { - $this->_caching = true; - $this->_restcache = false; - } - return true; - } - - // }}} - // {{{ _lockGC() - - /** - * @access private - */ - function _lockGC() { - if ($this->debug) { - $this->_debugMessage('_lockGC()'); - } - $tb = $this->lock_table; - $stb = $this->node_table; - $lockTTL = time() - $this->lockTTL; - $sql = "DELETE FROM $tb - WHERE lockTable=" . $this->quote($stb) . " AND - lockStamp < $lockTTL"; - - $res = $this->db->query($sql); - $this->_testFatalAbort($res, __FILE__, __LINE__); - } - - // }}} - // {{{ _values2Query() - - /** - * @access private - */ - function _values2Query($values, $addval = false) { - if ($this->debug) { - $this->_debugMessage('_values2Query($values, $addval = false)'); - } - if (is_array($addval)) { - $values = $values + $addval; - } - - $arq = array(); - foreach($values AS $key => $val) { - $k = trim($key); - $v = trim($val); - if ($k) { - - $arq[] = "$k=" . $this->quote($v); - } - } - - if (!is_array($arq) || count($arq) == 0) { - return false; - } - - $query = implode(', ', $arq); - return $query; - } - - // }}} - // {{{ _verifyUserValues() - - /** - * Clean values from protected or unknown columns - * - * @var string $caller The calling method - * @var string $values The values array - * - * @access private - * @return void - */ - function _verifyUserValues($caller, &$values) { - - if($this->_dumbmode) { - return true; - } - foreach($values AS $field=>$value) { - if(!isset($this->params[$field])) { - $epr = array( - $caller, - sprintf('Unknown column/param \'%s\'', $field)); - $this->_raiseError(NESE_ERROR_WRONG_MPARAM, E_USER_NOTICE, $epr); - unset($values[$field]); - } else { - $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)); - $this->_raiseError(NESE_ERROR_WRONG_MPARAM, E_USER_NOTICE, $epr); - unset($values[$field]); - } - } - } - } - - // }}} - // {{{ _debugMessage() - - /** - * @access private - */ - function _debugMessage($msg) { - if ($this->debug) { - $time = $this->_getmicrotime(); - echo "$time::Debug:: $msg<br />\n"; - } - } - - // }}} - // {{{ _getMessage() - - /** - * @access private - */ - function _getMessage($code) { - if ($this->debug) { - $this->_debugMessage('_getMessage($code)'); - } - return isset($this->messages[$code]) ? $this->messages[$code] : $this->messages[NESE_MESSAGE_UNKNOWN]; - - } - - function _raiseError($code, $option, $epr=array()) { - $message = vsprintf($this->_getMessage($code), $epr); - return PEAR::raiseError($message, $code, PEAR_ERROR_TRIGGER, $option); - } - - // }}} - // {{{ _getmicrotime() - - /** - * @access private - */ - function _getmicrotime() { - list($usec, $sec) = explode(' ', microtime()); - return ((float)$usec + (float)$sec); - } - // }}} - - function convertTreeModel(&$orig, &$copy, $parent=false) { - - static $firstSet; - - $isRoot = false; - if(!$parent) { - if(!is_object($orig) || !is_object($copy)) { - return false; - } - if($orig->node_table == $copy->node_table) { - return false; - } - $copy->_dumbmode = true; - $orig->sortmode = NESE_SORT_LEVEL; - $copy->sortmode = NESE_SORT_LEVEL; - $sibl = $orig->getRootNodes(true); - $isRoot = true; - } else { - $sibl = $orig->getChildren($parent, true); - } - - if(empty($sibl)) { - return false; - } - - foreach($sibl AS $sid=>$sibling) { - unset($sibling['l']); - unset($sibling['r']); - unset($sibling['norder']); - - $values = array(); - foreach($sibling AS $key=>$val) { - if(!isset($copy->_flparams[$key])) { - continue; - } - $values[$copy->_flparams[$key]] = $val; - } - - if(!$firstSet) { - $psid = $copy->createRootNode($values, false, true); - $firstSet = true; - } elseif($isRoot) { - $psid = $copy->createRightNode($psid, $values); - } else { - $copy->createSubNode($parent, $values); - } - - DB_NestedSet::convertTreeModel($orig, $copy, $sid); - } - - } -} - -// {{{ DB_NestedSet_Node:: class - -/** -* Generic class for node objects -* -* @autor Daniel Khan <dk@webcluster.at>; -* @version $Revision: 1.53 $ -* @package DB_NestedSet -* -* @access private -*/ - -class DB_NestedSet_Node { - // {{{ constructor - - /** - * Constructor - */ - function DB_NestedSet_Node($data) { - if (!is_array($data) || count($data) == 0) { - return new PEAR_ERROR($data, NESE_ERROR_PARAM_MISSING); - } - - $this->setAttr($data); - return true; - } - - // }}} - // {{{ setAttr() - - function setAttr($data) { - if(!is_array($data) || count($data) == 0) { - return false; - } - - foreach ($data as $key => $val) { - $this->$key = $val; - } - } - - // }}} - -} -// }}} - - -?> \ No newline at end of file + $this->_flparams['rootid'], + \ No newline at end of file