source: tags/1.0.0/lib/NodeHeirarchy.inc.php @ 1

Last change on this file since 1 was 1, checked in by scdev, 19 years ago

Initial import.

File size: 32.4 KB
Line 
1<?php
2/**
3 * NodeHeirarchy.inc.php
4 * Code by Strangecode :: www.strangecode.com :: This document contains copyrighted information
5 */
6
7/**
8 * Objective: This class provides the tools to organize pieces of data into a
9 * heirarchy of nodes. Any form of data (article, product, image) can be
10 * represented as a node in this heirarchy. This class does not manipulate the
11 * data, nor is it involved in storing or retrieving the data. In fact it does
12 * not access the tables where data exists and cannot find out info about the
13 * data. You must provide identification of a piece of data (type and ID) to
14 * insert it into the heirarchy. The node heirarchy is completely
15 * separate from data storage and retreival. You must separatly store the data
16 * using whatever logic is specific to the data then also call these functions.
17 * Nodes are not the data. The nodes are mere singularities in virtual space
18 * that represent a piece of data's relationship with another. The heirarchy
19 * is an inverted tree structure. Each node can have virtually infinite
20 * children. Each child can have multiple parents.
21 *
22 * @requires  This class requires App.inc.php
23 * @requires  This class requires Utilities.inc.php
24 * @author    Quinn Comendant <quinn@strangecode.com>
25 * @version   1.0
26 */
27 
28require_once dirname(__FILE__) . '/App.inc.php';
29require_once dirname(__FILE__) . '/Utilities.inc.php';
30
31class NodeHeirarchy {
32
33    /**
34     * Array containing object parameters.
35     * @var array $params
36     *
37     *  $params['child_type'] = 'article'
38     *  $params['child_id'] = 23
39     *
40     */
41    var $params = array();
42
43
44    /**
45     * The active node type.
46     * @var string $child_type
47     */
48    var $child_type;
49
50    /**
51     * The active node id.
52     * @var string $child_id
53     */
54    var $child_id;
55
56    /**
57     * Boolean indicating whether or not we've set
58     * the 'active' node type and id.
59     * @var bool $node_init
60     */
61    var $node_init = false;
62   
63    /**
64     * Constructor
65     * @param resource  $dbh    A database handler if we are already connected.
66     * @param array     $params A hash containing any additional
67     *                          configuration or connection parameters.
68     */
69    function NodeHeirarchy($params=array())
70    {
71        $this->params = $params;
72    }
73   
74    /**
75     * Defines the default child_type and child_id for this object.
76     * @child_type string   this node's type
77     * @child_id string     this node's id
78     * @return string       the previous node identifier, or the current one
79     *                      if no new ones are specified.
80     */
81    function currentNode($child_type=null, $child_id=null)
82    {
83        $old_type = isset($this->child_type) ? $this->child_type : $child_type;
84        $old_id = isset($this->child_id) ? $this->child_id : $child_id;
85        if (isset($child_type) && isset($child_id)) {
86            $this->child_type = $child_type;
87            $this->child_id = $child_id;
88            $this->node_init = true;
89        }
90        return $this->toStringID($old_type, $old_id);
91    }
92   
93    /**
94     * Takes a node type and id and returns them as a serialized identifier like
95     * article__24 or category__84. If the first argument is an array, and the
96     * second is -1, we loop through the array and serialize the identifiers inside.
97     * @param  mixed    $child_type
98     * @param  string   $child_id
99     * @return mixed    If a single type and id are provided, a single vector identifier is returned,
100     *                  otherwise if an array is provided, an array of identifiers is returned.
101     */
102    function toStringID($child_type=null, $child_id=null)
103    {
104        if (!isset($child_type) || !isset($child_id)) {
105            if ($this->node_init) {
106                $child_type =& $this->child_type;
107                $child_id =& $this->child_id;
108            } else {
109                logMsg(_("toStringID failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
110                return false;
111            }
112        }
113        if (is_array($child_type) && $child_id === -1) {
114            // We take an array in the first argument. It really has nothing
115            // to do with $child_type.
116            if (!isset($child_type[0]['child_type'])) {
117                // It's not a properly configured array.
118                return false;
119            }
120            foreach ($child_type as $node) {
121                $serialized_arr[] = $node['child_type'] . '__' . $node['child_id'];
122            }
123            return $serialized_arr;
124        } else {
125            return $child_type . '__' . $child_id;
126        }
127    }
128   
129    /**
130     * Takes a singlar node identifier and returns it as components of an array.
131     * @param string    $node
132     * @return mixed    Array of node type and id on success, false on failure.
133     */
134    function toArrayID(&$node)
135    {
136        if (preg_match('/^([[:alnum:]]+)__-?([[:digit:]]+)$/', $node, $node_parts)) {
137            return array('node_type' => $node_parts[1], 'node_id' => $node_parts[2]);
138        } else {
139            logMsg(_("Cannot parse node identifier, not formated correctly."), LOG_ERR, __FILE__, __LINE__);
140            return false;
141        }
142    }
143
144    /**
145     * Adds a single node. Tests if it already exists first.
146     * @param string    $child_type
147     * @param string    $child_id
148     * @param string    $parents    A serialized array of serialized parent identifiers
149     * @param string    $relationship_type
150     * @return bool     true on sucess, false on error.
151     */
152    function insertNode($parents, $child_type=null, $child_id=null, $relationship_type=null, $title='')
153    {
154        if (!isset($child_type) || !isset($child_id)) {
155            if ($this->node_init) {
156                $child_type =& $this->child_type;
157                $child_id =& $this->child_id;
158            } else {
159                logMsg(_("insertNode failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
160                return false;
161            }
162        }
163       
164        // Make sure this is not empty and an array, even if it has only one value.
165        if ('' == $parents) {
166            raiseMsg(sprintf(_("Cannot add node <strong>%s %s</strong>, no parent was specified."), $child_type, $child_id), MSG_ERR, __FILE__, __LINE__);
167            logMsg(sprintf(_("Cannot add node <strong>%s %s</strong>, no parent was specified."), $child_type, $child_id), LOG_ERR, __FILE__, __LINE__);
168            return false;
169        } else if (!is_array($parents)) {
170            $parents = array($parents);
171        }
172       
173        // Remove duplicates.
174        $parents = array_unique($parents);
175               
176        // Test that this node does not already exist and that the new parents
177        // do exist before we continue.
178        foreach ($parents as $parent_string) {
179            $parent = $this->toArrayID($parent_string);
180            if ($this->nodeExists($child_type, $child_id, $parent['node_type'], $parent['node_id'], $relationship_type)) {
181                raiseMsg(sprintf(_("Cannot add node <strong>%s %s</strong> to parent <strong>%s %s</strong>. It already exists there"), $child_type, $child_id, $parent['node_type'], $parent['node_id']), MSG_ERR, __FILE__, __LINE__);
182                logMsg(sprintf(_("Cannot add node <strong>%s %s</strong> to parent <strong>%s %s</strong>. It already exists there"), $child_type, $child_id, $parent['node_type'], $parent['node_id']), LOG_ERR, __FILE__, __LINE__);
183                return false;
184            }
185            if (!$this->nodeExists($parent['node_type'], $parent['node_id'])) {
186                raiseMsg(sprintf(_("Cannot add node <strong>%s %s</strong> to nonexistent parent <strong>%s %s</strong>."), $child_type, $child_id, $parent['node_type'], $parent['node_id']), MSG_ERR, __FILE__, __LINE__);
187                logMsg(sprintf(_("Cannot add node <strong>%s %s</strong> to nonexistent parent <strong>%s %s</strong>."), $child_type, $child_id, $parent['node_type'], $parent['node_id']), LOG_ERR, __FILE__, __LINE__);
188                return false;
189            }   
190        }
191       
192        // Insert new nodes with the new parents.
193        foreach ($parents as $parent_string) {
194            $parent = $this->toArrayID($parent_string);
195            dbQuery("
196                INSERT INTO node_tbl (
197                    parent_type,
198                    parent_id,
199                    child_type,
200                    child_id,
201                    relationship_type,
202                    title
203                ) VALUES (
204                    '" . addslashes($parent['node_type']) . "',
205                    '" . addslashes($parent['node_id']) . "',
206                    '" . addslashes($child_type) . "',
207                    '" . addslashes($child_id) . "',
208                    " . (is_null($relationship_type) ? "NULL" : "'" . addslashes($relationship_type) . "'") . ",
209                    '" . addslashes($title) . "'
210                )
211            ");
212            logMsg(sprintf('insertNode: Added node %s %s with parent %s %s.', $child_type, $child_id, $parent['node_type'], $parent['node_id']), LOG_DEBUG, __FILE__, __LINE__);
213        }
214        return true;
215    }
216
217    /**
218     * Deletes a node using the following algorithm:
219     * (1) Find each of this node's parents. Place serialized identifiers in array.
220     * (2) Find all children of this node, and move each to all the parents of this node.
221     * (3) Delete this node.
222     *
223     * @param  string    $child_type
224     * @param  string    $child_id
225     *
226     * @return bool      false on error, true otherwise.
227     */
228    function deleteNode($child_type=null, $child_id=null)
229    {
230        if (!isset($child_type) || !isset($child_id)) {
231            if ($this->node_init) {
232                $child_type =& $this->child_type;
233                $child_id =& $this->child_id;
234            } else {
235                logMsg(_("deleteNode failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
236                return false;
237            }
238        }
239
240        // Move children to parents of node being deleted.
241        $new_parents = $this->getParents($child_type, $child_id);
242        $children = $this->getChildren($child_type, $child_id);
243
244        if ($children && $new_parents) {
245            foreach ($children as $child) {
246                $this->moveNode($new_parents, $child['child_type'], $child['child_id']);
247            }
248        }
249
250        dbQuery("
251            DELETE FROM node_tbl
252            WHERE child_type = '" . addslashes($child_type) . "'
253            AND child_id = '" . addslashes($child_id) . "'
254        ");
255        logMsg(sprintf('deleteNode: Deleted node %s %s.', $child_type, $child_id), LOG_DEBUG, __FILE__, __LINE__);
256
257        return true;
258    }
259
260    /**
261     * Moves a node using the following algorithm:
262     * (1) Ensure the newly assigned parents exist.
263     * (2) Delete old nodes with the specified type and id.
264     * (3) Add the node with new parents.
265     *
266     * @param  string    $child_type
267     * @param  string    $child_id
268     * @param  array     $new_parents  An array of serialized node identifiers
269     *                                 that the node will belong to. Example:
270     *                                 $new_parents[0] = 'category__23';
271     * @param  string    $relationship_type
272     *
273     * @return bool      false on error, true otherwise.
274     */     
275    function moveNode($new_parents=null, $child_type=null, $child_id=null, $relationship_type=null, $title='')
276    {
277        if (!isset($child_type) || !isset($child_id)) {
278            if ($this->node_init) {
279                $child_type =& $this->child_type;
280                $child_id =& $this->child_id;
281            } else {
282                logMsg(_("moveNode failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
283                return false;
284            }
285        }
286       
287        // Make sure this is not empty and an array, even if it has only one value.
288        if (empty($new_parents)) {
289            raiseMsg(sprintf(_("Cannot move node <strong>%s %s</strong>, no parent was specified."), $child_type, $child_id), MSG_ERR, __FILE__, __LINE__);
290            logMsg(sprintf(_("Cannot move node <strong>%s %s</strong>, no parent was specified."), $child_type, $child_id), LOG_ERR, __FILE__, __LINE__);
291            return false;
292        } else if (!is_array($new_parents)) {
293            $new_parents = array($new_parents);
294        }
295       
296        // Remove duplicates.
297        $new_parents = array_unique($new_parents);
298
299        // Test that the new parents exist before we delete the old ones.
300        foreach ($new_parents as $parent_string) {
301            $parent = $this->toArrayID($parent_string);
302            if (!$this->nodeExists($parent['node_type'], $parent['node_id'])) {
303                raiseMsg(sprintf(_("Cannot move node <strong>%s %s</strong> to nonexistent parent <strong>%s %s</strong>."), $child_type, $child_id, $parent['node_type'], $parent['node_id']), MSG_ERR, __FILE__, __LINE__);
304                logMsg(sprintf(_("Cannot move node <strong>%s %s</strong> to nonexistent parent <strong>%s %s</strong>."), $child_type, $child_id, $parent['node_type'], $parent['node_id']), LOG_ERR, __FILE__, __LINE__);
305                return false;
306            }   
307            if ($this->isAncestor($child_type, $child_id, $parent['node_type'], $parent['node_id'])) {
308                raiseMsg(sprintf(_("Cannot move node <strong>%s %s</strong> to parent <strong>%s %s</strong> because a node cannot have itself as a parent."), $child_type, $child_id, $parent['node_type'], $parent['node_id']), MSG_ERR, __FILE__, __LINE__);
309                logMsg(sprintf(_("Cannot move node <strong>%s %s</strong> to parent <strong>%s %s</strong> because a node cannot have itself as a parent."), $child_type, $child_id, $parent['node_type'], $parent['node_id']), LOG_ERR, __FILE__, __LINE__);
310                return false;
311            }   
312        }
313       
314        if (empty($title)) {
315            // Select the title of the node we are moving, so we can add it again with the same info.
316            $qid = dbQuery("
317                SELECT title FROM node_tbl
318                WHERE child_type = '" . addslashes($child_type) . "'
319                AND child_id = '" . addslashes($child_id) . "'
320                AND relationship_type " . (is_null($relationship_type) ? "IS NULL" : "= '" . addslashes($relationship_type) . "'") . "
321            ");
322            list($title) = mysql_fetch_row($qid);
323        }
324       
325        // Delete the nodes with the old parents.
326        dbQuery("
327            DELETE FROM node_tbl
328            WHERE child_type = '" . addslashes($child_type) . "'
329            AND child_id = '" . addslashes($child_id) . "'
330            AND relationship_type " . (is_null($relationship_type) ? "IS NULL" : "= '" . addslashes($relationship_type) . "'") . "
331        ");
332        logMsg(sprintf('moveNode: Deleted node %s %s.', $child_type, $child_id), LOG_DEBUG, __FILE__, __LINE__);
333       
334        // Insert new nodes with the new parents.
335        $this->insertNode($new_parents, $child_type, $child_id, $relationship_type, $title);
336       
337        return true;
338    }
339
340    /**
341     * Returns an array of all the parents of the current node (just the ones
342     * immediatly above this node). You may need to call array_unique if you
343     * don't want duplicate nodes returned.
344     *
345     * @param  string   $child_type
346     * @param  string   $child_id
347     * @return string   The parents as an array of serialized node identifiers.
348     */
349    function getParents($child_type=null, $child_id=null)
350    {
351        if (!isset($child_type) || !isset($child_id)) {
352            if ($this->node_init) {
353                $child_type =& $this->child_type;
354                $child_id =& $this->child_id;
355            } else {
356                logMsg(_("getParents failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
357                return false;
358            }
359        }
360
361        $qid = dbQuery("
362            SELECT parent_type, parent_id
363            FROM node_tbl
364            WHERE child_type = '" . addslashes($child_type) . "'
365            AND child_id = '" . addslashes($child_id) . "'
366        ");
367       
368        $parents = array();
369        while ($row = mysql_fetch_assoc($qid)) {
370            $parents[] = $this->toStringID($row['parent_type'], $row['parent_id']);
371        }
372
373        if (sizeof($parents) > 0) {
374            return $parents;
375        } else {
376            return false;
377        }
378    }
379
380    /**
381     * Returns details of specified node.
382     *
383     * @param  string  $child_type
384     * @param  string  $child_id
385     * @return array   type, id, title, subnode_quantity.
386     */
387    function getNode($child_type=null, $child_id=null)
388    {
389        if (!isset($child_type) || !isset($child_id)) {
390            if ($this->node_init) {
391                $child_type =& $this->child_type;
392                $child_id =& $this->child_id;
393            } else {
394                logMsg(_("getNode failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
395                return false;
396            }
397        }
398       
399        $qid = dbQuery("
400            SELECT child_type, child_id, title, subnode_quantity
401            FROM node_tbl
402            WHERE child_type = '" . addslashes($child_type) . "'
403            AND child_id = '" . addslashes($child_id) . "'
404        ");
405       
406        $children = array();
407        while ($row = mysql_fetch_assoc($qid)) {
408            $children[] = $row;
409        }
410        if (sizeof($children) > 0) {
411            return $children;
412        } else {
413            return false;
414        }
415    }
416
417    /**
418     * Returns an array of all the children of the current node (just the ones
419     * immediatly below this node). You may need to call array_unique if you
420     * don't want duplicate nodes returned.
421     *
422     * @param  string  $child_type
423     * @param  string  $child_id
424     * @param  string  $type_constraint  An array of node types to restrict the search to.
425     *
426     * @return string  The children as an array of serialized node identifiers.
427     */
428    function getChildren($child_type=null, $child_id=null, $type_constraint=null, $order='')
429    {
430        if (!isset($child_type) || !isset($child_id)) {
431            if ($this->node_init) {
432                $child_type =& $this->child_type;
433                $child_id =& $this->child_id;
434            } else {
435                logMsg(_("getChildren failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
436                return false;
437            }
438        }
439       
440        $in_clause = '';
441        if (isset($type_constraint)) {
442            if (!is_array($type_constraint)) {
443                $type_constraint = array($type_constraint);
444            }
445            $in_clause = "AND child_type IN ('" . join("','", array_map('addslashes', $type_constraint)) . "')";
446        }
447       
448        $qid = dbQuery("
449            SELECT *
450            FROM node_tbl
451            WHERE parent_type = '" . addslashes($child_type) . "'
452            AND parent_id = '" . addslashes($child_id) . "'
453            $in_clause
454            " . addslashes($order) . "
455        ");
456       
457        $children = array();
458        while ($row = mysql_fetch_assoc($qid)) {
459            $children[] = $row;
460        }
461        if (sizeof($children) > 0) {
462            return $children;
463        } else {
464            return false;
465        }
466    }
467
468    /**
469     * Give the number of children a category has. We are talking about the
470     * direct children, on the next level.
471     *
472     * @param string optional $parent The name of the parent from where we begin.
473     * @param string $type_constraint  An array of node types to restrict the search to.
474     *
475     * @return integer
476     */
477    function getNumberChildren($child_type=null, $child_id=null, $type_constraint=null)
478    {
479        if (!isset($child_type) || !isset($child_id)) {
480            if ($this->node_init) {
481                $child_type =& $this->child_type;
482                $child_id =& $this->child_id;
483            } else {
484                logMsg(_("getNumberChildren failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
485                return false;
486            }
487        }
488       
489        $in_clause = '';
490        if (isset($type_constraint)) {
491            if (!is_array($type_constraint)) {
492                $type_constraint = array($type_constraint);
493            }
494            $in_clause = "AND child_type IN ('" . join("','", array_map('addslashes', $type_constraint)) . "')";
495        }
496
497        $qid = dbQuery("
498            SELECT COUNT(*)
499            FROM node_tbl
500            WHERE parent_type = '" . addslashes($child_type) . "'
501            AND parent_id = '" . addslashes($child_id) . "'
502            $in_clause
503        ");
504        list($num_children) = mysql_fetch_row($qid);
505
506        return $num_children;
507    }
508
509    /**
510     * Tests of the specified node is a leaf (i.e. it has no children).
511     * @param  string    $child_type
512     * @param  string    $child_id
513     * @return bool      true if a leaf, or false if not or an error
514     */
515    function isLeaf($child_type=null, $child_id=null)
516    {
517        if (!isset($child_type) || !isset($child_id)) {
518            if ($this->node_init) {
519                $child_type =& $this->child_type;
520                $child_id =& $this->child_id;
521            } else {
522                logMsg(_("isLeaf failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
523                return false;
524            }
525        }
526       
527        if ($this->getNumberChildren($child_type, $child_id) <= 0) {
528            return true;
529        } else {
530            return false;
531        }
532    }
533
534    /**
535     * This function will tell you if a specified node (the child) is actually
536     * an ancestor to another node that perhaps is considered to be a parent. If
537     * the specified node IS an ancestor of a node made into it's parent, we would
538     * have a circular reference that would cause an infinite loop with any
539     * recursive queries of the heirarchy.
540     * @param  string    $child_type
541     * @param  string    $child_id
542     * @param  string    $considered_parent_type
543     * @param  string    $considered_parent_id
544     * @return bool      true if the child is an ancestor to the considered
545     *                   parent, or false otherwise, or in case of failure.
546     */
547    function isAncestor($child_type, $child_id, $considered_parent_type, $considered_parent_id)
548    {
549        $family_tree = $this->getAllAncestors($considered_parent_type, $considered_parent_id);
550        $family_tree = $this->toStringID($family_tree, -1);
551       
552        if (in_array($this->toStringID($child_type, $child_id), $family_tree)) {
553            return true;
554        } else {
555            return false;
556        }
557    }
558
559    /**
560     * This function builds an array of all the parents starting from a
561     * specified node, up to the root of the tree. It will follow paths for
562     * nodes with multiple parents, branching off and filling the array with
563     * ALL ancestors to the specified node. I'm not sure what the order will be
564     * but that probably isn't useful anyways. I use this to prevent circular
565     * references in the heirarchy.
566     * @param  string    $child_type
567     * @param  string    $child_id
568     * @param  bool      $go_linear  ?
569     * @param  int       $_return_flag  An internal value that counts up as
570     *                                  recursion progesses. When the value
571     *                                  drops back to 0, we return the output.
572     * @return array     Array of serialized node identifiers.
573     */
574    function getAllAncestors($child_type, $child_id, $go_linear=false, $_return_flag=true)
575    {
576        static $output = array();
577        static $return_flag;
578       
579        $qid = dbQuery("
580            SELECT parent_type, parent_id, child_type, child_id, title, subnode_quantity
581            FROM node_tbl
582            WHERE child_type = '" . addslashes($child_type) . "'
583            AND child_id = '" . addslashes($child_id) . "'
584        ");
585        while ($row = mysql_fetch_assoc($qid)) {
586            // Preventing circular references.
587            if ($row['parent_type'] == $child_type && $row['parent_id'] == $child_id) {
588                continue;
589            }
590           
591            // Build a linear path to root...no wormholes.
592            if ($enough_already && $go_linear) {
593                continue;
594            }
595            $enough_already = true;
596           
597            // To prevent duplicates, only add the new found node
598            // if not already in the array of ancestors.
599            $curr_items = sizeof($output) > 0 ? $this->toStringID($output, -1) : array();
600            if (!in_array($this->toStringID($row['child_type'], $row['child_id']), $curr_items)) {
601                $output[] = $row;
602            }
603           
604            $this->getAllAncestors($row['parent_type'], $row['parent_id'], $go_linear, false);
605        }
606        if ($_return_flag) {
607            // We must reset the static $output variable so that it does
608            // not fill up during subsequent function calls.
609            $ret = $output;
610            $output = array();
611            return $ret;
612        }
613    }
614
615    /**
616     * Tests if the specified node exists. If only the child type and id is
617     * specified, check if any node exists like that. If the parent type and id
618     * is also provided, test for an exact node match.
619     * @param  string    $child_type
620     * @param  string    $child_id
621     * @param  string    $parent_type
622     * @param  string    $parent_id
623     * @param  string    $relationship_type
624     * @return bool      true if a leaf, or false if not or an error
625     */
626    function nodeExists($child_type=null, $child_id=null, $parent_type=null, $parent_id=null, $relationship_type=null)
627    {
628        if (!isset($child_type) || !isset($child_id)) {
629            if ($this->node_init) {
630                $child_type =& $this->child_type;
631                $child_id =& $this->child_id;
632            } else {
633                logMsg(_("nodeExists failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
634                return false;
635            }
636        }
637       
638        if (isset($parent_type) && isset($parent_id)) {
639            $qid = dbQuery("
640                SELECT 1 FROM node_tbl
641                WHERE parent_type = '" . addslashes($parent_type) . "'
642                AND parent_id = '" . addslashes($parent_id) . "'
643                AND child_type = '" . addslashes($child_type) . "'
644                AND child_id = '" . addslashes($child_id) . "'
645                AND relationship_type " . (is_null($relationship_type) ? "IS NULL" : "= '" . addslashes($relationship_type) . "'") . "
646            ");
647        } else {
648            $qid = dbQuery("
649                SELECT 1 FROM node_tbl
650                WHERE child_type = '" . addslashes($child_type) . "'
651                AND child_id = '" . addslashes($child_id) . "'
652            ");
653        }
654        if (mysql_num_rows($qid) > 0) {
655            return true;
656        } else {
657            return false;
658        }
659    }
660
661    /**
662     * Recursively go through the node tree, starting at a specified node, and
663     * drill down, filling an array with info about the nodes:
664     *
665     * @param  array   $preselected
666     * @param  string  $child_type
667     * @param  string  $child_id
668     * @param  string  $type_constraint  An array of node types to restrict the search to.
669     * @param  bool    $include_curr     Do we include the specified node in the list?
670     * @param  string  $order            SQL to append to the query of the getChildren
671     *                                   call. Ex: 'ORDER BY child_id DESC'
672     * @return array  Details of from the node table of all nodes below the
673     *                specified node: (type, id, title, indent level, selected status)
674     */
675    function &getNodeList($preselected=null, $child_type=null, $child_id=null, $type_constraint=null, $include_curr=false, $order='', $_indent=0, $_return_flag=true)
676    {
677        static $output = array();
678        static $is_a_leaf = array();
679       
680        if (!isset($child_type) || !isset($child_id)) {
681            if ($this->node_init) {
682                $child_type =& $this->child_type;
683                $child_id =& $this->child_id;
684            } else {
685                logMsg(_("getNodeList failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
686                return false;
687            }
688        }
689       
690        if (!is_array($preselected)) {
691            $preselected = array($preselected);
692        }
693       
694        if ($_return_flag && $include_curr) {
695            $my_children = $this->getNode($child_type, $child_id);
696        } else {
697            $my_children = $this->getChildren($child_type, $child_id, $type_constraint, $order);
698        }
699
700        if ($my_children) {
701            $num_children = sizeof($my_children);
702            for ($i=0; $i<$num_children; $i++) {
703
704                $row = $my_children[$i];
705                // Preventing circular references.
706                if ($row['child_type'] == $child_type && $row['child_id'] == $child_id) {
707                    logMsg(sprintf(_("Circular reference detected: %s has itself as a parent."), $this->toStringID($row['child_type'], $row['child_id'])), LOG_ERR, __FILE__, __LINE__);
708                    continue;
709                }
710                $row['indent'] = $_indent;
711                if (in_array($this->toStringID($row['child_type'], $row['child_id']), $preselected)) {
712                    $row['selected'] = true;
713                }
714                $output[] = $row;
715                unset($row);
716               
717                // This is so we test if each node is a string only once. We store the result in the is_a_leaf array statically.
718                if (!isset($is_a_leaf[$this->toStringID($my_children[$i]['child_type'], $my_children[$i]['child_id'])])) {
719                    $is_a_leaf[$this->toStringID($my_children[$i]['child_type'], $my_children[$i]['child_id'])] = $this->isLeaf($my_children[$i]['child_type'], $my_children[$i]['child_id']);
720                }
721                if (!$is_a_leaf[$this->toStringID($my_children[$i]['child_type'], $my_children[$i]['child_id'])]) {
722                    // If this node is not a leaf, we dive into it recursivly.
723                    $this->getNodeList($preselected, $my_children[$i]['child_type'], $my_children[$i]['child_id'], $type_constraint, $include_curr, $order, $_indent+1, false);
724                }
725            }
726        }
727
728        if ($_return_flag) {
729            // We must reset the static variables so that they do
730            // not fill up during subsequent function calls.
731            $ret = $output;
732            $output = array();
733            $is_a_leaf = array();
734            return $ret;
735        }
736
737    }
738   
739   
740    /**
741     * Counts the number of items linked to each parent node
742     * putting the result in the relevant category counters. This function is
743     * called each time a product, article, etc. is inserted, updated, or deleted.
744     *
745     * @param  string  $type_constraint  An array of node types to restrict the search to.
746     */
747    function rebuildSubnodeQty($type_constraint=null)
748    {
749        // Reset all the category counters to zero.
750        dbQuery("UPDATE node_tbl SET subnode_quantity = 0");
751       
752        // Get all the nodes.
753        $qid = dbQuery("SELECT DISTINCT child_type, child_id FROM node_tbl");
754       
755        // For each node count the number of children...
756        while (list($child_type, $child_id) = mysql_fetch_row($qid)) {
757            $num_children = $this->getNumberChildren($child_type, $child_id, $type_constraint);
758            // ...and add that number to the node counter for that object and all parents.
759            if ($num_children > 0) {
760                $this->setSubnodeQtyToParents($child_type, $child_id, $num_children);
761            }
762        }
763    }
764   
765    /**
766     * Used internally by setSubnodeQty to add the quantity of subnodes to
767     * all parents recursivly.
768     */
769    function setSubnodeQtyToParents($child_type, $child_id, $num_children)
770    {
771        dbQuery("
772            UPDATE node_tbl
773            SET subnode_quantity = subnode_quantity + '" . addslashes($num_children) . "'
774            WHERE child_type = '" . addslashes($child_type) . "'
775            AND child_id = '" . addslashes($child_id) . "'
776        ",false);
777        $qid = dbQuery("
778            SELECT parent_type, parent_id
779            FROM node_tbl
780            WHERE child_type = '" . addslashes($child_type) . "'
781            AND child_id = '" . addslashes($child_id) . "'
782        ",false);
783        while ((list($parent_type, $parent_id) = mysql_fetch_row($qid)) && $parent_id > 0) {
784            $this->setSubnodeQtyToParents($parent_type, $parent_id, $num_children);
785        }
786    }
787
788// THE END
789}
790
791
792
793
794?>
Note: See TracBrowser for help on using the repository browser.