<?php
/**
* @author:  Bunzia Alexander <nifus@mail.ru> <http://www.weblancer.net/users/nifus/>
* @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



?>
