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