From 6ad68ae1e2e1a0dada6554d0123bb6ea36899e85 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 15 Feb 2015 22:23:31 +0530 Subject: [PATCH 1/3] Allow setting level (depth) of tree nodes on save. Backported from 3.0. --- lib/Cake/Model/Behavior/TreeBehavior.php | 66 +++++++++++++++++-- .../Model/Behavior/TreeBehaviorNumberTest.php | 60 ++++++++++++++++- lib/Cake/Test/Fixture/NumberTreeFixture.php | 3 +- 3 files changed, 123 insertions(+), 6 deletions(-) diff --git a/lib/Cake/Model/Behavior/TreeBehavior.php b/lib/Cake/Model/Behavior/TreeBehavior.php index c4508fc21..bb3d81ba7 100644 --- a/lib/Cake/Model/Behavior/TreeBehavior.php +++ b/lib/Cake/Model/Behavior/TreeBehavior.php @@ -44,7 +44,7 @@ class TreeBehavior extends ModelBehavior { * @var array */ protected $_defaults = array( - 'parent' => 'parent_id', 'left' => 'lft', 'right' => 'rght', + 'parent' => 'parent_id', 'left' => 'lft', 'right' => 'rght', 'level' => null, 'scope' => '1 = 1', 'type' => 'nested', '__parentChange' => false, 'recursive' => -1 ); @@ -97,10 +97,47 @@ class TreeBehavior extends ModelBehavior { } } elseif ($this->settings[$Model->alias]['__parentChange']) { $this->settings[$Model->alias]['__parentChange'] = false; + if ($level) { + $this->_setChildrenLevel($Model, $Model->id); + } return $this->_setParent($Model, $Model->data[$Model->alias][$parent]); } } +/** + * Set level for descendents. + * + * @param Model $Model Model using this behavior. + * @param int|string $id Record ID + * @return void + */ + protected function _setChildrenLevel($Model, $id) { + $settings = $Model->Behaviors->Tree->settings[$Model->alias]; + $primaryKey = $Model->primaryKey; + $depths = array($id => (int)$Model->data[$Model->alias][$settings['level']]); + + $children = $Model->children( + $id, + false, + array($primaryKey, $settings['parent'], $settings['level']), + $settings['left'], + null, + 1, + -1 + ); + + foreach ($children as $node) { + $parentIdValue = $node[$Model->alias][$settings['parent']]; + $depth = (int)$depths[$parentIdValue] + 1; + $depths[$node[$Model->alias][$primaryKey]] = $depth; + + $Model->updateAll( + array($settings['level'] => $depth), + array($primaryKey => $node[$Model->alias][$primaryKey]) + ); + } + } + /** * Runs before a find() operation * @@ -182,8 +219,13 @@ class TreeBehavior extends ModelBehavior { extract($this->settings[$Model->alias]); $this->_addToWhitelist($Model, array($left, $right)); + if ($level) { + $this->_addToWhitelist($Model, $level); + } + $parentIsSet = array_key_exists($parent, $Model->data[$Model->alias]); + if (!$Model->id || !$Model->exists()) { - if (array_key_exists($parent, $Model->data[$Model->alias]) && $Model->data[$Model->alias][$parent]) { + if ($parentIsSet && $Model->data[$Model->alias][$parent]) { $parentNode = $this->_getNode($Model, $Model->data[$Model->alias][$parent]); if (!$parentNode) { return false; @@ -191,22 +233,31 @@ class TreeBehavior extends ModelBehavior { $Model->data[$Model->alias][$left] = 0; $Model->data[$Model->alias][$right] = 0; + if ($level) { + $Model->data[$Model->alias][$level] = (int)$parentNode[$Model->alias][$level] + 1; + } return true; } $edge = $this->_getMax($Model, $scope, $right, $recursive); $Model->data[$Model->alias][$left] = $edge + 1; $Model->data[$Model->alias][$right] = $edge + 2; + if ($level) { + $Model->data[$Model->alias][$level] = 0; + } return true; } - if (array_key_exists($parent, $Model->data[$Model->alias])) { + if ($parentIsSet) { if ($Model->data[$Model->alias][$parent] != $Model->field($parent)) { $this->settings[$Model->alias]['__parentChange'] = true; } if (!$Model->data[$Model->alias][$parent]) { $Model->data[$Model->alias][$parent] = null; $this->_addToWhitelist($Model, $parent); + if ($level) { + $Model->data[$Model->alias][$level] = 0; + } return true; } @@ -228,6 +279,9 @@ class TreeBehavior extends ModelBehavior { if ($node[$Model->primaryKey] === $parentNode[$Model->primaryKey]) { return false; } + if ($level) { + $Model->data[$Model->alias][$level] = (int)$parentNode[$level] + 1; + } } return true; @@ -242,10 +296,14 @@ class TreeBehavior extends ModelBehavior { */ protected function _getNode(Model $Model, $id) { $settings = $this->settings[$Model->alias]; + $fields = array($Model->primaryKey, $settings['parent'], $settings['left'], $settings['right']); + if ($settings['level']) { + $fields[] = $settings['level']; + } return $Model->find('first', array( 'conditions' => array($Model->escapeField() => $id), - 'fields' => array($Model->primaryKey, $settings['parent'], $settings['left'], $settings['right']), + 'fields' => $fields, 'recursive' => $settings['recursive'], 'order' => false, )); diff --git a/lib/Cake/Test/Case/Model/Behavior/TreeBehaviorNumberTest.php b/lib/Cake/Test/Case/Model/Behavior/TreeBehaviorNumberTest.php index e5503f563..da7da8196 100644 --- a/lib/Cake/Test/Case/Model/Behavior/TreeBehaviorNumberTest.php +++ b/lib/Cake/Test/Case/Model/Behavior/TreeBehaviorNumberTest.php @@ -46,7 +46,8 @@ class TreeBehaviorNumberTest extends CakeTestCase { 'modelClass' => 'NumberTree', 'leftField' => 'lft', 'rightField' => 'rght', - 'parentField' => 'parent_id' + 'parentField' => 'parent_id', + 'level' => 'level' ); /** @@ -1527,4 +1528,61 @@ class TreeBehaviorNumberTest extends CakeTestCase { ); $this->assertEquals($expected, $result); } + + public function testLevel() { + extract($this->settings); + $this->Tree = new $modelClass(); + $this->Tree->Behaviors->attach('Tree', array('level' => 'level')); + $this->Tree->initialize(2, 2); + + $result = $this->Tree->findByName('1. Root'); + $this->assertEquals(0, $result[$modelClass][$level]); + + $result = $this->Tree->findByName('1.1'); + $this->assertEquals(1, $result[$modelClass][$level]); + + $result = $this->Tree->findByName('1.2.2'); + $this->assertEquals(2, $result[$modelClass][$level]); + + $result = $this->Tree->findByName('1.2.1'); + $this->assertEquals(2, $result[$modelClass][$level]); + + // Save with parent_id not set + $this->Tree->save(array('id' => $result[$modelClass]['id'], 'name' => 'foo')); + $result = $this->Tree->findByName('foo'); + $this->assertEquals(2, $result[$modelClass][$level]); + + // Save with parent_id not changed + $this->Tree->save(array( + 'id' => $result[$modelClass]['id'], + 'parent_id' => $result[$modelClass]['parent_id'], + 'name' => 'foo2' + )); + $result = $this->Tree->findByName('foo2'); + $this->assertEquals(2, $result[$modelClass][$level]); + + // Save with parent_id changed + $result = $this->Tree->findByName('1.1'); + $this->Tree->save(array( + 'id' => $result[$modelClass]['id'], + 'parent_id' => '' + )); + $result = $this->Tree->findByName('1.1'); + $this->assertEquals(0, $result[$modelClass][$level]); + + $result = $this->Tree->findByName('1.1.2'); + $this->assertEquals(1, $result[$modelClass][$level]); + + $parent = $this->Tree->findByName('1.1.2'); + $result = $this->Tree->findByName('1.2'); + $this->Tree->save(array( + 'id' => $result[$modelClass]['id'], + 'parent_id' => $parent[$modelClass]['id'] + )); + $result = $this->Tree->findByName('1.2'); + $this->assertEquals(2, $result[$modelClass][$level]); + + $result = $this->Tree->findByName('1.2.2'); + $this->assertEquals(3, $result[$modelClass][$level]); + } } diff --git a/lib/Cake/Test/Fixture/NumberTreeFixture.php b/lib/Cake/Test/Fixture/NumberTreeFixture.php index cea904921..8d6a9495e 100644 --- a/lib/Cake/Test/Fixture/NumberTreeFixture.php +++ b/lib/Cake/Test/Fixture/NumberTreeFixture.php @@ -37,6 +37,7 @@ class NumberTreeFixture extends CakeTestFixture { 'name' => array('type' => 'string', 'null' => false), 'parent_id' => 'integer', 'lft' => array('type' => 'integer', 'null' => false), - 'rght' => array('type' => 'integer', 'null' => false) + 'rght' => array('type' => 'integer', 'null' => false), + 'level' => array('type' => 'integer', 'null' => false) ); } From e095885dc4803ec88d75d35b479720961a8602f8 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 15 Feb 2015 22:35:42 +0530 Subject: [PATCH 2/3] Fix test case --- lib/Cake/Test/Case/Model/ModelIntegrationTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Test/Case/Model/ModelIntegrationTest.php b/lib/Cake/Test/Case/Model/ModelIntegrationTest.php index 8f941b059..fce6067b9 100644 --- a/lib/Cake/Test/Case/Model/ModelIntegrationTest.php +++ b/lib/Cake/Test/Case/Model/ModelIntegrationTest.php @@ -226,7 +226,8 @@ class ModelIntegrationTest extends BaseModelTest { 'scope' => '1 = 1', 'type' => 'nested', '__parentChange' => false, - 'recursive' => -1 + 'recursive' => -1, + 'level' => null ); $this->assertEquals($expected, $TestModel->Behaviors->Tree->settings['Apple']); From 204f50bb97d17aa9efc51ab3cfaa2349dfb1a45f Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 15 Feb 2015 23:04:13 +0530 Subject: [PATCH 3/3] Allow level field to be null for pqsql and sqlite. --- lib/Cake/Test/Fixture/NumberTreeFixture.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Test/Fixture/NumberTreeFixture.php b/lib/Cake/Test/Fixture/NumberTreeFixture.php index 8d6a9495e..78d34cf21 100644 --- a/lib/Cake/Test/Fixture/NumberTreeFixture.php +++ b/lib/Cake/Test/Fixture/NumberTreeFixture.php @@ -38,6 +38,6 @@ class NumberTreeFixture extends CakeTestFixture { 'parent_id' => 'integer', 'lft' => array('type' => 'integer', 'null' => false), 'rght' => array('type' => 'integer', 'null' => false), - 'level' => array('type' => 'integer', 'null' => false) + 'level' => array('type' => 'integer', 'null' => true) ); }