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

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

finished updating DB:: to $db->

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