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