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

Last change on this file since 334 was 334, checked in by quinn, 16 years ago

Fixed lots of misplings. I'm so embarrassed! ;P

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