/[cvs]/nfo/php/libs/net.php.pear/Tree/Memory.php
ViewVC logotype

Contents of /nfo/php/libs/net.php.pear/Tree/Memory.php

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.3 - (show annotations)
Wed Jul 7 02:49:19 2004 UTC (20 years ago) by joko
Branch: MAIN
CVS Tags: HEAD
Changes since 1.2: +98 -95 lines
updated to Tree-0.2.4

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

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed