[ Index ]

PHP Cross Reference of Akelos Framework

title

Body

[close]

/AkActiveRecord/AkActsAsBehaviours/ -> AkActsAsNestedSet.php (source)

   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  ?>


Generated: Mon Oct 27 12:43:49 2008 Cross-referenced by PHPXref 0.6