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

Last change on this file was 708, checked in by anonymous, 4 years ago

Update class constructor method names to construct

File size: 37.1 KB
Line 
1<?php
2/**
3 * Hierarchy.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 * hierarchy of nodes. Any form of data (article, product, image) can be
10 * represented as a node in this hierarchy. 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 hierarchy. The node hierarchy 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 hierarchy
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.1
26 */
27 
28require_once dirname(__FILE__) . '/App.inc.php';
29require_once dirname(__FILE__) . '/Utilities.inc.php';
30
31class Hierarchy {
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 __construct($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 singular 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."), resource_tbl, __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."), resource_tbl, __FILE__, __LINE__);
405                return false;
406            }
407        }
408       
409        $qid = dbQuery("
410            SELECT *
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."), resource_tbl, __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."), resource_tbl, __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."), resource_tbl, __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 hierarchy.
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 hierarchy.
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."), resource_tbl, __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                // Preventing circular references (Except when including current item in list).
715                if ($my_children[$i]['child_type'] == $child_type && $my_children[$i]['child_id'] == $child_id && !($_return_flag && $include_curr)) {
716                    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__);
717                    continue;
718                }
719               
720                $my_children[$i]['indent'] = $_indent;
721
722                if (in_array($this->toStringID($my_children[$i]['child_type'], $my_children[$i]['child_id']), $preselected)) {
723                    $my_children[$i]['selected'] = true;
724                }
725
726                $output[] = $my_children[$i];
727               
728                // Test if each node is a string only once. Store the result in the is_a_leaf array statically.
729                if (!isset($is_a_leaf[$this->toStringID($my_children[$i]['child_type'], $my_children[$i]['child_id'])])) {
730                    $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']);
731                }
732                if (!$is_a_leaf[$this->toStringID($my_children[$i]['child_type'], $my_children[$i]['child_id'])]) {
733                    // If this node is not a leaf, we dive into it recursively.
734                    $this->getNodeList($preselected, $my_children[$i]['child_type'], $my_children[$i]['child_id'], $type_constraint, $include_curr, $order, $_indent+1, false);
735                }
736            }
737        }
738
739        if ($_return_flag) {
740            // We must reset the static variables so that they do
741            // not fill up during subsequent function calls.
742            $ret = $output;
743            $output = array();
744            $is_a_leaf = array();
745            return $ret;
746        }
747
748    }
749   
750    /*
751    * Returns an array converted from a stack to a multidimentional hierarchy based on the parent types and ids.
752    *
753    * @access   public
754    * @param    array   $curr           The starting array to convert from.
755    * @param    string  $child_type     Optional root node type to start from.
756    * @param    int     $child_id       Optional root node id to start from.
757    * @return   array                   Multidimentional array.
758    * @author   Quinn Comendant <quinn@strangecode.com>
759    * @version  1.0
760    * @since    26 Apr 2006 15:40:30
761    */
762    function convertListToTree($curr, $child_type=null, $child_id=null, $_indent=0)
763    {
764        if (!is_array($curr) || empty($curr)) {
765            return array();
766        }
767
768        static $orig;
769        static $children_map;
770        static $node_map;
771
772        // The original $curr contains the full list. Save a copy of this.
773        if (!isset($orig)) {
774            $orig = $curr;
775
776            // Create children map, a dictionary of Parent IDs -> Children IDs.
777            $children_map = array();
778            foreach ($orig as $i => $n) {
779                $n_parent = ('' != $n['parent_type'] && '' != $n['parent_id']) ? $this->toStringID($n['parent_type'], $n['parent_id']) : 'null__00';
780                $n_child = $this->toStringID($n['child_type'], $n['child_id']);
781                $children_map[$n_parent][] = $n_child;
782            }
783
784            // Create node array map, a dictionary of $orig keys -> node IDs.
785            $node_map = array();
786            foreach ($orig as $i => $n) {
787                $n_child = $this->toStringID($n['child_type'], $n['child_id']);
788                $node_map[$n_child] = $i;
789            }
790           
791            // Set initial (root) node.
792            if (isset($child_type) && isset($child_id)) {
793                // Use provided node as starting point.
794                $curr = $orig[$node_map[$this->toStringID($child_type, $child_id)]];
795            } else {
796                // Otherwise assume first element of orig is starting point.
797                $curr = $orig[0];
798            }
799        }
800
801        if (!isset($curr['indent'])) {
802            $curr['indent'] = $_indent;
803        }
804
805        // Get children of current node.
806        $curr_str_id = $this->toStringID($curr['child_type'], $curr['child_id']);
807        $curr_children = $children_map[$curr_str_id];
808       
809        // If any children, recurse in appending a multidimensional array to $curr.
810        if (!empty($curr_children)) {
811            foreach ($curr_children as $child) {
812                $curr['children'][] = $this->convertListToTree($orig[$node_map[$child]], null, null, $_indent + 1);
813            }
814        }
815       
816        if ($_indent === 0) {
817            // We must reset the static variables so that they do
818            // not fill up during subsequent function calls.
819            $orig = null;
820            $children_map = null;
821            $node_map = null;
822            return array($curr);
823        }
824
825        return $curr;
826    }
827
828    /*
829    * Prints a nested HTML list from input array generated by convertListToTree().
830    *
831    * @access   public
832    * @param    array   $in     Input array.
833    * @author   Quinn Comendant <quinn@strangecode.com>
834    * @version  1.0
835    * @since    26 Apr 2006 15:50:00
836    */
837    function printTree($in)
838    {
839        if (!is_array($in) || empty($in)) {
840            return false;
841        }
842
843        ?><ul><?php
844        foreach ($in as $n) {
845            $class = $n['selected'] ? ' class="current"' : '';
846            ?><li<?php echo $class; ?>><?php
847            echo oTxt($n['title']);
848            if (isset($n['children']) && !empty($n['children'])) {
849                $this->printTree($n['children']);
850            }
851            ?></li><?php
852        }
853        ?></ul><?php
854    }
855   
856    /**
857     * Counts the number of items linked to each parent node
858     * putting the result in the relevant category counters. This function is
859     * called each time a product, article, etc. is inserted, updated, or deleted.
860     *
861     * @param  string  $type_constraint  An array of node types to restrict the search to.
862     */
863    function rebuildSubnodeQty($type_constraint=null)
864    {
865        // Reset all the category counters to zero.
866        dbQuery("UPDATE node_tbl SET subnode_quantity = 0");
867       
868        // Get all the nodes.
869        $qid = dbQuery("SELECT DISTINCT child_type, child_id FROM node_tbl");
870       
871        // For each node count the number of children...
872        while (list($child_type, $child_id) = mysql_fetch_row($qid)) {
873            $num_children = $this->getNumberChildren($child_type, $child_id, $type_constraint);
874            // ...and add that number to the node counter for that object and all parents.
875            if ($num_children > 0) {
876                $this->setSubnodeQtyToParents($child_type, $child_id, $num_children);
877            }
878        }
879    }
880   
881    /**
882     * Used internally by setSubnodeQty to add the quantity of subnodes to
883     * all parents recursivly.
884     */
885    function setSubnodeQtyToParents($child_type, $child_id, $num_children)
886    {
887        dbQuery("
888            UPDATE node_tbl
889            SET subnode_quantity = subnode_quantity + '" . mysql_real_escape_string($num_children) . "'
890            WHERE child_type = '" . mysql_real_escape_string($child_type) . "'
891            AND child_id = '" . mysql_real_escape_string($child_id) . "'
892        ",false);
893        $qid = dbQuery("
894            SELECT parent_type, parent_id
895            FROM node_tbl
896            WHERE child_type = '" . mysql_real_escape_string($child_type) . "'
897            AND child_id = '" . mysql_real_escape_string($child_id) . "'
898        ",false);
899        while ((list($parent_type, $parent_id) = mysql_fetch_row($qid)) && $parent_id > 0) {
900            $this->setSubnodeQtyToParents($parent_type, $parent_id, $num_children);
901        }
902    }
903
904// THE END
905}
906
907
908
909
910?>
Note: See TracBrowser for help on using the repository browser.