PageRenderTime 54ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/joomla/database/tablenested.php

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