<?php defined('BASEPATH') or die('No direct script access.');
/*
* Необходимые таблицы:
* CREATE TABLE categories (
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
id_parent INTEGER UNSIGNED NULL,
allow_add INTEGER UNSIGNED NOT NULL DEFAULT 1,
[...]
PRIMARY KEY(id)
);
Имена полей id, id_parent и allow_add, а также имя таблицы categories
настраиваются через конфиг.
*
* */
class Treelib
{
// массив настроек
protected $options;
// суперобъект
protected $CI;
// список элементов
protected $tree_data;
// массив истории к указанному элементу
protected $history = array();
// возвращаемый html дерева
protected $tree_html = '';
// уровень вложенности
protected $tree_level = 0;
// массив пройденных элементов
protected $recursy_protect_array = array();
// массив элементов списка категорий с полными путями
protected $list = array();
// последний id_parent для Tree()
protected $id_parent = 0;
// последний id для History()
//-------------------------------------------------------------------------
public function __construct()
{
$this->CI =& get_instance();
$this->CI->load->database();
$this->options = array(
'id_name' => 'id',
'id_parent_name' => 'id_parent',
'allow_add_name' => 'allow_add',
'tree_max_level' => 1,
'cat_tablename' => 'categories',
'history_elem_name' => 'name',
'list_delimiter' => ' / ',
'id_current' => 0
);
}
//-------------------------------------------------------------------------
/*
* Установка настроек
*
* Передаваемый параметр:
* array(
* 'tree_elem_tmpl' => HTML - темплейт для элемента дерева. например: <li>%s - %s</li>,
* 'tree_start_tmpl' => html начала контейнера. например: <ul class="ABC">,
* 'tree_end_tmpl' => html конца контейнера. например: </ul>,
* 'id_name' => имя ключа хранящего поле id,
* 'id_parent_name' => имя ключа хранящего поле id_parent,
* 'id_current' => ID для построения от него дерева,
* 'tree_max_level' => INTEGER (значение глубины максимальной вложенности),
* 'cat_tablename' => имя таблицы категорий,
* 'history_elem_name' => имя поля описания категории,
* 'list_delimiter' => разграничитель категорий в элементе списка,
* 'allow_add_name' => имя поля таблицы хранящего возможность добавления товара в категорию
* )
* */
public function setOptions($options)
{
if(is_array($options))
{
foreach($options as $name => $value)
{
$this->options[$name] = $value;
}
}
$this->id_parent = $this->options['id_current'];
}
//-------------------------------------------------------------------------
/*
* Запрос всех имеющихся категорий в таблице и формирование специального массива
*
* Параметр $order:
* array(
* '<имя ключа для итогового массива>' => '<имя поля в таблице>',
* [...]
* )
* Например:
* $order = array('id' => 'id', 'id_parent' => 'id_parent', 'elem_name' => 'name');
*
* $order нужен для того, чтобы иметь возможность формировать специальный массив категорий
* произвольной структуры. При именах ключей для итогового массива отличных от дефолтовых
* нужно настроить их через конфиг
* */
public function getTreeData($order)
{
// строим строку имён полей
$fields = current($order);
while(next($order))
{
$fields .= ", " . current($order);
}
$result = $this->CI->db->query("SELECT ".$fields." FROM ".$this->options['cat_tablename']);
$this->tree_data = array();
foreach ($result->result_array() as $row)
{
$bufarr = array();
foreach($order as $key => $param)
{
$bufarr[$key] = $row[$param];
}
$this->tree_data[$bufarr[$this->options['id_parent_name']]][] = $bufarr;
}
return $this->tree_data;
}
//-------------------------------------------------------------------------
/*
* Установка уже имеющегося спецмассива категорий
* */
public function setTreeData($data)
{
if(is_array($data))
{
$this->tree_data = $data;
}
}
//-------------------------------------------------------------------------
/*
* Сброс служебных данных.
* Необходим после каждого вызова метода History() и Tree()
* Вызывается отдельно ввиду рекурсивной структуры последних
* */
public function Reset()
{
$this->tree_level = 0;
$this->tree_html = "";
$this->history = array();
$this->recursy_protect_array = array();
$this->id_parent = 0;
}
//-------------------------------------------------------------------------
/*
* Построение дерева категорий на основе спецмассива категорий и шаблонов
* */
public function Tree()
{
// защита от бесконечной рекурсии
if(!in_array($this->id_parent, $this->recursy_protect_array))
{
$this->recursy_protect_array[] = $this->id_parent;
$this->tree_level++;
// для правильного построения html
if($this->options['tree_max_level'] > 1) $this->tree_html .= $this->options['tree_start_tmpl'];
// если глубина не достигнута и существует дочерний элемент
if($this->tree_level <= $this->options['tree_max_level'] && isset($this->tree_data[$this->id_parent]))
{
// пройтись по дочерним
foreach($this->tree_data[$this->id_parent] as $element)
{
// добавить пропарсеный элемент в html дерева
$this->tree_html .= vsprintf($this->options['tree_elem_tmpl'], $element);
if(isset($this->tree_data[$element[$this->options['id_name']]]))
{
$this->id_parent = $element[$this->options['id_name']];
$this->Tree();
$this->tree_level--;
}
}
// для правильного построения html
if($this->options['tree_max_level'] > 1)
{
$this->tree_html .= $this->options['tree_end_tmpl'];
}
}
// если вошли не глубже 1-го уровня то обрамить элементы
if($this->options['tree_max_level'] <= 1 && $this->tree_html != "")
{
return $this->options['tree_start_tmpl'].$this->tree_html.$this->options['tree_end_tmpl'];
}
else return $this->tree_html;
}
}
//-------------------------------------------------------------------------
/*
* Формирование массива категорий с полными путями к конечным элементам
* Параметр $include_not_allowed разрешает/запрещает добавление в
* возвращаемый массив тех категорий, которые помечены в таблице
* для разрешения/запрета добавления в них каких-либо товаров
* на уровне бизнес-логики
* */
public function FullList($include_not_allowed = false)
{
$this->list = array();
foreach($this->tree_data as $parent => $element_array)
{
foreach($element_array as $element)
{
if($include_not_allowed || $element[$this->options['allow_add_name']])
{
$this->Reset();
$list_item = $this->History($element[$this->options['id_name']]);
$list_string = current($list_item);
while(next($list_item))
{
$item = current($list_item);
$list_string .= $this->options['list_delimiter'] . $item;
}
$this->list[$element[$this->options['id_name']]] = $list_string;
}
}
}
return $this->list;
}
//-------------------------------------------------------------------------
/*
* Возвращает ассоциативный массив:
* array(
* id => name
* )
* Используется для построения хронологии(истории) к элементу с переданным id
* */
public function History($id)
{
if(!in_array($id, $this->recursy_protect_array))
{
$this->recursy_protect_array[] = $id;
if(is_array($this->tree_data))
foreach($this->tree_data as $element)
{
if(is_array($element))
foreach($element as $item)
{
if($item[$this->options['id_name']] == $id)
{
$this->History($item[$this->options['id_parent_name']]);
$this->history[$item[$this->options['id_name']]] = $item[$this->options['history_elem_name']];
break;
}
}
}
}
return $this->history;
}
}
/*
$x = array();
$x[0][] = array('id' => 10, 'id_parent' => 0, 'name' => 'Cat 1', 'allow_add' => '1');
$x[0][] = array('id' => 20, 'id_parent' => 0, 'name' => 'Cat 2', 'allow_add' => '1');
$x[0][] = array('id' => 30, 'id_parent' => 0, 'name' => 'Cat 3', 'allow_add' => '1');
$x[10][] = array('id' => 40, 'id_parent' => 10, 'name' => 'Cat 1.1', 'allow_add' => '1');
$x[10][] = array('id' => 50, 'id_parent' => 10, 'name' => 'Cat 1.2', 'allow_add' => '1');
$x[10][] = array('id' => 60, 'id_parent' => 10, 'name' => 'Cat 1.3', 'allow_add' => '1');
$x[20][] = array('id' => 70, 'id_parent' => 20, 'name' => 'Cat 2.1', 'allow_add' => '1');
$x[20][] = array('id' => 80, 'id_parent' => 20, 'name' => 'Cat 2.2', 'allow_add' => '1');
$x[20][] = array('id' => 90, 'id_parent' => 20, 'name' => 'Cat 2.3', 'allow_add' => '1');
$x[30][] = array('id' => 100, 'id_parent' => 30, 'name' => 'Cat 3.1', 'allow_add' => '1');
$x[30][] = array('id' => 110, 'id_parent' => 30, 'name' => 'Cat 3.2', 'allow_add' => '1');
$x[30][] = array('id' => 120, 'id_parent' => 30, 'name' => 'Cat 3.3', 'allow_add' => '0');
$x[120][] = array('id' => 130, 'id_parent' => 120, 'name' => 'Cat 3.3.1', 'allow_add' => '1');
$x[130][] = array('id' => 140, 'id_parent' => 130, 'name' => 'Cat 3.3.1.1', 'allow_add' => '1');
$x[130][] = array('id' => 141, 'id_parent' => 130, 'name' => 'Cat 3.3.1.2', 'allow_add' => '1');
$x[130][] = array('id' => 143, 'id_parent' => 130, 'name' => 'Cat 3.3.1.3', 'allow_add' => '1');
$x[141][] = array('id' => 145, 'id_parent' => 141, 'name' => 'Cat 3.3.1.2.1', 'allow_add' => '1');
$this->load->library('treelib');
$data = array();
$this->treelib->setOptions(
array(
'tree_elem_tmpl' => '<li><b>ID:</b> %s, <b>ID_PARENT:</b> %s <b>NAME:</b> %s</li>',
'tree_start_tmpl' => '<ul>',
'tree_end_tmpl' => '</ul>',
'tree_max_level' => 10,
'cat_tablename' => 'category',
'id_current' => 0
)
);
$this->treelib->setTreeData($x);
//$order = array('id' => 'id', 'id_parent' => 'id_parent', 'name' => 'name');
//$this->treelib->getTreeData($order);
$data['tree'] = $this->treelib->Tree();
$this->treelib->Reset();
$data['history'] = $this->treelib->History(145);
$data['list'] = $this->treelib->FullList(true);
$this->load->view('welcome_message', $data);
*/
?>