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

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

Releasing trunk as stable version 2.1.5

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