Skip to content

Commit

Permalink
Allow setting level (depth) of tree nodes on save.
Browse files Browse the repository at this point in the history
Backported from 3.0.
  • Loading branch information
ADmad committed Feb 15, 2015
1 parent 18246da commit 6ad68ae
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 6 deletions.
66 changes: 62 additions & 4 deletions lib/Cake/Model/Behavior/TreeBehavior.php
Expand Up @@ -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
);

Expand Down Expand Up @@ -97,10 +97,47 @@ public function afterSave(Model $Model, $created, $options = array()) {
}
} 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
*
Expand Down Expand Up @@ -182,31 +219,45 @@ public function beforeSave(Model $Model, $options = array()) {
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;
}

$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;
}

Expand All @@ -228,6 +279,9 @@ public function beforeSave(Model $Model, $options = array()) {
if ($node[$Model->primaryKey] === $parentNode[$Model->primaryKey]) {
return false;
}
if ($level) {
$Model->data[$Model->alias][$level] = (int)$parentNode[$level] + 1;
}
}

return true;
Expand All @@ -242,10 +296,14 @@ public function beforeSave(Model $Model, $options = array()) {
*/
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,
));
Expand Down
60 changes: 59 additions & 1 deletion lib/Cake/Test/Case/Model/Behavior/TreeBehaviorNumberTest.php
Expand Up @@ -46,7 +46,8 @@ class TreeBehaviorNumberTest extends CakeTestCase {
'modelClass' => 'NumberTree',
'leftField' => 'lft',
'rightField' => 'rght',
'parentField' => 'parent_id'
'parentField' => 'parent_id',
'level' => 'level'
);

/**
Expand Down Expand Up @@ -1527,4 +1528,61 @@ public function testFindThreaded() {
);
$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]);
}
}
3 changes: 2 additions & 1 deletion lib/Cake/Test/Fixture/NumberTreeFixture.php
Expand Up @@ -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)
);
}

0 comments on commit 6ad68ae

Please sign in to comment.