source: tags/2.1.5/lib/Hierarchy.inc.php

Last change on this file was 377, checked in by quinn, 14 years ago

Releasing trunk as stable version 2.1.5

File size: 34.1 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-2010 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    var $params = array();
55
56
57    /**
58     * The active node type.
59     * @var string $child_type
60     */
61    var $child_type;
62
63    /**
64     * The active node id.
65     * @var string $child_id
66     */
67    var $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    var $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 Hierarchy($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    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    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    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    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    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    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    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    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    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    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    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    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    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    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    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    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    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}
850
851
852
853
854?>
Note: See TracBrowser for help on using the repository browser.