/[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.1 - (show annotations)
Thu Feb 27 16:49:58 2003 UTC (21 years, 6 months ago) by joko
Branch: MAIN
+ initial commit, from PEAR

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

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