Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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
 
21
require_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
 */
90
class 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($node = 0)
1434
    {
1435
        $dontDump = array('parent', 'child', 'children', 'next', 'previous');
1436
 
1437
        // if $node is an array, we assume it is a collection of elements
1438
        if (!is_array($node)) {
1439
            $nodes = $this->getNode($node);
1440
        } else {
1441
            $nodes = $node;
1442
        }
1443
        // if $node==0 then the entire tree is retreived
1444
        if (count($node)) {
1445
            echo '<table border="1"><tr><th>name</th>';
1446
            $keys = array();
1447
            foreach ($this->getRoot() as $key => $x) {
1448
                if (!is_array($x)) {
1449
                    echo "<th>$key</th>";
1450
                    $keys[] = $key;
1451
                }
1452
            }
1453
            echo '</tr>';
1454
 
1455
            foreach ($nodes as $aNode) {
1456
                echo '<tr><td nowrap="nowrap">';
1457
                $prefix = '';
1458
                for ($i = 0; $i < $aNode['level']; $i++) $prefix .= '- ';
1459
                echo "$prefix {$aNode['name']}</td>";
1460
                foreach ($keys as $aKey) {
1461
                    if (!is_array($key)) {
1462
                        $val = isset($aNode[$aKey]) ? $aNode[$aKey] : '&nbsp;';
1463
                        echo "<td>$val</td>";
1464
                    }
1465
                }
1466
                echo '</tr>';
1467
            }
1468
            echo '</table>';
1469
        }
1470
    }
1471
 
1472
    // }}}
1473
 
1474
    //### TODO's ###
1475
 
1476
    // {{{ copy()
1477
    /**
1478
     * NOT IMPLEMENTED YET
1479
     * copies a part of the tree under a given parent
1480
     *
1481
     * @version 2001/12/19
1482
     * @access  public
1483
     * @author  Wolfram Kriesing <wolfram@kriesing.de>
1484
     * @param   the id of the element which is copied, all its children are copied too
1485
     * @param   the id that shall be the new parent
1486
     * @return  boolean     true on success
1487
     */
1488
    function copy($srcId, $destId)
1489
    {
1490
        if (method_exists($this->dataSourceClass, 'copy')) {
1491
            return $this->dataSourceClass->copy($srcId, $destId);
1492
        } else {
1493
            return $this->_throwError('method not implemented yet.', __LINE__);
1494
        }
1495
/*
1496
    remove all array elements after 'parent' since those had been created
1497
    and remove id and set parentId and that should be it, build the tree and pass it to addNode
1498
 
1499
    those are the fields in one data-entry
1500
id=>41
1501
parentId=>39
1502
name=>Java
1503
parent=>Array
1504
prevId=>58
1505
previous=>Array
1506
childId=>77
1507
child=>Array
1508
nextId=>104
1509
next=>Array
1510
children=>Array
1511
level=>2
1512
 
1513
        $this->getNode
1514
        foreach($this->data[$srcId] as $key=>$value)
1515
            echo "$key=>$value<br>";
1516
*/
1517
    }
1518
 
1519
    // }}}
1520
 
1521
}