source: branches/1.1dev/lib/Hierarchy.inc.php @ 83

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

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

File size: 33.3 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                    '" . mysql_real_escape_string($parent['node_type']) . "',
205                    '" . mysql_real_escape_string($parent['node_id']) . "',
206                    '" . mysql_real_escape_string($child_type) . "',
207                    '" . mysql_real_escape_string($child_id) . "',
208                    " . (is_null($relationship_type) ? "NULL" : "'" . mysql_real_escape_string($relationship_type) . "'") . ",
209                    '" . mysql_real_escape_string($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 = '" . mysql_real_escape_string($child_type) . "'
253            AND child_id = '" . mysql_real_escape_string($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 = '" . mysql_real_escape_string($child_type) . "'
319                AND child_id = '" . mysql_real_escape_string($child_id) . "'
320                AND relationship_type " . (is_null($relationship_type) ? "IS NULL" : "= '" . mysql_real_escape_string($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 = '" . mysql_real_escape_string($child_type) . "'
329            AND child_id = '" . mysql_real_escape_string($child_id) . "'
330            AND relationship_type " . (is_null($relationship_type) ? "IS NULL" : "= '" . mysql_real_escape_string($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, $type_constraint=null, $order='')
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        $in_clause = '';
362        if (isset($type_constraint)) {
363            if (!is_array($type_constraint)) {
364                $type_constraint = array($type_constraint);
365            }
366            $in_clause = "AND parent_type IN ('" . join("','", array_map('mysql_real_escape_string', $type_constraint)) . "')";
367        }
368
369        $qid = dbQuery("
370            SELECT parent_type, parent_id
371            FROM node_tbl
372            WHERE child_type = '" . mysql_real_escape_string($child_type) . "'
373            AND child_id = '" . mysql_real_escape_string($child_id) . "'
374            $in_clause
375            " . mysql_real_escape_string($order) . "
376        ");
377       
378        $parents = array();
379        while ($row = mysql_fetch_assoc($qid)) {
380            $parents[] = $this->toStringID($row['parent_type'], $row['parent_id']);
381        }
382
383        if (sizeof($parents) > 0) {
384            return $parents;
385        } else {
386            return false;
387        }
388    }
389
390    /**
391     * Returns details of specified node.
392     *
393     * @param  string  $child_type
394     * @param  string  $child_id
395     * @return array   type, id, title, subnode_quantity.
396     */
397    function getNode($child_type=null, $child_id=null)
398    {
399        if (!isset($child_type) || !isset($child_id)) {
400            if ($this->node_init) {
401                $child_type =& $this->child_type;
402                $child_id =& $this->child_id;
403            } else {
404                logMsg(_("getNode failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
405                return false;
406            }
407        }
408       
409        $qid = dbQuery("
410            SELECT child_type, child_id, title, subnode_quantity
411            FROM node_tbl
412            WHERE child_type = '" . mysql_real_escape_string($child_type) . "'
413            AND child_id = '" . mysql_real_escape_string($child_id) . "'
414        ");
415       
416        $children = array();
417        while ($row = mysql_fetch_assoc($qid)) {
418            $children[] = $row;
419        }
420        if (sizeof($children) > 0) {
421            return $children;
422        } else {
423            return false;
424        }
425    }
426
427    /**
428     * Returns an array of all the children of the current node (just the ones
429     * immediatly below this node). You may need to call array_unique if you
430     * don't want duplicate nodes returned.
431     *
432     * @param  string  $child_type
433     * @param  string  $child_id
434     * @param  string  $type_constraint  An array of node types to restrict the search to.
435     *
436     * @return string  The children as an array of serialized node identifiers.
437     */
438    function getChildren($child_type=null, $child_id=null, $type_constraint=null, $order='')
439    {
440        if (!isset($child_type) || !isset($child_id)) {
441            if ($this->node_init) {
442                $child_type =& $this->child_type;
443                $child_id =& $this->child_id;
444            } else {
445                logMsg(_("getChildren failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
446                return false;
447            }
448        }
449       
450        $in_clause = '';
451        if (isset($type_constraint)) {
452            if (!is_array($type_constraint)) {
453                $type_constraint = array($type_constraint);
454            }
455            $in_clause = "AND child_type IN ('" . join("','", array_map('mysql_real_escape_string', $type_constraint)) . "')";
456        }
457       
458        $qid = dbQuery("
459            SELECT *
460            FROM node_tbl
461            WHERE parent_type = '" . mysql_real_escape_string($child_type) . "'
462            AND parent_id = '" . mysql_real_escape_string($child_id) . "'
463            $in_clause
464            " . mysql_real_escape_string($order) . "
465        ");
466       
467        $children = array();
468        while ($row = mysql_fetch_assoc($qid)) {
469            $children[] = $row;
470        }
471        if (sizeof($children) > 0) {
472            return $children;
473        } else {
474            return false;
475        }
476    }
477
478    /**
479     * Give the number of children a category has. We are talking about the
480     * direct children, on the next level.
481     *
482     * @param string optional $parent The name of the parent from where we begin.
483     * @param string $type_constraint  An array of node types to restrict the search to.
484     *
485     * @return integer
486     */
487    function getNumberChildren($child_type=null, $child_id=null, $type_constraint=null)
488    {
489        if (!isset($child_type) || !isset($child_id)) {
490            if ($this->node_init) {
491                $child_type =& $this->child_type;
492                $child_id =& $this->child_id;
493            } else {
494                logMsg(_("getNumberChildren failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
495                return false;
496            }
497        }
498       
499        $in_clause = '';
500        if (isset($type_constraint)) {
501            if (!is_array($type_constraint)) {
502                $type_constraint = array($type_constraint);
503            }
504            $in_clause = "AND child_type IN ('" . join("','", array_map('mysql_real_escape_string', $type_constraint)) . "')";
505        }
506
507        $qid = dbQuery("
508            SELECT COUNT(*)
509            FROM node_tbl
510            WHERE parent_type = '" . mysql_real_escape_string($child_type) . "'
511            AND parent_id = '" . mysql_real_escape_string($child_id) . "'
512            $in_clause
513        ");
514        list($num_children) = mysql_fetch_row($qid);
515
516        return $num_children;
517    }
518
519    /**
520     * Tests of the specified node is a leaf (i.e. it has no children).
521     * @param  string    $child_type
522     * @param  string    $child_id
523     * @return bool      true if a leaf, or false if not or an error
524     */
525    function isLeaf($child_type=null, $child_id=null)
526    {
527        if (!isset($child_type) || !isset($child_id)) {
528            if ($this->node_init) {
529                $child_type =& $this->child_type;
530                $child_id =& $this->child_id;
531            } else {
532                logMsg(_("isLeaf failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
533                return false;
534            }
535        }
536       
537        if ($this->getNumberChildren($child_type, $child_id) <= 0) {
538            return true;
539        } else {
540            return false;
541        }
542    }
543
544    /**
545     * This function will tell you if a specified node (the child) is actually
546     * an ancestor to another node that perhaps is considered to be a parent. If
547     * the specified node IS an ancestor of a node made into it's parent, we would
548     * have a circular reference that would cause an infinite loop with any
549     * recursive queries of the heirarchy.
550     * @param  string    $child_type
551     * @param  string    $child_id
552     * @param  string    $considered_parent_type
553     * @param  string    $considered_parent_id
554     * @return bool      true if the child is an ancestor to the considered
555     *                   parent, or false otherwise, or in case of failure.
556     */
557    function isAncestor($child_type, $child_id, $considered_parent_type, $considered_parent_id)
558    {
559        $family_tree = $this->getAllAncestors($considered_parent_type, $considered_parent_id);
560        $family_tree = $this->toStringID($family_tree, -1);
561       
562        if (in_array($this->toStringID($child_type, $child_id), $family_tree)) {
563            return true;
564        } else {
565            return false;
566        }
567    }
568
569    /**
570     * This function builds an array of all the parents starting from a
571     * specified node, up to the root of the tree. It will follow paths for
572     * nodes with multiple parents, branching off and filling the array with
573     * ALL ancestors to the specified node. I'm not sure what the order will be
574     * but that probably isn't useful anyways. I use this to prevent circular
575     * references in the heirarchy.
576     * @param  string    $child_type
577     * @param  string    $child_id
578     * @param  bool      $go_linear  ?
579     * @param  int       $_return_flag  An internal value that counts up as
580     *                                  recursion progesses. When the value
581     *                                  drops back to 0, we return the output.
582     * @return array     Array of serialized node identifiers.
583     */
584    function getAllAncestors($child_type, $child_id, $go_linear=false, $_return_flag=true)
585    {
586        static $output = array();
587        static $return_flag;
588       
589        $qid = dbQuery("
590            SELECT parent_type, parent_id, child_type, child_id, title, subnode_quantity
591            FROM node_tbl
592            WHERE child_type = '" . mysql_real_escape_string($child_type) . "'
593            AND child_id = '" . mysql_real_escape_string($child_id) . "'
594        ");
595        while ($row = mysql_fetch_assoc($qid)) {
596            // Preventing circular references.
597            if ($row['parent_type'] == $child_type && $row['parent_id'] == $child_id) {
598                continue;
599            }
600           
601            // Build a linear path to root...no wormholes.
602            if ($enough_already && $go_linear) {
603                continue;
604            }
605            $enough_already = true;
606           
607            // To prevent duplicates, only add the new found node
608            // if not already in the array of ancestors.
609            $curr_items = sizeof($output) > 0 ? $this->toStringID($output, -1) : array();
610            if (!in_array($this->toStringID($row['child_type'], $row['child_id']), $curr_items)) {
611                $output[] = $row;
612            }
613           
614            $this->getAllAncestors($row['parent_type'], $row['parent_id'], $go_linear, false);
615        }
616        if ($_return_flag) {
617            // We must reset the static $output variable so that it does
618            // not fill up during subsequent function calls.
619            $ret = $output;
620            $output = array();
621            return $ret;
622        }
623    }
624
625    /**
626     * Tests if the specified node exists. If only the child type and id is
627     * specified, check if any node exists like that. If the parent type and id
628     * is also provided, test for an exact node match.
629     * @param  string    $child_type
630     * @param  string    $child_id
631     * @param  string    $parent_type
632     * @param  string    $parent_id
633     * @param  string    $relationship_type
634     * @return bool      true if a leaf, or false if not or an error
635     */
636    function nodeExists($child_type=null, $child_id=null, $parent_type=null, $parent_id=null, $relationship_type=null)
637    {
638        if (!isset($child_type) || !isset($child_id)) {
639            if ($this->node_init) {
640                $child_type =& $this->child_type;
641                $child_id =& $this->child_id;
642            } else {
643                logMsg(_("nodeExists failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
644                return false;
645            }
646        }
647       
648        if (isset($parent_type) && isset($parent_id)) {
649            $qid = dbQuery("
650                SELECT 1 FROM node_tbl
651                WHERE parent_type = '" . mysql_real_escape_string($parent_type) . "'
652                AND parent_id = '" . mysql_real_escape_string($parent_id) . "'
653                AND child_type = '" . mysql_real_escape_string($child_type) . "'
654                AND child_id = '" . mysql_real_escape_string($child_id) . "'
655                AND relationship_type " . (is_null($relationship_type) ? "IS NULL" : "= '" . mysql_real_escape_string($relationship_type) . "'") . "
656            ");
657        } else {
658            $qid = dbQuery("
659                SELECT 1 FROM node_tbl
660                WHERE child_type = '" . mysql_real_escape_string($child_type) . "'
661                AND child_id = '" . mysql_real_escape_string($child_id) . "'
662            ");
663        }
664        if (mysql_num_rows($qid) > 0) {
665            return true;
666        } else {
667            return false;
668        }
669    }
670
671    /**
672     * Recursively go through the node tree, starting at a specified node, and
673     * drill down, filling an array with info about the nodes:
674     *
675     * @param  array   $preselected
676     * @param  string  $child_type
677     * @param  string  $child_id
678     * @param  string  $type_constraint  An array of node types to restrict the search to.
679     * @param  bool    $include_curr     Do we include the specified node in the list?
680     * @param  string  $order            SQL to append to the query of the getChildren
681     *                                   call. Ex: 'ORDER BY child_id DESC'
682     * @return array  Details of from the node table of all nodes below the
683     *                specified node: (type, id, title, indent level, selected status)
684     */
685    function &getNodeList($preselected=null, $child_type=null, $child_id=null, $type_constraint=null, $include_curr=false, $order='', $_indent=0, $_return_flag=true)
686    {
687        static $output = array();
688        static $is_a_leaf = array();
689       
690        if (!isset($child_type) || !isset($child_id)) {
691            if ($this->node_init) {
692                $child_type =& $this->child_type;
693                $child_id =& $this->child_id;
694            } else {
695                logMsg(_("getNodeList failed. Arguments not specified properly."), LOG_ERR, __FILE__, __LINE__);
696                return false;
697            }
698        }
699       
700        if (!is_array($preselected)) {
701            $preselected = array($preselected);
702        }
703       
704        if ($_return_flag && $include_curr) {
705            $my_children = $this->getNode($child_type, $child_id);
706        } else {
707            $my_children = $this->getChildren($child_type, $child_id, $type_constraint, $order);
708        }
709
710        if ($my_children) {
711            $num_children = sizeof($my_children);
712            for ($i=0; $i<$num_children; $i++) {
713
714                $row = $my_children[$i];
715                // Preventing circular references.
716                if ($row['child_type'] == $child_type && $row['child_id'] == $child_id) {
717                    logMsg(sprintf(_("Circular reference detected: %s has itself as a parent."), $this->toStringID($row['child_type'], $row['child_id'])), LOG_ERR, __FILE__, __LINE__);
718                    continue;
719                }
720                $row['indent'] = $_indent;
721                if (in_array($this->toStringID($row['child_type'], $row['child_id']), $preselected)) {
722                    $row['selected'] = true;
723                }
724                $output[] = $row;
725                unset($row);
726               
727                // This is so we test if each node is a string only once. We store the result in the is_a_leaf array statically.
728                if (!isset($is_a_leaf[$this->toStringID($my_children[$i]['child_type'], $my_children[$i]['child_id'])])) {
729                    $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']);
730                }
731                if (!$is_a_leaf[$this->toStringID($my_children[$i]['child_type'], $my_children[$i]['child_id'])]) {
732                    // If this node is not a leaf, we dive into it recursivly.
733                    $this->getNodeList($preselected, $my_children[$i]['child_type'], $my_children[$i]['child_id'], $type_constraint, $include_curr, $order, $_indent+1, false);
734                }
735            }
736        }
737
738        if ($_return_flag) {
739            // We must reset the static variables so that they do
740            // not fill up during subsequent function calls.
741            $ret = $output;
742            $output = array();
743            $is_a_leaf = array();
744            return $ret;
745        }
746
747    }
748   
749   
750    /**
751     * Counts the number of items linked to each parent node
752     * putting the result in the relevant category counters. This function is
753     * called each time a product, article, etc. is inserted, updated, or deleted.
754     *
755     * @param  string  $type_constraint  An array of node types to restrict the search to.
756     */
757    function rebuildSubnodeQty($type_constraint=null)
758    {
759        // Reset all the category counters to zero.
760        dbQuery("UPDATE node_tbl SET subnode_quantity = 0");
761       
762        // Get all the nodes.
763        $qid = dbQuery("SELECT DISTINCT child_type, child_id FROM node_tbl");
764       
765        // For each node count the number of children...
766        while (list($child_type, $child_id) = mysql_fetch_row($qid)) {
767            $num_children = $this->getNumberChildren($child_type, $child_id, $type_constraint);
768            // ...and add that number to the node counter for that object and all parents.
769            if ($num_children > 0) {
770                $this->setSubnodeQtyToParents($child_type, $child_id, $num_children);
771            }
772        }
773    }
774   
775    /**
776     * Used internally by setSubnodeQty to add the quantity of subnodes to
777     * all parents recursivly.
778     */
779    function setSubnodeQtyToParents($child_type, $child_id, $num_children)
780    {
781        dbQuery("
782            UPDATE node_tbl
783            SET subnode_quantity = subnode_quantity + '" . mysql_real_escape_string($num_children) . "'
784            WHERE child_type = '" . mysql_real_escape_string($child_type) . "'
785            AND child_id = '" . mysql_real_escape_string($child_id) . "'
786        ",false);
787        $qid = dbQuery("
788            SELECT parent_type, parent_id
789            FROM node_tbl
790            WHERE child_type = '" . mysql_real_escape_string($child_type) . "'
791            AND child_id = '" . mysql_real_escape_string($child_id) . "'
792        ",false);
793        while ((list($parent_type, $parent_id) = mysql_fetch_row($qid)) && $parent_id > 0) {
794            $this->setSubnodeQtyToParents($parent_type, $parent_id, $num_children);
795        }
796    }
797
798// THE END
799}
800
801
802
803
804?>
Note: See TracBrowser for help on using the repository browser.