"Refactoring TreeBehavior::recover();

Added TreeBehavior::reorder(); to reorder nodes of tree"

git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@6383 3807eeeb-6ff5-0310-8944-8be069107fe0
This commit is contained in:
phpnut 2008-01-16 16:51:18 +00:00
parent 520d179850
commit 5b3cbf2ea0
2 changed files with 118 additions and 14 deletions

View file

@ -36,6 +36,8 @@
*/
class TreeBehavior extends ModelBehavior {
var $errors = array();
function setup(&$model, $config = array()) {
$settings = array_merge(array(
'parent' => 'parent_id',
@ -110,8 +112,11 @@ class TreeBehavior extends ModelBehavior {
$diff = $data[$right] - $data[$left] + 1;
if ($diff > 2) {
$constraint = $scope . ' AND ' . $model->escapeField($left) . ' BETWEEN ' . ($data[$left] + 1) . ' AND ' . ($data[$right] - 1);
$model->deleteAll($constraint);
if (is_string($scope)) {
$scope = array($scope);
}
$scope[][$model->escapeField($left)] = 'BETWEEN ' . ($data[$left] + 1) . ' AND ' . ($data[$right] - 1);
$model->deleteAll($scope);
}
$this->__sync($model, $diff, '-', '> ' . $data[$right]);
return true;
@ -259,7 +264,7 @@ class TreeBehavior extends ModelBehavior {
$constraint = $scope;
} else {
@list($item) = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $id), 'fields' => array($left, $right), 'recursive' => -1)));
$constraint = array($scope, $model->escapeField($right) => '< ' . $item[$right], $model->escapeField($left) => '> ' . $item[$left]);
$constraint = array($scope, $model->escapeField($right) . '< ' . $item[$right], $model->escapeField($left) => '> ' . $item[$left]);
}
return $model->find('all', array('conditions' => $constraint, 'fields' => $fields, 'order' => $order, 'limit' => $limit, 'page' => $page, 'recursive' => $recursive));
}
@ -470,18 +475,46 @@ class TreeBehavior extends ModelBehavior {
*
* The mode parameter is used to specify the source of info that is valid/correct. The opposite source of data
* will be populated based upon that source of info. E.g. if the MPTT fields are corrupt or empty, with the $mode
* 'parent' the values of the parent_id field will be used to populate the left and right fields.
* 'parent' the values of the parent_id field will be used to populate the left and right fields. The missingParentAction
* parameter only applies to "parent" mode and determines what to do if the parent field contains an id that is not present.
*
* @todo Could be written to be faster, *maybe*. Ideally using a subquery and putting all the logic burden on the DB.
* @param AppModel $model
* @param string $mode parent or tree
* @param mixed $missingParentAction 'return' to do nothing and return, 'delete' to
* delete, or the id of the parent to set as the parent_id
* @return boolean true on success, false on failure
* @access public
*/
function recover(&$model, $mode = 'parent') {
function recover(&$model, $mode = 'parent', $missingParentAction = null) {
if (is_array($mode)) {
extract ($mode);
}
extract($this->settings[$model->alias]);
$model->recursive = -1;
if ($mode == 'parent') {
$model->bindModel(array('belongsTo' => array('VerifyParent' => array(
'className' => $model->alias,
'foreignKey' => $parent,
'fields' => array($model->primaryKey, $left, $right, $parent,
'actsAs' => '')
))));
$missingParents = $model->find('list', array('recursive' => 0, 'conditions' =>
array($scope, array('NOT' => array($model->escapeField($parent) => null), $model->VerifyParent->escapeField() => null))));
$model->unbindModel(array('belongsTo' => array('VerifyParent')));
if ($missingParents) {
if ($missingParentAction == 'return') {
foreach ($missingParents as $id => $display) {
$this->errors[] = 'cannot find the parent for ' . $model->alias . ' with id ' . $id . '(' . $display . ')';
}
return false;
} elseif ($missingParentAction == 'delete') {
$model->deleteAll(array($model->primaryKey => array_flip($missingParents)));
} else {
$model->updateAll(array($parent => $missingParentAction), array($model->primaryKey => array_flip($missingParents)));
}
}
$count = 1;
foreach ($model->find('all', array('conditions' => $scope, 'fields' => array($model->primaryKey), 'order' => $left)) as $array) {
$model->{$model->primaryKey} = $array[$model->alias][$model->primaryKey];
@ -505,6 +538,41 @@ class TreeBehavior extends ModelBehavior {
$model->updateAll(array($parent => $parentId), array($model->escapeField() => $array[$model->alias][$model->primaryKey]));
}
}
return true;
}
/**
* Reorder method.
*
* Reorders the nodes (and child nodes) of the tree according to the field and direction specified in the parameters.
* This method does not change the parent of any node.
*
* Requires a valid tree, by default it verifies the tree before beginning.
*
* @param AppModel $model
* @param array $options
* @return boolean true on success, false on failure
*/
function reorder(&$model, $options = array()) {
$options = am(array('id' => null, 'field' => $model->displayField, 'order' => 'ASC', 'verify' => true), $options);
extract($options);
if ($verify && !$model->verify()) {
return false;
}
$verify = false;
extract($this->settings[$model->alias]);
$fields = array($model->primaryKey, $field, $left, $right);
$sort = $field . ' ' . $order;
$nodes = $model->children($id, true, $fields, $sort, null, null, -1);
if ($nodes) {
foreach ($nodes as $node) {
$id = $node[$model->alias][$model->primaryKey];
$model->moveDown($id, true);
if ($node[$model->alias][$left] != $node[$model->alias][$right] - 1) {
$this->reorder($model, compact('id', 'field', 'order', 'verify'));
}
}
}
return true;
}
/**
* Remove the current node from the tree, and reparent all children up one level.
@ -662,7 +730,7 @@ class TreeBehavior extends ModelBehavior {
$this->__sync($model, $edge - $node[$left] + 1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right]);
$this->__sync($model, $node[$right] - $node[$left] + 1, '-', '> ' . $node[$left]);
} else {
list($parentNode)= array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $parentId),
@list($parentNode)= array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $parentId),
'fields' => array($model->primaryKey, $left, $right), 'recursive' => -1)));
if (empty ($parentNode)) {

View file

@ -162,6 +162,21 @@ class NumberTreeCase extends CakeTestCase {
$this->assertIdentical($result, true);
}
function testRecoverFromMissingParent() {
$this->NumberTree = & new NumberTree();
$this->NumberTree->__initialize(2, 2);
$result = $this->NumberTree->findByName('1.1');
$this->NumberTree->updateAll(array('parent_id' => 999999), array('id' => $result['NumberTree']['id']));
$result = $this->NumberTree->verify();
$this->assertNotIdentical($result, true);
$this->NumberTree->recover();
$result = $this->NumberTree->verify();
$this->assertIdentical($result, true);
}
function testDetectInvalidParents() {
$this->NumberTree = & new NumberTree();
$this->NumberTree->__initialize(2, 2);
@ -238,7 +253,7 @@ class NumberTreeCase extends CakeTestCase {
$this->NumberTree->id = null;
$initialCount = $this->NumberTree->findCount();
$this->expectError('Trying to save a node under a none-existant node in TreeBehavior::beforeSave');
//$this->expectError('Trying to save a node under a none-existant node in TreeBehavior::beforeSave');
$saveSuccess = $this->NumberTree->save(array('NumberTree' => array('name' => 'testAddInvalid', 'parent_id' => 99999)));
$this->assertIdentical($saveSuccess, false);
@ -272,7 +287,6 @@ class NumberTreeCase extends CakeTestCase {
$this->assertIdentical($validTree, true);
}
function testMoveWithWhitelist() {
$this->NumberTree = & new NumberTree();
$this->NumberTree->__initialize(2, 2);
@ -365,7 +379,7 @@ class NumberTreeCase extends CakeTestCase {
$before = $this->NumberTree->read(null, $data['NumberTree']['id']);
$this->NumberTree->id = $parent_id;
$this->expectError('Trying to save a node under itself in TreeBehavior::beforeSave');
//$this->expectError('Trying to save a node under itself in TreeBehavior::beforeSave');
$this->NumberTree->saveField('parent_id', $data['NumberTree']['id']);
//$this->NumberTree->setparent($data['NumberTree']['id']);
@ -387,7 +401,7 @@ class NumberTreeCase extends CakeTestCase {
$initialCount = $this->NumberTree->findCount();
$data= $this->NumberTree->findByName('1.1');
$this->expectError('Trying to save a node under a none-existant node in TreeBehavior::beforeSave');
//$this->expectError('Trying to save a node under a none-existant node in TreeBehavior::beforeSave');
$this->NumberTree->id = $data['NumberTree']['id'];
$this->NumberTree->saveField('parent_id', 999999);
//$saveSuccess = $this->NumberTree->setparent(999999);
@ -408,7 +422,7 @@ class NumberTreeCase extends CakeTestCase {
$initialCount = $this->NumberTree->findCount();
$data= $this->NumberTree->findByName('1.1');
$this->expectError('Trying to set a node to be the parent of itself in TreeBehavior::beforeSave');
//$this->expectError('Trying to set a node to be the parent of itself in TreeBehavior::beforeSave');
$this->NumberTree->id = $data['NumberTree']['id'];
$saveSuccess = $this->NumberTree->saveField('parent_id', $this->NumberTree->id);
//$saveSuccess= $this->NumberTree->setparent($this->NumberTree->id);
@ -796,5 +810,27 @@ class NumberTreeCase extends CakeTestCase {
array('NumberTree' => array('id' => 7, 'name' => '1.2.2', 'parent_id' => 5, 'lft' => 11, 'rght' => 12)));
$this->assertEqual($total, $expects);
}
function testReorderTree () {
$this->NumberTree = & new NumberTree();
$this->NumberTree->__initialize(3, 3);
$nodes = $this->NumberTree->find('list', array('order' => 'lft'));
$data = $this->NumberTree->find(array('NumberTree.name' => '1.1'), array('id'));
$this->NumberTree->moveDown($data['NumberTree']['id']);
$data = $this->NumberTree->find(array('NumberTree.name' => '1.2.1'), array('id'));
$this->NumberTree->moveDown($data['NumberTree']['id']);
$data = $this->NumberTree->find(array('NumberTree.name' => '1.3.2.2'), array('id'));
$this->NumberTree->moveDown($data['NumberTree']['id']);
$unsortedNodes = $this->NumberTree->find('list', array('order' => 'lft'));
$this->assertNotIdentical($nodes, $unsortedNodes);
$this->NumberTree->reorder();
$sortedNodes = $this->NumberTree->find('list', array('order' => 'lft'));
$this->assertIdentical($nodes, $sortedNodes);
}
}
?>