source: trunk/lib/Hierarchy.inc.php @ 86

Last change on this file since 86 was 86, checked in by scdev, 18 years ago

renamed NodeHeirarchy? to Hierarchy to fix misspelling and remove useless node part of name.

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