diff --git a/e107_handlers/admin_ui.php b/e107_handlers/admin_ui.php index 25eefb1b8..bc1e2965f 100644 --- a/e107_handlers/admin_ui.php +++ b/e107_handlers/admin_ui.php @@ -4209,8 +4209,8 @@ class e_admin_controller_ui extends e_admin_controller elseif($this->sortField && $this->sortParent && !deftrue('e_DEBUG_TREESORT')) // automated 'tree' sorting. { // $qry = "SELECT SQL_CALC_FOUND_ROWS a. *, CASE WHEN a.".$this->sortParent." = 0 THEN a.".$this->sortField." ELSE b.".$this->sortField." + (( a.".$this->sortField.")/1000) END AS treesort FROM `#".$this->table."` AS a LEFT JOIN `#".$this->table."` AS b ON a.".$this->sortParent." = b.".$this->pid; - $qry = $this->getParentChildQry(); - $this->listOrder = '_treesort '; // .$this->sortField; + $qry = $this->getParentChildQry(true); + //$this->listOrder = '_treesort '; // .$this->sortField; // $this->orderStep = ($this->orderStep === 1) ? 100 : $this->orderStep; } else @@ -4337,84 +4337,18 @@ class e_admin_controller_ui extends e_admin_controller /** * Return a Parent/Child SQL Query based on sortParent and sortField variables + * + * Note: Since 2018-01-28, the queries were replaced with pure PHP sorting. See: + * https://github.com/e107inc/e107/issues/3015 + * * @param bool|false $orderby - include 'ORDER BY' in the qry. * @return string */ public function getParentChildQry($orderby=false) { - - $parent= $this->getSortParent(); - $table = $this->getTableName(); - $pid = $this->getPrimaryName(); - $order = $this->getSortField(); - - - - $sql = "DROP FUNCTION IF EXISTS `getDepth` ;"; - - e107::getDb()->gen($sql); - - - $sql = " - CREATE FUNCTION `getDepth` (project_id INT) RETURNS int - BEGIN - DECLARE depth INT; - SET depth=1; - - WHILE project_id > 0 DO - - SELECT IFNULL(".$parent.",-1) - INTO project_id - FROM ( SELECT ".$parent." FROM `#".$table."` WHERE ".$pid." = project_id) AS t; - - IF project_id > 0 THEN - SET depth = depth + 1; - END IF; - - END WHILE; - - RETURN depth; - - END - ; - "; - - - e107::getDb()->gen($sql); - - $sql = "DROP FUNCTION IF EXISTS `getTreeSort`;"; - - e107::getDb()->gen($sql); - $sql = " - CREATE FUNCTION getTreeSort(incid INT) - RETURNS CHAR(255) - BEGIN - SET @parentstr = CONVERT(incid, CHAR); - SET @parent = -1; - label1: WHILE @parent != 0 DO - SET @parent = (SELECT ".$parent." FROM `#".$table."` WHERE ".$pid." =incid); - SET @order = (SELECT ".$order." FROM `#".$table."` WHERE ".$pid." =incid); - SET @parentstr = CONCAT(if(@parent = 0,'',@parent), LPAD(@order,4,0), @parentstr); - SET incid = @parent; - END WHILE label1; - - RETURN @parentstr; - END - ; - - "; - - - e107::getDb()->gen($sql); - - $qry = "SELECT SQL_CALC_FOUND_ROWS *, getTreeSort(".$pid.") as _treesort, getDepth(".$pid.") as _depth FROM `#".$table."` "; - - if($orderby === true) - { - $qry .= " ORDER BY _treesort"; - } - - return $qry; + return "SELECT SQL_CALC_FOUND_ROWS * FROM `#".$this->getTableName()."` "; + // Use the following return statement to break e107 native sorting but speed up tree creation by presorting for e_tree_model::arrayToTree() + #return "SELECT SQL_CALC_FOUND_ROWS * FROM `#".$this->getTableName()."` ORDER BY ".$this->getSortParent().", ".$this->getSortField(); } @@ -6256,7 +6190,14 @@ class e_admin_ui extends e_admin_controller_ui ->setFieldIdName($this->pid) ->setUrl($this->url) ->setMessageStackName('admin_ui_tree_'.$this->table) - ->setParams(array('model_class' => 'e_admin_model', 'model_message_stack' => 'admin_ui_model_'.$this->table ,'db_query' => $this->listQry)); + ->setParams(array('model_class' => 'e_admin_model', + 'model_message_stack' => 'admin_ui_model_'.$this->table , + 'db_query' => $this->listQry, + // Information necessary for PHP-based tree sort + 'sort_parent' => $this->getSortParent(), + 'sort_field' => $this->getSortField(), + 'primary_field' => $this->getPrimaryName(), + )); return $this; } diff --git a/e107_handlers/model_class.php b/e107_handlers/model_class.php old mode 100644 new mode 100755 index 1ef1cac0e..839b1459e --- a/e107_handlers/model_class.php +++ b/e107_handlers/model_class.php @@ -3331,7 +3331,12 @@ class e_tree_model extends e_front_model if($sql->gen($this->getParam('db_query'), $this->getParam('db_debug') ? true : false)) { $this->_total = is_integer($sql->total_results) ? $sql->total_results : false; //requires SQL_CALC_FOUND_ROWS in query - see db handler - while($tmp = $sql->db_Fetch()) + $rows = self::flatTreeFromArray($sql->rows(), + $this->getParam('primary_field'), + $this->getParam('sort_parent'), + $this->getParam('sort_field') + ); + foreach($rows as $tmp) { $tmp = new $class_name($tmp); if($this->getParam('model_message_stack')) @@ -3372,6 +3377,84 @@ class e_tree_model extends e_front_model return $this; } + /** + * Depth-first sort a relational array with a parent field and a sort order field + * @param array $rows Relational array with a parent field and a sort order field + * @param string $primary_field The field name of the primary key (matches children to parents) + * @param string $sort_parent The field name whose value is the parent ID + * @param string $sort_field The field name whose value is the sort order in the current tree node + * @return array Input array sorted depth-first as if it were a tree + */ + private static function flatTreeFromArray($rows, $primary_field, $sort_parent, $sort_field) + { + $rows_tree = self::arrayToTree($rows, $primary_field, $sort_parent); + return self::flattenTree($rows_tree, $sort_field); + } + + /** + * Converts a relational array with a parent field and a sort order field to a tree + * @param array $rows Relational array with a parent field and a sort order field + * @param string $primary_field The field name of the primary key (matches children to parents) + * @param string $sort_parent The field name whose value is the parent ID + * @return array Multidimensional array with child nodes under the "_children" key + */ + private static function arrayToTree($rows, $primary_field, $sort_parent) + { + $nodes = array(); + $root = array($primary_field => 0); + $nodes[] = &$root; + + while(!empty($nodes)) + { + $node = &$nodes[0]; + array_shift($nodes); + foreach($rows as $key => $row) + { + if(intval($row[$sort_parent]) === intval($node[$primary_field])) + { + $node['_children'][] = &$row; + unset($rows[$key]); + $nodes[] = &$row; + unset($row); + } + } + } + + return array(0 => $root); + } + + /** + * Flattens a tree into a depth-first array, sorting each node by a field's values + * @param array $tree Tree with child nodes under the "_children" key + * @param string $sort_field The field name whose value is the sort order in the current tree node + * @param int $depth The depth that this level of recursion is entering + * @return array One-dimensional array in depth-first order with depth indicated by the "_depth" key + */ + private static function flattenTree($tree, $sort_field = null, $depth = 0) + { + $flat = array(); + + foreach($tree as $item) + { + $children = $item['_children']; + unset($item['_children']); + $item['_depth'] = $depth; + if($depth > 0) + $flat[] = $item; + if(is_array($children)) + { + uasort($children, function($node1, $node2) + { + if(intval($node1[$sort_field]) === intval($node2[$sort_field])) return 0; + return intval($node1[$sort_field]) < intval($node2[$sort_field]) ? -1 : 1; + }); + $flat = array_merge($flat, self::flattenTree($children, $sort_field, $depth+1)); + } + } + + return $flat; + } + /** * Get single model instance from the collection * @param integer $node_id