/[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.2 - (show annotations)
Tue May 13 09:38:58 2003 UTC (21 years, 3 months ago) by joko
Branch: MAIN
Changes since 1.1: +2 -2 lines
minor fix: using "isset"

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

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