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

Contents of /nfo/php/libs/net.php.pear/Tree/Dynamic/DBnested.php

Parent Directory Parent Directory | Revision Log Revision Log


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

1 <?php
2 //
3 // +----------------------------------------------------------------------+
4 // | PHP Version 4 |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2003 The PHP Group |
7 // +----------------------------------------------------------------------+
8 // | This source file is subject to version 2.02 of the PHP license, |
9 // | that is bundled with this package in the file LICENSE, and is |
10 // | available at through the world-wide-web at |
11 // | http://www.php.net/license/2_02.txt. |
12 // | If you did not receive a copy of the PHP license and are unable to |
13 // | obtain it through the world-wide-web, please send a note to |
14 // | license@php.net so we can mail you a copy immediately. |
15 // +----------------------------------------------------------------------+
16 // | Authors: |
17 // +----------------------------------------------------------------------+
18 //
19 // $Id: DBnested.php,v 1.16 2003/03/04 18:57:18 cain Exp $
20
21 require_once('Tree/Common.php');
22 require_once('Tree/Error.php');
23
24 /**
25 * this class implements methods to work on a tree saved using the nested
26 * tree model
27 * explaination: http://research.calacademy.org/taf/proceedings/ballew/index.htm
28 *
29 * @access public
30 * @package Tree
31 */
32 class Tree_Dynamic_DBnested extends Tree_Common
33 // FIXXME should actually extend Tree_Common, to use the methods provided in there... but we need to connect
34 // to the db here, so we extend optionsDB for now, may be use "aggregate" function to fix that
35 {
36
37 var $debug = 0;
38
39 var $options = array(
40 // FIXXME to be implemented
41 'whereAddOn'=>'' // add on for the where clause, this string is simply added behind the WHERE in the select
42 // so you better make sure its correct SQL :-), i.e. 'uid=3'
43 // this is needed i.e. when you are saving many trees in one db-table
44 ,'table' =>'' //
45 // since the internal names are fixed, to be portable between different
46 // DB tables with different column namings, we map the internal name to the real column name
47 // using this array here, if it stays empty the internal names are used, which are:
48 // id, left, right
49 ,'columnNameMaps'=>array(
50 //'id' => 'node_id', // use "node_id" as "id"
51 'left' => 'l' // since mysql at least doesnt support 'left' ...
52 ,'right' => 'r' // ...as a column name we set default to the first letter only
53 //'name' => 'nodeName' //
54 ,'parentId' => 'parent' // parent id
55 )
56 ,'order' => '' // needed for sorting the tree, currently only used in Memory_DBnested
57 );
58
59 // the defined methods here are proposals for the implementation,
60 // they are named the same, as the methods in the "Memory" branch, if possible
61 // it would be cool to keep the same naming and if the same parameters would be possible too
62 // then it would be even better, so one could easily change from any kind of
63 // tree-implementation to another, without changing the source code, only the setupXXX would need to be changed
64
65 /**
66 *
67 *
68 * @access public
69 * @version 2002/03/02
70 * @author Wolfram Kriesing <wolfram@kriesing.de>
71 * @param
72 * @return
73 */
74 function __construct( $dsn , $options=array() )
75 {
76 Tree_Dynamic_DBnested( $dsn , $options );
77 }
78
79 /**
80 *
81 *
82 * @access public
83 * @version 2002/03/02
84 * @author Wolfram Kriesing <wolfram@kriesing.de>
85 * @param
86 * @return
87 */
88 function Tree_Dynamic_DBnested( $dsn , $options=array() )
89 {
90 parent::Tree_OptionsDB( $dsn , $options ); // instanciate DB
91 $this->table = $this->getOption('table');
92 }
93
94 /**
95 * add a new element to the tree
96 * there are three ways to use this method
97 * 1.
98 * give only the $parentId and the $newValues will be inserted as the first child of this parent
99 * i.e. // insert a new element under the parent with the ID=7
100 * $tree->add( array('name'=>'new element name') , 7 );
101 * 2.
102 * give the $prevId ($parentId will be dismissed) and the new element
103 * will be inserted in the tree after the element with the ID=$prevId
104 * the parentId is not necessary because the prevId defines exactly where
105 * the new element has to be place in the tree, and the parent is the same as
106 * for the element with the ID=$prevId
107 * i.e. // insert a new element after the element with the ID=5
108 * $tree->add( array('name'=>'new') , 0 , 5 );
109 * 3.
110 * neither $parentId nor prevId is given, then the root element will be inserted
111 * this requires that programmer is responsible to confirm this
112 * this method does not (yet) check if there is already a root element saved !!!
113 *
114 * @access public
115 * @author Wolfram Kriesing <wolfram@kriesing.de>
116 * @param array $newValues this array contains the values that shall be inserted in the db-table
117 * @param integer $parentId the id of the element which shall be the parent of the new element
118 * @param integer $prevId the id of the element which shall preceed the one to be inserted
119 * use either 'parentId' or 'prevId'
120 * @return integer the ID of the element that had been inserted
121 */
122 function add( $newValues , $parentId=0 , $prevId=0 )
123 {
124 $lName = $this->_getColName('left');
125 $rName = $this->_getColName('right');
126 $prevVisited = 0;
127
128 // check the DB-table if the columns which are given as keys
129 // in the array $newValues do really exist, if not remove them from the array
130 // FIXXME do the above described
131
132 if( $parentId || $prevId ) // if no parent and no prevId is given the root shall be added
133 {
134 if( $prevId )
135 {
136 $element = $this->getElement( $prevId );
137 $parentId = $element['parentId']; // we also need the parent id of the element, to write it in the db
138 }
139 else
140 {
141 $element = $this->getElement( $parentId );
142 }
143 $newValues['parentId'] = $parentId;
144
145 if( PEAR::isError($element) )
146 return $element;
147
148 // get the "visited"-value where to add the new element behind
149 // if $prevId is given, we need to use the right-value
150 // if only the $parentId is given we need to use the left-value
151 // look at it graphically, that made me understand it :-)
152 // i.e. at: http://research.calacademy.org/taf/proceedings/ballew/sld034.htm
153 $prevVisited = $prevId ? $element['right'] : $element['left'];
154
155 // FIXXME start transaction here
156
157 if( PEAR::isError($err=$this->_add( $prevVisited , 1 )) )
158 {
159 // FIXXME rollback
160 //$this->dbh->rollback();
161 return $err;
162 }
163 }
164
165 // inserting _one_ new element in the tree
166 $newData = array();
167 foreach( $newValues as $key=>$value ) // quote the values, as needed for the insert
168 {
169 $newData[$this->_getColName($key)] = $this->dbh->quote($value);
170 }
171
172 // set the proper right and left values
173 $newData[$lName] = $prevVisited+1;
174 $newData[$rName] = $prevVisited+2;
175
176 // use sequences to create a new id in the db-table
177 $nextId = $this->dbh->nextId($this->table);
178 $query = sprintf( 'INSERT INTO %s (%s,%s) VALUES (%s,%s)',
179 $this->table ,
180 $this->_getColName('id'),
181 implode( "," , array_keys($newData) ) ,
182 $nextId,
183 implode( "," , $newData )
184 );
185 if( DB::isError( $res = $this->dbh->query($query) ) )
186 {
187 // rollback
188 return $this->_throwError( $res->getMessage() , __LINE__ );
189 }
190 // commit here
191
192 return $nextId;
193 } // end of function
194
195 /**
196 * this method only updates the left/right values of all the
197 * elements that are affected by the insertion
198 * be sure to set the parentId of the element(s) you insert
199 *
200 * @param int this parameter is not the ID!!!
201 * it is the previous visit number, that means
202 * if you are inserting a child, you need to use the left-value
203 * of the parent
204 * if you are inserting a "next" element, on the same level
205 * you need to give the right value !!
206 * @param int the number of elements you plan to insert
207 * @return mixed either true on success or a Tree_Error on failure
208 */
209 function _add( $prevVisited , $numberOfElements=1 )
210 {
211 $lName = $this->_getColName('left');
212 $rName = $this->_getColName('right');
213
214 // update the elements which will be affected by the new insert
215 $query = sprintf( 'UPDATE %s SET %s=%s+%s WHERE%s %s>%s',
216 $this->table,
217 $lName,$lName,
218 $numberOfElements*2,
219 $this->_getWhereAddOn(),
220 $lName,
221 $prevVisited );
222 if( DB::isError( $res = $this->dbh->query($query) ) )
223 {
224 // FIXXME rollback
225 return $this->_throwError( $res->getMessage() , __LINE__ );
226 }
227
228 $query = sprintf( 'UPDATE %s SET %s=%s+%s WHERE%s %s>%s',
229 $this->table,
230 $rName,$rName,
231 $numberOfElements*2,
232 $this->_getWhereAddOn(),
233 $rName,
234 $prevVisited );
235 if( DB::isError( $res = $this->dbh->query($query) ) )
236 {
237 // FIXXME rollback
238 return $this->_throwError( $res->getMessage() , __LINE__ );
239 }
240 return true;
241 }
242
243 /**
244 * remove a tree element
245 * this automatically remove all children and their children
246 * if a node shall be removed that has children
247 *
248 * @access public
249 * @author Wolfram Kriesing <wolfram@kriesing.de>
250 * @param integer $id the id of the element to be removed
251 * @return boolean returns either true or throws an error
252 */
253 function remove( $id )
254 {
255 $element = $this->getElement( $id );
256 if( PEAR::isError($element) )
257 return $element;
258
259 // FIXXME start transaction
260 //$this->dbh->autoCommit(false);
261 $query = sprintf( 'DELETE FROM %s WHERE%s %s BETWEEN %s AND %s',
262 $this->table,
263 $this->_getWhereAddOn(),
264 $this->_getColName('left'),
265 $element['left'],$element['right']);
266 if( DB::isError( $res = $this->dbh->query($query) ) )
267 {
268 // FIXXME rollback
269 //$this->dbh->rollback();
270 return $this->_throwError( $res->getMessage() , __LINE__ );
271 }
272
273 if( PEAR::isError($err=$this->_remove( $element )) )
274 {
275 // FIXXME rollback
276 //$this->dbh->rollback();
277 return $err;
278 }
279 return true;
280 }
281
282 /**
283 * removes a tree element, but only updates the left/right values
284 * to make it seem as if the given element would not exist anymore
285 * it doesnt remove the row(s) in the db itself!
286 *
287 * @see getElement()
288 * @access private
289 * @author Wolfram Kriesing <wolfram@kriesing.de>
290 * @param array the entire element returned by "getElement"
291 * @return boolean returns either true or throws an error
292 */
293 function _remove( $element )
294 {
295 $delta = $element['right'] - $element['left'] +1;
296 $lName = $this->_getColName('left');
297 $rName = $this->_getColName('right');
298
299 // update the elements which will be affected by the remove
300 $query = sprintf( "UPDATE %s SET %s=%s-$delta, %s=%s-$delta WHERE%s %s>%s",
301 $this->table,
302 $lName,$lName,
303 $rName,$rName,
304 $this->_getWhereAddOn(),
305 $lName,$element['left'] );
306 if( DB::isError( $res = $this->dbh->query($query) ) )
307 {
308 // the rollback shall be done by the method calling this one, since it is only private we can do that
309 return $this->_throwError( $res->getMessage() , __LINE__ );
310 }
311
312 $query = sprintf( "UPDATE %s SET %s=%s-$delta WHERE%s %s<%s AND %s>%s",
313 $this->table,
314 $rName,$rName,
315 $this->_getWhereAddOn(),
316 $lName,$element['left'],
317 $rName,$element['right'] );
318 if( DB::isError( $res = $this->dbh->query($query) ) )
319 {
320 // the rollback shall be done by the method calling this one, since it is only private
321 return $this->_throwError( $res->getMessage() , __LINE__ );
322 }
323 // FIXXME commit - should that not also be done in the method calling this one? like when an error occurs?
324 //$this->dbh->commit();
325 return true;
326 } // end of function
327
328 /**
329 * move an entry under a given parent or behind a given entry.
330 * If a newPrevId is given the newParentId is dismissed!
331 * call it either like this:
332 * $tree->move( x , y )
333 * to move the element (or entire tree) with the id x
334 * under the element with the id y
335 * or
336 * $tree->move( x , 0 , y ); // ommit the second parameter by setting it to 0
337 * to move the element (or entire tree) with the id x
338 * behind the element with the id y
339 * or
340 * $tree->move( array(x1,x2,x3) , ...
341 * the first parameter can also be an array of elements that shall be moved
342 * the second and third para can be as described above
343 * If you are using the Memory_DBnested then this method would be invain,
344 * since Memory.php already does the looping through multiple elements, but if
345 * Dynamic_DBnested is used we need to do the looping here
346 *
347 * @version 2002/06/08
348 * @access public
349 * @author Wolfram Kriesing <wolfram@kriesing.de>
350 * @param integer the id(s) of the element(s) that shall be moved
351 * @param integer the id of the element which will be the new parent
352 * @param integer if prevId is given the element with the id idToMove
353 * shall be moved _behind_ the element with id=prevId
354 * if it is 0 it will be put at the beginning
355 * @return mixed true for success, Tree_Error on failure
356 */
357 function move( $idsToMove , $newParentId , $newPrevId=0 )
358 {
359 settype($idsToMove,'array');
360 $errors = array();
361 foreach( $idsToMove as $idToMove )
362 {
363 $ret = $this->_move( $idToMove , $newParentId , $newPrevId );
364 if( PEAR::isError($ret) )
365 $errors[] = $ret;
366 }
367 // FIXXME the error in a nicer way, or even better let the throwError method do it!!!
368 if( sizeof($errors) )
369 {
370 return $this->_throwError(serialize($errors),__LINE__);
371 }
372 return true;
373 }
374
375 /**
376 * this method moves one tree element
377 *
378 * @see move()
379 * @version 2002/04/29
380 * @access public
381 * @author Wolfram Kriesing <wolfram@kriesing.de>
382 * @param integer the id of the element that shall be moved
383 * @param integer the id of the element which will be the new parent
384 * @param integer if prevId is given the element with the id idToMove
385 * shall be moved _behind_ the element with id=prevId
386 * if it is 0 it will be put at the beginning
387 * @return mixed true for success, Tree_Error on failure
388 */
389 function _move( $idToMove , $newParentId , $newPrevId=0 )
390 {
391 // do some integrity checks first
392 if ($newPrevId) {
393 if ($newPrevId==$idToMove) { // dont let people move an element behind itself, tell it succeeded, since it already is there :-)
394 return true;
395 }
396 if (PEAR::isError($newPrevious=$this->getElement($newPrevId))) {
397 return $newPrevious;
398 }
399 $newParentId = $newPrevious['parentId'];
400 } else {
401 if ($newParentId==0) {
402 return $this->_throwError( 'no parent id given' , __LINE__ );
403 }
404 if ($this->isChildOf($idToMove,$newParentId)) { // if the element shall be moved under one of its children, return false
405 return $this->_throwError( 'can not move an element under one of its children' , __LINE__ );
406 }
407 if ($newParentId==$idToMove) { // dont do anything to let an element be moved under itself, which is bullshit
408 return true;
409 }
410 if (PEAR::isError($newParent=$this->getElement($newParentId))) { // try to retreive the data of the parent element
411 return $newParent;
412 }
413 }
414
415 if (PEAR::isError($element=$this->getElement($idToMove))) { // get the data of the element itself
416 return $element;
417 }
418
419 $numberOfElements = ($element['right'] - $element['left']+1)/2;
420 $prevVisited = $newPrevId ? $newPrevious['right'] : $newParent['left'];
421
422 // FIXXME start transaction
423
424 // add the left/right values in the new parent, to have the space to move the new values in
425 if (PEAR::isError($err=$this->_add( $prevVisited , $numberOfElements ))) {
426 // FIXXME rollback
427 //$this->dbh->rollback();
428 return $err;
429 }
430
431 // update the parentId of the element with $idToMove
432 if (PEAR::isError($err=$this->update($idToMove,array('parentId'=>$newParentId)))) {
433 // FIXXME rollback
434 //$this->dbh->rollback();
435 return $err;
436 }
437
438 // update the lefts and rights of those elements that shall be moved
439
440 // first get the offset we need to add to the left/right values
441 // if $newPrevId is given we need to get the right value, otherwise the left
442 // since the left/right has changed, because we already updated it up there we need to
443 // get them again, we have to do that anyway, to have the proper new left/right values
444 if ($newPrevId) {
445 if (PEAR::isError($temp = $this->getElement( $newPrevId ))) {
446 // FIXXME rollback
447 //$this->dbh->rollback();
448 return $temp;
449 }
450 $calcWith = $temp['right'];
451 } else {
452 if (PEAR::isError($temp=$this->getElement($newParentId))) {
453 // FIXXME rollback
454 //$this->dbh->rollback();
455 return $temp;
456 }
457 $calcWith = $temp['left'];
458 }
459
460 // get the element that shall be moved again, since the left and right might have changed by the add-call
461 if (PEAR::isError($element=$this->getElement($idToMove))) {
462 return $element;
463 }
464
465 $offset = $calcWith - $element['left']; // calc the offset that the element to move has to the spot where it should go
466 $offset++; // correct the offset by one, since it needs to go inbetween!
467
468 $lName = $this->_getColName('left');
469 $rName = $this->_getColName('right');
470 $query = sprintf( "UPDATE %s SET %s=%s+$offset,%s=%s+$offset WHERE%s %s>%s AND %s<%s",
471 $this->table,
472 $rName,$rName,
473 $lName,$lName,
474 $this->_getWhereAddOn(),
475 $lName,$element['left']-1,
476 $rName,$element['right']+1 );
477 if (DB::isError($res=$this->dbh->query($query))) {
478 // FIXXME rollback
479 //$this->dbh->rollback();
480 return $this->_throwError( $res->getMessage() , __LINE__ );
481 }
482
483 // remove the part of the tree where the element(s) was/were before
484 if (PEAR::isError($err=$this->_remove($element))) {
485 // FIXXME rollback
486 //$this->dbh->rollback();
487 return $err;
488 }
489 // FIXXME commit all changes
490 //$this->dbh->commit();
491
492 return true;
493 } // end of function
494
495 /**
496 * update the tree element given by $id with the values in $newValues
497 *
498 * @access public
499 * @author Wolfram Kriesing <wolfram@kriesing.de>
500 * @param int the id of the element to update
501 * @param array the new values, the index is the col name
502 * @return mixed either true or an Tree_Error
503 */
504 function update($id,$newValues)
505 {
506 // jsut to be sure nothing gets screwed up :-)
507 unset($newValues[$this->_getColName('left')]);
508 unset($newValues[$this->_getColName('right')]);
509 unset($newValues[$this->_getColName('parentId')]);
510
511 // updating _one_ element in the tree
512 $values = array();
513 foreach ($newValues as $key=>$value) {
514 $values[] = $this->_getColName($key).'='.$this->dbh->quote($value);
515 }
516 $query = sprintf( 'UPDATE %s SET %s WHERE%s %s=%s',
517 $this->table,
518 implode(',',$values),
519 $this->_getWhereAddOn(),
520 $this->_getColName('id'),
521 $id);
522 if (DB::isError( $res=$this->dbh->query($query))) {
523 // FIXXME raise PEAR error
524 return $this->_throwError( $res->getMessage() , __LINE__ );
525 }
526
527 return true;
528 } // end of function
529
530 /**
531 * copy a subtree/node/... under a new parent or/and behind a given element
532 *
533 *
534 * @access public
535 * @author Wolfram Kriesing <wolfram@kriesing.de>
536 * @param
537 * @return
538 */
539 function copy( $id , $parentId=0 , $prevId=0 )
540 {
541 return $this->_throwError( 'copy-method is not implemented yet!' , __LINE__ );
542 // get element tree
543 // $this->addTree
544 } // end of function
545
546
547 /**
548 * get the root
549 *
550 * @access public
551 * @version 2002/03/02
552 * @author Wolfram Kriesing <wolfram@kriesing.de>
553 * @param
554 * @return mixed either the data of the root element or an Tree_Error
555 */
556 function getRoot()
557 {
558 $query = sprintf( 'SELECT * FROM %s WHERE%s %s=1',
559 $this->table,
560 $this->_getWhereAddOn(),
561 $this->_getColName('left'));
562 if( DB::isError( $res = $this->dbh->getRow($query) ) )
563 {
564 return $this->_throwError( $res->getMessage() , __LINE__ );
565 }
566 return $this->_prepareResult( $res );
567 } // end of function
568
569 /**
570 *
571 *
572 * @access public
573 * @version 2002/03/02
574 * @author Wolfram Kriesing <wolfram@kriesing.de>
575 * @param
576 * @return mixed either the data of the requested element or an Tree_Error
577 */
578 function getElement( $id )
579 {
580 $query = sprintf( 'SELECT * FROM %s WHERE%s %s=%s',
581 $this->table,
582 $this->_getWhereAddOn(),
583 $this->_getColName('id'),
584 $id);
585 if( DB::isError( $res = $this->dbh->getRow($query) ) )
586 {
587 return $this->_throwError( $res->getMessage() , __LINE__ );
588 }
589 if( !$res )
590 return $this->_throwError( "Element with id $id does not exist!" , __LINE__ );
591
592 return $this->_prepareResult( $res );
593 } // end of function
594
595 /**
596 *
597 *
598 * @access public
599 * @version 2002/03/02
600 * @author Wolfram Kriesing <wolfram@kriesing.de>
601 * @param
602 * @return mixed either the data of the requested element or an Tree_Error
603 */
604 function getChild($id)
605 {
606 // subqueries would be cool :-)
607 $curElement = $this->getElement( $id );
608 if (PEAR::isError($curElement)) {
609 return $curElement;
610 }
611
612 $query = sprintf( 'SELECT * FROM %s WHERE%s %s=%s',
613 $this->table,
614 $this->_getWhereAddOn(),
615 $this->_getColName('left'),
616 $curElement['left']+1 );
617 if (DB::isError( $res = $this->dbh->getRow($query))) {
618 return $this->_throwError( $res->getMessage() , __LINE__ );
619 }
620 return $this->_prepareResult( $res );
621 }
622
623 /**
624 * gets the path from the element with the given id down
625 * to the root
626 * the returned array is sorted to start at root
627 * for simply walking through and retreiving the path
628 *
629 * @access public
630 * @version 2002/03/02
631 * @author Wolfram Kriesing <wolfram@kriesing.de>
632 * @param
633 * @return mixed either the data of the requested elements or an Tree_Error
634 */
635 function getPath( $id )
636 {
637 // subqueries would be cool :-)
638 $curElement = $this->getElement( $id );
639
640 $query = sprintf( 'SELECT * FROM %s WHERE%s %s<=%s AND %s>=%s ORDER BY %s',
641 $this->table,
642 $this->_getWhereAddOn(),
643 $this->_getColName('left'),
644 $curElement['left'],
645 $this->_getColName('right'),
646 $curElement['right'],
647 $this->_getColName('left') );
648 if (DB::isError( $res = $this->dbh->getAll($query))) {
649 return $this->_throwError( $res->getMessage() , __LINE__ );
650 }
651 return $this->_prepareResults( $res );
652 }
653
654 /**
655 * gets the element to the left, the left visit
656 *
657 * @access public
658 * @version 2002/03/07
659 * @author Wolfram Kriesing <wolfram@kriesing.de>
660 * @param
661 * @return mixed either the data of the requested element or an Tree_Error
662 */
663 function getLeft( $id )
664 {
665 $element = $this->getElement( $id );
666 if( PEAR::isError($element) )
667 return $element;
668
669 $query = sprintf( 'SELECT * FROM %s WHERE%s (%s=%s OR %s=%s)',
670 $this->table,
671 $this->_getWhereAddOn(),
672 $this->_getColName('right'),$element['left']-1,
673 $this->_getColName('left'),$element['left']-1 );
674 if( DB::isError( $res = $this->dbh->getRow($query) ) )
675 {
676 return $this->_throwError( $res->getMessage() , __LINE__ );
677 }
678 return $this->_prepareResult( $res );
679 }
680
681 /**
682 * gets the element to the right, the right visit
683 *
684 * @access public
685 * @version 2002/03/07
686 * @author Wolfram Kriesing <wolfram@kriesing.de>
687 * @param
688 * @return mixed either the data of the requested element or an Tree_Error
689 */
690 function getRight( $id )
691 {
692 $element = $this->getElement( $id );
693 if( PEAR::isError($element) )
694 return $element;
695
696 $query = sprintf( 'SELECT * FROM %s WHERE%s (%s=%s OR %s=%s)',
697 $this->table,
698 $this->_getWhereAddOn(),
699 $this->_getColName('left'),$element['right']+1,
700 $this->_getColName('right'),$element['right']+1);
701 if( DB::isError( $res = $this->dbh->getRow($query) ) )
702 {
703 return $this->_throwError( $res->getMessage() , __LINE__ );
704 }
705 return $this->_prepareResult( $res );
706 }
707
708 /**
709 * get the parent of the element with the given id
710 *
711 * @access public
712 * @version 2002/04/15
713 * @author Wolfram Kriesing <wolfram@kriesing.de>
714 * @param
715 * @return mixed the array with the data of the parent element
716 * or false, if there is no parent, if the element is the root
717 * or an Tree_Error
718 */
719 function getParent( $id )
720 {
721 $query = sprintf( 'SELECT p.* FROM %s p,%s e WHERE%s e.%s=p.%s AND e.%s=%s',
722 $this->table,$this->table,
723 $this->_getWhereAddOn( ' AND ' , 'p' ),
724 $this->_getColName('parentId'),
725 $this->_getColName('id'),
726 $this->_getColName('id'),
727 $id);
728 if( DB::isError( $res = $this->dbh->getRow($query) ) )
729 {
730 return $this->_throwError( $res->getMessage() , __LINE__ );
731 }
732 return $this->_prepareResult( $res );
733 }
734
735 /**
736 * get the children of the given element
737 * or if the parameter is an array, it gets the children of all
738 * the elements given by their ids in the array
739 *
740 * @access public
741 * @version 2002/04/15
742 * @author Wolfram Kriesing <wolfram@kriesing.de>
743 * @param mixed (1) int the id of one element
744 * (2) array an array of ids for which
745 * the children will be returned
746 * @param integer the children of how many levels shall be returned
747 * @return mixed the array with the data of all children
748 * or false, if there are none
749 */
750 function getChildren($ids,$levels=1)
751 {
752 $res = array();
753 for( $i=1 ; $i<$levels+1 ; $i++ )
754 {
755 // if $ids is an array implode the values
756 $getIds = is_array($ids) ? implode(',',$ids) : $ids;
757
758 $query = sprintf( 'SELECT c.* FROM %s c,%s e WHERE%s e.%s=c.%s AND e.%s IN (%s) '.
759 'ORDER BY c.%s',
760 $this->table,$this->table,
761 $this->_getWhereAddOn( ' AND ' , 'c' ),
762 $this->_getColName('id'),
763 $this->_getColName('parentId'),
764 $this->_getColName('id'),
765 $getIds,
766 // order by left, so we have it in the order as it is in the tree
767 // if no 'order'-option is given
768 $this->getOption('order') ? $this->getOption('order') : $this->_getColName('left')
769 );
770 if (DB::isError($_res = $this->dbh->getAll($query))) {
771 return $this->_throwError( $_res->getMessage() , __LINE__ );
772 }
773 $_res = $this->_prepareResults( $_res );
774
775 // we use the id as the index, to make the use easier esp. for multiple return-values
776 $tempRes = array();
777 foreach ($_res as $aRes) {
778 $tempRes[$aRes[$this->_getColName('id')]] = $aRes;
779 }
780 $_res = $tempRes;
781
782 //
783 if ($levels>1) {
784 $ids = array();
785 foreach( $_res as $aRes )
786 $ids[] = $aRes[$this->_getColName('id')];
787 }
788 $res = array_merge($res,$_res);
789
790 // quit the for-loop if there are no children in the current level
791 if (!sizeof($ids)) {
792 break;
793 }
794 }
795 return $res;
796 }
797
798 /**
799 * get the next element on the same level
800 * if there is none return false
801 *
802 * @access public
803 * @version 2002/04/15
804 * @author Wolfram Kriesing <wolfram@kriesing.de>
805 * @param
806 * @return mixed the array with the data of the next element
807 * or false, if there is no next
808 * or Tree_Error
809 */
810 function getNext( $id )
811 {
812 $query = sprintf( 'SELECT n.* FROM %s n,%s e WHERE%s e.%s=n.%s-1 AND e.%s=n.%s AND e.%s=%s',
813 $this->table,$this->table,
814 $this->_getWhereAddOn( ' AND ' , 'n' ),
815 $this->_getColName('right'),
816 $this->_getColName('left'),
817 $this->_getColName('parentId'),
818 $this->_getColName('parentId'),
819 $this->_getColName('id'),
820 $id);
821 if( DB::isError( $res = $this->dbh->getRow($query) ) )
822 {
823 return $this->_throwError( $res->getMessage() , __LINE__ );
824 }
825 if( !$res )
826 return false;
827 return $this->_prepareResult( $res );
828 }
829
830 /**
831 * get the previous element on the same level
832 * if there is none return false
833 *
834 * @access public
835 * @version 2002/04/15
836 * @author Wolfram Kriesing <wolfram@kriesing.de>
837 * @param
838 * @return mixed the array with the data of the previous element
839 * or false, if there is no previous
840 * or a Tree_Error
841 */
842 function getPrevious( $id )
843 {
844 $query = sprintf( 'SELECT p.* FROM %s p,%s e WHERE%s e.%s=p.%s+1 AND e.%s=p.%s AND e.%s=%s',
845 $this->table,$this->table,
846 $this->_getWhereAddOn( ' AND ' , 'p' ),
847 $this->_getColName('left'),
848 $this->_getColName('right'),
849 $this->_getColName('parentId'),
850 $this->_getColName('parentId'),
851 $this->_getColName('id'),
852 $id);
853 if( DB::isError( $res = $this->dbh->getRow($query) ) )
854 {
855 return $this->_throwError( $res->getMessage() , __LINE__ );
856 }
857 if( !$res )
858 return false;
859 return $this->_prepareResult( $res );
860 }
861
862 /**
863 * returns if $childId is a child of $id
864 *
865 * @abstract
866 * @version 2002/04/29
867 * @access public
868 * @author Wolfram Kriesing <wolfram@kriesing.de>
869 * @param int id of the element
870 * @param int id of the element to check if it is a child
871 * @return boolean true if it is a child
872 */
873 function isChildOf( $id , $childId )
874 {
875 // check simply if the left and right of the child are within the
876 // left and right of the parent, if so it definitly is a child :-)
877 $parent = $this->getElement($id);
878 $child = $this->getElement($childId);
879
880 if( $parent['left'] < $child['left'] &&
881 $parent['right'] > $child['right'] )
882 {
883 return true;
884 }
885
886 return false;
887 } // end of function
888
889 /**
890 * return the maximum depth of the tree
891 *
892 * @version 2003/02/25
893 * @access public
894 * @author Wolfram Kriesing <wolfram@kriesing.de>
895 * @return int the depth of the tree
896 */
897 function getDepth()
898 {
899 // FIXXXME TODO!!!
900 return $this->_throwError( 'not implemented yet' , __LINE__ );
901 }
902
903 /**
904 * Tells if the node with the given ID has children.
905 *
906 * @version 2003/03/04
907 * @access public
908 * @author Wolfram Kriesing <wolfram@kriesing.de>
909 * @param integer the ID of a node
910 * @return boolean if the node with the given id has children
911 */
912 function hasChildren($id)
913 {
914 $element = $this->getElement($id);
915 return $element['right']-$element['left']>1; // if the diff between left and right>1 then there are children
916 }
917
918
919 //
920 // PRIVATE METHODS
921 //
922
923
924 /**
925 *
926 *
927 * @access private
928 * @version 2002/04/20
929 * @author Wolfram Kriesing <wolfram@kriesing.de>
930 * @param string the current where clause
931 * @return
932 */
933 function _getWhereAddOn( $addAfter=' AND ' , $tableName='' )
934 {
935 if( $where=$this->getOption('whereAddOn') )
936 {
937 return ' '.($tableName?$tableName.'.':'')." $where$addAfter ";
938 }
939 return '';
940 }
941
942
943
944
945 // for compatibility to Memory methods
946 function getFirstRoot()
947 {
948 return $this->getRoot();
949 }
950 /**
951 * gets the tree under the given element in one array, sorted
952 * so you can go through the elements from begin to end and list them
953 * as they are in the tree, where every child (until the deepest) is retreived
954 *
955 * @see &_getNode()
956 * @access public
957 * @version 2001/12/17
958 * @author Wolfram Kriesing <wolfram@kriesing.de>
959 * @param integer $startId the id where to start walking
960 * @param integer $depth this number says how deep into the structure the elements shall be retreived
961 * @return array sorted as listed in the tree
962 */
963 function &getNode( $startId=0 , $depth=0 )
964 {
965 }
966
967 }
968 ?>

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