PageRenderTime 68ms CodeModel.GetById 16ms app.highlight 40ms RepoModel.GetById 1ms app.codeStats 1ms

/pear/php/Tree/Memory.php

https://github.com/wrobel/horde-glue
PHP | 1521 lines | 507 code | 125 blank | 889 comment | 90 complexity | ab63c01ec7434cf4cca4c174401ad4a8 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/* vim: set expandtab tabstop=4 shiftwidth=4: */
   3// +----------------------------------------------------------------------+
   4// | PHP Version 4                                                        |
   5// +----------------------------------------------------------------------+
   6// | Copyright (c) 1997-2003 The PHP Group                                |
   7// +----------------------------------------------------------------------+
   8// | This source file is subject to version 2.02 of the PHP license,      |
   9// | that is bundled with this package in the file LICENSE, and is        |
  10// | available at through the world-wide-web at                           |
  11// | http://www.php.net/license/2_02.txt.                                 |
  12// | If you did not receive a copy of the PHP license and are unable to   |
  13// | obtain it through the world-wide-web, please send a note to          |
  14// | license@php.net so we can mail you a copy immediately.               |
  15// +----------------------------------------------------------------------+
  16// | Authors: Wolfram Kriesing <wolfram@kriesing.de>                      |
  17// +----------------------------------------------------------------------+
  18//
  19//  $Id: Memory.php,v 1.34.2.3 2009/03/12 17:19:52 dufuz Exp $
  20
  21require_once 'Tree/Common.php';
  22
  23/**
  24 * this class can be used to step through a tree using ['parent'],['child']
  25 * the tree is saved as flat data in a db, where at least the parent
  26 * needs to be given if a previous member is given too then the order
  27 * on a level can be determined too
  28 * actually this class was used for a navigation tree
  29 * now it is extended to serve any kind of tree
  30 * you can unambigiously refer to any element by using the following
  31 * syntax
  32 * tree->data[currentId][<where>]...[<where>]
  33 * <where> can be either "parent", "child", "next" or "previous", this way
  34 * you can "walk" from any point to any other point in the tree
  35 * by using <where> in any order you want
  36 * example (in parentheses the id):
  37 * root
  38 *  +---level 1_1 (1)
  39 *  |      +----level 2_1 (2)
  40 *  |      +----level 2_2 (3)
  41 *  |              +-----level 3_1 (4)
  42 *  +---level 1_2 (5)
  43 *
  44 *  the database table to this structure (without defined order)
  45 *  id     parentId        name
  46 *  1         0         level 1_1
  47 *  2         1         level 2_1
  48 *  3         1         level 2_1
  49 *  4         3         level 3_1
  50 *  5         0         level 1_2
  51 *
  52 * now you can refer to elements for example like this (all examples assume you
  53 * know the structure):
  54 * go from "level 3_1" to "level 1_1": $tree->data[4]['parent']['parent']
  55 * go from "level 3_1" to "level 1_2":
  56 *          $tree->data[4]['parent']['parent']['next']
  57 * go from "level 2_1" to "level 3_1": $tree->data[2]['next']['child']
  58 * go from "level 2_2" to "level 2_1": $tree->data[3]['previous']
  59 * go from "level 1_2" to "level 3_1":
  60 *          $tree->data[5]['previous']['child']['next']['child']
  61 *
  62 * on a pentium 1.9 GHz 512 MB RAM, Linux 2.4, Apache 1.3.19, PHP 4.0.6
  63 * performance statistics for version 1.26, using examples/Tree/Tree.php
  64 *  -   reading from DB and preparing took: 0.14958894252777
  65 *  -   building took: 0.074488043785095
  66 *  -  buildStructure took: 0.05151903629303
  67 *  -  setting up the tree time: 0.29579293727875
  68 *  -  number of elements: 1564
  69 *  -  deepest level: 17
  70 * so you can use it for tiny-big trees too :-)
  71 * but watch the db traffic, which might be considerable, depending
  72 * on your setup.
  73 *
  74 * FIXXXME there is one really bad thing about the entire class, at some points
  75 * there are references to $this->data returned, or the programmer can even
  76 * access this->data, which means he can change the structure, since this->data
  77 * can not be set to read-only, therefore this->data has to be handled
  78 * with great care!!! never do something like this:
  79 * <code>
  80 * $x = &$tree->data[<some-id>]; $x = $y;
  81 * </code>
  82 * this overwrites the element in the structure !!!
  83 *
  84 *
  85 * @access   public
  86 * @author   Wolfram Kriesing <wolfram@kriesing.de>
  87 * @version  2001/06/27
  88 * @package  Tree
  89 */
  90class Tree_Memory extends Tree_Common
  91{
  92    /**
  93     * this array contains the pure data from the DB
  94     * which are always kept, since all other structures will
  95     * only make references on any element
  96     * and those data are extended by the elements 'parent' 'children' etc...
  97     * @var    array $data
  98     */
  99    var $data = array();
 100
 101    /**
 102     * this array contains references to this->data but it
 103     * additionally represents the directory structure
 104     * that means the array has as many dimensions as the
 105     * tree structure has levels
 106     * but this array is only used internally from outside you can do
 107     * everything using the node-id's
 108     *
 109     * @var    array $structure
 110     * @access private
 111     */
 112    var $structure = array();
 113
 114    /**
 115     * it contains all the parents and their children, where the parentId is the
 116     * key and all the children are the values, this is for speeding up
 117     * the tree-building process
 118     *
 119     * @var    array   $children
 120     */
 121    var $children = array();
 122
 123    /**
 124     * @access private
 125     * @var    boolean saves if tree nodes shall be removed recursively
 126     * @see    setRemoveRecursively()
 127     */
 128    var $removeRecursively = false;
 129
 130
 131    /**
 132     * @access public
 133     * @var integer  the debug mode, if > 0 then debug info are shown,
 134     *              actually those messages only show performance
 135     *              times
 136     */
 137    var $debug = 0;
 138
 139    /**
 140     * @see &getNode()
 141     * @see &_getNode()
 142     * @access private
 143     * @var integer variable only used in the method getNode and _getNode
 144     */
 145    var $_getNodeMaxLevel;
 146
 147    /**
 148     * @see    &getNode()
 149     * @see    &_getNode()
 150     * @access private
 151     * @var    integer  variable only used in the method getNode and
 152     *                  _getNode
 153     */
 154    var $_getNodeCurParent;
 155
 156    /**
 157     * the maximum depth of the tree
 158     * @access private
 159     * @var    int     the maximum depth of the tree
 160     */
 161    var $_treeDepth = 0;
 162
 163    // {{{ Tree_Memory()
 164
 165    /**
 166     * set up this object
 167     *
 168     * @version   2001/06/27
 169     * @access    public
 170     * @author    Wolfram Kriesing <wolfram@kriesing.de>
 171     * @param mixed   this is a DSN for the PEAR::DB, can be
 172     *                            either an object/string
 173     * @param array   additional options you can set
 174     */
 175    function Tree_Memory($type, $dsn = '', $options = array())
 176    {
 177        // set the options for $this
 178        $this->Tree_Options($options);
 179        include_once "Tree/Memory/$type.php";
 180        $className = 'Tree_Memory_'.$type;
 181        $this->dataSourceClass =& new $className($dsn, $options);
 182
 183        // copy the options to be able to get them via getOption(s)
 184        // FIXXME this is not really cool, maybe overwrite
 185        // the *Option* methods!!!
 186        if (isset($this->dataSourceClass->options)) {
 187            $this->options = $this->dataSourceClass->options;
 188        }
 189
 190    }
 191
 192    // }}}
 193    // {{{ switchDataSource()
 194
 195    /**
 196     * use this to switch data sources on the run
 197     * i.e. if you are reading the data from a db-tree and want to save it
 198     * as xml data (which will work one day too)
 199     * or reading the data from an xml file and writing it in the db
 200     * which should already work
 201     *
 202     * @version 2002/01/17
 203     * @access  public
 204     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 205     * @param   string  this is a DSN of the for that PEAR::DB uses it
 206     *                  only that additionally you can add parameters
 207     *                  like ...?table=test_table to define the table.
 208     * @param   array   additional options you can set
 209     * @return  boolean true on success
 210     */
 211    function switchDataSource($type, $dsn = '', $options = array())
 212    {
 213        $data = $this->getNode();
 214        //$this->Tree($dsn, $options);
 215        $this->Tree_Memory($type, $GLOBALS['dummy'], $options);
 216
 217        // this method prepares data retreived using getNode to be used
 218        // in this type of tree
 219        $this->dataSourceClass->setData($data);
 220        $this->setup();
 221    }
 222
 223    // }}}
 224    // {{{ setupByRawData()
 225
 226    /**
 227     *
 228     *
 229     * @version 2002/01/19
 230     * @access  public
 231     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 232     * @return  boolean true if the setup succeeded
 233     * @
 234     */
 235    function setupByRawData($string)
 236    {
 237        // expects
 238        // for XML an XML-String,
 239        // for DB-a result set, may be or an array, dont know here
 240        // not implemented yet
 241        $res = $this->dataSourceClass->setupByRawData($string);
 242        return $this->_setup($res);
 243    }
 244
 245    // }}}
 246    // {{{ setup()
 247
 248    /**
 249     * @version 2002/01/19
 250     * @access  public
 251     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 252     * @param   array   the result of a query which retreives (all)
 253     *                  the tree data from a source
 254     * @return  true or Tree_Error
 255     */
 256    function setup($data = null)
 257    {
 258        if ($this->debug) {
 259            $startTime = split(' ',microtime());
 260            $startTime = $startTime[1]+$startTime[0];
 261        }
 262
 263        if (PEAR::isError($res = $this->dataSourceClass->setup($data))) {
 264            return $res;
 265        }
 266
 267        if ($this->debug) {
 268            $endTime = split(' ',microtime());
 269            $endTime = $endTime[1]+$endTime[0];
 270            echo ' reading and preparing tree data took: '.
 271                    ($endTime - $startTime) . '<br>';
 272        }
 273
 274        return $this->_setup($res);
 275    }
 276
 277    // }}}
 278    // {{{ _setup()
 279
 280    /**
 281     * retreive all the navigation data from the db and build the
 282     * tree in the array data and structure
 283     *
 284     * @version 2001/11/20
 285     * @access  private
 286     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 287     * @return  boolean     true on success
 288     */
 289    function _setup($setupData)
 290    {
 291        // TODO sort by prevId (parentId,prevId $addQuery) too if it exists
 292        // in the table, or the root might be wrong TODO since the prevId
 293        // of the root should be 0
 294        if (!$setupData) {
 295            return false;
 296        }
 297
 298        //FIXXXXXME validate the structure.
 299        // i.e. a problem occurs, if you give one node, which has a parentId=1,
 300        // it screws up everything!!!
 301        //empty the data structures, since we are reading the data
 302        // from the db (again)
 303        $this->structure = array();
 304        $this->data = array();
 305        $this->children = array();
 306        // build an array where all the parents have their children as a member
 307        // this i do to speed up the buildStructure
 308        $columnNameMappings = $this->getOption('columnNameMaps');
 309        foreach ($setupData as $values) {
 310            if (is_array($values)) {
 311                $this->data[$values['id']] = $values;
 312                $this->children[$values['parentId']][] = $values['id'];
 313            }
 314        }
 315
 316        // walk through all the children on each level and set the
 317        // next/previous relations of those children, since all children
 318        // for "children[$id]" are on the same level we can do
 319        // this here :-)
 320        foreach ($this->children as $children) {
 321            $lastPrevId = 0;
 322            if (count($children)) {
 323                foreach ($children as $key) {
 324                    if ($lastPrevId) {
 325                        // remember the nextId too, so the build process can
 326                        // be speed up
 327                        $this->data[$lastPrevId]['nextId'] = $key;
 328                        $this->data[$lastPrevId]['next'] =   &$this->data[$key];
 329
 330                        $this->data[$key]['prevId'] = $lastPrevId;
 331                        $this->data[$key]['previous'] = &$this->data[ $lastPrevId ];
 332                    }
 333                    $lastPrevId = $key;
 334                }
 335            }
 336        }
 337
 338        if ($this->debug) {
 339            $startTime = split(' ',microtime());
 340            $startTime = $startTime[1] + $startTime[0];
 341        }
 342
 343        // when NO prevId is given, sort the entries in each level by the given
 344        // sort order (to be defined) and set the prevId so the build can work
 345        // properly does a prevId exist?
 346        if (!isset($setupData[0]['prevId'])) {
 347            $lastPrevId = 0;
 348            $lastParentId = 0;
 349            $level = 0;
 350            // build the entire recursive relations, so you have 'parentId',
 351            // 'childId', 'nextId', 'prevId' and the references 'child',
 352            // 'parent', 'next', 'previous' set in the property 'data'
 353            foreach($this->data as $key => $value) {
 354                // most if checks in this foreach are for the following reason,
 355                // if not stated otherwise:
 356                // dont make an data[''] or data[0] since this was not read
 357                // from the DB, because id is autoincrement and starts at 1
 358                // and also in an xml tree there can not be an element </>
 359                // i hope :-)
 360                if ($value['parentId']) {
 361                    $this->data[$key]['parent'] = &$this->data[$value['parentId']];
 362                    // the parent has an extra array which contains a reference
 363                    // to all it's children, set it here
 364                    $this->data[ $value['parentId']]['children'][] =
 365                                                        &$this->data[$key];
 366                }
 367
 368                // was a child saved (in the above 'if')
 369                // see comment above
 370                if (isset($this->children[$key]) &&
 371                    count($this->children[$key])
 372                ) {
 373                    // refer to the first child in the [child]
 374                    // and [childId] keys
 375                    $this->data[$key]['childId'] = $this->children[$key][0];
 376                    $this->data[$key]['child'] =
 377                                &$this->data[$this->children[$key][0]];
 378                }
 379
 380                $lastParentId = $value['parentId'];
 381            }
 382        }
 383
 384        if ($this->debug) {
 385            $endTime = split(' ',microtime());
 386            $endTime = $endTime[1]+$endTime[0];
 387            echo ' building took: ' . ($endTime - $startTime) . ' <br>';
 388        }
 389
 390        // build the property 'structure'
 391        // empty it, just to be sure everything
 392        //will be set properly
 393        $this->structure = array();
 394
 395        if ($this->debug) {
 396            $startTime = split(' ',microtime());
 397            $startTime = $startTime[1] + $startTime[0];
 398        }
 399
 400        // build all the children that are on the root level, if we wouldnt
 401        // do that. We would have to create a root element with an id 0,
 402        // but since this is not read from the db we dont add another element.
 403        // The user wants to get what he had saved
 404        if (isset($this->children[0])) {
 405            foreach ($this->children[0] as $rootElement) {
 406                $this->buildStructure($rootElement, $this->structure);
 407            }
 408        }
 409
 410        if ($this->debug) {
 411            $endTime = split(' ',microtime());
 412            $endTime = $endTime[1] + $endTime[0];
 413            echo ' buildStructure took: ' . ($endTime - $startTime).' <br>';
 414        }
 415        return true;
 416    }
 417
 418    // }}}
 419    // {{{ add()
 420
 421    /**
 422     * adds _one_ new element in the tree under the given parent
 423     * the values' keys given have to match the db-columns, because the
 424     * value gets inserted in the db directly
 425     * to add an entire node containing children and so on see 'addNode()'
 426     * @see addNode()
 427     * @version 2001/10/09
 428     * @access  public
 429     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 430     * @param   array   this array contains the values that shall be inserted
 431     *                  in the db-table
 432     * @param   int the parent id
 433     * @param   int the prevId
 434     * @return  mixed   either boolean false on failure or the id
 435     *                  of the inserted row
 436     */
 437    function add($newValues, $parentId = 0, $prevId = 0)
 438    {
 439        // see comments in 'move' and 'remove'
 440
 441        if (method_exists($this->dataSourceClass, 'add')) {
 442            return $this->dataSourceClass->add($newValues, $parentId, $prevId);
 443        } else {
 444            return $this->_throwError('method not implemented yet.' ,
 445                                __LINE__);
 446        }
 447    }
 448
 449    // }}}
 450    // {{{ remove()
 451
 452    /**
 453     * removes the given node and all children if removeRecursively is on
 454     *
 455     * @version    2002/01/24
 456     * @access     public
 457     * @author     Wolfram Kriesing <wolfram@kriesing.de>
 458     * @param      mixed   $id     the id of the node to be removed
 459     * @return     boolean true on success
 460     */
 461    function remove($id)
 462    {
 463        // if removing recursively is not allowed, which means every child
 464        // should be removed
 465        // then check if this element has a child and return
 466        // "sorry baby cant remove :-) "
 467        if ($this->removeRecursively != true) {
 468            if (isset($this->data[$id]['child'])) {
 469                // TODO raise PEAR warning
 470                return $this->_throwError("Element with id=$id has children ".
 471                                          "that cant be removed. Set ".
 472                                          "'setRemoveRecursively' to true to ".
 473                                          "allow this.",
 474                                          __LINE__
 475                                        );
 476            }
 477        }
 478
 479        // see comment in 'move'
 480        // if the prevId is in use we need to update the prevId of the element
 481        // after the one that is removed too, to have the prevId of the one
 482        // that is removed!!!
 483        if (method_exists($this->dataSourceClass, 'remove')) {
 484            return $this->dataSourceClass->remove($id);
 485        } else {
 486            return $this->_throwError('method not implemented yet.', __LINE__);
 487        }
 488    }
 489
 490    // }}}
 491    // {{{ _remove()
 492
 493    /**
 494     * collects the ID's of the elements to be removed
 495     *
 496     * @version    2001/10/09
 497     * @access     public
 498     * @author     Wolfram Kriesing <wolfram@kriesing.de>
 499     * @param      mixed   $id   the id of the node to be removed
 500     * @return     boolean true on success
 501     */
 502    function _remove($element)
 503    {
 504        return $element['id'];
 505    }
 506
 507    // }}}
 508    // {{{ move()
 509
 510    /**
 511     * move an entry under a given parent or behind a given entry.
 512     * !!! the 'move behind another element' is only implemented for nested
 513     * trees now!!!.
 514     * If a newPrevId is given the newParentId is dismissed!
 515     * call it either like this:
 516     *      $tree->move(x, y)
 517     *      to move the element (or entire tree) with the id x
 518     *      under the element with the id y
 519     * or
 520     * <code>
 521     *      // ommit the second parameter by setting it to 0
 522     *      $tree->move(x, 0, y);
 523     * </code>
 524     *      to move the element (or entire tree) with the id x
 525     *      behind the element with the id y
 526     * or
 527     * <code>
 528     *      $tree->move(array(x1,x2,x3), ...
 529     * </code>
 530     *      the first parameter can also be an array of elements that shall
 531     *      be moved
 532     *      the second and third para can be as described above
 533     *
 534     * @version 2002/06/08
 535     * @access  public
 536     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 537     * @param   integer the id(s) of the element(s) that shall be moved
 538     * @param   integer the id of the element which will be the new parent
 539     * @param   integer if prevId is given the element with the id idToMove
 540     *                  shall be moved _behind_ the element
 541     *                  with id=prevId if it is 0 it will be put at
 542     *                  the beginning
 543     * @return     boolean     true for success
 544     */
 545    function move($idsToMove, $newParentId, $newPrevId = 0)
 546    {
 547        settype($idsToMove,'array');
 548        $errors = array();
 549        foreach ($idsToMove as $idToMove) {
 550            $ret = $this->_move($idToMove, $newParentId, $newPrevId);
 551            if (PEAR::isError($ret)) {
 552                $errors[] = $ret;
 553            }
 554        }
 555// FIXXME return a Tree_Error, not an array !!!!!
 556        if (count($errors)) {
 557            return $errors;
 558        }
 559        return true;
 560    }
 561
 562    // }}}
 563    // {{{ _move()
 564
 565    /**
 566     * this method moves one tree element
 567     *
 568     * @see move()
 569     * @version 2001/10/10
 570     * @access  public
 571     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 572     * @param   integer     the id of the element that shall be moved
 573     * @param   integer the id of the element which will be
 574     *                  the new parent
 575     * @param   integer if prevId is given the element with the id idToMove
 576     *                  shall be moved _behind_ the element with id=prevId
 577     *                  if it is 0 it will be put at the beginning
 578     * @return  mixed   true for success, Tree_Error on failure
 579     */
 580    function _move($idToMove, $newParentId, $prevId = 0)
 581    {
 582        // itself can not be a parent of itself
 583        if ($idToMove == $newParentId) {
 584            // TODO PEAR-ize error
 585            return TREE_ERROR_INVALID_PARENT;
 586        }
 587
 588        // check if $newParentId is a child (or a child-child ...) of $idToMove
 589        // if so prevent moving, because that is not possible
 590        // does this element have children?
 591        if ($this->hasChildren($idToMove)) {
 592            $allChildren = $this->getChildren($idToMove);
 593            // FIXXME what happens here we are changing $allChildren,
 594            // doesnt this change the property data too??? since getChildren
 595            // (might, not yet) return a reference use while since foreach
 596            // only works on a copy of the data to loop through, but we are
 597            // changing $allChildren in the loop
 598            while (list(, $aChild) = each ($allChildren)) {
 599                // remove the first element because if array_merge is called
 600                // the array pointer seems to be
 601                array_shift($allChildren);
 602                // set to the beginning and this way the beginning is always
 603                // the current element, simply work off and truncate in front
 604                if (@$aChild['children']) {
 605                    $allChildren = array_merge($allChildren,
 606                                                $aChild['children']
 607                                            );
 608                }
 609                if ($newParentId == $aChild['id']) {
 610                    // TODO PEAR-ize error
 611                    return TREE_ERROR_INVALID_PARENT;
 612                }
 613            }
 614        }
 615
 616        // what happens if i am using the prevId too, then the db class also
 617        // needs to know where the element should be moved to
 618        // and it has to change the prevId of the element that will be after it
 619        // so we may be simply call some method like 'update' too?
 620        if (method_exists($this->dataSourceClass, 'move')) {
 621            return $this->dataSourceClass->move($idToMove,
 622                                                $newParentId,
 623                                                $prevId
 624                                            );
 625        } else {
 626            return $this->_throwError('method not implemented yet.', __LINE__);
 627        }
 628    }
 629
 630    // }}}
 631    // {{{ update()
 632
 633    /**
 634     * update data in a node
 635     *
 636     * @version    2002/01/29
 637     * @access     public
 638     * @author     Wolfram Kriesing <wolfram@kriesing.de>
 639     * @param      integer the ID of the element that shall be updated
 640     * @param      array   the data to update
 641     * @return     mixed   either boolean or
 642     *                     an error object if the method is not implemented
 643     */
 644    function update($id, $data)
 645    {
 646        if (method_exists($this->dataSourceClass, 'update')) {
 647            return $this->dataSourceClass->update($id,$data);
 648        } else {
 649            return $this->_throwError(
 650                        'method not implemented yet.', __LINE__
 651                    );
 652        }
 653    }
 654
 655    // }}}
 656
 657    //
 658    //
 659    //  from here all methods are not interacting on the  'dataSourceClass'
 660    //
 661    //
 662
 663    // {{{ buildStructure()
 664    /**
 665     * builds the structure in the parameter $insertIn
 666     * this function works recursively down into depth of the folder structure
 667     * it builds an array which goes as deep as the structure goes
 668     *
 669     * @access  public
 670     * @version 2001/05/02
 671     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 672     * @param   integer the parent for which it's structure shall
 673     *                  be built
 674     * @param   integer the array where to build the structure in
 675     *                  given as a reference to be sure the substructure is built
 676     *                  in the same array as passed to the function
 677     * @return  boolean returns always true
 678     *
 679     */
 680    function buildStructure($parentId, &$insertIn)
 681    {
 682        // create the element, so it exists in the property "structure"
 683        // also if there are no children below
 684        $insertIn[$parentId] = array();
 685
 686        // set the level, since we are walking through the structure here.
 687        // Anyway we can do this here, instead of up in the setup method :-)
 688        // always set the level to one higher than the parent's level, easy ha?
 689        // this applies only to the root element(s)
 690        if (isset($this->data[$parentId]['parent']['level'])) {
 691            $this->data[$parentId]['level'] =
 692                            $this->data[$parentId]['parent']['level']+1;
 693            if ($this->data[$parentId]['level']>$this->_treeDepth) {
 694                $this->_treeDepth = $this->data[$parentId]['level'];
 695            }
 696        } else {
 697            // set first level number to 0
 698            $this->data[$parentId]['level'] = 0;
 699        }
 700
 701        if (isset($this->children[$parentId])
 702            && count($this->children[$parentId])) {
 703            // go thru all the folders
 704            foreach ($this->children[$parentId] as $child) {
 705                // build the structure under this folder,
 706                // use the current folder as the new parent and call
 707                // build recursively to build all the children by calling build
 708                // with $insertIn[someindex] the array is filled
 709                // since the array was empty before
 710                $this->buildStructure($child, $insertIn[$parentId]);
 711            }
 712        }
 713        return true;
 714    }
 715
 716    // }}}
 717    // {{{ walk()
 718
 719    /**
 720     * this method only serves to call the _walk method and
 721     * reset $this->walkReturn that will be returned by all the walk-steps
 722     *
 723     * @version 2001/11/25
 724     * @access  public
 725     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 726     * @param   mixed   the name of the function to call for each walk step,
 727     *                  or an array for a method, where [0] is the method name
 728     *                  and [1] the object
 729     * @param   array   the id to start walking through the tree, everything
 730     *                  below is walked through
 731     * @param   string  the return of all the walk data will be of the given
 732     *                  type (values: string, array)
 733     * @return  mixed   this is all the return data collected from all
 734     *                  the walk-steps
 735     */
 736    function walk($walkFunction, $id = 0, $returnType = 'string')
 737    {
 738        // by default all of structure is used
 739        $useNode = $this->structure;
 740        if ($id == 0) {
 741            $keys = array_keys($this->structure);
 742            $id = $keys[0];
 743        } else {
 744            // get the path, to be able to go to the element in this->structure
 745            $path = $this->getPath($id);
 746            // pop off the last element, since it is the one requested
 747            array_pop($path);
 748            // start at the root of structure
 749            $curNode = $this->structure;
 750            foreach ($path as $node) {
 751                // go as deep into structure as path defines
 752                $curNode = $curNode[$node['id']];
 753            }
 754            // empty it first, so we dont have the other stuff in there
 755            // from before
 756            $useNode = array();
 757            // copy only the branch of the tree that the parameter
 758            // $id requested
 759            $useNode[$id] = $curNode[$id];
 760        }
 761
 762        // a new walk starts, unset the return value
 763        unset($this->walkReturn);
 764        return $this->_walk($walkFunction, $useNode, $returnType);
 765    }
 766
 767    // }}}
 768    // {{{ _walk()
 769
 770    /**
 771     * walks through the entire tree and returns the current element and the level
 772     * so a user can use this to build a treemap or whatever
 773     *
 774     * @version 2001/06/xx
 775     * @access  private
 776     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 777     * @param   mixed   the name of the function to call for each walk step,
 778     *                  or an array for a method, where [0] is the method name
 779     *                  and [1] the object
 780     *
 781     * @param   array   the reference in the this->structure, to walk
 782     *                  everything below
 783     * @param   string  the return of all the walk data will be
 784     *                  of the given type (values: string, array, ifArray)
 785     * @return  mixed   this is all the return data collected from all
 786     *                  the walk-steps
 787     */
 788    function _walk($walkFunction, &$curLevel, $returnType)
 789    {
 790        if (count($curLevel)) {
 791            foreach ($curLevel as $key => $value) {
 792                $ret = call_user_func($walkFunction, $this->data[$key]);
 793                switch ($returnType) {
 794                case 'array':
 795                    $this->walkReturn[] = $ret;
 796                    break;
 797                // this only adds the element if the $ret is an array
 798                // and contains data
 799                case 'ifArray':
 800                    if (is_array($ret)) {
 801                        $this->walkReturn[] = $ret;
 802                    }
 803                    break;
 804                default:
 805                    $this->walkReturn.= $ret;
 806                    break;
 807                }
 808                $this->_walk($walkFunction, $value, $returnType);
 809            }
 810        }
 811        return $this->walkReturn;
 812    }
 813
 814    // }}}
 815    // {{{ addNode()
 816
 817    /**
 818     * Adds multiple elements. You have to pass those elements
 819     * in a multidimensional array which represents the tree structure
 820     * as it shall be added (this array can of course also simply contain
 821     * one element).
 822     * The following array $x passed as the parameter
 823     *      $x[0] = array('name'     => 'bla',
 824     *                    'parentId' => '30',
 825     *                    array('name'    => 'bla1',
 826     *                          'comment' => 'foo',
 827     *                          array('name' => 'bla2'),
 828     *                          array('name' => 'bla2_1')
 829     *                          ),
 830     *                    array('name'=>'bla1_1'),
 831     *                    );
 832     *      $x[1] = array('name'     => 'fooBla',
 833     *                    'parentId' => '30');
 834     *
 835     * would add the following tree (or subtree/node) under the parent
 836     * with the id 30 (since 'parentId'=30 in $x[0] and in $x[1]):
 837     *  +--bla
 838     *  |   +--bla1
 839     *  |   |    +--bla2
 840     *  |   |    +--bla2_1
 841     *  |   +--bla1_1
 842     *  +--fooBla
 843     *
 844     * @see add()
 845     * @version 2001/12/19
 846     * @access  public
 847     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 848     * @param   array   the tree to be inserted, represents the tree structure,
 849     *                             see add() for the exact member of each node
 850     * @return  mixed   either boolean false on failure
 851     *                  or the id of the inserted row
 852     */
 853    function addNode($node)
 854    {
 855        if (count($node)) {
 856            foreach ($node as $aNode) {
 857                $newNode = array();
 858                // this should always have data, if not the passed
 859                // structure has an error
 860                foreach ($aNode as $name => $value) {
 861                    // collect the data that need to go in the DB
 862                    if (!is_array($value)) {
 863                        $newEntry[$name] = $value;
 864                    } else {
 865                        // collect the children
 866                        $newNode[] = $value;
 867                    }
 868                }
 869                // add the element and get the id, that it got, to have
 870                // the parentId for the children
 871                $insertedId = $this->add($newEntry);
 872                // if inserting suceeded, we have received the id
 873                // under which we can insert the children
 874                if ($insertedId!= false) {
 875                    // if there are children, set their parentId.
 876                    // So they kknow where they belong in the tree
 877                    if (count($newNode)) {
 878                        foreach($newNode as $key => $aNewNode) {
 879                            $newNode[$key]['parentId'] = $insertedId;
 880                        }
 881                    }
 882                    // call yourself recursively to insert the children
 883                    // and its children
 884                    $this->addNode($newNode);
 885                }
 886            }
 887        }
 888    }
 889
 890    // }}}
 891    // {{{ getPath()
 892
 893    /**
 894     * gets the path to the element given by its id
 895     * !!! ATTENTION watch out that you never change any of the data returned,
 896     * since they are references to the internal property $data
 897     *
 898     * @access  public
 899     * @version 2001/10/10
 900     * @access  public
 901     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 902     * @param   mixed   the id of the node to get the path for
 903     * @return  array   this array contains all elements from the root
 904     *                      to the element given by the id
 905     *
 906     */
 907    function getPath($id)
 908    {
 909        // empty the path, to be clean
 910        $path = array();
 911
 912        // FIXXME may its better to use a for(level) to count down,
 913        // since a while is always a little risky
 914        // until there are no more parents
 915        while (@$this->data[$id]['parent']) {
 916            // curElement is already a reference, so save it in path
 917            $path[] = &$this->data[$id];
 918            // get the next parent id, for the while to retreive the parent's parent
 919            $id = $this->data[$id]['parent']['id'];
 920        }
 921        // dont forget the last one
 922        $path[] = &$this->data[$id];
 923
 924        return array_reverse($path);
 925    }
 926
 927    // }}}
 928    // {{{ setRemoveRecursively()
 929
 930     /**
 931     * sets the remove-recursively mode, either true or false
 932     *
 933     * @version 2001/10/09
 934     * @access  public
 935     * @author  Wolfram Kriesing <wolfram@kriesing.de>
 936     * @param   boolean set to true if removing a tree level
 937     *                  shall remove all it's children and theit children
 938     *
 939     */
 940    function setRemoveRecursively($case=true)
 941    {
 942        $this->removeRecursively = $case;
 943    }
 944
 945    // }}}
 946    // {{{ _getElement()
 947
 948    /**
 949     *
 950     *
 951     * @version    2002/01/21
 952     * @access     private
 953     * @author     Wolfram Kriesing <wolfram@kriesing.de>
 954     * @param      int     the element ID
 955     *
 956     */
 957    function &_getElement($id, $what = '')
 958    {
 959        // We should not return false, since that might be a value of the
 960        // element that is requested.
 961        $element = null;
 962
 963        if ($what == '') {
 964            $element = $this->data[$id];
 965        }
 966        $elementId = $this->_getElementId($id, $what);
 967        if ($elementId !== null) {
 968            $element = $this->data[$elementId];
 969        }
 970        // we should not return false, since that might be a value
 971        // of the element that is requested
 972        return $element;
 973    }
 974
 975    // }}}
 976    // {{{ _getElementId()
 977
 978    /**
 979     *
 980     *
 981     * @version    2002/01/21
 982     * @access     private
 983     * @author     Wolfram Kriesing <wolfram@kriesing.de>
 984     * @param      int     the element ID
 985     *
 986     */
 987    function _getElementId($id, $what)
 988    {
 989        if (isset($this->data[$id][$what]) && $this->data[$id][$what]) {
 990            return $this->data[$id][$what]['id'];
 991        }
 992        return null;
 993    }
 994
 995    // }}}
 996    // {{{ getElement()
 997
 998    /**
 999     * gets an element as a reference
1000     *
1001     * @version 2002/01/21
1002     * @access  private
1003     * @author  Wolfram Kriesing <wolfram@kriesing.de>
1004     * @param   int the element ID
1005     *
1006     */
1007    function &getElement($id)
1008    {
1009        return $element = &$this->_getElement($id);
1010    }
1011
1012    // }}}
1013    // {{{ getElementContent()
1014
1015    /**
1016     *
1017     *
1018     * @version 2002/02/06
1019     * @access  private
1020     * @author  Wolfram Kriesing <wolfram@kriesing.de>
1021     * @param   mixed    either the id of an element
1022     *                      or the path to the element
1023     *
1024     */
1025    function getElementContent($idOrPath, $fieldName)
1026    {
1027        if (is_string($idOrPath)) {
1028            $idOrPath = $this->getIdByPath($idOrPath);
1029        }
1030        return $this->data[$idOrPath][$fieldName];
1031    }
1032
1033    // }}}
1034    // {{{ getElementsContent()
1035
1036    /**
1037     *
1038     *
1039     * @version    2002/02/06
1040     * @access     private
1041     * @author     Wolfram Kriesing <wolfram@kriesing.de>
1042     * @param      int     the element ID
1043     *
1044     */
1045    function getElementsContent($ids, $fieldName) {
1046        // i dont know if this method is not just overloading the file.
1047        // Since it only serves my lazyness
1048        // is this effective here? i can also loop in the calling code!?
1049        $fields = array();
1050        if (is_array($ids) && count($ids)) {
1051            foreach ($ids as $aId) {
1052                $fields[] = $this->getElementContent($aId, $fieldName);
1053            }
1054        }
1055        return $fields;
1056    }
1057
1058    // }}}
1059    // {{{ getElementByPath()
1060
1061    /**
1062     * gets an element given by it's path as a reference
1063     *
1064     * @version 2002/01/21
1065     * @access  public
1066     * @author  Wolfram Kriesing <wolfram@kriesing.de>
1067     * @param   string  the path to search for
1068     * @param   integer the id where to search for the path
1069     * @param   string  the name of the key that contains the node name
1070     * @param   string  the path separator
1071     * @return  integer the id of the searched element
1072     *
1073     */
1074    function &getElementByPath($path, $startId = 0, $nodeName = 'name', $seperator = '/')
1075    {
1076        $element = null;
1077        $id = $this->getIdByPath($path,$startId);
1078        if ($id) {
1079            $element = &$this->getElement($id);
1080        }
1081        // return null since false might be interpreted as id 0
1082        return $element;
1083    }
1084
1085    // }}}
1086    // {{{ getLevel()
1087
1088    /**
1089     * get the level, which is how far below the root are we?
1090     *
1091     * @version 2001/11/25
1092     * @access  public
1093     * @author  Wolfram Kriesing <wolfram@kriesing.de>
1094     * @param   mixed   $id     the id of the node to get the level for
1095     *
1096     */
1097    function getLevel($id)
1098    {
1099        return $this->data[$id]['level'];
1100    }
1101
1102    // }}}
1103    // {{{ getChild()
1104
1105    /**
1106     * returns the child if the node given has one
1107     * !!! ATTENTION watch out that you never change any of the data returned,
1108     * since they are references to the internal property $data
1109     *
1110     * @version 2001/11/27
1111     * @access  public
1112     * @author  Wolfram Kriesing <wolfram@kriesing.de>
1113     * @param   mixed   the id of the node to get the child for
1114     *
1115     */
1116    function &getChild($id)
1117    {
1118        return $element = &$this->_getElement($id, 'child');
1119    }
1120
1121    // }}}
1122    // {{{ getParent()
1123
1124    /**
1125     * returns the child if the node given has one
1126     * !!! ATTENTION watch out that you never change any of the data returned,
1127     * since they are references to the internal property $data
1128     *
1129     * @version 2001/11/27
1130     * @access  public
1131     * @author  Wolfram Kriesing <wolfram@kriesing.de>
1132     * @param   mixed   $id     the id of the node to get the child for
1133     *
1134     */
1135    function &getParent($id)
1136    {
1137        return $element = &$this->_getElement($id, 'parent');
1138    }
1139
1140    // }}}
1141    // {{{ getNext()
1142
1143    /**
1144     * returns the next element if the node given has one
1145     * !!! ATTENTION watch out that you never change any of the data returned,
1146     * since they are references to the internal property $data
1147     *
1148     * @version 2002/01/17
1149     * @access  public
1150     * @author  Wolfram Kriesing <wolfram@kriesing.de>
1151     * @param   mixed   the id of the node to get the child for
1152     * @return  mixed   reference to the next element or false if there is none
1153     */
1154    function &getNext($id)
1155    {
1156        return $element = &$this->_getElement($id, 'next');
1157    }
1158
1159    // }}}
1160    // {{{ getPrevious()
1161
1162    /**
1163     * returns the previous element if the node given has one
1164     * !!! ATTENTION watch out that you never change any of the data returned,
1165     * since they are references to the internal property $data
1166     *
1167     * @version 2002/02/05
1168     * @access  public
1169     * @author  Wolfram Kriesing <wolfram@kriesing.de>
1170     * @param   mixed   the id of the node to get the child for
1171     * @return  mixed   reference to the next element or false if there is none
1172     */
1173    function &getPrevious($id)
1174    {
1175        return $element = &$this->_getElement($id, 'previous');
1176    }
1177
1178    // }}}
1179    // {{{ getNode()
1180
1181    /**
1182     * returns the node for the given id
1183     * !!! ATTENTION watch out that you never change any of the data returned,
1184     * since they are references to the internal property $data
1185     *
1186     * @version    2001/11/28
1187     * @access     public
1188     * @author     Wolfram Kriesing <wolfram@kriesing.de>
1189     * @param      mixed   $id     the id of the node to get
1190     *
1191     */
1192/*
1193    function &getNode($id)
1194    {
1195        //return $element = &$this->_getElement($id);
1196    }
1197*/
1198
1199    // }}}
1200    // {{{ getIdByPath()
1201
1202    /**
1203     * return the id of the element which is referenced by $path
1204     * this is useful for xml-structures, like: getIdByPath('/root/sub1/sub2')
1205     * this requires the structure to use each name uniquely
1206     * if this is not given it will return the first proper path found
1207     * i.e. there should only be one path /x/y/z
1208     *
1209     * @version    2001/11/28
1210     * @access     public
1211     * @author     Wolfram Kriesing <wolfram@kriesing.de>
1212     * @param      string  $path       the path to search for
1213     * @param      integer $startId    the id where to search for the path
1214     * @param      string  $nodeName   the name of the key that contains the node name
1215     * @param      string  $seperator  the path seperator
1216     * @return     integer the id of the searched element
1217     *
1218     */
1219    function getIdByPath($path, $startId = 0, $nodeName = 'name', $seperator = '/')
1220// should this method be called getElementIdByPath ????
1221    {
1222        // if no start ID is given get the root
1223        if ($startId == 0) {
1224            $startId = $this->getFirstRootId();
1225        } else {   // if a start id is given, get its first child to start searching there
1226            $startId = $this->getChildId($startId);
1227            if ($startId==false) {                 // is there a child to this element?
1228                return false;
1229            }
1230        }
1231
1232        if (strpos($path,$seperator) === 0) {  // if a seperator is at the beginning strip it off
1233            $path = substr($path,strlen($seperator));
1234        }
1235        $nodes = explode($seperator, $path);
1236        $curId = $startId;
1237        foreach ($nodes as $key => $aNodeName) {
1238            $nodeFound = false;
1239            do {
1240                if ($this->data[$curId][$nodeName] == $aNodeName) {
1241                    $nodeFound = true;
1242                    // do only save the child if we are not already at the end of path
1243                    // because then we need curId to return it
1244                    if ($key < (count($nodes) - 1)) {
1245                        $curId = $this->getChildId($curId);
1246                    }
1247                    break;
1248                }
1249                $curId = $this->getNextId($curId);
1250            } while($curId);
1251
1252            if ($nodeFound == false) {
1253                return false;
1254            }
1255        }
1256        return $curId;
1257        // FIXXME to be implemented
1258    }
1259
1260    // }}}
1261    // {{{ getFirstRoot()
1262
1263    /**
1264     * this gets the first element that is in the root node
1265     * i think that there can't be a "getRoot" method since there might
1266     * be multiple number of elements in the root node, at least the
1267     * way it works now
1268     *
1269     * @access     public
1270     * @version    2001/12/10
1271     * @author     Wolfram Kriesing <wolfram@kriesing.de>
1272     * @return     returns the first root element
1273     */
1274    function &getFirstRoot()
1275    {
1276        // could also be reset($this->data) i think since php keeps the order
1277        // ... but i didnt try
1278        reset($this->structure);
1279        return $this->data[key($this->structure)];
1280    }
1281
1282    // }}}
1283    // {{{ getRoot()
1284
1285    /**
1286     * since in a nested tree there can only be one root
1287     * which i think (now) is correct, we also need an alias for this method
1288     * this also makes all the methods in Tree_Common, which access the
1289     * root element work properly!
1290     *
1291     * @access     public
1292     * @version    2002/07/26
1293     * @author     Wolfram Kriesing <wolfram@kriesing.de>
1294     * @return     returns the first root element
1295     */
1296    function &getRoot()
1297    {
1298        return $this->getFirstRoot();
1299    }
1300
1301    // }}}
1302    // {{{ getRoot()
1303
1304    /**
1305     * gets the tree under the given element in one array, sorted
1306     * so you can go through the elements from begin to end and list them
1307     * as they are in the tree, where every child (until the deepest) is retreived
1308     *
1309     * @see        &_getNode()
1310     * @access     public
1311     * @version    2001/12/17
1312     * @author     Wolfram Kriesing <wolfram@kriesing.de>
1313     * @param      integer  the id where to start walking
1314     * @param      integer  this number says how deep into
1315     *                      the structure the elements shall be
1316     *                      retreived
1317     * @return     array    sorted as listed in the tree
1318     */
1319    function &getNode($startId=0, $depth=0)
1320    {
1321        if ($startId == 0) {
1322            $level = 0;
1323        } else {
1324            $level = $this->getLevel($startId);
1325        }
1326
1327        $this->_getNodeMaxLevel = $depth ? ($depth + $level) : 0 ;
1328        //!!!        $this->_getNodeCurParent = $this->data['parent']['id'];
1329
1330        // if the tree is empty dont walk through it
1331        if (!count($this->data)) {
1332            return;
1333        }
1334
1335        $ret = $this->walk(array(&$this,'_getNode'), $startId, 'ifArray');
1336        return $ret;
1337    }
1338
1339    // }}}
1340    // {{{ _getNode()
1341
1342    /**
1343     * this is used for walking through the tree structure
1344     * until a given level, this method should only be used by getNode
1345     *
1346     * @see        &getNode()
1347     * @see        walk()
1348     * @see        _walk()
1349     * @access     private
1350     * @version    2001/12/17
1351     * @author     Wolfram Kriesing <wolfram@kriesing.de>
1352     * @param      array    the node passed by _walk
1353     * @return     mixed    either returns the node, or nothing
1354     *                      if the level _getNodeMaxLevel is reached
1355     */
1356    function &_getNode(&$node)
1357    {
1358        if ($this->_getNodeMaxLevel) {
1359            if ($this->getLevel($node['id']) < $this->_getNodeMaxLevel) {
1360                return $node;
1361            }
1362            return;
1363        }
1364        return $node;
1365    }
1366
1367    // }}}
1368    // {{{ getChildren()
1369
1370    /**
1371     * returns the children of the given ids
1372     *
1373     * @version 2001/12/17
1374     * @access  public
1375     * @author  Wolfram Kriesing <wolfram@kriesing.de>
1376     * @param   integer $id the id of the node to check for children
1377     * @param   integer the children of how many levels shall be returned
1378     * @return  boolean true if the node has children
1379     */
1380    function getChildren($ids, $levels = 1)
1381    {
1382        //FIXXME $levels to be implemented
1383        $ret = array();
1384        if (is_array($ids)) {
1385            foreach ($ids as $aId) {
1386                if ($this->hasChildren($aId)) {
1387                    $ret[$aId] = $this->data[$aId]['children'];
1388                }
1389            }
1390        } else {
1391            if ($this->hasChildren($ids)) {
1392                $ret = $this->data[$ids]['children'];
1393            }
1394        }
1395        return $ret;
1396    }
1397
1398    // }}}
1399    // {{{ isNode()
1400
1401    /**
1402     * returns if the given element is a valid node
1403     *
1404     * @version 2001/12/21
1405     * @access  public
1406     * @author  Wolfram Kriesing <wolfram@kriesing.de>
1407     * @param   integer $id the id of the node to check for children
1408     * @return  boolean true if the node has children
1409     */
1410    function isNode($id = 0)
1411    {
1412        return isset($this->data[$id]);
1413    }
1414
1415    // }}}
1416    // {{{ varDump()
1417
1418    /**
1419     * this is for debugging, dumps the entire data-array
1420     * an extra method is needed, since this array contains recursive
1421     * elements which make a normal print_f or var_dump not show all the data
1422     *
1423     * @version 2002/01/21
1424     * @access  public
1425     * @author  Wolfram Kriesing <wolfram@kriesing.de>
1426     * @params  mixed   either the id of the node to dump, this will dump
1427     *                  everything below the given node or an array of nodes
1428     *                  to dump. This only dumps the elements passed
1429     *                  as an array. 0 or no parameter if the entire tree shall
1430     *                  be dumped if you want to dump only a single element
1431     *                  pass it as an array using array($element).
1432     */
1433    function varDump($…

Large files files are truncated, but you can click here to view the full file