source: trunk/lib/Hierarchy.inc.php

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

Many minor fixes during pulso development

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