# Source for file NestedSet.php

Documentation is available at NestedSet.php

1. <?php
2. /*
3. * $Id: NestedSet.php 2263 2007-08-20 07:45:29Z romanb $
4. *
5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16. *
17. * This software consists of voluntary contributions made by many individuals
18. * and is licensed under the LGPL. For more information, see
19. * <http://www.phpdoctrine.com>.
20. */
21. /**
22. * Doctrine_Node_NestedSet
23. *
24. * @package Doctrine
25. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
26. * @category Object Relational Mapping
27. * @link www.phpdoctrine.com
28. * @since 1.0
29. * @version $Revision: 2263 $
30. * @author Joe Simms <joe.simms@websites4.com>
31. * @author Roman Borschel <roman@code-factory.org>
32. */
33. class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Interface
34. {
35. /**
36. * test if node has previous sibling
37. *
38. * @return bool
39. */
40. public function hasPrevSibling()
41. {
42. return $this->isValidNode($this->getPrevSibling());
43. }
45. /**
46. * test if node has next sibling
47. *
48. * @return bool
49. */
50. public function hasNextSibling()
51. {
52. return $this->isValidNode($this->getNextSibling());
53. }
55. /**
56. * test if node has children
57. *
58. * @return bool
59. */
60. public function hasChildren()
61. {
62. return (($this->getRightValue() - $this->getLeftValue() ) >1 );
63. }
65. /**
66. * test if node has parent
67. *
68. * @return bool
69. */
70. public function hasParent()
71. {
72. return !$this->isRoot();
73. }
75. /**
76. * gets record of prev sibling or empty record
77. *
78. * @return object Doctrine_Record
79. */
80. public function getPrevSibling()
81. {
82. $baseAlias = $this->_tree->getBaseAlias();
83. $q = $this->_tree->getBaseQuery();
84. $q = $q->addWhere("$baseAlias.rgt = ?", $this->getLeftValue() - 1);
85. $q = $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
86. $result = $q->execute();
88. if (count($result) <= 0) {
89. return false;
90. }
92. if ($result instanceof Doctrine_Collection) {
93. $sibling = $result->getFirst();
94. } else if (is_array($result)) {
95. $sibling = array_shift($result);
96. }
98. return $sibling;
99. }
101. /**
102. * gets record of next sibling or empty record
103. *
104. * @return object Doctrine_Record
105. */
106. public function getNextSibling()
107. {
108. $baseAlias = $this->_tree->getBaseAlias();
109. $q = $this->_tree->getBaseQuery();
110. $q = $q->addWhere("$baseAlias.lft = ?", $this->getRightValue() + 1);
111. $q = $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
112. $result = $q->execute();
114. if (count($result) <= 0) {
115. return false;
116. }
118. if ($result instanceof Doctrine_Collection) {
119. $sibling = $result->getFirst();
120. } else if (is_array($result)) {
121. $sibling = array_shift($result);
122. }
124. return $sibling;
125. }
127. /**
128. * gets siblings for node
129. *
130. * @return array array of sibling Doctrine_Record objects
131. */
132. public function getSiblings($includeNode = false)
133. {
134. $parent = $this->getParent();
135. $siblings = array();
136. if ($parent->exists()) {
137. foreach ($parent->getNode()->getChildren() as $child) {
138. if ($this->isEqualTo($child) && !$includeNode) {
139. continue;
140. }
141. $siblings[] = $child;
142. }
143. }
144. return $siblings;
145. }
147. /**
148. * gets record of first child or empty record
149. *
150. * @return object Doctrine_Record
151. */
152. public function getFirstChild()
153. {
154. $baseAlias = $this->_tree->getBaseAlias();
155. $q = $this->_tree->getBaseQuery();
156. $q->addWhere("$baseAlias.lft = ?", $this->getLeftValue() + 1);
157. $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
158. $result = $q->execute();
160. if (count($result) <= 0) {
161. return false;
162. }
164. if ($result instanceof Doctrine_Collection) {
165. $child = $result->getFirst();
166. } else if (is_array($result)) {
167. $child = array_shift($result);
168. }
170. return $child;
171. }
173. /**
174. * gets record of last child or empty record
175. *
176. * @return object Doctrine_Record
177. */
178. public function getLastChild()
179. {
180. $baseAlias = $this->_tree->getBaseAlias();
181. $q = $this->_tree->getBaseQuery();
182. $q->addWhere("$baseAlias.rgt = ?", $this->getRightValue() - 1);
183. $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
184. $result = $q->execute();
186. if (count($result) <= 0) {
187. return false;
188. }
190. if ($result instanceof Doctrine_Collection) {
191. $child = $result->getFirst();
192. } else if (is_array($result)) {
193. $child = array_shift($result);
194. }
196. return $child;
197. }
199. /**
200. * gets children for node (direct descendants only)
201. *
202. * @return mixed The children of the node or FALSE if the node has no children.
203. */
204. public function getChildren()
205. {
206. return $this->getDescendants(1);
207. }
209. /**
210. * gets descendants for node (direct descendants only)
211. *
212. * @return mixed The descendants of the node or FALSE if the node has no descendants.
213. * @todo Currently all descendants are fetched, no matter the depth. Maybe there is a better
214. * solution with less overhead.
215. */
216. public function getDescendants($depth = null, $includeNode = false)
217. {
218. $baseAlias = $this->_tree->getBaseAlias();
219. $q = $this->_tree->getBaseQuery();
220. $params = array($this->record->get('lft'), $this->record->get('rgt'));
222. if ($includeNode) {
223. $q->addWhere("$baseAlias.lft >= ? AND $baseAlias.rgt <= ?", $params)->addOrderBy("$baseAlias.lft asc");
224. } else {
225. $q->addWhere("$baseAlias.lft > ? AND $baseAlias.rgt < ?", $params)->addOrderBy("$baseAlias.lft asc");
226. }
228. if ($depth !== null) {
229. $q->addWhere("$baseAlias.level <= ?", $this->record['level'] + $depth);
230. }
232. $q = $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
233. $result = $q->execute();
235. if (count($result) <= 0) {
236. return false;
237. }
239. return $result;
240. }
242. /**
243. * gets record of parent or empty record
244. *
245. * @return object Doctrine_Record
246. */
247. public function getParent()
248. {
249. $baseAlias = $this->_tree->getBaseAlias();
250. $q = $this->_tree->getBaseQuery();
251. $q->addWhere("$baseAlias.lft < ? AND $baseAlias.rgt > ?", array($this->getLeftValue(), $this->getRightValue()))
252. ->addOrderBy("$baseAlias.rgt asc");
253. $q = $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
254. $result = $q->execute();
256. if (count($result) <= 0) {
257. return false;
258. }
260. if ($result instanceof Doctrine_Collection) {
261. $parent = $result->getFirst();
262. } else if (is_array($result)) {
263. $parent = array_shift($result);
264. }
266. return $parent;
267. }
269. /**
270. * gets ancestors for node
271. *
272. * @param integer $deth The depth 'upstairs'.
273. * @return mixed The ancestors of the node or FALSE if the node has no ancestors (this
274. * basically means it's a root node).
275. */
276. public function getAncestors($depth = null)
277. {
278. $baseAlias = $this->_tree->getBaseAlias();
279. $q = $this->_tree->getBaseQuery();
280. $q->addWhere("$baseAlias.lft < ? AND $baseAlias.rgt > ?", array($this->getLeftValue(), $this->getRightValue()))
281. ->addOrderBy("$baseAlias.lft asc");
282. if ($depth !== null) {
283. $q->addWhere("$baseAlias.level >= ?", $this->record['level'] - $depth);
284. }
285. $q = $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
286. $ancestors = $q->execute();
287. if (count($ancestors) <= 0) {
288. return false;
289. }
290. return $ancestors;
291. }
293. /**
294. * gets path to node from root, uses record::toString() method to get node names
295. *
296. * @param string $seperator path seperator
297. * @param bool $includeNode whether or not to include node at end of path
298. * @return string string representation of path
299. */
300. public function getPath($seperator = ' > ', $includeRecord = false)
301. {
302. $path = array();
303. $ancestors = $this->getAncestors();
304. foreach ($ancestors as $ancestor) {
305. $path[] = $ancestor->__toString();
306. }
307. if ($includeRecord) {
308. $path[] = $this->getRecord()->__toString();
309. }
311. return implode($seperator, $path);
312. }
314. /**
315. * gets number of children (direct descendants)
316. *
317. * @return int
318. */
319. public function getNumberChildren()
320. {
321. return count($this->getChildren());
322. }
324. /**
325. * gets number of descendants (children and their children)
326. *
327. * @return int
328. */
329. public function getNumberDescendants()
330. {
331. return ($this->getRightValue() - $this->getLeftValue() - 1) / 2;
332. }
334. /**
335. * inserts node as parent of dest record
336. *
337. * @return bool
338. * @todo Wrap in transaction
339. */
340. public function insertAsParentOf(Doctrine_Record $dest)
341. {
342. // cannot insert a node that has already has a place within the tree
343. if ($this->isValidNode()) {
344. return false;
345. }
346. // cannot insert as parent of root
347. if ($dest->getNode()->isRoot()) {
348. return false;
349. }
350. $newRoot = $dest->getNode()->getRootValue();
351. $this->shiftRLValues($dest->getNode()->getLeftValue(), 1, $newRoot);
352. $this->shiftRLValues($dest->getNode()->getRightValue() + 2, 1, $newRoot);
354. $newLeft = $dest->getNode()->getLeftValue();
355. $newRight = $dest->getNode()->getRightValue() + 2;
357. $this->record['level'] = $dest['level'] - 1;
358. $this->insertNode($newLeft, $newRight, $newRoot);
360. return true;
361. }
363. /**
364. * inserts node as previous sibling of dest record
365. *
366. * @return bool
367. * @todo Wrap in transaction
368. */
369. public function insertAsPrevSiblingOf(Doctrine_Record $dest)
370. {
371. // cannot insert a node that has already has a place within the tree
372. if ($this->isValidNode())
373. return false;
375. $newLeft = $dest->getNode()->getLeftValue();
376. $newRight = $dest->getNode()->getLeftValue() + 1;
377. $newRoot = $dest->getNode()->getRootValue();
379. $this->shiftRLValues($newLeft, 2, $newRoot);
380. $this->record['level'] = $dest['level'];
381. $this->insertNode($newLeft, $newRight, $newRoot);
382. // update destination left/right values to prevent a refresh
383. // $dest->getNode()->setLeftValue($dest->getNode()->getLeftValue() + 2);
384. // $dest->getNode()->setRightValue($dest->getNode()->getRightValue() + 2);
386. return true;
387. }
389. /**
390. * inserts node as next sibling of dest record
391. *
392. * @return bool
393. * @todo Wrap in transaction
394. */
395. public function insertAsNextSiblingOf(Doctrine_Record $dest)
396. {
397. // cannot insert a node that has already has a place within the tree
398. if ($this->isValidNode())
399. return false;
401. $newLeft = $dest->getNode()->getRightValue() + 1;
402. $newRight = $dest->getNode()->getRightValue() + 2;
403. $newRoot = $dest->getNode()->getRootValue();
405. $this->shiftRLValues($newLeft, 2, $newRoot);
406. $this->record['level'] = $dest['level'];
407. $this->insertNode($newLeft, $newRight, $newRoot);
409. // update destination left/right values to prevent a refresh
410. // no need, node not affected
412. return true;
413. }
415. /**
416. * inserts node as first child of dest record
417. *
418. * @return bool
419. * @todo Wrap in transaction
420. */
421. public function insertAsFirstChildOf(Doctrine_Record $dest)
422. {
423. // cannot insert a node that has already has a place within the tree
424. if ($this->isValidNode())
425. return false;
427. $newLeft = $dest->getNode()->getLeftValue() + 1;
428. $newRight = $dest->getNode()->getLeftValue() + 2;
429. $newRoot = $dest->getNode()->getRootValue();
431. $this->shiftRLValues($newLeft, 2, $newRoot);
432. $this->record['level'] = $dest['level'] + 1;
433. $this->insertNode($newLeft, $newRight, $newRoot);
435. // update destination left/right values to prevent a refresh
436. // $dest->getNode()->setRightValue($dest->getNode()->getRightValue() + 2);
438. return true;
439. }
441. /**
442. * inserts node as last child of dest record
443. *
444. * @return bool
445. * @todo Wrap in transaction
446. */
447. public function insertAsLastChildOf(Doctrine_Record $dest)
448. {
449. // cannot insert a node that has already has a place within the tree
450. if ($this->isValidNode())
451. return false;
453. $newLeft = $dest->getNode()->getRightValue();
454. $newRight = $dest->getNode()->getRightValue() + 1;
455. $newRoot = $dest->getNode()->getRootValue();
457. $this->shiftRLValues($newLeft, 2, $newRoot);
458. $this->record['level'] = $dest['level'] + 1;
459. $this->insertNode($newLeft, $newRight, $newRoot);
461. // update destination left/right values to prevent a refresh
462. // $dest->getNode()->setRightValue($dest->getNode()->getRightValue() + 2);
464. return true;
465. }
467. /**
468. * Accomplishes moving of nodes between different trees.
469. * Used by the move* methods if the root values of the two nodes are different.
470. *
471. * @param Doctrine_Record $dest
472. * @param unknown_type $newLeftValue
473. * @param unknown_type $moveType
474. * @todo Better exception handling/wrapping
475. */
476. private function _moveBetweenTrees(Doctrine_Record $dest, $newLeftValue, $moveType)
477. {
478. $conn = $this->record->getTable()->getConnection();
480. try {
481. $conn->beginTransaction();
483. // Move between trees: Detach from old tree & insert into new tree
484. $newRoot = $dest->getNode()->getRootValue();
485. $oldRoot = $this->getRootValue();
486. $oldLft = $this->getLeftValue();
487. $oldRgt = $this->getRightValue();
488. $oldLevel = $this->record['level'];
490. // Prepare target tree for insertion, make room
491. $this->shiftRlValues($newLeftValue, $oldRgt - $oldLft - 1, $newRoot);
493. // Set new root id for this node
494. $this->setRootValue($newRoot);
495. $this->record->save();
497. // Close gap in old tree
498. $first = $oldRgt + 1;
499. $delta = $oldLft - $oldRgt - 1;
500. $this->shiftRLValues($first, $delta, $oldRoot);
502. // Insert this node as a new node
503. $this->setRightValue(0);
504. $this->setLeftValue(0);
506. switch ($moveType) {
507. case 'moveAsPrevSiblingOf':
508. $this->insertAsPrevSiblingOf($dest);
509. break;
510. case 'moveAsFirstChildOf':
511. $this->insertAsFirstChildOf($dest);
512. break;
513. case 'moveAsNextSiblingOf':
514. $this->insertAsNextSiblingOf($dest);
515. break;
516. case 'moveAsLastChildOf':
517. $this->insertAsLastChildOf($dest);
518. break;
519. default:
520. throw new Exception("Unknown move operation: $moveType.");
521. }
523. $diff = $oldRgt - $oldLft;
524. $this->setRightValue($this->getLeftValue() + ($oldRgt - $oldLft));
525. $this->record->save();
527. $newLevel = $this->record['level'];
528. $levelDiff = $newLevel - $oldLevel;
530. // Relocate descendants of the node
531. $diff = $this->getLeftValue() - $oldLft;
532. $componentName = $this->_tree->getBaseComponent();
533. $rootColName = $this->record->getTable()->getTree()->getAttribute('rootColumnName');
535. // Update lft/rgt/root/level for all descendants
536. $q = new Doctrine_Query($conn);
537. $q = $q->update($componentName)
538. ->set($componentName . '.lft', 'lft + ?', $diff)
539. ->set($componentName . '.rgt', 'rgt + ?', $diff)
540. ->set($componentName . '.level', 'level + ?', $levelDiff)
541. ->set($componentName . '.' . $rootColName, '?', $newRoot)
542. ->where($componentName . '.lft > ? AND ' . $componentName . '.rgt < ?',
543. array($oldLft, $oldRgt));
544. $q = $this->_tree->returnQueryWithRootId($q, $oldRoot);
545. $q->execute();
547. $conn->commit();
548. } catch (Exception $e) {
549. $conn->rollback();
550. throw $e;
551. }
552. }
554. /**
555. * moves node as prev sibling of dest record
556. *
557. */
558. public function moveAsPrevSiblingOf(Doctrine_Record $dest)
559. {
560. if ($dest->getNode()->getRootValue() != $this->getRootValue()) {
561. // Move between trees
562. $this->_moveBetweenTrees($dest, $dest->getNode()->getLeftValue(), __FUNCTION__);
563. } else {
564. // Move within the tree
565. $oldLevel = $this->record['level'];
566. $this->record['level'] = $dest['level'];
567. $this->updateNode($dest->getNode()->getLeftValue(), $this->record['level'] - $oldLevel);
568. }
569. }
571. /**
572. * moves node as next sibling of dest record
573. *
574. */
575. public function moveAsNextSiblingOf(Doctrine_Record $dest)
576. {
577. if ($dest->getNode()->getRootValue() != $this->getRootValue()) {
578. // Move between trees
579. $this->_moveBetweenTrees($dest, $dest->getNode()->getRightValue() + 1, __FUNCTION__);
580. } else {
581. // Move within tree
582. $oldLevel = $this->record['level'];
583. $this->record['level'] = $dest['level'];
584. $this->updateNode($dest->getNode()->getRightValue() + 1, $this->record['level'] - $oldLevel);
585. }
586. }
588. /**
589. * moves node as first child of dest record
590. *
591. */
592. public function moveAsFirstChildOf(Doctrine_Record $dest)
593. {
594. if ($dest->getNode()->getRootValue() != $this->getRootValue()) {
595. // Move between trees
596. $this->_moveBetweenTrees($dest, $dest->getNode()->getLeftValue() + 1, __FUNCTION__);
597. } else {
598. // Move within tree
599. $oldLevel = $this->record['level'];
600. $this->record['level'] = $dest['level'] + 1;
601. $this->updateNode($dest->getNode()->getLeftValue() + 1, $this->record['level'] - $oldLevel);
602. }
603. }
605. /**
606. * moves node as last child of dest record
607. *
608. */
609. public function moveAsLastChildOf(Doctrine_Record $dest)
610. {
611. if ($dest->getNode()->getRootValue() != $this->getRootValue()) {
612. // Move between trees
613. $this->_moveBetweenTrees($dest, $dest->getNode()->getRightValue(), __FUNCTION__);
614. } else {
615. // Move within tree
616. $oldLevel = $this->record['level'];
617. $this->record['level'] = $dest['level'] + 1;
618. $this->updateNode($dest->getNode()->getRightValue(), $this->record['level'] - $oldLevel);
619. }
620. }
622. /**
623. * Makes this node a root node. Only used in multiple-root trees.
624. *
625. * @todo Exception handling/wrapping
626. */
627. public function makeRoot($newRootId)
628. {
629. // TODO: throw exception instead?
630. if ($this->getLeftValue() == 1 || !$this->record->getTable()->getTree()->getAttribute('hasManyRoots')) {
631. return false;
632. }
634. $oldRgt = $this->getRightValue();
635. $oldLft = $this->getLeftValue();
636. $oldRoot = $this->getRootValue();
637. $oldLevel = $this->record['level'];
639. try {
640. $conn = $this->record->getTable()->getConnection();
641. $conn->beginTransaction();
643. // Detach from old tree (close gap in old tree)
644. $first = $oldRgt + 1;
645. $delta = $oldLft - $oldRgt - 1;
646. $this->shiftRLValues($first, $delta, $this->getRootValue());
648. // Set new lft/rgt/root/level values for root node
649. $this->setLeftValue(1);
650. $this->setRightValue($oldRgt - $oldLft + 1);
651. $this->setRootValue($newRootId);
652. $this->record['level'] = 0;
654. // Update descendants lft/rgt/root/level values
655. $diff = 1 - $oldLft;
656. $newRoot = $newRootId;
657. $componentName = $this->_tree->getBaseComponent();
658. $rootColName = $this->record->getTable()->getTree()->getAttribute('rootColumnName');
659. $q = new Doctrine_Query($conn);
660. $q = $q->update($componentName)
661. ->set($componentName . '.lft', 'lft + ?', $diff)
662. ->set($componentName . '.rgt', 'rgt + ?', $diff)
663. ->set($componentName . '.level', 'level - ?', $oldLevel)
664. ->set($componentName . '.' . $rootColName, '?', $newRoot)
665. ->where($componentName . '.lft > ? AND ' . $componentName . '.rgt < ?',
666. array($oldLft, $oldRgt));
667. $q = $this->_tree->returnQueryWithRootId($q, $oldRoot);
668. $q->execute();
670. $conn->commit();
672. } catch (Exception $e) {
673. $conn->rollback();
674. throw $e;
675. }
676. }
678. /**
679. * adds node as last child of record
680. *
681. */
682. public function addChild(Doctrine_Record $record)
683. {
684. $record->getNode()->insertAsLastChildOf($this->getRecord());
685. }
687. /**
688. * determines if node is leaf
689. *
690. * @return bool
691. */
692. public function isLeaf()
693. {
694. return (($this->getRightValue() - $this->getLeftValue()) == 1);
695. }
697. /**
698. * determines if node is root
699. *
700. * @return bool
701. */
702. public function isRoot()
703. {
704. return ($this->getLeftValue() == 1);
705. }
707. /**
708. * determines if node is equal to subject node
709. *
710. * @return bool
711. */
712. public function isEqualTo(Doctrine_Record $subj)
713. {
714. return (($this->getLeftValue() == $subj->getNode()->getLeftValue()) &&
715. ($this->getRightValue() == $subj->getNode()->getRightValue()) &&
716. ($this->getRootValue() == $subj->getNode()->getRootValue())
717. );
718. }
720. /**
721. * determines if node is child of subject node
722. *
723. * @return bool
724. */
725. public function isDescendantOf(Doctrine_Record $subj)
726. {
727. return (($this->getLeftValue() > $subj->getNode()->getLeftValue()) &&
728. ($this->getRightValue() < $subj->getNode()->getRightValue()) &&
729. ($this->getRootValue() == $subj->getNode()->getRootValue()));
730. }
732. /**
733. * determines if node is child of or sibling to subject node
734. *
735. * @return bool
736. */
737. public function isDescendantOfOrEqualTo(Doctrine_Record $subj)
738. {
739. return (($this->getLeftValue() >= $subj->getNode()->getLeftValue()) &&
740. ($this->getRightValue() <= $subj->getNode()->getRightValue()) &&
741. ($this->getRootValue() == $subj->getNode()->getRootValue()));
742. }
744. /**
745. * determines if node is valid
746. *
747. * @return bool
748. */
749. public function isValidNode($record = null)
750. {
751. if ($record === null) {
752. return ($this->getRightValue() > $this->getLeftValue());
753. } else if( $record instanceof Doctrine_Record ) {
754. return ($record->getNode()->getRightValue() > $record->getNode()->getLeftValue());
755. } else {
756. return false;
757. }
758. }
760. /**
761. * deletes node and it's descendants
762. * @todo Delete more efficiently. Wrap in transaction if needed.
763. */
764. public function delete()
765. {
766. // TODO: add the setting whether or not to delete descendants or relocate children
767. $oldRoot = $this->getRootValue();
768. $q = $this->_tree->getBaseQuery();
770. $baseAlias = $this->_tree->getBaseAlias();
771. $componentName = $this->_tree->getBaseComponent();
773. $q = $q->addWhere("$baseAlias.lft >= ? AND $baseAlias.rgt <= ?", array($this->getLeftValue(), $this->getRightValue()));
775. $q = $this->record->getTable()->getTree()->returnQueryWithRootId($q, $oldRoot);
777. $coll = $q->execute();
779. $coll->delete();
781. $first = $this->getRightValue() + 1;
782. $delta = $this->getLeftValue() - $this->getRightValue() - 1;
783. $this->shiftRLValues($first, $delta, $oldRoot);
785. return true;
786. }
788. /**
789. * sets node's left and right values and save's it
790. *
791. * @param int $destLeft node left value
792. * @param int $destRight node right value
793. */
794. private function insertNode($destLeft = 0, $destRight = 0, $destRoot = 1)
795. {
796. $this->setLeftValue($destLeft);
797. $this->setRightValue($destRight);
798. $this->setRootValue($destRoot);
799. $this->record->save();
800. }
802. /**
803. * move node's and its children to location $destLeft and updates rest of tree
804. *
805. * @param int $destLeft destination left value
806. * @todo Wrap in transaction
807. */
808. private function updateNode($destLeft, $levelDiff)
809. {
810. $componentName = $this->_tree->getBaseComponent();
811. $left = $this->getLeftValue();
812. $right = $this->getRightValue();
813. $rootId = $this->getRootValue();
815. $treeSize = $right - $left + 1;
817. // Make room in the new branch
818. $this->shiftRLValues($destLeft, $treeSize, $rootId);
820. if ($left >= $destLeft){ // src was shifted too?
821. $left += $treeSize;
822. $right += $treeSize;
823. }
825. // update level for descendants
826. $q = new Doctrine_Query();
827. $q = $q->update($componentName)
828. ->set($componentName . '.level', 'level + ?')
829. ->where($componentName . '.lft > ? AND ' . $componentName . '.rgt < ?',
830. array($levelDiff, $left, $right));
831. $q = $this->_tree->returnQueryWithRootId($q, $rootId);
832. $q->execute();
834. // now there's enough room next to target to move the subtree
835. $this->shiftRLRange($left, $right, $destLeft - $left, $rootId);
837. // correct values after source (close gap in old tree)
838. $this->shiftRLValues($right + 1, -$treeSize, $rootId);
840. $this->record->save();
841. $this->record->refresh();
842. }
844. /**
845. * adds '$delta' to all Left and Right values that are >= '$first'. '$delta' can also be negative.
846. *
847. * @param int $first First node to be shifted
848. * @param int $delta Value to be shifted by, can be negative
849. */
850. private function shiftRlValues($first, $delta, $rootId = 1)
851. {
852. $qLeft = new Doctrine_Query();
853. $qRight = new Doctrine_Query();
855. // shift left columns
856. $componentName = $this->_tree->getBaseComponent();
857. $qLeft = $qLeft->update($componentName)
858. ->set($componentName . '.lft', 'lft + ?')
859. ->where($componentName . '.lft >= ?', array($delta, $first));
861. $qLeft = $this->record->getTable()->getTree()->returnQueryWithRootId($qLeft, $rootId);
863. $resultLeft = $qLeft->execute();
865. // shift right columns
866. $resultRight = $qRight->update($componentName)
867. ->set($componentName . '.rgt', 'rgt + ?')
868. ->where($componentName . '.rgt >= ?', array($delta, $first));
870. $qRight = $this->record->getTable()->getTree()->returnQueryWithRootId($qRight, $rootId);
872. $resultRight = $qRight->execute();
873. }
875. /**
876. * adds '$delta' to all Left and Right values that are >= '$first' and <= '$last'.
877. * '$delta' can also be negative.
878. *
879. * @param int $first First node to be shifted (L value)
880. * @param int $last Last node to be shifted (L value)
881. * @param int $delta Value to be shifted by, can be negative
882. */
883. private function shiftRlRange($first, $last, $delta, $rootId = 1)
884. {
885. $qLeft = new Doctrine_Query();
886. $qRight = new Doctrine_Query();
888. // shift left column values
889. $componentName = $this->_tree->getBaseComponent();
890. $qLeft = $qLeft->update($componentName)
891. ->set($componentName . '.lft', 'lft + ?')
892. ->where($componentName . '.lft >= ? AND ' . $componentName . '.lft <= ?', array($delta, $first, $last));
894. $qLeft = $this->record->getTable()->getTree()->returnQueryWithRootId($qLeft, $rootId);
896. $resultLeft = $qLeft->execute();
898. // shift right column values
899. $qRight = $qRight->update($componentName)
900. ->set($componentName . '.rgt', 'rgt + ?')
901. ->where($componentName . '.rgt >= ? AND ' . $componentName . '.rgt <= ?', array($delta, $first, $last));
903. $qRight = $this->record->getTable()->getTree()->returnQueryWithRootId($qRight, $rootId);
905. $resultRight = $qRight->execute();
906. }
908. /**
909. * gets record's left value
910. *
911. * @return int
912. */
913. public function getLeftValue()
914. {
915. return $this->record->get('lft');
916. }
918. /**
919. * sets record's left value
920. *
921. * @param int
922. */
923. public function setLeftValue($lft)
924. {
925. $this->record->set('lft', $lft);
926. }
928. /**
929. * gets record's right value
930. *
931. * @return int
932. */
933. public function getRightValue()
934. {
935. return $this->record->get('rgt');
936. }
938. /**
939. * sets record's right value
940. *
941. * @param int
942. */
943. public function setRightValue($rgt)
944. {
945. $this->record->set('rgt', $rgt);
946. }
948. /**
949. * gets level (depth) of node in the tree
950. *
951. * @return int
952. */
953. public function getLevel()
954. {
955. if (!isset($this->record['level'])) {
956. $baseAlias = $this->_tree->getBaseAlias();
957. $componentName = $this->_tree->getBaseComponent();
958. $q = $this->_tree->getBaseQuery();
959. $q = $q->addWhere("$baseAlias.lft < ? AND $baseAlias.rgt > ?", array($this->getLeftValue(), $this->getRightValue()));
961. $q = $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
963. $coll = $q->execute();
965. $this->record['level'] = count($coll) ? count($coll) : 0;
966. }
967. return $this->record['level'];
968. }
970. /**
971. * get records root id value
972. *
973. */
974. public function getRootValue()
975. {
976. if ($this->_tree->getAttribute('hasManyRoots')) {
977. return $this->record->get($this->_tree->getAttribute('rootColumnName'));
978. }
979. return 1;
980. }
982. /**
983. * sets records root id value
984. *
985. * @param int
986. */
987. public function setRootValue($value)
988. {
989. if ($this->_tree->getAttribute('hasManyRoots')) {
990. $this->record->set($this->_tree->getAttribute('rootColumnName'), $value);
991. }
992. }
993. }