Advertisement
Guest User

Untitled

a guest
Oct 18th, 2012
207
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 43.99 KB | None | 0 0
  1. <?php
  2. /**
  3.  * Tree behavior class.
  4.  *
  5.  * Enables a model object to act as a node-based tree.
  6.  *
  7.  * PHP 5
  8.  *
  9.  * CakePHP :  Rapid Development Framework (http://cakephp.org)
  10.  * Copyright 2005-2011, Cake Software Foundation, Inc.
  11.  *
  12.  * Licensed under The MIT License
  13.  * Redistributions of files must retain the above copyright notice.
  14.  *
  15.  * @copyright     Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  16.  * @link          http://cakephp.org CakePHP Project
  17.  * @package       Cake.Model.Behavior
  18.  * @since         CakePHP v 1.2.0.4487
  19.  * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
  20.  */
  21.  
  22. /**
  23.  * Tree Behavior.
  24.  *
  25.  * Enables a model object to act as a node-based tree. Using Modified Preorder Tree Traversal
  26.  *
  27.  * @see http://en.wikipedia.org/wiki/Tree_traversal
  28.  * @package       Cake.Model.Behavior
  29.  * @link http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html
  30.  */
  31. class TreeBehavior extends ModelBehavior {
  32.  
  33. /**
  34.  * Errors
  35.  *
  36.  * @var array
  37.  */
  38.     public $errors = array();
  39.  
  40. /**
  41.  * Defaults
  42.  *
  43.  * @var array
  44.  */
  45.     protected $_defaults = array(
  46.         'parent' => 'parent_id',
  47.         'left' => 'lft',
  48.         'right' => 'rght',
  49.         'scope' => '1 = 1',
  50.         'type' => 'nested',
  51.         '__parentChange' => false,
  52.         'recursive' => -1,
  53.         'multiTree' => false,
  54.         '__multiTreeKey' => null,
  55.         'scopeField' => false,
  56.         'recursive' => -1
  57.     );
  58.  
  59. /**
  60.  * Initiate Tree behavior
  61.  *
  62.  * @param Model $Model instance of model
  63.  * @param array $config array of configuration settings.
  64.  * @return void
  65.  */
  66.     public function setup($Model, $config = array()) {
  67.         if (isset($config[0])) {
  68.             $config['type'] = $config[0];
  69.             unset($config[0]);
  70.         }
  71.         $settings = array_merge($this->_defaults, $config);
  72.  
  73.         if (in_array($settings['scope'], $Model->getAssociated('belongsTo'))) {
  74.             $data = $Model->getAssociated($settings['scope']);
  75.             $parent = $Model->{$settings['scope']};
  76.             $settings['scope'] = $Model->alias . '.' . $data['foreignKey'] . ' = ' . $parent->alias . '.' . $parent->primaryKey;
  77.             $settings['scopeField'] = $data['foreignKey'];
  78.             $settings['recursive'] = 0;
  79.             if ($settings['multiTree']) {
  80.                 $settings['__multiTreeKey'] = $data['foreignKey'];
  81.             }
  82.         }
  83.        
  84.         $this->settings[$Model->alias] = $settings;
  85.     }
  86.  
  87. /**
  88.  * After save method. Called after all saves
  89.  *
  90.  * Overridden to transparently manage setting the lft and rght fields if and only if the parent field is included in the
  91.  * parameters to be saved.
  92.  *
  93.  * @param Model $Model Model instance.
  94.  * @param boolean $created indicates whether the node just saved was created or updated
  95.  * @return boolean true on success, false on failure
  96.  */
  97.     public function afterSave($Model, $created) {
  98.         extract($this->settings[$Model->alias]);
  99.         if ($created) {
  100.             if ((isset($Model->data[$Model->alias][$parent])) && $Model->data[$Model->alias][$parent]) {
  101.                 return $this->_setParent($Model, $Model->data[$Model->alias][$parent], $created);
  102.             }
  103.         } elseif ($__parentChange) {
  104.             $this->settings[$Model->alias]['__parentChange'] = false;
  105.             return $this->_setParent($Model, $Model->data[$Model->alias][$parent]);
  106.         }
  107.     }
  108.  
  109. /**
  110.  * Runs before a find() operation
  111.  *
  112.  * @param Model $Model  Model using the behavior
  113.  * @param array $query Query parameters as set by cake
  114.  * @return array
  115.  */
  116.     public function beforeFind($Model, $query) {
  117.         if ($Model->findQueryType == 'threaded' && !isset($query['parent'])) {
  118.             $query['parent'] = $this->settings[$Model->alias]['parent'];
  119.         }
  120.         return $query;
  121.     }
  122.  
  123. /**
  124.  * Before delete method. Called before all deletes
  125.  *
  126.  * Will delete the current node and all children using the deleteAll method and sync the table
  127.  *
  128.  * @param Model $Model Model instance
  129.  * @param boolean $cascade
  130.  * @return boolean true to continue, false to abort the delete
  131.  */
  132.     public function beforeDelete($Model, $cascade = true) {
  133.         extract($this->settings[$Model->alias]);
  134.         $scope = $this->_addScopeField($Model, $scope);
  135.         list($name, $data) = array($Model->alias, $Model->read());
  136.         $data = $data[$name];
  137.  
  138.         if (!$data[$right] || !$data[$left]) {
  139.             return true;
  140.         }
  141.         $diff = $data[$right] - $data[$left] + 1;
  142.  
  143.         if ($multiTree) {
  144.             $scope = $this->_multiTreeScope($Model, $data[$__multiTreeKey]);
  145.         }
  146.         if ($diff > 2) {
  147.             if (is_string($scope)) {
  148.                 $scope = array($scope);
  149.             }
  150.             $scope[]["{$Model->alias}.{$left} BETWEEN ? AND ?"] = array($data[$left] + 1, $data[$right] - 1);
  151.             $Model->deleteAll($scope);
  152.         }
  153.         $this->_sync($Model, $diff, '-', '> ' . $data[$right]);
  154.         return true;
  155.     }
  156.  
  157. /**
  158.  * Before save method. Called before all saves
  159.  *
  160.  * Overridden to transparently manage setting the lft and rght fields if and only if the parent field is included in the
  161.  * parameters to be saved. For newly created nodes with NO parent the left and right field values are set directly by
  162.  * this method bypassing the setParent logic.
  163.  *
  164.  * @since         1.2
  165.  * @param Model $Model Model instance
  166.  * @return boolean true to continue, false to abort the save
  167.  */
  168.     public function beforeSave($Model) {
  169.         extract($this->settings[$Model->alias]);
  170.         $scope = $this->_addScopeField($Model, $scope);
  171.  
  172.         $this->_addToWhitelist($Model, array($left, $right));
  173.         if (!$Model->id) {
  174.             if (array_key_exists($parent, $Model->data[$Model->alias]) && $Model->data[$Model->alias][$parent]) {
  175.                 $parentFields = array($Model->primaryKey, $right);
  176.                 if ($multiTree) {
  177.                     $parentFields[] = $__multiTreeKey;
  178.                     $scope = array();
  179.                 }
  180.                 $parentNode = $Model->find('first', array(
  181.                     'conditions' => array($scope, $Model->escapeField() => $Model->data[$Model->alias][$parent]),
  182.                     'fields' => $parentFields, 'recursive' => $recursive
  183.                 ));
  184.                 if (!$parentNode) {
  185.                     return false;
  186.                 }
  187.                 list($parentNode) = array_values($parentNode);
  188.                 $Model->data[$Model->alias][$left] = 0; //$parentNode[$right];
  189.                 $Model->data[$Model->alias][$right] = 0; //$parentNode[$right] + 1;
  190.                 if ($multiTree) {
  191.                     $this->_addToWhitelist($Model, $__multiTreeKey);
  192.                     $Model->data[$Model->alias][$__multiTreeKey] = $parentNode[$__multiTreeKey];
  193.                     $scope = $this->_multiTreeScope($Model, $parentNode[$__multiTreeKey]);
  194.                 }
  195.             } else {
  196.                 if ($multiTree) {
  197.                     if (array_key_exists($__multiTreeKey, $Model->data[$Model->alias]) && $Model->data[$Model->alias][$__multiTreeKey]) { //no parent, must have tree_id
  198.                         $scope = $this->_multiTreeScope($Model, $Model->data[$Model->alias][$__multiTreeKey]);
  199.                         $this->_addToWhitelist($Model, $__multiTreeKey);
  200.                     } else {
  201.                         return false;
  202.                     }
  203.                 }
  204.                 $edge = $this->_getMax($Model, $scope, $right, $recursive);
  205.                 $Model->data[$Model->alias][$left] = $edge + 1;
  206.                 $Model->data[$Model->alias][$right] = $edge + 2;
  207.             }
  208.         } elseif (array_key_exists($parent, $Model->data[$Model->alias])) {
  209.             if ($Model->data[$Model->alias][$parent] != $Model->field($parent)) {
  210.                 $this->settings[$Model->alias]['__parentChange'] = true;
  211.             }
  212.             if (!$Model->data[$Model->alias][$parent]) {
  213.                 $Model->data[$Model->alias][$parent] = null;
  214.                 $this->_addToWhitelist($Model, $parent);
  215.             } else {
  216.                 $fields = array($Model->primaryKey, $parent, $left, $right);
  217.                 if ($multiTree) {
  218.                     $fields[] = $__multiTreeKey;
  219.                     $scope = array();
  220.                 }
  221.                 $values = $Model->find('first', array(
  222.                     'conditions' => array($scope, $Model->escapeField() => $Model->id),
  223.                     'fields' => $fields, 'recursive' => $recursive)
  224.                 );
  225.  
  226.                 if ($values === false) {
  227.                     return false;
  228.                 }
  229.                 list($node) = array_values($values);
  230.  
  231.                 if ($multiTree) {
  232.                     $scope = $this->_multiTreeScope($Model, $node[$__multiTreeKey]);
  233.                 }
  234.                 $parentNode = $Model->find('first', array(
  235.                     'conditions' => array($scope, $Model->escapeField() => $Model->data[$Model->alias][$parent]),
  236.                     'fields' => $fields, 'recursive' => $recursive
  237.                 ));
  238.                 if (!$parentNode) {
  239.                     return false;
  240.                 }
  241.                 list($parentNode) = array_values($parentNode);
  242.  
  243.                 if (($node[$left] < $parentNode[$left]) && ($parentNode[$right] < $node[$right])) {
  244.                     return false;
  245.                 } elseif ($node[$Model->primaryKey] == $parentNode[$Model->primaryKey]) {
  246.                     return false;
  247.                 }
  248.             }
  249.         }
  250.         return true;
  251.     }
  252.  
  253. /**
  254.  * Get the number of child nodes
  255.  *
  256.  * If the direct parameter is set to true, only the direct children are counted (based upon the parent_id field)
  257.  * If false is passed for the id parameter, all top level nodes are counted, or all nodes are counted.
  258.  *
  259.  * @param Model $Model Model instance
  260.  * @param mixed $id The ID of the record to read or false to read all top level nodes
  261.  * @param boolean $direct whether to count direct, or all, children
  262.  * @return integer number of child nodes
  263.  * @link http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html#TreeBehavior::childCount
  264.  */
  265.     public function childCount($Model, $id = null, $direct = false) {
  266.         if (is_array($id)) {
  267.             extract (array_merge(array('id' => null), $id));
  268.         }
  269.         if ($id === null && $Model->id) {
  270.             $id = $Model->id;
  271.         } elseif (!$id) {
  272.             $id = null;
  273.         }
  274.         extract($this->settings[$Model->alias]);
  275.         $scope = $this->_addScopeField($Model, $scope);
  276.  
  277.         if ($multiTree) {
  278.             if (isset($Model->data[$Model->alias][$__multiTreeKey]) && $Model->data[$Model->alias][$__multiTreeKey]) {
  279.                 $scope = $Model->data[$Model->alias][$__multiTreeKey];
  280.             } else {
  281.                 $node = $Model->find('first', array('conditions' => array($Model->escapeField() => $id), 'fields' => array($__multiTreeKey)));
  282.                 if (!$node) {
  283.                     return false;
  284.                 }
  285.                 $scope = $node[$Model->alias][$__multiTreeKey];
  286.             }
  287.         }
  288.         if ($direct) {
  289.             return $Model->find('count', array('conditions' => array($scope, $Model->escapeField($parent) => $id)));
  290.         }
  291.  
  292.         if ($id === null) {
  293.             return $Model->find('count', array('conditions' => $scope));
  294.         } elseif ($Model->id === $id && isset($Model->data[$Model->alias][$left]) && isset($Model->data[$Model->alias][$right])) {
  295.             $data = $Model->data[$Model->alias];
  296.         } else {
  297.             $data = $Model->find('first', array('conditions' => array($scope, $Model->escapeField() => $id), 'recursive' => $recursive));
  298.             if (!$data) {
  299.                 return 0;
  300.             }
  301.             $data = $data[$Model->alias];
  302.         }
  303.         return ($data[$right] - $data[$left] - 1) / 2;
  304.     }
  305.  
  306. /**
  307.  * Get the child nodes of the current model
  308.  *
  309.  * If the direct parameter is set to true, only the direct children are returned (based upon the parent_id field)
  310.  * If false is passed for the id parameter, top level, or all (depending on direct parameter appropriate) are counted.
  311.  *
  312.  * @param Model $Model Model instance
  313.  * @param mixed $id The ID of the record to read
  314.  * @param boolean $direct whether to return only the direct, or all, children
  315.  * @param mixed $fields Either a single string of a field name, or an array of field names
  316.  * @param string $order SQL ORDER BY conditions (e.g. "price DESC" or "name ASC") defaults to the tree order
  317.  * @param integer $limit SQL LIMIT clause, for calculating items per page.
  318.  * @param integer $page Page number, for accessing paged data
  319.  * @param integer $recursive The number of levels deep to fetch associated records
  320.  * @return array Array of child nodes
  321.  * @link http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html#TreeBehavior::children
  322.  */
  323.     public function children($Model, $id = null, $direct = false, $fields = null, $order = null, $limit = null, $page = 1, $recursive = null) {
  324.         if (is_array($id)) {
  325.             extract (array_merge(array('id' => null), $id));
  326.         }
  327.         $overrideRecursive = $recursive;
  328.  
  329.         if ($id === null && $Model->id) {
  330.             $id = $Model->id;
  331.         } elseif (!$id) {
  332.             $id = null;
  333.         }
  334.         $name = $Model->alias;
  335.         extract($this->settings[$Model->alias]);
  336.         $scope = $this->_addScopeField($Model, $scope);
  337.  
  338.         if (!is_null($overrideRecursive)) {
  339.             $recursive = $overrideRecursive;
  340.         }
  341.         if (!$order) {
  342.             $order = $Model->alias . '.' . $left . ' asc';
  343.         }
  344.         if ($multiTree) {
  345.             $order = array($Model->alias . '.' . $__multiTreeKey . ' asc', $order);
  346.             $scope = array();
  347.         }
  348.         if ($direct) {
  349.             $conditions = array($scope, $Model->escapeField($parent) => $id);
  350.             return $Model->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
  351.         }
  352.  
  353.         if (!$id) {
  354.             if ($multiTree) {
  355.                 return false;
  356.             }
  357.             $conditions = $scope;
  358.         } else {
  359.             $resultFields = array($left, $right);
  360.             if($multiTree){
  361.                 $resultFields[] = $__multiTreeKey;
  362.             }
  363.             $result = array_values((array)$Model->find('first', array(
  364.                 'conditions' => array($scope, $Model->escapeField() => $id),
  365.                 'fields' => $resultFields,
  366.                 'recursive' => $recursive
  367.             )));
  368.  
  369.             if (empty($result) || !isset($result[0])) {
  370.                 return array();
  371.             }
  372.             $conditions = array($scope,
  373.                 $Model->escapeField($right) . ' <' => $result[0][$right],
  374.                 $Model->escapeField($left) . ' >' => $result[0][$left]
  375.             );
  376.             if ($multiTree) {
  377.                 $scope = $this->_multiTreeScope($Model, $result[0][$__multiTreeKey]);
  378.                 $conditions = array_merge($scope, $conditions);
  379.             }
  380.         }
  381.         return $Model->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
  382.     }
  383.  
  384. /**
  385.  * A convenience method for returning a hierarchical array used for HTML select boxes
  386.  *
  387.  * @param Model $Model Model instance
  388.  * @param mixed $conditions SQL conditions as a string or as an array('field' =>'value',...)
  389.  * @param string $keyPath A string path to the key, i.e. "{n}.Post.id"
  390.  * @param string $valuePath A string path to the value, i.e. "{n}.Post.title"
  391.  * @param string $spacer The character or characters which will be repeated
  392.  * @param integer $recursive The number of levels deep to fetch associated records
  393.  * @param integer $treeScope id of the desired tree if using multiTree (returns all by default)
  394.  * @return array An associative array of records, where the id is the key, and the display field is the value
  395.  * @link http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html#TreeBehavior::generateTreeList
  396.  */
  397.     public function generateTreeList($Model, $conditions = null, $keyPath = null, $valuePath = null, $spacer = '_', $recursive = null, $treeScope = null) {
  398.         $overrideRecursive = $recursive;
  399.         extract($this->settings[$Model->alias]);
  400.  
  401.         if ($multiTree && !$treeScope) {
  402.             $trees = $Model->find('all', array('fields' => 'DISTINCT ' . $Model->escapeField($__multiTreeKey), 'recursive' => -1));
  403.             $conditions = (array)$conditions;
  404.  
  405.             foreach($trees as $tree){
  406.                 $treeId = $tree[$Model->alias][$__multiTreeKey];
  407.                 $allTrees[$treeId] = $this->generateTreeList($Model, $conditions, $keyPath, $valuePath, $spacer, $recursive, $treeId);
  408.             }
  409.             if (empty($allTrees)) {
  410.                 return array();
  411.             }
  412.             return $allTrees;
  413.  
  414.         } elseif ($multiTree) {
  415.             $conditions[$Model->escapeField($__multiTreeKey)] = $treeScope;
  416.         }
  417.         $scope = $this->_addScopeField($Model, $scope);
  418.         if (!is_null($overrideRecursive)) {
  419.             $recursive = $overrideRecursive;
  420.         }
  421.  
  422.         if ($keyPath == null && $valuePath == null && $Model->hasField($Model->displayField)) {
  423.             $fields = array($Model->primaryKey, $Model->displayField, $left, $right);
  424.         } else {
  425.             $fields = null;
  426.         }
  427.  
  428.         if ($keyPath == null) {
  429.             $keyPath = '{n}.' . $Model->alias . '.' . $Model->primaryKey;
  430.         }
  431.  
  432.         if ($valuePath == null) {
  433.             $valuePath = array('{0}{1}', '{n}.tree_prefix', '{n}.' . $Model->alias . '.' . $Model->displayField);
  434.  
  435.         } elseif (is_string($valuePath)) {
  436.             $valuePath = array('{0}{1}', '{n}.tree_prefix', $valuePath);
  437.  
  438.         } else {
  439.             $valuePath[0] = '{' . (count($valuePath) - 1) . '}' . $valuePath[0];
  440.             $valuePath[] = '{n}.tree_prefix';
  441.         }
  442.         $order = $Model->alias . '.' . $left . ' asc';
  443.         $results = $Model->find('all', compact('conditions', 'fields', 'order', 'recursive'));
  444.         $stack = array();
  445.  
  446.         foreach ($results as $i => $result) {
  447.             while ($stack && ($stack[count($stack) - 1] < $result[$Model->alias][$right])) {
  448.                 array_pop($stack);
  449.             }
  450.             $results[$i]['tree_prefix'] = str_repeat($spacer,count($stack));
  451.             $stack[] = $result[$Model->alias][$right];
  452.         }
  453.         if (empty($results)) {
  454.             return array();
  455.         }
  456.         return Set::combine($results, $keyPath, $valuePath);
  457.     }
  458.  
  459. /**
  460.  * Get the parent node
  461.  *
  462.  * reads the parent id and returns this node
  463.  *
  464.  * @param Model $Model Model instance
  465.  * @param mixed $id The ID of the record to read
  466.  * @param string|array $fields
  467.  * @param integer $recursive The number of levels deep to fetch associated records
  468.  * @return array|boolean Array of data for the parent node
  469.  * @link http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html#TreeBehavior::getParentNode
  470.  */
  471.     public function getParentNode($Model, $id = null, $fields = null, $recursive = null) {
  472.         if (is_array($id)) {
  473.             extract (array_merge(array('id' => null), $id));
  474.         }
  475.         $overrideRecursive = $recursive;
  476.         if (empty ($id)) {
  477.             $id = $Model->id;
  478.         }
  479.         extract($this->settings[$Model->alias]);
  480.         $scope = $this->_addScopeField($Model, $scope);
  481.         if (!is_null($overrideRecursive)) {
  482.             $recursive = $overrideRecursive;
  483.         }
  484.         $parentId = $Model->find('first', array('conditions' => array($Model->primaryKey => $id), 'fields' => array($parent), 'recursive' => -1));
  485.  
  486.         if ($parentId) {
  487.             $parentId = $parentId[$Model->alias][$parent];
  488.             $parent = $Model->find('first', array('conditions' => array($Model->escapeField() => $parentId), 'fields' => $fields, 'recursive' => $recursive));
  489.  
  490.             return $parent;
  491.         }
  492.         return false;
  493.     }
  494.  
  495. /**
  496.  * Get the path to the given node
  497.  *
  498.  * @param Model $Model Model instance
  499.  * @param mixed $id The ID of the record to read
  500.  * @param mixed $fields Either a single string of a field name, or an array of field names
  501.  * @param integer $recursive The number of levels deep to fetch associated records
  502.  * @return array Array of nodes from top most parent to current node
  503.  * @link http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html#TreeBehavior::getPath
  504.  */
  505.     public function getPath($Model, $id = null, $fields = null, $recursive = null) {
  506.         if (is_array($id)) {
  507.             extract (array_merge(array('id' => null), $id));
  508.         }
  509.         $overrideRecursive = $recursive;
  510.         if (empty ($id)) {
  511.             $id = $Model->id;
  512.         }
  513.         extract($this->settings[$Model->alias]);
  514.         $scope = $this->_addScopeField($Model, $scope);
  515.         if (!is_null($overrideRecursive)) {
  516.             $recursive = $overrideRecursive;
  517.         }
  518.         $resultFields = array($left, $right);
  519.         if ($multiTree) {
  520.             $resultFields[] = $__multiTreeKey;
  521.         }
  522.         $result = $Model->find('first', array('conditions' => array($Model->escapeField() => $id), 'fields' => $resultFields, 'recursive' => $recursive));
  523.         if ($result) {
  524.             $result = array_values($result);
  525.         } else {
  526.             return null;
  527.         }
  528.         $item = $result[0];
  529.         if ($multiTree) {
  530.             $scope = $this->_multiTreeScope($Model, $item[$__multiTreeKey]);
  531.         }
  532.         $results = $Model->find('all', array(
  533.             'conditions' => array($scope, $Model->escapeField($left) . ' <=' => $item[$left], $Model->escapeField($right) . ' >=' => $item[$right]),
  534.             'fields' => $fields, 'order' => array($Model->escapeField($left) => 'asc'), 'recursive' => $recursive
  535.         ));
  536.         return $results;
  537.     }
  538.  
  539. /**
  540.  * Reorder the node without changing the parent.
  541.  *
  542.  * If the node is the last child, or is a top level node with no subsequent node this method will return false
  543.  *
  544.  * @param Model $Model Model instance
  545.  * @param mixed $id The ID of the record to move
  546.  * @param integer|boolean $number how many places to move the node or true to move to last position
  547.  * @return boolean true on success, false on failure
  548.  * @link http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html#TreeBehavior::moveDown
  549.  */
  550.     public function moveDown($Model, $id = null, $number = 1) {
  551.         if (is_array($id)) {
  552.             extract (array_merge(array('id' => null), $id));
  553.         }
  554.         if (!$number) {
  555.             return false;
  556.         }
  557.         if (empty ($id)) {
  558.             $id = $Model->id;
  559.         } else {
  560.             $Model->id = $id;
  561.         }
  562.         extract($this->settings[$Model->alias]);
  563.         $nodeFields = array($Model->primaryKey, $left, $right, $parent);
  564.         if ($multiTree) {
  565.             $nodeFields[] = $__multiTreeKey;
  566.             $scope = array();
  567.         }
  568.         $scope = $this->_addScopeField($Model, $scope);
  569.  
  570.         list($node) = array_values($Model->find('first', array(
  571.             'conditions' => array($scope, $Model->escapeField() => $id),
  572.             'fields' => $nodeFields, 'recursive' => $recursive
  573.         )));
  574.         if ($multiTree) {
  575.             $scope = $this->_multiTreeScope($Model, $node[$__multiTreeKey]);
  576.         }
  577.         if ($node[$parent]) {
  578.             list($parentNode) = array_values($Model->find('first', array(
  579.                 'conditions' => array($scope, $Model->escapeField() => $node[$parent]),
  580.                 'fields' => array($Model->primaryKey, $left, $right), 'recursive' => $recursive
  581.             )));
  582.             if (($node[$right] + 1) == $parentNode[$right]) {
  583.                 return false;
  584.             }
  585.         }
  586.         $nextNode = $Model->find('first', array(
  587.             'conditions' => array($scope, $Model->escapeField($left) => ($node[$right] + 1)),
  588.             'fields' => array($Model->primaryKey, $left, $right), 'recursive' => $recursive)
  589.         );
  590.         if ($nextNode) {
  591.             list($nextNode) = array_values($nextNode);
  592.         } else {
  593.             return false;
  594.         }
  595.         $edge = $this->_getMax($Model, $scope, $right, $recursive);
  596.         $this->_sync($Model, $edge - $node[$left] + 1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right]);
  597.         $this->_sync($Model, $nextNode[$left] - $node[$left], '-', 'BETWEEN ' . $nextNode[$left] . ' AND ' . $nextNode[$right]);
  598.         $this->_sync($Model, $edge - $node[$left] - ($nextNode[$right] - $nextNode[$left]), '-', '> ' . $edge);
  599.  
  600.         if (is_int($number)) {
  601.             $number--;
  602.         }
  603.         if ($number) {
  604.             $this->moveDown($Model, $id, $number);
  605.         }
  606.         return true;
  607.     }
  608.  
  609. /**
  610.  * Reorder the node without changing the parent.
  611.  *
  612.  * If the node is the first child, or is a top level node with no previous node this method will return false
  613.  *
  614.  * @param Model $Model Model instance
  615.  * @param mixed $id The ID of the record to move
  616.  * @param integer|boolean $number how many places to move the node, or true to move to first position
  617.  * @return boolean true on success, false on failure
  618.  * @link http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html#TreeBehavior::moveUp
  619.  */
  620.     public function moveUp($Model, $id = null, $number = 1) {
  621.         if (is_array($id)) {
  622.             extract (array_merge(array('id' => null), $id));
  623.         }
  624.         if (!$number) {
  625.             return false;
  626.         }
  627.         if (empty ($id)) {
  628.             $id = $Model->id;
  629.         } else {
  630.             $Model->id = $id;
  631.         }
  632.         extract($this->settings[$Model->alias]);
  633.         $nodeFields = array($Model->primaryKey, $left, $right, $parent);
  634.         if ($multiTree) {
  635.             $nodeFields[] = $__multiTreeKey;
  636.             $scope = array();
  637.         }
  638.  
  639.         $scope = $this->_addScopeField($Model, $scope);
  640.  
  641.         list($node) = array_values($Model->find('first', array(
  642.             'conditions' => array($scope, $Model->escapeField() => $id),
  643.             'fields' => $nodeFields, 'recursive' => $recursive
  644.         )));
  645.         if ($multiTree) {
  646.             $scope = $this->_multiTreeScope($Model, $node[$__multiTreeKey]);
  647.         }
  648.         if ($node[$parent]) {
  649.             list($parentNode) = array_values($Model->find('first', array(
  650.                 'conditions' => array($scope, $Model->escapeField() => $node[$parent]),
  651.                 'fields' => array($Model->primaryKey, $left, $right), 'recursive' => $recursive
  652.             )));
  653.             if (($node[$left] - 1) == $parentNode[$left]) {
  654.                 return false;
  655.             }
  656.         }
  657.         $previousNode = $Model->find('first', array(
  658.             'conditions' => array($scope, $Model->escapeField($right) => ($node[$left] - 1)),
  659.             'fields' => array($Model->primaryKey, $left, $right),
  660.             'recursive' => $recursive
  661.         ));
  662.  
  663.         if ($previousNode) {
  664.             list($previousNode) = array_values($previousNode);
  665.         } else {
  666.             return false;
  667.         }
  668.         $edge = $this->_getMax($Model, $scope, $right, $recursive);
  669.         $this->_sync($Model, $edge - $previousNode[$left] +1, '+', 'BETWEEN ' . $previousNode[$left] . ' AND ' . $previousNode[$right]);
  670.         $this->_sync($Model, $node[$left] - $previousNode[$left], '-', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right]);
  671.         $this->_sync($Model, $edge - $previousNode[$left] - ($node[$right] - $node[$left]), '-', '> ' . $edge);
  672.         if (is_int($number)) {
  673.             $number--;
  674.         }
  675.         if ($number) {
  676.             $this->moveUp($Model, $id, $number);
  677.         }
  678.         return true;
  679.     }
  680.  
  681. /**
  682.  * Recover a corrupted tree
  683.  *
  684.  * The mode parameter is used to specify the source of info that is valid/correct. The opposite source of data
  685.  * will be populated based upon that source of info. E.g. if the MPTT fields are corrupt or empty, with the $mode
  686.  * 'parent' the values of the parent_id field will be used to populate the left and right fields. The missingParentAction
  687.  * parameter only applies to "parent" mode and determines what to do if the parent field contains an id that is not present.
  688.  *
  689.  * @todo Could be written to be faster, *maybe*. Ideally using a subquery and putting all the logic burden on the DB.
  690.  * @param Model $Model Model instance
  691.  * @param string $mode parent or tree
  692.  * @param mixed $missingParentAction 'return' to do nothing and return, 'delete' to
  693.  * delete, or the id of the parent to set as the parent_id
  694.  * @param integer $treeScope id of the tree to be recovered if using multiTree (recovers all by default)
  695.  * @return boolean true on success, false on failure
  696.  * @link http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html#TreeBehavior::recover
  697.  */
  698.     public function recover($Model, $mode = 'parent', $missingParentAction = null, $treeScope = null) {
  699.         if (is_array($mode)) {
  700.             extract (array_merge(array('mode' => 'parent'), $mode));
  701.         }
  702.         extract($this->settings[$Model->alias]);
  703.         if ($multiTree && !$treeScope) {
  704.             $trees = $Model->find('all', array('fields' => 'DISTINCT ' . $Model->escapeField($__multiTreeKey), 'recursive' => -1));
  705.             foreach ($trees as $tree) {
  706.                 $this->recover($Model, $mode, $missingParentAction, $tree[$Model->alias][$__multiTreeKey]);
  707.             }
  708.             if ($this->errors){
  709.                 return false;
  710.             }
  711.             return true;
  712.         } elseif ($multiTree) {
  713.             $scope = $this->_multiTreeScope($Model, $treeScope);
  714.         }
  715.         $Model->recursive = $recursive;
  716.         if ($mode == 'parent') {
  717.             $Model->bindModel(array('belongsTo' => array('VerifyParent' => array(
  718.                 'className' => $Model->name,
  719.                 'foreignKey' => $parent,
  720.                 'fields' => array($Model->primaryKey, $left, $right, $parent),
  721.             ))));
  722.             $missingParents = $Model->find('list', array(
  723.                 'recursive' => 0,
  724.                 'conditions' => array($scope, array(
  725.                     'NOT' => array($Model->escapeField($parent) => null), $Model->VerifyParent->escapeField() => null
  726.                 ))
  727.             ));
  728.             $Model->unbindModel(array('belongsTo' => array('VerifyParent')));
  729.             if ($missingParents) {
  730.                 if ($missingParentAction == 'return') {
  731.                     foreach ($missingParents as $id => $display) {
  732.                         $this->errors[] = 'cannot find the parent for ' . $Model->alias . ' with id ' . $id . '(' . $display . ')';
  733.                     }
  734.                     return false;
  735.                 } elseif ($missingParentAction == 'delete') {
  736.                     $Model->deleteAll(array($Model->primaryKey => array_flip($missingParents)));
  737.                 } else {
  738.                     $Model->updateAll(array($parent => $missingParentAction), array($Model->escapeField($Model->primaryKey) => array_flip($missingParents)));
  739.                 }
  740.             }
  741.             $count = 1;
  742.             foreach ($Model->find('all', array('conditions' => $scope, 'fields' => array($Model->primaryKey), 'order' => $Model->escapeField($left))) as $array) {
  743.                 $lft = $count++;
  744.                 $rght = $count++;
  745.                 $Model->create(false);
  746.                 $Model->id = $array[$Model->alias][$Model->primaryKey];
  747.                 $Model->save(array($left => $lft, $right => $rght), array('callbacks' => false));
  748.             }
  749.             foreach ($Model->find('all', array('conditions' => $scope, 'fields' => array($Model->primaryKey, $parent), 'order' => $Model->escapeField($left))) as $array) {
  750.                 $Model->create(false);
  751.                 $Model->id = $array[$Model->alias][$Model->primaryKey];
  752.                 $this->_setParent($Model, $array[$Model->alias][$parent]);
  753.             }
  754.         } else {
  755.             $db = ConnectionManager::getDataSource($Model->useDbConfig);
  756.             foreach ($Model->find('all', array('conditions' => $scope, 'fields' => array($Model->primaryKey, $parent), 'order' => $Model->escapeField($left))) as $array) {
  757.                 $path = $this->getPath($Model, $array[$Model->alias][$Model->primaryKey]);
  758.                 if ($path == null || count($path) < 2) {
  759.                     $parentId = null;
  760.                 } else {
  761.                     $parentId = $path[count($path) - 2][$Model->alias][$Model->primaryKey];
  762.                 }
  763.                 $Model->updateAll(array($parent => $db->value($parentId, $parent)), array($Model->escapeField() => $array[$Model->alias][$Model->primaryKey]));
  764.             }
  765.         }
  766.         return true;
  767.     }
  768.  
  769. /**
  770.  * Reorder method.
  771.  *
  772.  * Reorders the nodes (and child nodes) of the tree according to the field and direction specified in the parameters.
  773.  * This method does not change the parent of any node.
  774.  *
  775.  * Requires a valid tree, by default it verifies the tree before beginning.
  776.  *
  777.  * Options:
  778.  *
  779.  * - 'id' id of record to use as top node for reordering
  780.  * - 'field' Which field to use in reordering defaults to displayField
  781.  * - 'order' Direction to order either DESC or ASC (defaults to ASC)
  782.  * - 'verify' Whether or not to verify the tree before reorder. defaults to true.
  783.  *
  784.  * @param Model $Model Model instance
  785.  * @param array $options array of options to use in reordering.
  786.  * @return boolean true on success, false on failure
  787.  * @link http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html#TreeBehavior::reorder
  788.  */
  789.     public function reorder($Model, $options = array()) {
  790.         $options = array_merge(array('id' => null, 'field' => $Model->displayField, 'order' => 'ASC', 'verify' => true), $options);
  791.         extract($options);
  792.         if ($verify && !$this->verify($Model)) {
  793.             return false;
  794.         }
  795.         $verify = false;
  796.         extract($this->settings[$Model->alias]);
  797.         $fields = array($Model->primaryKey, $field, $left, $right);
  798.         $sort = $field . ' ' . $order;
  799.         $nodes = $this->children($Model, $id, true, $fields, $sort, null, null, $recursive);
  800.  
  801.         $cacheQueries = $Model->cacheQueries;
  802.         $Model->cacheQueries = false;
  803.         if ($nodes) {
  804.             foreach ($nodes as $node) {
  805.                 $id = $node[$Model->alias][$Model->primaryKey];
  806.                 $this->moveDown($Model, $id, true);
  807.                 if ($node[$Model->alias][$left] != $node[$Model->alias][$right] - 1) {
  808.                     $this->reorder($Model, compact('id', 'field', 'order', 'verify'));
  809.                 }
  810.             }
  811.         }
  812.         $Model->cacheQueries = $cacheQueries;
  813.         return true;
  814.     }
  815.  
  816. /**
  817.  * Remove the current node from the tree, and reparent all children up one level.
  818.  *
  819.  * If the parameter delete is false, the node will become a new top level node. Otherwise the node will be deleted
  820.  * after the children are reparented.
  821.  *
  822.  * @param Model $Model Model instance
  823.  * @param mixed $id The ID of the record to remove
  824.  * @param boolean $delete whether to delete the node after reparenting children (if any)
  825.  * @return boolean true on success, false on failure
  826.  * @link http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html#TreeBehavior::removeFromTree
  827.  */
  828.     public function removeFromTree($Model, $id = null, $delete = false) {
  829.         if (is_array($id)) {
  830.             extract (array_merge(array('id' => null), $id));
  831.         }
  832.         extract($this->settings[$Model->alias]);
  833.        
  834.         $nodeFields = array($Model->primaryKey, $left, $right, $parent);
  835.         if ($multiTree) {
  836.             $nodeFields[] = $__multiTreeKey;
  837.             $scope = array();
  838.         }
  839.         $scope = $this->_addScopeField($Model, $scope);
  840.  
  841.         list($node) = array_values($Model->find('first', array(
  842.             'conditions' => array($scope, $Model->escapeField() => $id),
  843.             'fields' => $nodeFields,
  844.             'recursive' => $recursive
  845.         )));
  846.        
  847.         if ($multiTree) {
  848.             $scope = $this->_multiTreeScope($Model, $node[$__multiTreeKey]);
  849.         }
  850.         if ($node[$right] == $node[$left] + 1) {
  851.             if ($delete) {
  852.                 return $Model->delete($id);
  853.             } else {
  854.                 $Model->id = $id;
  855.                 return $Model->saveField($parent, null);
  856.             }
  857.         } elseif ($node[$parent]) {
  858.             list($parentNode) = array_values($Model->find('first', array(
  859.                 'conditions' => array($scope, $Model->escapeField() => $node[$parent]),
  860.                 'fields' => array($Model->primaryKey, $left, $right),
  861.                 'recursive' => $recursive
  862.             )));
  863.         } else {
  864.             $parentNode[$right] = $node[$right] + 1;
  865.         }
  866.  
  867.         $db = ConnectionManager::getDataSource($Model->useDbConfig);
  868.         $Model->updateAll(
  869.             array($parent => $db->value($node[$parent], $parent)),
  870.             array($Model->escapeField($parent) => $node[$Model->primaryKey])
  871.         );
  872.         $this->_sync($Model, 1, '-', 'BETWEEN ' . ($node[$left] + 1) . ' AND ' . ($node[$right] - 1));
  873.         $this->_sync($Model, 2, '-', '> ' . ($node[$right]));
  874.         $Model->id = $id;
  875.  
  876.         if ($delete) {
  877.             $Model->updateAll(
  878.                 array(
  879.                     $Model->escapeField($left) => 0,
  880.                     $Model->escapeField($right) => 0,
  881.                     $Model->escapeField($parent) => null
  882.                 ),
  883.                 array($Model->escapeField() => $id)
  884.             );
  885.             return $Model->delete($id);
  886.         } else {
  887.             $edge = $this->_getMax($Model, $scope, $right, $recursive);
  888.             if ($node[$right] == $edge) {
  889.                 $edge = $edge - 2;
  890.             }
  891.             $Model->id = $id;
  892.             return $Model->save(
  893.                 array($left => $edge + 1, $right => $edge + 2, $parent => null),
  894.                 array('callbacks' => false)
  895.             );
  896.         }
  897.     }
  898.  
  899. /**
  900.  * Check if the current tree is valid.
  901.  *
  902.  * Returns true if the tree is valid otherwise an array of (type, incorrect left/right index, message)
  903.  *
  904.  * @param $Model Model instance
  905.  * @param $treeScope id of tree to verify (verifies all by deafault)
  906.  * @return mixed true if the tree is valid or empty, otherwise an array of (error type [index, node],
  907.  *  [incorrect left/right index,node id], message)
  908.  * @link http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html#TreeBehavior::verify
  909.  */
  910.     public function verify($Model, $treeScope = null) {
  911.         extract($this->settings[$Model->alias]);
  912.         if ($multiTree && !$treeScope) {
  913.             $trees = $Model->find('all', array('fields' => 'DISTINCT ' . $Model->escapeField($__multiTreeKey), 'recursive' => -1));
  914.             $errors = array();
  915.             foreach ($trees as $tree) {
  916.                 $verified = $this->verify($Model, $tree[$Model->alias][$__multiTreeKey]);
  917.                 if ($verified !== true) {
  918.                     $errors[$tree[$Model->alias][$__multiTreeKey]] = $verified;
  919.                 }
  920.             }
  921.             if ($errors){
  922.                 return $errors;
  923.             }
  924.             return true;
  925.         } elseif ($multiTree) {
  926.             $scope = $this->_multiTreeScope($Model, $treeScope);
  927.         }
  928.         if (!$Model->find('count', array('conditions' => $scope))) {
  929.             return true;
  930.         }
  931.         $min = $this->_getMin($Model, $scope, $left, $recursive);
  932.         $edge = $this->_getMax($Model, $scope, $right, $recursive);
  933.         $errors =  array();
  934.  
  935.         for ($i = $min; $i <= $edge; $i++) {
  936.             $count = $Model->find('count', array('conditions' => array(
  937.                 $scope, 'OR' => array($Model->escapeField($left) => $i, $Model->escapeField($right) => $i)
  938.             )));
  939.             if ($count != 1) {
  940.                 if ($count == 0) {
  941.                     $errors[] = array('index', $i, 'missing');
  942.                 } else {
  943.                     $errors[] = array('index', $i, 'duplicate');
  944.                 }
  945.             }
  946.         }
  947.         $node = $Model->find('first', array('conditions' => array($scope, $Model->escapeField($right) . '< ' . $Model->escapeField($left)), 'recursive' => 0));
  948.         if ($node) {
  949.             $errors[] = array('node', $node[$Model->alias][$Model->primaryKey], 'left greater than right.');
  950.         }
  951.  
  952.         $Model->bindModel(array('belongsTo' => array('VerifyParent' => array(
  953.             'className' => $Model->name,
  954.             'foreignKey' => $parent,
  955.             'fields' => array($Model->primaryKey, $left, $right, $parent)
  956.         ))));
  957.  
  958.         foreach ($Model->find('all', array('conditions' => $scope, 'recursive' => 0)) as $instance) {
  959.             if (is_null($instance[$Model->alias][$left]) || is_null($instance[$Model->alias][$right])) {
  960.                 $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey],
  961.                     'has invalid left or right values');
  962.             } elseif ($instance[$Model->alias][$left] == $instance[$Model->alias][$right]) {
  963.                 $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey],
  964.                     'left and right values identical');
  965.             } elseif ($instance[$Model->alias][$parent]) {
  966.                 if (!$instance['VerifyParent'][$Model->primaryKey]) {
  967.                     $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey],
  968.                         'The parent node ' . $instance[$Model->alias][$parent] . ' doesn\'t exist');
  969.                 } elseif ($instance[$Model->alias][$left] < $instance['VerifyParent'][$left]) {
  970.                     $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey],
  971.                         'left less than parent (node ' . $instance['VerifyParent'][$Model->primaryKey] . ').');
  972.                 } elseif ($instance[$Model->alias][$right] > $instance['VerifyParent'][$right]) {
  973.                     $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey],
  974.                         'right greater than parent (node ' . $instance['VerifyParent'][$Model->primaryKey] . ').');
  975.                 }
  976.             } elseif ($Model->find('count', array('conditions' => array($scope, $Model->escapeField($left) . ' <' => $instance[$Model->alias][$left], $Model->escapeField($right) . ' >' => $instance[$Model->alias][$right]), 'recursive' => 0))) {
  977.                 $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey], 'The parent field is blank, but has a parent');
  978.             }
  979.         }
  980.         if ($errors) {
  981.             return $errors;
  982.         }
  983.         return true;
  984.     }
  985.  
  986. /**
  987.  * Sets the parent of the given node
  988.  *
  989.  * The force parameter is used to override the "don't change the parent to the current parent" logic in the event
  990.  * of recovering a corrupted table, or creating new nodes. Otherwise it should always be false. In reality this
  991.  * method could be private, since calling save with parent_id set also calls setParent
  992.  *
  993.  * @param Model $Model Model instance
  994.  * @param mixed $parentId
  995.  * @param boolean $created
  996.  * @return boolean true on success, false on failure
  997.  */
  998.     protected function _setParent($Model, $parentId = null, $created = false) {
  999.         extract($this->settings[$Model->alias]);
  1000.         $nodeFields = array($Model->primaryKey, $parent, $left, $right);
  1001.         if ($multiTree) {
  1002.             $nodeFields[] = $__multiTreeKey;
  1003.             $scope = array();
  1004.         }
  1005.  
  1006.         $scope = $this->_addScopeField($Model, $scope);
  1007.         list($node) = array_values($Model->find('first', array(
  1008.             'conditions' => array($scope, $Model->escapeField() => $Model->id),
  1009.             'fields' => $nodeFields,
  1010.             'recursive' => $recursive
  1011.         )));
  1012.         if ($multiTree) {
  1013.             $scope = $this->_multiTreeScope($Model, $node[$__multiTreeKey]);
  1014.         }
  1015.         $edge = $this->_getMax($Model, $scope, $right, $recursive, $created);
  1016.  
  1017.         if (empty ($parentId)) {
  1018.             $this->_sync($Model, $edge - $node[$left] + 1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right], $created);
  1019.             $this->_sync($Model, $node[$right] - $node[$left] + 1, '-', '> ' . $node[$left], $created);
  1020.         } else {
  1021.             $values = $Model->find('first', array(
  1022.                 'conditions' => array($scope, $Model->escapeField() => $parentId),
  1023.                 'fields' => array($Model->primaryKey, $left, $right),
  1024.                 'recursive' => $recursive
  1025.             ));
  1026.  
  1027.             if ($values === false) {
  1028.                 return false;
  1029.             }
  1030.             $parentNode = array_values($values);
  1031.  
  1032.             if (empty($parentNode) || empty($parentNode[0])) {
  1033.                 return false;
  1034.             }
  1035.             $parentNode = $parentNode[0];
  1036.  
  1037.             if (($Model->id == $parentId)) {
  1038.                 return false;
  1039.             } elseif (($node[$left] < $parentNode[$left]) && ($parentNode[$right] < $node[$right])) {
  1040.                 return false;
  1041.             }
  1042.             if (empty($node[$left]) && empty($node[$right])) {
  1043.                 $this->_sync($Model, 2, '+', '>= ' . $parentNode[$right], $created);
  1044.                 $result = $Model->save(
  1045.                     array($left => $parentNode[$right], $right => $parentNode[$right] + 1, $parent => $parentId),
  1046.                     array('validate' => false, 'callbacks' => false)
  1047.                 );
  1048.                 $Model->data = $result;
  1049.             } else {
  1050.                 $this->_sync($Model, $edge - $node[$left] +1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right], $created);
  1051.                 $diff = $node[$right] - $node[$left] + 1;
  1052.  
  1053.                 if ($node[$left] > $parentNode[$left]) {
  1054.                     if ($node[$right] < $parentNode[$right]) {
  1055.                         $this->_sync($Model, $diff, '-', 'BETWEEN ' . $node[$right] . ' AND ' . ($parentNode[$right] - 1), $created);
  1056.                         $this->_sync($Model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge, $created);
  1057.                     } else {
  1058.                         $this->_sync($Model, $diff, '+', 'BETWEEN ' . $parentNode[$right] . ' AND ' . $node[$right], $created);
  1059.                         $this->_sync($Model, $edge - $parentNode[$right] + 1, '-', '> ' . $edge, $created);
  1060.                     }
  1061.                 } else {
  1062.                     $this->_sync($Model, $diff, '-', 'BETWEEN ' . $node[$right] . ' AND ' . ($parentNode[$right] - 1), $created);
  1063.                     $this->_sync($Model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge, $created);
  1064.                 }
  1065.             }
  1066.         }
  1067.         return true;
  1068.     }
  1069.  
  1070. /**
  1071.  * get the maximum index value in the table.
  1072.  *
  1073.  * @param Model $Model
  1074.  * @param string $scope
  1075.  * @param string $right
  1076.  * @param integer $recursive
  1077.  * @param boolean $created
  1078.  * @return integer
  1079.  */
  1080.     protected function _getMax($Model, $scope, $right, $recursive = -1, $created = false) {
  1081.         $db = ConnectionManager::getDataSource($Model->useDbConfig);
  1082.         if ($created) {
  1083.             if (is_string($scope)) {
  1084.                 $scope .= " AND {$Model->alias}.{$Model->primaryKey} <> ";
  1085.                 $scope .= $db->value($Model->id, $Model->getColumnType($Model->primaryKey));
  1086.                 $scope = array($scope);
  1087.             } else {
  1088.                 $scope['NOT'][$Model->alias . '.' . $Model->primaryKey] = $Model->id;
  1089.             }
  1090.         }
  1091.  
  1092.         $scope = $this->_addScopeField($Model, $scope);
  1093.  
  1094.         $name = $Model->alias . '.' . $right;
  1095.         list($edge) = array_values($Model->find('first', array(
  1096.             'conditions' => $scope,
  1097.             'fields' => $db->calculate($Model, 'max', array($name, $right)),
  1098.             'recursive' => $recursive
  1099.         )));
  1100.         return (empty($edge[$right])) ? 0 : $edge[$right];
  1101.     }
  1102.  
  1103. /**
  1104.  * get the minimum index value in the table.
  1105.  *
  1106.  * @param Model $Model
  1107.  * @param string $scope
  1108.  * @param string $left
  1109.  * @param integer $recursive
  1110.  * @return integer
  1111.  */
  1112.     protected function _getMin($Model, $scope, $left, $recursive = -1) {
  1113.         $db = ConnectionManager::getDataSource($Model->useDbConfig);
  1114.         $name = $Model->alias . '.' . $left;
  1115.         list($edge) = array_values($Model->find('first', array(
  1116.             'conditions' => $scope,
  1117.             'fields' => $db->calculate($Model, 'min', array($name, $left)),
  1118.             'recursive' => $recursive
  1119.         )));
  1120.         return (empty($edge[$left])) ? 0 : $edge[$left];
  1121.     }
  1122.  
  1123.     protected function _addScopeField($Model, $scope) {
  1124.         if (empty($this->settings[$Model->alias]['scopeField'])) {
  1125.             return $scope;
  1126.         }
  1127.         if (!is_array($scope)) {
  1128.             $scope = array($scope);
  1129.         }
  1130.         if ($Model->data) {
  1131.             if (!empty($Model->data[$Model->alias][$this->settings[$Model->alias]['scopeField']])) {
  1132.                 $value = $Model->data[$Model->alias][$this->settings[$Model->alias]['scopeField']];
  1133.             } else {
  1134.                 $value = $Model->field($this->settings[$Model->alias]['scopeField']);
  1135.             }
  1136.         } elseif ($Model->id) {
  1137.             $value = $Model->field($this->settings[$Model->alias]['scopeField']);
  1138.         } else {
  1139.             $value = null;
  1140.         }
  1141.         $scope[][$Model->alias . '.' . $this->settings[$Model->alias]['scopeField']] = $value;
  1142.  
  1143.         return $scope;
  1144.     }
  1145.  
  1146. /**
  1147.  * Table sync method.
  1148.  *
  1149.  * Handles table sync operations, Taking account of the behavior scope.
  1150.  *
  1151.  * @param Model $Model
  1152.  * @param integer $shift
  1153.  * @param string $dir
  1154.  * @param array $conditions
  1155.  * @param integer $treeScope
  1156.  * @param boolean $created
  1157.  * @param string $field
  1158.  * @return void
  1159.  */
  1160.     protected function _sync($Model, $shift, $dir = '+', $conditions = array(), $treeScope = null, $created = false, $field = 'both') {
  1161.         $ModelRecursive = $Model->recursive;
  1162.         extract($this->settings[$Model->alias]);
  1163.         $scope = $this->_addScopeField($Model, $scope);
  1164.         $Model->recursive = $recursive;
  1165.  
  1166.         if ($field == 'both') {
  1167.             $this->_sync($Model, $shift, $dir, $conditions, $treeScope, $created, $left);
  1168.             $field = $right;
  1169.         }
  1170.         if (is_string($conditions)) {
  1171.             $conditions = array("{$Model->alias}.{$field} {$conditions}");
  1172.         }
  1173.         if (($scope != '1 = 1' && $scope !== true) && $scope) {
  1174.             $conditions[] = $scope;
  1175.         }
  1176.         if ($created) {
  1177.             $conditions['NOT'][$Model->alias . '.' . $Model->primaryKey] = $Model->id;
  1178.         }
  1179.         $Model->updateAll(array($Model->alias . '.' . $field => $Model->escapeField($field) . ' ' . $dir . ' ' . $shift), $conditions);
  1180.         $Model->recursive = $ModelRecursive;
  1181.     }
  1182.  
  1183. /**
  1184.  * Get scope condition for multiTree and store in current settings
  1185.  *
  1186.  * @param Model $Model
  1187.  * @param integer $treeScope
  1188.  * @return array
  1189.  */
  1190.     protected function _multiTreeScope($Model, $treeScope) {
  1191.         if (!$this->settings[$Model->alias]['__multiTreeKey']) {
  1192.             return $this->settings[$Model->alias]['scope'];
  1193.         }
  1194.         $scope = array($Model->escapeField($this->settings[$Model->alias]['__multiTreeKey']) => $treeScope);
  1195.         $this->settings[$Model->alias]['scope'] = $scope;
  1196.         return $scope;
  1197.     }
  1198. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement