* @copyright: Copyright (c) 2010, Bunzia Alexander * @version: 1.0 * @license: http://www.gnu.org/copyleft/gpl.html GNU/GPL */ /** * Базовый класс для управления ns списками * Класс формирования запросов к ns-таблице */ class ns_tree_q extends query{ private $id='c_id'; public $left = 'c_left'; public $right = 'c_right'; public $level = 'c_level'; public function __construct($table_name,$alias='c') { global $MAIN_DB; $this -> set_table( $table_name ); $this -> set_alias($alias); } // выбираем запись по уникальному ключу id public function where_id($v){ $v = intval($v); if ( empty($v) ){ return FALSE; } return $this -> set_where( $this -> alias.'.c_id',$v); } // выбираем записи уровня v public function where_level($v){ $v = intval($v); if ( empty($v) ){ return false; } return $this -> set_where( $this -> alias.'.c_level',$v); } // выбираем записи в которых столбец k больше значения v public function where_more($k,$v){ if ( empty($k) ){ return false; } return $this -> set_anothe_where( $this -> alias.'.'.$k.'>'.$v); } public function where_down($v){ $v = intval($v); if ( empty($v) ){ return FALSE; } list($l,$r,$level) = ns_tree::get_service_id($v,$this -> table,$this -> alias); $this -> set_anothe_where( $this -> alias.'.c_left>='.$l); $this -> set_anothe_where( $this -> alias.'.c_right<='.$r); $this -> set_anothe_where( $this -> alias.'.c_level>'.$level); return true; } // вверх по дереву // получим всё что ниже уровня категории v public function where_up($v){ $v = intval($v); if ( empty($v) ){ return FALSE; } list($l,$r,$level) = ns_tree::get_service_id($v,$this -> table,$this -> alias); $this -> set_anothe_where( $this -> alias.'.c_left<='.$l); $this -> set_anothe_where( $this -> alias.'.c_right>='.$r); $this -> set_anothe_where( $this -> alias.'.c_level!=0'); return true; } // выбираем записи в которых столбец k меньше значения v public function where_less($k,$v){ if ( empty($k) ){ return false; } return $this -> set_anothe_where( $this -> alias.'.'.$k.'<'.$v); } public function order_left($v){ return $this ->set_orderby($this -> alias.'.c_left',$v); } } //end ns_tree_q /************ * @project cms * Класс управления записями в ns-таблице *************/ class ns_tree extends objects { // в потомках нужно переопределить public $table = 'system_category'; public $alias ='c'; public $main_key =0; // перенести запись в другую ветку public $parent_id =false; static $cache = array(); /************ * @project cms * Вызывается только при вставки новой записи в таблицу * используется внутри класса *************/ protected function __construct( $array=array(),$table,$alias ){ $this -> table = $table; $this -> alias = $alias; $this -> info = $array; $this -> main_key = $array['c_id']; } /********************************** * @project cms * Загружаем id для работы с ним * @var * id -номер записи из ns-таблицы * table/alias - данные талицы * * @return объект для работы с записью таюлицы ************************************/ static function load_id($id,$table,$alias){ $n = new ns_tree_q( $table,$alias); if ( false==$n -> where_id($id) ){ return false; } $n -> get('*',false); $row = $n -> row(); if ( false===$row ){ return false; } return new ns_tree($row,$table,$alias); } /********************************** * @project cms * создаём новый элемент ns списка * @var * id -номер записи из ns-таблицы * table/alias - данные талицы * * @return объект для работы с записью таюлицы ************************************/ static function create($table,$alias){ return new ns_tree(array('c_id'=>0),$table,$alias); } public function error(){ return $this -> last_error; } public function get($k){ if ( isset($this -> info[$k]) ){ return $this -> info[$k]; } sys_error(ERROR_500, sprintf(A_NOT_VAR,$k)); } public function set($k,$v){ if ( method_exists($this,'set_'.$k) ){ $f = 'set_'.$k; return $this -> $f($v); } $this -> set_info[$k] = $v; return true; } protected function set_c_name($v){ if ( empty($v) ){ $this -> make_error('empty_name'); return false; } $this -> set_info['c_name']=$v; return true; } /********************************** * @project cms * переносим запись в новую ветку * @var * v -номер записи из ns-таблицы * ************************************/ public function set_c_parent_id($v){ $v = intval($v); // родитель не может быть пустым if ( empty($v) ){ $this -> make_error(E_NSTREE_BAD_PARENT); return false; } $this -> parent_id = $v; return true; } /*** * GET ***/ public function id(){ return $this -> info['c_id']; } /********************************** * @project cms * вставляем новую ns запись * ************************************/ function insert(){ global $MAIN_DB; // запись должна имеь родителя if ( empty($this -> parent_id) ){ sys_error('500',E_NSTREE_BAD_PARENT); } if ( !empty($this -> last_error ) ){ return false; } if ( sizeof($this -> set_info)==0 ){ sys_error('500',A_EMPTY_ARRAY); } // получаем данные касающиеся ID list($left_id, $right_id, $level) = ns_tree::get_service_id($this -> parent_id,$this -> table,$this -> alias); $param = array(); // увеличиваем правое смещение и уровень глубины $this -> set('c_left',$right_id); $this -> set('c_right',($right_id+1) ); $this -> set('c_level',($level+1) ); // creating a place for the record being inserted $sql = ' UPDATE '.$this -> table.' SET c_left=IF(c_left>'.$right_id.',c_left+2,c_left), c_right=IF(c_right>='.$right_id.',c_right+2,c_right) WHERE c_right>='.$right_id; $MAIN_DB -> query($sql); $sql = array(); foreach( $this -> set_info as $k=>$v ){ $sql[]="`$k`='".sys_in_sql($v)."'"; } $sql = " INSERT ".$this -> table." SET ".implode(',',$sql)." "; if ( !empty($this -> debug) ){ die($sql); } $MAIN_DB -> query($sql); return $MAIN_DB -> insert_id(); } /********************************** * @project cms * удаляем текущую ns запись * ************************************/ function delete(){ global $MAIN_DB; list($left_id, $right_id, $level) = ns_tree::get_service_id($this -> main_key,$this -> table,$this -> alias); // корневой элемент удалить низяяя if ($level==0){ return FALSE; } $sql = 'DELETE FROM '.$this -> table." WHERE c_id=".$this -> main_key; $MAIN_DB -> query($sql); $sql = 'UPDATE '.$this -> table.' SET c_left=IF(c_left BETWEEN '.$left_id.' AND '.$right_id.',c_left-1,c_left), c_right=IF(c_right BETWEEN '.$left_id.' AND '.$right_id.',c_right-1,c_right), c_level=IF(c_left BETWEEN '.$left_id.' AND '.$right_id.',c_level-1,c_level), c_left=IF(c_left>'.$right_id.',c_left-2,c_left), c_right=IF(c_right>'.$right_id.',c_right-2,c_right) WHERE c_right>'.$left_id; $MAIN_DB -> query($sql); return true; } function delete_all() { global $MAIN_DB; $array_cat = array(); list($left_id, $right_id, $level) = ns_tree::get_service_id($this -> main_key,$this -> table,$this -> alias); if ( empty($left_id) || empty($right_id) ){ return FALSE; } $sql = 'SELECT c_id FROM '.$this -> table.' WHERE c_left BETWEEN '.$left_id.' AND '.$right_id; $res = $MAIN_DB -> query($sql); while( list($c_id) = $MAIN_DB -> fetch_array($res) ) { $array_cat[]=$c_id; } $sql = 'DELETE FROM '.$this -> table.' WHERE c_left BETWEEN '.$left_id.' AND '.$right_id; $MAIN_DB -> query($sql); // Clearing blank spaces in a tree $delta_id = ($right_id - $left_id)+1; $sql= ' UPDATE '.$this -> table.' SET c_left=IF(c_left>'.$left_id.',c_left-'.$delta_id.',c_left),c_right=IF(c_right>'.$left_id.',c_right-'.$delta_id.',c_right) WHERE c_right>'.$right_id; $MAIN_DB -> query($sql); return $array_cat; } /********************************** * @project cms * обновляем текущую ns запись * ************************************/ function update(){ global $MAIN_DB; if ( false===$this -> main_key ){ sys_error(ERROR_500,A_EMPTY_ID); } if ( !empty($this -> last_error ) ){ return false; } if ( sizeof($this -> set_info)==0 ){ sys_error(ERROR_500,A_EMPTY_ARRAY); } $sql = array(); foreach( $this -> set_info as $k=>$v ){ $this -> info[$k]=$v; $sql[]="`$k`='".sys_in_sql($v)."'"; } $sql = " UPDATE ".$this -> table." SET ".implode(',',$sql)." WHERE c_id='".$this -> main_key."'"; if ( !empty($this -> debug) ){ die($sql); } $MAIN_DB -> query($sql); // переносим ветку if ( !empty($this -> parent_id) ){ $this -> move_all($this -> main_key,$this -> parent_id); } return true; } // переносим дерево id к новому родителю new_parent_id public function move_all($id, $new_parent_id) { global $MAIN_DB; if ($id==$new_parent_id) { sys_error(ERROR_500); } list($leftId, $rightId, $level) = ns_tree::get_service_id($id,$this -> table,$this -> alias); if (!$leftId) { sys_error(ERROR_500); } // получаем родительский $c_parent_id = ns_tree::get_parent($id,$this -> table,$this -> alias); if ($c_parent_id == $new_parent_id){ return false; } list($leftIdP, $rightIdP, $levelP) = ns_tree::get_service_id($new_parent_id,$this -> table,$this -> alias); if (FALSE === $node_info) { sys_error('500'); } if ($leftIdP < $leftId && $rightIdP > $rightId && $levelP < $level - 1) { $sql = 'UPDATE ' . $this->table . ' SET c_level = CASE WHEN c_left BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN c_level'.sprintf('%+d', -($level-1)+$levelP) . ' ELSE c_level END, c_right = CASE WHEN c_right BETWEEN ' . ($rightId+1) . ' AND ' . ($rightIdP-1) . ' THEN c_right-' . ($rightId-$leftId+1) . ' ' . 'WHEN c_left BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN c_right+' . ((($rightIdP-$rightId-$level+$levelP)/2)*2+$level-$levelP-1) . ' ELSE c_right END, c_left = CASE WHEN c_left BETWEEN ' . ($rightId+1) . ' AND ' . ($rightIdP-1) . ' THEN c_left-' . ($rightId-$leftId+1) . ' ' . 'WHEN c_left BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN c_left+' . ((($rightIdP-$rightId-$level+$levelP)/2)*2+$level-$levelP-1) . ' ELSE c_left END ' . 'WHERE c_left BETWEEN ' . ($leftIdP+1) . ' AND ' . ($rightIdP-1); } elseif ($leftIdP < $leftId) { $sql = 'UPDATE ' . $this->table . ' SET c_level = CASE WHEN c_left BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN c_level '.sprintf('%+d', -($level-1)+$levelP) . ' ELSE c_level END, c_left = CASE WHEN c_left BETWEEN ' . $rightIdP . ' AND ' . ($leftId-1) . ' THEN c_left+' . ($rightId-$leftId+1) . ' ' . 'WHEN c_left BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN c_left-' . ($leftId-$rightIdP) . ' ELSE c_left END, c_right = CASE WHEN c_right BETWEEN ' . $rightIdP . ' AND ' . $leftId . ' THEN c_right+' . ($rightId-$leftId+1) . ' ' . 'WHEN c_right BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN c_right-' . ($leftId-$rightIdP) . ' ELSE c_right END ' . 'WHERE (c_left BETWEEN ' . $leftIdP . ' AND ' . $rightId. ' ' . 'OR c_right BETWEEN ' . $leftIdP . ' AND ' . $rightId . ')'; } else { $sql = 'UPDATE ' . $this->table . ' SET c_level = CASE WHEN c_left BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN c_level '.sprintf('%+d', -($level-1)+$levelP) . ' ELSE c_level END, c_left = CASE WHEN c_left BETWEEN ' . $rightId . ' AND ' . $rightIdP . ' THEN c_left-' . ($rightId-$leftId+1) . ' ' . 'WHEN c_left BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN c_left+' . ($rightIdP-1-$rightId) . ' ELSE c_left END, c_right = CASE WHEN c_right BETWEEN ' . ($rightId+1) . ' AND ' . ($rightIdP-1) . ' THEN c_right-' . ($rightId-$leftId+1) . ' ' . 'WHEN c_right BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN c_right+' . ($rightIdP-1-$rightId) . ' ELSE c_right END ' . 'WHERE (c_left BETWEEN ' . $leftId . ' AND ' . $rightIdP . ' ' . 'OR c_right BETWEEN ' . $leftId . ' AND ' . $rightIdP . ')'; } $MAIN_DB -> query($sql); return TRUE; } // --- меняем местами 2 ветки дерева одного уровня public function order_tree($id,$new_id) { global $MAIN_DB; // --- элемент который будет перемещать $node_info = ns_tree::get_service_id($id,$this -> table,$this -> alias); if (FALSE === $node_info) { return FALSE; } list($left_id, $right_id, $level) = $node_info; $count = $right_id - $left_id+1; // --- размер дерева // --- элемент за которым будет размещён перемещаемый элемент $node_info = ns_tree::get_service_id($new_id,$this -> table,$this -> alias); if (FALSE === $node_info) { return FALSE; } list($left_id_p, $right_id_p, $level_p) = $node_info; if ( ($right_id_p+1)==$left_id ) { // --- переносить некуда return FALSE; } if ( $left_id < $left_id_p ) { $left_smech = ($right_id_p - $right_id); // --- переносим вправо $sql = 'UPDATE '.$this -> table.' SET c_left=IF(c_left BETWEEN '.$left_id.' AND '.$right_id.',c_left+'.$left_smech.', IF(c_left BETWEEN '.($right_id+1).' AND '.$right_id_p.',c_left-'.($count).',c_left)), c_right=IF(c_right BETWEEN '.$left_id.' AND '.$right_id.',c_right+'.$left_smech.', IF(c_right BETWEEN '.($right_id+1).' AND '.$right_id_p.',c_right-'.($count).',c_right) )'; } else { $left_smech = ($left_id - $left_id_p)-1; // --- переносим влево $sql = 'UPDATE '.$this -> table.' SET c_left=IF(c_left BETWEEN '.$left_id.' AND '.$right_id.',c_left-'.$left_smech.', IF(c_left BETWEEN '.($left_id_p+1).' AND '.($left_id-1).',c_left+'.($count).',c_left)), c_right=IF(c_right BETWEEN '.$left_id.' AND '.$right_id.',c_right-'.$left_smech.', IF(c_right BETWEEN '.($left_id_p+1).' AND '.($left_id-1).',c_right+'.($count).',c_right) )'; } $MAIN_DB -> query($sql); } // получаем следующий элемент в текущей ветке public function get_next($id){ $node_info = ns_tree::get_service_id($id); if ( false===$node_info ){ return false; } list($c_left,$c_right,$c_level) = $node_info; return $row; } // получаем предыдущий элемент в текущей ветке static function get_prev($id){ $n = new ns_tree_q( $table,$alias); if ( false===$n -> where_id($id) ){ return false; } $n -> get('c_left,c_right,c_level',false); $row = $n -> row(); if ( false===$row ){ return false; } return $row; } // служебная инфорация о id static function get_service_id($id,$table,$alias){ $n = new ns_tree_q( $table,$alias); if ( false===$n -> where_id($id) ){ return false; } $n -> get('c_left,c_right,c_level',false); $row = $n -> row(); if ( false===$row ){ return false; } return $row; } static function get_parent($id,$table,$alias){ if ( empty($id) ){ return false; } list($l,$r,$level) = ns_tree::get_service_id($id,$table,$alias); if ( empty($l) ){ return false; } $level--; $p = new ns_tree_q($table,$alias); $p -> where_less('c_left',$l); $p -> where_more('c_right',$r); $p -> where_level($level); $p -> get('c_id'); list($c_id) = $p -> row(); if ( false==$c_id ){ return false; } return $c_id; } // найдём все дочерние элементы static function get_childs($id,$table,$alias){ if ( empty($id) ){ return false; } list($l,$r,$level) = ns_tree::get_service_id($id,$table,$alias); if ( empty($l) ){ return false; } //$p -> where_less('c_left',$l); // $p -> where_more('c_right',$r); $p -> where($level); $p -> get('c_id'); list($c_id) = $p -> row(); if ( false==$c_id ){ return false; } return $c_id; } function make_error($msg){ $this -> last_error = $msg; } function set_debug($flag){ $this -> debug = $flag; } static function replace($id,$new_id,$table) { global $MAIN_DB; // --- элемент который будет перемещать $node_info = ns_tree::get_service_id($id,$table,'a'); if (FALSE === $node_info) { return FALSE; } list($left_id, $right_id, $level) = $node_info; $count = $right_id - $left_id+1; // --- размер дерева // --- элемент за которым будет размещён перемещаемый элемент $node_info = ns_tree::get_service_id($new_id,$table,'a'); if (FALSE === $node_info) { return FALSE; } list($left_id_p, $right_id_p, $level_p) = $node_info; if ( ($right_id_p+1)==$left_id ) { // --- переносить некуда return FALSE; } if ( $left_id < $left_id_p ) { $left_smech = ($right_id_p - $right_id); // --- переносим вправо $sql = 'UPDATE '.$table.' SET c_left=IF(c_left BETWEEN '.$left_id.' AND '.$right_id.',c_left+'.$left_smech.', IF(c_left BETWEEN '.($right_id+1).' AND '.$right_id_p.',c_left-'.($count).',c_left)), c_right=IF(c_right BETWEEN '.$left_id.' AND '.$right_id.',c_right+'.$left_smech.', IF(c_right BETWEEN '.($right_id+1).' AND '.$right_id_p.',c_right-'.($count).',c_right) )'; } else { $left_smech = ($left_id - $left_id_p)-1; // --- переносим влево $sql = 'UPDATE '.$table.' SET c_left=IF(c_left BETWEEN '.$left_id.' AND '.$right_id.',c_left-'.$left_smech.', IF(c_left BETWEEN '.($left_id_p+1).' AND '.($left_id-1).',c_left+'.($count).',c_left)), c_right=IF(c_right BETWEEN '.$left_id.' AND '.$right_id.',c_right-'.$left_smech.', IF(c_right BETWEEN '.($left_id_p+1).' AND '.($left_id-1).',c_right+'.($count).',c_right) )'; } $MAIN_DB -> query($sql); } }//end class ?>