source: trunk/lib/Hierarchy.inc.php @ 99

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

Q - Complete rebuild of PEdit

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