root/trunk/lib/AkActiveRecord/AkActsAsBehaviours/AkActsAsNestedSet.php

Revision 1399, 26.6 kB (checked in by bermi, 1 year ago)

When loading associations using load(), it will always return false is no associates are found.

Converting most Active record files to vanilla PHP5

Line 
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4 // +----------------------------------------------------------------------+
5 // | Akelos Framework - http://www.akelos.org                             |
6 // +----------------------------------------------------------------------+
7 // | Copyright (c) 2002-2006, Akelos Media, S.L.  & Bermi Ferrer Martinez |
8 // | Released under the GNU Lesser General Public License, see LICENSE.txt|
9 // +----------------------------------------------------------------------+
10
11 /**
12 * @package ActiveRecord
13 * @subpackage Behaviours
14  * @author Bermi Ferrer <bermi a.t akelos c.om>
15  * @author Jean-Christophe Michel, Symétrie
16  * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
17  * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
18  */
19
20 require_once(AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkObserver.php');
21
22 class AkActsAsNestedSet extends AkObserver
23 {
24
25     /**
26     * This acts provides Nested Set functionality.  Nested Set is similiar to Tree, but with
27     * the added feature that you can select the children and all of it's descendants with
28     * a single query.  A good use case for this is a threaded post system, where you want
29     * to display every reply to a comment without multiple selects.
30     *
31     * A google search for "Nested Set" should point you in the direction to explain the
32     * data base theory.  I figured a bunch of this from
33     * http://threebit.net/tutorials/nestedset/tutorial1.html
34     *
35     * Instead of picturing a leaf node structure with child pointing back to their parent,
36     * the best way to imagine how this works is to think of the parent entity surrounding all
37     * of it's children, and it's parent surrounding it, etc.  Assuming that they are lined up
38     * horizontally, we store the left and right boundaries in the database.
39     *
40     * Imagine:
41     *   root
42     *     |_ Child 1
43     *       |_ Child 1.1
44     *       |_ Child 1.2
45     *     |_ Child 2
46     *       |_ Child 2.1
47     *       |_ Child 2.2
48     *
49     * If my circles in circles description didn't make sense, check out this sweet
50     * ASCII art:
51     *
52     *     ___________________________________________________________________
53     *    |  Root                                                             |
54     *    |    ____________________________    ____________________________   |
55     *    |   |  Child 1                  |   |  Child 2                  |   |
56     *    |   |   __________   _________  |   |   __________   _________  |   |
57     *    |   |  |  C 1.1  |  |  C 1.2 |  |   |  |  C 2.1  |  |  C 2.2 |  |   |
58     *    1   2  3_________4  5________6  7   8  9_________10 11_______12 13  14
59     *    |   |___________________________|   |___________________________|   |
60     *    |___________________________________________________________________|
61     *
62     * The numbers represent the left and right boundaries.  The table them might
63     * look like this:
64     *    ID | PARENT | LEFT | RIGHT | DATA
65     *     1 |      0 |    1 |    14 | root
66     *     2 |      1 |    2 |     7 | Child 1
67     *     3 |      2 |    3 |     4 | Child 1.1
68     *     4 |      2 |    5 |     6 | Child 1.2
69     *     5 |      1 |    8 |    13 | Child 2
70     *     6 |      5 |    9 |    10 | Child 2.1
71     *     7 |      5 |   11 |    12 | Child 2.2
72     *
73     * So, to get all children of an entry, you
74     *     SELECT * WHERE CHILD.LEFT IS BETWEEN PARENT.LEFT AND PARENT.RIGHT
75     *
76     * To get the count, it's (LEFT - RIGHT + 1)/2, etc.
77     *
78     * To get the direct parent, it falls back to using the PARENT_ID field.
79     *
80     * There are instance methods for all of these.
81     *
82     * The structure is good if you need to group things together; the downside is that
83     * keeping data integrity is a pain, and both adding and removing and entry
84     * require a full table write.
85     *
86     * This sets up a beforeDestroy() trigger to prune the tree correctly if one of it's
87     * elements gets deleted.
88     *
89     */
90
91     /**
92     * Configuration options are:
93     *
94     * * +parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
95     * * +left_column+ - column name for left boundary data, default "lft"
96     * * +right_column+ - column name for right boundary data, default "rgt"
97     * * +scope+ - restricts what is to be considered a list.
98     *   Example: <tt>actsAsList(array('scope' => array('todo_list_id = ? AND completed = 0',$todo_list_id)));</tt>
99     */
100
101     public $_scope_condition;
102     public $_left_column_name = 'lft';
103     public $_right_column_name = 'rgt';
104     public $_parent_column_name = 'parent_id';
105
106     public $_ActiveRecordInstance;
107
108     public function AkActsAsNestedSet(&$ActiveRecordInstance)
109     {
110         $this->_ActiveRecordInstance =& $ActiveRecordInstance;
111     }
112
113     public function init($options = array())
114     {
115         empty($options['parent_column']) ? null : ($this->_parent_column_name = $options['parent_column']);
116         empty($options['left_column']) ? null : ($this->_left_column_name = $options['left_column']);
117         empty($options['right_column']) ? null : ($this->_right_column_name = $options['right_column']);
118         empty($options['scope']) ? null : $this->setScopeCondition($options['scope']);
119         return $this->_ensureIsActiveRecordInstance($this->_ActiveRecordInstance);
120     }
121
122
123     public function _ensureIsActiveRecordInstance(&$ActiveRecordInstance)
124     {
125         if(is_object($ActiveRecordInstance) && method_exists($ActiveRecordInstance,'actsLike')){
126             $this->_ActiveRecordInstance =& $ActiveRecordInstance;
127             if(!$this->_ActiveRecordInstance->hasColumn($this->_parent_column_name) || !$this->_ActiveRecordInstance->hasColumn($this->_left_column_name) || !$this->_ActiveRecordInstance->hasColumn($this->_right_column_name)){
128                 trigger_error(Ak::t(
129                 'The following columns are required in the table "%table" for the model "%model" to act as a Nested Set: "%columns".',array(
130                 '%columns'=>$this->getParentColumnName().', '.$this->getLeftColumnName().', '.$this->getRightColumnName(),'%table'=>$this->_ActiveRecordInstance->getTableName(),'%model'=>$this->_ActiveRecordInstance->getModelName())),E_USER_ERROR);
131                 unset($this->_ActiveRecordInstance->nested_set);
132                 return false;
133             }else{
134                 $this->observe(&$ActiveRecordInstance);
135             }
136         }else{
137             trigger_error(Ak::t('You are trying to set an object that is not an active record.'), E_USER_ERROR);
138             return false;
139         }
140         return true;
141     }
142
143     public function getType()
144     {
145         return 'nested set';
146     }
147
148     public function getScopeCondition()
149     {
150         if (!empty($this->variable_scope_condition)){
151             return $this->_ActiveRecordInstance->_getVariableSqlCondition($this->variable_scope_condition);
152
153             // True condition in case we don't have a scope
154         }elseif(empty($this->scope_condition) && empty($this->scope)){
155             $this->scope_condition = ($this->_ActiveRecordInstance->_db->type() == 'postgre') ? 'true' : '1';
156         }elseif (!empty($this->scope)){
157             $this->setScopeCondition(join(' AND ',array_map(array(&$this,'getScopedColumn'),(array)$this->scope)));
158         }
159         return  $this->scope_condition;
160     }
161
162
163     public function setScopeCondition($scope_condition)
164     {
165         if(!is_array($scope_condition) && strstr($scope_condition, '?')){
166             $this->variable_scope_condition = $scope_condition;
167         }else{
168             $this->scope_condition  = $scope_condition;
169         }
170     }
171
172     public function getScopedColumn($column)
173     {
174         if($this->_ActiveRecordInstance->hasColumn($column)){
175             $value = $this->_ActiveRecordInstance->get($column);
176             $condition = $this->_ActiveRecordInstance->getAttributeCondition($value);
177             $value = $this->_ActiveRecordInstance->castAttributeForDatabase($column, $value);
178             return $column.' '.str_replace('?', $value, $condition);
179         }else{
180             return $column;
181         }
182     }
183
184     public function getLeftColumnName()
185     {
186         return $this->_left_column_name;
187     }
188     public function setLeftColumnName($left_column_name)
189     {
190         $this->_left_column_name = $left_column_name;
191     }
192
193     public function getRightColumnName()
194     {
195         return $this->_right_column_name;
196     }
197     public function setRightColumnName($right_column_name)
198     {
199         $this->_right_column_name = $right_column_name;
200     }
201
202
203     public function getParentColumnName()
204     {
205         return $this->_parent_column_name;
206     }
207     public function setParentColumnName($parent_column_name)
208     {
209         $this->_parent_column_name = $parent_column_name;
210     }
211
212     /**
213     * Returns true is this is a root node.
214     */
215     public function isRoot()
216     {
217         $left_id = $this->_ActiveRecordInstance->get($this->getLeftColumnName());
218         return ($this->_ActiveRecordInstance->get($this->getParentColumnName()) == null) && ($left_id == 1) && ($this->_ActiveRecordInstance->get($this->getRightColumnName()) > $left_id);
219     }
220
221     /**
222     * Returns true is this is a child node
223     */
224     public function isChild()
225     {
226         $parent_id = $this->_ActiveRecordInstance->get($this->getParentColumnName());
227         $left_id = $this->_ActiveRecordInstance->get($this->getLeftColumnName());
228         return !($parent_id == 0 || is_null($parent_id)) && ($left_id > 1) && ($this->_ActiveRecordInstance->get($this->getRightColumnName()) > $left_id);
229     }
230
231     /**
232     * Returns true if we have no idea what this is
233     */
234     public function isUnknown()
235     {
236         return !$this->isRoot() && !$this->isChild();
237     }
238
239     /**
240     * Added a child to this object in the tree.  If this object hasn't been initialized,
241     * it gets set up as a root node.  Otherwise, this method will update all of the
242     * other elements in the tree and shift them to the right. Keeping everything
243     * balanced.
244     */
245     public function addChild( &$child )
246     {
247         $self =& $this->_ActiveRecordInstance;
248         $self->reload();
249         $child->reload();
250         $left_column = $this->getLeftColumnName();
251         $right_column = $this->getRightColumnName();
252         $parent_column = $this->getParentColumnName();
253
254         if ($child->nested_set->isRoot()){
255             trigger_error(Ak::t("Adding sub-tree isn't currently supported"),E_USER_ERROR);
256         }elseif ( (is_null($self->get($left_column))) || (is_null($self->get($right_column))) ){
257             // Looks like we're now the root node!  Woo
258             $self->set($left_column, 1);
259             $self->set($right_column, 4);
260
261             $self->transactionStart();
262             // What do to do about validation?
263             if(!$self->save()){
264                 $self->transactionFail();
265                 $self->transactionComplete();
266                 return false;
267             }
268
269             $child->set($parent_column, $self->getId());
270             $child->set($left_column, 2);
271             $child->set($right_column, 3);
272
273             if(!$child->save()){
274                 $self->transactionFail();
275                 $self->transactionComplete();
276                 return false;
277             }
278             $self->transactionComplete();
279             return $child;
280         }else{
281             // OK, we need to add and shift everything else to the right
282             $child->set($parent_column, $self->getId());
283             $right_bound = $self->get($right_column);
284             $child->set($left_column, $right_bound);
285             $child->set($right_column, $right_bound +1);
286             $self->set($right_column, $self->get($right_column) + 2);
287
288             $self->transactionStart();
289             $self->updateAll( "$left_column = ($left_column + 2)"$this->getScopeCondition()." AND $left_column >= $right_bound" );
290             $self->updateAll( "$right_column = ($right_column + 2)"$this->getScopeCondition()." AND $right_column >= $right_bound" );
291             $self->save();
292             $child->save();
293             $self->transactionComplete();
294             return $child;
295         }
296     }
297
298
299     /**
300     * Returns the parent Object
301     */
302     public function &getParent()
303     {
304         if(!$this->isChild()){
305             $result = false;
306         }else{
307             $result =& $this->_ActiveRecordInstance->find(
308             // str_replace(array_keys($options['conditions']), array_values($this->getSanitizedConditionsArray($options['conditions'])),$pattern);
309             'first', array('conditions' => " ".$this->getScopeCondition()." AND ".$this->_ActiveRecordInstance->getPrimaryKey()." = ".$this->_ActiveRecordInstance->{$this->getParentColumnName()})
310             );
311         }
312         return $result;
313     }
314
315     /**
316     * Returns an array of parent Objects this is usefull to make breadcrum like stuctures
317     */
318     public function &getParents()
319     {
320         $Ancestors =& $this->getAncestors();
321         return $Ancestors;
322     }
323
324
325     /**
326     * Prunes a branch off of the tree, shifting all of the elements on the right
327     * back to the left so the counts still work.
328     */
329     public function beforeDestroy(&$object)
330     {
331         if(!empty($object->__avoid_nested_set_before_destroy_recursion)){
332             return true;
333         }
334         if((empty($object->{$this->getRightColumnName()}) || empty($object->{$this->getLeftColumnName()})) || $object->nested_set->isUnknown()){
335             return true;
336         }
337         $dif = $object->{$this->getRightColumnName()} - $object->{$this->getLeftColumnName()} + 1;
338
339         $ObjectsToDelete = $object->nested_set->getAllChildren();
340
341         $object->transactionStart();
342
343         if(!empty($ObjectsToDelete)){
344             foreach (array_keys($ObjectsToDelete) as $k){
345                 $Child =& $ObjectsToDelete[$k];
346                 $Child->__avoid_nested_set_before_destroy_recursion = true;
347                 if($Child->beforeDestroy()){
348                     if($Child->notifyObservers('beforeDestroy') === false){
349                         $Child->transactionFail();
350                     }
351                 }else{
352                     $Child->transactionFail();
353                 }
354             }
355         }
356
357         $object->deleteAll($this->getScopeCondition().
358         " AND ".$this->getLeftColumnName()." > ".$object->{$this->getLeftColumnName()}.
359         " AND ".$this->getRightColumnName()." < ".$object->{$this->getRightColumnName()});
360
361         $object->updateAll($this->getLeftColumnName()." = (".$this->getLeftColumnName()." - $dif)",
362         $this->getScopeCondition()." AND ".$this->getLeftColumnName()." >= ".$object->{$this->getRightColumnName()} );
363
364         $object->updateAll($this->getRightColumnName()." = (".$this->getRightColumnName()." - $dif )",
365         $this->getScopeCondition()." AND ".$this->getRightColumnName()." >= ".$object->{$this->getRightColumnName()});
366
367
368         if(!empty($ObjectsToDelete)){
369             foreach (array_keys($ObjectsToDelete) as $k){
370                 $Child =& $ObjectsToDelete[$k];
371                 $Child->__avoid_nested_set_before_destroy_recursion = true;
372                 if(!$Child->afterDestroy() || $Child->notifyObservers('afterDestroy') === false){
373                     $Child->transactionFail();
374                 }
375             }
376         }
377
378         if($object->transactionHasFailed()){
379             $object->transactionComplete();
380             return false;
381         }
382         $object->transactionComplete();
383
384         return true;
385     }
386
387     /**
388      * on creation, set automatically lft and rgt to the end of the tree
389      */
390     public function beforeCreate(&$object)
391     {
392         $object->nested_set->_setLeftAndRightToTheEndOfTheTree();
393         return true;
394     }
395
396     public function _setLeftAndRightToTheEndOfTheTree()
397     {
398         $left = $this->getLeftColumnName();
399         $right = $this->getRightColumnName();
400
401         $maxright = $this->_ActiveRecordInstance->maximum($right, array('conditions'=>$this->getScopeCondition()));
402         $maxright = empty($maxright) ? 0 : $maxright;
403
404         $this->_ActiveRecordInstance->set($left, $maxright+1);
405         $this->_ActiveRecordInstance->set($right, $maxright+2);
406     }
407
408     /**
409      * Returns the single root
410      */
411     public function getRoot()
412     {
413         return $this->_ActiveRecordInstance->find('first', array('conditions' => " ".$this->getScopeCondition()." AND ".$this->getParentColumnName()." IS NULL "));
414     }
415
416     /**
417      * Returns roots when multiple roots (or virtual root, which is the same)
418      */
419     public function getRoots()
420     {
421         return $this->_ActiveRecordInstance->find('all', array('conditions' => " ".$this->getScopeCondition()." AND ".$this->getParentColumnName()." IS NULL ",'order' => $this->getLeftColumnName()));
422     }
423
424
425     /**
426      * Returns an array of all parents
427      */
428     public function &getAncestors()
429     {
430         $Ancestors =& $this->_ActiveRecordInstance->find('all', array('conditions' => ' '.$this->getScopeCondition().' AND '.
431         $this->getLeftColumnName().' < '.$this->_ActiveRecordInstance->get($this->getLeftColumnName()).' AND '.
432         $this->getRightColumnName().' > '.$this->_ActiveRecordInstance->get($this->getRightColumnName())
433         ,'order' => $this->getLeftColumnName()));
434         return $Ancestors;
435     }
436
437     /**
438      * Returns the array of all parents and self
439      */
440     public function &getSelfAndAncestors()
441     {
442         if($result =& $this->getAncestors()){
443             array_push($result, $this->_ActiveRecordInstance);
444         }else{
445             $result = array(&$this->_ActiveRecordInstance);
446         }
447         return $result;
448     }
449
450
451     /**
452      * Returns the array of all children of the parent, except self
453      */
454     public function getSiblings($search_for_self = false)
455     {
456         return $this->_ActiveRecordInstance->find('all', array('conditions' => ' (('.$this->getScopeCondition().' AND '.
457         $this->getParentColumnName().' = '.$this->_ActiveRecordInstance->get($this->getParentColumnName()).' AND '.
458         $this->_ActiveRecordInstance->getPrimaryKey().' <> '.$this->_ActiveRecordInstance->getId().
459         ($search_for_self&&!$this->_ActiveRecordInstance->isNewRecord()?') OR ('.$this->_ActiveRecordInstance->getPrimaryKey().' = '.$this->_ActiveRecordInstance->quotedId().'))':'))')
460         ,'order' => $this->getLeftColumnName()));
461     }
462
463     /**
464      * Returns the array of all children of the parent, included self
465      */
466     public function getSelfAndSiblings()
467     {
468         $parent_id = $this->_ActiveRecordInstance->get($this->getParentColumnName());
469         if(empty($parent_id) || !$result = $this->getSiblings(true)){
470             $result = array($this->_ActiveRecordInstance);
471         }
472         return $result;
473     }
474
475
476     /**
477      * Returns the level of this object in the tree
478      * root level is 0
479      */
480     public function getLevel()
481     {
482         $parent_id = $this->_ActiveRecordInstance->get($this->getParentColumnName());
483         if(empty($parent_id)){
484             return 0;
485         }
486         return $this->_ActiveRecordInstance->count(' '.$this->getScopeCondition().' AND '.
487         $this->getLeftColumnName().' < '.$this->_ActiveRecordInstance->get($this->getLeftColumnName()).' AND '.
488         $this->getRightColumnName().' > '.$this->_ActiveRecordInstance->get($this->getRightColumnName()));
489     }
490
491
492     /**
493     * Returns the number of all nested children of this object.
494     */
495     public function countChildren()
496     {
497         $children_count = ($this->_ActiveRecordInstance->get($this->getRightColumnName()) - $this->_ActiveRecordInstance->get($this->getLeftColumnName()) - 1)/2;
498         return $children_count > 0 ? $children_count : 0;
499     }
500
501
502     /**
503      * Returns a set of only this entry's immediate children
504      */
505     public function getChildren()
506     {
507         return $this->_ActiveRecordInstance->find('all', array('conditions' => ' '.$this->getScopeCondition().' AND '.
508         $this->getParentColumnName().' = '.$this->_ActiveRecordInstance->getId()
509         ,'order' => $this->getLeftColumnName()));
510     }
511
512     /**
513      * Returns a set of all of its children and nested children
514      */
515     public function getAllChildren()
516     {
517         $args = func_get_args();
518         $excluded_ids = array();
519         if(!empty($args)){
520             $exclude = count($args) > 1 ? $args : (is_array($args[0]) ? $args[0] : (empty($args[0]) ? false : array($args[0])));
521             if(!empty($exclude)){
522                 $parent_class_name = get_class($this->_ActiveRecordInstance);
523                 foreach (array_keys($exclude) as $k){
524                     $Item =& $exclude[$k];
525                     if(is_a($Item,$parent_class_name)){
526                         $ItemToExclude =& $Item;
527                     }else{
528                         $ItemToExclude =& $this->_ActiveRecordInstance->find($Item);
529                     }
530                     if($ItemSet = $ItemToExclude->nested_set->getFullSet()){
531                         foreach (array_keys($ItemSet) as $l){
532                             $excluded_ids[] = $ItemSet[$l]->getId();
533                         }
534                     }
535                 }
536                 $excluded_ids = array_unique(array_diff($excluded_ids,array('')));
537             }
538         }
539         return $this->_ActiveRecordInstance->find('all', array('conditions' => ' '.$this->getScopeCondition().' AND '.
540         (empty($excluded_ids) ? '' : ' id NOT IN ('.join(',',$excluded_ids).') AND ').
541         $this->getLeftColumnName().' > '.$this->_ActiveRecordInstance->get($this->getLeftColumnName()).' AND '.
542         $this->getRightColumnName().' < '.$this->_ActiveRecordInstance->get($this->getRightColumnName())
543         ,'order' => $this->getLeftColumnName()));
544     }
545
546     /**
547      * Returns a set of itself and all of its nested children
548      */
549     public function getFullSet($exclude = null)
550     {
551         if($this->_ActiveRecordInstance->isNewRecord() || $this->_ActiveRecordInstance->get($this->getRightColumnName()) - $this->_ActiveRecordInstance->get($this->getLeftColumnName()) == 1 ){
552             $result = array($this->_ActiveRecordInstance);
553         }else{
554         (array)$result = $this->getAllChildren($exclude);
555         array_unshift($result, $this->_ActiveRecordInstance);
556         }
557         return $result;
558     }
559
560
561     /**
562      * Move the node to the left of another node
563      */
564     public function moveToLeftOf($node)
565     {
566         return $this->moveTo($node, 'left');
567     }
568
569     /**
570      * Move the node to the left of another node
571      */
572     public function moveToRightOf($node)
573     {
574         return $this->moveTo($node, 'right');
575     }
576
577     /**
578      * Move the node to the child of another node
579      */
580     public function moveToChildOf($node)
581     {
582         return $this->moveTo($node, 'child');
583     }
584
585     public function moveTo($target, $position)
586     {
587         if($this->_ActiveRecordInstance->isNewRecord()){
588             trigger_error(Ak::t('You cannot move a new node'), E_USER_ERROR);
589         }
590         $current_left = $this->_ActiveRecordInstance->get($this->getLeftColumnName());
591         $current_right = $this->_ActiveRecordInstance->get($this->getRightColumnName());
592         // $extent is the width of the tree self and children
593         $extent = $current_right - $current_left + 1;
594
595         // load object if node is not an object
596         if (is_numeric($target)){
597             $target =& $this->_ActiveRecordInstance->find($target);
598         }
599         if(!$target || !is_a($target, get_class($this->_ActiveRecordInstance))){
600             trigger_error(Ak::t('Invalid target'), E_USER_NOTICE);
601             return false;
602         }
603
604         $target_left = $target->get($this->getLeftColumnName());
605         $target_right = $target->get($this->getRightColumnName());
606
607
608         // detect impossible move
609         if ((($current_left <= $target_left) && ($target_left <= $current_right)) || (($current_left <= $target_right) && ($target_right <= $current_right))){
610             trigger_error(Ak::t('Impossible move, target node cannot be inside moved tree.'), E_USER_ERROR);
611         }
612
613         // compute new left/right for self
614         if ($position == 'child'){
615             if ($target_left < $current_left){
616                 $new_left  = $target_left + 1;
617                 $new_right = $target_left + $extent;
618             }else{
619                 $new_left  = $target_left - $extent + 1;
620                 $new_right = $target_left;
621             }
622         }elseif($position == 'left'){
623             if ($target_left < $current_left){
624                 $new_left  = $target_left;
625                 $new_right = $target_left + $extent - 1;
626             }else{
627                 $new_left  = $target_left - $extent;
628                 $new_right = $target_left - 1;
629             }
630         }elseif($position == 'right'){
631             if ($target_right < $current_right){
632                 $new_left  = $target_right + 1;
633                 $new_right = $target_right + $extent;
634             }else{
635                 $new_left  = $target_right - $extent + 1;
636                 $new_right = $target_right;
637             }
638         }else{
639             trigger_error(Ak::t("Position should be either left or right ('%position' received).",array('%position'=>$position)), E_USER_ERROR);
640         }
641
642         // boundaries of update action
643         $left_boundary = min($current_left, $new_left);
644         $right_boundary = max($current_right, $new_right);
645
646         // Shift value to move self to new $position
647         $shift = $new_left - $current_left;
648
649         // Shift value to move nodes inside boundaries but not under self_and_children
650         $updown = ($shift > 0) ? -$extent : $extent;
651
652         // change null to NULL for new parent
653         if($position == 'child'){
654             $new_parent = $target->getId();
655         }else{
656             $target_parent = $target->get($this->getParentColumnName());
657             $new_parent = empty($target_parent) ? 'NULL' : $target_parent;
658         }
659
660         $this->_ActiveRecordInstance->updateAll(
661
662         $this->getLeftColumnName().' = CASE '.
663         'WHEN '.$this->getLeftColumnName().' BETWEEN '.$current_left.' AND '.$current_right.' '.
664         'THEN '.$this->getLeftColumnName().' + '.$shift.' '.
665         'WHEN '.$this->getLeftColumnName().' BETWEEN '.$left_boundary.' AND '.$right_boundary.' '.
666         'THEN '.$this->getLeftColumnName().' + '.$updown.' '.
667         'ELSE '.$this->getLeftColumnName().' END, '.
668
669         $this->getRightColumnName().' = CASE '.
670         'WHEN '.$this->getRightColumnName().' BETWEEN '.$current_left.' AND '.$current_right.' '.
671         'THEN '.$this->getRightColumnName().' + '.$shift.' '.
672         'WHEN '.$this->getRightColumnName().' BETWEEN '.$left_boundary.' AND '.$right_boundary.' '.
673         'THEN '.$this->getRightColumnName().' + '.$updown.' '.
674         'ELSE '.$this->getRightColumnName().' END, '.
675
676         $this->getParentColumnName().' = CASE '.
677         'WHEN '.$this->_ActiveRecordInstance->getPrimaryKey().' = '.$this->_ActiveRecordInstance->getId().' '.
678         'THEN '.$new_parent.' '.
679         'ELSE '.$this->getParentColumnName().' END',
680
681         $this->getScopeCondition() );
682         $this->_ActiveRecordInstance->reload();
683
684         return true;
685     }
686
687 }
688
689
690 ?>
Note: See TracBrowser for help on using the browser.