| [ Index ] |
PHP Cross Reference of Akelos Framework |
[Summary view] [Print] [Text view]
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 var $_scope_condition; 102 var $_left_column_name = 'lft'; 103 var $_right_column_name = 'rgt'; 104 var $_parent_column_name = 'parent_id'; 105 106 var $_ActiveRecordInstance; 107 108 function AkActsAsNestedSet(&$ActiveRecordInstance) 109 { 110 $this->_ActiveRecordInstance =& $ActiveRecordInstance; 111 } 112 113 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 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 function reloadActiveRecordInstance(&$nodeInstance) 144 { 145 AK_PHP5 ? null : $nodeInstance->nested_set->_ensureIsActiveRecordInstance($nodeInstance); 146 } 147 148 function getType() 149 { 150 return 'nested set'; 151 } 152 153 function getScopeCondition() 154 { 155 if (!empty($this->variable_scope_condition)){ 156 return $this->_ActiveRecordInstance->_getVariableSqlCondition($this->variable_scope_condition); 157 158 // True condition in case we don't have a scope 159 }elseif(empty($this->scope_condition) && empty($this->scope)){ 160 $this->scope_condition = ($this->_ActiveRecordInstance->_db->type() == 'postgre') ? 'true' : '1'; 161 }elseif (!empty($this->scope)){ 162 $this->setScopeCondition(join(' AND ',array_map(array(&$this,'getScopedColumn'),(array)$this->scope))); 163 } 164 return $this->scope_condition; 165 } 166 167 168 function setScopeCondition($scope_condition) 169 { 170 if(!is_array($scope_condition) && strstr($scope_condition, '?')){ 171 $this->variable_scope_condition = $scope_condition; 172 }else{ 173 $this->scope_condition = $scope_condition; 174 } 175 } 176 177 function getScopedColumn($column) 178 { 179 if($this->_ActiveRecordInstance->hasColumn($column)){ 180 $value = $this->_ActiveRecordInstance->get($column); 181 $condition = $this->_ActiveRecordInstance->getAttributeCondition($value); 182 $value = $this->_ActiveRecordInstance->castAttributeForDatabase($column, $value); 183 return $column.' '.str_replace('?', $value, $condition); 184 }else{ 185 return $column; 186 } 187 } 188 189 function getLeftColumnName() 190 { 191 return $this->_left_column_name; 192 } 193 function setLeftColumnName($left_column_name) 194 { 195 $this->_left_column_name = $left_column_name; 196 } 197 198 function getRightColumnName() 199 { 200 return $this->_right_column_name; 201 } 202 function setRightColumnName($right_column_name) 203 { 204 $this->_right_column_name = $right_column_name; 205 } 206 207 208 function getParentColumnName() 209 { 210 return $this->_parent_column_name; 211 } 212 function setParentColumnName($parent_column_name) 213 { 214 $this->_parent_column_name = $parent_column_name; 215 } 216 217 /** 218 * Returns true is this is a root node. 219 */ 220 function isRoot() 221 { 222 $left_id = $this->_ActiveRecordInstance->get($this->getLeftColumnName()); 223 return ($this->_ActiveRecordInstance->get($this->getParentColumnName()) == null) && ($left_id == 1) && ($this->_ActiveRecordInstance->get($this->getRightColumnName()) > $left_id); 224 } 225 226 /** 227 * Returns true is this is a child node 228 */ 229 function isChild() 230 { 231 $parent_id = $this->_ActiveRecordInstance->get($this->getParentColumnName()); 232 $left_id = $this->_ActiveRecordInstance->get($this->getLeftColumnName()); 233 return !($parent_id == 0 || is_null($parent_id)) && ($left_id > 1) && ($this->_ActiveRecordInstance->get($this->getRightColumnName()) > $left_id); 234 } 235 236 /** 237 * Returns true if we have no idea what this is 238 */ 239 function isUnknown() 240 { 241 return !$this->isRoot() && !$this->isChild(); 242 } 243 244 /** 245 * Added a child to this object in the tree. If this object hasn't been initialized, 246 * it gets set up as a root node. Otherwise, this method will update all of the 247 * other elements in the tree and shift them to the right. Keeping everything 248 * balanced. 249 */ 250 function addChild( &$child ) 251 { 252 $self =& $this->_ActiveRecordInstance; 253 $self->reload(); 254 $child->reload(); 255 $left_column = $this->getLeftColumnName(); 256 $right_column = $this->getRightColumnName(); 257 $parent_column = $this->getParentColumnName(); 258 259 if ($child->nested_set->isRoot()){ 260 trigger_error(Ak::t("Adding sub-tree isn't currently supported"),E_USER_ERROR); 261 }elseif ( (is_null($self->get($left_column))) || (is_null($self->get($right_column))) ){ 262 // Looks like we're now the root node! Woo 263 $self->set($left_column, 1); 264 $self->set($right_column, 4); 265 266 $self->transactionStart(); 267 // What do to do about validation? 268 if(!$self->save()){ 269 $self->transactionFail(); 270 $self->transactionComplete(); 271 return false; 272 } 273 274 $child->set($parent_column, $self->getId()); 275 $child->set($left_column, 2); 276 $child->set($right_column, 3); 277 278 if(!$child->save()){ 279 $self->transactionFail(); 280 $self->transactionComplete(); 281 return false; 282 } 283 $self->transactionComplete(); 284 return $child; 285 }else{ 286 // OK, we need to add and shift everything else to the right 287 $child->set($parent_column, $self->getId()); 288 $right_bound = $self->get($right_column); 289 $child->set($left_column, $right_bound); 290 $child->set($right_column, $right_bound +1); 291 $self->set($right_column, $self->get($right_column) + 2); 292 293 $self->transactionStart(); 294 $self->updateAll( "$left_column = ($left_column + 2)", $this->getScopeCondition()." AND $left_column >= $right_bound" ); 295 $self->updateAll( "$right_column = ($right_column + 2)", $this->getScopeCondition()." AND $right_column >= $right_bound" ); 296 $self->save(); 297 $child->save(); 298 $this->reloadActiveRecordInstance($child); 299 $self->transactionComplete(); 300 return $child; 301 } 302 } 303 304 305 /** 306 * Returns the parent Object 307 */ 308 function &getParent() 309 { 310 if(!$this->isChild()){ 311 $result = false; 312 }else{ 313 $result =& $this->_ActiveRecordInstance->find( 314 // str_replace(array_keys($options['conditions']), array_values($this->getSanitizedConditionsArray($options['conditions'])),$pattern); 315 'first', array('conditions' => " ".$this->getScopeCondition()." AND ".$this->_ActiveRecordInstance->getPrimaryKey()." = ".$this->_ActiveRecordInstance->{$this->getParentColumnName()}) 316 ); 317 } 318 return $result; 319 } 320 321 /** 322 * Returns an array of parent Objects this is usefull to make breadcrum like stuctures 323 */ 324 function &getParents() 325 { 326 $Ancestors =& $this->getAncestors(); 327 return $Ancestors; 328 } 329 330 331 /** 332 * Prunes a branch off of the tree, shifting all of the elements on the right 333 * back to the left so the counts still work. 334 */ 335 function beforeDestroy(&$object) 336 { 337 if(!empty($object->__avoid_nested_set_before_destroy_recursion)){ 338 return true; 339 } 340 if((empty($object->{$this->getRightColumnName()}) || empty($object->{$this->getLeftColumnName()})) || $object->nested_set->isUnknown()){ 341 return true; 342 } 343 $dif = $object->{$this->getRightColumnName()} - $object->{$this->getLeftColumnName()} + 1; 344 345 $ObjectsToDelete = $object->nested_set->getAllChildren(); 346 347 $object->transactionStart(); 348 349 if(!empty($ObjectsToDelete)){ 350 foreach (array_keys($ObjectsToDelete) as $k){ 351 $Child =& $ObjectsToDelete[$k]; 352 $Child->__avoid_nested_set_before_destroy_recursion = true; 353 if($Child->beforeDestroy()){ 354 if($Child->notifyObservers('beforeDestroy') === false){ 355 $Child->transactionFail(); 356 } 357 }else{ 358 $Child->transactionFail(); 359 } 360 } 361 } 362 363 $object->deleteAll($this->getScopeCondition(). 364 " AND ".$this->getLeftColumnName()." > ".$object->{$this->getLeftColumnName()}. 365 " AND ".$this->getRightColumnName()." < ".$object->{$this->getRightColumnName()}); 366 367 $object->updateAll($this->getLeftColumnName()." = (".$this->getLeftColumnName()." - $dif)", 368 $this->getScopeCondition()." AND ".$this->getLeftColumnName()." >= ".$object->{$this->getRightColumnName()} ); 369 370 $object->updateAll($this->getRightColumnName()." = (".$this->getRightColumnName()." - $dif )", 371 $this->getScopeCondition()." AND ".$this->getRightColumnName()." >= ".$object->{$this->getRightColumnName()}); 372 373 374 if(!empty($ObjectsToDelete)){ 375 foreach (array_keys($ObjectsToDelete) as $k){ 376 $Child =& $ObjectsToDelete[$k]; 377 $Child->__avoid_nested_set_before_destroy_recursion = true; 378 if(!$Child->afterDestroy() || $Child->notifyObservers('afterDestroy') === false){ 379 $Child->transactionFail(); 380 } 381 } 382 } 383 384 if($object->transactionHasFailed()){ 385 $object->transactionComplete(); 386 return false; 387 } 388 $object->transactionComplete(); 389 390 return true; 391 } 392 393 /** 394 * on creation, set automatically lft and rgt to the end of the tree 395 */ 396 function beforeCreate(&$object) 397 { 398 $object->nested_set->_setLeftAndRightToTheEndOfTheTree(); 399 return true; 400 } 401 402 function _setLeftAndRightToTheEndOfTheTree() 403 { 404 $left = $this->getLeftColumnName(); 405 $right = $this->getRightColumnName(); 406 407 $maxright = $this->_ActiveRecordInstance->maximum($right, array('conditions'=>$this->getScopeCondition())); 408 $maxright = empty($maxright) ? 0 : $maxright; 409 410 $this->_ActiveRecordInstance->set($left, $maxright+1); 411 $this->_ActiveRecordInstance->set($right, $maxright+2); 412 } 413 414 /** 415 * Returns the single root 416 */ 417 function getRoot() 418 { 419 return $this->_ActiveRecordInstance->find('first', array('conditions' => " ".$this->getScopeCondition()." AND ".$this->getParentColumnName()." IS NULL ")); 420 } 421 422 /** 423 * Returns roots when multiple roots (or virtual root, which is the same) 424 */ 425 function getRoots() 426 { 427 return $this->_ActiveRecordInstance->find('all', array('conditions' => " ".$this->getScopeCondition()." AND ".$this->getParentColumnName()." IS NULL ",'order' => $this->getLeftColumnName())); 428 } 429 430 431 /** 432 * Returns an array of all parents 433 */ 434 function &getAncestors() 435 { 436 $Ancestors =& $this->_ActiveRecordInstance->find('all', array('conditions' => ' '.$this->getScopeCondition().' AND '. 437 $this->getLeftColumnName().' < '.$this->_ActiveRecordInstance->get($this->getLeftColumnName()).' AND '. 438 $this->getRightColumnName().' > '.$this->_ActiveRecordInstance->get($this->getRightColumnName()) 439 ,'order' => $this->getLeftColumnName())); 440 return $Ancestors; 441 } 442 443 /** 444 * Returns the array of all parents and self 445 */ 446 function &getSelfAndAncestors() 447 { 448 if($result =& $this->getAncestors()){ 449 array_push($result, $this->_ActiveRecordInstance); 450 }else{ 451 $result = array(&$this->_ActiveRecordInstance); 452 } 453 return $result; 454 } 455 456 457 /** 458 * Returns the array of all children of the parent, except self 459 */ 460 function getSiblings($search_for_self = false) 461 { 462 return $this->_ActiveRecordInstance->find('all', array('conditions' => ' (('.$this->getScopeCondition().' AND '. 463 $this->getParentColumnName().' = '.$this->_ActiveRecordInstance->get($this->getParentColumnName()).' AND '. 464 $this->_ActiveRecordInstance->getPrimaryKey().' <> '.$this->_ActiveRecordInstance->getId(). 465 ($search_for_self&&!$this->_ActiveRecordInstance->isNewRecord()?') OR ('.$this->_ActiveRecordInstance->getPrimaryKey().' = '.$this->_ActiveRecordInstance->quotedId().'))':'))') 466 ,'order' => $this->getLeftColumnName())); 467 } 468 469 /** 470 * Returns the array of all children of the parent, included self 471 */ 472 function getSelfAndSiblings() 473 { 474 $parent_id = $this->_ActiveRecordInstance->get($this->getParentColumnName()); 475 if(empty($parent_id) || !$result = $this->getSiblings(true)){ 476 $result = array($this->_ActiveRecordInstance); 477 } 478 return $result; 479 } 480 481 482 /** 483 * Returns the level of this object in the tree 484 * root level is 0 485 */ 486 function getLevel() 487 { 488 $parent_id = $this->_ActiveRecordInstance->get($this->getParentColumnName()); 489 if(empty($parent_id)){ 490 return 0; 491 } 492 return $this->_ActiveRecordInstance->count(' '.$this->getScopeCondition().' AND '. 493 $this->getLeftColumnName().' < '.$this->_ActiveRecordInstance->get($this->getLeftColumnName()).' AND '. 494 $this->getRightColumnName().' > '.$this->_ActiveRecordInstance->get($this->getRightColumnName())); 495 } 496 497 498 /** 499 * Returns the number of all nested children of this object. 500 */ 501 function countChildren() 502 { 503 $children_count = ($this->_ActiveRecordInstance->get($this->getRightColumnName()) - $this->_ActiveRecordInstance->get($this->getLeftColumnName()) - 1)/2; 504 return $children_count > 0 ? $children_count : 0; 505 } 506 507 508 /** 509 * Returns a set of only this entry's immediate children 510 */ 511 function getChildren() 512 { 513 return $this->_ActiveRecordInstance->find('all', array('conditions' => ' '.$this->getScopeCondition().' AND '. 514 $this->getParentColumnName().' = '.$this->_ActiveRecordInstance->getId() 515 ,'order' => $this->getLeftColumnName())); 516 } 517 518 /** 519 * Returns a set of all of its children and nested children 520 */ 521 function getAllChildren() 522 { 523 $args = func_get_args(); 524 $excluded_ids = array(); 525 if(!empty($args)){ 526 $exclude = count($args) > 1 ? $args : (is_array($args[0]) ? $args[0] : (empty($args[0]) ? false : array($args[0]))); 527 if(!empty($exclude)){ 528 $parent_class_name = get_class($this->_ActiveRecordInstance); 529 foreach (array_keys($exclude) as $k){ 530 $Item =& $exclude[$k]; 531 if(is_a($Item,$parent_class_name)){ 532 $ItemToExclude =& $Item; 533 }else{ 534 $ItemToExclude =& $this->_ActiveRecordInstance->find($Item); 535 } 536 if($ItemSet = $ItemToExclude->nested_set->getFullSet()){ 537 foreach (array_keys($ItemSet) as $l){ 538 $excluded_ids[] = $ItemSet[$l]->getId(); 539 } 540 } 541 } 542 $excluded_ids = array_unique(array_diff($excluded_ids,array(''))); 543 } 544 } 545 return $this->_ActiveRecordInstance->find('all', array('conditions' => ' '.$this->getScopeCondition().' AND '. 546 (empty($excluded_ids) ? '' : ' id NOT IN ('.join(',',$excluded_ids).') AND '). 547 $this->getLeftColumnName().' > '.$this->_ActiveRecordInstance->get($this->getLeftColumnName()).' AND '. 548 $this->getRightColumnName().' < '.$this->_ActiveRecordInstance->get($this->getRightColumnName()) 549 ,'order' => $this->getLeftColumnName())); 550 } 551 552 /** 553 * Returns a set of itself and all of its nested children 554 */ 555 function getFullSet($exclude = null) 556 { 557 if($this->_ActiveRecordInstance->isNewRecord() || $this->_ActiveRecordInstance->get($this->getRightColumnName()) - $this->_ActiveRecordInstance->get($this->getLeftColumnName()) == 1 ){ 558 $result = array($this->_ActiveRecordInstance); 559 }else{ 560 (array)$result = $this->getAllChildren($exclude); 561 array_unshift($result, $this->_ActiveRecordInstance); 562 } 563 return $result; 564 } 565 566 567 /** 568 * Move the node to the left of another node 569 */ 570 function moveToLeftOf($node) 571 { 572 return $this->moveTo($node, 'left'); 573 } 574 575 /** 576 * Move the node to the left of another node 577 */ 578 function moveToRightOf($node) 579 { 580 return $this->moveTo($node, 'right'); 581 } 582 583 /** 584 * Move the node to the child of another node 585 */ 586 function moveToChildOf($node) 587 { 588 return $this->moveTo($node, 'child'); 589 } 590 591 function moveTo($target, $position) 592 { 593 if($this->_ActiveRecordInstance->isNewRecord()){ 594 trigger_error(Ak::t('You cannot move a new node'), E_USER_ERROR); 595 } 596 $current_left = $this->_ActiveRecordInstance->get($this->getLeftColumnName()); 597 $current_right = $this->_ActiveRecordInstance->get($this->getRightColumnName()); 598 // $extent is the width of the tree self and children 599 $extent = $current_right - $current_left + 1; 600 601 // load object if node is not an object 602 if (is_numeric($target)){ 603 $target =& $this->_ActiveRecordInstance->find($target); 604 } 605 if(!$target || !is_a($target, get_class($this->_ActiveRecordInstance))){ 606 trigger_error(Ak::t('Invalid target'), E_USER_NOTICE); 607 return false; 608 } 609 610 $target_left = $target->get($this->getLeftColumnName()); 611 $target_right = $target->get($this->getRightColumnName()); 612 613 614 // detect impossible move 615 if ((($current_left <= $target_left) && ($target_left <= $current_right)) || (($current_left <= $target_right) && ($target_right <= $current_right))){ 616 trigger_error(Ak::t('Impossible move, target node cannot be inside moved tree.'), E_USER_ERROR); 617 } 618 619 // compute new left/right for self 620 if ($position == 'child'){ 621 if ($target_left < $current_left){ 622 $new_left = $target_left + 1; 623 $new_right = $target_left + $extent; 624 }else{ 625 $new_left = $target_left - $extent + 1; 626 $new_right = $target_left; 627 } 628 }elseif($position == 'left'){ 629 if ($target_left < $current_left){ 630 $new_left = $target_left; 631 $new_right = $target_left + $extent - 1; 632 }else{ 633 $new_left = $target_left - $extent; 634 $new_right = $target_left - 1; 635 } 636 }elseif($position == 'right'){ 637 if ($target_right < $current_right){ 638 $new_left = $target_right + 1; 639 $new_right = $target_right + $extent; 640 }else{ 641 $new_left = $target_right - $extent + 1; 642 $new_right = $target_right; 643 } 644 }else{ 645 trigger_error(Ak::t("Position should be either left or right ('%position' received).",array('%position'=>$position)), E_USER_ERROR); 646 } 647 648 // boundaries of update action 649 $left_boundary = min($current_left, $new_left); 650 $right_boundary = max($current_right, $new_right); 651 652 // Shift value to move self to new $position 653 $shift = $new_left - $current_left; 654 655 // Shift value to move nodes inside boundaries but not under self_and_children 656 $updown = ($shift > 0) ? -$extent : $extent; 657 658 // change null to NULL for new parent 659 if($position == 'child'){ 660 $new_parent = $target->getId(); 661 }else{ 662 $target_parent = $target->get($this->getParentColumnName()); 663 $new_parent = empty($target_parent) ? 'NULL' : $target_parent; 664 } 665 666 $this->_ActiveRecordInstance->updateAll( 667 668 $this->getLeftColumnName().' = CASE '. 669 'WHEN '.$this->getLeftColumnName().' BETWEEN '.$current_left.' AND '.$current_right.' '. 670 'THEN '.$this->getLeftColumnName().' + '.$shift.' '. 671 'WHEN '.$this->getLeftColumnName().' BETWEEN '.$left_boundary.' AND '.$right_boundary.' '. 672 'THEN '.$this->getLeftColumnName().' + '.$updown.' '. 673 'ELSE '.$this->getLeftColumnName().' END, '. 674 675 $this->getRightColumnName().' = CASE '. 676 'WHEN '.$this->getRightColumnName().' BETWEEN '.$current_left.' AND '.$current_right.' '. 677 'THEN '.$this->getRightColumnName().' + '.$shift.' '. 678 'WHEN '.$this->getRightColumnName().' BETWEEN '.$left_boundary.' AND '.$right_boundary.' '. 679 'THEN '.$this->getRightColumnName().' + '.$updown.' '. 680 'ELSE '.$this->getRightColumnName().' END, '. 681 682 $this->getParentColumnName().' = CASE '. 683 'WHEN '.$this->_ActiveRecordInstance->getPrimaryKey().' = '.$this->_ActiveRecordInstance->getId().' '. 684 'THEN '.$new_parent.' '. 685 'ELSE '.$this->getParentColumnName().' END', 686 687 $this->getScopeCondition() ); 688 $this->_ActiveRecordInstance->reload(); 689 690 return true; 691 } 692 693 } 694 695 696 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Oct 27 12:43:49 2008 | Cross-referenced by PHPXref 0.6 |