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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

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: |
17     // +----------------------------------------------------------------------+
18     //
19 joko 1.2 // $Id: DBnested.php,v 1.16 2003/03/04 18:57:18 cain Exp $
20 joko 1.1
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 joko 1.2 if ($newPrevId) {
393     if ($newPrevId==$idToMove) { // dont let people move an element behind itself, tell it succeeded, since it already is there :-)
394 joko 1.1 return true;
395     }
396 joko 1.2 if (PEAR::isError($newPrevious=$this->getElement($newPrevId))) {
397 joko 1.1 return $newPrevious;
398 joko 1.2 }
399 joko 1.1 $newParentId = $newPrevious['parentId'];
400 joko 1.2 } else {
401     if ($newParentId==0) {
402 joko 1.1 return $this->_throwError( 'no parent id given' , __LINE__ );
403     }
404 joko 1.2 if ($this->isChildOf($idToMove,$newParentId)) { // if the element shall be moved under one of its children, return false
405 joko 1.1 return $this->_throwError( 'can not move an element under one of its children' , __LINE__ );
406     }
407 joko 1.2 if ($newParentId==$idToMove) { // dont do anything to let an element be moved under itself, which is bullshit
408 joko 1.1 return true;
409     }
410 joko 1.2 if (PEAR::isError($newParent=$this->getElement($newParentId))) { // try to retreive the data of the parent element
411 joko 1.1 return $newParent;
412     }
413     }
414    
415 joko 1.2 if (PEAR::isError($element=$this->getElement($idToMove))) { // get the data of the element itself
416 joko 1.1 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 joko 1.2 if (PEAR::isError($err=$this->_add( $prevVisited , $numberOfElements ))) {
426 joko 1.1 // FIXXME rollback
427     //$this->dbh->rollback();
428     return $err;
429     }
430    
431     // update the parentId of the element with $idToMove
432 joko 1.2 if (PEAR::isError($err=$this->update($idToMove,array('parentId'=>$newParentId)))) {
433 joko 1.1 // 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 joko 1.2 if ($newPrevId) {
445     if (PEAR::isError($temp = $this->getElement( $newPrevId ))) {
446 joko 1.1 // FIXXME rollback
447     //$this->dbh->rollback();
448     return $temp;
449     }
450     $calcWith = $temp['right'];
451 joko 1.2 } else {
452     if (PEAR::isError($temp=$this->getElement($newParentId))) {
453 joko 1.1 // 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 joko 1.2 if (PEAR::isError($element=$this->getElement($idToMove))) {
462 joko 1.1 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 joko 1.2 if (DB::isError($res=$this->dbh->query($query))) {
478 joko 1.1 // 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 joko 1.2 if (PEAR::isError($err=$this->_remove($element))) {
485 joko 1.1 // 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 joko 1.2 function update($id,$newValues)
505 joko 1.1 {
506     // jsut to be sure nothing gets screwed up :-)
507 joko 1.2 unset($newValues[$this->_getColName('left')]);
508     unset($newValues[$this->_getColName('right')]);
509     unset($newValues[$this->_getColName('parentId')]);
510 joko 1.1
511     // updating _one_ element in the tree
512     $values = array();
513 joko 1.2 foreach ($newValues as $key=>$value) {
514 joko 1.1 $values[] = $this->_getColName($key).'='.$this->dbh->quote($value);
515 joko 1.2 }
516 joko 1.1 $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 joko 1.2 if (DB::isError( $res=$this->dbh->query($query))) {
523 joko 1.1 // 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 joko 1.2 function getChild($id)
605 joko 1.1 {
606     // subqueries would be cool :-)
607     $curElement = $this->getElement( $id );
608 joko 1.2 if (PEAR::isError($curElement)) {
609 joko 1.1 return $curElement;
610 joko 1.2 }
611 joko 1.1
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 joko 1.2 if (DB::isError( $res = $this->dbh->getRow($query))) {
618 joko 1.1 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 joko 1.2 if (DB::isError( $res = $this->dbh->getAll($query))) {
649 joko 1.1 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 joko 1.2 function getChildren($ids,$levels=1)
751 joko 1.1 {
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 joko 1.2 if (DB::isError($_res = $this->dbh->getAll($query))) {
771 joko 1.1 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 joko 1.2 foreach ($_res as $aRes) {
778 joko 1.1 $tempRes[$aRes[$this->_getColName('id')]] = $aRes;
779     }
780     $_res = $tempRes;
781    
782     //
783 joko 1.2 if ($levels>1) {
784 joko 1.1 $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 joko 1.2 if (!sizeof($ids)) {
792 joko 1.1 break;
793 joko 1.2 }
794 joko 1.1 }
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 joko 1.2 $parent = $this->getElement($id);
878     $child = $this->getElement($childId);
879 joko 1.1
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 joko 1.2 /**
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 joko 1.1
903 joko 1.2 /**
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 joko 1.1
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