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