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