[ Index ]

PHP Cross Reference of Joomla 2.5.4 DE

title

Body

[close]

/libraries/joomla/database/ -> tablenested.php (source)

   1  <?php
   2  /**
   3   * @package     Joomla.Platform
   4   * @subpackage  Database
   5   *
   6   * @copyright   Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
   7   * @license     GNU General Public License version 2 or later; see LICENSE
   8   */
   9  
  10  defined('JPATH_PLATFORM') or die;
  11  
  12  jimport('joomla.database.table');
  13  
  14  /**
  15   * Table class supporting modified pre-order tree traversal behavior.
  16   *
  17   * @package     Joomla.Platform
  18   * @subpackage  Database
  19   * @link        http://docs.joomla.org/JTableNested
  20   * @since       11.1
  21   */
  22  class JTableNested extends JTable
  23  {
  24      /**
  25       * Object property holding the primary key of the parent node.  Provides
  26       * adjacency list data for nodes.
  27       *
  28       * @var    integer
  29       * @since  11.1
  30       */
  31      public $parent_id;
  32  
  33      /**
  34       * Object property holding the depth level of the node in the tree.
  35       *
  36       * @var    integer
  37       * @since  11.1
  38       */
  39      public $level;
  40  
  41      /**
  42       * Object property holding the left value of the node for managing its
  43       * placement in the nested sets tree.
  44       *
  45       * @var    integer
  46       * @since  11.1
  47       */
  48      public $lft;
  49  
  50      /**
  51       * Object property holding the right value of the node for managing its
  52       * placement in the nested sets tree.
  53       *
  54       * @var    integer
  55       * @since  11.1
  56       */
  57      public $rgt;
  58  
  59      /**
  60       * Object property holding the alias of this node used to constuct the
  61       * full text path, forward-slash delimited.
  62       *
  63       * @var    string
  64       * @since  11.1
  65       */
  66      public $alias;
  67  
  68      /**
  69       * Object property to hold the location type to use when storing the row.
  70       * Possible values are: ['before', 'after', 'first-child', 'last-child'].
  71       *
  72       * @var    string
  73       * @since  11.1
  74       */
  75      protected $_location;
  76  
  77      /**
  78       * Object property to hold the primary key of the location reference node to
  79       * use when storing the row.  A combination of location type and reference
  80       * node describes where to store the current node in the tree.
  81       *
  82       * @var    integer
  83       * @since  11.1
  84       */
  85      protected $_location_id;
  86  
  87      /**
  88       * An array to cache values in recursive processes.
  89       *
  90       * @var    array
  91       * @since  11.1
  92       */
  93      protected $_cache = array();
  94  
  95      /**
  96       * Debug level
  97       *
  98       * @var    integer
  99       * @since  11.1
 100       */
 101      protected $_debug = 0;
 102  
 103      /**
 104       * Sets the debug level on or off
 105       *
 106       * @param   integer  $level  0 = off, 1 = on
 107       *
 108       * @return  void
 109       *
 110       * @since   11.1
 111       */
 112  	public function debug($level)
 113      {
 114          $this->_debug = intval($level);
 115      }
 116  
 117      /**
 118       * Method to get an array of nodes from a given node to its root.
 119       *
 120       * @param   integer  $pk          Primary key of the node for which to get the path.
 121       * @param   boolean  $diagnostic  Only select diagnostic data for the nested sets.
 122       *
 123       * @return  mixed    Boolean false on failure or array of node objects on success.
 124       *
 125       * @link    http://docs.joomla.org/JTableNested/getPath
 126       * @since   11.1
 127       */
 128  	public function getPath($pk = null, $diagnostic = false)
 129      {
 130          // Initialise variables.
 131          $k = $this->_tbl_key;
 132          $pk = (is_null($pk)) ? $this->$k : $pk;
 133  
 134          // Get the path from the node to the root.
 135          $query = $this->_db->getQuery(true);
 136          $select = ($diagnostic) ? 'p.' . $k . ', p.parent_id, p.level, p.lft, p.rgt' : 'p.*';
 137          $query->select($select);
 138          $query->from($this->_tbl . ' AS n, ' . $this->_tbl . ' AS p');
 139          $query->where('n.lft BETWEEN p.lft AND p.rgt');
 140          $query->where('n.' . $k . ' = ' . (int) $pk);
 141          $query->order('p.lft');
 142  
 143          $this->_db->setQuery($query);
 144          $path = $this->_db->loadObjectList();
 145  
 146          // Check for a database error.
 147          if ($this->_db->getErrorNum())
 148          {
 149              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_GET_PATH_FAILED', get_class($this), $this->_db->getErrorMsg()));
 150              $this->setError($e);
 151              return false;
 152          }
 153  
 154          return $path;
 155      }
 156  
 157      /**
 158       * Method to get a node and all its child nodes.
 159       *
 160       * @param   integer  $pk          Primary key of the node for which to get the tree.
 161       * @param   boolean  $diagnostic  Only select diagnostic data for the nested sets.
 162       *
 163       * @return  mixed    Boolean false on failure or array of node objects on success.
 164       *
 165       * @link    http://docs.joomla.org/JTableNested/getTree
 166       * @since   11.1
 167       */
 168  	public function getTree($pk = null, $diagnostic = false)
 169      {
 170          // Initialise variables.
 171          $k = $this->_tbl_key;
 172          $pk = (is_null($pk)) ? $this->$k : $pk;
 173  
 174          // Get the node and children as a tree.
 175          $query = $this->_db->getQuery(true);
 176          $select = ($diagnostic) ? 'n.' . $k . ', n.parent_id, n.level, n.lft, n.rgt' : 'n.*';
 177          $query->select($select);
 178          $query->from($this->_tbl . ' AS n, ' . $this->_tbl . ' AS p');
 179          $query->where('n.lft BETWEEN p.lft AND p.rgt');
 180          $query->where('p.' . $k . ' = ' . (int) $pk);
 181          $query->order('n.lft');
 182          $this->_db->setQuery($query);
 183          $tree = $this->_db->loadObjectList();
 184  
 185          // Check for a database error.
 186          if ($this->_db->getErrorNum())
 187          {
 188              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_GET_TREE_FAILED', get_class($this), $this->_db->getErrorMsg()));
 189              $this->setError($e);
 190              return false;
 191          }
 192  
 193          return $tree;
 194      }
 195  
 196      /**
 197       * Method to determine if a node is a leaf node in the tree (has no children).
 198       *
 199       * @param   integer  $pk  Primary key of the node to check.
 200       *
 201       * @return  boolean  True if a leaf node.
 202       *
 203       * @link    http://docs.joomla.org/JTableNested/isLeaf
 204       * @since   11.1
 205       */
 206  	public function isLeaf($pk = null)
 207      {
 208          // Initialise variables.
 209          $k = $this->_tbl_key;
 210          $pk = (is_null($pk)) ? $this->$k : $pk;
 211  
 212          // Get the node by primary key.
 213          if (!$node = $this->_getNode($pk))
 214          {
 215              // Error message set in getNode method.
 216              return false;
 217          }
 218  
 219          // The node is a leaf node.
 220          return (($node->rgt - $node->lft) == 1);
 221      }
 222  
 223      /**
 224       * Method to set the location of a node in the tree object.  This method does not
 225       * save the new location to the database, but will set it in the object so
 226       * that when the node is stored it will be stored in the new location.
 227       *
 228       * @param   integer  $referenceId  The primary key of the node to reference new location by.
 229       * @param   string   $position     Location type string. ['before', 'after', 'first-child', 'last-child']
 230       *
 231       * @return  boolean  True on success.
 232       *
 233       * @link    http://docs.joomla.org/JTableNested/setLocation
 234       * @since   11.1
 235       */
 236  	public function setLocation($referenceId, $position = 'after')
 237      {
 238          // Make sure the location is valid.
 239          if (($position != 'before') && ($position != 'after') && ($position != 'first-child') && ($position != 'last-child'))
 240          {
 241              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_INVALID_LOCATION', get_class($this)));
 242              $this->setError($e);
 243              return false;
 244          }
 245  
 246          // Set the location properties.
 247          $this->_location = $position;
 248          $this->_location_id = $referenceId;
 249  
 250          return true;
 251      }
 252  
 253      /**
 254       * Method to move a row in the ordering sequence of a group of rows defined by an SQL WHERE clause.
 255       * Negative numbers move the row up in the sequence and positive numbers move it down.
 256       *
 257       * @param   integer  $delta  The direction and magnitude to move the row in the ordering sequence.
 258       * @param   string   $where  WHERE clause to use for limiting the selection of rows to compact the
 259       * ordering values.
 260       *
 261       * @return  mixed    Boolean true on success.
 262       *
 263       * @link    http://docs.joomla.org/JTable/move
 264       * @since   11.1
 265       */
 266  	public function move($delta, $where = '')
 267      {
 268          // Initialise variables.
 269          $k = $this->_tbl_key;
 270          $pk = $this->$k;
 271  
 272          $query = $this->_db->getQuery(true);
 273          $query->select($k);
 274          $query->from($this->_tbl);
 275          $query->where('parent_id = ' . $this->parent_id);
 276          if ($where)
 277          {
 278              $query->where($where);
 279          }
 280          $position = 'after';
 281          if ($delta > 0)
 282          {
 283              $query->where('rgt > ' . $this->rgt);
 284              $query->order('rgt ASC');
 285              $position = 'after';
 286          }
 287          else
 288          {
 289              $query->where('lft < ' . $this->lft);
 290              $query->order('lft DESC');
 291              $position = 'before';
 292          }
 293  
 294          $this->_db->setQuery($query);
 295          $referenceId = $this->_db->loadResult();
 296  
 297          if ($referenceId)
 298          {
 299              return $this->moveByReference($referenceId, $position, $pk);
 300          }
 301          else
 302          {
 303              return false;
 304          }
 305      }
 306  
 307      /**
 308       * Method to move a node and its children to a new location in the tree.
 309       *
 310       * @param   integer  $referenceId  The primary key of the node to reference new location by.
 311       * @param   string   $position     Location type string. ['before', 'after', 'first-child', 'last-child']
 312       * @param   integer  $pk           The primary key of the node to move.
 313       *
 314       * @return  boolean  True on success.
 315       *
 316       * @link    http://docs.joomla.org/JTableNested/moveByReference
 317       * @since   11.1
 318       */
 319  
 320  	public function moveByReference($referenceId, $position = 'after', $pk = null)
 321      {
 322          if ($this->_debug)
 323          {
 324              echo "\nMoving ReferenceId:$referenceId, Position:$position, PK:$pk";
 325          }
 326  
 327          // Initialise variables.
 328          $k = $this->_tbl_key;
 329          $pk = (is_null($pk)) ? $this->$k : $pk;
 330  
 331          // Get the node by id.
 332          if (!$node = $this->_getNode($pk))
 333          {
 334              // Error message set in getNode method.
 335              return false;
 336          }
 337  
 338          // Get the ids of child nodes.
 339          $query = $this->_db->getQuery(true);
 340          $query->select($k);
 341          $query->from($this->_tbl);
 342          $query->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
 343          $this->_db->setQuery($query);
 344          $children = $this->_db->loadColumn();
 345  
 346          // Check for a database error.
 347          if ($this->_db->getErrorNum())
 348          {
 349              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_MOVE_FAILED', get_class($this), $this->_db->getErrorMsg()));
 350              $this->setError($e);
 351              return false;
 352          }
 353          if ($this->_debug)
 354          {
 355              $this->_logtable(false);
 356          }
 357  
 358          // Cannot move the node to be a child of itself.
 359          if (in_array($referenceId, $children))
 360          {
 361              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_INVALID_NODE_RECURSION', get_class($this)));
 362              $this->setError($e);
 363              return false;
 364          }
 365  
 366          // Lock the table for writing.
 367          if (!$this->_lock())
 368          {
 369              return false;
 370          }
 371  
 372          /*
 373           * Move the sub-tree out of the nested sets by negating its left and right values.
 374           */
 375          $query = $this->_db->getQuery(true);
 376          $query->update($this->_tbl);
 377          $query->set('lft = lft * (-1), rgt = rgt * (-1)');
 378          $query->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
 379          $this->_db->setQuery($query);
 380  
 381          $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
 382  
 383          /*
 384           * Close the hole in the tree that was opened by removing the sub-tree from the nested sets.
 385           */
 386          // Compress the left values.
 387          $query = $this->_db->getQuery(true);
 388          $query->update($this->_tbl);
 389          $query->set('lft = lft - ' . (int) $node->width);
 390          $query->where('lft > ' . (int) $node->rgt);
 391          $this->_db->setQuery($query);
 392  
 393          $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
 394  
 395          // Compress the right values.
 396          $query = $this->_db->getQuery(true);
 397          $query->update($this->_tbl);
 398          $query->set('rgt = rgt - ' . (int) $node->width);
 399          $query->where('rgt > ' . (int) $node->rgt);
 400          $this->_db->setQuery($query);
 401  
 402          $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
 403  
 404          // We are moving the tree relative to a reference node.
 405          if ($referenceId)
 406          {
 407              // Get the reference node by primary key.
 408              if (!$reference = $this->_getNode($referenceId))
 409              {
 410                  // Error message set in getNode method.
 411                  $this->_unlock();
 412                  return false;
 413              }
 414  
 415              // Get the reposition data for shifting the tree and re-inserting the node.
 416              if (!$repositionData = $this->_getTreeRepositionData($reference, $node->width, $position))
 417              {
 418                  // Error message set in getNode method.
 419                  $this->_unlock();
 420                  return false;
 421              }
 422          }
 423          // We are moving the tree to be the last child of the root node
 424          else
 425          {
 426              // Get the last root node as the reference node.
 427              $query = $this->_db->getQuery(true);
 428              $query->select($this->_tbl_key . ', parent_id, level, lft, rgt');
 429              $query->from($this->_tbl);
 430              $query->where('parent_id = 0');
 431              $query->order('lft DESC');
 432              $this->_db->setQuery($query, 0, 1);
 433              $reference = $this->_db->loadObject();
 434  
 435              // Check for a database error.
 436              if ($this->_db->getErrorNum())
 437              {
 438                  $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_MOVE_FAILED', get_class($this), $this->_db->getErrorMsg()));
 439                  $this->setError($e);
 440                  $this->_unlock();
 441                  return false;
 442              }
 443              if ($this->_debug)
 444              {
 445                  $this->_logtable(false);
 446              }
 447  
 448              // Get the reposition data for re-inserting the node after the found root.
 449              if (!$repositionData = $this->_getTreeRepositionData($reference, $node->width, 'last-child'))
 450              {
 451                  // Error message set in getNode method.
 452                  $this->_unlock();
 453                  return false;
 454              }
 455          }
 456  
 457          /*
 458           * Create space in the nested sets at the new location for the moved sub-tree.
 459           */
 460          // Shift left values.
 461          $query = $this->_db->getQuery(true);
 462          $query->update($this->_tbl);
 463          $query->set('lft = lft + ' . (int) $node->width);
 464          $query->where($repositionData->left_where);
 465          $this->_db->setQuery($query);
 466  
 467          $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
 468  
 469          // Shift right values.
 470          $query = $this->_db->getQuery(true);
 471          $query->update($this->_tbl);
 472          $query->set('rgt = rgt + ' . (int) $node->width);
 473          $query->where($repositionData->right_where);
 474          $this->_db->setQuery($query);
 475  
 476          $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
 477  
 478          /*
 479           * Calculate the offset between where the node used to be in the tree and
 480           * where it needs to be in the tree for left ids (also works for right ids).
 481           */
 482          $offset = $repositionData->new_lft - $node->lft;
 483          $levelOffset = $repositionData->new_level - $node->level;
 484  
 485          // Move the nodes back into position in the tree using the calculated offsets.
 486          $query = $this->_db->getQuery(true);
 487          $query->update($this->_tbl);
 488          $query->set('rgt = ' . (int) $offset . ' - rgt');
 489          $query->set('lft = ' . (int) $offset . ' - lft');
 490          $query->set('level = level + ' . (int) $levelOffset);
 491          $query->where('lft < 0');
 492          $this->_db->setQuery($query);
 493  
 494          $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
 495  
 496          // Set the correct parent id for the moved node if required.
 497          if ($node->parent_id != $repositionData->new_parent_id)
 498          {
 499              $query = $this->_db->getQuery(true);
 500              $query->update($this->_tbl);
 501  
 502              // Update the title and alias fields if they exist for the table.
 503              if (property_exists($this, 'title') && $this->title !== null)
 504              {
 505                  $query->set('title = ' . $this->_db->Quote($this->title));
 506              }
 507              if (property_exists($this, 'alias') && $this->alias !== null)
 508              {
 509                  $query->set('alias = ' . $this->_db->Quote($this->alias));
 510              }
 511  
 512              $query->set('parent_id = ' . (int) $repositionData->new_parent_id);
 513              $query->where($this->_tbl_key . ' = ' . (int) $node->$k);
 514              $this->_db->setQuery($query);
 515  
 516              $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
 517          }
 518  
 519          // Unlock the table for writing.
 520          $this->_unlock();
 521  
 522          // Set the object values.
 523          $this->parent_id = $repositionData->new_parent_id;
 524          $this->level = $repositionData->new_level;
 525          $this->lft = $repositionData->new_lft;
 526          $this->rgt = $repositionData->new_rgt;
 527  
 528          return true;
 529      }
 530  
 531      /**
 532       * Method to delete a node and, optionally, its child nodes from the table.
 533       *
 534       * @param   integer  $pk        The primary key of the node to delete.
 535       * @param   boolean  $children  True to delete child nodes, false to move them up a level.
 536       *
 537       * @return  boolean  True on success.
 538       *
 539       * @link    http://docs.joomla.org/JTableNested/delete
 540       * @since   11.1
 541       */
 542  	public function delete($pk = null, $children = true)
 543      {
 544          // Initialise variables.
 545          $k = $this->_tbl_key;
 546          $pk = (is_null($pk)) ? $this->$k : $pk;
 547  
 548          // Lock the table for writing.
 549          if (!$this->_lock())
 550          {
 551              // Error message set in lock method.
 552              return false;
 553          }
 554  
 555          // If tracking assets, remove the asset first.
 556          if ($this->_trackAssets)
 557          {
 558              $name = $this->_getAssetName();
 559              $asset = JTable::getInstance('Asset');
 560  
 561              // Lock the table for writing.
 562              if (!$asset->_lock())
 563              {
 564                  // Error message set in lock method.
 565                  return false;
 566              }
 567  
 568              if ($asset->loadByName($name))
 569              {
 570                  // Delete the node in assets table.
 571                  if (!$asset->delete(null, $children))
 572                  {
 573                      $this->setError($asset->getError());
 574                      $asset->_unlock();
 575                      return false;
 576                  }
 577                  $asset->_unlock();
 578              }
 579              else
 580              {
 581                  $this->setError($asset->getError());
 582                  $asset->_unlock();
 583                  return false;
 584              }
 585          }
 586  
 587          // Get the node by id.
 588          if (!$node = $this->_getNode($pk))
 589          {
 590              // Error message set in getNode method.
 591              $this->_unlock();
 592              return false;
 593          }
 594  
 595          // Should we delete all children along with the node?
 596          if ($children)
 597          {
 598              // Delete the node and all of its children.
 599              $query = $this->_db->getQuery(true);
 600              $query->delete();
 601              $query->from($this->_tbl);
 602              $query->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
 603              $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
 604  
 605              // Compress the left values.
 606              $query = $this->_db->getQuery(true);
 607              $query->update($this->_tbl);
 608              $query->set('lft = lft - ' . (int) $node->width);
 609              $query->where('lft > ' . (int) $node->rgt);
 610              $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
 611  
 612              // Compress the right values.
 613              $query = $this->_db->getQuery(true);
 614              $query->update($this->_tbl);
 615              $query->set('rgt = rgt - ' . (int) $node->width);
 616              $query->where('rgt > ' . (int) $node->rgt);
 617              $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
 618          }
 619          // Leave the children and move them up a level.
 620          else
 621          {
 622              // Delete the node.
 623              $query = $this->_db->getQuery(true);
 624              $query->delete();
 625              $query->from($this->_tbl);
 626              $query->where('lft = ' . (int) $node->lft);
 627              $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
 628  
 629              // Shift all node's children up a level.
 630              $query = $this->_db->getQuery(true);
 631              $query->update($this->_tbl);
 632              $query->set('lft = lft - 1');
 633              $query->set('rgt = rgt - 1');
 634              $query->set('level = level - 1');
 635              $query->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
 636              $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
 637  
 638              // Adjust all the parent values for direct children of the deleted node.
 639              $query = $this->_db->getQuery(true);
 640              $query->update($this->_tbl);
 641              $query->set('parent_id = ' . (int) $node->parent_id);
 642              $query->where('parent_id = ' . (int) $node->$k);
 643              $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
 644  
 645              // Shift all of the left values that are right of the node.
 646              $query = $this->_db->getQuery(true);
 647              $query->update($this->_tbl);
 648              $query->set('lft = lft - 2');
 649              $query->where('lft > ' . (int) $node->rgt);
 650              $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
 651  
 652              // Shift all of the right values that are right of the node.
 653              $query = $this->_db->getQuery(true);
 654              $query->update($this->_tbl);
 655              $query->set('rgt = rgt - 2');
 656              $query->where('rgt > ' . (int) $node->rgt);
 657              $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
 658          }
 659  
 660          // Unlock the table for writing.
 661          $this->_unlock();
 662  
 663          return true;
 664      }
 665  
 666      /**
 667       * Asset that the nested set data is valid.
 668       *
 669       * @return  boolean  True if the instance is sane and able to be stored in the database.
 670       *
 671       * @link    http://docs.joomla.org/JTable/check
 672       * @since   11.1
 673       */
 674  	public function check()
 675      {
 676          $this->parent_id = (int) $this->parent_id;
 677          if ($this->parent_id > 0)
 678          {
 679              $query = $this->_db->getQuery(true);
 680              $query->select('COUNT(' . $this->_tbl_key . ')');
 681              $query->from($this->_tbl);
 682              $query->where($this->_tbl_key . ' = ' . $this->parent_id);
 683              $this->_db->setQuery($query);
 684  
 685              if ($this->_db->loadResult())
 686              {
 687                  return true;
 688              }
 689              else
 690              {
 691                  if ($this->_db->getErrorNum())
 692                  {
 693                      $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_CHECK_FAILED', get_class($this), $this->_db->getErrorMsg()));
 694                      $this->setError($e);
 695                  }
 696                  else
 697                  {
 698                      $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_INVALID_PARENT_ID', get_class($this)));
 699                      $this->setError($e);
 700                  }
 701              }
 702          }
 703          else
 704          {
 705              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_INVALID_PARENT_ID', get_class($this)));
 706              $this->setError($e);
 707          }
 708  
 709          return false;
 710      }
 711  
 712      /**
 713       * Method to store a node in the database table.
 714       *
 715       * @param   boolean  $updateNulls  True to update null values as well.
 716       *
 717       * @return  boolean  True on success.
 718       *
 719       * @link    http://docs.joomla.org/JTableNested/store
 720       * @since   11.1
 721       */
 722  	public function store($updateNulls = false)
 723      {
 724          // Initialise variables.
 725          $k = $this->_tbl_key;
 726  
 727          if ($this->_debug)
 728          {
 729              echo "\n" . get_class($this) . "::store\n";
 730              $this->_logtable(true, false);
 731          }
 732          /*
 733           * If the primary key is empty, then we assume we are inserting a new node into the
 734           * tree.  From this point we would need to determine where in the tree to insert it.
 735           */
 736          if (empty($this->$k))
 737          {
 738              /*
 739               * We are inserting a node somewhere in the tree with a known reference
 740               * node.  We have to make room for the new node and set the left and right
 741               * values before we insert the row.
 742               */
 743              if ($this->_location_id >= 0)
 744              {
 745                  // Lock the table for writing.
 746                  if (!$this->_lock())
 747                  {
 748                      // Error message set in lock method.
 749                      return false;
 750                  }
 751  
 752                  // We are inserting a node relative to the last root node.
 753                  if ($this->_location_id == 0)
 754                  {
 755                      // Get the last root node as the reference node.
 756                      $query = $this->_db->getQuery(true);
 757                      $query->select($this->_tbl_key . ', parent_id, level, lft, rgt');
 758                      $query->from($this->_tbl);
 759                      $query->where('parent_id = 0');
 760                      $query->order('lft DESC');
 761                      $this->_db->setQuery($query, 0, 1);
 762                      $reference = $this->_db->loadObject();
 763  
 764                      // Check for a database error.
 765                      if ($this->_db->getErrorNum())
 766                      {
 767                          $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED', get_class($this), $this->_db->getErrorMsg()));
 768                          $this->setError($e);
 769                          $this->_unlock();
 770                          return false;
 771                      }
 772                      if ($this->_debug)
 773                      {
 774                          $this->_logtable(false);
 775                      }
 776                  }
 777                  // We have a real node set as a location reference.
 778                  else
 779                  {
 780                      // Get the reference node by primary key.
 781                      if (!$reference = $this->_getNode($this->_location_id))
 782                      {
 783                          // Error message set in getNode method.
 784                          $this->_unlock();
 785                          return false;
 786                      }
 787                  }
 788  
 789                  // Get the reposition data for shifting the tree and re-inserting the node.
 790                  if (!($repositionData = $this->_getTreeRepositionData($reference, 2, $this->_location)))
 791                  {
 792                      // Error message set in getNode method.
 793                      $this->_unlock();
 794                      return false;
 795                  }
 796  
 797                  // Create space in the tree at the new location for the new node in left ids.
 798                  $query = $this->_db->getQuery(true);
 799                  $query->update($this->_tbl);
 800                  $query->set('lft = lft + 2');
 801                  $query->where($repositionData->left_where);
 802                  $this->_runQuery($query, 'JLIB_DATABASE_ERROR_STORE_FAILED');
 803  
 804                  // Create space in the tree at the new location for the new node in right ids.
 805                  $query = $this->_db->getQuery(true);
 806                  $query->update($this->_tbl);
 807                  $query->set('rgt = rgt + 2');
 808                  $query->where($repositionData->right_where);
 809                  $this->_runQuery($query, 'JLIB_DATABASE_ERROR_STORE_FAILED');
 810  
 811                  // Set the object values.
 812                  $this->parent_id = $repositionData->new_parent_id;
 813                  $this->level = $repositionData->new_level;
 814                  $this->lft = $repositionData->new_lft;
 815                  $this->rgt = $repositionData->new_rgt;
 816              }
 817              else
 818              {
 819                  // Negative parent ids are invalid
 820                  $e = new JException(JText::_('JLIB_DATABASE_ERROR_INVALID_PARENT_ID'));
 821                  $this->setError($e);
 822                  return false;
 823              }
 824          }
 825          /*
 826           * If we have a given primary key then we assume we are simply updating this
 827           * node in the tree.  We should assess whether or not we are moving the node
 828           * or just updating its data fields.
 829           */
 830          else
 831          {
 832              // If the location has been set, move the node to its new location.
 833              if ($this->_location_id > 0)
 834              {
 835                  if (!$this->moveByReference($this->_location_id, $this->_location, $this->$k))
 836                  {
 837                      // Error message set in move method.
 838                      return false;
 839                  }
 840              }
 841  
 842              // Lock the table for writing.
 843              if (!$this->_lock())
 844              {
 845                  // Error message set in lock method.
 846                  return false;
 847              }
 848          }
 849  
 850          // Store the row to the database.
 851          if (!parent::store($updateNulls))
 852          {
 853              $this->_unlock();
 854              return false;
 855          }
 856          if ($this->_debug)
 857          {
 858              $this->_logtable();
 859          }
 860  
 861          // Unlock the table for writing.
 862          $this->_unlock();
 863  
 864          return true;
 865      }
 866  
 867      /**
 868       * Method to set the publishing state for a node or list of nodes in the database
 869       * table.  The method respects rows checked out by other users and will attempt
 870       * to checkin rows that it can after adjustments are made. The method will not
 871       * allow you to set a publishing state higher than any ancestor node and will
 872       * not allow you to set a publishing state on a node with a checked out child.
 873       *
 874       * @param   mixed    $pks     An optional array of primary key values to update.  If not
 875       *                            set the instance property value is used.
 876       * @param   integer  $state   The publishing state. eg. [0 = unpublished, 1 = published]
 877       * @param   integer  $userId  The user id of the user performing the operation.
 878       *
 879       * @return  boolean  True on success.
 880       *
 881       * @link    http://docs.joomla.org/JTableNested/publish
 882       * @since   11.1
 883       */
 884  	public function publish($pks = null, $state = 1, $userId = 0)
 885      {
 886          // Initialise variables.
 887          $k = $this->_tbl_key;
 888  
 889          // Sanitize input.
 890          JArrayHelper::toInteger($pks);
 891          $userId = (int) $userId;
 892          $state = (int) $state;
 893          // If $state > 1, then we allow state changes even if an ancestor has lower state
 894          // (for example, can change a child state to Archived (2) if an ancestor is Published (1)
 895          $compareState = ($state > 1) ? 1 : $state;
 896  
 897          // If there are no primary keys set check to see if the instance key is set.
 898          if (empty($pks))
 899          {
 900              if ($this->$k)
 901              {
 902                  $pks = explode(',', $this->$k);
 903              }
 904              // Nothing to set publishing state on, return false.
 905              else
 906              {
 907                  $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED', get_class($this)));
 908                  $this->setError($e);
 909                  return false;
 910              }
 911          }
 912  
 913          // Determine if there is checkout support for the table.
 914          $checkoutSupport = (property_exists($this, 'checked_out') || property_exists($this, 'checked_out_time'));
 915  
 916          // Iterate over the primary keys to execute the publish action if possible.
 917          foreach ($pks as $pk)
 918          {
 919              // Get the node by primary key.
 920              if (!$node = $this->_getNode($pk))
 921              {
 922                  // Error message set in getNode method.
 923                  return false;
 924              }
 925  
 926              // If the table has checkout support, verify no children are checked out.
 927              if ($checkoutSupport)
 928              {
 929                  // Ensure that children are not checked out.
 930                  $query = $this->_db->getQuery(true);
 931                  $query->select('COUNT(' . $k . ')');
 932                  $query->from($this->_tbl);
 933                  $query->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
 934                  $query->where('(checked_out <> 0 AND checked_out <> ' . (int) $userId . ')');
 935                  $this->_db->setQuery($query);
 936  
 937                  // Check for checked out children.
 938                  if ($this->_db->loadResult())
 939                  {
 940                      $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_CHILD_ROWS_CHECKED_OUT', get_class($this)));
 941                      $this->setError($e);
 942                      return false;
 943                  }
 944              }
 945  
 946              // If any parent nodes have lower published state values, we cannot continue.
 947              if ($node->parent_id)
 948              {
 949                  // Get any ancestor nodes that have a lower publishing state.
 950                  $query = $this->_db->getQuery(true)->select('n.' . $k)->from($this->_db->quoteName($this->_tbl) . ' AS n')
 951                      ->where('n.lft < ' . (int) $node->lft)->where('n.rgt > ' . (int) $node->rgt)->where('n.parent_id > 0')
 952                      ->where('n.published < ' . (int) $compareState);
 953  
 954                  // Just fetch one row (one is one too many).
 955                  $this->_db->setQuery($query, 0, 1);
 956  
 957                  $rows = $this->_db->loadColumn();
 958  
 959                  // Check for a database error.
 960                  if ($this->_db->getErrorNum())
 961                  {
 962                      $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_PUBLISH_FAILED', get_class($this), $this->_db->getErrorMsg()));
 963                      $this->setError($e);
 964                      return false;
 965                  }
 966  
 967                  if (!empty($rows))
 968                  {
 969                      $e = new JException(JText::_('JLIB_DATABASE_ERROR_ANCESTOR_NODES_LOWER_STATE'));
 970                      $this->setError($e);
 971                      return false;
 972                  }
 973              }
 974  
 975              // Update and cascade the publishing state.
 976              $query = $this->_db->getQuery(true)->update($this->_db->quoteName($this->_tbl))->set('published = ' . (int) $state)
 977                  ->where('(lft > ' . (int) $node->lft . ' AND rgt < ' . (int) $node->rgt . ')' . ' OR ' . $k . ' = ' . (int) $pk);
 978              $this->_db->setQuery($query);
 979  
 980              // Check for a database error.
 981              if (!$this->_db->query())
 982              {
 983                  $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_PUBLISH_FAILED', get_class($this), $this->_db->getErrorMsg()));
 984                  $this->setError($e);
 985                  return false;
 986              }
 987  
 988              // If checkout support exists for the object, check the row in.
 989              if ($checkoutSupport)
 990              {
 991                  $this->checkin($pk);
 992              }
 993          }
 994  
 995          // If the JTable instance value is in the list of primary keys that were set, set the instance.
 996          if (in_array($this->$k, $pks))
 997          {
 998              $this->published = $state;
 999          }
1000  
1001          $this->setError('');
1002          return true;
1003      }
1004  
1005      /**
1006       * Method to move a node one position to the left in the same level.
1007       *
1008       * @param   integer  $pk  Primary key of the node to move.
1009       *
1010       * @return  boolean  True on success.
1011       *
1012       * @link    http://docs.joomla.org/JTableNested/orderUp
1013       * @since   11.1
1014       */
1015  	public function orderUp($pk)
1016      {
1017          // Initialise variables.
1018          $k = $this->_tbl_key;
1019          $pk = (is_null($pk)) ? $this->$k : $pk;
1020  
1021          // Lock the table for writing.
1022          if (!$this->_lock())
1023          {
1024              // Error message set in lock method.
1025              return false;
1026          }
1027  
1028          // Get the node by primary key.
1029          if (!$node = $this->_getNode($pk))
1030          {
1031              // Error message set in getNode method.
1032              $this->_unlock();
1033              return false;
1034          }
1035  
1036          // Get the left sibling node.
1037          if (!$sibling = $this->_getNode($node->lft - 1, 'right'))
1038          {
1039              // Error message set in getNode method.
1040              $this->_unlock();
1041              return false;
1042          }
1043  
1044          // Get the primary keys of child nodes.
1045          $query = $this->_db->getQuery(true);
1046          $query->select($this->_tbl_key);
1047          $query->from($this->_tbl);
1048          $query->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
1049          $this->_db->setQuery($query);
1050          $children = $this->_db->loadColumn();
1051  
1052          // Check for a database error.
1053          if ($this->_db->getErrorNum())
1054          {
1055              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_ORDERUP_FAILED', get_class($this), $this->_db->getErrorMsg()));
1056              $this->setError($e);
1057              $this->_unlock();
1058              return false;
1059          }
1060  
1061          // Shift left and right values for the node and it's children.
1062          $query = $this->_db->getQuery(true);
1063          $query->update($this->_tbl);
1064          $query->set('lft = lft - ' . (int) $sibling->width);
1065          $query->set('rgt = rgt - ' . (int) $sibling->width);
1066          $query->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
1067          $this->_db->setQuery($query);
1068  
1069          // Check for a database error.
1070          if (!$this->_db->query())
1071          {
1072              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_ORDERUP_FAILED', get_class($this), $this->_db->getErrorMsg()));
1073              $this->setError($e);
1074              $this->_unlock();
1075              return false;
1076          }
1077  
1078          // Shift left and right values for the sibling and it's children.
1079          $query = $this->_db->getQuery(true);
1080          $query->update($this->_tbl);
1081          $query->set('lft = lft + ' . (int) $node->width);
1082          $query->set('rgt = rgt + ' . (int) $node->width);
1083          $query->where('lft BETWEEN ' . (int) $sibling->lft . ' AND ' . (int) $sibling->rgt);
1084          $query->where($this->_tbl_key . ' NOT IN (' . implode(',', $children) . ')');
1085          $this->_db->setQuery($query);
1086  
1087          // Check for a database error.
1088          if (!$this->_db->query())
1089          {
1090              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_ORDERUP_FAILED', get_class($this), $this->_db->getErrorMsg()));
1091              $this->setError($e);
1092              $this->_unlock();
1093              return false;
1094          }
1095  
1096          // Unlock the table for writing.
1097          $this->_unlock();
1098  
1099          return true;
1100      }
1101  
1102      /**
1103       * Method to move a node one position to the right in the same level.
1104       *
1105       * @param   integer  $pk  Primary key of the node to move.
1106       *
1107       * @return  boolean  True on success.
1108       *
1109       * @link    http://docs.joomla.org/JTableNested/orderDown
1110       * @since   11.1
1111       */
1112  	public function orderDown($pk)
1113      {
1114          // Initialise variables.
1115          $k = $this->_tbl_key;
1116          $pk = (is_null($pk)) ? $this->$k : $pk;
1117  
1118          // Lock the table for writing.
1119          if (!$this->_lock())
1120          {
1121              // Error message set in lock method.
1122              return false;
1123          }
1124  
1125          // Get the node by primary key.
1126          if (!$node = $this->_getNode($pk))
1127          {
1128              // Error message set in getNode method.
1129              $this->_unlock();
1130              return false;
1131          }
1132  
1133          // Get the right sibling node.
1134          if (!$sibling = $this->_getNode($node->rgt + 1, 'left'))
1135          {
1136              // Error message set in getNode method.
1137              $query->unlock($this->_db);
1138              $this->_locked = false;
1139              return false;
1140          }
1141  
1142          // Get the primary keys of child nodes.
1143          $query = $this->_db->getQuery(true);
1144          $query->select($this->_tbl_key);
1145          $query->from($this->_tbl);
1146          $query->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
1147          $this->_db->setQuery($query);
1148          $children = $this->_db->loadColumn();
1149  
1150          // Check for a database error.
1151          if ($this->_db->getErrorNum())
1152          {
1153              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_ORDERDOWN_FAILED', get_class($this), $this->_db->getErrorMsg()));
1154              $this->setError($e);
1155              $this->_unlock();
1156              return false;
1157          }
1158  
1159          // Shift left and right values for the node and it's children.
1160          $query = $this->_db->getQuery(true);
1161          $query->update($this->_tbl);
1162          $query->set('lft = lft + ' . (int) $sibling->width);
1163          $query->set('rgt = rgt + ' . (int) $sibling->width);
1164          $query->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
1165          $this->_db->setQuery($query);
1166  
1167          // Check for a database error.
1168          if (!$this->_db->query())
1169          {
1170              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_ORDERDOWN_FAILED', get_class($this), $this->_db->getErrorMsg()));
1171              $this->setError($e);
1172              $this->_unlock();
1173              return false;
1174          }
1175  
1176          // Shift left and right values for the sibling and it's children.
1177          $query = $this->_db->getQuery(true);
1178          $query->update($this->_tbl);
1179          $query->set('lft = lft - ' . (int) $node->width);
1180          $query->set('rgt = rgt - ' . (int) $node->width);
1181          $query->where('lft BETWEEN ' . (int) $sibling->lft . ' AND ' . (int) $sibling->rgt);
1182          $query->where($this->_tbl_key . ' NOT IN (' . implode(',', $children) . ')');
1183          $this->_db->setQuery($query);
1184  
1185          // Check for a database error.
1186          if (!$this->_db->query())
1187          {
1188              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_ORDERDOWN_FAILED', get_class($this), $this->_db->getErrorMsg()));
1189              $this->setError($e);
1190              $this->_unlock();
1191              return false;
1192          }
1193  
1194          // Unlock the table for writing.
1195          $this->_unlock();
1196  
1197          return true;
1198      }
1199  
1200      /**
1201       * Gets the ID of the root item in the tree
1202       *
1203       * @return  mixed  The ID of the root row, or false and the internal error is set.
1204       *
1205       * @since   11.1
1206       */
1207  	public function getRootId()
1208      {
1209          // Get the root item.
1210          $k = $this->_tbl_key;
1211  
1212          // Test for a unique record with parent_id = 0
1213          $query = $this->_db->getQuery(true);
1214          $query->select($k);
1215          $query->from($this->_tbl);
1216          $query->where('parent_id = 0');
1217          $this->_db->setQuery($query);
1218  
1219          $result = $this->_db->loadColumn();
1220  
1221          if ($this->_db->getErrorNum())
1222          {
1223              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_GETROOTID_FAILED', get_class($this), $this->_db->getErrorMsg()));
1224              $this->setError($e);
1225              return false;
1226          }
1227  
1228          if (count($result) == 1)
1229          {
1230              $parentId = $result[0];
1231          }
1232          else
1233          {
1234              // Test for a unique record with lft = 0
1235              $query = $this->_db->getQuery(true);
1236              $query->select($k);
1237              $query->from($this->_tbl);
1238              $query->where('lft = 0');
1239              $this->_db->setQuery($query);
1240  
1241              $result = $this->_db->loadColumn();
1242              if ($this->_db->getErrorNum())
1243              {
1244                  $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_GETROOTID_FAILED', get_class($this), $this->_db->getErrorMsg()));
1245                  $this->setError($e);
1246                  return false;
1247              }
1248  
1249              if (count($result) == 1)
1250              {
1251                  $parentId = $result[0];
1252              }
1253              elseif (property_exists($this, 'alias'))
1254              {
1255                  // Test for a unique record alias = root
1256                  $query = $this->_db->getQuery(true);
1257                  $query->select($k);
1258                  $query->from($this->_tbl);
1259                  $query->where('alias = ' . $this->_db->quote('root'));
1260                  $this->_db->setQuery($query);
1261  
1262                  $result = $this->_db->loadColumn();
1263                  if ($this->_db->getErrorNum())
1264                  {
1265                      $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_GETROOTID_FAILED', get_class($this), $this->_db->getErrorMsg()));
1266                      $this->setError($e);
1267                      return false;
1268                  }
1269  
1270                  if (count($result) == 1)
1271                  {
1272                      $parentId = $result[0];
1273                  }
1274                  else
1275                  {
1276                      $e = new JException(JText::_('JLIB_DATABASE_ERROR_ROOT_NODE_NOT_FOUND'));
1277                      $this->setError($e);
1278                      return false;
1279                  }
1280              }
1281              else
1282              {
1283                  $e = new JException(JText::_('JLIB_DATABASE_ERROR_ROOT_NODE_NOT_FOUND'));
1284                  $this->setError($e);
1285                  return false;
1286              }
1287          }
1288  
1289          return $parentId;
1290      }
1291  
1292      /**
1293       * Method to recursively rebuild the whole nested set tree.
1294       *
1295       * @param   integer  $parentId  The root of the tree to rebuild.
1296       * @param   integer  $leftId    The left id to start with in building the tree.
1297       * @param   integer  $level     The level to assign to the current nodes.
1298       * @param   string   $path      The path to the current nodes.
1299       *
1300       * @return  integer  1 + value of root rgt on success, false on failure
1301       *
1302       * @link    http://docs.joomla.org/JTableNested/rebuild
1303       * @since   11.1
1304       */
1305  	public function rebuild($parentId = null, $leftId = 0, $level = 0, $path = '')
1306      {
1307          // If no parent is provided, try to find it.
1308          if ($parentId === null)
1309          {
1310              // Get the root item.
1311              $parentId = $this->getRootId();
1312              if ($parentId === false)
1313              {
1314                  return false;
1315              }
1316  
1317          }
1318  
1319          // Build the structure of the recursive query.
1320          if (!isset($this->_cache['rebuild.sql']))
1321          {
1322              $query = $this->_db->getQuery(true);
1323              $query->select($this->_tbl_key . ', alias');
1324              $query->from($this->_tbl);
1325              $query->where('parent_id = %d');
1326  
1327              // If the table has an ordering field, use that for ordering.
1328              if (property_exists($this, 'ordering'))
1329              {
1330                  $query->order('parent_id, ordering, lft');
1331              }
1332              else
1333              {
1334                  $query->order('parent_id, lft');
1335              }
1336              $this->_cache['rebuild.sql'] = (string) $query;
1337          }
1338  
1339          // Make a shortcut to database object.
1340  
1341          // Assemble the query to find all children of this node.
1342          $this->_db->setQuery(sprintf($this->_cache['rebuild.sql'], (int) $parentId));
1343          $children = $this->_db->loadObjectList();
1344  
1345          // The right value of this node is the left value + 1
1346          $rightId = $leftId + 1;
1347  
1348          // execute this function recursively over all children
1349          foreach ($children as $node)
1350          {
1351              // $rightId is the current right value, which is incremented on recursion return.
1352              // Increment the level for the children.
1353              // Add this item's alias to the path (but avoid a leading /)
1354              $rightId = $this->rebuild($node->{$this->_tbl_key}, $rightId, $level + 1, $path . (empty($path) ? '' : '/') . $node->alias);
1355  
1356              // If there is an update failure, return false to break out of the recursion.
1357              if ($rightId === false)
1358              {
1359                  return false;
1360              }
1361          }
1362  
1363          // We've got the left value, and now that we've processed
1364          // the children of this node we also know the right value.
1365          $query = $this->_db->getQuery(true);
1366          $query->update($this->_tbl);
1367          $query->set('lft = ' . (int) $leftId);
1368          $query->set('rgt = ' . (int) $rightId);
1369          $query->set('level = ' . (int) $level);
1370          $query->set('path = ' . $this->_db->quote($path));
1371          $query->where($this->_tbl_key . ' = ' . (int) $parentId);
1372          $this->_db->setQuery($query);
1373  
1374          // If there is an update failure, return false to break out of the recursion.
1375          if (!$this->_db->query())
1376          {
1377              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_REBUILD_FAILED', get_class($this), $this->_db->getErrorMsg()));
1378              $this->setError($e);
1379              return false;
1380          }
1381  
1382          // Return the right value of this node + 1.
1383          return $rightId + 1;
1384      }
1385  
1386      /**
1387       * Method to rebuild the node's path field from the alias values of the
1388       * nodes from the current node to the root node of the tree.
1389       *
1390       * @param   integer  $pk  Primary key of the node for which to get the path.
1391       *
1392       * @return  boolean  True on success.
1393       *
1394       * @link    http://docs.joomla.org/JTableNested/rebuildPath
1395       * @since   11.1
1396       */
1397  	public function rebuildPath($pk = null)
1398      {
1399          // If there is no alias or path field, just return true.
1400          if (!property_exists($this, 'alias') || !property_exists($this, 'path'))
1401          {
1402              return true;
1403          }
1404  
1405          // Initialise variables.
1406          $k = $this->_tbl_key;
1407          $pk = (is_null($pk)) ? $this->$k : $pk;
1408  
1409          // Get the aliases for the path from the node to the root node.
1410          $query = $this->_db->getQuery(true);
1411          $query->select('p.alias');
1412          $query->from($this->_tbl . ' AS n, ' . $this->_tbl . ' AS p');
1413          $query->where('n.lft BETWEEN p.lft AND p.rgt');
1414          $query->where('n.' . $this->_tbl_key . ' = ' . (int) $pk);
1415          $query->order('p.lft');
1416          $this->_db->setQuery($query);
1417  
1418          $segments = $this->_db->loadColumn();
1419  
1420          // Make sure to remove the root path if it exists in the list.
1421          if ($segments[0] == 'root')
1422          {
1423              array_shift($segments);
1424          }
1425  
1426          // Build the path.
1427          $path = trim(implode('/', $segments), ' /\\');
1428  
1429          // Update the path field for the node.
1430          $query = $this->_db->getQuery(true);
1431          $query->update($this->_tbl);
1432          $query->set('path = ' . $this->_db->quote($path));
1433          $query->where($this->_tbl_key . ' = ' . (int) $pk);
1434          $this->_db->setQuery($query);
1435  
1436          // Check for a database error.
1437          if (!$this->_db->query())
1438          {
1439              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_REBUILDPATH_FAILED', get_class($this), $this->_db->getErrorMsg()));
1440              $this->setError($e);
1441              return false;
1442          }
1443  
1444          // Update the current record's path to the new one:
1445          $this->path = $path;
1446  
1447          return true;
1448      }
1449  
1450      /**
1451       * Method to update order of table rows
1452       *
1453       * @param   array  $idArray    id numbers of rows to be reordered.
1454       * @param   array  $lft_array  lft values of rows to be reordered.
1455       *
1456       * @return  integer  1 + value of root rgt on success, false on failure.
1457       *
1458       * @since   11.1
1459       */
1460  	public function saveorder($idArray = null, $lft_array = null)
1461      {
1462          // Validate arguments
1463          if (is_array($idArray) && is_array($lft_array) && count($idArray) == count($lft_array))
1464          {
1465              for ($i = 0, $count = count($idArray); $i < $count; $i++)
1466              {
1467                  // Do an update to change the lft values in the table for each id
1468                  $query = $this->_db->getQuery(true);
1469                  $query->update($this->_tbl);
1470                  $query->where($this->_tbl_key . ' = ' . (int) $idArray[$i]);
1471                  $query->set('lft = ' . (int) $lft_array[$i]);
1472                  $this->_db->setQuery($query);
1473  
1474                  // Check for a database error.
1475                  if (!$this->_db->query())
1476                  {
1477                      $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_REORDER_FAILED', get_class($this), $this->_db->getErrorMsg()));
1478                      $this->setError($e);
1479                      $this->_unlock();
1480                      return false;
1481                  }
1482  
1483                  if ($this->_debug)
1484                  {
1485                      $this->_logtable();
1486                  }
1487  
1488              }
1489  
1490              return $this->rebuild();
1491          }
1492          else
1493          {
1494              return false;
1495          }
1496      }
1497  
1498      /**
1499       * Method to get nested set properties for a node in the tree.
1500       *
1501       * @param   integer  $id   Value to look up the node by.
1502       * @param   string   $key  Key to look up the node by.
1503       *
1504       * @return  mixed    Boolean false on failure or node object on success.
1505       *
1506       * @since   11.1
1507       */
1508  	protected function _getNode($id, $key = null)
1509      {
1510          // Determine which key to get the node base on.
1511          switch ($key)
1512          {
1513              case 'parent':
1514                  $k = 'parent_id';
1515                  break;
1516              case 'left':
1517                  $k = 'lft';
1518                  break;
1519              case 'right':
1520                  $k = 'rgt';
1521                  break;
1522              default:
1523                  $k = $this->_tbl_key;
1524                  break;
1525          }
1526  
1527          // Get the node data.
1528          $query = $this->_db->getQuery(true);
1529          $query->select($this->_tbl_key . ', parent_id, level, lft, rgt');
1530          $query->from($this->_tbl);
1531          $query->where($k . ' = ' . (int) $id);
1532          $this->_db->setQuery($query, 0, 1);
1533  
1534          $row = $this->_db->loadObject();
1535  
1536          // Check for a database error or no $row returned
1537          if ((!$row) || ($this->_db->getErrorNum()))
1538          {
1539              $e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_GETNODE_FAILED', get_class($this), $this->_db->getErrorMsg()));
1540              $this->setError($e);
1541              return false;
1542          }
1543  
1544          // Do some simple calculations.
1545          $row->numChildren = (int) ($row->rgt - $row->lft - 1) / 2;
1546          $row->width = (int) $row->rgt - $row->lft + 1;
1547  
1548          return $row;
1549      }
1550  
1551      /**
1552       * Method to get various data necessary to make room in the tree at a location
1553       * for a node and its children.  The returned data object includes conditions
1554       * for SQL WHERE clauses for updating left and right id values to make room for
1555       * the node as well as the new left and right ids for the node.
1556       *
1557       * @param   object   $referenceNode  A node object with at least a 'lft' and 'rgt' with
1558       * which to make room in the tree around for a new node.
1559       * @param   integer  $nodeWidth      The width of the node for which to make room in the tree.
1560       * @param   string   $position       The position relative to the reference node where the room
1561       * should be made.
1562       *
1563       * @return  mixed    Boolean false on failure or data object on success.
1564       *
1565       * @since   11.1
1566       */
1567  	protected function _getTreeRepositionData($referenceNode, $nodeWidth, $position = 'before')
1568      {
1569          // Make sure the reference an object with a left and right id.
1570          if (!is_object($referenceNode) && isset($referenceNode->lft) && isset($referenceNode->rgt))
1571          {
1572              return false;
1573          }
1574  
1575          // A valid node cannot have a width less than 2.
1576          if ($nodeWidth < 2)
1577          {
1578              return false;
1579          }
1580  
1581          // Initialise variables.
1582          $k = $this->_tbl_key;
1583          $data = new stdClass;
1584  
1585          // Run the calculations and build the data object by reference position.
1586          switch ($position)
1587          {
1588              case 'first-child':
1589                  $data->left_where = 'lft > ' . $referenceNode->lft;
1590                  $data->right_where = 'rgt >= ' . $referenceNode->lft;
1591  
1592                  $data->new_lft = $referenceNode->lft + 1;
1593                  $data->new_rgt = $referenceNode->lft + $nodeWidth;
1594                  $data->new_parent_id = $referenceNode->$k;
1595                  $data->new_level = $referenceNode->level + 1;
1596                  break;
1597  
1598              case 'last-child':
1599                  $data->left_where = 'lft > ' . ($referenceNode->rgt);
1600                  $data->right_where = 'rgt >= ' . ($referenceNode->rgt);
1601  
1602                  $data->new_lft = $referenceNode->rgt;
1603                  $data->new_rgt = $referenceNode->rgt + $nodeWidth - 1;
1604                  $data->new_parent_id = $referenceNode->$k;
1605                  $data->new_level = $referenceNode->level + 1;
1606                  break;
1607  
1608              case 'before':
1609                  $data->left_where = 'lft >= ' . $referenceNode->lft;
1610                  $data->right_where = 'rgt >= ' . $referenceNode->lft;
1611  
1612                  $data->new_lft = $referenceNode->lft;
1613                  $data->new_rgt = $referenceNode->lft + $nodeWidth - 1;
1614                  $data->new_parent_id = $referenceNode->parent_id;
1615                  $data->new_level = $referenceNode->level;
1616                  break;
1617  
1618              default:
1619              case 'after':
1620                  $data->left_where = 'lft > ' . $referenceNode->rgt;
1621                  $data->right_where = 'rgt > ' . $referenceNode->rgt;
1622  
1623                  $data->new_lft = $referenceNode->rgt + 1;
1624                  $data->new_rgt = $referenceNode->rgt + $nodeWidth;
1625                  $data->new_parent_id = $referenceNode->parent_id;
1626                  $data->new_level = $referenceNode->level;
1627                  break;
1628          }
1629  
1630          if ($this->_debug)
1631          {
1632              echo "\nRepositioning Data for $position" . "\n-----------------------------------" . "\nLeft Where:    $data->left_where"
1633                  . "\nRight Where:   $data->right_where" . "\nNew Lft:       $data->new_lft" . "\nNew Rgt:       $data->new_rgt"
1634                  . "\nNew Parent ID: $data->new_parent_id" . "\nNew Level:     $data->new_level" . "\n";
1635          }
1636  
1637          return $data;
1638      }
1639  
1640      /**
1641       * Method to create a log table in the buffer optionally showing the query and/or data.
1642       *
1643       * @param   boolean  $showData   True to show data
1644       * @param   boolean  $showQuery  True to show query
1645       *
1646       * @return  void
1647       *
1648       * @since   11.1
1649       */
1650  	protected function _logtable($showData = true, $showQuery = true)
1651      {
1652          $sep = "\n" . str_pad('', 40, '-');
1653          $buffer = '';
1654          if ($showQuery)
1655          {
1656              $buffer .= "\n" . $this->_db->getQuery() . $sep;
1657          }
1658  
1659          if ($showData)
1660          {
1661              $query = $this->_db->getQuery(true);
1662              $query->select($this->_tbl_key . ', parent_id, lft, rgt, level');
1663              $query->from($this->_tbl);
1664              $query->order($this->_tbl_key);
1665              $this->_db->setQuery($query);
1666  
1667              $rows = $this->_db->loadRowList();
1668              $buffer .= sprintf("\n| %4s | %4s | %4s | %4s |", $this->_tbl_key, 'par', 'lft', 'rgt');
1669              $buffer .= $sep;
1670  
1671              foreach ($rows as $row)
1672              {
1673                  $buffer .= sprintf("\n| %4s | %4s | %4s | %4s |", $row[0], $row[1], $row[2], $row[3]);
1674              }
1675              $buffer .= $sep;
1676          }
1677          echo $buffer;
1678      }
1679  
1680      /**
1681       * Method to run an update query and check for a database error
1682       *
1683       * @param   string  $query         The query.
1684       * @param   string  $errorMessage  Unused.
1685       *
1686       * @return  boolean  False on exception
1687       *
1688       * @since   11.1
1689       */
1690  	protected function _runQuery($query, $errorMessage)
1691      {
1692          $this->_db->setQuery($query);
1693  
1694          // Check for a database error.
1695          if (!$this->_db->query())
1696          {
1697              $e = new JException(JText::sprintf('$errorMessage', get_class($this), $this->_db->getErrorMsg()));
1698              $this->setError($e);
1699              $this->_unlock();
1700              return false;
1701          }
1702          if ($this->_debug)
1703          {
1704              $this->_logtable();
1705          }
1706      }
1707  
1708  }


Generated: Tue Apr 3 11:40:28 2012 Cross-referenced by PHPXref 0.7.1