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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1 - (hide annotations)
Thu Feb 27 16:49:56 2003 UTC (21 years, 6 months ago) by joko
Branch: MAIN
+ initial commit, from PEAR

1 joko 1.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.17 2003/01/18 15:38:04 cain 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( $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