| [ Index ] |
PHP Cross Reference of Joomla 2.5.4 DE |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Tue Apr 3 11:40:28 2012 | Cross-referenced by PHPXref 0.7.1 |