1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
20:
21: App::uses('ModelBehavior', 'Model');
22:
23: 24: 25: 26: 27: 28: 29: 30: 31:
32: class TreeBehavior extends ModelBehavior {
33:
34: 35: 36: 37: 38:
39: public $errors = array();
40:
41: 42: 43: 44: 45:
46: protected $_defaults = array(
47: 'parent' => 'parent_id', 'left' => 'lft', 'right' => 'rght', 'level' => null,
48: 'scope' => '1 = 1', 'type' => 'nested', '__parentChange' => false, 'recursive' => -1
49: );
50:
51: 52: 53: 54: 55:
56: protected $_deletedRow = array();
57:
58: 59: 60: 61: 62: 63: 64:
65: public function setup(Model $Model, $config = array()) {
66: if (isset($config[0])) {
67: $config['type'] = $config[0];
68: unset($config[0]);
69: }
70: $settings = $config + $this->_defaults;
71:
72: if (in_array($settings['scope'], $Model->getAssociated('belongsTo'))) {
73: $data = $Model->getAssociated($settings['scope']);
74: $Parent = $Model->{$settings['scope']};
75: $settings['scope'] = $Model->escapeField($data['foreignKey']) . ' = ' . $Parent->escapeField();
76: $settings['recursive'] = 0;
77: }
78: $this->settings[$Model->alias] = $settings;
79: }
80:
81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91:
92: public function afterSave(Model $Model, $created, $options = array()) {
93: extract($this->settings[$Model->alias]);
94: if ($created) {
95: if ((isset($Model->data[$Model->alias][$parent])) && $Model->data[$Model->alias][$parent]) {
96: return $this->_setParent($Model, $Model->data[$Model->alias][$parent], $created);
97: }
98: } elseif ($this->settings[$Model->alias]['__parentChange']) {
99: $this->settings[$Model->alias]['__parentChange'] = false;
100: if ($level) {
101: $this->_setChildrenLevel($Model, $Model->id);
102: }
103: return $this->_setParent($Model, $Model->data[$Model->alias][$parent]);
104: }
105: }
106:
107: 108: 109: 110: 111: 112: 113:
114: protected function _setChildrenLevel(Model $Model, $id) {
115: $settings = $Model->Behaviors->Tree->settings[$Model->alias];
116: $primaryKey = $Model->primaryKey;
117: $depths = array($id => (int)$Model->data[$Model->alias][$settings['level']]);
118:
119: $children = $Model->children(
120: $id,
121: false,
122: array($primaryKey, $settings['parent'], $settings['level']),
123: $settings['left'],
124: null,
125: 1,
126: -1
127: );
128:
129: foreach ($children as $node) {
130: $parentIdValue = $node[$Model->alias][$settings['parent']];
131: $depth = (int)$depths[$parentIdValue] + 1;
132: $depths[$node[$Model->alias][$primaryKey]] = $depth;
133:
134: $Model->updateAll(
135: array($Model->escapeField($settings['level']) => $depth),
136: array($Model->escapeField($primaryKey) => $node[$Model->alias][$primaryKey])
137: );
138: }
139: }
140:
141: 142: 143: 144: 145: 146: 147:
148: public function beforeFind(Model $Model, $query) {
149: if ($Model->findQueryType === 'threaded' && !isset($query['parent'])) {
150: $query['parent'] = $this->settings[$Model->alias]['parent'];
151: }
152: return $query;
153: }
154:
155: 156: 157: 158: 159: 160: 161: 162: 163:
164: public function beforeDelete(Model $Model, $cascade = true) {
165: extract($this->settings[$Model->alias]);
166: $data = $Model->find('first', array(
167: 'conditions' => array($Model->escapeField($Model->primaryKey) => $Model->id),
168: 'fields' => array($Model->escapeField($left), $Model->escapeField($right)),
169: 'order' => false,
170: 'recursive' => -1));
171: if ($data) {
172: $this->_deletedRow[$Model->alias] = current($data);
173: }
174: return true;
175: }
176:
177: 178: 179: 180: 181: 182: 183: 184:
185: public function afterDelete(Model $Model) {
186: extract($this->settings[$Model->alias]);
187: $data = $this->_deletedRow[$Model->alias];
188: $this->_deletedRow[$Model->alias] = null;
189:
190: if (!$data[$right] || !$data[$left]) {
191: return true;
192: }
193: $diff = $data[$right] - $data[$left] + 1;
194:
195: if ($diff > 2) {
196: if (is_string($scope)) {
197: $scope = array($scope);
198: }
199: $scope[][$Model->escapeField($left) . " BETWEEN ? AND ?"] = array($data[$left] + 1, $data[$right] - 1);
200: $Model->deleteAll($scope);
201: }
202: $this->_sync($Model, $diff, '-', '> ' . $data[$right]);
203: return true;
204: }
205:
206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217:
218: public function beforeSave(Model $Model, $options = array()) {
219: extract($this->settings[$Model->alias]);
220:
221: $this->_addToWhitelist($Model, array($left, $right));
222: if ($level) {
223: $this->_addToWhitelist($Model, $level);
224: }
225: $parentIsSet = array_key_exists($parent, $Model->data[$Model->alias]);
226:
227: if (!$Model->id || !$Model->exists()) {
228: if ($parentIsSet && $Model->data[$Model->alias][$parent]) {
229: $parentNode = $this->_getNode($Model, $Model->data[$Model->alias][$parent]);
230: if (!$parentNode) {
231: return false;
232: }
233:
234: $Model->data[$Model->alias][$left] = 0;
235: $Model->data[$Model->alias][$right] = 0;
236: if ($level) {
237: $Model->data[$Model->alias][$level] = (int)$parentNode[$Model->alias][$level] + 1;
238: }
239: return true;
240: }
241:
242: $edge = $this->_getMax($Model, $scope, $right, $recursive);
243: $Model->data[$Model->alias][$left] = $edge + 1;
244: $Model->data[$Model->alias][$right] = $edge + 2;
245: if ($level) {
246: $Model->data[$Model->alias][$level] = 0;
247: }
248: return true;
249: }
250:
251: if ($parentIsSet) {
252: if ($Model->data[$Model->alias][$parent] != $Model->field($parent)) {
253: $this->settings[$Model->alias]['__parentChange'] = true;
254: }
255: if (!$Model->data[$Model->alias][$parent]) {
256: $Model->data[$Model->alias][$parent] = null;
257: $this->_addToWhitelist($Model, $parent);
258: if ($level) {
259: $Model->data[$Model->alias][$level] = 0;
260: }
261: return true;
262: }
263:
264: $values = $this->_getNode($Model, $Model->id);
265: if (empty($values)) {
266: return false;
267: }
268: list($node) = array_values($values);
269:
270: $parentNode = $this->_getNode($Model, $Model->data[$Model->alias][$parent]);
271: if (!$parentNode) {
272: return false;
273: }
274: list($parentNode) = array_values($parentNode);
275:
276: if (($node[$left] < $parentNode[$left]) && ($parentNode[$right] < $node[$right])) {
277: return false;
278: }
279: if ($node[$Model->primaryKey] === $parentNode[$Model->primaryKey]) {
280: return false;
281: }
282: if ($level) {
283: $Model->data[$Model->alias][$level] = (int)$parentNode[$level] + 1;
284: }
285: }
286:
287: return true;
288: }
289:
290: 291: 292: 293: 294: 295: 296:
297: protected function _getNode(Model $Model, $id) {
298: $settings = $this->settings[$Model->alias];
299: $fields = array($Model->primaryKey, $settings['parent'], $settings['left'], $settings['right']);
300: if ($settings['level']) {
301: $fields[] = $settings['level'];
302: }
303:
304: return $Model->find('first', array(
305: 'conditions' => array($Model->escapeField() => $id),
306: 'fields' => $fields,
307: 'recursive' => $settings['recursive'],
308: 'order' => false,
309: ));
310: }
311:
312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323:
324: public function childCount(Model $Model, $id = null, $direct = false) {
325: if (is_array($id)) {
326: extract(array_merge(array('id' => null), $id));
327: }
328: if ($id === null && $Model->id) {
329: $id = $Model->id;
330: } elseif (!$id) {
331: $id = null;
332: }
333: extract($this->settings[$Model->alias]);
334:
335: if ($direct) {
336: return $Model->find('count', array('conditions' => array($scope, $Model->escapeField($parent) => $id)));
337: }
338:
339: if ($id === null) {
340: return $Model->find('count', array('conditions' => $scope));
341: } elseif ($Model->id === $id && isset($Model->data[$Model->alias][$left]) && isset($Model->data[$Model->alias][$right])) {
342: $data = $Model->data[$Model->alias];
343: } else {
344: $data = $this->_getNode($Model, $id);
345: if (!$data) {
346: return 0;
347: }
348: $data = $data[$Model->alias];
349: }
350: return ($data[$right] - $data[$left] - 1) / 2;
351: }
352:
353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369:
370: public function children(Model $Model, $id = null, $direct = false, $fields = null, $order = null, $limit = null, $page = 1, $recursive = null) {
371: $options = array();
372: if (is_array($id)) {
373: $options = $this->_getOptions($id);
374: extract(array_merge(array('id' => null), $id));
375: }
376: $overrideRecursive = $recursive;
377:
378: if ($id === null && $Model->id) {
379: $id = $Model->id;
380: } elseif (!$id) {
381: $id = null;
382: }
383:
384: extract($this->settings[$Model->alias]);
385:
386: if ($overrideRecursive !== null) {
387: $recursive = $overrideRecursive;
388: }
389: if (!$order) {
390: $order = $Model->escapeField($left) . " asc";
391: }
392: if ($direct) {
393: $conditions = array($scope, $Model->escapeField($parent) => $id);
394: return $Model->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
395: }
396:
397: if (!$id) {
398: $conditions = $scope;
399: } else {
400: $result = array_values((array)$Model->find('first', array(
401: 'conditions' => array($scope, $Model->escapeField() => $id),
402: 'fields' => array($left, $right),
403: 'recursive' => $recursive,
404: 'order' => false,
405: )));
406:
407: if (empty($result) || !isset($result[0])) {
408: return array();
409: }
410: $conditions = array($scope,
411: $Model->escapeField($right) . ' <' => $result[0][$right],
412: $Model->escapeField($left) . ' >' => $result[0][$left]
413: );
414: }
415: $options = array_merge(compact(
416: 'conditions', 'fields', 'order', 'limit', 'page', 'recursive'
417: ), $options);
418: return $Model->find('all', $options);
419: }
420:
421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432:
433: public function generateTreeList(Model $Model, $conditions = null, $keyPath = null, $valuePath = null, $spacer = '_', $recursive = null) {
434: $overrideRecursive = $recursive;
435: extract($this->settings[$Model->alias]);
436: if ($overrideRecursive !== null) {
437: $recursive = $overrideRecursive;
438: }
439:
440: $fields = null;
441: if (!$keyPath && !$valuePath && $Model->hasField($Model->displayField)) {
442: $fields = array($Model->primaryKey, $Model->displayField, $left, $right);
443: }
444:
445: $conditions = (array)$conditions;
446: if ($scope) {
447: $conditions[] = $scope;
448: }
449:
450: $order = $Model->escapeField($left) . ' asc';
451: $results = $Model->find('all', compact('conditions', 'fields', 'order', 'recursive'));
452:
453: return $this->formatTreeList($Model, $results, compact('keyPath', 'valuePath', 'spacer'));
454: }
455:
456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472:
473: public function formatTreeList(Model $Model, array $results, array $options = array()) {
474: if (empty($results)) {
475: return array();
476: }
477: $defaults = array(
478: 'keyPath' => null,
479: 'valuePath' => null,
480: 'spacer' => '_'
481: );
482: $options += $defaults;
483:
484: extract($this->settings[$Model->alias]);
485:
486: if (!$options['keyPath']) {
487: $options['keyPath'] = '{n}.' . $Model->alias . '.' . $Model->primaryKey;
488: }
489:
490: if (!$options['valuePath']) {
491: $options['valuePath'] = array('%s%s', '{n}.tree_prefix', '{n}.' . $Model->alias . '.' . $Model->displayField);
492:
493: } elseif (is_string($options['valuePath'])) {
494: $options['valuePath'] = array('%s%s', '{n}.tree_prefix', $options['valuePath']);
495:
496: } else {
497: array_unshift($options['valuePath'], '%s' . $options['valuePath'][0], '{n}.tree_prefix');
498: }
499:
500: $stack = array();
501:
502: foreach ($results as $i => $result) {
503: $count = count($stack);
504: while ($stack && ($stack[$count - 1] < $result[$Model->alias][$right])) {
505: array_pop($stack);
506: $count--;
507: }
508: $results[$i]['tree_prefix'] = str_repeat($options['spacer'], $count);
509: $stack[] = $result[$Model->alias][$right];
510: }
511:
512: return Hash::combine($results, $options['keyPath'], $options['valuePath']);
513: }
514:
515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526:
527: public function getParentNode(Model $Model, $id = null, $fields = null, $recursive = null) {
528: $options = array();
529: if (is_array($id)) {
530: $options = $this->_getOptions($id);
531: extract(array_merge(array('id' => null), $id));
532: }
533: $overrideRecursive = $recursive;
534: if (empty($id)) {
535: $id = $Model->id;
536: }
537: extract($this->settings[$Model->alias]);
538: if ($overrideRecursive !== null) {
539: $recursive = $overrideRecursive;
540: }
541: $parentId = $Model->find('first', array(
542: 'conditions' => array($Model->primaryKey => $id),
543: 'fields' => array($parent),
544: 'order' => false,
545: 'recursive' => -1
546: ));
547:
548: if ($parentId) {
549: $parentId = $parentId[$Model->alias][$parent];
550: $options = array_merge(array(
551: 'conditions' => array($Model->escapeField() => $parentId),
552: 'fields' => $fields,
553: 'order' => false,
554: 'recursive' => $recursive
555: ), $options);
556: $parent = $Model->find('first', $options);
557:
558: return $parent;
559: }
560: return false;
561: }
562:
563: 564: 565: 566: 567: 568: 569:
570: protected function _getOptions($arg) {
571: return count(array_filter(array_keys($arg), 'is_string') > 0) ?
572: $arg :
573: array();
574: }
575:
576: 577: 578: 579: 580: 581: 582: 583: 584: 585:
586: public function getPath(Model $Model, $id = null, $fields = null, $recursive = null) {
587: $options = array();
588: if (is_array($id)) {
589: $options = $this->_getOptions($id);
590: extract(array_merge(array('id' => null), $id));
591: }
592:
593: if (!empty($options)) {
594: $fields = null;
595: if (!empty($options['fields'])) {
596: $fields = $options['fields'];
597: }
598: if (!empty($options['recursive'])) {
599: $recursive = $options['recursive'];
600: }
601: }
602: $overrideRecursive = $recursive;
603: if (empty($id)) {
604: $id = $Model->id;
605: }
606: extract($this->settings[$Model->alias]);
607: if ($overrideRecursive !== null) {
608: $recursive = $overrideRecursive;
609: }
610: $result = $Model->find('first', array(
611: 'conditions' => array($Model->escapeField() => $id),
612: 'fields' => array($left, $right),
613: 'order' => false,
614: 'recursive' => $recursive
615: ));
616: if ($result) {
617: $result = array_values($result);
618: } else {
619: return array();
620: }
621: $item = $result[0];
622: $options = array_merge(array(
623: 'conditions' => array(
624: $scope,
625: $Model->escapeField($left) . ' <=' => $item[$left],
626: $Model->escapeField($right) . ' >=' => $item[$right],
627: ),
628: 'fields' => $fields,
629: 'order' => array($Model->escapeField($left) => 'asc'),
630: 'recursive' => $recursive
631: ), $options);
632: $results = $Model->find('all', $options);
633: return $results;
634: }
635:
636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646:
647: public function moveDown(Model $Model, $id = null, $number = 1) {
648: if (is_array($id)) {
649: extract(array_merge(array('id' => null), $id));
650: }
651: if (!$number) {
652: return false;
653: }
654: if (empty($id)) {
655: $id = $Model->id;
656: }
657: extract($this->settings[$Model->alias]);
658: list($node) = array_values($this->_getNode($Model, $id));
659: if ($node[$parent]) {
660: list($parentNode) = array_values($this->_getNode($Model, $node[$parent]));
661: if (($node[$right] + 1) == $parentNode[$right]) {
662: return false;
663: }
664: }
665: $nextNode = $Model->find('first', array(
666: 'conditions' => array($scope, $Model->escapeField($left) => ($node[$right] + 1)),
667: 'fields' => array($Model->primaryKey, $left, $right),
668: 'order' => false,
669: 'recursive' => $recursive)
670: );
671: if ($nextNode) {
672: list($nextNode) = array_values($nextNode);
673: } else {
674: return false;
675: }
676: $edge = $this->_getMax($Model, $scope, $right, $recursive);
677: $this->_sync($Model, $edge - $node[$left] + 1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right]);
678: $this->_sync($Model, $nextNode[$left] - $node[$left], '-', 'BETWEEN ' . $nextNode[$left] . ' AND ' . $nextNode[$right]);
679: $this->_sync($Model, $edge - $node[$left] - ($nextNode[$right] - $nextNode[$left]), '-', '> ' . $edge);
680:
681: if (is_int($number)) {
682: $number--;
683: }
684: if ($number) {
685: $this->moveDown($Model, $id, $number);
686: }
687: return true;
688: }
689:
690: 691: 692: 693: 694: 695: 696: 697: 698: 699: 700:
701: public function moveUp(Model $Model, $id = null, $number = 1) {
702: if (is_array($id)) {
703: extract(array_merge(array('id' => null), $id));
704: }
705: if (!$number) {
706: return false;
707: }
708: if (empty($id)) {
709: $id = $Model->id;
710: }
711: extract($this->settings[$Model->alias]);
712: list($node) = array_values($this->_getNode($Model, $id));
713: if ($node[$parent]) {
714: list($parentNode) = array_values($this->_getNode($Model, $node[$parent]));
715: if (($node[$left] - 1) == $parentNode[$left]) {
716: return false;
717: }
718: }
719: $previousNode = $Model->find('first', array(
720: 'conditions' => array($scope, $Model->escapeField($right) => ($node[$left] - 1)),
721: 'fields' => array($Model->primaryKey, $left, $right),
722: 'order' => false,
723: 'recursive' => $recursive
724: ));
725:
726: if ($previousNode) {
727: list($previousNode) = array_values($previousNode);
728: } else {
729: return false;
730: }
731: $edge = $this->_getMax($Model, $scope, $right, $recursive);
732: $this->_sync($Model, $edge - $previousNode[$left] + 1, '+', 'BETWEEN ' . $previousNode[$left] . ' AND ' . $previousNode[$right]);
733: $this->_sync($Model, $node[$left] - $previousNode[$left], '-', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right]);
734: $this->_sync($Model, $edge - $previousNode[$left] - ($node[$right] - $node[$left]), '-', '> ' . $edge);
735: if (is_int($number)) {
736: $number--;
737: }
738: if ($number) {
739: $this->moveUp($Model, $id, $number);
740: }
741: return true;
742: }
743:
744: 745: 746: 747: 748: 749: 750: 751: 752: 753: 754: 755: 756: 757: 758:
759: public function recover(Model $Model, $mode = 'parent', $missingParentAction = null) {
760: if (is_array($mode)) {
761: extract(array_merge(array('mode' => 'parent'), $mode));
762: }
763: extract($this->settings[$Model->alias]);
764: $Model->recursive = $recursive;
765: if ($mode === 'parent') {
766: $Model->bindModel(array('belongsTo' => array('VerifyParent' => array(
767: 'className' => $Model->name,
768: 'foreignKey' => $parent,
769: 'fields' => array($Model->primaryKey, $left, $right, $parent),
770: ))));
771: $missingParents = $Model->find('list', array(
772: 'recursive' => 0,
773: 'conditions' => array($scope, array(
774: 'NOT' => array($Model->escapeField($parent) => null), $Model->VerifyParent->escapeField() => null
775: )),
776: 'order' => false,
777: ));
778: $Model->unbindModel(array('belongsTo' => array('VerifyParent')));
779: if ($missingParents) {
780: if ($missingParentAction === 'return') {
781: foreach ($missingParents as $id => $display) {
782: $this->errors[] = 'cannot find the parent for ' . $Model->alias . ' with id ' . $id . '(' . $display . ')';
783: }
784: return false;
785: } elseif ($missingParentAction === 'delete') {
786: $Model->deleteAll(array($Model->escapeField($Model->primaryKey) => array_flip($missingParents)), false);
787: } else {
788: $Model->updateAll(array($Model->escapeField($parent) => $missingParentAction), array($Model->escapeField($Model->primaryKey) => array_flip($missingParents)));
789: }
790: }
791:
792: $this->_recoverByParentId($Model);
793: } else {
794: $db = ConnectionManager::getDataSource($Model->useDbConfig);
795: foreach ($Model->find('all', array('conditions' => $scope, 'fields' => array($Model->primaryKey, $parent), 'order' => $left)) as $array) {
796: $path = $this->getPath($Model, $array[$Model->alias][$Model->primaryKey]);
797: $parentId = null;
798: if (count($path) > 1) {
799: $parentId = $path[count($path) - 2][$Model->alias][$Model->primaryKey];
800: }
801: $Model->updateAll(array($parent => $db->value($parentId, $parent)), array($Model->escapeField() => $array[$Model->alias][$Model->primaryKey]));
802: }
803: }
804: return true;
805: }
806:
807: 808: 809: 810: 811: 812: 813: 814: 815: 816:
817: protected function _recoverByParentId(Model $Model, $counter = 1, $parentId = null) {
818: $params = array(
819: 'conditions' => array(
820: $this->settings[$Model->alias]['parent'] => $parentId
821: ),
822: 'fields' => array($Model->primaryKey),
823: 'page' => 1,
824: 'limit' => 100,
825: 'order' => array($Model->primaryKey)
826: );
827:
828: $scope = $this->settings[$Model->alias]['scope'];
829: if ($scope && ($scope !== '1 = 1' && $scope !== true)) {
830: $params['conditions'][] = $scope;
831: }
832:
833: $children = $Model->find('all', $params);
834: $hasChildren = (bool)$children;
835:
836: if ($parentId !== null) {
837: if ($hasChildren) {
838: $Model->updateAll(
839: array($this->settings[$Model->alias]['left'] => $counter),
840: array($Model->escapeField() => $parentId)
841: );
842: $counter++;
843: } else {
844: $Model->updateAll(
845: array(
846: $this->settings[$Model->alias]['left'] => $counter,
847: $this->settings[$Model->alias]['right'] => $counter + 1
848: ),
849: array($Model->escapeField() => $parentId)
850: );
851: $counter += 2;
852: }
853: }
854:
855: while ($children) {
856: foreach ($children as $row) {
857: $counter = $this->_recoverByParentId($Model, $counter, $row[$Model->alias][$Model->primaryKey]);
858: }
859:
860: if (count($children) !== $params['limit']) {
861: break;
862: }
863: $params['page']++;
864: $children = $Model->find('all', $params);
865: }
866:
867: if ($parentId !== null && $hasChildren) {
868: $Model->updateAll(
869: array($this->settings[$Model->alias]['right'] => $counter),
870: array($Model->escapeField() => $parentId)
871: );
872: $counter++;
873: }
874:
875: return $counter;
876: }
877:
878: 879: 880: 881: 882: 883: 884: 885: 886: 887: 888: 889: 890: 891: 892: 893: 894: 895: 896: 897:
898: public function reorder(Model $Model, $options = array()) {
899: $options += array('id' => null, 'field' => $Model->displayField, 'order' => 'ASC', 'verify' => true);
900: extract($options);
901: if ($verify && !$this->verify($Model)) {
902: return false;
903: }
904: $verify = false;
905: extract($this->settings[$Model->alias]);
906: $fields = array($Model->primaryKey, $field, $left, $right);
907: $sort = $field . ' ' . $order;
908: $nodes = $this->children($Model, $id, true, $fields, $sort, null, null, $recursive);
909:
910: $cacheQueries = $Model->cacheQueries;
911: $Model->cacheQueries = false;
912: if ($nodes) {
913: foreach ($nodes as $node) {
914: $id = $node[$Model->alias][$Model->primaryKey];
915: $this->moveDown($Model, $id, true);
916: if ($node[$Model->alias][$left] != $node[$Model->alias][$right] - 1) {
917: $this->reorder($Model, compact('id', 'field', 'order', 'verify'));
918: }
919: }
920: }
921: $Model->cacheQueries = $cacheQueries;
922: return true;
923: }
924:
925: 926: 927: 928: 929: 930: 931: 932: 933: 934: 935: 936:
937: public function removeFromTree(Model $Model, $id = null, $delete = false) {
938: if (is_array($id)) {
939: extract(array_merge(array('id' => null), $id));
940: }
941: extract($this->settings[$Model->alias]);
942:
943: list($node) = array_values($this->_getNode($Model, $id));
944:
945: if ($node[$right] == $node[$left] + 1) {
946: if ($delete) {
947: return $Model->delete($id);
948: }
949: $Model->id = $id;
950: return $Model->saveField($parent, null);
951: } elseif ($node[$parent]) {
952: list($parentNode) = array_values($this->_getNode($Model, $node[$parent]));
953: } else {
954: $parentNode[$right] = $node[$right] + 1;
955: }
956:
957: $db = ConnectionManager::getDataSource($Model->useDbConfig);
958: $Model->updateAll(
959: array($parent => $db->value($node[$parent], $parent)),
960: array($Model->escapeField($parent) => $node[$Model->primaryKey])
961: );
962: $this->_sync($Model, 1, '-', 'BETWEEN ' . ($node[$left] + 1) . ' AND ' . ($node[$right] - 1));
963: $this->_sync($Model, 2, '-', '> ' . ($node[$right]));
964: $Model->id = $id;
965:
966: if ($delete) {
967: $Model->updateAll(
968: array(
969: $Model->escapeField($left) => 0,
970: $Model->escapeField($right) => 0,
971: $Model->escapeField($parent) => null
972: ),
973: array($Model->escapeField() => $id)
974: );
975: return $Model->delete($id);
976: }
977: $edge = $this->_getMax($Model, $scope, $right, $recursive);
978: if ($node[$right] == $edge) {
979: $edge = $edge - 2;
980: }
981: $Model->id = $id;
982: return $Model->save(
983: array($left => $edge + 1, $right => $edge + 2, $parent => null),
984: array('callbacks' => false, 'validate' => false)
985: );
986: }
987:
988: 989: 990: 991: 992: 993: 994: 995: 996: 997:
998: public function verify(Model $Model) {
999: extract($this->settings[$Model->alias]);
1000: if (!$Model->find('count', array('conditions' => $scope))) {
1001: return true;
1002: }
1003: $min = $this->_getMin($Model, $scope, $left, $recursive);
1004: $edge = $this->_getMax($Model, $scope, $right, $recursive);
1005: $errors = array();
1006:
1007: for ($i = $min; $i <= $edge; $i++) {
1008: $count = $Model->find('count', array('conditions' => array(
1009: $scope, 'OR' => array($Model->escapeField($left) => $i, $Model->escapeField($right) => $i)
1010: )));
1011: if ($count != 1) {
1012: if (!$count) {
1013: $errors[] = array('index', $i, 'missing');
1014: } else {
1015: $errors[] = array('index', $i, 'duplicate');
1016: }
1017: }
1018: }
1019: $node = $Model->find('first', array(
1020: 'conditions' => array($scope, $Model->escapeField($right) . '< ' . $Model->escapeField($left)),
1021: 'order' => false,
1022: 'recursive' => 0
1023: ));
1024: if ($node) {
1025: $errors[] = array('node', $node[$Model->alias][$Model->primaryKey], 'left greater than right.');
1026: }
1027:
1028: $Model->bindModel(array('belongsTo' => array('VerifyParent' => array(
1029: 'className' => $Model->name,
1030: 'foreignKey' => $parent,
1031: 'fields' => array($Model->primaryKey, $left, $right, $parent)
1032: ))));
1033:
1034: $rows = $Model->find('all', array('conditions' => $scope, 'recursive' => 0));
1035: foreach ($rows as $instance) {
1036: if ($instance[$Model->alias][$left] === null || $instance[$Model->alias][$right] === null) {
1037: $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey],
1038: 'has invalid left or right values');
1039: } elseif ($instance[$Model->alias][$left] == $instance[$Model->alias][$right]) {
1040: $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey],
1041: 'left and right values identical');
1042: } elseif ($instance[$Model->alias][$parent]) {
1043: if (!$instance['VerifyParent'][$Model->primaryKey]) {
1044: $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey],
1045: 'The parent node ' . $instance[$Model->alias][$parent] . ' doesn\'t exist');
1046: } elseif ($instance[$Model->alias][$left] < $instance['VerifyParent'][$left]) {
1047: $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey],
1048: 'left less than parent (node ' . $instance['VerifyParent'][$Model->primaryKey] . ').');
1049: } elseif ($instance[$Model->alias][$right] > $instance['VerifyParent'][$right]) {
1050: $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey],
1051: 'right greater than parent (node ' . $instance['VerifyParent'][$Model->primaryKey] . ').');
1052: }
1053: } elseif ($Model->find('count', array('conditions' => array($scope, $Model->escapeField($left) . ' <' => $instance[$Model->alias][$left], $Model->escapeField($right) . ' >' => $instance[$Model->alias][$right]), 'recursive' => 0))) {
1054: $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey], 'The parent field is blank, but has a parent');
1055: }
1056: }
1057: if ($errors) {
1058: return $errors;
1059: }
1060: return true;
1061: }
1062:
1063: 1064: 1065: 1066: 1067: 1068: 1069:
1070: public function getLevel(Model $Model, $id = null) {
1071: if ($id === null) {
1072: $id = $Model->id;
1073: }
1074:
1075: $node = $Model->find('first', array(
1076: 'conditions' => array($Model->escapeField() => $id),
1077: 'order' => false,
1078: 'recursive' => -1
1079: ));
1080:
1081: if (empty($node)) {
1082: return false;
1083: }
1084:
1085: extract($this->settings[$Model->alias]);
1086:
1087: return $Model->find('count', array(
1088: 'conditions' => array(
1089: $scope,
1090: $left . ' <' => $node[$Model->alias][$left],
1091: $right . ' >' => $node[$Model->alias][$right]
1092: ),
1093: 'order' => false,
1094: 'recursive' => -1
1095: ));
1096: }
1097:
1098: 1099: 1100: 1101: 1102: 1103: 1104: 1105: 1106: 1107: 1108: 1109:
1110: protected function _setParent(Model $Model, $parentId = null, $created = false) {
1111: extract($this->settings[$Model->alias]);
1112: list($node) = array_values($this->_getNode($Model, $Model->id));
1113: $edge = $this->_getMax($Model, $scope, $right, $recursive, $created);
1114:
1115: if (empty($parentId)) {
1116: $this->_sync($Model, $edge - $node[$left] + 1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right], $created);
1117: $this->_sync($Model, $node[$right] - $node[$left] + 1, '-', '> ' . $node[$left], $created);
1118: } else {
1119: $values = $this->_getNode($Model, $parentId);
1120:
1121: if ($values === false) {
1122: return false;
1123: }
1124: $parentNode = array_values($values);
1125:
1126: if (empty($parentNode) || empty($parentNode[0])) {
1127: return false;
1128: }
1129: $parentNode = $parentNode[0];
1130:
1131: if (($Model->id === $parentId)) {
1132: return false;
1133: } elseif (($node[$left] < $parentNode[$left]) && ($parentNode[$right] < $node[$right])) {
1134: return false;
1135: }
1136: if (empty($node[$left]) && empty($node[$right])) {
1137: $this->_sync($Model, 2, '+', '>= ' . $parentNode[$right], $created);
1138: $result = $Model->save(
1139: array($left => $parentNode[$right], $right => $parentNode[$right] + 1, $parent => $parentId),
1140: array('validate' => false, 'callbacks' => false)
1141: );
1142: $Model->data = $result;
1143: } else {
1144: $this->_sync($Model, $edge - $node[$left] + 1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right], $created);
1145: $diff = $node[$right] - $node[$left] + 1;
1146:
1147: if ($node[$left] > $parentNode[$left]) {
1148: if ($node[$right] < $parentNode[$right]) {
1149: $this->_sync($Model, $diff, '-', 'BETWEEN ' . $node[$right] . ' AND ' . ($parentNode[$right] - 1), $created);
1150: $this->_sync($Model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge, $created);
1151: } else {
1152: $this->_sync($Model, $diff, '+', 'BETWEEN ' . $parentNode[$right] . ' AND ' . $node[$right], $created);
1153: $this->_sync($Model, $edge - $parentNode[$right] + 1, '-', '> ' . $edge, $created);
1154: }
1155: } else {
1156: $this->_sync($Model, $diff, '-', 'BETWEEN ' . $node[$right] . ' AND ' . ($parentNode[$right] - 1), $created);
1157: $this->_sync($Model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge, $created);
1158: }
1159: }
1160: }
1161: return true;
1162: }
1163:
1164: 1165: 1166: 1167: 1168: 1169: 1170: 1171: 1172: 1173:
1174: protected function _getMax(Model $Model, $scope, $right, $recursive = -1, $created = false) {
1175: $db = ConnectionManager::getDataSource($Model->useDbConfig);
1176: if ($created) {
1177: if (is_string($scope)) {
1178: $scope .= " AND " . $Model->escapeField() . " <> ";
1179: $scope .= $db->value($Model->id, $Model->getColumnType($Model->primaryKey));
1180: } else {
1181: $scope['NOT'][$Model->alias . '.' . $Model->primaryKey] = $Model->id;
1182: }
1183: }
1184: $name = $Model->escapeField($right);
1185: list($edge) = array_values($Model->find('first', array(
1186: 'conditions' => $scope,
1187: 'fields' => $db->calculate($Model, 'max', array($name, $right)),
1188: 'recursive' => $recursive,
1189: 'order' => false,
1190: 'callbacks' => false
1191: )));
1192: return (empty($edge[$right])) ? 0 : $edge[$right];
1193: }
1194:
1195: 1196: 1197: 1198: 1199: 1200: 1201: 1202: 1203:
1204: protected function _getMin(Model $Model, $scope, $left, $recursive = -1) {
1205: $db = ConnectionManager::getDataSource($Model->useDbConfig);
1206: $name = $Model->escapeField($left);
1207: list($edge) = array_values($Model->find('first', array(
1208: 'conditions' => $scope,
1209: 'fields' => $db->calculate($Model, 'min', array($name, $left)),
1210: 'recursive' => $recursive,
1211: 'order' => false,
1212: 'callbacks' => false
1213: )));
1214: return (empty($edge[$left])) ? 0 : $edge[$left];
1215: }
1216:
1217: 1218: 1219: 1220: 1221: 1222: 1223: 1224: 1225: 1226: 1227: 1228: 1229:
1230: protected function _sync(Model $Model, $shift, $dir = '+', $conditions = array(), $created = false, $field = 'both') {
1231: $ModelRecursive = $Model->recursive;
1232: extract($this->settings[$Model->alias]);
1233: $Model->recursive = $recursive;
1234:
1235: if ($field === 'both') {
1236: $this->_sync($Model, $shift, $dir, $conditions, $created, $left);
1237: $field = $right;
1238: }
1239: if (is_string($conditions)) {
1240: $conditions = array($Model->escapeField($field) . " {$conditions}");
1241: }
1242: if (($scope !== '1 = 1' && $scope !== true) && $scope) {
1243: $conditions[] = $scope;
1244: }
1245: if ($created) {
1246: $conditions['NOT'][$Model->escapeField()] = $Model->id;
1247: }
1248: $Model->updateAll(array($Model->escapeField($field) => $Model->escapeField($field) . ' ' . $dir . ' ' . $shift), $conditions);
1249: $Model->recursive = $ModelRecursive;
1250: }
1251:
1252: }
1253: