mirror of
https://github.com/e107inc/e107.git
synced 2025-08-06 14:46:56 +02:00
Merge pull request #3031 from Deltik/fix-3029
Implemented custom sorting for e_tree_model
This commit is contained in:
9
e107_handlers/admin_ui.php
Normal file → Executable file
9
e107_handlers/admin_ui.php
Normal file → Executable file
@@ -6191,7 +6191,7 @@ class e_admin_ui extends e_admin_controller_ui
|
|||||||
->setUrl($this->url)
|
->setUrl($this->url)
|
||||||
->setMessageStackName('admin_ui_tree_'.$this->table)
|
->setMessageStackName('admin_ui_tree_'.$this->table)
|
||||||
->setParams(array('model_class' => 'e_admin_model',
|
->setParams(array('model_class' => 'e_admin_model',
|
||||||
'model_message_stack' => 'admin_ui_model_'.$this->table ,
|
'model_message_stack' => 'admin_ui_model_'.$this->table,
|
||||||
'db_query' => $this->listQry,
|
'db_query' => $this->listQry,
|
||||||
// Information necessary for PHP-based tree sort
|
// Information necessary for PHP-based tree sort
|
||||||
'sort_parent' => $this->getSortParent(),
|
'sort_parent' => $this->getSortParent(),
|
||||||
@@ -6484,9 +6484,6 @@ class e_admin_form_ui extends e_form
|
|||||||
// if going through confirm screen - no JS confirm
|
// if going through confirm screen - no JS confirm
|
||||||
$controller->setFieldAttr('options', 'noConfirm', $controller->deleteConfirmScreen);
|
$controller->setFieldAttr('options', 'noConfirm', $controller->deleteConfirmScreen);
|
||||||
|
|
||||||
$this->listTotal = $tree[$id]->getTotal();
|
|
||||||
|
|
||||||
|
|
||||||
$fields = $controller->getFields();
|
$fields = $controller->getFields();
|
||||||
|
|
||||||
// checks dispatcher acess/perms for create/edit/delete access in list mode.
|
// checks dispatcher acess/perms for create/edit/delete access in list mode.
|
||||||
@@ -6916,7 +6913,7 @@ class e_admin_form_ui extends e_form
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// FIXME - use e_form::batchoptions(), nice way of buildig batch dropdown - news administration show_batch_options()
|
// FIXME - use e_form::batchoptions(), nice way of building batch dropdown - news administration show_batch_options()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $options array of flags for copy, delete, url, featurebox, batch
|
* @param array $options array of flags for copy, delete, url, featurebox, batch
|
||||||
@@ -7017,7 +7014,7 @@ class e_admin_form_ui extends e_form
|
|||||||
|
|
||||||
$text .= "
|
$text .= "
|
||||||
|
|
||||||
<div id='admin-ui-list-total-records' class='span6 col-md-6 right'><span>".e107::getParser()->lanVars(LAN_UI_TOTAL_RECORDS,number_format($this->listTotal))."</span></div>
|
<div id='admin-ui-list-total-records' class='span6 col-md-6 right'><span>".e107::getParser()->lanVars(LAN_UI_TOTAL_RECORDS,number_format($this->getController()->getTreeModel()->getTotal()))."</span></div>
|
||||||
</div>
|
</div>
|
||||||
";
|
";
|
||||||
|
|
||||||
|
103
e107_handlers/model_class.php
Normal file → Executable file
103
e107_handlers/model_class.php
Normal file → Executable file
@@ -3339,14 +3339,17 @@ class e_tree_model extends e_front_model
|
|||||||
|
|
||||||
// Workaround: Parse and modify db_query param for simulated pagination
|
// Workaround: Parse and modify db_query param for simulated pagination
|
||||||
$this->prepareSimulatedPagination();
|
$this->prepareSimulatedPagination();
|
||||||
|
// Workaround: Parse and modify db_query param for simulated custom ordering
|
||||||
|
$this->prepareSimulatedCustomOrdering();
|
||||||
|
|
||||||
if($sql->gen($this->getParam('db_query'), $this->getParam('db_debug') ? true : false))
|
if($sql->gen($this->getParam('db_query'), $this->getParam('db_debug') ? true : false))
|
||||||
{
|
{
|
||||||
$rows = self::flatTreeFromArray($sql->rows(),
|
$rows_tree = self::arrayToTree($sql->rows,
|
||||||
$this->getParam('primary_field'),
|
$this->getParam('primary_field'),
|
||||||
$this->getParam('sort_parent'),
|
$this->getParam('sort_parent'));
|
||||||
$this->getParam('sort_field')
|
$rows = self::flattenTree($rows_tree,
|
||||||
);
|
$this->getParam('sort_field'),
|
||||||
|
$this->getParam('sort_order'));
|
||||||
|
|
||||||
// Simulated pagination
|
// Simulated pagination
|
||||||
$rows = array_splice($rows,
|
$rows = array_splice($rows,
|
||||||
@@ -3384,20 +3387,6 @@ class e_tree_model extends e_front_model
|
|||||||
return $this;
|
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
|
* 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 array $rows Relational array with a parent field and a sort order field
|
||||||
@@ -3448,11 +3437,13 @@ class e_tree_model extends e_front_model
|
|||||||
/**
|
/**
|
||||||
* Flattens a tree into a depth-first array, sorting each node by a field's values
|
* 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 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 mixed $sort_field The field name (string) or field names (array) whose value
|
||||||
|
* is or values are the sort order in the current tree node
|
||||||
|
* @param int $sort_order Desired sorting direction: 1 if ascending, -1 if descending
|
||||||
* @param int $depth The depth that this level of recursion is entering
|
* @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
|
* @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)
|
private static function flattenTree($tree, $sort_field = null, $sort_order = 1, $depth = 0)
|
||||||
{
|
{
|
||||||
$flat = array();
|
$flat = array();
|
||||||
|
|
||||||
@@ -3465,18 +3456,40 @@ class e_tree_model extends e_front_model
|
|||||||
$flat[] = $item;
|
$flat[] = $item;
|
||||||
if(is_array($children))
|
if(is_array($children))
|
||||||
{
|
{
|
||||||
uasort($children, function($node1, $node2)
|
uasort($children, function($node1, $node2) use ($sort_field, $sort_order)
|
||||||
{
|
{
|
||||||
if(intval($node1[$sort_field]) === intval($node2[$sort_field])) return 0;
|
return self::multiFieldCmp($node1, $node2, $sort_field, $sort_order);
|
||||||
return intval($node1[$sort_field]) < intval($node2[$sort_field]) ? -1 : 1;
|
|
||||||
});
|
});
|
||||||
$flat = array_merge($flat, self::flattenTree($children, $sort_field, $depth+1));
|
$flat = array_merge($flat, self::flattenTree($children, $sort_field, $sort_order, $depth+1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $flat;
|
return $flat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Naturally compares two associative arrays given multiple sort keys and a reverse order flag
|
||||||
|
* @param array $row1 Associative array to compare to $row2
|
||||||
|
* @param array $row2 Associative array to compare to $row1
|
||||||
|
* @param mixed $sort_field Key (string) or keys (array) to compare
|
||||||
|
* the values of in both $row1 and $row2
|
||||||
|
* @param int $sort_order -1 to reverse the sorting order or 1 to keep the order as ascending
|
||||||
|
* @return int -1 if $row1 is less than $row2
|
||||||
|
* 0 if $row1 is equal to $row2
|
||||||
|
* 1 if $row1 is greater than $row2
|
||||||
|
*/
|
||||||
|
private static function multiFieldCmp($row1, $row2, $sort_field, $sort_order = 1)
|
||||||
|
{
|
||||||
|
if (is_array($sort_field))
|
||||||
|
$field = array_shift($sort_field);
|
||||||
|
$cmp = strnatcmp((string) $row1[$field], (string) $row2[$field]);
|
||||||
|
if ($sort_order === -1 || $sort_order === 1) $cmp *= $sort_order;
|
||||||
|
if ($cmp === 0 && count($sort_field) >= 1)
|
||||||
|
return self::multiFieldCmp($row1, $row2, $sort_field, $sort_order);
|
||||||
|
return $cmp;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resiliently counts the results from the last SQL query in the given resource
|
* Resiliently counts the results from the last SQL query in the given resource
|
||||||
*
|
*
|
||||||
@@ -3516,13 +3529,13 @@ class e_tree_model extends e_front_model
|
|||||||
private function prepareSimulatedPagination()
|
private function prepareSimulatedPagination()
|
||||||
{
|
{
|
||||||
$db_query = $this->getParam('db_query');
|
$db_query = $this->getParam('db_query');
|
||||||
$db_query = preg_replace_callback("/LIMIT ([\d]+)[ ]*(,|OFFSET){0,1}[ ]*([\d]*)/", function($matches)
|
$db_query = preg_replace_callback("/LIMIT ([\d]+)[ ]*(?:,|OFFSET){0,1}[ ]*([\d]*)/i", function($matches)
|
||||||
{
|
{
|
||||||
// Offset and count
|
// Offset and count
|
||||||
if (isset($matches[3]))
|
if (isset($matches[2]))
|
||||||
{
|
{
|
||||||
$this->setParam('db_limit_offset', $matches[1]);
|
$this->setParam('db_limit_offset', $matches[1]);
|
||||||
$this->setParam('db_limit_count', $matches[3]);
|
$this->setParam('db_limit_count', $matches[2]);
|
||||||
}
|
}
|
||||||
// Count only
|
// Count only
|
||||||
else
|
else
|
||||||
@@ -3535,6 +3548,42 @@ class e_tree_model extends e_front_model
|
|||||||
$this->setParam('db_query', $db_query);
|
$this->setParam('db_query', $db_query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Workaround: Parse and modify query to prepare for simulation of custom ordering
|
||||||
|
*
|
||||||
|
* XXX: Not compliant with all forms of ORDER BY clauses
|
||||||
|
* XXX: Does not support quoted identifiers (`identifier`)
|
||||||
|
* XXX: Does not support mixed sort orders (identifier1 ASC, identifier2 DESC)
|
||||||
|
*
|
||||||
|
* This is a hack to enable custom ordering of tree models when
|
||||||
|
* flattening the tree.
|
||||||
|
*
|
||||||
|
* Implemented out of necessity under
|
||||||
|
* https://github.com/e107inc/e107/issues/3029
|
||||||
|
*
|
||||||
|
* @returns null
|
||||||
|
*/
|
||||||
|
private function prepareSimulatedCustomOrdering()
|
||||||
|
{
|
||||||
|
$db_query = $this->getParam('db_query');
|
||||||
|
$db_query = preg_replace_callback('/ORDER BY (?:.+\.)*[\.]*([A-Za-z0-9$_,]+)[ ]*(ASC|DESC)*/i', function($matches)
|
||||||
|
{
|
||||||
|
if (isset($matches[1]))
|
||||||
|
{
|
||||||
|
$current_sort_field = $this->getParam('sort_field');
|
||||||
|
if (!empty($current_sort_field))
|
||||||
|
{
|
||||||
|
$matches[1] = $current_sort_field.",".$matches[1];
|
||||||
|
}
|
||||||
|
$this->setParam('sort_field', array_map('trim', explode(',', $matches[1])));
|
||||||
|
}
|
||||||
|
if (isset($matches[2]))
|
||||||
|
$this->setParam('sort_order',
|
||||||
|
(0 === strcasecmp($matches[2], 'DESC') ? -1 : 1)
|
||||||
|
);
|
||||||
|
}, $db_query);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get single model instance from the collection
|
* Get single model instance from the collection
|
||||||
* @param integer $node_id
|
* @param integer $node_id
|
||||||
|
Reference in New Issue
Block a user