source: branches/eli_branch/lib/Hierarchy.inc.php @ 442

Last change on this file since 442 was 439, checked in by anonymous, 11 years ago

added public and private keywords to all properties and methods, changed old classname constructor function to construct, removed more ?> closing tags

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