source: branches/2.0singleton/lib/Hierarchy.inc.php @ 127

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

Updated App.inc.php thru Hierarchy.inc.php

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