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