"hdl_categories", "lvalname" => "idleft", "rvalname" => "idright")) { $this->handle = $nsthandle; } /* ******************************************************************* * Tree Constructors * ********************************************************************/ /** * Create a new "root" for the tree and return the node * * @param array $otherfields An array of the other field to update with * the values. ie. : array("fieldname" => "value") * * @return mixed On success, array : the new root Left and Right values * of the node. * On fail, bool : false */ function nstNewRoot($otherfields) { $newnode['l'] = 1; $newnode['r'] = 2; if(!$this->_insertNew($newnode, $otherfields)) { return false; } return $newnode; } /** * Create a new child at the first position for the subtree and return the node * * @param array $otherfields An array of the other field to update with * the values. ie. : array("fieldname" => "value") * * @return mixed On success, array : the Left and Right values * of the new root node. * On fail, bool : false */ function nstNewFirstChild($node, $otherfields) { $newnode['l'] = $node['l']+1; $newnode['r'] = $node['l']+2; $this->_shiftRLValues($newnode['l'], 2); if($this->_insertNew($newnode, $otherfields)) { return $newnode; } else { return false; } } /** * Create a new child at the last position for the subtree and return the node * * @param array $otherfields An array of the other field to update with * the values. ie. : array("fieldname" => "value") * * @return mixed On success, array : the Left and Right values * of the new node. * On fail, bool : false */ function nstNewLastChild($node, $otherfields) /* creates a new last child of 'node'. */ { $newnode['l'] = $node['r']; $newnode['r'] = $node['r']+1; $this->_shiftRLValues($newnode['l'], 2); if($this->_insertNew($newnode, $otherfields)) { return $newnode; } else { return false; } } /** * Create a new sibling node before the given node (at the same "level") * * @param array $node The sibling Left and Right values of the node * @param array $otherfields An array of the other field to update with * the values. ie. : array("fieldname" => "value") * * @return mixed On success, array : the Left and Right values * of the new node. * On fail, bool : false */ function nstNewPrevSibling($node, $otherfields) { $newnode['l'] = $node['l']; $newnode['r'] = $node['l']+1; $this->_shiftRLValues($newnode['l'], 2); if($this->_insertNew($newnode, $otherfields)) { return $newnode; } else { return false; } } /** * Create a new sibling node after the given node (at the same "level") * * @param array $node The sibling Left and Right values of the node * @param array $otherfields An array of the other field to update with * the values. ie. : array("fieldname" => "value") * * @return mixed On success, array : the Left and Right values * of the new node. * On fail, bool : false */ function nstNewNextSibling($node, $otherfields) { $newnode['l'] = $node['r']+1; $newnode['r'] = $node['r']+2; $this->_shiftRLValues($newnode['l'], 2); if($this->_insertNew($newnode, $otherfields)) { return $newnode; } else { return false; } } /** * Add a delta to all the Left and Right values that are greater * than a first node * * @param string $first First value of Right or Left value to increase or decrease * @param string $delta Value of the delta to apply, can be positive or negative * * @access private */ function _shiftRLValues($first, $delta) { mysql_query("UPDATE ".$this->handle['table']." SET ".$this->handle['lvalname'] ."=".$this->handle['lvalname']."+$delta WHERE " .$this->handle['lvalname'].">=$first" ); mysql_query("UPDATE ".$this->handle['table']." SET ".$this->handle['rvalname'] ."=".$this->handle['rvalname']."+$delta WHERE " .$this->handle['rvalname'].">=$first" ); } /** * Add a delta to all the Left and Right values that are greater or equal * than a first node and smaller or equal than a last node. * * @param string $first First Right or Left value of the range to increase * or decrease * @param string $last Last Right or Left value of the range to increase * or decrease * @param string $delta Value of the delta to apply, it can be positive * or negative * * @return array The first and last modified values * * @access private */ function _shiftRLRange($first, $last, $delta) { mysql_query("UPDATE ".$this->handle['table']." SET ".$this->handle['lvalname'] ."=".$this->handle['lvalname']."+$delta WHERE " .$this->handle['lvalname'].">=$first AND " .$this->handle['lvalname']."<=$last" ); mysql_query("UPDATE ".$this->handle['table']." SET ".$this->handle['rvalname'] ."=".$this->handle['rvalname']."+$delta WHERE " .$this->handle['rvalname'].">=$first AND " .$this->handle['rvalname']."<=$last" ); return array('l'=>$first+$delta, 'r'=>$last+$delta); } /** * Insert a new node in the tree (might be a root, child, ...) * * @param array $node The Right and Left values of new node * @param array $otherfields An array of the other field to update with * the values. ie. : array("fieldname" => "value") * * @return bool On success, true. On fail, false. * * @access private */ function _insertNew($node, $otherfields) { $sqlotherfields = ""; if(isset($otherfields)) { if(is_array($otherfields) && (sizeof($otherfields) > 0)) { foreach($otherfields as $fieldname => $fieldvalue) { $sqlotherfields .= $fieldname."=".$fieldvalue.","; } } else { return false; } } if(!mysql_query("INSERT INTO ".$this->handle['table']." SET " .$sqlotherfields.$this->handle['lvalname']."=" .$node['l'].", ".$this->handle['rvalname']."=" .$node['r'] )) { $this->_prtError(); return false; } else { return true; } } /********************************************************************* * Tree Modification **********************************************************************/ /** * Updated specified fields in database for the specified node * * @param array $node The Right and Left values of new node to modify * @param array $otherfields An array of the other field to update with * the values. ie. : array("fieldname" => "value") * * @return bool On success, true. On fail, false. */ function nstUpdateNodeFields($node, $otherfields) { $fields = ""; foreach($otherfields as $fieldname => $fieldvalue) { $fields .= $fieldname."=".$fieldvalue.","; } $fields = rtrim($fields,","); return mysql_query("UPDATE ".$this->handle['table']." SET ".$fields ." WHERE ".$this->handle['lvalname']."=".$node['l'] ); } /********************************************************************* * Tree Reorganization **********************************************************************/ /** * Move a node and all its children to another place as a sibling after * the specified sibling. * * @param array $src Source node, which will be moved * @param array $dst Destination node that will before the moved node * * @return array New position of the moved subtree. */ function nstMoveToNextSibling($src, $dst) { return $this->_moveSubtree($src, $dst['r']+1); } /** * Move a node and all its children to another place as a sibling before * the specified sibling. * * @param array $src Source node, which will be moved * @param array $dst Destination node that will be after the moved node * * @return array New position of the moved subtree. */ function nstMoveToPrevSibling($src, $dst) { return $this->_moveSubtree($src, $dst['l']); } /** * Move a node and all its children to another place as the first child * of the specified destination node. * * @param array $src Source node, which will be moved * @param array $dst Destination node that will be the parent * of the moved node * * @return array New position of the moved subtree. */ function nstMoveToFirstChild($src, $dst) { return $this->_moveSubtree($src, $dst['l']+1); } /** * Move a node and all its children to another place as the last child * of the specified destination node. * * @param array $src Source node, which will be moved * @param array $dst Destination node that will be the parent * of the moved node * * @return array New position of the moved subtree. */ function nstMoveToLastChild($src, $dst) { return $this->_moveSubtree($src, $dst['r']); } /** * Move a node and all its children to another place * * @param array $src Source node, which will be moved * @param array $dst Destination node * * @return array New position of the moved subtree. * * @access private */ function _moveSubtree($src, $dst) { $treesize = $src['r']-$src['l']+1; $this->_shiftRLValues($dst, $treesize); if($src['l'] >= $dst){ // src was shifted too? $src['l'] += $treesize; $src['r'] += $treesize; } /* Now there is enough room next to target to move the subtree */ $newpos = $this->_shiftRLRange($src['l'], $src['r'], $dst-$src['l']); /* Correct values after source */ $this->_shiftRLValues($src['r']+1, -$treesize); if($src['l'] <= $dst){ // dst was shifted too? $newpos['l'] -= $treesize; $newpos['r'] -= $treesize; } return $newpos; } /********************************************************************* * Tree Destructors **********************************************************************/ /** * Delete the whole tree structure and content (even the root) * * @return bool On success, true. On fail, false. */ function nstDeleteTree() { if(!mysql_query("DELETE FROM ".$this->handle['table'])) { $this->_prtError(); return false; } return true; } /** * Delete a node and all its children or keep and outdent all its children * * @param array $node The Left and Right values of the node to delete * @param bool $outchildren true, outdent and keep children * false, delete children aswell * * @return mixed On success, array : The parent or previous sibling node the one * that has been deleted * On fail, bool : false */ function nstDelete($node, $outchildren=false) { $leftanchor = $node['l']; if($outchildren) { /* Outdent the children if needed and possible */ if(!$this->nstIsRoot($node)) { while($this->nstNbChildren($node) > 0) { /* Outdent, we sart from the end to keep the order of children */ $this->nstMoveToNextSibling($this->nstLastChild($node),$node); /* Update the original */ $node = $this->nstGetNodeWhereLeft($leftanchor); } } else { return false; } } $res = mysql_query("DELETE FROM ".$this->handle['table']." WHERE " .$this->handle['lvalname'].">=".$node['l']." AND " .$this->handle['rvalname']."<=".$node['r'] ); $this->_shiftRLValues($node['r']+1, $node['l'] - $node['r'] -1); if(!$res) { $this->_prtError(); return false; } else { return $this->nstGetNodeWhere($this->handle['lvalname']."<" .$leftanchor." ORDER BY " .$this->handle['lvalname']." DESC" ); } } /********************************************************************* * Tree Queries **********************************************************************/ /** * Get the Left and Right values of a node from a SQL query based on any * field (You specify the whole WHERE clause). * * @param string $whereclause WHERE clause to use to get the values * of the node. You can specify ORDER BY or * LIMIT clause here too. * * @return array Left and Right values of the node. * On fail, Left and Right are equal to "0" */ function nstGetNodeWhere($whereclause) { $noderes['l'] = 0; $noderes['r'] = 0; $res = mysql_query("SELECT * FROM ".$this->handle['table']." WHERE " .$whereclause ); if(!$res) { $this->_prtError(); } else { if($row = mysql_fetch_array($res)) { $noderes['l'] = $row[$this->handle['lvalname']]; $noderes['r'] = $row[$this->handle['rvalname']]; } } return $noderes; } /** * Get Right and Left values of the node that has the specified Left value. * * @param string $leftval Left value of the node to get * * @return array Left and Right values of the node. * On fail, Left and Right are equal to "0" */ function nstGetNodeWhereLeft($leftval) { return $this->nstGetNodeWhere($this->handle['lvalname']."=".$leftval); } /** * Get Right and Left values of the node that has the specified Right value. * * @param string $rightval Right value of the node to get * * @return array Left and Right values of the node. * On fail, Left and Right are equal to "0" */ function nstGetNodeWhereRight($rightval) { return $this->nstGetNodeWhere($this->handle['rvalname']."=".$rightval); } /** * Get the Left and Right values of the root node. * * @return array Left and Right values of the node. * On fail, Left and Right are equal to "0" */ function nstRoot() { return $this->nstGetNodeWhere($this->handle['lvalname']."=1"); } /** * Get Left and Right values of the First child of the specified node. * * @param array $node Base node from which the First child will be determined * * @return array Left and Right values of the node. * On fail, Left and Right are equal to "0" */ function nstFirstChild($node) { return $this->nstGetNodeWhere ($this->handle['lvalname']."=".($node['l']+1)); } /** * Get Left and Right values of the Last child of the specified node. * * @param array $node Base node from which the Last child will be determined * * @return array Left and Right values of the node. * On fail, Left and Right are equal to "0" */ function nstLastChild($node) { return $this->nstGetNodeWhere ($this->handle['rvalname']."=".($node['r']-1)); } /** * Get Left and Right values of the Previous sibling of a specified node. * * @param array $node Base node from which the Previous sibling will be determined * * @return array Left and Right values of the node. * On fail, Left and Right are equal to "0" */ function nstPrevSibling($node) { return $this->nstGetNodeWhere ($this->handle['rvalname']."=".($node['l']-1)); } /** * Get Left and Right values of the Next sibling of a specified node. * * @param array $node Base node from which the Next sibling will be determined * * @return array Left and Right values of the node. * On fail, Left and Right are equal to "0" */ function nstNextSibling($node) { return $this->nstGetNodeWhere ($this->handle['lvalname']."=".($node['r']+1)); } /** * Get Left and Right values of the ancestor of a node. * * @param array $node Base node from which the ancestor will be determined * * @return array Left and Right values of the node. * On fail, Left and Right are equal to "0" */ function nstAncestor($node) { return $this->nstGetNodeWhere($this->handle['lvalname']."<".($node['l']) ." AND ".$this->handle['rvalname'].">" .($node['r'])." ORDER BY " .$this->handle['rvalname'] ); } /********************************************************************* * Tree Functions **********************************************************************/ /** * Determine if a node is valid or not, just based on Left and * Right values of the specified node (NO SQL query). * * @param array $node Node to be validated * * @return bool On success, true. On fail, false. */ function nstValidNode($node) { return ($node['l'] < $node['r']); } /** * Determine if a node has ancestor or not. * * @param array $node Node to be analysed * * @return bool On success, true. On fail, false. */ function nstHasAncestor($node) { return $this->nstValidNode($this->nstAncestor($node)); } /** * Determine if a node has a previous sibiling node or not. * * @param array $node Node to be analysed * * @return bool On success, true. On fail, false. */ function nstHasPrevSibling($node) { return $this->nstValidNode($this->nstPrevSibling($node)); } /** * Determine if a node has a next sibiling node or not. * * @param array $node Node to be analysed * * @return bool On success, true. On fail, false. */ function nstHasNextSibling($node) { return $this->nstValidNode($this->nstNextSibling($node)); } /** * Determine if a node has children or not. * * @param array $node Node to be analysed * * @return bool On success, true. On fail, false. */ function nstHasChildren($node) { return (($node['r']-$node['l'])>1); } /** * Determine if a node is the root of the tree. * * @param array $node Node to be analysed * * @return bool On success, true. On fail, false. */ function nstIsRoot($node) { return ($node['l']==1); } /** * Determine if a node is a leaf of the tree. * * @param array $node Node to be analysed * * @return bool On success, true. On fail, false. */ function nstIsLeaf($node) { return (($node['r']-$node['l'])==1); } /** * Determine if a node is the direct or indirect child of another. * * @param array $node1 Potential direct or indirect child node * @param array $node2 Potential parent node * * @return bool On success, true. On fail, false. */ function nstIsChild($node1, $node2) { return (($node1['l']>$node2['l']) and ($node1['r']<$node2['r'])); } /** * Determine if a node is the child of another or if they are equal. * * @param array $node1 Potential direct or indirect child node * @param array $node2 Potential parent node * * @return bool On success, true. On fail, false. */ function nstIsChildOrEqual($node1, $node2) { return (($node1['l']>=$node2['l']) and ($node1['r']<=$node2['r'])); } /** * Determine if a node is equal to another. * * @param array $node1 First node * @param array $node2 Second node * * @return bool On success, true. On fail, false. */ function nstEqual($node1, $node2) { return (($node1['l']==$node2['l']) and ($node1['r']==$node2['r'])); } /** * Get the number of children (direct or not) of a node. * * @param array $node Node to analyse * * @return int Number of children, 0 if there is no child. * Less than 0 if not isn't a valid node ! */ function nstNbChildren($node) { return (($node['r']-$node['l']-1)/2); } /** * Get the numeric level of a node, root level is 0. * * @param array $node Node to analyse * * @return int Level of the specified node. */ function nstLevel($node) { $res = mysql_query("SELECT COUNT(*) AS level FROM ".$this->handle['table'] ." WHERE ".$this->handle['lvalname']."<".($node['l']) ." AND ".$this->handle['rvalname'].">".($node['r']) ); if($row = mysql_fetch_array ($res)) { return $row["level"]; }else{ return 0; } } /** * Get the popular "Breadcrumbs String", it is the string that describes * the full path to a node. * Example : "root > a-node > another-node > current-node" * * @param array $node The Left and Right values of the start node. * * @return array The path, descending order (root first) to the nood */ function nstGetBreadcrumbsPath($node, $attribute) { $breadcrumb = array(); $breadcrumb[] = $this->nstNodeAttribute($node, $attribute); /* Treat ancestor nodes */ while($this->nstAncestor($node) != array("l"=>0,"r"=>0)){ $breadcrumb[] = $this->nstNodeAttribute($this->nstAncestor($node), $attribute ); $node = $this->nstAncestor($node); } /* This is better than using array_unshift to add elements to the array */ return array_reverse($breadcrumb); } /********************************************************************* * Tree Walks ********************************************************************** * * The walks might be tricky to understand at start, but if you * understand the principle of mySQL recordset it's quite obvious. * * First you have to create the walk handle with nstWalkPreorder and * then you can go step by step throught the results with the function * nstWalkNext. With the return of nstWalkNext you know if you're at * the end of the structure. * * And you use your walk handle to get the datas with the function * nstWalkAttribute. Every fields of the table are in the query, you * just need to know their (field)name ! * **********************************************************************/ /** * Create a walk handle to walk through the tree according to * the preordered traversal. * * @param array $node Start node, only its children will be part * of the walk * * @return array A walk handle to use with other functions */ function nstWalkPreorder($node) { $res = mysql_query("SELECT * FROM ".$this->handle['table'] ." WHERE ".$this->handle['lvalname'].">=".$node['l'] ." AND ".$this->handle['rvalname']."<=".$node['r'] ." ORDER BY ".$this->handle['lvalname']); return array('recset' => $res, 'prevl' => $node['l'], /* Will be used mark the end */ 'prevr' => $node['r'], /* of the walk with other functions */ 'level' => -2 ); } /** * Go to the next step of the walk handle, update the level and get the * datas for the current row. * * @param array &$walkhand The walk handle (recordset, "start" node, level) * * @return mixed Still rows in the recordset, array : The Left and Right values * for the current node (from database). * No more row in the recordset, bool : false */ function nstWalkNext(&$walkhand) { if($row = mysql_fetch_array ($walkhand['recset'], MYSQL_ASSOC)){ /* Calculate the level */ $walkhand['level'] += $walkhand['prevl'] - $row[$this->handle['lvalname']] +2; /* Store current node */ $walkhand['prevl'] = $row[$this->handle['lvalname']]; $walkhand['prevr'] = $row[$this->handle['rvalname']]; $walkhand['row'] = $row; return array('l'=>$row[$this->handle['lvalname']], 'r'=>$row[$this->handle['rvalname']] ); } else{ return false; } } /** * Get the field value of a specified field from the walk handle recordset * * @param array $walkhand The walk handle (recordset, "start" node, level) * !!! ATTENTION !!! the walk handle recordset must * have been used once in nstWalkNext or at least * been through mysql_fetch_array once. * @param string $attribute The name of the field to get. * * @return mixed The value of the field, type depends of the field type. */ function nstWalkAttribute($walkhand, $attribute) { return $walkhand['row'][$attribute]; } /** * Get the Left and Right values of the current node from the walk handle * * @param array $walkhand The walk handle (recordset, "start" node, level) * * @return array The Left and Right values of the current walk handle. */ function nstWalkCurrent($walkhand) { return array('l'=>$walkhand['prevl'], 'r'=>$walkhand['prevr']); } /** * Get the Level od the current node from the walk handle * * @param array $walkhand The walk handle (recordset, "start" node, level) * * @return int The level of the current node in the walk handle. */ function nstWalkLevel($walkhand) { return $walkhand['level']; } /********************************************************************* * Display Tools **********************************************************************/ /** * Get the field value of the specified node with the field name. * * @param array $node The Left and Right values of the node. * @param string $attribute The name of the field to get. * * @return mixed If node found in DB and field exsits, string : the value * of the field. * Otherwise, bool : false */ function nstNodeAttribute($node, $attribute) { $res = mysql_query("SELECT * FROM ".$this->handle['table']." WHERE " .$this->handle['lvalname']."=".$node['l'] ); if($row = mysql_fetch_array($res)) { return $row[$attribute]; } else { return false; } } /** * Get the field value of the specified node with the field name. * * @param array $node The Left and Right values of the node. * @param string $attribute The name of the field to get. * * @return mixed If node found in DB and field exsits, string : the value * of the field. * Otherwise, bool : false */ function nstNodeAllAttributes($node) { $res = mysql_query("SELECT * FROM ".$this->handle['table']." WHERE " .$this->handle['lvalname']."=".$node['l'] ); if($row = mysql_fetch_array($res)) { return $row; } else { return false; } } /** * Display a subtree by starting with the specified node. * * @param array $node The Left and Right values of the start node. * @param string $attributes An array of the fields values you want to display * ie. : array("fieldname" => "value"). * Order of the array determins the order of display */ function nstPrintSubtree($node, $attributes) { $walk = $this->nstWalkPreorder($node); while($curr = $this->nstWalkNext($walk)) { /* Print Indentation */ print(str_repeat(" ", $this->nstWalkLevel($walk)*4)); /* Print Attributes */ $att = reset($attributes); while($att){ print ($walk['row'][$att]); $att = next($attributes); } print ("
"); } } /** * Display a subtree by starting with the specified node. * * @param array $node The Left and Right values of the start node. * @param string $attributes An array of the fields values you want to display * ie. : array("fieldname" => "value"). * Order of the array determins the order of display * * @deprecated Method depracated from 0.02 */ function nstPrintSubtree_old($node, $attributes) { $res = mysql_query("SELECT * FROM ".$this->handle['table']." ORDER BY " .$this->handle['lvalname'] ); if(!$res) { $this->_prtError(); } else { $level = -1; $prevl = 0; while($row = mysql_fetch_array ($res)) { /* Calculate level */ if($row[$this->handle['lvalname']] == ($prevl+1)) { $level+=1; } elseif($row[$this->handle['lvalname']] != ($prevr+1)) { $level-=1; } /* Add indentation */ print (str_repeat(" ", $level*4)); /* Add attributes */ $att = reset($attributes); while($att){ print ($att.":".$row[$att]); $att = next($attributes); } print ("
"); $prevl = $row[$this->handle['lvalname']]; $prevr = $row[$this->handle['rvalname']]; } } } /** * Display the whole tree (use the subtree display with the root node) * * @param string $attributes An array of the fields values you want to display * ie. : array("fieldname" => "value"). * Order of the array determins the order of display */ function nstPrintTree($attributes) { $this->nstPrintSubtree($this->nstRoot(), $attributes); } /********************************************************************* * Internal Functions **********************************************************************/ function _prtError(){ echo "

Error: ".mysql_errno().": ".mysql_error()."

"; } } ?>