1 <?php
2 3 4 5 6 7
8
9
10 defined('FOF_INCLUDED') or die;
11
12 13 14 15 16 17 18 19 20
21 class FOFTableNested extends FOFTable
22 {
23
24 protected $treeDepth = null;
25
26
27 protected $treeRoot = null;
28
29
30 protected $treeParent = null;
31
32
33 protected $treeNestedGet = false;
34
35
36 protected $whereClauses = array();
37
38 39 40 41 42 43 44 45 46 47 48
49 public function __construct($table, $key, &$db, $config = array())
50 {
51 parent::__construct($table, $key, $db, $config);
52
53 if (!$this->hasField('lft') || !$this->hasField('rgt'))
54 {
55 throw new \RuntimeException("Table " . $this->getTableName() . " is not compatible with FOFTableNested: it does not have lft/rgt columns");
56 }
57 }
58
59 60 61 62 63
64 public function check()
65 {
66
67 if ($this->hasField('title') && $this->hasField('slug') && empty($this->slug))
68 {
69 $this->slug = FOFStringUtils::toSlug($this->title);
70 }
71
72
73
74 if ($this->hasField('hash') && $this->hasField('slug'))
75 {
76 $this->hash = sha1($this->slug);
77 }
78
79
80 $this->resetTreeCache();
81
82 return parent::check();
83 }
84
85 86 87 88 89 90 91 92 93 94 95
96 public function delete($oid = null)
97 {
98
99 if (!empty($oid))
100 {
101 $this->load($oid);
102 }
103
104 $k = $this->_tbl_key;
105 $pk = (!$oid) ? $this->$k : $oid;
106
107
108 if (!$pk)
109 {
110 throw new UnexpectedValueException('Null primary key not allowed.');
111 }
112
113
114
115 if (!$this->onBeforeDelete($oid))
116 {
117 return false;
118 }
119
120 $result = true;
121
122
123 if (!$this->isLeaf())
124 {
125
126 $table = $this->getClone();
127 $table->bind($this->getData());
128 $subNodes = $table->getDescendants();
129
130
131 if (!empty($subNodes))
132 {
133
134 foreach ($subNodes as $item)
135 {
136
137
138 if(!$item->delete($item->$k))
139 {
140
141
142 $result = false;
143 }
144 };
145
146
147 $this->load($pk);
148 }
149 }
150
151 if($result)
152 {
153
154 $query = $this->_db->getQuery(true);
155 $query->delete();
156 $query->from($this->_tbl);
157 $query->where($this->_tbl_key . ' = ' . $this->_db->q($pk));
158
159 $this->_db->setQuery($query)->execute();
160
161 $result = $this->onAfterDelete($oid);
162 }
163
164 return $result;
165 }
166
167 protected function onAfterDelete($oid)
168 {
169 $db = $this->getDbo();
170
171 $myLeft = $this->lft;
172 $myRight = $this->rgt;
173
174 $fldLft = $db->qn($this->getColumnAlias('lft'));
175 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
176
177
178 $width = $this->rgt - $this->lft + 1;
179
180
181 $db->transactionStart();
182
183 try
184 {
185
186 $query = $db->getQuery(true)
187 ->update($db->qn($this->getTableName()))
188 ->set($fldLft . ' = ' . $fldLft . ' - '.$width)
189 ->where($fldLft . ' > ' . $db->q($myLeft));
190 $db->setQuery($query)->execute();
191
192
193 $query = $db->getQuery(true)
194 ->update($db->qn($this->getTableName()))
195 ->set($fldRgt . ' = ' . $fldRgt . ' - '.$width)
196 ->where($fldRgt . ' > ' . $db->q($myRight));
197 $db->setQuery($query)->execute();
198
199
200 $db->transactionCommit();
201 }
202 catch (\Exception $e)
203 {
204
205 $db->transactionRollback();
206
207 throw $e;
208 }
209
210 return parent::onAfterDelete($oid);
211 }
212
213 214 215 216 217 218 219 220 221
222 public function reorder($where = '')
223 {
224 throw new RuntimeException('reorder() is not supported by FOFTableNested');
225 }
226
227 228 229 230 231 232 233 234 235 236
237 public function move($delta, $where = '')
238 {
239 throw new RuntimeException('move() is not supported by FOFTableNested');
240 }
241
242 243 244 245 246 247 248
249 public function create($data)
250 {
251 $newNode = $this->getClone();
252 $newNode->reset();
253 $newNode->bind($data);
254
255 if ($this->isRoot())
256 {
257 return $newNode->insertAsChildOf($this);
258 }
259 else
260 {
261 return $newNode->insertAsChildOf($this->getParent());
262 }
263 }
264
265 266 267 268 269 270 271 272
273 public function copy($cid = null)
274 {
275
276 if($cid)
277 {
278 $cid = (array) $cid;
279 }
280
281 FOFUtilsArray::toInteger($cid);
282 $k = $this->_tbl_key;
283
284 if (count($cid) < 1)
285 {
286 if ($this->$k)
287 {
288 $cid = array($this->$k);
289 }
290 else
291 {
292
293 $this->create($this->getData());
294
295 return $this;
296 }
297 }
298
299 foreach ($cid as $item)
300 {
301
302
303 if (!$item)
304 {
305 continue;
306 }
307
308 $this->load($item);
309
310 $this->create($this->getData());
311 }
312
313 return $this;
314 }
315
316 317 318 319 320 321 322
323 public function reset()
324 {
325 $this->resetTreeCache();
326
327 parent::reset();
328 }
329
330 331 332 333 334 335
336 public function insertAsRoot()
337 {
338
339 if($this->getId())
340 {
341 throw new RuntimeException(__METHOD__.' can be only used with new nodes');
342 }
343
344
345 $db = $this->getDbo();
346
347
348 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
349
350 $query = $db->getQuery(true)
351 ->select('MAX(' . $fldRgt . ')')
352 ->from($db->qn($this->getTableName()));
353 $maxRgt = $db->setQuery($query, 0, 1)->loadResult();
354
355 if (empty($maxRgt))
356 {
357 $maxRgt = 0;
358 }
359
360 $this->lft = ++$maxRgt;
361 $this->rgt = ++$maxRgt;
362
363 $this->store();
364
365 return $this;
366 }
367
368 369 370 371 372 373 374 375 376 377 378 379
380 public function insertAsFirstChildOf(FOFTableNested &$parentNode)
381 {
382 if($parentNode->lft >= $parentNode->rgt)
383 {
384 throw new RuntimeException('Invalid position values for the parent node');
385 }
386
387
388 $db = $this->getDbo();
389
390
391 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
392 $fldLft = $db->qn($this->getColumnAlias('lft'));
393
394
395 $pk = $this->getKeyName();
396 $this->$pk = null;
397
398
399 $myLeft = $parentNode->lft;
400
401
402 $this->lft = $myLeft + 1;
403 $this->rgt = $myLeft + 2;
404
405
406 $parentNode->rgt += 2;
407
408
409 $db->transactionStart();
410
411 try
412 {
413
414 $query = $db->getQuery(true)
415 ->update($db->qn($this->getTableName()))
416 ->set($fldLft . ' = ' . $fldLft . '+2')
417 ->where($fldLft . ' > ' . $db->q($myLeft));
418 $db->setQuery($query)->execute();
419
420 $query = $db->getQuery(true)
421 ->update($db->qn($this->getTableName()))
422 ->set($fldRgt . ' = ' . $fldRgt . '+ 2')
423 ->where($fldRgt . '>' . $db->q($myLeft));
424 $db->setQuery($query)->execute();
425
426
427 $this->store();
428
429
430 $db->transactionCommit();
431 }
432 catch (\Exception $e)
433 {
434
435 $db->transactionRollback();
436
437 throw $e;
438 }
439
440 return $this;
441 }
442
443 444 445 446 447 448 449 450 451 452 453 454
455 public function insertAsLastChildOf(FOFTableNested &$parentNode)
456 {
457 if($parentNode->lft >= $parentNode->rgt)
458 {
459 throw new RuntimeException('Invalid position values for the parent node');
460 }
461
462
463 $db = $this->getDbo();
464
465
466 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
467 $fldLft = $db->qn($this->getColumnAlias('lft'));
468
469
470 $pk = $this->getKeyName();
471 $this->$pk = null;
472
473
474 $myRight = $parentNode->rgt;
475
476
477 $this->lft = $myRight;
478 $this->rgt = $myRight + 1;
479
480
481 $parentNode->rgt += 2;
482
483
484 $db->transactionStart();
485
486 try
487 {
488
489 $query = $db->getQuery(true)
490 ->update($db->qn($this->getTableName()))
491 ->set($fldRgt . ' = ' . $fldRgt . '+2')
492 ->where($fldRgt . '>=' . $db->q($myRight));
493 $db->setQuery($query)->execute();
494
495 $query = $db->getQuery(true)
496 ->update($db->qn($this->getTableName()))
497 ->set($fldLft . ' = ' . $fldLft . '+2')
498 ->where($fldLft . '>' . $db->q($myRight));
499 $db->setQuery($query)->execute();
500
501
502 $this->store();
503
504
505 $db->transactionCommit();
506 }
507 catch (\Exception $e)
508 {
509
510 $db->transactionRollback();
511
512 throw $e;
513 }
514
515 return $this;
516 }
517
518 519 520 521 522 523 524 525 526 527
528 public function insertAsChildOf(FOFTableNested &$parentNode)
529 {
530 return $this->insertAsLastChildOf($parentNode);
531 }
532
533 534 535 536 537 538 539 540 541 542 543 544
545 public function insertLeftOf(FOFTableNested &$siblingNode)
546 {
547 if($siblingNode->lft >= $siblingNode->rgt)
548 {
549 throw new RuntimeException('Invalid position values for the sibling node');
550 }
551
552
553 $db = $this->getDbo();
554
555
556 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
557 $fldLft = $db->qn($this->getColumnAlias('lft'));
558
559
560 $pk = $this->getKeyName();
561 $this->$pk = null;
562
563
564 $myLeft = $siblingNode->lft;
565
566
567 $this->lft = $myLeft;
568 $this->rgt = $myLeft + 1;
569
570
571 $siblingNode->lft += 2;
572 $siblingNode->rgt += 2;
573
574 $db->transactionStart();
575
576 try
577 {
578 $db->setQuery(
579 $db->getQuery(true)
580 ->update($db->qn($this->getTableName()))
581 ->set($fldLft . ' = ' . $fldLft . '+2')
582 ->where($fldLft . ' >= ' . $db->q($myLeft))
583 )->execute();
584
585 $db->setQuery(
586 $db->getQuery(true)
587 ->update($db->qn($this->getTableName()))
588 ->set($fldRgt . ' = ' . $fldRgt . '+2')
589 ->where($fldRgt . ' > ' . $db->q($myLeft))
590 )->execute();
591
592 $this->store();
593
594
595 $db->transactionCommit();
596 }
597 catch (\Exception $e)
598 {
599 $db->transactionRollback();
600
601 throw $e;
602 }
603
604 return $this;
605 }
606
607 608 609 610 611 612 613 614 615 616 617
618 public function insertRightOf(FOFTableNested &$siblingNode)
619 {
620 if($siblingNode->lft >= $siblingNode->rgt)
621 {
622 throw new RuntimeException('Invalid position values for the sibling node');
623 }
624
625
626 $db = $this->getDbo();
627
628
629 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
630 $fldLft = $db->qn($this->getColumnAlias('lft'));
631
632
633 $pk = $this->getKeyName();
634 $this->$pk = null;
635
636
637 $myRight = $siblingNode->rgt;
638
639
640 $this->lft = $myRight + 1;
641 $this->rgt = $myRight + 2;
642
643 $db->transactionStart();
644
645 try
646 {
647 $db->setQuery(
648 $db->getQuery(true)
649 ->update($db->qn($this->getTableName()))
650 ->set($fldRgt . ' = ' . $fldRgt . '+2')
651 ->where($fldRgt . ' > ' . $db->q($myRight))
652 )->execute();
653
654 $db->setQuery(
655 $db->getQuery(true)
656 ->update($db->qn($this->getTableName()))
657 ->set($fldLft . ' = ' . $fldLft . '+2')
658 ->where($fldLft . ' > ' . $db->q($myRight))
659 )->execute();
660
661 $this->store();
662
663
664 $db->transactionCommit();
665 }
666 catch (\Exception $e)
667 {
668 $db->transactionRollback();
669
670 throw $e;
671 }
672
673 return $this;
674 }
675
676 677 678 679 680 681 682 683
684 public function insertAsSiblingOf(FOFTableNested &$siblingNode)
685 {
686 return $this->insertRightOf($siblingNode);
687 }
688
689 690 691 692 693 694 695
696 public function moveLeft()
697 {
698
699 if($this->lft >= $this->rgt)
700 {
701 throw new RuntimeException('Invalid position values for the current node');
702 }
703
704
705 if ($this->isRoot())
706 {
707 return $this;
708 }
709
710
711 $parentNode = $this->getParent();
712
713 if ($parentNode->lft == ($this->lft - 1))
714 {
715 return $this;
716 }
717
718
719 $db = $this->getDbo();
720 $table = $this->getClone();
721 $table->reset();
722 $leftSibling = $table->whereRaw($db->qn($this->getColumnAlias('rgt')) . ' = ' . $db->q($this->lft - 1))
723 ->get(0, 1)->current();
724
725
726 if (is_object($leftSibling) && ($leftSibling instanceof FOFTableNested))
727 {
728 return $this->moveToLeftOf($leftSibling);
729 }
730
731 return false;
732 }
733
734 735 736 737 738 739 740
741 public function moveRight()
742 {
743
744 if($this->lft >= $this->rgt)
745 {
746 throw new RuntimeException('Invalid position values for the current node');
747 }
748
749
750 if ($this->isRoot())
751 {
752 return $this;
753 }
754
755
756 $parentNode = $this->getParent();
757
758 if ($parentNode->rgt == ($this->rgt + 1))
759 {
760 return $this;
761 }
762
763
764 $db = $this->getDbo();
765
766 $table = $this->getClone();
767 $table->reset();
768 $rightSibling = $table->whereRaw($db->qn($this->getColumnAlias('lft')) . ' = ' . $db->q($this->rgt + 1))
769 ->get(0, 1)->current();
770
771
772 if (is_object($rightSibling) && ($rightSibling instanceof FOFTableNested))
773 {
774 return $this->moveToRightOf($rightSibling);
775 }
776
777 return false;
778 }
779
780 781 782 783 784 785 786 787 788 789 790
791 public function moveToLeftOf(FOFTableNested $siblingNode)
792 {
793
794 if($this->lft >= $this->rgt)
795 {
796 throw new RuntimeException('Invalid position values for the current node');
797 }
798
799 if($siblingNode->lft >= $siblingNode->rgt)
800 {
801 throw new RuntimeException('Invalid position values for the sibling node');
802 }
803
804 $db = $this->getDbo();
805 $left = $db->qn($this->getColumnAlias('lft'));
806 $right = $db->qn($this->getColumnAlias('rgt'));
807
808
809 $myLeft = $this->lft;
810 $myRight = $this->rgt;
811 $myWidth = $myRight - $myLeft + 1;
812
813
814 $sibLeft = $siblingNode->lft;
815
816
817 $db->transactionStart();
818
819 try
820 {
821
822 $query = $db->getQuery(true)
823 ->update($db->qn($this->getTableName()))
824 ->set("$left = " . $db->q(0) . " - $left")
825 ->set("$right = " . $db->q(0) . " - $right")
826 ->where($left . ' >= ' . $db->q($myLeft))
827 ->where($right . ' <= ' . $db->q($myRight));
828 $db->setQuery($query)->execute();
829
830
831 $query = $db->getQuery(true)
832 ->update($db->qn($this->getTableName()))
833 ->set($left . ' = ' . $left . ' - ' . $db->q($myWidth))
834 ->where($left . ' > ' . $db->q($myRight));
835 $db->setQuery($query)->execute();
836
837 $query = $db->getQuery(true)
838 ->update($db->qn($this->getTableName()))
839 ->set($right . ' = ' . $right . ' - ' . $db->q($myWidth))
840 ->where($right . ' > ' . $db->q($myRight));
841 $db->setQuery($query)->execute();
842
843
844 $newSibLeft = ($sibLeft > $myRight) ? $sibLeft - $myWidth : $sibLeft;
845
846 $query = $db->getQuery(true)
847 ->update($db->qn($this->getTableName()))
848 ->set($right . ' = ' . $right . ' + ' . $db->q($myWidth))
849 ->where($right . ' >= ' . $db->q($newSibLeft));
850 $db->setQuery($query)->execute();
851
852 $query = $db->getQuery(true)
853 ->update($db->qn($this->getTableName()))
854 ->set($left . ' = ' . $left . ' + ' . $db->q($myWidth))
855 ->where($left . ' >= ' . $db->q($newSibLeft));
856 $db->setQuery($query)->execute();
857
858
859 $moveRight = $newSibLeft - $myLeft;
860
861 $query = $db->getQuery(true)
862 ->update($db->qn($this->getTableName()))
863 ->set($left . ' = ' . $db->q(0) . ' - ' . $left . ' + ' . $db->q($moveRight))
864 ->set($right . ' = ' . $db->q(0) . ' - ' . $right . ' + ' . $db->q($moveRight))
865 ->where($left . ' <= 0 - ' . $db->q($myLeft))
866 ->where($right . ' >= 0 - ' . $db->q($myRight));
867 $db->setQuery($query)->execute();
868
869
870 $db->transactionCommit();
871 }
872 catch (\Exception $e)
873 {
874 $db->transactionRollback();
875
876 throw $e;
877 }
878
879
880 $this->load();
881
882 return $this;
883 }
884
885 886 887 888 889 890 891 892 893 894 895
896 public function moveToRightOf(FOFTableNested $siblingNode)
897 {
898
899 if($this->lft >= $this->rgt)
900 {
901 throw new RuntimeException('Invalid position values for the current node');
902 }
903
904 if($siblingNode->lft >= $siblingNode->rgt)
905 {
906 throw new RuntimeException('Invalid position values for the sibling node');
907 }
908
909 $db = $this->getDbo();
910 $left = $db->qn($this->getColumnAlias('lft'));
911 $right = $db->qn($this->getColumnAlias('rgt'));
912
913
914 $myLeft = $this->lft;
915 $myRight = $this->rgt;
916 $myWidth = $myRight - $myLeft + 1;
917
918
919 $sibRight = $siblingNode->rgt;
920
921
922 $db->transactionStart();
923
924 try
925 {
926
927 $query = $db->getQuery(true)
928 ->update($db->qn($this->getTableName()))
929 ->set("$left = " . $db->q(0) . " - $left")
930 ->set("$right = " . $db->q(0) . " - $right")
931 ->where($left . ' >= ' . $db->q($myLeft))
932 ->where($right . ' <= ' . $db->q($myRight));
933 $db->setQuery($query)->execute();
934
935
936 $query = $db->getQuery(true)
937 ->update($db->qn($this->getTableName()))
938 ->set($left . ' = ' . $left . ' - ' . $db->q($myWidth))
939 ->where($left . ' > ' . $db->q($myRight));
940 $db->setQuery($query)->execute();
941
942 $query = $db->getQuery(true)
943 ->update($db->qn($this->getTableName()))
944 ->set($right . ' = ' . $right . ' - ' . $db->q($myWidth))
945 ->where($right . ' > ' . $db->q($myRight));
946 $db->setQuery($query)->execute();
947
948
949 $newSibRight = ($sibRight > $myRight) ? $sibRight - $myWidth : $sibRight;
950
951 $query = $db->getQuery(true)
952 ->update($db->qn($this->getTableName()))
953 ->set($left . ' = ' . $left . ' + ' . $db->q($myWidth))
954 ->where($left . ' > ' . $db->q($newSibRight));
955 $db->setQuery($query)->execute();
956
957 $query = $db->getQuery(true)
958 ->update($db->qn($this->getTableName()))
959 ->set($right . ' = ' . $right . ' + ' . $db->q($myWidth))
960 ->where($right . ' > ' . $db->q($newSibRight));
961 $db->setQuery($query)->execute();
962
963
964 $moveRight = ($sibRight > $myRight) ? $sibRight - $myRight : $sibRight - $myRight + $myWidth;
965
966 $query = $db->getQuery(true)
967 ->update($db->qn($this->getTableName()))
968 ->set($left . ' = ' . $db->q(0) . ' - ' . $left . ' + ' . $db->q($moveRight))
969 ->set($right . ' = ' . $db->q(0) . ' - ' . $right . ' + ' . $db->q($moveRight))
970 ->where($left . ' <= 0 - ' . $db->q($myLeft))
971 ->where($right . ' >= 0 - ' . $db->q($myRight));
972 $db->setQuery($query)->execute();
973
974
975 $db->transactionCommit();
976 }
977 catch (\Exception $e)
978 {
979 $db->transactionRollback();
980
981 throw $e;
982 }
983
984
985 $this->load();
986
987 return $this;
988 }
989
990 991 992 993 994 995 996 997
998 public function makeNextSiblingOf(FOFTableNested $siblingNode)
999 {
1000 return $this->moveToRightOf($siblingNode);
1001 }
1002
1003 1004 1005 1006 1007 1008 1009 1010
1011 public function makeSiblingOf(FOFTableNested $siblingNode)
1012 {
1013 return $this->makeNextSiblingOf($siblingNode);
1014 }
1015
1016 1017 1018 1019 1020 1021 1022 1023
1024 public function makePreviousSiblingOf(FOFTableNested $siblingNode)
1025 {
1026 return $this->moveToLeftOf($siblingNode);
1027 }
1028
1029 1030 1031 1032 1033 1034 1035 1036 1037
1038 public function makeFirstChildOf(FOFTableNested $parentNode)
1039 {
1040
1041 if($this->lft >= $this->rgt)
1042 {
1043 throw new RuntimeException('Invalid position values for the current node');
1044 }
1045
1046 if($parentNode->lft >= $parentNode->rgt)
1047 {
1048 throw new RuntimeException('Invalid position values for the parent node');
1049 }
1050
1051 $db = $this->getDbo();
1052 $left = $db->qn($this->getColumnAlias('lft'));
1053 $right = $db->qn($this->getColumnAlias('rgt'));
1054
1055
1056 $myLeft = $this->lft;
1057 $myRight = $this->rgt;
1058 $myWidth = $myRight - $myLeft + 1;
1059
1060
1061 $parentLeft = $parentNode->lft;
1062
1063
1064 $db->transactionStart();
1065
1066 try
1067 {
1068
1069 $query = $db->getQuery(true)
1070 ->update($db->qn($this->getTableName()))
1071 ->set("$left = " . $db->q(0) . " - $left")
1072 ->set("$right = " . $db->q(0) . " - $right")
1073 ->where($left . ' >= ' . $db->q($myLeft))
1074 ->where($right . ' <= ' . $db->q($myRight));
1075 $db->setQuery($query)->execute();
1076
1077
1078 $query = $db->getQuery(true)
1079 ->update($db->qn($this->getTableName()))
1080 ->set($left . ' = ' . $left . ' - ' . $db->q($myWidth))
1081 ->where($left . ' > ' . $db->q($myRight));
1082 $db->setQuery($query)->execute();
1083
1084 $query = $db->getQuery(true)
1085 ->update($db->qn($this->getTableName()))
1086 ->set($right . ' = ' . $right . ' - ' . $db->q($myWidth))
1087 ->where($right . ' > ' . $db->q($myRight));
1088 $db->setQuery($query)->execute();
1089
1090
1091 $newParentLeft = ($parentLeft > $myRight) ? $parentLeft - $myWidth : $parentLeft;
1092
1093 $query = $db->getQuery(true)
1094 ->update($db->qn($this->getTableName()))
1095 ->set($right . ' = ' . $right . ' + ' . $db->q($myWidth))
1096 ->where($right . ' >= ' . $db->q($newParentLeft));
1097 $db->setQuery($query)->execute();
1098
1099 $query = $db->getQuery(true)
1100 ->update($db->qn($this->getTableName()))
1101 ->set($left . ' = ' . $left . ' + ' . $db->q($myWidth))
1102 ->where($left . ' > ' . $db->q($newParentLeft));
1103 $db->setQuery($query)->execute();
1104
1105
1106 $moveRight = $newParentLeft - $myLeft + 1;
1107
1108 $query = $db->getQuery(true)
1109 ->update($db->qn($this->getTableName()))
1110 ->set($left . ' = ' . $db->q(0) . ' - ' . $left . ' + ' . $db->q($moveRight))
1111 ->set($right . ' = ' . $db->q(0) . ' - ' . $right . ' + ' . $db->q($moveRight))
1112 ->where($left . ' <= 0 - ' . $db->q($myLeft))
1113 ->where($right . ' >= 0 - ' . $db->q($myRight));
1114 $db->setQuery($query)->execute();
1115
1116
1117 $db->transactionCommit();
1118 }
1119 catch (\Exception $e)
1120 {
1121 $db->transactionRollback();
1122
1123 throw $e;
1124 }
1125
1126
1127 $this->load();
1128
1129 return $this;
1130 }
1131
1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
1142 public function makeLastChildOf(FOFTableNested $parentNode)
1143 {
1144
1145 if($this->lft >= $this->rgt)
1146 {
1147 throw new RuntimeException('Invalid position values for the current node');
1148 }
1149
1150 if($parentNode->lft >= $parentNode->rgt)
1151 {
1152 throw new RuntimeException('Invalid position values for the parent node');
1153 }
1154
1155 $db = $this->getDbo();
1156 $left = $db->qn($this->getColumnAlias('lft'));
1157 $right = $db->qn($this->getColumnAlias('rgt'));
1158
1159
1160 $myLeft = $this->lft;
1161 $myRight = $this->rgt;
1162 $myWidth = $myRight - $myLeft + 1;
1163
1164
1165 $parentRight = $parentNode->rgt;
1166
1167
1168 $db->transactionStart();
1169
1170 try
1171 {
1172
1173 $query = $db->getQuery(true)
1174 ->update($db->qn($this->getTableName()))
1175 ->set("$left = " . $db->q(0) . " - $left")
1176 ->set("$right = " . $db->q(0) . " - $right")
1177 ->where($left . ' >= ' . $db->q($myLeft))
1178 ->where($right . ' <= ' . $db->q($myRight));
1179 $db->setQuery($query)->execute();
1180
1181
1182 $query = $db->getQuery(true)
1183 ->update($db->qn($this->getTableName()))
1184 ->set($left . ' = ' . $left . ' - ' . $db->q($myWidth))
1185 ->where($left . ' > ' . $db->q($myRight));
1186 $db->setQuery($query)->execute();
1187
1188 $query = $db->getQuery(true)
1189 ->update($db->qn($this->getTableName()))
1190 ->set($right . ' = ' . $right . ' - ' . $db->q($myWidth))
1191 ->where($right . ' > ' . $db->q($myRight));
1192 $db->setQuery($query)->execute();
1193
1194
1195 $newLeft = ($parentRight > $myRight) ? $parentRight - $myWidth : $parentRight;
1196
1197 $query = $db->getQuery(true)
1198 ->update($db->qn($this->getTableName()))
1199 ->set($left . ' = ' . $left . ' + ' . $db->q($myWidth))
1200 ->where($left . ' >= ' . $db->q($newLeft));
1201 $db->setQuery($query)->execute();
1202
1203 $query = $db->getQuery(true)
1204 ->update($db->qn($this->getTableName()))
1205 ->set($right . ' = ' . $right . ' + ' . $db->q($myWidth))
1206 ->where($right . ' >= ' . $db->q($newLeft));
1207 $db->setQuery($query)->execute();
1208
1209
1210 $moveRight = ($parentRight > $myRight) ? $parentRight - $myRight - 1 : $parentRight - $myRight - 1 + $myWidth;
1211
1212 $query = $db->getQuery(true)
1213 ->update($db->qn($this->getTableName()))
1214 ->set($left . ' = ' . $db->q(0) . ' - ' . $left . ' + ' . $db->q($moveRight))
1215 ->set($right . ' = ' . $db->q(0) . ' - ' . $right . ' + ' . $db->q($moveRight))
1216 ->where($left . ' <= 0 - ' . $db->q($myLeft))
1217 ->where($right . ' >= 0 - ' . $db->q($myRight));
1218 $db->setQuery($query)->execute();
1219
1220
1221 $db->transactionCommit();
1222 }
1223 catch (\Exception $e)
1224 {
1225 $db->transactionRollback();
1226
1227 throw $e;
1228 }
1229
1230
1231 $this->load();
1232
1233 return $this;
1234 }
1235
1236 1237 1238 1239 1240 1241 1242 1243
1244 public function makeChildOf(FOFTableNested $parentNode)
1245 {
1246 return $this->makeLastChildOf($parentNode);
1247 }
1248
1249 1250 1251 1252 1253 1254
1255 public function makeRoot()
1256 {
1257
1258 if ($this->isRoot())
1259 {
1260 return $this;
1261 }
1262
1263
1264 $myRoot = $this->getRoot();
1265
1266
1267 if ($this->equals($myRoot))
1268 {
1269 return $this;
1270 }
1271
1272
1273 $this->moveToRightOf($myRoot);
1274 $this->treeDepth = 0;
1275
1276 return $this;
1277 }
1278
1279 1280 1281 1282 1283 1284 1285
1286 public function getLevel()
1287 {
1288
1289 if($this->lft >= $this->rgt)
1290 {
1291 throw new RuntimeException('Invalid position values for the current node');
1292 }
1293
1294 if (is_null($this->treeDepth))
1295 {
1296 $db = $this->getDbo();
1297
1298 $fldLft = $db->qn($this->getColumnAlias('lft'));
1299 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
1300
1301 $query = $db->getQuery(true)
1302 ->select('(COUNT(' . $db->qn('parent') . '.' . $fldLft . ') - 1) AS ' . $db->qn('depth'))
1303 ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'))
1304 ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'))
1305 ->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft)
1306 ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt)
1307 ->where($db->qn('node') . '.' . $fldLft . ' = ' . $db->q($this->lft))
1308 ->group($db->qn('node') . '.' . $fldLft)
1309 ->order($db->qn('node') . '.' . $fldLft . ' ASC');
1310
1311 $this->treeDepth = $db->setQuery($query, 0, 1)->loadResult();
1312 }
1313
1314 return $this->treeDepth;
1315 }
1316
1317 1318 1319 1320 1321 1322 1323
1324 public function getParent()
1325 {
1326
1327 if($this->lft >= $this->rgt)
1328 {
1329 throw new RuntimeException('Invalid position values for the current node');
1330 }
1331
1332 if ($this->isRoot())
1333 {
1334 return $this;
1335 }
1336
1337 if (empty($this->treeParent) || !is_object($this->treeParent) || !($this->treeParent instanceof FOFTableNested))
1338 {
1339 $db = $this->getDbo();
1340
1341 $fldLft = $db->qn($this->getColumnAlias('lft'));
1342 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
1343
1344 $query = $db->getQuery(true)
1345 ->select($db->qn('parent') . '.' . $fldLft)
1346 ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'))
1347 ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'))
1348 ->where($db->qn('node') . '.' . $fldLft . ' > ' . $db->qn('parent') . '.' . $fldLft)
1349 ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt)
1350 ->where($db->qn('node') . '.' . $fldLft . ' = ' . $db->q($this->lft))
1351 ->order($db->qn('parent') . '.' . $fldLft . ' DESC');
1352 $targetLft = $db->setQuery($query, 0, 1)->loadResult();
1353
1354 $table = $this->getClone();
1355 $table->reset();
1356 $this->treeParent = $table
1357 ->whereRaw($fldLft . ' = ' . $db->q($targetLft))
1358 ->get()->current();
1359 }
1360
1361 return $this->treeParent;
1362 }
1363
1364 1365 1366 1367 1368
1369 public function isRoot()
1370 {
1371
1372 if ($this->lft == 1)
1373 {
1374 return true;
1375 }
1376
1377
1378 return $this->getLevel() == 0;
1379 }
1380
1381 1382 1383 1384 1385 1386 1387
1388 public function isLeaf()
1389 {
1390
1391 if($this->lft >= $this->rgt)
1392 {
1393 throw new RuntimeException('Invalid position values for the current node');
1394 }
1395
1396 return ($this->rgt - 1) == $this->lft;
1397 }
1398
1399 1400 1401 1402 1403 1404 1405
1406 public function isChild()
1407 {
1408 return !$this->isRoot();
1409 }
1410
1411 1412 1413 1414 1415 1416 1417 1418 1419
1420 public function isDescendantOf(FOFTableNested $otherNode)
1421 {
1422
1423 if($this->lft >= $this->rgt)
1424 {
1425 throw new RuntimeException('Invalid position values for the current node');
1426 }
1427
1428 if($otherNode->lft >= $otherNode->rgt)
1429 {
1430 throw new RuntimeException('Invalid position values for the other node');
1431 }
1432
1433 return ($otherNode->lft < $this->lft) && ($otherNode->rgt > $this->rgt);
1434 }
1435
1436 1437 1438 1439 1440 1441 1442 1443 1444
1445 public function isSelfOrDescendantOf(FOFTableNested $otherNode)
1446 {
1447
1448 if($this->lft >= $this->rgt)
1449 {
1450 throw new RuntimeException('Invalid position values for the current node');
1451 }
1452
1453 if($otherNode->lft >= $otherNode->rgt)
1454 {
1455 throw new RuntimeException('Invalid position values for the other node');
1456 }
1457
1458 return ($otherNode->lft <= $this->lft) && ($otherNode->rgt >= $this->rgt);
1459 }
1460
1461 1462 1463 1464 1465 1466 1467 1468
1469 public function isAncestorOf(FOFTableNested $otherNode)
1470 {
1471 return $otherNode->isDescendantOf($this);
1472 }
1473
1474 1475 1476 1477 1478 1479 1480 1481
1482 public function isSelfOrAncestorOf(FOFTableNested $otherNode)
1483 {
1484 return $otherNode->isSelfOrDescendantOf($this);
1485 }
1486
1487 1488 1489 1490 1491 1492 1493 1494 1495
1496 public function equals(FOFTableNested &$node)
1497 {
1498
1499 if($this->lft >= $this->rgt)
1500 {
1501 throw new RuntimeException('Invalid position values for the current node');
1502 }
1503
1504 if($node->lft >= $node->rgt)
1505 {
1506 throw new RuntimeException('Invalid position values for the other node');
1507 }
1508
1509 return (
1510 ($this->getId() == $node->getId())
1511 && ($this->lft == $node->lft)
1512 && ($this->rgt == $node->rgt)
1513 );
1514 }
1515
1516 1517 1518 1519 1520 1521 1522 1523
1524 public function insideSubtree(FOFTableNested $otherNode)
1525 {
1526 return $this->isDescendantOf($otherNode);
1527 }
1528
1529 1530 1531 1532 1533 1534 1535
1536 public function inSameScope(FOFTableNested $otherNode)
1537 {
1538 if ($this->isLeaf())
1539 {
1540 return $otherNode->isLeaf();
1541 }
1542 elseif ($this->isRoot())
1543 {
1544 return $otherNode->isRoot();
1545 }
1546 elseif ($this->isChild())
1547 {
1548 return $otherNode->isChild();
1549 }
1550 else
1551 {
1552 return false;
1553 }
1554 }
1555
1556 1557 1558 1559 1560
1561 protected function scopeAncestorsAndSelf()
1562 {
1563 $this->treeNestedGet = true;
1564
1565 $db = $this->getDbo();
1566
1567 $fldLft = $db->qn($this->getColumnAlias('lft'));
1568 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
1569
1570 $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' >= ' . $db->qn('node') . '.' . $fldLft);
1571 $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' <= ' . $db->qn('node') . '.' . $fldRgt);
1572 $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' = ' . $db->q($this->lft));
1573 }
1574
1575 1576 1577 1578 1579
1580 protected function scopeAncestors()
1581 {
1582 $this->treeNestedGet = true;
1583
1584 $db = $this->getDbo();
1585
1586 $fldLft = $db->qn($this->getColumnAlias('lft'));
1587 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
1588
1589 $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' > ' . $db->qn('node') . '.' . $fldLft);
1590 $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' < ' . $db->qn('node') . '.' . $fldRgt);
1591 $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' = ' . $db->q($this->lft));
1592 }
1593
1594 1595 1596 1597 1598
1599 protected function scopeSiblingsAndSelf()
1600 {
1601 $db = $this->getDbo();
1602
1603 $fldLft = $db->qn($this->getColumnAlias('lft'));
1604 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
1605
1606 $parent = $this->getParent();
1607 $this->whereRaw($db->qn('node') . '.' . $fldLft . ' > ' . $db->q($parent->lft));
1608 $this->whereRaw($db->qn('node') . '.' . $fldRgt . ' < ' . $db->q($parent->rgt));
1609 }
1610
1611 1612 1613 1614 1615 1616 1617
1618 protected function scopeSiblings()
1619 {
1620 $this->scopeSiblingsAndSelf();
1621 $this->scopeWithoutSelf();
1622 }
1623
1624 1625 1626 1627 1628
1629 protected function scopeLeaves()
1630 {
1631 $db = $this->getDbo();
1632
1633 $fldLft = $db->qn($this->getColumnAlias('lft'));
1634 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
1635
1636 $this->whereRaw($db->qn('node') . '.' . $fldLft . ' = ' . $db->qn('node') . '.' . $fldRgt . ' - ' . $db->q(1));
1637 }
1638
1639 1640 1641 1642 1643
1644 protected function scopeDescendantsAndSelf()
1645 {
1646 $this->treeNestedGet = true;
1647
1648 $db = $this->getDbo();
1649
1650 $fldLft = $db->qn($this->getColumnAlias('lft'));
1651 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
1652
1653 $this->whereRaw($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft);
1654 $this->whereRaw($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt);
1655 $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' = ' . $db->q($this->lft));
1656 }
1657
1658 1659 1660 1661 1662
1663 protected function scopeDescendants()
1664 {
1665 $this->treeNestedGet = true;
1666
1667 $db = $this->getDbo();
1668
1669 $fldLft = $db->qn($this->getColumnAlias('lft'));
1670 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
1671
1672 $this->whereRaw($db->qn('node') . '.' . $fldLft . ' > ' . $db->qn('parent') . '.' . $fldLft);
1673 $this->whereRaw($db->qn('node') . '.' . $fldLft . ' < ' . $db->qn('parent') . '.' . $fldRgt);
1674 $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' = ' . $db->q($this->lft));
1675 }
1676
1677 1678 1679 1680 1681
1682 protected function scopeImmediateDescendants()
1683 {
1684
1685 if($this->lft >= $this->rgt)
1686 {
1687 throw new RuntimeException('Invalid position values for the current node');
1688 }
1689
1690 $db = $this->getDbo();
1691
1692 $fldLft = $db->qn($this->getColumnAlias('lft'));
1693 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
1694
1695 $subQuery = $db->getQuery(true)
1696 ->select(array(
1697 $db->qn('node') . '.' . $fldLft,
1698 '(COUNT(*) - 1) AS ' . $db->qn('depth')
1699 ))
1700 ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'))
1701 ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'))
1702 ->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft)
1703 ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt)
1704 ->where($db->qn('node') . '.' . $fldLft . ' = ' . $db->q($this->lft))
1705 ->group($db->qn('node') . '.' . $fldLft)
1706 ->order($db->qn('node') . '.' . $fldLft . ' ASC');
1707
1708 $query = $db->getQuery(true)
1709 ->select(array(
1710 $db->qn('node') . '.' . $fldLft,
1711 '(COUNT(' . $db->qn('parent') . '.' . $fldLft . ') - (' .
1712 $db->qn('sub_tree') . '.' . $db->qn('depth') . ' + 1)) AS ' . $db->qn('depth')
1713 ))
1714 ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'))
1715 ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'))
1716 ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('sub_parent'))
1717 ->join('CROSS', '(' . $subQuery . ') AS ' . $db->qn('sub_tree'))
1718 ->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft)
1719 ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt)
1720 ->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('sub_parent') . '.' . $fldLft)
1721 ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('sub_parent') . '.' . $fldRgt)
1722 ->where($db->qn('sub_parent') . '.' . $fldLft . ' = ' . $db->qn('sub_tree') . '.' . $fldLft)
1723 ->group($db->qn('node') . '.' . $fldLft)
1724 ->having(array(
1725 $db->qn('depth') . ' > ' . $db->q(0),
1726 $db->qn('depth') . ' <= ' . $db->q(1),
1727 ))
1728 ->order($db->qn('node') . '.' . $fldLft . ' ASC');
1729
1730 $leftValues = $db->setQuery($query)->loadColumn();
1731
1732 if (empty($leftValues))
1733 {
1734 $leftValues = array(0);
1735 }
1736
1737 array_walk($leftValues, function (&$item, $key) use (&$db)
1738 {
1739 $item = $db->q($item);
1740 });
1741
1742 $this->whereRaw($db->qn('node') . '.' . $fldLft . ' IN (' . implode(',', $leftValues) . ')');
1743 }
1744
1745 1746 1747 1748 1749 1750 1751
1752 public function withoutNode(FOFTableNested $node)
1753 {
1754 $db = $this->getDbo();
1755
1756 $fldLft = $db->qn($this->getColumnAlias('lft'));
1757
1758 $this->whereRaw('NOT(' . $db->qn('node') . '.' . $fldLft . ' = ' . $db->q($node->lft) . ')');
1759 }
1760
1761 1762 1763 1764 1765 1766 1767
1768 protected function scopeWithoutSelf()
1769 {
1770 $this->withoutNode($this);
1771 }
1772
1773 1774 1775 1776 1777 1778 1779
1780 protected function scopeWithoutRoot()
1781 {
1782 $rootNode = $this->getRoot();
1783 $this->withoutNode($rootNode);
1784 }
1785
1786 1787 1788 1789 1790 1791 1792
1793 public function getRoot()
1794 {
1795
1796 if($this->lft >= $this->rgt)
1797 {
1798 throw new RuntimeException('Invalid position values for the current node');
1799 }
1800
1801
1802 if ($this->isRoot())
1803 {
1804 return $this;
1805 }
1806
1807 if (empty($this->treeRoot) || !is_object($this->treeRoot) || !($this->treeRoot instanceof FOFTableNested))
1808 {
1809 $this->treeRoot = null;
1810
1811
1812 $db = $this->getDbo();
1813
1814 $fldLft = $db->qn($this->getColumnAlias('lft'));
1815 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
1816
1817 $subQuery = $db->getQuery(true)
1818 ->select('MIN(' . $fldLft . ')')
1819 ->from($db->qn($this->getTableName()));
1820
1821 try
1822 {
1823 $table = $this->getClone();
1824 $table->reset();
1825 $root = $table
1826 ->whereRaw($fldLft . ' = (' . (string)$subQuery . ')')
1827 ->get(0, 1)->current();
1828
1829 if ($this->isDescendantOf($root))
1830 {
1831 $this->treeRoot = $root;
1832 }
1833 }
1834 catch (\RuntimeException $e)
1835 {
1836
1837 throw new \RuntimeException("No root found for table " . $this->getTableName() . ", node lft=" . $this->lft);
1838 }
1839
1840
1841 if (is_null($this->treeRoot))
1842 {
1843
1844 $query = $db->getQuery(true)
1845 ->select(array(
1846 $db->qn('node') . '.' . $fldLft,
1847 '(COUNT(' . $db->qn('parent') . '.' . $fldLft . ') - 1) AS ' . $db->qn('depth')
1848 ))
1849 ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'))
1850 ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'))
1851 ->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft)
1852 ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt)
1853 ->where($db->qn('node') . '.' . $fldLft . ' < ' . $db->q($this->lft))
1854 ->where($db->qn('node') . '.' . $fldRgt . ' > ' . $db->q($this->rgt))
1855 ->having($db->qn('depth') . ' = ' . $db->q(0))
1856 ->group($db->qn('node') . '.' . $fldLft);
1857
1858
1859 $targetLeft = $db->setQuery($query)->loadResult();
1860
1861 if (empty($targetLeft))
1862 {
1863
1864 throw new \RuntimeException("No root found for table " . $this->getTableName() . ", node lft=" . $this->lft);
1865 }
1866
1867 try
1868 {
1869 $table = $this->getClone();
1870 $table->reset();
1871 $this->treeRoot = $table
1872 ->whereRaw($fldLft . ' = ' . $db->q($targetLeft))
1873 ->get(0, 1)->current();
1874 }
1875 catch (\RuntimeException $e)
1876 {
1877
1878 throw new \RuntimeException("No root found for table " . $this->getTableName() . ", node lft=" . $this->lft);
1879 }
1880 }
1881 }
1882
1883 return $this->treeRoot;
1884 }
1885
1886 1887 1888 1889 1890 1891 1892 1893
1894 public function getAncestorsAndSelf()
1895 {
1896 $this->scopeAncestorsAndSelf();
1897
1898 return $this->get();
1899 }
1900
1901 1902 1903 1904 1905 1906 1907
1908 public function getAncestorsAndSelfWithoutRoot()
1909 {
1910 $this->scopeAncestorsAndSelf();
1911 $this->scopeWithoutRoot();
1912
1913 return $this->get();
1914 }
1915
1916 1917 1918 1919 1920 1921 1922 1923
1924 public function getAncestors()
1925 {
1926 $this->scopeAncestorsAndSelf();
1927 $this->scopeWithoutSelf();
1928
1929 return $this->get();
1930 }
1931
1932 1933 1934 1935 1936 1937 1938
1939 public function getAncestorsWithoutRoot()
1940 {
1941 $this->scopeAncestors();
1942 $this->scopeWithoutRoot();
1943
1944 return $this->get();
1945 }
1946
1947 1948 1949 1950 1951 1952 1953
1954 public function getSiblingsAndSelf()
1955 {
1956 $this->scopeSiblingsAndSelf();
1957
1958 return $this->get();
1959 }
1960
1961 1962 1963 1964 1965 1966 1967
1968 public function getSiblings()
1969 {
1970 $this->scopeSiblings();
1971
1972 return $this->get();
1973 }
1974
1975 1976 1977 1978 1979 1980 1981 1982
1983 public function getLeaves()
1984 {
1985 $this->scopeLeaves();
1986
1987 return $this->get();
1988 }
1989
1990 1991 1992 1993 1994 1995 1996 1997 1998
1999 public function getDescendantsAndSelf()
2000 {
2001 $this->scopeDescendantsAndSelf();
2002
2003 return $this->get();
2004 }
2005
2006 2007 2008 2009 2010 2011 2012 2013 2014
2015 public function getDescendants()
2016 {
2017 $this->scopeDescendants();
2018
2019 return $this->get();
2020 }
2021
2022 2023 2024 2025 2026 2027 2028 2029
2030 public function getImmediateDescendants()
2031 {
2032 $this->scopeImmediateDescendants();
2033
2034 return $this->get();
2035 }
2036
2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049
2050 public function getNestedList($column = 'title', $key = null, $seperator = ' ')
2051 {
2052 $db = $this->getDbo();
2053
2054 $fldLft = $db->qn($this->getColumnAlias('lft'));
2055 $fldRgt = $db->qn($this->getColumnAlias('rgt'));
2056
2057 if (empty($key) || !$this->hasField($key))
2058 {
2059 $key = $this->getKeyName();
2060 }
2061
2062 if (empty($column))
2063 {
2064 $column = 'title';
2065 }
2066
2067 $fldKey = $db->qn($this->getColumnAlias($key));
2068 $fldColumn = $db->qn($this->getColumnAlias($column));
2069
2070 $query = $db->getQuery(true)
2071 ->select(array(
2072 $db->qn('node') . '.' . $fldKey,
2073 $db->qn('node') . '.' . $fldColumn,
2074 '(COUNT(' . $db->qn('parent') . '.' . $fldKey . ') - 1) AS ' . $db->qn('depth')
2075 ))
2076 ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'))
2077 ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'))
2078 ->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft)
2079 ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt)
2080 ->group($db->qn('node') . '.' . $fldLft)
2081 ->order($db->qn('node') . '.' . $fldLft . ' ASC');
2082
2083 $tempResults = $db->setQuery($query)->loadAssocList();
2084 $ret = array();
2085
2086 if (!empty($tempResults))
2087 {
2088 foreach ($tempResults as $row)
2089 {
2090 $ret[$row[$key]] = str_repeat($seperator, $row['depth']) . $row[$column];
2091 }
2092 }
2093
2094 return $ret;
2095 }
2096
2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110
2111 public function findByPath($path)
2112 {
2113
2114 }
2115
2116 public function isValid()
2117 {
2118
2119 }
2120
2121 public function rebuild()
2122 {
2123
2124 }
2125
2126 2127 2128 2129 2130
2131 protected function resetTreeCache()
2132 {
2133 $this->treeDepth = null;
2134 $this->treeRoot = null;
2135 $this->treeParent = null;
2136 $this->treeNestedGet = false;
2137
2138 return $this;
2139 }
2140
2141 2142 2143 2144 2145 2146 2147 2148 2149
2150 public function whereRaw($rawWhereClause)
2151 {
2152 $this->whereClauses[] = $rawWhereClause;
2153
2154 return $this;
2155 }
2156
2157 2158 2159 2160 2161
2162 protected function buildQuery()
2163 {
2164 $db = $this->getDbo();
2165
2166 $query = $db->getQuery(true)
2167 ->select($db->qn('node') . '.*')
2168 ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'));
2169
2170 if ($this->treeNestedGet)
2171 {
2172 $query
2173 ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'));
2174 }
2175
2176
2177 if (count($this->whereClauses))
2178 {
2179 foreach ($this->whereClauses as $clause)
2180 {
2181 $query->where($clause);
2182 }
2183 }
2184
2185 return $query;
2186 }
2187
2188 2189 2190 2191 2192 2193 2194 2195 2196
2197 public function get($limitstart = 0, $limit = 0)
2198 {
2199 $limitstart = max($limitstart, 0);
2200 $limit = max($limit, 0);
2201
2202 $query = $this->buildQuery();
2203 $db = $this->getDbo();
2204 $db->setQuery($query, $limitstart, $limit);
2205 $cursor = $db->execute();
2206
2207 $dataCollection = FOFDatabaseIterator::getIterator($db->name, $cursor, null, $this->config['_table_class']);
2208
2209 return $dataCollection;
2210 }
2211 }
2212