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

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