/** * Sets the parent of the given node * * The force parameter is used to override the "don't change the parent to the current parent" logic in the event * of recovering a corrupted table, or creating new nodes. Otherwise it should always be false. In reality this * method could be private, since calling save with parent_id set also calls setParent * * @param AppModel $Model Model instance * @param mixed $parentId * @return boolean true on success, false on failure * @access protected */ 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' => $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], $created); $this->__sync($Model, $node[$right] - $node[$left] + 1, '-', '> ' . $node[$left], $created); } else { $values = $Model->find('first', array('conditions' => array($scope, $Model->escapeField() => $parentId), 'fields' => array($Model->primaryKey, $left, $right), 'recursive' => $recursive)); if ($values === false) { return false; } $parentNode = array_values($values); if (empty($parentNode) || empty($parentNode[0])) { return false; } $parentNode = $parentNode[0]; if ($Model->id == $parentId) { return false; } elseif ($node[$left] < $parentNode[$left] && $parentNode[$right] < $node[$right]) { return false; } if (empty($node[$left]) && empty($node[$right])) { $this->__sync($Model, 2, '+', '>= ' . $parentNode[$right], $created); $result = $Model->save(array($left => $parentNode[$right], $right => $parentNode[$right] + 1, $parent => $parentId), array('validate' => false, 'callbacks' => false)); $Model->data = $result; } else { $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), $created); $this->__sync($Model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge, $created); } else { $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), $created); $this->__sync($Model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge, $created); } } } return true; }
/** * Sets the parent of the given node * * The force parameter is used to override the "don't change the parent to the current parent" logic in the event * of recovering a corrupted table, or creating new nodes. Otherwise it should always be false. In reality this * method could be private, since calling save with parent_id set also calls setParent * * @param AppModel $Model Model instance * @param mixed $parentId * @return boolean true on success, false on failure */ function _setParent(&$Model, $parentId = null, $created = false) { extract($this->settings[$Model->alias]); $cachequeries = $Model->cacheQueries; $Model->cacheQueries = false; list($node) = array_values($Model->find('first', array('conditions' => array($scope, $Model->escapeField() => $Model->id), 'fields' => array($Model->primaryKey, $parent, $left, $right), 'recursive' => $recursive))); if (empty($parentId)) { $edge = $this->__getMax($Model, $scope, $right, $recursive, $created); $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' => $recursive))); if (empty($parentNode) || empty($parentNode[0])) { return false; } $parentNode = $parentNode[0]; $edge = $this->__getMax($Model, "{$Model->alias}.{$left} <= " . $parentNode[$left], $right, $recursive, $created); if ($Model->id == $parentId) { return false; } elseif ($node[$left] < $parentNode[$left] && $parentNode[$right] < $node[$right]) { return false; } if (empty($node[$left]) && empty($node[$right])) { $result = $Model->save(array($left => $parentNode[$right], $right => $parentNode[$right] + 1, $parent => $parentId), array('validate' => false, 'callbacks' => false)); $this->__sync($Model, 2, '+', '>= ' . $parentNode[$right], $created); $Model->data = $result; } else { $diff = $node[$right] - $node[$left]; // increment position values of new family of retargeted subtree $this->__sync($Model, $diff + 1, '+', '>= ' . $parentNode[$right], $created); // relocate target node under parent $this->__sync($Model, $parentNode[$right] - $node[$left], '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right], $created); // reduce values in former position $nodeTreeMax = $this->__getMax($Model, "{$Model->alias}.{$left} <= " . $node[$left], $right, $recursive, $created); $this->__sync($Model, $diff + 1, '-', 'BETWEEN ' . $node[$right] . ' AND ' . $nodeTreeMax, $created); } } $Model->cacheQueries = $cachequeries; return true; }
/** * Get the number of child nodes * * If the direct parameter is set to true, only the direct children are counted (based upon the parent_id field) * If false is passed for the id parameter, all top level nodes are counted, or all nodes are counted. * * @param AppModel $Model Model instance * @param integer $id The ID of the record to read * @param boolean $direct whether to count direct, or all, children * @return integer number of child nodes * @access public */ function getChildCount(&$Model, $id = null, $direct = false) { if (!$id && $Model->id) { $id = $Model->id; } extract($this->settings[$Model->alias]); if ($direct) { return $Model->find('count', array('conditions' => array($Model->escapeField($parent) => $id))); } else { // Use cached node if possible if (isset($Model->data[$Model->alias][$left]) && isset($Model->data[$Model->alias][$right])) { $node = $Model->data[$Model->alias]; } else { // Get node if (($node = $this->_node($Model, $id)) === false) { return false; } } return ($node[$right] - $node[$left] - 1) / 2; } }
/** * Sets the parent of the given node * * The force parameter is used to override the "don't change the parent to the current parent" logic in the event * of recovering a corrupted table, or creating new nodes. Otherwise it should always be false. In reality this * method could be private, since calling save with parent_id set also calls setParent * * @param AppModel $model * @param mixed $parentId * @return boolean true on success, false on failure * @access protected */ function _setParent(&$model, $parentId = null) { 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); 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]); } else { 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)) { return false; } elseif ($model->id == $parentId) { return false; } elseif ($node[$left] < $parentNode[$left] && $parentNode[$right] < $node[$right]) { return false; } if (empty($node[$left]) && empty($node[$right])) { $this->__sync($model, 2, '+', '>= ' . $parentNode[$right]); $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]); $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); } else { $this->__sync($model, $diff, '+', 'BETWEEN ' . $parentNode[$right] . ' AND ' . $node[$right]); $this->__sync($model, $edge - $parentNode[$right] + 1, '-', '> ' . $edge); } } else { $this->__sync($model, $diff, '-', 'BETWEEN ' . $node[$right] . ' AND ' . ($parentNode[$right] - 1)); $this->__sync($model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge); } } } return true; }
/** * Reorder the node without changing the parent. * * If the node is the last child, or is a top level node with no subsequent node this method will return false * * @since 1.2 * @param AppModel $model * @param mixed $id The ID of the record to move * @param int $number how many places to move the node * @return boolean True on success, false on failure * @access public */ function move_down(&$model, $id = null, $number = 1) { if (empty($id)) { $id = $model->id; } extract($this->settings[$model->name]); list($node) = array_values($model->find(array($scope, $model->escapeField() => $id), array($model->primaryKey, $left, $right, $parent), null, -1)); if ($node[$parent]) { list($parentNode) = array_values($model->find(array($scope, $model->escapeField() => $node[$parent]), array($model->primaryKey, $left, $right), null, -1)); if ($node[$right] + 1 == $parentNode[$right]) { return false; } } $nextNode = $model->find(array($scope, $left => $node[$right] + 1), array($model->primaryKey, $left, $right), null, -1); if ($nextNode) { list($nextNode) = array_values($nextNode); } else { return false; } $edge = $this->__get_max($model, $scope, $right); // First, move the node (and subnodes) to the end $this->__sync($model, $edge - $node[$left] + 1, '+', "BETWEEN {$node[$left]} AND {$node[$right]}", $scope); // Then move the 'next' node backwards $this->__sync($model, $nextNode[$left] - $node[$left], '-', "BETWEEN {$nextNode[$left]} AND {$nextNode[$right]}", $scope); // Then put in the hole $this->__sync($model, $edge - $node[$left] - ($nextNode[$right] - $nextNode[$left]), '-', "> {$edge}", $scope); if ($number > 1) { return $this->move_down($model, $number - 1); } else { return true; } }
/** * When doing any update all calls, you want to avoid updating the record * you've just modified, as the order will have been set already, so exclude * it with some conditions. * * @param AppModel $model * @return array Array Model.primary_key => $id */ function conditionsNotCurrent(&$model) { if (!($id = $model->id)) { $id = $model->getInsertID(); } return array($model->escapeField($model->primaryKey) . ' <>' => $id); }
/** * Callback... * * Allows automagic sorting and field retrival. * Add support for $query['localized'] that will filter out items not translated to the current language * * @param AppModel $model */ public function beforeFind($model, $query) { // $this->_checkSchema($model); $fields = $this->settings[$model->alias]; $currLocale = SlConfigure::read('I18n.locale'); if (empty($currLocale)) { return $query; } if (!empty($query['localized'])) { if (!is_array($query['conditions'])) { $query['conditions'] = array($query['conditions']); } $query['conditions']["_{$currLocale}"] = true; unset($query['localized']); } // reorder based on current localization if (count($query['order']) > 0) { $orderArray =& $query['order']; foreach ($orderArray as &$order) { if (!is_array($order) && $order) { $parts = explode(' ', $order); $order = array($parts[0] => empty($parts[1]) ? 'ASC' : $parts[1]); } if (is_array($order)) { $newOrder = array(); foreach ($order as $key => $direction) { $field = $key; if (is_int($field)) { $field = $direction; $direction = 'ASC'; } $temp = explode('.', $field); if (count($temp) === 1) { $modelClass = $model->alias; $fieldName = Inflector::slug($temp[0], ''); } else { $modelClass = Inflector::slug($temp[0], ''); $fieldName = Inflector::slug($temp[1], ''); } if (isset($this->settings[$modelClass]) && in_array($fieldName, $this->settings[$modelClass])) { $newOrder[$modelClass . '.' . $fieldName . '_' . $currLocale] = $direction; } else { $newOrder[$key] = $direction; } } $order = $newOrder; } } } if (is_string($query['fields'])) { $query['fields'] = array($query['fields']); } $locales = SlConfigure::read('I18n.locales'); if (is_array($query['fields']) && count($query['fields']) > 0) { // Add {$field}_{$lang} to fields list (when needed) foreach ($fields as $field) { foreach ($query['fields'] as $fieldName) { $temp = explode('.', $fieldName); if (count($temp) === 1) { $modelClass = $model->alias; $fieldName = Inflector::slug($temp[0], ''); } else { $modelClass = Inflector::slug($temp[0], ''); $fieldName = Inflector::slug($temp[1], ''); } if (isset($this->settings[$modelClass]) && in_array($fieldName, $this->settings[$modelClass])) { $query['fields'][] = $modelClass . '.' . $fieldName . '_' . $currLocale; } // foreach (array($field, $model->alias.'.'.$field, $model->escapeField($field)) as $_field) { // if ($_field === $fieldName) { // foreach ($locales as $locale) { // $query['fields'][] = $model->alias.'.'.$field.'_'.$locale; // } // //unset($query['fields'][$fieldName]); // } // } } } } // transcript conditions if ($query['conditions']) { $fields2 = array(); $recursive = isset($query['recursive']) ? $query['recursive'] : $model->recursive; foreach ($fields as $field) { foreach (array($field, $model->alias . '.' . $field, $model->escapeField($field)) as $_field) { $fields2["{$_field} LIKE"] = $model->alias . '.' . $field . '_' . $currLocale . ' LIKE'; //if ($recursive < 0 || $field != $_field) { $fields2[$_field] = "{$_field}_{$currLocale}"; //} } } if ($fields2) { $query['conditions'] = $this->__parseConditions($query['conditions'], $fields2); } } return $query; }
/** * Sets the parent of the given node * * The force parameter is used to override the "don't change the parent to the current parent" logic in the event * of recovering a corrupted table, or creating new nodes. Otherwise it should always be false. In reality this * method could be private, since calling save with parent_id set also calls setParent * * @param AppModel $Model Model instance * @param mixed $parentId * @return boolean true on success, false on failure * @access protected */ function _setParent(&$Model, $parentId = null, $created = false) { extract($this->settings[$Model->alias]); $cachequeries = $Model->cacheQueries; $Model->cacheQueries = false; list($node) = array_values($Model->find('first', array('conditions' => array($scope, $Model->escapeField() => $Model->id), 'fields' => array($Model->primaryKey, $parent, $left, $right), 'recursive' => $recursive))); if (empty($parentId)) { $edge = $this->__getMax($Model, $scope, $right, $recursive, $created); $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' => $recursive))); if (empty($parentNode) || empty($parentNode[0])) { return false; } $parentNode = $parentNode[0]; // find current rightmost edge of parent tree $edge = $this->__getMax($Model, "{$Model->alias}.{$left} <= " . $parentNode[$left], $right, $recursive, $created); if ($Model->id == $parentId) { return false; } elseif ($node[$left] < $parentNode[$left] && $parentNode[$right] < $node[$right]) { return false; } if (empty($node[$left]) && empty($node[$right])) { $result = $Model->save(array($left => $parentNode[$right], $right => $parentNode[$right] + 1, $parent => $parentId), array('validate' => false, 'callbacks' => false)); $this->__sync($Model, 2, '+', '>= ' . $parentNode[$right], $created); $Model->data = $result; } else { $diff = $node[$right] - $node[$left]; // swap subtree out into free memory and get updated positions $this->__swapout($Model, $node); $this->__updateNode($Model, $parentNode); // push new neighbours on our new right side to make room and get updated parent $this->__sync($Model, $diff + 1, '+', '>= ' . $parentNode[$right], $created); $this->__updateNode($Model, $parentNode); $this->__updateNode($Model, $node); // relocate target node under parent $this->__sync($Model, $parentNode[$left] - $node[$left] + $diff, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right], $created); } } $Model->cacheQueries = $cachequeries; return true; }