source: trunk/lib/Hierarchy.inc.php

Last change on this file was 502, checked in by anonymous, 9 years ago

Many minor fixes during pulso development

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