Работа с деревом в виде вложенного множества
This commit is contained in:
commit
d32455d043
5 changed files with 866 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
*.bak
|
||||||
15
composer.json
Normal file
15
composer.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "ctiso/tree",
|
||||||
|
"description": "nested set tree",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Phedor Podlesnov",
|
||||||
|
"email": "phedor@edu.yar.ru"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"Tree": "src/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
636
src/DBtree.php
Normal file
636
src/DBtree.php
Normal file
|
|
@ -0,0 +1,636 @@
|
||||||
|
<?php
|
||||||
|
//****************************************************************************
|
||||||
|
// phpDBTree 1.4
|
||||||
|
//****************************************************************************
|
||||||
|
// Author: Maxim Poltarak <maxx at e dash taller dot net>
|
||||||
|
// WWW: http://dev.e-taller.net/dbtree/
|
||||||
|
// Category: Databases
|
||||||
|
// Description: PHP class implementing a Nested Sets approach to storing
|
||||||
|
// tree-like structures in database tables. This technique was
|
||||||
|
// introduced by Joe Celko <http://www.celko.com/> and has some
|
||||||
|
// advantages over a widely used method called Adjacency Matrix.
|
||||||
|
//****************************************************************************
|
||||||
|
// The lib is FREEWARE. That means you may use it anywhere you want, you may
|
||||||
|
// do anything with it. The Author mentioned above is NOT responsible for any
|
||||||
|
// consequences of using this library.
|
||||||
|
// If you don't agree with this, you are NOT ALLOWED to use the lib!
|
||||||
|
//****************************************************************************
|
||||||
|
// You're welcome to send me all improvings, feature requests, bug reports...
|
||||||
|
//****************************************************************************
|
||||||
|
// SAMPLE DB TABLE STRUCTURE:
|
||||||
|
//
|
||||||
|
// CREATE TABLE categories (
|
||||||
|
// cat_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
// cat_left INT UNSIGNED NOT NULL,
|
||||||
|
// cat_right INT UNSIGNED NOT NULL,
|
||||||
|
// cat_level INT UNSIGNED NOT NULL,
|
||||||
|
// PRIMARY KEY(cat_id),
|
||||||
|
// KEY(cat_left, cat_right, cat_level)
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// This is believed to be the optimal Nested Sets use case. Use `one-to-one`
|
||||||
|
// relations on `cat_id` field between this `structure` table and
|
||||||
|
// another `data` table in your database.
|
||||||
|
//
|
||||||
|
// Don't forget to make a single call to clear()
|
||||||
|
// to set up the Root node in an empty table.
|
||||||
|
//
|
||||||
|
//****************************************************************************
|
||||||
|
// NOTE: Although you may use this library to retrieve data from the table,
|
||||||
|
// it is recommended to write your own queries for doing that.
|
||||||
|
// The main purpose of the library is to provide a simpler way to
|
||||||
|
// create, update and delete records. Not to SELECT them.
|
||||||
|
//****************************************************************************
|
||||||
|
//
|
||||||
|
// IMPORTANT! DO NOT create either UNIQUE or PRIMARY keys on the set of
|
||||||
|
// fields (`cat_left`, `cat_right` and `cat_level`)!
|
||||||
|
// Unique keys will destroy the Nested Sets structure!
|
||||||
|
//
|
||||||
|
//****************************************************************************
|
||||||
|
// CHANGELOG:
|
||||||
|
// 16-Apr-2003 -=- 1.1
|
||||||
|
// - Added moveAll() method
|
||||||
|
// - Added fourth parameter to the constructor
|
||||||
|
// - Renamed getElementInfo() to getNodeInfo() /keeping BC/
|
||||||
|
// - Added "Sample Table Structure" comment
|
||||||
|
// - Now it trigger_error()'s in case of any serious error, because if not you
|
||||||
|
// will lose the Nested Sets structure in your table
|
||||||
|
// 19-Feb-2004 -=- 1.2
|
||||||
|
// - Fixed a bug in moveAll() method?
|
||||||
|
// Thanks to Maxim Matyukhin for the patch.
|
||||||
|
// 13-Jul-2004 -=- 1.3
|
||||||
|
// - Changed all die()'s for a more standard trigger_error()
|
||||||
|
// Thanks to Dmitry Romakhin for pointing out an issue with
|
||||||
|
// incorrect error message call.
|
||||||
|
// 09-Nov-2004 -=- 1.4
|
||||||
|
// - Added insertNear() method.
|
||||||
|
// Thanks to Michael Krenz who sent its implementation.
|
||||||
|
// - Removed IGNORE keyword from UPDATE clauses in insert() methods.
|
||||||
|
// It was dumb to have it there all the time. Sorry. Anyway, you had
|
||||||
|
// to follow the important note mencioned above. If you hadn't, you're
|
||||||
|
// in problem.
|
||||||
|
//
|
||||||
|
//****************************************************************************
|
||||||
|
// Note: For best viewing of the code Tab size 4 is recommended
|
||||||
|
//****************************************************************************
|
||||||
|
|
||||||
|
function _case($c, $then, $else) {
|
||||||
|
return " (CASE WHEN $c THEN $then ELSE $else END) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _between($a, $x, $y) {
|
||||||
|
return " " . $a . " BETWEEN " . $x . " AND " . $y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _inside($a, $x, $y) {
|
||||||
|
return " " . $a . " > " . $x . " AND " . $a . " < " . $y;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Tree_DBTree
|
||||||
|
{
|
||||||
|
var $db; // CDatabase class to plug to
|
||||||
|
var $table; // Table with Nested Sets implemented
|
||||||
|
var $id; // Name of the ID-auto_increment-field in the table.
|
||||||
|
// These 3 variables are names of fields which are needed to implement
|
||||||
|
// Nested Sets. All 3 fields should exist in your table!
|
||||||
|
// However, you may want to change their names here to avoid name collisions.
|
||||||
|
var $left = 'cat_left';
|
||||||
|
var $right = 'cat_right';
|
||||||
|
var $level = 'cat_level';
|
||||||
|
var $qryParams = '';
|
||||||
|
var $qryFields = '';
|
||||||
|
var $qryTables = '';
|
||||||
|
var $qryWhere = '';
|
||||||
|
var $qryGroupBy = '';
|
||||||
|
var $qryHaving = '';
|
||||||
|
var $qryOrderBy = '';
|
||||||
|
var $qryLimit = '';
|
||||||
|
var $sqlNeedReset = true;
|
||||||
|
var $sql; // Last SQL query
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
// Constructor
|
||||||
|
// $DB : CDatabase class instance to link to
|
||||||
|
// $tableName : table in database where to implement nested sets
|
||||||
|
// $itemId : name of the field which will uniquely identify every record
|
||||||
|
// $fieldNames : optional configuration array to set field names. Example:
|
||||||
|
// array(
|
||||||
|
// 'left' => 'cat_left',
|
||||||
|
// 'right' => 'cat_right',
|
||||||
|
// 'level' => 'cat_level'
|
||||||
|
// )
|
||||||
|
function CDBTree(&$DB, $tableName, $itemId, $seq, $fieldNames = array())
|
||||||
|
{
|
||||||
|
|
||||||
|
if (empty($tableName)) trigger_error("phpDbTree: Unknown table", E_USER_ERROR);
|
||||||
|
|
||||||
|
if (empty($itemId)) trigger_error("phpDbTree: Unknown ID column", E_USER_ERROR);
|
||||||
|
$this->seq = $seq;
|
||||||
|
$this->db = $DB;
|
||||||
|
$this->table = $tableName;
|
||||||
|
$this->id = $itemId;
|
||||||
|
|
||||||
|
if (is_array($fieldNames) && sizeof($fieldNames))
|
||||||
|
foreach($fieldNames as $k => $v) $this->$k = $v;
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
// Returns a Left and Right IDs and Level of an element or false on error
|
||||||
|
// $ID : an ID of the element
|
||||||
|
function getElementInfo($ID)
|
||||||
|
{
|
||||||
|
return $this->getNodeInfo($ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeInfo($ID)
|
||||||
|
{
|
||||||
|
$this->sql = 'SELECT ' . $this->left . ',' . $this->right . ',' . $this->level . ' FROM ' . $this->table . ' WHERE ' . $this->id . '=\'' . $ID . '\'';
|
||||||
|
// print_r($this->sql);
|
||||||
|
|
||||||
|
if (($query = $this->db->query($this->sql)) && ($this->db->num_rows($query) == 1) && ($Data = $this->db->fetch_array($query))) return array(
|
||||||
|
(int)$Data[$this->left],
|
||||||
|
(int)$Data[$this->right],
|
||||||
|
(int)$Data[$this->level]
|
||||||
|
);
|
||||||
|
else trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR); // throw new Exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
// Clears table and creates 'root' node
|
||||||
|
// $data : optional argument with data for the root node
|
||||||
|
function clear($data = array())
|
||||||
|
{
|
||||||
|
// clearing table
|
||||||
|
|
||||||
|
if ((!$this->db->query('TRUNCATE ' . $this->table)) && (!$this->db->query('DELETE FROM ' . $this->table))) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
// preparing data to be inserted
|
||||||
|
|
||||||
|
if (sizeof($data)) {
|
||||||
|
$fld_names = implode(',', array_keys($data)) . ',';
|
||||||
|
|
||||||
|
if (sizeof($data)) $fld_values = '\'' . implode('\',\'', array_values($data)) . '\',';
|
||||||
|
}
|
||||||
|
$fld_names.= $this->left . ',' . $this->right . ',' . $this->level;
|
||||||
|
$fld_values.= '1,2,0';
|
||||||
|
// inserting new record
|
||||||
|
$this->sql = 'INSERT INTO ' . $this->table . '(' . $fld_names . ') VALUES(' . $fld_values . ')';
|
||||||
|
|
||||||
|
if (!($this->db->query($this->sql))) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
return $this->db->insert_id($this->seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
// Updates a record
|
||||||
|
// $ID : element ID
|
||||||
|
// $data : array with data to update: array(<field_name> => <fields_value>)
|
||||||
|
function update($ID, $data)
|
||||||
|
{
|
||||||
|
$sql_set = '';
|
||||||
|
|
||||||
|
foreach($data as $k => $v) $sql_set.= ',' . $k . '=\'' . addslashes($v) . '\'';
|
||||||
|
return $this->db->query('UPDATE ' . $this->table . ' SET ' . substr($sql_set, 1) . ' WHERE ' . $this->id . '=\'' . $ID . '\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
// Inserts a record into the table with nested sets
|
||||||
|
// $ID : an ID of the parent element
|
||||||
|
// $data : array with data to be inserted: array(<field_name> => <field_value>)
|
||||||
|
// Returns : true on success, or false on error
|
||||||
|
function insert($ID, $data)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!(list($leftId, $rightId, $level) = $this->getNodeInfo($ID))) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
// preparing data to be inserted
|
||||||
|
|
||||||
|
if (sizeof($data)) {
|
||||||
|
$fld_names = implode(',', array_keys($data)) . ',';
|
||||||
|
$fld_values = '\'' . implode('\',\'', array_values($data)) . '\',';
|
||||||
|
}
|
||||||
|
$fld_names.= $this->left . ',' . $this->right . ',' . $this->level;
|
||||||
|
$fld_values.= ($rightId) . ',' . ($rightId+1) . ',' . ($level+1);
|
||||||
|
// creating a place for the record being inserted
|
||||||
|
|
||||||
|
if ($ID) {
|
||||||
|
$this->sql = 'UPDATE ' . $this->table . ' SET ' . $this->left . '=' . _case($this->left . '>' . $rightId, $this->left . '+2', $this->left) . ',' . $this->right . '=' . _case($this->right . '>=' . $rightId, $this->right . '+2', $this->right) . 'WHERE ' . $this->right . '>=' . $rightId;
|
||||||
|
|
||||||
|
if (!($this->db->query($this->sql))) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
}
|
||||||
|
// inserting new record
|
||||||
|
$this->sql = 'INSERT INTO ' . $this->table . '(' . $fld_names . ') VALUES(' . $fld_values . ')';
|
||||||
|
|
||||||
|
if (!($this->db->query($this->sql))) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
return $this->db->insert_id($this->seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
// Inserts a record into the table with nested sets
|
||||||
|
// $ID : ID of the element after which (i.e. at the same level) the new element
|
||||||
|
// is to be inserted
|
||||||
|
// $data : array with data to be inserted: array(<field_name> => <field_value>)
|
||||||
|
// Returns : true on success, or false on error
|
||||||
|
function insertNear($ID, $data)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!(list($leftId, $rightId, $level) = $this->getNodeInfo($ID))) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
// preparing data to be inserted
|
||||||
|
|
||||||
|
if (sizeof($data)) {
|
||||||
|
$fld_names = implode(',', array_keys($data)) . ',';
|
||||||
|
$fld_values = '\'' . implode('\',\'', array_values($data)) . '\',';
|
||||||
|
}
|
||||||
|
$fld_names.= $this->left . ',' . $this->right . ',' . $this->level;
|
||||||
|
$fld_values.= ($rightId+1) . ',' . ($rightId+2) . ',' . ($level);
|
||||||
|
// creating a place for the record being inserted
|
||||||
|
|
||||||
|
if ($ID) {
|
||||||
|
$this->sql = 'UPDATE ' . $this->table . ' SET ' . $this->left . '=' . _case($this->left . '>' . $rightId, $this->left . '+2', $this->left) . $this->right . '=' . _case($this->right . '>' . $rightId, $this->right . '+2', $this->right) . 'WHERE ' . $this->right . '>' . $rightId;
|
||||||
|
|
||||||
|
if (!($this->db->query($this->sql))) trigger_error("phpDbTree error:" . $this->db->error() , E_USER_ERROR);
|
||||||
|
}
|
||||||
|
// inserting new record
|
||||||
|
$this->sql = 'INSERT INTO ' . $this->table . '(' . $fld_names . ') VALUES(' . $fld_values . ')';
|
||||||
|
|
||||||
|
if (!($this->db->query($this->sql))) trigger_error("phpDbTree error:" . $this->db->error() , E_USER_ERROR);
|
||||||
|
return $this->db->insert_id($this->seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
// Assigns a node with all its children to another parent
|
||||||
|
// $ID : node ID
|
||||||
|
// $newParentID : ID of new parent node
|
||||||
|
// Returns : false on error
|
||||||
|
function moveAll($ID, $newParentId)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!(list($leftId, $rightId, $level) = $this->getNodeInfo($ID))) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
|
||||||
|
if (!(list($leftIdP, $rightIdP, $levelP) = $this->getNodeInfo($newParentId))) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
|
||||||
|
if ($ID == $newParentId || $leftId == $leftIdP || ($leftIdP >= $leftId && $leftIdP <= $rightId)) return false;
|
||||||
|
// whether it is being moved upwards along the path
|
||||||
|
|
||||||
|
if ($leftIdP < $leftId && $rightIdP > $rightId && $levelP < $level-1) {
|
||||||
|
$this->sql = 'UPDATE ' . $this->table . ' SET '
|
||||||
|
. $this->level . '=' . _case($this->left . ' BETWEEN ' . $leftId . ' AND ' . $rightId, $this->level . sprintf('%+d', -($level-1) +$levelP) , $this->level) . ","
|
||||||
|
. $this->right . '=' . _case($this->right . ' BETWEEN ' . ($rightId+1) . ' AND ' . ($rightIdP-1) , $this->right . '-' . ($rightId-$leftId+1)
|
||||||
|
, _case($this->left . ' BETWEEN ' . ($leftId) . ' AND ' . ($rightId) , $this->right . '+' . ((($rightIdP-$rightId-$level+$levelP) /2) *2+$level-$levelP-1) , $this->right)) . ","
|
||||||
|
. $this->left . '=' . _case($this->left . ' BETWEEN ' . ($rightId+1) . ' AND ' . ($rightIdP-1) , $this->left . '-' . ($rightId-$leftId+1) , _case($this->left . ' BETWEEN ' . $leftId . ' AND ' . ($rightId) , $this->left . '+' . ((($rightIdP-$rightId-$level+$levelP) /2) *2+$level-$levelP-1) , $this->left)) . 'WHERE ' . $this->left . ' BETWEEN ' . ($leftIdP+1) . ' AND ' . ($rightIdP-1);
|
||||||
|
|
||||||
|
} elseif ($leftIdP < $leftId) {
|
||||||
|
$this->sql = 'UPDATE ' . $this->table . ' SET '
|
||||||
|
. $this->level . '=' . _case($this->left . ' BETWEEN ' . $leftId . ' AND ' . $rightId, $this->level . sprintf('%+d', -($level-1) +$levelP) , $this->level) . ","
|
||||||
|
. $this->left . '=' . _case($this->left . ' BETWEEN ' . $rightIdP . ' AND ' . ($leftId-1) , $this->left . '+' . ($rightId-$leftId+1)
|
||||||
|
, _case($this->left . ' BETWEEN ' . $leftId . ' AND ' . $rightId, $this->left . '-' . ($leftId-$rightIdP) , $this->left)) . ","
|
||||||
|
. $this->right . '=' . _case($this->right . ' BETWEEN ' . $rightIdP . ' AND ' . $leftId, $this->right . '+' . ($rightId-$leftId+1) , _case($this->right . ' BETWEEN ' . $leftId . ' AND ' . $rightId, $this->right . '-' . ($leftId-$rightIdP) , $this->right)) . 'WHERE ' . $this->left . ' BETWEEN ' . $leftIdP . ' AND ' . $rightId
|
||||||
|
// !!! added this line (Maxim Matyukhin)
|
||||||
|
. ' OR ' . $this->right . ' BETWEEN ' . $leftIdP . ' AND ' . $rightId;
|
||||||
|
} else {
|
||||||
|
$this->sql = 'UPDATE ' . $this->table . ' SET '
|
||||||
|
. $this->level . '=' . _case($this->left . ' BETWEEN ' . $leftId . ' AND ' . $rightId, $this->level . sprintf('%+d', -($level-1) +$levelP) , $this->level) . ","
|
||||||
|
. $this->left . '=' . _case($this->left . ' BETWEEN ' . $rightId . ' AND ' . $rightIdP, $this->left . '-' . ($rightId-$leftId+1)
|
||||||
|
, _case($this->left . ' BETWEEN ' . $leftId . ' AND ' . $rightId, $this->left . '+' . ($rightIdP-1-$rightId), $this->left)) . ", "
|
||||||
|
. $this->right . '=' . _case($this->right . ' BETWEEN ' . ($rightId+1) . ' AND ' . ($rightIdP-1) , $this->right . '-' . ($rightId-$leftId+1) , _case($this->right . ' BETWEEN ' . $leftId . ' AND ' . $rightId, $this->right . '+' . ($rightIdP-1-$rightId) , $this->right)) . 'WHERE ' . $this->left . ' BETWEEN ' . $leftId . ' AND ' . $rightIdP
|
||||||
|
// !!! added this line (Maxim Matyukhin)
|
||||||
|
. ' OR ' . $this->right . ' BETWEEN ' . $leftId . ' AND ' . $rightIdP;
|
||||||
|
}
|
||||||
|
return $this->db->query($this->sql) or trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перемещение всех детей ветки в другую ветку
|
||||||
|
function moveChildren($ID, $newParentId)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!(list($leftId, $rightId, $level) = $this->getNodeInfo($ID))) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
|
||||||
|
if (!(list($leftIdP, $rightIdP, $levelP) = $this->getNodeInfo($newParentId))) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
|
||||||
|
if ($ID == $newParentId || $leftId == $leftIdP || ($leftIdP >= $leftId && $leftIdP <= $rightId)) return false;
|
||||||
|
// whether it is being moved upwards along the path
|
||||||
|
|
||||||
|
if ($leftIdP < $leftId && $rightIdP > $rightId && $levelP < $level-1) {
|
||||||
|
// _update($this->table, array(), )
|
||||||
|
$this->sql = 'UPDATE ' . $this->table . ' SET '
|
||||||
|
// Меняем уровень
|
||||||
|
. $this->level . '=' .
|
||||||
|
_case(_between($this->left, $leftId, $rightId),
|
||||||
|
$this->level . sprintf('%+d', -($level-1) +$levelP) , $this->level)
|
||||||
|
// Меняем границы
|
||||||
|
. $this->left . '=' .
|
||||||
|
_case(_beetween($this->left, $rightId+1, $rightIdP-1), $this->left . '-' . $rightId-$leftId+1 ,
|
||||||
|
_case(_between($this->left, $leftId, $rightId), $this->left . '+' . ((($rightIdP-$rightId-$level+$levelP) /2) *2+$level-$levelP-1) , $this->left))
|
||||||
|
. $this->right . '=' .
|
||||||
|
_case(_between($this->right, $rightId+1, $rightIdP-1), $this->right . '-' . ($rightId-$leftId+1) ,
|
||||||
|
_case(_between($this->left, $leftId, $rightId), $this->right . '+' . ((($rightIdP-$rightId-$level+$levelP) /2) *2+$level-$levelP-1) , $this->right))
|
||||||
|
. 'WHERE ' . _between($this->left, ($leftIdP+1), ($rightIdP-1));
|
||||||
|
|
||||||
|
} elseif ($leftIdP < $leftId) {
|
||||||
|
$this->sql = 'UPDATE ' . $this->table . ' SET '
|
||||||
|
. $this->level . '=' .
|
||||||
|
_case(_between($this->left, $leftId, $rightId),
|
||||||
|
$this->level . sprintf('%+d', -($level-1) +$levelP) , $this->level)
|
||||||
|
. $this->left . '=' .
|
||||||
|
_case(_between($this->left, $rightIdP, $leftId-1), $this->left . '+' . ($rightId-$leftId+1),
|
||||||
|
_case(_between($this->left, $leftId, $rightId), $this->left . '-' . ($leftId-$rightIdP) , $this->left))
|
||||||
|
. $this->right . '=' .
|
||||||
|
_case(_between($this->right, $rightIdP, $leftId), $this->right . '+' . ($rightId-$leftId+1),
|
||||||
|
_case(_between($this->right, $leftId, $rightId), $this->right . '-' . ($leftId-$rightIdP) , $this->right))
|
||||||
|
. 'WHERE ' . _between($this->left, $leftIdP, $rightId)
|
||||||
|
// !!! added this line (Maxim Matyukhin)
|
||||||
|
. ' OR ' . _between($this->right, $leftIdP, $rightId);
|
||||||
|
} else {
|
||||||
|
$this->sql = 'UPDATE ' . $this->table . ' SET '
|
||||||
|
. $this->level . '='
|
||||||
|
. _case(_between($this->left, $leftId, $rightId),
|
||||||
|
$this->level . sprintf('%+d', -($level-1) +$levelP) , $this->level)
|
||||||
|
. $this->left . '=' .
|
||||||
|
_case(_between($this->left, $rightId, $rightIdP), $this->left . '-' . ($rightId-$leftId+1),
|
||||||
|
_case(_between($this->left, $leftId, $rightId), $this->left . '+' . ($rightIdP-1-$rightId), $this->left))
|
||||||
|
. $this->right . '=' .
|
||||||
|
_case(_between($this->right, $rightId+1, $rightIdP-1), $this->right . '-' . ($rightId-$leftId+1),
|
||||||
|
_case(_between($this->right, $leftId, $rightId), $this->right . '+' . ($rightIdP-1-$rightId) , $this->right))
|
||||||
|
. 'WHERE ' . _between($this->left, $leftId, $rightIdP)
|
||||||
|
// !!! added this line (Maxim Matyukhin)
|
||||||
|
. ' OR ' . _between($this->right, $leftId, $rightIdP);
|
||||||
|
}
|
||||||
|
return $this->db->query($this->sql) or trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
// Deletes a record wihtout deleting its children
|
||||||
|
// $ID : an ID of the element to be deleted
|
||||||
|
// Returns : true on success, or false on error
|
||||||
|
function delete($ID)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!(list($leftId, $rightId, $level) = $this->getNodeInfo($ID))) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
// Deleting record
|
||||||
|
$this->sql = 'DELETE FROM ' . $this->table . ' WHERE ' . $this->id . '=\'' . $ID . '\'';
|
||||||
|
|
||||||
|
if (!$this->db->query($this->sql)) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
// Clearing blank spaces in a tree
|
||||||
|
$this->sql = 'UPDATE ' . $this->table . ' SET '
|
||||||
|
. $this->left . '=' . _case($this->left . ' BETWEEN ' . $leftId . ' AND ' . $rightId, $this->left . '-1', $this->left) . ", "
|
||||||
|
. $this->right . '=' . _case($this->right . ' BETWEEN ' . $leftId . ' AND ' . $rightId, $this->right . '-1', $this->right) . ", "
|
||||||
|
. $this->level . '=' . _case($this->left . ' BETWEEN ' . $leftId . ' AND ' . $rightId, $this->level . '-1', $this->level) . ", "
|
||||||
|
. $this->left . '=' . _case($this->left . '>' . $rightId, $this->left . '-2', $this->left) . ", "
|
||||||
|
. $this->right . '=' . _case($this->right . '>' . $rightId, $this->right . '-2', $this->right)
|
||||||
|
. ' WHERE ' . $this->right . '>' . $leftId;
|
||||||
|
|
||||||
|
if (!$this->db->query($this->sql)) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
// Deletes a record with all its children
|
||||||
|
// $ID : an ID of the element to be deleted
|
||||||
|
// Returns : true on success, or false on error
|
||||||
|
function deleteAll($ID)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!(list($leftId, $rightId, $level) = $this->getNodeInfo($ID))) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
// Deleteing record(s)
|
||||||
|
$this->sql = 'DELETE FROM ' . $this->table . ' WHERE ' . $this->left . ' BETWEEN ' . $leftId . ' AND ' . $rightId;
|
||||||
|
|
||||||
|
if (!$this->db->query($this->sql)) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
// Clearing blank spaces in a tree
|
||||||
|
$deltaId = ($rightId-$leftId) +1;
|
||||||
|
$this->sql = 'UPDATE ' . $this->table . ' SET '
|
||||||
|
. $this->left . '=' . _case($this->left . '>' . $leftId, $this->left . '-' . $deltaId, $this->left) . ", "
|
||||||
|
. $this->right . '=' . _case($this->right . '>' . $leftId, $this->right . '-' . $deltaId, $this->right)
|
||||||
|
. ' WHERE ' . $this->right . '>' . $rightId;
|
||||||
|
|
||||||
|
if (!$this->db->query($this->sql)) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
// Enumerates children of an element
|
||||||
|
// $ID : an ID of an element which children to be enumerated
|
||||||
|
// $start_level : relative level from which start to enumerate children
|
||||||
|
// $end_level : the last relative level at which enumerate children
|
||||||
|
// 1. If $end_level isn't given, only children of
|
||||||
|
// $start_level levels are enumerated
|
||||||
|
// 2. Level values should always be greater than zero.
|
||||||
|
// Level 1 means direct children of the element
|
||||||
|
// Returns : a result id for using with other DB functions
|
||||||
|
function enumChildrenAll($ID)
|
||||||
|
{
|
||||||
|
return $this->enumChildren($ID, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enumChildren($ID, $start_level = 1, $end_level = 1)
|
||||||
|
{
|
||||||
|
if ($start_level < 0) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
// We could use sprintf() here, but it'd be too slow
|
||||||
|
$whereSql1 = ' AND ' . $this->table . '.' . $this->level;
|
||||||
|
$whereSql2 = '_' . $this->table . '.' . $this->level . '+';
|
||||||
|
|
||||||
|
if (!$end_level) $whereSql = $whereSql1 . '>=' . $whereSql2 . (int)$start_level;
|
||||||
|
else {
|
||||||
|
$whereSql = ($end_level <= $start_level) ? $whereSql1 . '=' . $whereSql2 . (int)$start_level : ' AND ' . $this->table . '.' . $this->level . ' BETWEEN _' . $this->table . '.' . $this->level . '+' . (int)$start_level . ' AND _' . $this->table . '.' . $this->level . '+' . (int)$end_level;
|
||||||
|
}
|
||||||
|
$this->sql = $this->sqlComposeSelect(array(
|
||||||
|
'', // Params
|
||||||
|
'', // Fields
|
||||||
|
$this->table . ' _' . $this->table . ', ' . $this->table, // Tables
|
||||||
|
'_' . $this->table . '.' . $this->id . '=\'' . $ID . '\'' . ' AND ' . $this->table . '.' . $this->left . ' BETWEEN _' . $this->table . '.' . $this->left . ' AND _' . $this->table . '.' . $this->right . $whereSql
|
||||||
|
));
|
||||||
|
return $this->db->query($this->sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enumChildrenArray($ID, $start_level = 1, $end_level = 1)
|
||||||
|
{
|
||||||
|
return $this->db->result2array($this->enumChildren($ID, $start_level, $end_level));
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
// Enumerates the PATH from an element to its top level parent
|
||||||
|
// $ID : an ID of an element
|
||||||
|
// $showRoot : whether to show root node in a path
|
||||||
|
// Returns : a result id for using with other DB functions
|
||||||
|
function enumPath($ID, $showRoot = false)
|
||||||
|
{
|
||||||
|
$this->sql = $this->sqlComposeSelect(array(
|
||||||
|
'', // Params
|
||||||
|
'', // Fields
|
||||||
|
$this->table . ' _' . $this->table . ', ' . $this->table, // Tables
|
||||||
|
'_' . $this->table . '.' . $this->id . '=\'' . $ID . '\'' . ' AND _' . $this->table . '.' . $this->left . ' BETWEEN ' . $this->table . '.' . $this->left . ' AND ' . $this->table . '.' . $this->right . (($showRoot) ? '' : ' AND ' . $this->table . '.' . $this->level . '>0') , // Where
|
||||||
|
'', // GroupBy
|
||||||
|
'', // Having
|
||||||
|
$this->table . '.' . $this->left // OrderBy
|
||||||
|
|
||||||
|
));
|
||||||
|
return $this->db->query($this->sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enumPathArray($ID, $showRoot = false)
|
||||||
|
{
|
||||||
|
return $this->db->result2array($this->enumPath($ID, $showRoot));
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
// Returns query result to fetch data of the element's parent
|
||||||
|
// $ID : an ID of an element which parent to be retrieved
|
||||||
|
// $level : Relative level of parent
|
||||||
|
// Returns : a result id for using with other DB functions
|
||||||
|
function getParent($ID, $level = 1)
|
||||||
|
{
|
||||||
|
|
||||||
|
if ($level < 1) trigger_error("phpDbTree error: " . $this->db->error() , E_USER_ERROR);
|
||||||
|
$this->sql = $this->sqlComposeSelect(array(
|
||||||
|
'', // Params
|
||||||
|
'', // Fields
|
||||||
|
$this->table . ' _' . $this->table . ', ' . $this->table, // Tables
|
||||||
|
'_' . $this->table . '.' . $this->id . '=\'' . $ID . '\'' . ' AND _' . $this->table . '.' . $this->left . ' BETWEEN ' . $this->table . '.' . $this->left . ' AND ' . $this->table . '.' . $this->right . ' AND ' . $this->table . '.' . $this->level . '=_' . $this->table . '.' . $this->level . '-' . (int)$level // Where
|
||||||
|
|
||||||
|
));
|
||||||
|
$result = $this->db->result2array($this->db->query($this->sql));
|
||||||
|
return (int)$result[0][$this->id];
|
||||||
|
}
|
||||||
|
//************************************************************************
|
||||||
|
|
||||||
|
function sqlReset()
|
||||||
|
{
|
||||||
|
$this->qryParams = '';
|
||||||
|
$this->qryFields = '';
|
||||||
|
$this->qryTables = '';
|
||||||
|
$this->qryWhere = '';
|
||||||
|
$this->qryGroupBy = '';
|
||||||
|
$this->qryHaving = '';
|
||||||
|
$this->qryOrderBy = '';
|
||||||
|
$this->qryLimit = '';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
function sqlSetReset($resetMode)
|
||||||
|
{
|
||||||
|
$this->sqlNeedReset = ($resetMode) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
function sqlParams($param = '')
|
||||||
|
{
|
||||||
|
return (empty($param)) ? $this->qryParams : $this->qryParams = $param;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlFields($param = '')
|
||||||
|
{
|
||||||
|
return (empty($param)) ? $this->qryFields : $this->qryFields = $param;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlSelect($param = '')
|
||||||
|
{
|
||||||
|
return $this->sqlFields($param);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlTables($param = '')
|
||||||
|
{
|
||||||
|
return (empty($param)) ? $this->qryTables : $this->qryTables = $param;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlFrom($param = '')
|
||||||
|
{
|
||||||
|
return $this->sqlTables($param);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlWhere($param = '')
|
||||||
|
{
|
||||||
|
return (empty($param)) ? $this->qryWhere : $this->qryWhere = $param;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlGroupBy($param = '')
|
||||||
|
{
|
||||||
|
return (empty($param)) ? $this->qryGroupBy : $this->qryGroupBy = $param;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlHaving($param = '')
|
||||||
|
{
|
||||||
|
return (empty($param)) ? $this->qryHaving : $this->qryHaving = $param;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlOrderBy($param = '')
|
||||||
|
{
|
||||||
|
return (empty($param)) ? $this->qryOrderBy : $this->qryOrderBy = $param;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlLimit($param = '')
|
||||||
|
{
|
||||||
|
return (empty($param)) ? $this->qryLimit : $this->qryLimit = $param;
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************************************
|
||||||
|
function sqlComposeSelect($arSql)
|
||||||
|
{
|
||||||
|
$joinTypes = array(
|
||||||
|
'join' => 1,
|
||||||
|
'cross' => 1,
|
||||||
|
'inner' => 1,
|
||||||
|
'straight' => 1,
|
||||||
|
'left' => 1,
|
||||||
|
'natural' => 1,
|
||||||
|
'right' => 1
|
||||||
|
);
|
||||||
|
$this->sql = 'SELECT ' . $arSql[0] . ' ';
|
||||||
|
|
||||||
|
if (!empty($this->qryParams)) $this->sql.= $this->sqlParams . ' ';
|
||||||
|
|
||||||
|
if (empty($arSql[1]) && empty($this->qryFields)) $this->sql.= $this->table . '.' . $this->id;
|
||||||
|
else {
|
||||||
|
|
||||||
|
if (!empty($arSql[1])) $this->sql.= $arSql[1];
|
||||||
|
|
||||||
|
if (!empty($this->qryFields)) $this->sql.= ((empty($arSql[1])) ? '' : ',') . $this->qryFields;
|
||||||
|
}
|
||||||
|
$this->sql.= ' FROM ';
|
||||||
|
// $tblAr = array(0 => 'join');
|
||||||
|
$isJoin = ($tblAr = explode(' ', trim($this->qryTables)))
|
||||||
|
&& /*($joinTypes[strtolower($tblAr[0]) ])*/ 1;
|
||||||
|
|
||||||
|
if (empty($arSql[2]) && empty($this->qryTables)) $this->sql.= $this->table;
|
||||||
|
else {
|
||||||
|
|
||||||
|
if (!empty($arSql[2])) $this->sql.= $arSql[2];
|
||||||
|
|
||||||
|
if (!empty($this->qryTables)) {
|
||||||
|
|
||||||
|
if (!empty($arSql[2])) $this->sql.= (($isJoin) ? ' ' : ',');
|
||||||
|
elseif ($isJoin) $this->sql.= $this->table . ' ';
|
||||||
|
$this->sql.= $this->qryTables;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!empty($arSql[3])) || (!empty($this->qryWhere))) {
|
||||||
|
$this->sql.= ' WHERE ' . $arSql[3] . ' ';
|
||||||
|
|
||||||
|
if (!empty($this->qryWhere)) $this->sql.= (empty($arSql[3])) ? $this->qryWhere : 'AND(' . $this->qryWhere . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!empty($arSql[4])) || (!empty($this->qryGroupBy))) {
|
||||||
|
$this->sql.= ' GROUP BY ' . $arSql[4] . ' ';
|
||||||
|
|
||||||
|
if (!empty($this->qryGroupBy)) $this->sql.= (empty($arSql[4])) ? $this->qryGroupBy : ',' . $this->qryGroupBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!empty($arSql[5])) || (!empty($this->qryHaving))) {
|
||||||
|
$this->sql.= ' HAVING ' . $arSql[5] . ' ';
|
||||||
|
|
||||||
|
if (!empty($this->qryHaving)) $this->sql.= (empty($arSql[5])) ? $this->qryHaving : 'AND(' . $this->qryHaving . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!empty($arSql[6])) || (!empty($this->qryOrderBy))) {
|
||||||
|
$this->sql.= ' ORDER BY ' . (isset($arSql[6]) ? $arSql[6] : '') . ' ';
|
||||||
|
|
||||||
|
if (!empty($this->qryOrderBy)) $this->sql.= (empty($arSql[6])) ? $this->qryOrderBy : ',' . $this->qryOrderBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($arSql[7])) $this->sql.= ' LIMIT ' . $arSql[7];
|
||||||
|
elseif (!empty($this->qryLimit)) $this->sql.= ' LIMIT ' . $this->qryLimit;
|
||||||
|
|
||||||
|
if ($this->sqlNeedReset) $this->sqlReset();
|
||||||
|
return $this->sql;
|
||||||
|
}
|
||||||
|
//************************************************************************
|
||||||
|
|
||||||
|
}
|
||||||
135
src/Database.php
Normal file
135
src/Database.php
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
<?php
|
||||||
|
//****************************************************************************
|
||||||
|
// phpDatabase 2.1
|
||||||
|
//****************************************************************************
|
||||||
|
// Author: Maxim Poltarak <maxx at e dash taller dot net>
|
||||||
|
// Category: Databases
|
||||||
|
//****************************************************************************
|
||||||
|
// The lib is FREEWARE. This means you may use it anywhere you want, you may
|
||||||
|
// do anything with it. The Author mentioned above is NOT responsible for any
|
||||||
|
// consequences of using this library.
|
||||||
|
// If you don't agree with this, you MAY NOT use the lib!
|
||||||
|
//****************************************************************************
|
||||||
|
// All improvings, feature requests, bug reports, etc. are gladly accepted.
|
||||||
|
//****************************************************************************
|
||||||
|
// Note: For best viewing of the code Tab size 4 is recommended
|
||||||
|
//****************************************************************************
|
||||||
|
|
||||||
|
class Tree_Database
|
||||||
|
{
|
||||||
|
var $link;
|
||||||
|
var $db;
|
||||||
|
var $host, $user, $pass;
|
||||||
|
|
||||||
|
function CDatabase($db, $host = "localhost", $user = "", $pass = "")
|
||||||
|
{
|
||||||
|
$this->db = $db;
|
||||||
|
$this->host = $host;
|
||||||
|
$this->user = $user;
|
||||||
|
$this->pass = $pass;
|
||||||
|
$str = "host={$host} port=5432 dbname={$db} user={$user} password={$pass}";
|
||||||
|
$this->link = pg_connect($str);
|
||||||
|
}
|
||||||
|
|
||||||
|
function query($sql)
|
||||||
|
{
|
||||||
|
if (!$this->link) return 0;
|
||||||
|
return pg_query($this->link, $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
function affected_rows()
|
||||||
|
{
|
||||||
|
return pg_affected_rows($this->link);
|
||||||
|
}
|
||||||
|
|
||||||
|
function num_rows($q)
|
||||||
|
{
|
||||||
|
return pg_num_rows($q);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch_array($q) // fetchAll
|
||||||
|
{
|
||||||
|
return pg_fetch_array($q, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch_object($q) // fetchObject
|
||||||
|
{
|
||||||
|
return pg_fetch_object($q);
|
||||||
|
}
|
||||||
|
/* function data_seek($q, $n) {
|
||||||
|
return pg_data_seek($q, $n);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function free_result($q)
|
||||||
|
{
|
||||||
|
return pg_free_result($q);
|
||||||
|
}
|
||||||
|
|
||||||
|
function insert_id($seq)
|
||||||
|
{
|
||||||
|
$query = "SELECT currval('$seq')";
|
||||||
|
$res = $this->query($query);
|
||||||
|
$row = pg_fetch_array($res, NULL, PGSQL_ASSOC);
|
||||||
|
pg_free_result($res);
|
||||||
|
return ($row) ? $row['currval'] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function error()
|
||||||
|
{
|
||||||
|
return pg_last_error($this->link);
|
||||||
|
}
|
||||||
|
|
||||||
|
function error_die($msg = '')
|
||||||
|
{
|
||||||
|
die(((empty($msg)) ? '' : $msg . ': ') . $this->error());
|
||||||
|
}
|
||||||
|
|
||||||
|
function sql2var($sql)
|
||||||
|
{
|
||||||
|
|
||||||
|
if ((empty($sql)) || (!($query = $this->query($sql)))) return false;
|
||||||
|
|
||||||
|
if ($this->num_rows($query) < 1) return false;
|
||||||
|
return $this->result2var($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
function result2var($q)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!($Data = $this->fetch_array($q))) return false;
|
||||||
|
$this->free_result($q);
|
||||||
|
|
||||||
|
foreach($Data as $k => $v) $GLOBALS[$k] = $v;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*function sql2array($sql, $keyField = '')
|
||||||
|
{
|
||||||
|
|
||||||
|
if ((empty($sql)) || (!($query = $this->query($sql)))) return false;
|
||||||
|
|
||||||
|
if ($this->num_rows($query) < 1) return false;
|
||||||
|
return $this->result2array($query, $keyField);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
function result2array($q, $keyField = '')
|
||||||
|
{
|
||||||
|
$Result = array();
|
||||||
|
while ($Data = $this->fetch_array($q))
|
||||||
|
if (empty($keyField)) $Result[] = $Data;
|
||||||
|
else $Result[$Data[$keyField]] = $Data;
|
||||||
|
$this->free_result($q);
|
||||||
|
return $Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*function list_tables()
|
||||||
|
{
|
||||||
|
return mysql_list_tables($this->db, $this->link);
|
||||||
|
}
|
||||||
|
|
||||||
|
function list_fields($table_name)
|
||||||
|
{
|
||||||
|
return mysql_list_fields($this->db, $table_name, $this->link);
|
||||||
|
}*/
|
||||||
|
};
|
||||||
79
src/Sort.php
Normal file
79
src/Sort.php
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сортировка дерева в представлении Nested Set
|
||||||
|
* Для дерева которое хранится в базе данных используя представление Nested Set нет возможности отсортировать элементы дерева по
|
||||||
|
* произвольному полю. Поэтому после извлечения дерева из базы данных оно преобразуется в обычное представление сортируется и обратно
|
||||||
|
*
|
||||||
|
* Пример:
|
||||||
|
* $sort = new Tree_Sort();
|
||||||
|
* $data = $sort->sortBy($data, 'name');
|
||||||
|
*/
|
||||||
|
class Tree_Sort {
|
||||||
|
private $data = array();
|
||||||
|
private $result = array();
|
||||||
|
private $sortBy = '';
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Преобразуем Nested Set в дерево и сортируем
|
||||||
|
private function listTree(array $tree, $offset, $level) {
|
||||||
|
$result = array();
|
||||||
|
for ($i = $offset; $i < sizeof($tree); $i++) {
|
||||||
|
$leaf = $tree[$i];
|
||||||
|
$clevel = $leaf['cat_level'];
|
||||||
|
if ($clevel == $level) {
|
||||||
|
$result [] = array($i);
|
||||||
|
} else if ($clevel > $level) {
|
||||||
|
list($subtree, $i) = $this->listTree($tree, $i, $clevel);
|
||||||
|
$i--;
|
||||||
|
$result[sizeof($result) - 1][1] = $subtree;
|
||||||
|
} else {
|
||||||
|
$this->sortList($result, $tree);
|
||||||
|
return array($result, $i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->sortList($result, $tree);
|
||||||
|
return array($result, $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сравнение двух элементов
|
||||||
|
private function compare($a, $b) {
|
||||||
|
$a1 = $this->data[$a[0]][$this->sortBy];
|
||||||
|
$b1 = $this->data[$b[0]][$this->sortBy];
|
||||||
|
return strcmp($a1, $b1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сортировка списка
|
||||||
|
private function sortList(array &$list, $data) {
|
||||||
|
usort($list, array($this, 'compare'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создает дерево в виде списка
|
||||||
|
private function reorder(array $tree) {
|
||||||
|
foreach($tree as $node) {
|
||||||
|
$this->result[] = $this->data[$node[0]];
|
||||||
|
if (isset($node[1])) {
|
||||||
|
$this->reorder($node[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sortBy0(array $data, $sortBy)
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
$this->sortBy = $sortBy;
|
||||||
|
$order = $this->listTree($data, 0, 0);
|
||||||
|
return $order[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сортировка по заданному полю
|
||||||
|
public function sortBy(array $data, $sortBy) {
|
||||||
|
$this->data = $data;
|
||||||
|
$this->sortBy = $sortBy;
|
||||||
|
$order = $this->listTree($data, 0, 0);
|
||||||
|
$this->reorder($order[0]);
|
||||||
|
return $this->result;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue