updated tree behavior addressing most of the issues related to its use with postgreSQL and sqlite

git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@6995 3807eeeb-6ff5-0310-8944-8be069107fe0
This commit is contained in:
AD7six 2008-05-21 20:03:12 +00:00
parent c0ff1b6fd1
commit 84eb4ae36d

View file

@ -40,7 +40,7 @@ class TreeBehavior extends ModelBehavior {
var $_defaults = array(
'parent' => 'parent_id', 'left' => 'lft', 'right' => 'rght',
'scope' => '1 = 1', 'type' => 'nested', '__parentChange' => false
'scope' => '1 = 1', 'type' => 'nested', '__parentChange' => false, 'recursive' => -1
);
function setup(&$model, $config = array()) {
@ -53,6 +53,7 @@ class TreeBehavior extends ModelBehavior {
$data = $model->getAssociated($settings['scope']);
$parent =& $model->{$settings['scope']};
$settings['scope'] = $model->alias . '.' . $data['foreignKey'] . ' = ' . $parent->alias . '.' . $parent->primaryKey;
$settings['recursive'] = 0;
}
$this->settings[$model->alias] = $settings;
}
@ -61,7 +62,10 @@ class TreeBehavior extends ModelBehavior {
*/
function setScope(&$model, $scope) {
trigger_error(__('(TreeBehavior::setScope) Deprecated - Use BehaviorCollection::attach() to re-attach with new settings', true), E_USER_WARNING);
$this->settings[$model->name]['scope'] = $scope;
$this->settings[$model->alias]['scope'] = $scope;
if ($this->settings[$model->alias]['recursive'] < 0) {
$this->settings[$model->alias]['recursive'] = 0;
}
}
/**
* After save method. Called after all saves
@ -77,7 +81,7 @@ class TreeBehavior extends ModelBehavior {
extract($this->settings[$model->alias]);
if ($created) {
if ((isset($model->data[$model->alias][$parent])) && $model->data[$model->alias][$parent]) {
return $this->_setParent($model, $model->data[$model->alias][$parent]);
return $this->_setParent($model, $model->data[$model->alias][$parent], $created);
}
} elseif ($__parentChange) {
$this->settings[$model->alias]['__parentChange'] = false;
@ -135,21 +139,23 @@ class TreeBehavior extends ModelBehavior {
unset($model->data[$model->alias][$model->primaryKey]);
}
$this->_addToWhitelist($model, array($left, $right));
if (!$model->id) {
if ((!array_key_exists($parent, $model->data[$model->alias])) || (!$model->data[$model->alias][$parent])) {
$edge = $this->__getMax($model, $scope, $right);
$model->data[$model->alias][$left] = $edge + 1;
$model->data[$model->alias][$right] = $edge + 2;
$this->_addToWhitelist($model, array($left, $right));
} else {
if (array_key_exists($parent, $model->data[$model->alias]) && $model->data[$model->alias][$parent]) {
$parentNode = $model->find('first', array(
'conditions' => array($scope, $model->escapeField() => $model->data[$model->alias][$parent]),
'fields' => array($model->primaryKey), 'recursive' => -1
'fields' => array($model->primaryKey, $right), 'recursive' => $recursive
));
if (!$parentNode) {
return false;
}
list($parentNode) = array_values($parentNode);
$model->data[$model->alias][$left] = 0; //$parentNode[$right];
$model->data[$model->alias][$right] = 0; //$parentNode[$right] + 1;
} else {
$edge = $this->__getMax($model, $scope, $right, $recursive);
$model->data[$model->alias][$left] = $edge + 1;
$model->data[$model->alias][$right] = $edge + 2;
}
} elseif (array_key_exists($parent, $model->data[$model->alias])) {
if ($model->data[$model->alias][$parent] != $model->field($parent)) {
@ -161,12 +167,12 @@ class TreeBehavior extends ModelBehavior {
} else {
list($node) = array_values($model->find('first', array(
'conditions' => array($scope,$model->escapeField() => $model->id),
'fields' => array($model->primaryKey, $parent, $left, $right ), 'recursive' => -1)
'fields' => array($model->primaryKey, $parent, $left, $right ), 'recursive' => $recursive)
));
$parentNode = $model->find('first', array(
'conditions' => array($scope, $model->escapeField() => $model->data[$model->alias][$parent]),
'fields' => array($model->primaryKey, $left, $right), 'recursive' => -1
'fields' => array($model->primaryKey, $left, $right), 'recursive' => $recursive
));
if (!$parentNode) {
return false;
@ -195,6 +201,9 @@ class TreeBehavior extends ModelBehavior {
* @access public
*/
function childcount(&$model, $id = null, $direct = false) {
if (is_array($id)) {
extract (array_merge($id, array('id' => null)));
}
if ($id === null && $model->id) {
$id = $model->id;
} elseif (!$id) {
@ -211,7 +220,7 @@ class TreeBehavior extends ModelBehavior {
} elseif (!empty ($model->data)) {
$data = $model->data[$model->alias];
} else {
list($data) = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $id), 'recursive' => -1)));
list($data) = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $id), 'recursive' => $recursive)));
}
return ($data[$right] - $data[$left] - 1) / 2;
}
@ -232,16 +241,20 @@ class TreeBehavior extends ModelBehavior {
* @return array Array of child nodes
* @access public
*/
function children(&$model, $id = null, $direct = false, $fields = null, $order = null, $limit = null, $page = 1, $recursive = -1) {
function children(&$model, $id = null, $direct = false, $fields = null, $order = null, $limit = null, $page = 1, $recursive = null) {
if (is_array($id)) {
extract (array_merge($id, array('id' => null)));
}
$overrideRecursive = $recursive;
if ($id === null && $model->id) {
$id = $model->id;
} elseif (!$id) {
$id = null;
}
$name = $model->alias;
extract($this->settings[$name]);
if ($scope != '1 = 1') {
$recursive = 0;
extract($this->settings[$model->alias]);
if (!is_null($overrideRecursive)) {
$recursive = $overrideRecursive;
}
if (!$order) {
$order = $model->alias . '.' . $left . ' asc';
@ -275,8 +288,15 @@ class TreeBehavior extends ModelBehavior {
* @return array An associative array of records, where the id is the key, and the display field is the value
* @access public
*/
function generatetreelist(&$model, $conditions = null, $keyPath = null, $valuePath = null, $spacer = '_', $recursive = -1) {
function generatetreelist(&$model, $conditions = null, $keyPath = null, $valuePath = null, $spacer = '_', $recursive = null) {
if (is_array($conditions)) {
extract (array_merge($conditions, array('conditions' => null)));
}
$overrideRecursive = $recursive;
extract($this->settings[$model->alias]);
if (!is_null($overrideRecursive)) {
$recursive = $overrideRecursive;
}
if ($keyPath == null && $valuePath == null && $model->hasField($model->displayField)) {
$fields = array($model->primaryKey, $model->displayField, $left, $right);
@ -325,11 +345,18 @@ class TreeBehavior extends ModelBehavior {
* @return array Array of data for the parent node
* @access public
*/
function getparentnode(&$model, $id = null, $fields = null, $recursive = -1) {
function getparentnode(&$model, $id = null, $fields = null, $recursive = null) {
if (is_array($id)) {
extract (array_merge($id, array('id' => null)));
}
$overrideRecursive = $recursive;
if (empty ($id)) {
$id = $model->id;
}
extract($this->settings[$model->alias]);
if (!is_null($overrideRecursive)) {
$recursive = $overrideRecursive;
}
$parentId = $model->read($parent, $id);
if ($parentId) {
@ -351,14 +378,22 @@ class TreeBehavior extends ModelBehavior {
* @return array Array of nodes from top most parent to current node
* @access public
*/
function getpath(&$model, $id = null, $fields = null, $recursive = -1) {
function getpath(&$model, $id = null, $fields = null, $recursive = null) {
if (is_array($id)) {
extract (array_merge($id, array('id' => null)));
}
$overrideRecursive = $recursive;
if (empty ($id)) {
$id = $model->id;
}
extract($this->settings[$model->alias]);
$result = array_values($model->find('first', array('conditions' => array($model->escapeField() => $id), 'fields' => array($left, $right), 'recursive' => -1)));
if (empty($result) || !isset($result[0])) {
if (!is_null($overrideRecursive)) {
$recursive = $overrideRecursive;
}
$result = $model->find('first', array('conditions' => array($model->escapeField() => $id), 'fields' => array($left, $right), 'recursive' => $recursive));
if ($result) {
$result = array_values($result);
} else {
return null;
}
$item = $result[0];
@ -380,6 +415,9 @@ class TreeBehavior extends ModelBehavior {
* @access public
*/
function movedown(&$model, $id = null, $number = 1) {
if (is_array($id)) {
extract (array_merge($id, array('id' => null)));
}
if (!$number) {
return false;
}
@ -387,10 +425,6 @@ class TreeBehavior extends ModelBehavior {
$id = $model->id;
}
extract($this->settings[$model->alias]);
$recursive = -1;
if ($scope != '1 = 1') {
$recursive = 0;
}
list($node) = array_values($model->find('first', array(
'conditions' => array($scope, $model->escapeField() => $id),
'fields' => array($model->primaryKey, $left, $right, $parent), 'recursive' => $recursive
@ -435,6 +469,9 @@ class TreeBehavior extends ModelBehavior {
* @access public
*/
function moveup(&$model, $id = null, $number = 1) {
if (is_array($id)) {
extract (array_merge($id, array('id' => null)));
}
if (!$number) {
return false;
}
@ -442,10 +479,6 @@ class TreeBehavior extends ModelBehavior {
$id = $model->id;
}
extract($this->settings[$model->alias]);
$recursive = -1;
if ($scope != '1 = 1') {
$recursive = 0;
}
list($node) = array_values($model->find('first', array(
'conditions' => array($scope, $model->escapeField() => $id),
'fields' => array($model->primaryKey, $left, $right, $parent ), 'recursive' => $recursive
@ -496,10 +529,10 @@ class TreeBehavior extends ModelBehavior {
*/
function recover(&$model, $mode = 'parent', $missingParentAction = null) {
if (is_array($mode)) {
extract ($mode);
extract (array_merge($mode, array('mode' => 'parent')));
}
extract($this->settings[$model->alias]);
$model->recursive = -1;
$model->recursive = $recursive;
if ($mode == 'parent') {
$model->bindModel(array('belongsTo' => array('VerifyParent' => array(
'className' => $model->alias,
@ -533,7 +566,7 @@ class TreeBehavior extends ModelBehavior {
foreach ($model->find('all', array('conditions' => $scope, 'fields' => array($model->primaryKey, $parent), 'order' => $left)) as $array) {
$model->create();
$model->id = $array[$model->alias][$model->primaryKey];
$this->_setParent($model, $array[$model->alias][$parent], true);
$this->_setParent($model, $array[$model->alias][$parent]);
}
} else {
foreach ($model->find('all', array('conditions' => $scope, 'fields' => array($model->primaryKey, $parent), 'order' => $left)) as $array) {
@ -570,7 +603,7 @@ class TreeBehavior extends ModelBehavior {
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);
$nodes = $model->children($id, true, $fields, $sort, null, null, $recursive);
if ($nodes) {
foreach ($nodes as $node) {
$id = $node[$model->alias][$model->primaryKey];
@ -595,12 +628,12 @@ class TreeBehavior extends ModelBehavior {
* @access public
*/
function removefromtree(&$model, $id = null, $delete = false) {
if (empty ($id)) {
$id = $model->id;
if (is_array($id)) {
extract (array_merge($id, array('id' => null)));
}
extract($this->settings[$model->alias]);
list($node) = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $id),
'fields' => array($model->primaryKey, $left, $right, $parent), 'recursive' => -1))
'fields' => array($model->primaryKey, $left, $right, $parent), 'recursive' => $recursive))
);
if ($node[$right] == $node[$left] + 1) {
if ($delete) {
@ -610,7 +643,7 @@ class TreeBehavior extends ModelBehavior {
}
} elseif ($node[$parent]) {
list($parentNode) = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $node[$parent]),
'fields' => array($model->primaryKey, $left, $right), 'recursive' => -1))
'fields' => array($model->primaryKey, $left, $right), 'recursive' => $recursive))
);
} else {
$parentNode[$right]= $node[$right] + 1;
@ -622,12 +655,12 @@ class TreeBehavior extends ModelBehavior {
if ($delete) {
$model->updateAll(
array($model->escapeField($left) => null, $model->escapeField($right) => null, $model->escapeField($parent) => null),
array($model->escapeField($left) => 0, $model->escapeField($right) => 0, $model->escapeField($parent) => null),
array($model->escapeField() => $id)
);
return $model->delete($id);
} else {
$edge = $this->__getMax($model, $scope, $right);
$edge = $this->__getMax($model, $scope, $right, $recursive);
if ($node[$right] == $edge) {
$edge = $edge - 2;
}
@ -667,8 +700,8 @@ class TreeBehavior extends ModelBehavior {
if (!$model->find('count', array('conditions' => $scope))) {
return true;
}
$min = $this->__getMin($model, $scope, $left);
$edge = $this->__getMax($model, $scope, $right);
$min = $this->__getMin($model, $scope, $left, $recursive);
$edge = $this->__getMax($model, $scope, $right, $recursive);
$errors = array();
for ($i = $min; $i <= $edge; $i++) {
@ -681,10 +714,9 @@ class TreeBehavior extends ModelBehavior {
}
}
}
$count = $model->find('count', array('conditions' => array($scope, $model->escapeField($right) => '< ' . $model->escapeField($left))));
if ($count != 0) {
$node = $model->find('first', array('conditions' => array($scope, $model->escapeField($right) => '< ' . $model->escapeField($left))));
$errors[] = array('node', $node[$model->primaryKey], 'left greater than right.');
$node = $model->find('first', array('conditions' => array($scope, $model->escapeField($right) . '< ' . $model->escapeField($left)), 'recursive' => 0));
if ($node) {
$errors[] = array('node', $node[$model->alias][$model->primaryKey], 'left greater than right.');
}
$model->bindModel(array('belongsTo' => array('VerifyParent' => array(
@ -693,8 +725,11 @@ class TreeBehavior extends ModelBehavior {
'fields' => array($model->primaryKey, $left, $right, $parent)
))));
foreach ($model->find('all', array('conditions' => $scope, 'recursive' => 1)) as $instance) {
if ($instance[$model->alias][$parent]) {
foreach ($model->find('all', array('conditions' => $scope, 'recursive' => 0)) as $instance) {
if (is_null($instance[$model->alias][$left]) || is_null($instance[$model->alias][$right])) {
$errors[] = array('node', $instance[$model->alias][$model->primaryKey],
'has invalid left or right values');
} elseif ($instance[$model->alias][$parent]) {
if (!$instance['VerifyParent'][$model->primaryKey]) {
$errors[] = array('node', $instance[$model->alias][$model->primaryKey],
'The parent node ' . $instance[$model->alias][$parent] . ' doesn\'t exist');
@ -705,7 +740,7 @@ class TreeBehavior extends ModelBehavior {
$errors[] = array('node', $instance[$model->alias][$model->primaryKey],
'right greater than parent (node ' . $instance['VerifyParent'][$model->primaryKey] . ').');
}
} elseif ($model->find('count', array('conditions' => array($scope, $left . '< ' . $instance[$model->alias][$left], $right . '> ' . $instance[$model->alias][$right])))) {
} elseif ($model->find('count', array('conditions' => array($scope, $left . '< ' . $instance[$model->alias][$left], $right . '> ' . $instance[$model->alias][$right]), 'recursive' => 0))) {
$errors[] = array('node', $instance[$model->alias][$model->primaryKey], 'The parent field is blank, but has a parent');
}
}
@ -728,18 +763,18 @@ class TreeBehavior extends ModelBehavior {
* @return boolean true on success, false on failure
* @access protected
*/
function _setParent(&$model, $parentId = null) {
function _setParent(&$model, $parentId = null, $created = false) {
extract($this->settings[$model->alias]);
list($node) = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $model->id),
'fields' => array($model->primaryKey, $parent, $left, $right), 'recursive' => -1)));
$edge = $this->__getMax($model, $scope, $right);
'fields' => array($model->primaryKey, $parent, $left, $right), 'recursive' => $recursive)));
$edge = $this->__getMax($model, $scope, $right, $recursive, $created);
if (empty ($parentId)) {
$this->__sync($model, $edge - $node[$left] + 1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right]);
$this->__sync($model, $node[$right] - $node[$left] + 1, '-', '> ' . $node[$left]);
$this->__sync($model, $edge - $node[$left] + 1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right], $created);
$this->__sync($model, $node[$right] - $node[$left] + 1, '-', '> ' . $node[$left], $created);
} else {
$parentNode = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $parentId),
'fields' => array($model->primaryKey, $left, $right), 'recursive' => -1)));
'fields' => array($model->primaryKey, $left, $right), 'recursive' => $recursive)));
if (empty($parentNode) || empty($parentNode[0])) {
return false;
@ -752,25 +787,24 @@ class TreeBehavior extends ModelBehavior {
} elseif (($node[$left] < $parentNode[$left]) && ($parentNode[$right] < $node[$right])) {
return false;
}
if (empty ($node[$left]) && empty ($node[$right])) {
$this->__sync($model, 2, '+', '>= ' . $parentNode[$right]);
$this->__sync($model, 2, '+', '>= ' . $parentNode[$right], $created);
$model->save(array($left => $parentNode[$right], $right => $parentNode[$right] + 1, $parent => $parentId), false);
} else {
$this->__sync($model, $edge - $node[$left] +1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right]);
$this->__sync($model, $edge - $node[$left] +1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right], $created);
$diff = $node[$right] - $node[$left] + 1;
if ($node[$left] > $parentNode[$left]) {
if ($node[$right] < $parentNode[$right]) {
$this->__sync($model, $diff, '-', 'BETWEEN ' . $node[$right] . ' AND ' . ($parentNode[$right] - 1));
$this->__sync($model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge);
$this->__sync($model, $diff, '-', 'BETWEEN ' . $node[$right] . ' AND ' . ($parentNode[$right] - 1), $created);
$this->__sync($model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge, $created);
} else {
$this->__sync($model, $diff, '+', 'BETWEEN ' . $parentNode[$right] . ' AND ' . $node[$right]);
$this->__sync($model, $edge - $parentNode[$right] + 1, '-', '> ' . $edge);
$this->__sync($model, $diff, '+', 'BETWEEN ' . $parentNode[$right] . ' AND ' . $node[$right], $created);
$this->__sync($model, $edge - $parentNode[$right] + 1, '-', '> ' . $edge, $created);
}
} else {
$this->__sync($model, $diff, '-', 'BETWEEN ' . $node[$right] . ' AND ' . ($parentNode[$right] - 1));
$this->__sync($model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge);
$this->__sync($model, $diff, '-', 'BETWEEN ' . $node[$right] . ' AND ' . ($parentNode[$right] - 1), $created);
$this->__sync($model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge, $created);
}
}
}
@ -785,8 +819,15 @@ class TreeBehavior extends ModelBehavior {
* @return int
* @access private
*/
function __getMax($model, $scope, $right, $recursive = -1) {
function __getMax($model, $scope, $right, $recursive = -1, $created = false) {
$db =& ConnectionManager::getDataSource($model->useDbConfig);
if ($created) {
if (is_string($scope)) {
$scope .= ' AND ' . $model->alias . '.' . $model->primaryKey . ' <> ' . $model->id;
} else {
$scope['NOT'][$model->alias . '.' . $model->primaryKey] = $model->id;
}
}
list($edge) = array_values($model->find('first', array('conditions' => $scope, 'fields' => $db->calculate($model, 'max', array($right)), 'recursive' => $recursive)));
return ife(empty ($edge[$right]), 0, $edge[$right]);
}
@ -799,8 +840,9 @@ class TreeBehavior extends ModelBehavior {
* @return int
* @access private
*/
function __getMin($model, $scope, $left) {
list($edge) = array_values($model->find('first', array('conditions' => $scope, 'fields' => 'MIN(' . $left . ') AS ' . $left, 'recursive' => -1)));
function __getMin($model, $scope, $left, $recursive = -1) {
$db =& ConnectionManager::getDataSource($model->useDbConfig);
list($edge) = array_values($model->find('first', array('conditions' => $scope, 'fields' => $db->calculate($model, 'min', array($left)), 'recursive' => $recursive)));
return ife(empty ($edge[$left]), 0, $edge[$left]);
}
/**
@ -815,10 +857,12 @@ class TreeBehavior extends ModelBehavior {
* @param string $field
* @access private
*/
function __sync(&$model, $shift, $dir = '+', $conditions = array(), $field = 'both') {
function __sync(&$model, $shift, $dir = '+', $conditions = array(), $created = false, $field = 'both') {
$modelRecursive = $model->recursive;
extract($this->settings[$model->alias]);
$model->recursive = $recursive;
if ($field == 'both') {
$this->__sync($model, $shift, $dir, $conditions, $left);
$this->__sync($model, $shift, $dir, $conditions, $created, $left);
$field = $right;
}
if (is_string($conditions)) {
@ -831,7 +875,11 @@ class TreeBehavior extends ModelBehavior {
$conditions= array_merge($conditions, $scope);
}
}
if ($created) {
$conditions['NOT'][$model->alias . '.' . $model->primaryKey] = $model->id;
}
$model->updateAll(array($model->alias . '.' . $field => $model->alias . '.' . $field . ' ' . $dir . ' ' . $shift), $conditions);
$model->recursive = $modelRecursive;
}
}
?>