DB_NestedSet
[ class tree: DB_NestedSet ] [ index: DB_NestedSet ] [ all elements ]

Source for file NestedSet.php

Documentation is available at NestedSet.php

  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PEAR :: DB_NestedSet                                                 |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 1997-2003 The PHP Group                                |
  6. // +----------------------------------------------------------------------+
  7. // | This source file is subject to version 2.0 of the PHP license,       |
  8. // | that is bundled with this package in the file LICENSE, and is        |
  9. // | available at through the world-wide-web at                           |
  10. // | http://www.php.net/license/2_02.txt.                                 |
  11. // | If you did not receive a copy of the PHP license and are unable to   |
  12. // | obtain it through the world-wide-web, please send a note to          |
  13. // | license@php.net so we can mail you a copy immediately.               |
  14. // +----------------------------------------------------------------------+
  15. // | Authors: Daniel Khan <dk@webcluster.at>                              |
  16. // |          Jason Rust  <jason@rustyparts.com>                          |
  17. // +----------------------------------------------------------------------+
  18. // $Id: NestedSet.php,v 1.86 2004/08/10 21:41:28 datenpunk Exp $
  19. // CREDITS:
  20. // --------
  21. // - Thanks to Kristian Koehntopp for publishing an explanation of the Nested Set
  22. // technique and for the great work he did and does for the php community
  23. // - Thanks to Daniel T. Gorski for his great tutorial on www.develnet.org
  24. // - Thanks to my parents for ... just kidding :]
  25. require_once 'PEAR.php';
  26. // {{{ constants
  27. // Error and message codes
  28. define('NESE_ERROR_RECURSION''E100');
  29. define('NESE_ERROR_NODRIVER''E200');
  30. define('NESE_ERROR_NOHANDLER''E300');
  31. define('NESE_ERROR_TBLOCKED''E010');
  32. define('NESE_MESSAGE_UNKNOWN''E0');
  33. define('NESE_ERROR_NOTSUPPORTED''E1');
  34. define('NESE_ERROR_PARAM_MISSING''E400');
  35. define('NESE_ERROR_NOT_FOUND''E500');
  36. define('NESE_ERROR_WRONG_MPARAM''E2');
  37. // for moving a node before another
  38. define('NESE_MOVE_BEFORE''BE');
  39. // for moving a node after another
  40. define('NESE_MOVE_AFTER''AF');
  41. // for moving a node below another
  42. define('NESE_MOVE_BELOW''SUB');
  43. // Sortorders
  44. define('NESE_SORT_LEVEL''SLV');
  45. define('NESE_SORT_PREORDER''SPO');
  46. // }}}
  47. // {{{ DB_NestedSet:: class
  48. /**
  49. * DB_NestedSet is a class for handling nested sets
  50. *
  51. @author Daniel Khan <dk@webcluster.at>
  52. @package DB_NestedSet
  53. @version $Revision: 1.86 $
  54. @access public
  55. */
  56. // }}}
  57. class DB_NestedSet {
  58.     // {{{ properties
  59.     /**
  60.     *
  61.     * @var array The field parameters of the table with the nested set. Format: 'realFieldName' => 'fieldId'
  62.     * @access public
  63.     */
  64.     var $params = array('STRID' => 'id',
  65.     'ROOTID' => 'rootid',
  66.     'l' => 'l',
  67.     'r' => 'r',
  68.     'STREH' => 'norder',
  69.     'LEVEL' => 'level',
  70.     // 'parent'=>'parent', // Optional but very useful
  71.     'STRNA' => 'name'
  72.     );
  73.     // To be used with 2.0 - would be an api break atm
  74.     // var $quotedParams = array('name');
  75.     /**
  76.     *
  77.     * @var string The table with the actual tree data
  78.     * @access public
  79.     */
  80.     var $node_table = 'tb_nodes';
  81.  
  82.     /**
  83.     *
  84.     * @var string The table to handle locking
  85.     * @access public
  86.     */
  87.     var $lock_table = 'tb_locks';
  88.  
  89.     /**
  90.     *
  91.     * @var string The table used for sequences
  92.     * @access public
  93.     */
  94.     var $sequence_table;
  95.  
  96.     /**
  97.     * Secondary order field.  Normally this is the order field, but can be changed to
  98.     * something else (i.e. the name field so that the tree can be shown alphabetically)
  99.     *
  100.     * @var string 
  101.     * @access public
  102.     */
  103.     var $secondarySort;
  104.  
  105.     /**
  106.     * Used to store the secondary sort method set by the user while doing manipulative queries
  107.     *
  108.     * @var string 
  109.     * @access private
  110.     */
  111.     var $_userSecondarySort = false;
  112.  
  113.     /**
  114.     * The default sorting field - will be set to the table column inside the constructor
  115.     *
  116.     * @var string 
  117.     * @access private
  118.     */
  119.     var $_defaultSecondarySort 'norder';
  120.  
  121.     /**
  122.     *
  123.     * @var int The time to live of the lock
  124.     * @access public
  125.     */
  126.     var $lockTTL = 1;
  127.  
  128.     /**
  129.     *
  130.     * @var bool Enable debugging statements?
  131.     * @access public
  132.     */
  133.     var $debug = 0;
  134.  
  135.     /**
  136.     *
  137.     * @var bool Lock the structure of the table?
  138.     * @access private
  139.     */
  140.     var $_structureTableLock = false;
  141.  
  142.     /**
  143.     *
  144.     * @var bool Don't allow unlocking (used inside of moves)
  145.     * @access private
  146.     */
  147.     var $_lockExclusive = false;
  148.  
  149.     /**
  150.     *
  151.     * @var object cache Optional PEAR::Cache object
  152.     * @access public
  153.     */
  154.     var $cache = false;
  155.  
  156.     /**
  157.     * Specify the sortMode of the query methods
  158.     * NESE_SORT_LEVEL is the 'old' sorting method and sorts a tree by level
  159.     * all nodes of level 1, all nodes of level 2,...
  160.     * NESE_SORT_PREORDER will sort doing a preorder walk.
  161.     * So all children of node x will come right after it
  162.     * Note that moving a node within it's siblings will obviously not change the output
  163.     * in this mode
  164.     *
  165.     * @var constant Order method (NESE_SORT_LEVEL|NESE_SORT_PREORDER)
  166.     * @access private
  167.     */
  168.     var $_sortMode = NESE_SORT_LEVEL;
  169.  
  170.     /**
  171.     *
  172.     * @var array Available sortModes
  173.     * @access private
  174.     */
  175.     var $_sortModes = array(NESE_SORT_LEVELNESE_SORT_PREORDER);
  176.  
  177.     /**
  178.     *
  179.     * @var array An array of field ids that must exist in the table
  180.     * @access private
  181.     */
  182.     var $_requiredParams = array('id''rootid''l''r''norder''level');
  183.  
  184.     /**
  185.     *
  186.     * @var bool Skip the callback events?
  187.     * @access private
  188.     */
  189.     var $_skipCallbacks = false;
  190.  
  191.     /**
  192.     *
  193.     * @var bool Do we want to use caching
  194.     * @access private
  195.     */
  196.     var $_caching = false;
  197.  
  198.     /**
  199.     *
  200.     * @var array The above parameters flipped for easy access
  201.     * @access private
  202.     */
  203.     var $flparams = array();
  204.  
  205.     /**
  206.     *
  207.     * @var bool Temporary switch for cache
  208.     * @access private
  209.     */
  210.     var $_restcache = false;
  211.  
  212.     /**
  213.     * Used to determine the presence of listeners for an event in triggerEvent()
  214.     *
  215.     * If any event listeners are registered for an event, the event name will
  216.     * have a key set in this array, otherwise, it will not be set.
  217.     *
  218.     * @see triggerEvent
  219.     * @var arrayg 
  220.     * @access private
  221.     */
  222.     var $_hasListeners = array();
  223.  
  224.     /**
  225.     *
  226.     * @var string packagename
  227.     * @access private
  228.     */
  229.     var $_packagename 'DB_NestedSet';
  230.  
  231.     /**
  232.     *
  233.     * @var int Majorversion
  234.     * @access private
  235.     */
  236.     var $_majorversion = 1;
  237.  
  238.     /**
  239.     *
  240.     * @var string Minorversion
  241.     * @access private
  242.     */
  243.     var $_minorversion '3';
  244.  
  245.     /**
  246.     *
  247.     * @var array Used for mapping a cloned tree to the real tree for move_* operations
  248.     * @access private
  249.     */
  250.     var $_relations = array();
  251.  
  252.     /**
  253.     * Used for _internal_ tree conversion
  254.     *
  255.     * @var bool Turn off user param verification and id generation
  256.     * @access private
  257.     */
  258.     var $_dumbmode = false;
  259.  
  260.     /**
  261.     *
  262.     * @var array Map of error messages to their descriptions
  263.     */
  264.     var $messages = array(
  265.     NESE_ERROR_RECURSION => '%s: This operation would lead to a recursion',
  266.     NESE_ERROR_TBLOCKED => 'The structure Table is locked for another database operation, please retry.',
  267.     NESE_ERROR_NODRIVER => 'The selected database driver %s wasn\'t found',
  268.     NESE_ERROR_NOTSUPPORTED => 'Method not supported yet',
  269.     NESE_ERROR_NOHANDLER => 'Event handler not found',
  270.     NESE_ERROR_PARAM_MISSING => 'Parameter missing',
  271.     NESE_MESSAGE_UNKNOWN => 'Unknown error or message',
  272.     NESE_ERROR_NOT_FOUND => '%s: Node %s not found',
  273.     NESE_ERROR_WRONG_MPARAM => '%s: %s'
  274.     );
  275.  
  276.     /**
  277.     *
  278.     * @var array The array of event listeners
  279.     * @access private
  280.     */
  281.     var $eventListeners = array();
  282.     // }}}
  283.     // +---------------------------------------+
  284.     // | Base methods                          |
  285.     // +---------------------------------------+
  286.     // {{{ constructor
  287.     /**
  288.     * Constructor
  289.     *
  290.     * @param array $params Database column fields which should be returned
  291.     * @access private
  292.     * @return void 
  293.     */
  294.     function DB_NestedSet($params{
  295.         if ($this->debug{
  296.             $this->_debugMessage('DB_NestedSet()');
  297.         }
  298.         if (is_array($params&& count($params> 0{
  299.             $this->params = $params;
  300.         }
  301.  
  302.         $this->flparams array_flip($this->params);
  303.         $this->sequence_table = $this->node_table . '_' $this->flparams['id'];
  304.         $this->secondarySort = $this->flparams[$this->_defaultSecondarySort];
  305.         register_shutdown_function(array($this'_DB_NestedSet'));
  306.     }
  307.     // }}}
  308.     // {{{ destructor
  309.     /**
  310.     * PEAR Destructor
  311.     * Releases all locks
  312.     * Closes open database connections
  313.     *
  314.     * @access private
  315.     * @return void 
  316.     */
  317.     function _DB_NestedSet({
  318.         if ($this->debug{
  319.             $this->_debugMessage('_DB_NestedSet()');
  320.         }
  321.         $this->_releaseLock(true);
  322.     }
  323.     // }}}
  324.     // {{{ factory
  325.     /**
  326.     * Handles the returning of a concrete instance of DB_NestedSet based on the driver.
  327.     * If the class given by $driver allready exists it will be used.
  328.     * If not the driver will be searched inside the default path ./NestedSet/
  329.     *
  330.     * @param string $driver The driver, such as DB or MDB
  331.     * @param string $dsn The dsn for connecting to the database
  332.     * @param array $params The field name params for the node table
  333.     * @static
  334.     * @access public
  335.     * @return object The DB_NestedSet object
  336.     */
  337.     function factory($driver$dsn$params = array()) {
  338.         $classname 'DB_NestedSet_' $driver;
  339.         if (!class_exists($classname)) {
  340.             $driverpath dirname(__FILE__'/NestedSet/' $driver '.php';
  341.             if (!file_exists($driverpath|| !$driver{
  342.                 return PEAR::raiseError("factory(): The database driver '$driver' wasn't found"NESE_ERROR_NODRIVERPEAR_ERROR_TRIGGERE_USER_ERROR);
  343.             }
  344.             include_once($driverpath);
  345.         }
  346.         $c new $classname($dsn$params);
  347.         return $c;
  348.     }
  349.     // }}}
  350.     // +----------------------------------------------+
  351.     // | NestedSet manipulation and query methods     |
  352.     // |----------------------------------------------+
  353.     // | Querying the tree                            |
  354.     // +----------------------------------------------+
  355.     // {{{ getAllNodes()
  356.     /**
  357.     * Fetch the whole NestedSet
  358.     *
  359.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  360.     *                 a set of DB_NestedSet_Node objects?
  361.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  362.     *                 of the parameter keys, or leave them as is?
  363.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  364.     * @access public
  365.     * @return mixed False on error, or an array of nodes
  366.     */
  367.     function getAllNodes($keepAsArray = false$aliasFields = true$addSQL = array()) {
  368.         if ($this->debug{
  369.             $this->_debugMessage('getAllNodes()');
  370.         }
  371.  
  372.         if ($this->_sortMode == NESE_SORT_LEVEL{
  373.             $sql sprintf('SELECT %s %s FROM %s %s %s %s ORDER BY %s.%s, %s.%s ASC',
  374.             $this->_getSelectFields($aliasFields),
  375.             $this->_addSQL($addSQL'cols'),
  376.             $this->node_table,
  377.             $this->_addSQL($addSQL'join'),
  378.             $this->_addSQL($addSQL'where''WHERE'),
  379.             $this->_addSQL($addSQL'append'),
  380.             $this->node_table,
  381.             $this->flparams['level'],
  382.             $this->node_table,
  383.             $this->secondarySort);
  384.  
  385.         elseif ($this->_sortMode == NESE_SORT_PREORDER{
  386.             $nodeSet = array();
  387.             $rootnodes $this->getRootNodes(true);
  388.             foreach($rootnodes AS $rid => $rootnode{
  389.                 $nodeSet $nodeSet $this->getBranch($rootnode$keepAsArray$aliasFields$addSQL);
  390.             }
  391.             return $nodeSet;
  392.         }
  393.  
  394.         if (!$this->_caching{
  395.             $nodeSet $this->_processResultSet($sql$keepAsArray$aliasFields);
  396.         else {
  397.             $nodeSet $this->cache->call('DB_NestedSet->_processResultSet'$sql$keepAsArray$aliasFields);
  398.         }
  399.  
  400.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
  401.             // EVENT (nodeLoad)
  402.             foreach (array_keys($nodeSetas $key{
  403.                 $this->triggerEvent('nodeLoad'$nodeSet[$key]);
  404.             }
  405.         }
  406.         return $nodeSet;
  407.     }
  408.     // }}}
  409.     // {{{ getRootNodes()
  410.     /**
  411.     * Fetches the first level (the rootnodes) of the NestedSet
  412.     *
  413.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  414.     *                 a set of DB_NestedSet_Node objects?
  415.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  416.     *                 of the parameter keys, or leave them as is?
  417.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  418.     * @see _addSQL
  419.     * @access public
  420.     * @return mixed False on error, or an array of nodes
  421.     */
  422.     function getRootNodes($keepAsArray = false$aliasFields = true$addSQL = array()) {
  423.         if ($this->debug{
  424.             $this->_debugMessage('getRootNodes()');
  425.         }
  426.         $sql sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s.%s %s %s ORDER BY %s.%s ASC',
  427.         $this->_getSelectFields($aliasFields),
  428.         $this->_addSQL($addSQL'cols'),
  429.         $this->node_table,
  430.         $this->_addSQL($addSQL'join'),
  431.         $this->node_table,
  432.         $this->flparams['id'],
  433.         $this->node_table,
  434.         $this->flparams['rootid'],
  435.         $this->_addSQL($addSQL'where''AND'),
  436.         $this->_addSQL($addSQL'append'),
  437.         $this->node_table,
  438.         $this->secondarySort);
  439.  
  440.         if (!$this->_caching{
  441.             $nodeSet $this->_processResultSet($sql$keepAsArray$aliasFields);
  442.         else {
  443.             $nodeSet $this->cache->call('DB_NestedSet->_processResultSet'$sql$keepAsArray$aliasFields);
  444.         }
  445.  
  446.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
  447.             // EVENT (nodeLoad)
  448.             foreach (array_keys($nodeSetas $key{
  449.                 $this->triggerEvent('nodeLoad'$nodeSet[$key]);
  450.             }
  451.         }
  452.         return $nodeSet;
  453.     }
  454.     // }}}
  455.     // {{{ getBranch()
  456.     /**
  457.     * Fetch the whole branch where a given node id is in
  458.     *
  459.     * @param int $id The node ID
  460.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  461.     *                 a set of DB_NestedSet_Node objects?
  462.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  463.     *                 of the parameter keys, or leave them as is?
  464.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  465.     * @see _addSQL
  466.     * @access public
  467.     * @return mixed False on error, or an array of nodes
  468.     */
  469.     function getBranch($id$keepAsArray = false$aliasFields = true$addSQL = array()) {
  470.         if ($this->debug{
  471.             $this->_debugMessage('getBranch($id)');
  472.         }
  473.         if (!($thisnode $this->pickNode($idtrue))) {
  474.             $epr = array('getBranch()'$id);
  475.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDPEAR_ERROR_TRIGGERE_USER_NOTICE$epr);
  476.         }
  477.         if ($this->_sortMode == NESE_SORT_LEVEL || $this->params[$this->secondarySort!= $this->_defaultSecondarySort{
  478.             $firstsort $this->flparams['level'];
  479.             $sql sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s %s %s ORDER BY %s.%s, %s.%s ASC',
  480.             $this->_getSelectFields($aliasFields),
  481.             $this->_addSQL($addSQL'cols'),
  482.             $this->node_table,
  483.             $this->_addSQL($addSQL'join'),
  484.             $this->node_table,
  485.             $this->flparams['rootid'],
  486.             $thisnode['rootid'],
  487.             $this->_addSQL($addSQL'where''AND'),
  488.             $this->_addSQL($addSQL'append'),
  489.             $this->node_table,
  490.             $firstsort,
  491.             $this->node_table,
  492.             $this->secondarySort);
  493.         elseif ($this->_sortMode == NESE_SORT_PREORDER{
  494.             $firstsort $this->flparams['l'];
  495.             $sql sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s %s %s ORDER BY %s.%s ASC',
  496.             $this->_getSelectFields($aliasFields),
  497.             $this->_addSQL($addSQL'cols'),
  498.             $this->node_table,
  499.             $this->_addSQL($addSQL'join'),
  500.             $this->node_table,
  501.             $this->flparams['rootid'],
  502.             $thisnode['rootid'],
  503.             $this->_addSQL($addSQL'where''AND'),
  504.             $this->_addSQL($addSQL'append'),
  505.             $this->node_table,
  506.             $firstsort,
  507.             $this->node_table);
  508.         }
  509.  
  510.         if (!$this->_caching{
  511.             $nodeSet $this->_processResultSet($sql$keepAsArray$aliasFields);
  512.         else {
  513.             $nodeSet $this->cache->call('DB_NestedSet->_processResultSet'$sql$keepAsArray$aliasFields);
  514.         }
  515.  
  516.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
  517.             // EVENT (nodeLoad)
  518.             foreach (array_keys($nodeSetas $key{
  519.                 $this->triggerEvent('nodeLoad'$nodeSet[$key]);
  520.             }
  521.         }
  522.  
  523.         if ($this->_sortMode == NESE_SORT_PREORDER && ($this->params[$this->secondarySort!= $this->_defaultSecondarySort)) {
  524.             $nodeSet $this->_secSort($nodeSet);
  525.         }
  526.         return $nodeSet;
  527.     }
  528.     // }}}
  529.     // {{{ getParents()
  530.     /**
  531.     * Fetch the parents of a node given by id
  532.     *
  533.     * @param int $id The node ID
  534.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  535.     *                 a set of DB_NestedSet_Node objects?
  536.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  537.     *                 of the parameter keys, or leave them as is?
  538.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  539.     * @see _addSQL
  540.     * @access public
  541.     * @return mixed False on error, or an array of nodes
  542.     */
  543.     function getParents($id$keepAsArray = false$aliasFields = true$addSQL = array()) {
  544.         if ($this->debug{
  545.             $this->_debugMessage('getParents($id)');
  546.         }
  547.         if (!($child $this->pickNode($idtrue))) {
  548.             $epr = array('getParents()'$id);
  549.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDPEAR_ERROR_TRIGGERE_USER_NOTICE$epr);
  550.         }
  551.  
  552.         $sql sprintf('SELECT %s %s FROM %s %s
  553.                         WHERE %s.%s=%s AND %s.%s<%s AND %s.%s<%s AND %s.%s>%s %s %s
  554.                         ORDER BY %s.%s ASC',
  555.         $this->_getSelectFields($aliasFields),
  556.         $this->_addSQL($addSQL'cols'),
  557.         $this->node_table,
  558.         $this->_addSQL($addSQL'join'),
  559.         $this->node_table,
  560.         $this->flparams['rootid'],
  561.         $child['rootid'],
  562.         $this->node_table,
  563.         $this->flparams['level'],
  564.         $child['level'],
  565.         $this->node_table,
  566.         $this->flparams['l'],
  567.         $child['l'],
  568.         $this->node_table,
  569.         $this->flparams['r'],
  570.         $child['r'],
  571.         $this->_addSQL($addSQL'where''AND'),
  572.         $this->_addSQL($addSQL'append'),
  573.         $this->node_table,
  574.         $this->flparams['level']);
  575.  
  576.         if (!$this->_caching{
  577.             $nodeSet $this->_processResultSet($sql$keepAsArray$aliasFields);
  578.         else {
  579.             $nodeSet $this->cache->call('DB_NestedSet->_processResultSet'$sql$keepAsArray$aliasFields);
  580.         }
  581.  
  582.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
  583.             // EVENT (nodeLoad)
  584.             foreach (array_keys($nodeSetas $key{
  585.                 $this->triggerEvent('nodeLoad'$nodeSet[$key]);
  586.             }
  587.         }
  588.         return $nodeSet;
  589.     }
  590.     // }}}
  591.     // {{{ getParent()
  592.     /**
  593.     * Fetch the immediate parent of a node given by id
  594.     *
  595.     * @param int $id The node ID
  596.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  597.     *                 a set of DB_NestedSet_Node objects?
  598.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  599.     *                 of the parameter keys, or leave them as is?
  600.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  601.     * @see _addSQL
  602.     * @access public
  603.     * @return mixed False on error, or the parent node
  604.     */
  605.     function getParent($id$keepAsArray = false$aliasFields = true$addSQL = array()$useDB = true{
  606.         if ($this->debug{
  607.             $this->_debugMessage('getParent($id)');
  608.         }
  609.         if (!($child $this->pickNode($idtrue))) {
  610.             $epr = array('getParent()'$id);
  611.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDPEAR_ERROR_TRIGGERE_USER_NOTICE$epr);
  612.         }
  613.  
  614.         if ($child['id'== $child['rootid']{
  615.             return false;
  616.         }
  617.         // If parent node is set inside the db simply return it
  618.         if (isset($child['parent']&& !empty($child['parent']&& ($useDB == true)) {
  619.             return $this->pickNode($child['parent']$keepAsArray$aliasFields'id'$addSQL);
  620.         }
  621.  
  622.         $addSQL['where'sprintf('%s.%s = %s',
  623.         $this->node_table,
  624.         $this->flparams['level'],
  625.         $child['level']-1);
  626.  
  627.         $nodeSet $this->getParents($id$keepAsArray$aliasFields$addSQL);
  628.         if (!empty($nodeSet)) {
  629.             $keys array_keys($nodeSet);
  630.             return $nodeSet[$keys[0]];
  631.         else {
  632.             return false;
  633.         }
  634.     }
  635.     // }}}
  636.     // {{{ getSiblings()
  637.     /**
  638.     * Fetch all siblings of the node given by id
  639.     * Important: The node given by ID will also be returned
  640.     * Do a unset($array[$id]) on the result if you don't want that
  641.     *
  642.     * @param int $id The node ID
  643.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  644.     *                 a set of DB_NestedSet_Node objects?
  645.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  646.     *                 of the parameter keys, or leave them as is?
  647.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  648.     * @see _addSQL
  649.     * @access public
  650.     * @return mixed False on error, or the parent node
  651.     */
  652.     function getSiblings($id$keepAsArray = false$aliasFields = true$addSQL = array()) {
  653.         if ($this->debug{
  654.             $this->_debugMessage('getSiblings($id)');
  655.         }
  656.  
  657.         if (!($sibling $this->pickNode($idtrue))) {
  658.             $epr = array('getSibling()'$id);
  659.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDPEAR_ERROR_TRIGGERE_USER_NOTICE$epr);
  660.         }
  661.  
  662.         $parent $this->getParent($siblingtrue);
  663.         return $this->getChildren($parent$keepAsArray$aliasFieldsfalse$addSQL);
  664.     }
  665.     // }}}
  666.     // {{{ getChildren()
  667.     /**
  668.     * Fetch the children _one level_ after of a node given by id
  669.     *
  670.     * @param int $id The node ID
  671.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  672.     *                 a set of DB_NestedSet_Node objects?
  673.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  674.     *                 of the parameter keys, or leave them as is?
  675.     * @param bool $forceNorder (optional) Force the result to be ordered by the norder
  676.     *                 param (as opposed to the value of secondary sort).  Used by the move and
  677.     *                 add methods.
  678.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  679.     * @see _addSQL
  680.     * @access public
  681.     * @return mixed False on error, or an array of nodes
  682.     */
  683.     function getChildren($id$keepAsArray = false$aliasFields = true$forceNorder = false$addSQL = array()) {
  684.         if ($this->debug{
  685.             $this->_debugMessage('getChildren($id)');
  686.         }
  687.  
  688.         if (!($parent $this->pickNode($idtrue))) {
  689.             $epr = array('getChildren()'$id);
  690.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDPEAR_ERROR_TRIGGERE_USER_NOTICE$epr);
  691.         }
  692.         if (!$parent || $parent['l'== ($parent['r'- 1)) {
  693.             return false;
  694.         }
  695.  
  696.         $sql sprintf('SELECT %s %s FROM %s %s
  697.                         WHERE %s.%s=%s AND %s.%s=%s+1 AND %s.%s BETWEEN %s AND %s %s %s
  698.                         ORDER BY %s.%s ASC',
  699.         $this->_getSelectFields($aliasFields)$this->_addSQL($addSQL'cols'),
  700.         $this->node_table$this->_addSQL($addSQL'join'),
  701.         $this->node_table$this->flparams['rootid']$parent['rootid'],
  702.         $this->node_table$this->flparams['level']$parent['level'],
  703.         $this->node_table$this->flparams['l']$parent['l']$parent['r'],
  704.         $this->_addSQL($addSQL'where''AND'),
  705.         $this->_addSQL($addSQL'append'),
  706.         $this->node_table$this->secondarySort);
  707.         if (!$this->_caching{
  708.             $nodeSet $this->_processResultSet($sql$keepAsArray$aliasFields);
  709.         else {
  710.             $nodeSet $this->cache->call('DB_NestedSet->_processResultSet'$sql$keepAsArray$aliasFields);
  711.         }
  712.  
  713.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
  714.             // EVENT (nodeLoad)
  715.             foreach (array_keys($nodeSetas $key{
  716.                 $this->triggerEvent('nodeLoad'$nodeSet[$key]);
  717.             }
  718.         }
  719.         return $nodeSet;
  720.     }
  721.     // }}}
  722.     // {{{ getSubBranch()
  723.     /**
  724.     * Fetch all the children of a node given by id
  725.     *
  726.     * getChildren only queries the immediate children
  727.     * getSubBranch returns all nodes below the given node
  728.     *
  729.     * @param string $id The node ID
  730.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  731.     *                 a set of DB_NestedSet_Node objects?
  732.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  733.     *                 of the parameter keys, or leave them as is?
  734.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  735.     * @see _addSQL
  736.     * @access public
  737.     * @return mixed False on error, or an array of nodes
  738.     */
  739.     function getSubBranch($id$keepAsArray = false$aliasFields = true$addSQL = array()) {
  740.         if ($this->debug{
  741.             $this->_debugMessage('getSubBranch($id)');
  742.         }
  743.         if (!($parent $this->pickNode($idtrue))) {
  744.             $epr = array('getSubBranch()'$id);
  745.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDE_USER_NOTICE$epr);
  746.         }
  747.         if ($this->_sortMode == NESE_SORT_LEVEL || $this->params[$this->secondarySort!= $this->_defaultSecondarySort{
  748.             $firstsort $this->flparams['level'];
  749.             $sql sprintf('SELECT %s %s FROM %s %s
  750.                     WHERE %s.%s BETWEEN %s AND %s AND %s.%s=%s AND %s.%s!=%s %s %s
  751.                     ORDER BY %s.%s, %s.%s ASC',
  752.             $this->_getSelectFields($aliasFields)$this->_addSQL($addSQL'cols'),
  753.             $this->node_table$this->_addSQL($addSQL'join'),
  754.             $this->node_table$this->flparams['l']$parent['l']$parent['r'],
  755.             $this->node_table$this->flparams['rootid']$parent['rootid'],
  756.             $this->node_table$this->flparams['id']$id$this->_addSQL($addSQL'where''AND')$this->_addSQL($addSQL'append'),
  757.             $this->node_table$firstsort,
  758.             $this->node_table$this->secondarySort);
  759.         elseif ($this->_sortMode == NESE_SORT_PREORDER{
  760.             $firstsort $this->flparams['l'];
  761.  
  762.             $sql sprintf('SELECT %s %s FROM %s %s
  763.                     WHERE %s.%s BETWEEN %s AND %s AND %s.%s=%s AND %s.%s!=%s %s %s
  764.                     ORDER BY %s.%s ASC',
  765.             $this->_getSelectFields($aliasFields)$this->_addSQL($addSQL'cols'),
  766.             $this->node_table,
  767.             $this->_addSQL($addSQL'join'),
  768.             $this->node_table,
  769.             $this->flparams['l'],
  770.             $parent['l'],
  771.             $parent['r'],
  772.             $this->node_table,
  773.             $this->flparams['rootid'],
  774.             $parent['rootid'],
  775.             $this->node_table,
  776.             $this->flparams['id'],
  777.             $id,
  778.             $this->_addSQL($addSQL'where''AND'),
  779.             $this->_addSQL($addSQL'append'),
  780.             $this->node_table,
  781.             $firstsort);
  782.         }
  783.  
  784.         if (!$this->_caching{
  785.             $nodeSet $this->_processResultSet($sql$keepAsArray$aliasFields);
  786.         else {
  787.             $nodeSet $this->cache->call('DB_NestedSet->_processResultSet'$sql$keepAsArray$aliasFields);
  788.         }
  789.  
  790.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
  791.             // EVENT (nodeLoad)
  792.             foreach (array_keys($nodeSetas $key{
  793.                 $this->triggerEvent('nodeLoad'$nodeSet[$key]);
  794.             }
  795.         }
  796.  
  797.         if ($this->_sortMode == NESE_SORT_PREORDER && ($this->params[$this->secondarySort!= $this->_defaultSecondarySort)) {
  798.             $nodeSet $this->_secSort($nodeSet);
  799.         }
  800.         return $nodeSet;
  801.     }
  802.     // }}}
  803.     // {{{ pickNode()
  804.     /**
  805.     * Fetch the data of a node with the given id
  806.     *
  807.     * @param int $id The node id of the node to fetch
  808.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  809.     *                 a set of DB_NestedSet_Node objects?
  810.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  811.     *                 of the parameter keys, or leave them as is?
  812.     * @param string $idfield (optional) Which field has to be compared with $id?
  813.     *                  This is can be used to pick a node by other values (e.g. it's name).
  814.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  815.     * @see _addSQL
  816.     * @access public
  817.     * @return mixed False on error, or an array of nodes
  818.     */
  819.     function pickNode($id$keepAsArray = false$aliasFields = true$idfield 'id'$addSQL = array()) {
  820.         if ($this->debug{
  821.             $this->_debugMessage('pickNode($id)');
  822.         }
  823.  
  824.         if (is_object($id&& $id->id{
  825.             return $id;
  826.         elseif (is_array($id&& isset($id['id'])) {
  827.             return $id;
  828.         }
  829.  
  830.         if (!$id{
  831.             return false;
  832.         }
  833.  
  834.         $sql sprintf("SELECT %s %s FROM %s %s WHERE %s.%s=%s %s %s",
  835.         $this->_getSelectFields($aliasFields)$this->_addSQL($addSQL'cols'),
  836.         $this->node_table$this->_addSQL($addSQL'join'),
  837.         $this->node_table$this->flparams[$idfield]$this->_quote($id),
  838.         $this->_addSQL($addSQL'where''AND'),
  839.         $this->_addSQL($addSQL'append'));
  840.         if (!$this->_caching{
  841.             $nodeSet $this->_processResultSet($sql$keepAsArray$aliasFields);
  842.         else {
  843.             $nodeSet $this->cache->call('DB_NestedSet->_processResultSet'$sql$keepAsArray$aliasFields);
  844.         }
  845.  
  846.         $nsKey = false;
  847.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
  848.             // EVENT (nodeLoad)
  849.             foreach (array_keys($nodeSetas $key{
  850.                 $this->triggerEvent('nodeLoad'$nodeSet[$key]);
  851.                 $nsKey $key;
  852.             }
  853.         else {
  854.             foreach (array_keys($nodeSetas $key{
  855.                 $nsKey $key;
  856.             }
  857.         }
  858.  
  859.         if (is_array($nodeSet&& $idfield != 'id'{
  860.             $id $nsKey;
  861.         }
  862.  
  863.         return isset($nodeSet[$id]$nodeSet[$id: false;
  864.     }
  865.     // }}}
  866.     // {{{ isParent()
  867.     /**
  868.     * See if a given node is a parent of another given node
  869.     *
  870.     * A node is considered to be a parent if it resides above the child
  871.     * So it doesn't mean that the node has to be an immediate parent.
  872.     * To get this information simply compare the levels of the two nodes
  873.     * after you know that you have a parent relation.
  874.     *
  875.     * @param mixed $parent The parent node as array or object
  876.     * @param mixed $child The child node as array or object
  877.     * @access public
  878.     * @return bool True if it's a parent
  879.     */
  880.     function isParent($parent$child{
  881.         if ($this->debug{
  882.             $this->_debugMessage('isParent($parent, $child)');
  883.         }
  884.  
  885.         if (!isset($parent|| !isset($child)) {
  886.             return false;
  887.         }
  888.  
  889.         if (is_array($parent)) {
  890.             $p_rootid $parent['rootid'];
  891.             $p_l $parent['l'];
  892.             $p_r $parent['r'];
  893.         elseif (is_object($parent)) {
  894.             $p_rootid $parent->rootid;
  895.             $p_l $parent->l;
  896.             $p_r $parent->r;
  897.         }
  898.  
  899.         if (is_array($child)) {
  900.             $c_rootid $child['rootid'];
  901.             $c_l $child['l'];
  902.             $c_r $child['r'];
  903.         elseif (is_object($child)) {
  904.             $c_rootid $child->rootid;
  905.             $c_l $child->l;
  906.             $c_r $child->r;
  907.         }
  908.  
  909.         if (($p_rootid == $c_rootid&& ($p_l $c_l && $p_r $c_r)) {
  910.             return true;
  911.         }
  912.  
  913.         return false;
  914.     }
  915.     // }}}
  916.     // +----------------------------------------------+
  917.     // | NestedSet manipulation and query methods     |
  918.     // |----------------------------------------------+
  919.     // | insert / delete / update of nodes            |
  920.     // +----------------------------------------------+
  921.     // | [PUBLIC]                                     |
  922.     // +----------------------------------------------+
  923.     // {{{ createRootNode()
  924.     /**
  925.     * Creates a new root node.  If no id is specified then it is either
  926.     * added to the beginning/end of the tree based on the $pos.
  927.     * Optionally it deletes the whole tree and creates one initial rootnode
  928.     *
  929.     * <pre>
  930.     * +-- root1 [target]
  931.     * |
  932.     * +-- root2 [new]
  933.     * |
  934.     * +-- root3
  935.     * </pre>
  936.     *
  937.     * @param array $values Hash with param => value pairs of the node (see $this->params)
  938.     * @param integer $id ID of target node (the rootnode after which the node should be inserted)
  939.     * @param bool $first Danger: Deletes and (re)init's the hole tree - sequences are reset
  940.     * @param string $pos The position in which to insert the new node.
  941.     * @access public
  942.     * @return mixed The node id or false on error
  943.     */
  944.     function createRootNode($values$id = false$first = false$pos = NESE_MOVE_AFTER{
  945.         if ($this->debug{
  946.             $this->_debugMessage('createRootNode($values, $id = false, $first = false, $pos = \'AF\')');
  947.         }
  948.         // Try to aquire a table lock
  949.         if (PEAR::isError($lock $this->_setLock())) {
  950.             return $lock;
  951.         }
  952.         $this->_verifyUserValues('createRootNode()'$values);
  953.         // If they specified an id, see if the parent is valid
  954.         if (!$first && ($id && !$parent $this->pickNode($idtrue))) {
  955.             $epr = array('createRootNode()'$id);
  956.             $this->_releaseLock();
  957.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDPEAR_ERROR_TRIGGERE_USER_ERROR$epr);
  958.         elseif ($first && $id{
  959.             // No notice for now.  But these 2 params don't make sense together
  960.             $epr = array('createRootNode()''[id] AND [first] were passed - that doesn\'t make sense');
  961.             // $this->_raiseError(NESE_ERROR_WRONG_MPARAM, E_USER_WARNING, $epr);
  962.         elseif (!$first && !$id{
  963.             // If no id was specified, then determine order
  964.             $parent = array();
  965.             if ($pos == NESE_MOVE_BEFORE{
  966.                 $parent['norder'= 1;
  967.             elseif ($pos == NESE_MOVE_AFTER{
  968.                 // Put it at the end of the tree
  969.                 $qry sprintf('SELECT MAX(%s) FROM %s WHERE %s=1',
  970.                 $this->flparams['norder'],
  971.                 $this->node_table,
  972.                 $this->flparams['l']);
  973.                 $tmp_order $this->_getOne($qry);
  974.                 // If null, then it's the first one
  975.                 $parent['norder'is_null($tmp_order? 0 : $tmp_order;
  976.             }
  977.         }
  978.  
  979.  
  980.         $sql = array();
  981.         $addval = array();
  982.         $addval[$this->flparams['level']] = 1;
  983.         // Shall we delete the existing tree (reinit)
  984.         if ($first{
  985.             $dsql sprintf('DELETE FROM %s'$this->node_table);
  986.             $this->_query($dsql);
  987.             $this->_dropSequence($this->sequence_table);
  988.             // New order of the new node will be 1
  989.             $addval[$this->flparams['norder']] = 1;
  990.         else {
  991.             // Let's open a gap for the new node
  992.             if ($pos == NESE_MOVE_AFTER{
  993.                 $addval[$this->flparams['norder']] $parent['norder'+ 1;
  994.                 $sql[sprintf('UPDATE %s SET %s=%s+1 WHERE %s=1 AND %s > %s',
  995.                 $this->node_table,
  996.                 $this->flparams['norder']$this->flparams['norder'],
  997.                 $this->flparams['l'],
  998.                 $this->flparams['norder']$parent['norder']);
  999.             elseif ($pos == NESE_MOVE_BEFORE{
  1000.                 $addval[$this->flparams['norder']] $parent['norder'];
  1001.                 $sql[sprintf('UPDATE %s SET %s=%s+1 WHERE %s=1 AND %s >= %s',
  1002.                 $this->node_table,
  1003.                 $this->flparams['norder']$this->flparams['norder'],
  1004.                 $this->flparams['l'],
  1005.                 $this->flparams['norder']$parent['norder']);
  1006.             }
  1007.         }
  1008.  
  1009.         if (isset($this->flparams['parent'])) {
  1010.             $addval[$this->flparams['parent']] = 0;
  1011.         }
  1012.         // Sequence of node id (equals to root id in this case
  1013.         if (!$this->_dumbmode || !$node_id = isset($values[$this->flparams['id']]|| !isset($values[$this->flparams['rootid']])) {
  1014.             $addval[$this->flparams['rootid']] $node_id $addval[$this->flparams['id']] $this->_nextID($this->sequence_table);
  1015.         else {
  1016.             $node_id $values[$this->flparams['id']];
  1017.         }
  1018.         // Left/Right values for rootnodes
  1019.         $addval[$this->flparams['l']] = 1;
  1020.         $addval[$this->flparams['r']] = 2;
  1021.         // Transform the node data hash to a query
  1022.         if (!$qr $this->_values2InsertQuery($values$addval)) {
  1023.             $this->_releaseLock();
  1024.             return false;
  1025.         }
  1026.         // Insert the new node
  1027.         $sql[sprintf('INSERT INTO %s (%s) VALUES (%s)'$this->node_tableimplode(', 'array_keys($qr))implode(', '$qr));
  1028.         foreach ($sql as $qry{
  1029.             $res $this->_query($qry);
  1030.             $this->_testFatalAbort($res__FILE____LINE__);
  1031.         }
  1032.         // EVENT (nodeCreate)
  1033.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeCreate'])) {
  1034.             $thisnode $this->pickNode($node_id);
  1035.             $this->triggerEvent('nodeCreate'$thisnode);
  1036.         }
  1037.         $this->_releaseLock();
  1038.         return $node_id;
  1039.     }
  1040.     // }}}
  1041.     // {{{ createSubNode()
  1042.     /**
  1043.     * Creates a subnode
  1044.     *
  1045.     * <pre>
  1046.     * +-- root1
  1047.     * |
  1048.     * +-\ root2 [target]
  1049.     * | |
  1050.     * | |-- subnode1 [new]
  1051.     * |
  1052.     * +-- root3
  1053.     * </pre>
  1054.     *
  1055.     * @param integer $id Parent node ID
  1056.     * @param array $values Hash with param => value pairs of the node (see $this->params)
  1057.     * @access public
  1058.     * @return mixed The node id or false on error
  1059.     */
  1060.     function createSubNode($id$values{
  1061.         if ($this->debug{
  1062.             $this->_debugMessage('createSubNode($id, $values)');
  1063.         }
  1064.  
  1065.         // Try to aquire a table lock
  1066.         if (PEAR::isError($lock $this->_setLock())) {
  1067.             return $lock;
  1068.         }
  1069.  
  1070.         // invalid parent id, bail out
  1071.         if (!($thisnode $this->pickNode($idtrue))) {
  1072.             $epr = array('createSubNode()'$id);
  1073.             $this->_releaseLock();
  1074.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDPEAR_ERROR_TRIGGERE_USER_ERROR$epr);
  1075.         }
  1076.  
  1077.         $this->_verifyUserValues('createRootNode()'$values);
  1078.         // Get the children of the target node
  1079.         $children $this->getChildren($idtrue);
  1080.         // We have children here
  1081.         if ($thisnode['r']-1 != $thisnode['l']{
  1082.             // Get the last child
  1083.             $last array_pop($children);
  1084.             // What we have to do is virtually an insert of a node after the last child
  1085.             // So we don't have to proceed creating a subnode
  1086.             $newNode $this->createRightNode($last['id']$values);
  1087.             $this->_releaseLock();
  1088.             return $newNode;
  1089.         }
  1090.  
  1091.         $sql = array();
  1092.         $sql[sprintf('UPDATE %s SET
  1093.                 %s=CASE WHEN %s>%s THEN %s+2 ELSE %s END,
  1094.                 %s=CASE WHEN (%s>%s OR %s>=%s) THEN %s+2 ELSE %s END
  1095.                 WHERE %s=%s',
  1096.         $this->node_table,
  1097.         $this->flparams['l'],
  1098.         $this->flparams['l']$thisnode['l'],
  1099.         $this->flparams['l']$this->flparams['l'],
  1100.         $this->flparams['r'],
  1101.         $this->flparams['l']$thisnode['l'],
  1102.         $this->flparams['r']$thisnode['r'],
  1103.         $this->flparams['r']$this->flparams['r'],
  1104.         $this->flparams['rootid']$thisnode['rootid']);
  1105.         $addval = array();
  1106.         if (isset($this->flparams['parent'])) {
  1107.             $addval[$this->flparams['parent']] $thisnode['id'];
  1108.         }
  1109.  
  1110.         $addval[$this->flparams['l']] $thisnode['r'];
  1111.         $addval[$this->flparams['r']] $thisnode['r'+ 1;
  1112.         $addval[$this->flparams['rootid']] $thisnode['rootid'];
  1113.         $addval[$this->flparams['norder']] = 1;
  1114.         $addval[$this->flparams['level']] $thisnode['level'+ 1;
  1115.         if (!$this->_dumbmode || !$node_id = isset($values[$this->flparams['id']])) {
  1116.             $node_id $addval[$this->flparams['id']] $this->_nextID($this->sequence_table);
  1117.         else {
  1118.             $node_id $values[$this->flparams['id']];
  1119.         }
  1120.  
  1121.         if (!$qr $this->_values2InsertQuery($values$addval)) {
  1122.             $this->_releaseLock();
  1123.             return false;
  1124.         }
  1125.  
  1126.         $sql[sprintf('INSERT INTO %s (%s) VALUES (%s)'$this->node_tableimplode(', 'array_keys($qr))implode(', '$qr));
  1127.         foreach ($sql as $qry{
  1128.             $res $this->_query($qry);
  1129.             $this->_testFatalAbort($res__FILE____LINE__);
  1130.         }
  1131.         // EVENT (NodeCreate)
  1132.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeCreate'])) {
  1133.             $thisnode $this->pickNode($node_id);
  1134.             $this->triggerEvent('nodeCreate'$thisnode);
  1135.         }
  1136.         $this->_releaseLock();
  1137.         return $node_id;
  1138.     }
  1139.     // }}}
  1140.     // {{{ createLeftNode()
  1141.     /**
  1142.     * Creates a node before a given node
  1143.     * <pre>
  1144.     * +-- root1
  1145.     * |
  1146.     * +-\ root2
  1147.     * | |
  1148.     * | |-- subnode2 [new]
  1149.     * | |-- subnode1 [target]
  1150.     * | |-- subnode3
  1151.     * |
  1152.     * +-- root3
  1153.     * </pre>
  1154.     *
  1155.     * @param int $id Target node ID
  1156.     * @param array $values Hash with param => value pairs of the node (see $this->params)
  1157.     * @param bool $returnID Tell the method to return a node id instead of an object.
  1158.     *                                    ATTENTION: That the method defaults to return an object instead of the node id
  1159.     *                                    has been overseen and is basically a bug. We have to keep this to maintain BC.
  1160.     *                                    You will have to set $returnID to true to make it behave like the other creation methods.
  1161.     *                                    This flaw will get fixed with the next major version.
  1162.     * @access public
  1163.     * @return mixed The node id or false on error
  1164.     */
  1165.     function createLeftNode($id$values{
  1166.         if ($this->debug{
  1167.             $this->_debugMessage('createLeftNode($target, $values)');
  1168.         }
  1169.  
  1170.         if (PEAR::isError($lock $this->_setLock())) {
  1171.             return $lock;
  1172.         }
  1173.  
  1174.         $this->_verifyUserValues('createLeftode()'$values);
  1175.         // invalid target node, bail out
  1176.         if (!($thisnode $this->pickNode($idtrue))) {
  1177.             $epr = array('createLeftNode()'$id);
  1178.             $this->_releaseLock();
  1179.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDPEAR_ERROR_TRIGGERE_USER_ERROR$epr);
  1180.         }
  1181.  
  1182.  
  1183.         // If the target node is a rootnode we virtually want to create a new root node
  1184.         if ($thisnode['rootid'== $thisnode['id']{
  1185.             $this->_releaseLock();
  1186.             return $this->createRootNode($values$idfalseNESE_MOVE_BEFORE);
  1187.         }
  1188.  
  1189.         $addval = array();
  1190.         $parent $this->getParent($idtrue);
  1191.         if (isset($this->flparams['parent'])) {
  1192.             $addval[$this->flparams['parent']] $parent['id'];
  1193.         }
  1194.  
  1195.         $sql = array();
  1196.         $sql[sprintf('UPDATE %s SET %s=%s+1
  1197.                         WHERE %s=%s AND %s>=%s AND %s=%s AND %s BETWEEN %s AND %s',
  1198.         $this->node_table,
  1199.         $this->flparams['norder']$this->flparams['norder'],
  1200.         $this->flparams['rootid']$thisnode['rootid'],
  1201.         $this->flparams['norder']$thisnode['norder'],
  1202.         $this->flparams['level']$thisnode['level'],
  1203.         $this->flparams['l']$parent['l']$parent['r']);
  1204.         // Update all nodes which have dependent left and right values
  1205.         $sql[sprintf('UPDATE %s SET
  1206.                 %s=CASE WHEN %s>=%s THEN %s+2 ELSE %s END,
  1207.                 %s=CASE WHEN (%s>=%s OR %s>=%s) THEN %s+2 ELSE %s END
  1208.                 WHERE %s=%s',
  1209.         $this->node_table,
  1210.         $this->flparams['l'],
  1211.         $this->flparams['l']$thisnode['l'],
  1212.         $this->flparams['l']$this->flparams['l'],
  1213.         $this->flparams['r'],
  1214.         $this->flparams['r']$thisnode['r'],
  1215.         $this->flparams['l']$thisnode['l'],
  1216.         $this->flparams['r']$this->flparams['r'],
  1217.         $this->flparams['rootid']$thisnode['rootid']);
  1218.         $addval[$this->flparams['norder']] $thisnode['norder'];
  1219.         $addval[$this->flparams['l']] $thisnode['l'];
  1220.         $addval[$this->flparams['r']] $thisnode['l'+ 1;
  1221.         $addval[$this->flparams['rootid']] $thisnode['rootid'];
  1222.         $addval[$this->flparams['level']] $thisnode['level'];
  1223.         if (!$this->_dumbmode || !$node_id = isset($values[$this->flparams['id']])) {
  1224.             $node_id $addval[$this->flparams['id']] $this->_nextID($this->sequence_table);
  1225.         else {
  1226.             $node_id $values[$this->flparams['id']];
  1227.         }
  1228.  
  1229.         if (!$qr $this->_values2InsertQuery($values$addval)) {
  1230.             $this->_releaseLock();
  1231.             return false;
  1232.         }
  1233.         // Insert the new node
  1234.         $sql[sprintf('INSERT INTO %s (%s) VALUES (%s)'$this->node_tableimplode(', 'array_keys($qr))implode(', '$qr));
  1235.         foreach ($sql as $qry{
  1236.             $res $this->_query($qry);
  1237.             $this->_testFatalAbort($res__FILE____LINE__);
  1238.         }
  1239.         // EVENT (NodeCreate)
  1240.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeCreate'])) {
  1241.             $thisnode $this->pickNode($node_id);
  1242.             $this->triggerEvent('nodeCreate'$thisnode);
  1243.         }
  1244.         $this->_releaseLock();
  1245.         return $node_id;
  1246.     }
  1247.     // }}}
  1248.     // {{{ createRightNode()
  1249.     /**
  1250.     * Creates a node after a given node
  1251.     * <pre>
  1252.     * +-- root1
  1253.     * |
  1254.     * +-\ root2
  1255.     * | |
  1256.     * | |-- subnode1 [target]
  1257.     * | |-- subnode2 [new]
  1258.     * | |-- subnode3
  1259.     * |
  1260.     * +-- root3
  1261.     * </pre>
  1262.     *
  1263.     * @param int $id Target node ID
  1264.     * @param array $values Hash with param => value pairs of the node (see $this->params)
  1265.     * @param bool $returnID Tell the method to return a node id instead of an object.
  1266.     *                                    ATTENTION: That the method defaults to return an object instead of the node id
  1267.     *                                    has been overseen and is basically a bug. We have to keep this to maintain BC.
  1268.     *                                    You will have to set $returnID to true to make it behave like the other creation methods.
  1269.     *                                    This flaw will get fixed with the next major version.
  1270.     * @access public
  1271.     * @return mixed The node id or false on error
  1272.     */
  1273.     function createRightNode($id$values{
  1274.         if ($this->debug{
  1275.             $this->_debugMessage('createRightNode($target, $values)');
  1276.         }
  1277.  
  1278.         if (PEAR::isError($lock $this->_setLock())) {
  1279.             return $lock;
  1280.         }
  1281.  
  1282.         $this->_verifyUserValues('createRootNode()'$values);
  1283.         // invalid target node, bail out
  1284.         if (!($thisnode $this->pickNode($idtrue))) {
  1285.             $epr = array('createRightNode()'$id);
  1286.             $this->_releaseLock();
  1287.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDPEAR_ERROR_TRIGGERE_USER_ERROR$epr);
  1288.         }
  1289.  
  1290.  
  1291.         // If the target node is a rootnode we virtually want to create a new root node
  1292.         if ($thisnode['rootid'== $thisnode['id']{
  1293.             $nid $this->createRootNode($values$id);
  1294.             $this->_releaseLock();
  1295.             return $nid;
  1296.         }
  1297.  
  1298.         $addval = array();
  1299.         $parent $this->getParent($idtrue);
  1300.         if (isset($this->flparams['parent'])) {
  1301.             $addval[$this->flparams['parent']] $parent['id'];
  1302.         }
  1303.  
  1304.         $sql = array();
  1305.         $sql[sprintf('UPDATE %s SET %s=%s+1
  1306.                         WHERE %s=%s AND %s>%s AND %s=%s AND %s BETWEEN %s AND %s',
  1307.         $this->node_table,
  1308.         $this->flparams['norder']$this->flparams['norder'],
  1309.         $this->flparams['rootid']$thisnode['rootid'],
  1310.         $this->flparams['norder']$thisnode['norder'],
  1311.         $this->flparams['level']$thisnode['level'],
  1312.         $this->flparams['l']$parent['l']$parent['r']);
  1313.         // Update all nodes which have dependent left and right values
  1314.         $sql[sprintf('UPDATE %s SET
  1315.                 %s=CASE WHEN (%s>%s AND %s>%s) THEN %s+2 ELSE %s END,
  1316.                 %s=CASE WHEN %s>%s THEN %s+2 ELSE %s END
  1317.                 WHERE %s=%s',
  1318.         $this->node_table,
  1319.         $this->flparams['l'],
  1320.         $this->flparams['l']$thisnode['l'],
  1321.         $this->flparams['r']$thisnode['r'],
  1322.         $this->flparams['l']$this->flparams['l'],
  1323.         $this->flparams['r'],
  1324.         $this->flparams['r']$thisnode['r'],
  1325.         $this->flparams['r']$this->flparams['r'],
  1326.         $this->flparams['rootid']$thisnode['rootid']);
  1327.         $addval[$this->flparams['norder']] $thisnode['norder'+ 1;
  1328.         $addval[$this->flparams['l']] $thisnode['r'+ 1;
  1329.         $addval[$this->flparams['r']] $thisnode['r'+ 2;
  1330.         $addval[$this->flparams['rootid']] $thisnode['rootid'];
  1331.         $addval[$this->flparams['level']] $thisnode['level'];
  1332.         if (!$this->_dumbmode || !isset($values[$this->flparams['id']])) {
  1333.             $node_id $addval[$this->flparams['id']] $this->_nextID($this->sequence_table);
  1334.         else {
  1335.             $node_id $values[$this->flparams['id']];
  1336.         }
  1337.  
  1338.         if (!$qr $this->_values2InsertQuery($values$addval)) {
  1339.             $this->_releaseLock();
  1340.             return false;
  1341.         }
  1342.         // Insert the new node
  1343.         $sql[sprintf('INSERT INTO %s (%s) VALUES (%s)'$this->node_tableimplode(', 'array_keys($qr))implode(', '$qr));
  1344.         foreach ($sql as $qry{
  1345.             $res $this->_query($qry);
  1346.             $this->_testFatalAbort($res__FILE____LINE__);
  1347.         }
  1348.         // EVENT (NodeCreate)
  1349.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeCreate'])) {
  1350.             $thisnode $this->pickNode($node_id);
  1351.             $this->triggerEvent('nodeCreate'$thisnode);
  1352.         }
  1353.         $this->_releaseLock();
  1354.         return $node_id;
  1355.     }
  1356.     // }}}
  1357.     // {{{ deleteNode()
  1358.     /**
  1359.     * Deletes a node
  1360.     *
  1361.     * @param int $id ID of the node to be deleted
  1362.     * @access public
  1363.     * @return bool True if the delete succeeds
  1364.     */
  1365.     function deleteNode($id{
  1366.         if ($this->debug{
  1367.             $this->_debugMessage("deleteNode($id)");
  1368.         }
  1369.         if (PEAR::isError($lock $this->_setLock())) {
  1370.             return $lock;
  1371.         }
  1372.  
  1373.         // invalid target node, bail out
  1374.         if (!($thisnode $this->pickNode($idtrue))) {
  1375.             $epr = array('deleteNode()'$id);
  1376.             $this->_releaseLock();
  1377.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDPEAR_ERROR_TRIGGERE_USER_ERROR$epr);
  1378.         }
  1379.  
  1380.  
  1381.  
  1382.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeDelete'])) {
  1383.             // EVENT (NodeDelete)
  1384.             $this->triggerEvent('nodeDelete'$this->pickNode($id));
  1385.         }
  1386.  
  1387.         $parent $this->getParent($idtrue);
  1388.         $len $thisnode['r'$thisnode['l'+ 1;
  1389.         $sql = array();
  1390.         // Delete the node
  1391.         $sql[sprintf('DELETE FROM %s WHERE %s BETWEEN %s AND %s AND %s=%s',
  1392.         $this->node_table,
  1393.         $this->flparams['l']$thisnode['l']$thisnode['r'],
  1394.         $this->flparams['rootid']$thisnode['rootid']);
  1395.         if ($thisnode['id'!= $thisnode['rootid']{
  1396.             // The node isn't a rootnode so close the gap
  1397.             $sql[sprintf('UPDATE %s SET
  1398.                             %s=CASE WHEN %s>%s THEN %s-%s ELSE %s END,
  1399.                             %s=CASE WHEN %s>%s THEN %s-%s ELSE %s END
  1400.                             WHERE %s=%s AND (%s>%s OR %s>%s)',
  1401.             $this->node_table,
  1402.             $this->flparams['l'],
  1403.             $this->flparams['l']$thisnode['l'],
  1404.             $this->flparams['l']$len$this->flparams['l'],
  1405.             $this->flparams['r'],
  1406.             $this->flparams['r']$thisnode['l'],
  1407.             $this->flparams['r']$len$this->flparams['r'],
  1408.             $this->flparams['rootid']$thisnode['rootid'],
  1409.             $this->flparams['l']$thisnode['l'],
  1410.             $this->flparams['r']$thisnode['r']);
  1411.             // Re-order
  1412.             $sql[sprintf('UPDATE %s SET %s=%s-1
  1413.                     WHERE %s=%s AND %s=%s AND %s>%s AND %s BETWEEN %s AND %s',
  1414.             $this->node_table,
  1415.             $this->flparams['norder']$this->flparams['norder'],
  1416.             $this->flparams['rootid']$thisnode['rootid'],
  1417.             $this->flparams['level']$thisnode['level'],
  1418.             $this->flparams['norder']$thisnode['norder'],
  1419.             $this->flparams['l']$parent['l']$parent['r']);
  1420.         else {
  1421.             // A rootnode was deleted and we only have to close the gap inside the order
  1422.             $sql[sprintf('UPDATE %s SET %s=%s-1 WHERE %s=%s AND %s > %s',
  1423.             $this->node_table,
  1424.             $this->flparams['norder']$this->flparams['norder'],
  1425.             $this->flparams['rootid']$this->flparams['id'],
  1426.             $this->flparams['norder']$thisnode['norder']);
  1427.         }
  1428.  
  1429.         foreach ($sql as $qry{
  1430.             $res $this->_query($qry);
  1431.             $this->_testFatalAbort($res__FILE____LINE__);
  1432.         }
  1433.         $this->_releaseLock();
  1434.         return true;
  1435.     }
  1436.     // }}}
  1437.     // {{{ updateNode()
  1438.     /**
  1439.     * Changes the payload of a node
  1440.     *
  1441.     * @param int $id Node ID
  1442.     * @param array $values Hash with param => value pairs of the node (see $this->params)
  1443.     * @param bool $_intermal Internal use only. Used to skip value validation. Leave this as it is.
  1444.     * @access public
  1445.     * @return bool True if the update is successful
  1446.     */
  1447.     function updateNode($id$values$_internal = false{
  1448.         if ($this->debug{
  1449.             $this->_debugMessage('updateNode($id, $values)');
  1450.         }
  1451.  
  1452.         if (PEAR::isError($lock $this->_setLock())) {
  1453.             return $lock;
  1454.         }
  1455.  
  1456.         if (!$_internal{
  1457.             $this->_verifyUserValues('createRootNode()'$values);
  1458.         }
  1459.  
  1460.         $eparams = array('values' => $values);
  1461.         if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeUpdate'])) {
  1462.             // EVENT (NodeUpdate)
  1463.             $this->triggerEvent('nodeUpdate'$this->pickNode($id)$eparams);
  1464.         }
  1465.  
  1466.         $addvalues = array();
  1467.         if (!$qr $this->_values2UpdateQuery($values$addvalues)) {
  1468.             $this->_releaseLock();
  1469.             return false;
  1470.         }
  1471.  
  1472.         $sql sprintf('UPDATE %s SET %s WHERE %s=%s',
  1473.         $this->node_table,
  1474.         $qr,
  1475.         $this->flparams['id']$id);
  1476.         $res $this->_query($sql);
  1477.         $this->_testFatalAbort($res__FILE____LINE__);
  1478.         $this->_releaseLock();
  1479.         return true;
  1480.     }
  1481.     // }}}
  1482.     // +----------------------------------------------+
  1483.     // | Moving and copying                           |
  1484.     // |----------------------------------------------+
  1485.     // | [PUBLIC]                                     |
  1486.     // +----------------------------------------------+
  1487.     // {{{ moveTree()
  1488.     /**
  1489.     * Wrapper for node moving and copying
  1490.     *
  1491.     * @param int $id Source ID
  1492.     * @param int $target Target ID
  1493.     * @param constant $pos Position (use one of the NESE_MOVE_* constants)
  1494.     * @param bool $copy Shall we create a copy
  1495.     * @see _moveInsideLevel
  1496.     * @see _moveAcross
  1497.     * @see _moveRoot2Root
  1498.     * @access public
  1499.     * @return int ID of the moved node or false on error
  1500.     */
  1501.     function moveTree($id$targetid$pos$copy = false{
  1502.         if ($this->debug{
  1503.             $this->_debugMessage('moveTree($id, $target, $pos, $copy = false)');
  1504.         }
  1505.  
  1506.         if (PEAR::isError($lock $this->_setLock(true))) {
  1507.             return $lock;
  1508.         }
  1509.  
  1510.         if ($id == $targetid && !$copy{
  1511.             $epr = array('moveTree()');
  1512.             $this->_releaseLock();
  1513.             return $this->_raiseError(NESE_ERROR_RECURSIONPEAR_ERROR_RETURNE_USER_NOTICE$epr);
  1514.         }
  1515.         // Get information about source and target
  1516.         if (!($source $this->pickNode($idtrue))) {
  1517.             $epr = array('moveTree()'$id);
  1518.             $this->_releaseLock();
  1519.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDPEAR_ERROR_TRIGGERE_USER_ERROR$epr);
  1520.         }
  1521.  
  1522.         if (!($target $this->pickNode($targetidtrue))) {
  1523.             $epr = array('moveTree()'$targetid);
  1524.             $this->_releaseLock();
  1525.             return $this->_raiseError(NESE_ERROR_NOT_FOUNDPEAR_ERROR_TRIGGERE_USER_ERROR$epr);
  1526.         }
  1527.  
  1528.  
  1529.  
  1530.         $this->_relations = array();
  1531.         // This operations don't need callbacks except the copy handler
  1532.         // which ignores this setting
  1533.         $this->_skipCallbacks = true;
  1534.         if (!$copy{
  1535.             // We have a recursion - let's stop
  1536.             if (($target['rootid'== $source['rootid']&&
  1537.             (($source['l'<= $target['l']&&
  1538.             ($source['r'>= $target['r']))) {
  1539.                 $this->_releaseLock(true);
  1540.                 $epr = array('moveTree()');
  1541.                 return $this->_raiseError(NESE_ERROR_RECURSIONPEAR_ERROR_RETURNE_USER_NOTICE$epr);
  1542.             }
  1543.             // Insert/move before or after
  1544.             if (($source['rootid'== $source['id']&&
  1545.             ($target['rootid'== $target['id']&& ($pos != NESE_MOVE_BELOW)) {
  1546.                 // We have to move a rootnode which is different from moving inside a tree
  1547.                 $nid $this->_moveRoot2Root($source$target$pos);
  1548.                 $this->_releaseLock(true);
  1549.                 return $nid;
  1550.             }
  1551.         elseif (($target['rootid'== $source['rootid']&&
  1552.         (($source['l'$target['l']&&
  1553.         ($source['r'$target['r']))) {
  1554.             $this->_releaseLock(true);
  1555.             $epr = array('moveTree()');
  1556.             return $this->_raiseError(NESE_ERROR_RECURSIONPEAR_ERROR_RETURNE_USER_NOTICE$epr);
  1557.         }
  1558.         // We have to move between different levels and maybe subtrees - let's rock ;)
  1559.         $moveID $this->_moveAcross($source$target$postrue);
  1560.         $this->_moveCleanup($copy);
  1561.         $this->_releaseLock(true);
  1562.         if (!$copy{
  1563.             return $id;
  1564.         else {
  1565.             return $moveID;
  1566.         }
  1567.     }
  1568.     // }}}
  1569.     // {{{ _moveAcross()
  1570.     /**
  1571.     * Moves nodes and trees to other subtrees or levels
  1572.     *
  1573.     * <pre>
  1574.     * [+] <--------------------------------+
  1575.     * +-[\] root1 [target]                 |
  1576.     *        <-------------------------+      |p
  1577.     * +-\ root2                     |      |
  1578.     * | |                           |      |
  1579.     * | |-- subnode1 [target]       |      |B
  1580.     * | |-- subnode2 [new]          |S     |E
  1581.     * | |-- subnode3                |U     |F
  1582.     * |                             |B     |O
  1583.     * +-\ root3                     |      |R
  1584.     *      |-- subnode 3.1             |      |E
  1585.     *      |-\ subnode 3.2 [source] >--+------+
  1586.     *        |-- subnode 3.2.1
  1587.     * </pre>
  1588.     *
  1589.     * @param object NodeCT $source   Source node
  1590.     * @param object NodeCT $target   Target node
  1591.     * @param string $pos Position [SUBnode/BEfore]
  1592.     * @param bool $copy Shall we create a copy
  1593.     * @access private
  1594.     * @see moveTree
  1595.     * @see _r_moveAcross
  1596.     * @see _moveCleanup
  1597.     */
  1598.     function _moveAcross($source$target$pos$first = false{
  1599.         if ($this->debug{
  1600.             $this->_debugMessage('_moveAcross($source, $target, $pos, $copy = false)');
  1601.         }
  1602.         // Get the current data from a node and exclude the id params which will be changed
  1603.         // because of the node move
  1604.         $values = array();
  1605.         foreach($this->params as $key => $val{
  1606.             if ($source[$val&& $val != 'parent' && !in_array($val$this->_requiredParams)) {
  1607.                 $values[$keytrim($source[$val]);
  1608.             }
  1609.         }
  1610.  
  1611.         switch ($pos{
  1612.             case NESE_MOVE_BEFORE:
  1613.             $clone_id $this->createLeftNode($target['id']$values);
  1614.             break;
  1615.             case NESE_MOVE_AFTER:
  1616.             $clone_id $this->createRightNode($target['id']$values);
  1617.             break;
  1618.             case NESE_MOVE_BELOW:
  1619.             $clone_id $this->createSubNode($target['id']$values);
  1620.             break;
  1621.         }
  1622.  
  1623.         if ($first && isset($this->flparams['parent'])) {
  1624.             $t_parent $this->getParent($clone_idtruetruearray()false);
  1625.             $t_parent_id $t_parent['id'];
  1626.         elseif (isset($this->flparams['parent'])) {
  1627.             $t_parent_id $source['parent'];
  1628.         else {
  1629.             $t_parent_id = false;
  1630.         }
  1631.  
  1632.         $children $this->getChildren($source['id']truetruetrue);
  1633.         if ($children{
  1634.             $pos NESE_MOVE_BELOW;
  1635.             $sclone_id $clone_id;
  1636.             // Recurse through the child nodes
  1637.             foreach($children AS $cid => $child{
  1638.                 $sclone $this->pickNode($sclone_idtrue);
  1639.                 $sclone_id $this->_moveAcross($child$sclone$pos);
  1640.                 $pos NESE_MOVE_AFTER;
  1641.             }
  1642.         }
  1643.  
  1644.         $this->_relations[$source['id']]['clone'$clone_id;
  1645.         $this->_relations[$source['id']]['parent'$t_parent_id;
  1646.         return $clone_id;
  1647.     }
  1648.     // }}}
  1649.     // {{{ _moveCleanup()
  1650.     /**
  1651.     * Deletes the old subtree (node) and writes the node id's into the cloned tree
  1652.     *
  1653.     * @param array $relations Hash in der Form $h[alteid]=neueid
  1654.     * @param array $copy Are we in copy mode?
  1655.     * @access private
  1656.     */
  1657.     function _moveCleanup($copy = false{
  1658.         $relations $this->_relations;
  1659.         if ($this->debug{
  1660.             $this->_debugMessage('_moveCleanup($relations, $copy = false)');
  1661.         }
  1662.  
  1663.         $deletes = array();
  1664.         $updates = array();
  1665.         $pupdates = array();
  1666.         $tb $this->node_table;
  1667.         $fid $this->flparams['id'];
  1668.         $froot $this->flparams['rootid'];
  1669.         foreach($relations AS $key => $val{
  1670.             $cloneid $val['clone'];
  1671.             $parentID $val['parent'];
  1672.             $clone $this->pickNode($cloneid);
  1673.             if ($copy{
  1674.                 // EVENT (NodeCopy)
  1675.                 $eparams = array('clone' => $clone);
  1676.                 if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeCopy'])) {
  1677.                     $this->triggerEvent('nodeCopy'$this->pickNode($key)$eparams);
  1678.                 }
  1679.                 continue;
  1680.             }
  1681.             // No callbacks here because the node itself doesn't get changed
  1682.             // Only it's position
  1683.             // If one needs a callback here please let me know
  1684.             if (!empty($parentID)) {
  1685.                 $sql sprintf('UPDATE %s SET %s=%s WHERE %s=%s',
  1686.                 $this->node_table,
  1687.                 $this->flparams['parent'],
  1688.                 $parentID,
  1689.                 $fid,
  1690.                 $key);
  1691.                 $pupdates[$sql;
  1692.             }
  1693.  
  1694.             $deletes[$key;
  1695.             // It's isn't a rootnode
  1696.             if ($clone->id != $clone->rootid{
  1697.                 $sql sprintf('UPDATE %s SET %s=%s WHERE %s=%s',
  1698.                 $this->node_table,
  1699.                 $fid$key,
  1700.                 $fid$cloneid);
  1701.                 $updates[$sql;
  1702.             else {
  1703.                 $sql sprintf('UPDATE %s SET %s=%s, %s=%s WHERE %s=%s',
  1704.                 $this->node_table,
  1705.                 $fid$key,
  1706.                 $froot$cloneid,
  1707.                 $fid$cloneid);
  1708.                 $updates[$sql;
  1709.                 $orootid $clone->rootid;
  1710.                 $sql sprintf('UPDATE %s SET %s=%s WHERE %s=%s',
  1711.                 $tb,
  1712.                 $froot$key,
  1713.                 $froot$orootid);
  1714.                 $updates[$sql;
  1715.             }
  1716.             $this->_skipCallbacks = false;
  1717.         }
  1718.  
  1719.         if (!empty($deletes)) {
  1720.             foreach ($deletes as $delete{
  1721.                 $this->deleteNode($delete);
  1722.             }
  1723.         }
  1724.  
  1725.         if (!empty($updates)) {
  1726.             for($i = 0;$i count($updates);$i++{
  1727.                 $res $this->_query($updates[$i]);
  1728.                 $this->_testFatalAbort($res__FILE____LINE__);
  1729.             }
  1730.         }
  1731.  
  1732.         if (!empty($pupdates)) {
  1733.             for($i = 0;$i count($pupdates);$i++{
  1734.                 $res $this->_query($pupdates[$i]);
  1735.                 $this->_testFatalAbort($res__FILE____LINE__);
  1736.             }
  1737.         }
  1738.  
  1739.         return true;
  1740.     }
  1741.     // }}}
  1742.     // {{{ _moveRoot2Root()
  1743.     /**
  1744.     * Moves rootnodes
  1745.     *
  1746.     * <pre>
  1747.     * +-- root1
  1748.     * |
  1749.     * +-\ root2
  1750.     * | |
  1751.     * | |-- subnode1 [target]
  1752.     * | |-- subnode2 [new]
  1753.     * | |-- subnode3
  1754.     * |
  1755.     * +-\ root3
  1756.     *     [|]  <-----------------------+
  1757.     *      |-- subnode 3.1 [target]    |
  1758.     *      |-\ subnode 3.2 [source] >--+
  1759.     *        |-- subnode 3.2.1
  1760.     * </pre>
  1761.     *
  1762.     * @param object NodeCT $source    Source
  1763.     * @param object NodeCT $target    Target
  1764.     * @param string $pos BEfore | AFter
  1765.     * @access private
  1766.     * @see moveTree
  1767.     */
  1768.     function _moveRoot2Root($source$target$pos{
  1769.         if ($this->debug{
  1770.             $this->_debugMessage('_moveRoot2Root($source, $target, $pos, $copy)');
  1771.         }
  1772.         if (PEAR::isError($lock $this->_setLock())) {
  1773.             return $lock;
  1774.         }
  1775.  
  1776.         $tb $this->node_table;
  1777.         $fid $this->flparams['id'];
  1778.         $froot $this->flparams['rootid'];
  1779.         $freh $this->flparams['norder'];
  1780.         $s_order $source['norder'];
  1781.         $t_order $target['norder'];
  1782.         $s_id $source['id'];
  1783.         $t_id $target['id'];
  1784.         if ($s_order $t_order{
  1785.             if ($pos == NESE_MOVE_BEFORE{
  1786.                 $sql = "UPDATE $tb SET $freh=$freh-1
  1787.                         WHERE $freh BETWEEN $s_order AND $t_order AND
  1788.                             $fid!=$t_id AND
  1789.                             $fid!=$s_id AND
  1790.                             $froot=$fid";
  1791.                 $res $this->_query($sql);
  1792.                 $this->_testFatalAbort($res__FILE____LINE__);
  1793.                 $sql = "UPDATE $tb SET $freh=$t_order -1 WHERE $fid=$s_id";
  1794.                 $res $this->_query($sql);
  1795.                 $this->_testFatalAbort($res__FILE____LINE__);
  1796.             elseif ($pos == NESE_MOVE_AFTER{
  1797.                 $sql = "UPDATE $tb SET $freh=$freh-1
  1798.                         WHERE $freh BETWEEN $s_order AND $t_order AND
  1799.                             $fid!=$s_id AND
  1800.                             $froot=$fid";
  1801.                 $res $this->_query($sql);
  1802.                 $this->_testFatalAbort($res__FILE____LINE__);
  1803.                 $sql = "UPDATE $tb SET $freh=$t_order WHERE $fid=$s_id";
  1804.                 $res $this->_query($sql);
  1805.                 $this->_testFatalAbort($res__FILE____LINE__);
  1806.             }
  1807.         }
  1808.  
  1809.         if ($s_order $t_order{
  1810.             if ($pos == NESE_MOVE_BEFORE{
  1811.                 $sql = "UPDATE $tb SET $freh=$freh+1
  1812.                         WHERE $freh BETWEEN $t_order AND $s_order AND
  1813.                             $fid != $s_id AND
  1814.                             $froot=$fid";
  1815.                 $res $this->_query($sql);
  1816.                 $this->_testFatalAbort($res__FILE____LINE__);
  1817.                 $sql = "UPDATE $tb SET $freh=$t_order WHERE $fid=$s_id";
  1818.                 $res $this->_query($sql);
  1819.                 $this->_testFatalAbort($res__FILE____LINE__);
  1820.             elseif ($pos == NESE_MOVE_AFTER{
  1821.                 $sql = "UPDATE $tb SET $freh=$freh+1
  1822.                         WHERE $freh BETWEEN $t_order AND $s_order AND
  1823.                         $fid!=$t_id AND
  1824.                         $fid!=$s_id AND
  1825.                         $froot=$fid";
  1826.                 $res $this->_query($sql);
  1827.                 $this->_testFatalAbort($res__FILE____LINE__);
  1828.                 $sql = "UPDATE $tb SET $freh=$t_order+1 WHERE $fid = $s_id";
  1829.                 $res $this->_query($sql);
  1830.                 $this->_testFatalAbort($res__FILE____LINE__);
  1831.             }
  1832.         }
  1833.         $this->_releaseLock();
  1834.         return $s_id;
  1835.     }
  1836.     // }}}
  1837.     // +-----------------------+
  1838.     // | Helper methods        |
  1839.     // +-----------------------+
  1840.     // {{{ _secSort()
  1841.     /**
  1842.     * OMG ... This is nasty
  1843.     *
  1844.     * @access private
  1845.     */
  1846.     function _secSort($nodeSet{
  1847.         
  1848.         // Nothing to do - empty tree
  1849.         if(empty($nodeSet)) {
  1850.             return $nodeSet;
  1851.         }
  1852.         
  1853.         $retArray = array();
  1854.         foreach($nodeSet AS $nodeID=>$node{
  1855.             $deepArray[$node['parent']][$nodeID$node;
  1856.         }
  1857.         
  1858.         $reset = true;
  1859.         foreach($deepArray AS $parentID=>$children{
  1860.             $retArray $this->_secSortCollect($children$deepArray$reset);
  1861.             $reset = false;
  1862.         }
  1863.         return $retArray;
  1864.     }
  1865.  
  1866.  
  1867.     function _secSortCollect($segment$deepArray$reset = false{
  1868.  
  1869.         static $retArray;
  1870.         if(!$retArray || $reset{
  1871.             $retArray = array();
  1872.         }
  1873.  
  1874.         foreach($segment AS $nodeID=>$node{
  1875.             if(isset($retArray[$nodeID])) {
  1876.                 continue;
  1877.             }
  1878.             $retArray[$nodeID$node;
  1879.             if(isset($deepArray[$nodeID])) {
  1880.                 $this->_secSortCollect($deepArray[$nodeID]$deepArray);
  1881.             }
  1882.         }
  1883.         return $retArray;
  1884.     }
  1885.  
  1886.     // }}}
  1887.     // {{{ _addSQL()
  1888.     /**
  1889.     * Adds a specific type of SQL to a query string
  1890.     *
  1891.     * @param array $addSQL The array of SQL strings to add.  Example value:
  1892.     *                   $addSQL = array(
  1893.     *                   'cols' => 'tb2.col2, tb2.col3',         // Additional tables/columns
  1894.     *                   'join' => 'LEFT JOIN tb1 USING(STRID)', // Join statement
  1895.     *                                       'where' => 'A='B' AND C='D',                    // Where statement without 'WHERE' OR 'AND' in front
  1896.     *                   'append' => 'GROUP by tb1.STRID');      // Group condition
  1897.     * @param string $type The type of SQL.  Can be 'cols', 'join', or 'append'.
  1898.     * @access private
  1899.     * @return string The SQL, properly formatted
  1900.     */
  1901.     function _addSQL($addSQL$type$prefix = false{
  1902.         if (!isset($addSQL[$type])) {
  1903.             return '';
  1904.         }
  1905.  
  1906.         switch ($type{
  1907.             case 'cols':
  1908.             return ', ' $addSQL[$type];
  1909.             case 'where':
  1910.             return $prefix ' (' $addSQL[$type')';
  1911.             default:
  1912.             return $addSQL[$type];
  1913.         }
  1914.     }
  1915.     // }}}
  1916.     // {{{ _getSelectFields()
  1917.     /**
  1918.     * Gets the select fields based on the params
  1919.     *
  1920.     * @param bool $aliasFields Should we alias the fields so they are the names of the
  1921.     *                 parameter keys, or leave them as is?
  1922.     * @access private
  1923.     * @return string A string of query fields to select
  1924.     */
  1925.     function _getSelectFields($aliasFields{
  1926.         $queryFields = array();
  1927.         foreach ($this->params as $key => $val{
  1928.             $tmp_field $this->node_table . '.' $key;
  1929.             if ($aliasFields{
  1930.                 $tmp_field .= ' AS ' $this->_quoteIdentifier($val);
  1931.             }
  1932.             $queryFields[$tmp_field;
  1933.         }
  1934.         $fields implode(', '$queryFields);
  1935.         return $fields;
  1936.     }
  1937.     // }}}
  1938.     // {{{ _processResultSet()
  1939.     /**
  1940.     * Processes a DB result set by checking for a DB error and then transforming the result
  1941.     * into a set of DB_NestedSet_Node objects or leaving it as an array.
  1942.     *
  1943.     * @param string $sql The sql query to be done
  1944.     * @param bool $keepAsArray Keep the result as an array or transform it into a set of
  1945.     *                 DB_NestedSet_Node objects?
  1946.     * @param bool $fieldsAreAliased Are the fields aliased?
  1947.     * @access private
  1948.     * @return mixed False on error or the transformed node set.
  1949.     */
  1950.     function _processResultSet($sql$keepAsArray$fieldsAreAliased{
  1951.         $result $this->_getAll($sql);
  1952.         if ($this->_testFatalAbort($result__FILE____LINE__)) {
  1953.             return false;
  1954.         }
  1955.  
  1956.         $nodes = array();
  1957.         $idKey $fieldsAreAliased 'id' $this->flparams['id'];
  1958.         foreach ($result as $row{
  1959.             $node_id $row[$idKey];
  1960.             if ($keepAsArray{
  1961.                 $nodes[$node_id$row;
  1962.             else {
  1963.                 // Create an instance of the node container
  1964.                 $nodes[$node_idnew DB_NestedSet_Node($row);
  1965.             }
  1966.         }
  1967.         return $nodes;
  1968.     }
  1969.     // }}}
  1970.     // {{{ _testFatalAbort()
  1971.     /**
  1972.     * Error Handler
  1973.     *
  1974.     * Tests if a given ressource is a PEAR error object
  1975.     * ans raises a fatal error in case of an error object
  1976.     *
  1977.     * @param object PEAR::Error $errobj     The object to test
  1978.     * @param string $file The filename wher the error occured
  1979.     * @param int $line The line number of the error
  1980.     * @return void 
  1981.     * @access private
  1982.     */
  1983.     function _testFatalAbort($errobj$file$line{
  1984.         if (!$this->_isDBError($errobj)) {
  1985.             return false;
  1986.         }
  1987.  
  1988.         if ($this->debug{
  1989.             $this->_debugMessage('_testFatalAbort($errobj, $file, $line)');
  1990.         }
  1991.         if ($this->debug{
  1992.             $message $errobj->getUserInfo();
  1993.             $code $errobj->getCode();
  1994.             $msg = "$message ($code) in file $file at line $line";
  1995.         else {
  1996.             $msg $errobj->getMessage();
  1997.             $code $errobj->getCode();
  1998.         }
  1999.  
  2000.         PEAR::raiseError($msg$codePEAR_ERROR_TRIGGERE_USER_ERROR);
  2001.     }
  2002.     // }}}
  2003.     // {{{ __raiseError()
  2004.     /**
  2005.     *
  2006.     * @access private
  2007.     */
  2008.     function _raiseError($code$mode$option$epr = array()) {
  2009.         $message vsprintf($this->_getMessage($code)$epr);
  2010.         return PEAR::raiseError($message$code$mode$option);
  2011.     }
  2012.     // }}}
  2013.     // {{{ addListener()
  2014.     /**
  2015.     * Add an event listener
  2016.     *
  2017.     * Adds an event listener and returns an ID for it
  2018.     * <pre>Known events are
  2019.     * nodeCreate
  2020.     * nodeDelete
  2021.     * nodeUpdate
  2022.     * nodeCopy
  2023.     * nodeLoad
  2024.     * </pre>
  2025.     * @param string $event The event name
  2026.     * @param string $listener The listener object
  2027.     * @return string 
  2028.     * @access public
  2029.     */
  2030.     function addListener($event$listener{
  2031.         $listenerID uniqid('el');
  2032.         $this->eventListeners[$event][$listenerID$listener;
  2033.         $this->_hasListeners[$event= true;
  2034.         return $listenerID;
  2035.     }
  2036.     // }}}
  2037.     // {{{ removeListener()
  2038.     /**
  2039.     * Removes an event listener
  2040.     *
  2041.     * Removes the event listener with the given ID
  2042.     *
  2043.     * @param string $event The ivent name
  2044.     * @param string $listenerID The listener's ID
  2045.     * @return bool 
  2046.     * @access public
  2047.     */
  2048.     function removeListener($event$listenerID{
  2049.         unset($this->eventListeners[$event][$listenerID]);
  2050.         if (!isset($this->eventListeners[$event]|| !is_array($this->eventListeners[$event]||
  2051.         count($this->eventListeners[$event]== 0{
  2052.             unset($this->_hasListeners[$event]);
  2053.         }
  2054.         return true;
  2055.     }
  2056.     // }}}
  2057.     // {{{ triggerEvent()
  2058.     /**
  2059.     * Triggers and event an calls the event listeners
  2060.     *
  2061.     * @param string $event The Event that occured
  2062.     * @param object node $node A Reference to the node object which was subject to changes
  2063.     * @param array $eparams A associative array of params which may be needed by the handler
  2064.     * @return bool 
  2065.     * @access public
  2066.     */
  2067.     function triggerEvent($event$node$eparams = false{
  2068.         if ($this->_skipCallbacks || !isset($this->_hasListeners[$event])) {
  2069.             return false;
  2070.         }
  2071.  
  2072.         foreach($this->eventListeners[$eventas $key => $val{
  2073.             if (!method_exists($val'callEvent')) {
  2074.                 return new PEAR_Error($this->_getMessage(NESE_ERROR_NOHANDLER)NESE_ERROR_NOHANDLER);
  2075.             }
  2076.  
  2077.             $val->callEvent($event$node$eparams);
  2078.         }
  2079.  
  2080.         return true;
  2081.     }
  2082.     // }}}
  2083.     // {{{ apiVersion()
  2084.     function apiVersion({
  2085.         return array('package:' => $this->_packagename,
  2086.         'majorversion' => $this->_majorversion,
  2087.         'minorversion' => $this->_minorversion,
  2088.         'version' => sprintf('%s.%s'$this->_majorversion$this->_minorversion),
  2089.         'revision' => str_replace('$''''$Revision: 1.86 $')
  2090.         );
  2091.     }
  2092.     // }}}
  2093.     // {{{ setAttr()
  2094.     /**
  2095.     * Sets an object attribute
  2096.     *
  2097.     * @param array $attr An associative array with attributes
  2098.     * @return bool 
  2099.     * @access public
  2100.     */
  2101.     function setAttr($attr{
  2102.         static $hasSetSequence;
  2103.         if (!isset($hasSetSequence)) {
  2104.             $hasSetSequence = false;
  2105.         }
  2106.  
  2107.         if (!is_array($attr|| count($attr== 0{
  2108.             return false;
  2109.         }
  2110.  
  2111.         foreach ($attr as $key => $val{
  2112.             $this->$key $val;
  2113.             if ($key == 'sequence_table'{
  2114.                 $hasSetSequence = true;
  2115.             }
  2116.             // only update sequence to reflect new table if they haven't set it manually
  2117.             if (!$hasSetSequence && $key == 'node_table'{
  2118.                 $this->sequence_table = $this->node_table . '_' $this->flparams['id'];
  2119.             }
  2120.             if ($key == 'cache' && is_object($val)) {
  2121.                 $this->_caching = true;
  2122.                 $GLOBALS['DB_NestedSet'$this;
  2123.             }
  2124.         }
  2125.  
  2126.         return true;
  2127.     }
  2128.     // }}}
  2129.     // {{{ setsortMode()
  2130.     /**
  2131.     * This enables you to set specific options for each output method
  2132.     *
  2133.     * @param constant $sortMode 
  2134.     * @access public
  2135.     * @return Current sortMode
  2136.     */
  2137.     function setsortMode($sortMode = false{
  2138.         if ($sortMode && in_array($sortMode$this->_sortModes)) {
  2139.             $this->_sortMode $sortMode;
  2140.         else {
  2141.             return $this->_sortMode;
  2142.         }
  2143.         return $this->_sortMode;
  2144.     }
  2145.     // }}}
  2146.     // {{{ setDbOption()
  2147.     /**
  2148.     * Sets a db option.  Example, setting the sequence table format
  2149.     *
  2150.     * @var string $option The option to set
  2151.     * @var string $val The value of the option
  2152.     * @access public
  2153.     * @return void 
  2154.     */
  2155.     function setDbOption($option$val{
  2156.         $this->db->setOption($option$val);
  2157.     }
  2158.     // }}}
  2159.     // {{{ testLock()
  2160.     /**
  2161.     * Tests if a database lock is set
  2162.     *
  2163.     * @access public
  2164.     */
  2165.     function testLock({
  2166.         if ($this->debug{
  2167.             $this->_debugMessage('testLock()');
  2168.         }
  2169.  
  2170.         if ($lockID $this->_structureTableLock{
  2171.             return $lockID;
  2172.         }
  2173.         $this->_lockGC();
  2174.         $sql sprintf('SELECT lockID FROM %s WHERE lockTable=%s',
  2175.         $this->lock_table,
  2176.         $this->_quote($this->node_table)) ;
  2177.         $res $this->_query($sql);
  2178.         $this->_testFatalAbort($res__FILE____LINE__);
  2179.         if ($this->_numRows($res)) {
  2180.             return new PEAR_Error($this->_getMessage(NESE_ERROR_TBLOCKED)NESE_ERROR_TBLOCKED);
  2181.         }
  2182.  
  2183.         return false;
  2184.     }
  2185.     // }}}
  2186.     // {{{ _setLock()
  2187.     /**
  2188.     *
  2189.     * @access private
  2190.     */
  2191.     function _setLock($exclusive = false{
  2192.         $lock $this->testLock();
  2193.         if (PEAR::isError($lock)) {
  2194.             return $lock;
  2195.         }
  2196.  
  2197.         if ($this->debug{
  2198.             $this->_debugMessage('_setLock()');
  2199.         }
  2200.         if ($this->_caching{
  2201.             @$this->cache->flush('function_cache');
  2202.             $this->_caching = false;
  2203.             $this->_restcache = true;
  2204.         }
  2205.  
  2206.         if (!$lockID $this->_structureTableLock{
  2207.             $lockID $this->_structureTableLock uniqid('lck-');
  2208.             $sql sprintf('INSERT INTO %s (lockID, lockTable, lockStamp) VALUES (%s, %s, %s)',
  2209.             $this->lock_table,
  2210.             $this->_quote($lockID)$this->_quote($this->node_table)time());
  2211.         else {
  2212.             $sql sprintf('UPDATE %s SET lockStamp=%s WHERE lockID=%s AND lockTable=%s',
  2213.             $this->lock_table,
  2214.             time(),
  2215.             $this->_quote($lockID)$this->_quote($this->node_table));
  2216.         }
  2217.  
  2218.         if ($exclusive{
  2219.             $this->_lockExclusive = true;
  2220.         }
  2221.  
  2222.         $res $this->_query($sql);
  2223.         $this->_testFatalAbort($res__FILE____LINE__);
  2224.         return $lockID;
  2225.     }
  2226.     // }}}
  2227.     // {{{ _releaseLock()
  2228.     /**
  2229.     *
  2230.     * @access private
  2231.     */
  2232.     function _releaseLock($exclusive = false{
  2233.         if ($this->debug{
  2234.             $this->_debugMessage('_releaseLock()');
  2235.         }
  2236.  
  2237.         if ($exclusive{
  2238.             $this->_lockExclusive = false;
  2239.         }
  2240.  
  2241.         if ((!$lockID $this->_structureTableLock|| $this->_lockExclusive{
  2242.             return false;
  2243.         }
  2244.  
  2245.         $tb $this->lock_table;
  2246.         $stb $this->node_table;
  2247.         $sql = "DELETE FROM $tb
  2248.                 WHERE lockTable=" . $this->_quote($stb" AND
  2249.                     lockID=" $this->_quote($lockID);
  2250.         $res $this->_query($sql);
  2251.         $this->_testFatalAbort($res__FILE____LINE__);
  2252.         $this->_structureTableLock = false;
  2253.         if ($this->_restcache{
  2254.             $this->_caching = true;
  2255.             $this->_restcache = false;
  2256.         }
  2257.         return true;
  2258.     }
  2259.     // }}}
  2260.     // {{{ _lockGC()
  2261.     /**
  2262.     *
  2263.     * @access private
  2264.     */
  2265.     function _lockGC({
  2266.         if ($this->debug{
  2267.             $this->_debugMessage('_lockGC()');
  2268.         }
  2269.         $tb $this->lock_table;
  2270.         $stb $this->node_table;
  2271.         $lockTTL time($this->lockTTL;
  2272.         $sql = "DELETE FROM $tb
  2273.                 WHERE lockTable=" . $this->_quote($stb. " AND
  2274.                     lockStamp < $lockTTL";
  2275.         $res $this->_query($sql);
  2276.         $this->_testFatalAbort($res__FILE____LINE__);
  2277.     }
  2278.     // }}}
  2279.     // {{{ _values2UpdateQuery()
  2280.     /**
  2281.     *
  2282.     * @access private
  2283.     */
  2284.     function _values2UpdateQuery($values$addval = false{
  2285.         if ($this->debug{
  2286.             $this->_debugMessage('_values2UpdateQuery($values, $addval = false)');
  2287.         }
  2288.         if (is_array($addval)) {
  2289.             $values $values $addval;
  2290.         }
  2291.  
  2292.         $arq = array();
  2293.         foreach($values AS $key => $val{
  2294.             $k $this->_quoteIdentifier(trim($key));
  2295.  
  2296.             // To be used with the next major version
  2297.             // $iv = in_array($this->params[$k], $this->_quotedParams) ? $this->_quote($v) : $v;
  2298.             $iv $this->_quote(trim($val));
  2299.             $arq[= "$k=$iv";
  2300.         }
  2301.  
  2302.         if (!is_array($arq|| count($arq== 0{
  2303.             return false;
  2304.         }
  2305.  
  2306.         $query implode(', '$arq);
  2307.         return $query;
  2308.     }
  2309.     // }}}
  2310.     // {{{ _values2UpdateQuery()
  2311.     /**
  2312.     *
  2313.     * @access private
  2314.     */
  2315.     function _values2InsertQuery($values$addval = false{
  2316.         if ($this->debug{
  2317.             $this->_debugMessage('_values2InsertQuery($values, $addval = false)');
  2318.         }
  2319.         if (is_array($addval)) {
  2320.             $values $values $addval;
  2321.         }
  2322.  
  2323.         $arq = array();
  2324.         foreach($values AS $key => $val{
  2325.             $k $this->_quoteIdentifier(trim($key));
  2326.  
  2327.             // To be used with the next major version
  2328.             // $iv = in_array($this->params[$k], $this->_quotedParams) ? $this->_quote($v) : $v;
  2329.             $iv $this->_quote(trim($val));
  2330.             $arq[$k$iv;
  2331.         }
  2332.  
  2333.         if (!is_array($arq|| count($arq== 0{
  2334.             return false;
  2335.         }
  2336.  
  2337.         return $arq;
  2338.     }
  2339.     // }}}
  2340.     // {{{ _verifyUserValues()
  2341.     /**
  2342.     * Clean values from protected or unknown columns
  2343.     *
  2344.     * @var string $caller The calling method
  2345.     * @var string $values The values array
  2346.     * @access private
  2347.     * @return void 
  2348.     */
  2349.     function _verifyUserValues($caller$values{
  2350.         if ($this->_dumbmode{
  2351.             return true;
  2352.         }
  2353.         foreach($values as $field => $value{
  2354.             if (!isset($this->params[$field])) {
  2355.                 $epr = array($callersprintf('Unknown column/param \'%s\''$field));
  2356.                 $this->_raiseError(NESE_ERROR_WRONG_MPARAMPEAR_ERROR_RETURNE_USER_NOTICE$epr);
  2357.                 unset($values[$field]);
  2358.             else {
  2359.                 $flip $this->params[$field];
  2360.                 if (in_array($flip$this->_requiredParams)) {
  2361.                     $epr = array($callersprintf('\'%s\' is autogenerated and can\'t be passed - it will be ignored'$field));
  2362.                     $this->_raiseError(NESE_ERROR_WRONG_MPARAMPEAR_ERROR_RETURNE_USER_NOTICE$epr);
  2363.                     unset($values[$field]);
  2364.                 }
  2365.             }
  2366.         }
  2367.     }
  2368.     // }}}
  2369.     // {{{ _debugMessage()
  2370.     /**
  2371.     *
  2372.     * @access private
  2373.     */
  2374.     function _debugMessage($msg{
  2375.         if ($this->debug{
  2376.             list($usec$secexplode(' 'microtime());
  2377.             $time ((float)$usec + (float)$sec);
  2378.             echo "$time::Debug:: $msg<br />\n";
  2379.         }
  2380.     }
  2381.     // }}}
  2382.     // {{{ _getMessage()
  2383.     /**
  2384.     *
  2385.     * @access private
  2386.     */
  2387.     function _getMessage($code{
  2388.         if ($this->debug{
  2389.             $this->_debugMessage('_getMessage($code)');
  2390.         }
  2391.         return isset($this->messages[$code]$this->messages[$code$this->messages[NESE_MESSAGE_UNKNOWN];
  2392.     }
  2393.     // }}}
  2394.     // {{{ convertTreeModel()
  2395.     /**
  2396.     * Convert a <1.3 tree into a 1.3 tree format
  2397.     *
  2398.     * This will convert the tree into a format needed for some new features in
  2399.     * 1.3. Your <1.3 tree will still work without converting but some new features
  2400.     * like preorder sorting won't work as expected.
  2401.     *
  2402.     * <pre>
  2403.     * Usage:
  2404.     * - Create a new node table (tb_nodes2) from the current node table (tb_nodes1) (only copy the structure).
  2405.     * - Create a nested set instance of the 'old' set (NeSe1) and one of the new set (NeSe2)
  2406.     * - Now you have 2 identical objects where only node_table differs
  2407.     * - Call DB_NestedSet::convertTreeModel(&$orig, &$copy);
  2408.     * - After that you have a cleaned up copy of tb_nodes1 inside tb_nodes2
  2409.     * </pre>
  2410.     *
  2411.     * @param object DB_NestedSet $orig  Nested set we want to copy
  2412.     * @param object DB_NestedSet $copy  Object where the new tree is copied to
  2413.     * @param integer $_parent ID of the parent node (private)
  2414.     * @static
  2415.     * @access public
  2416.     * @return bool True uns success
  2417.     */
  2418.     function convertTreeModel($orig$copy$_parent = false{
  2419.         static $firstSet;
  2420.         $isRoot = false;
  2421.         if (!$_parent{
  2422.             if (!is_object($orig|| !is_object($copy)) {
  2423.                 return false;
  2424.             }
  2425.             if ($orig->node_table == $copy->node_table{
  2426.                 return false;
  2427.             }
  2428.             $copy->_dumbmode = true;
  2429.             $orig->sortMode = NESE_SORT_LEVEL;
  2430.             $copy->sortMode = NESE_SORT_LEVEL;
  2431.             $sibl $orig->getRootNodes(true);
  2432.             $isRoot = true;
  2433.         else {
  2434.             $sibl $orig->getChildren($_parenttrue);
  2435.         }
  2436.  
  2437.         if (empty($sibl)) {
  2438.             return false;
  2439.         }
  2440.  
  2441.         foreach($sibl AS $sid => $sibling{
  2442.             unset($sibling['l']);
  2443.             unset($sibling['r']);
  2444.             unset($sibling['norder']);
  2445.             $values = array();
  2446.             foreach($sibling AS $key => $val{
  2447.                 if (!isset($copy->flparams[$key])) {
  2448.                     continue;
  2449.                 }
  2450.                 $values[$copy->flparams[$key]] $val;
  2451.             }
  2452.  
  2453.             if (!$firstSet{
  2454.                 $psid $copy->createRootNode($valuesfalsetrue);
  2455.                 $firstSet = true;
  2456.             elseif ($isRoot{
  2457.                 $psid $copy->createRightNode($psid$values);
  2458.             else {
  2459.                 $copy->createSubNode($_parent$values);
  2460.             }
  2461.  
  2462.             DB_NestedSet::convertTreeModel($orig$copy$sid);
  2463.         }
  2464.         return true;
  2465.     }
  2466.     // }}}
  2467. }
  2468. // {{{ DB_NestedSet_Node:: class
  2469. /**
  2470. * Generic class for node objects
  2471. *
  2472. @autor Daniel Khan <dk@webcluster.at>;
  2473. @version $Revision: 1.86 $
  2474. @package DB_NestedSet
  2475. @access private
  2476. */
  2477.  
  2478. class DB_NestedSet_Node {
  2479.     // {{{ constructor
  2480.     /**
  2481.     * Constructor
  2482.     */
  2483.     function DB_NestedSet_Node($data{
  2484.         if (!is_array($data|| count($data== 0{
  2485.             return new PEAR_ERROR($dataNESE_ERROR_PARAM_MISSING);
  2486.         }
  2487.  
  2488.         $this->setAttr($data);
  2489.         return true;
  2490.     }
  2491.     // }}}
  2492.     // {{{ setAttr()
  2493.     function setAttr($data{
  2494.         if (!is_array($data|| count($data== 0{
  2495.             return false;
  2496.         }
  2497.  
  2498.         foreach ($data as $key => $val{
  2499.             $this->$key $val;
  2500.         }
  2501.     }
  2502.     // }}}
  2503. }
  2504. // }}}
  2505. ?>

Documentation generated on Mon, 11 Mar 2019 13:54:38 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.