<?
function TREE($table_name)
{ if (!is_object($GLOBALS['TREE_'.$table_name])) $GLOBALS['TREE_'.$table_name]=new c_tree($table_name);
  return($GLOBALS['TREE_'.$table_name]);
  return(new c_tree());

}

class c_tree
{   var $table_id ;
    var $table_name ;
    var $arr_section_path ;

    function __construct($table_name)
    {
        $this->table_name=$table_name ;
    }

    function get_arr_parents_name($rec,$options=array())
    {  $arr_parents=array() ; $arr_parents_name=array() ;
       if ($rec['parent'] and isset($this->arr_section_path[$rec['parent']])) $arr_parents_name=$this->arr_section_path[$rec['parent']] ;
       else
       {   foreach($rec as $id=>$value) if (preg_match('/^parent([1-9])/is',$id,$matches) and  $value)
		 if (!$options['to_level'] or $options['to_level']<=$matches[1]) $arr_parents[$matches[1]]=$value  ;
	   if ($options['use_this_id']) $arr_parents[]=$rec['pkey']  ;
	   if (_sizeof($arr_parents))
           { $rec_parent_names=execSQL_row('select pkey,obj_name from '.$this->table_name.' where pkey in ('.implode(',',$arr_parents).')') ;
	 	 foreach($arr_parents as $indx=>$parent_id) $arr_parents_name[$indx]=$rec_parent_names[$parent_id] ;
	   }
           $this->arr_section_path[$rec['parent']]=$arr_parents_name ;
       }
	   return($arr_parents_name) ;
    }

  var $id_root ;
  var $tree ;
  var $name ;
  var $recs ;

  function upload_tree_objects($usl='',$options=array())
  { if (!$usl) $usl='1' ;
  	$order=($options['order'])? $options['order']:'parent,indx'  ;
	$this->recs=execSQL('select * from '.$this->table_name.' where '.$usl.' order by parent,indx',array('debug'=>$options['debug'])) ;

  }

  function create_model($options=array())
  { // первый проход массива записей по объектам - просто создаем объекты и помещаем их в массив this->tree
	$item_class_name=($options['item_class_name'])? $options['item_class_name']:'c_obj' ;
    if (_sizeof($this->recs)) foreach($this->recs as $rec)
    { // по новой системе в конструктор передается вся запись по объекту, а он уже сам сохраняет необходимые ему поля
      $this->tree[$rec['pkey']] = new $item_class_name($rec,$this) ;
      if (!$rec['parent']) $this->id_root=$rec['pkey'] ; // если корень дерева не указан, берем за начало объект, у которого parent=0
    }

    // второй проход массива записей по объектам - проставляем список потомоков у объектов
    if (_sizeof($this->recs)) foreach($this->recs as $rec)  if (isset($this->tree[$rec['parent']])) $this->tree[$rec['parent']]->list_obj[]=$rec['pkey'] ;

    // даем команду корневому элементу дерева проставить номера уровней и отметить используемые элементы каталога
    if (isset($this->tree[$this->id_root])) {   $this->tree['root']=&$this->tree[$this->id_root] ;
                                                $this->tree['root']->update_child_level() ;
                                            }
    else  { echo 'Не найден корень каталога: '.$this->name.' - '.$this->id_root.'<br>' ; trace() ;}
  }

 // возвращает массив кодов дочерних объектов в формате код объекта-класс обеъекта
 // если указать класс объектов, в массиве остнуться только коды объектов указанного класса
 function get_list_child($pkey,$clss=-1,$usl_select='')
 { $list_child=execSQL_row('select pkey,clss from '.$this->table_name.' where parent in('.$pkey.')'.$usl_select) ;
   if (_sizeof($list_child))
   { $list_child_pkeys=implode(',',array_keys($list_child)) ;
     $arr_child=$this->get_list_child($list_child_pkeys,-1,$usl_select) ;
     if (_sizeof($arr_child)) $list_child+=$arr_child ;
   }
   if ($clss!=-1 and _sizeof($list_child)) foreach($list_child as $pkey=>$cur_clss) if ($clss!=$cur_clss) unset($list_child[$pkey]) ;
   return($list_child) ;
 }


  var $parent_matrix=[] ;
  function update_levels_parents($usl='',$options=array())
  { $t1=fixed_time() ; $t_all=fixed_time() ;

    $arr=array() ;
    //if (!$options['from_id']) execSQL_update('update '.$this->table_name.' set parent1=0,parent2=0,parent3=0,parent4=0,parent5=0,parent6=0,parent7=0,parent8=0,parent9=0') ;
    $usl_select=($usl)? 'where '.$usl:'' ;


    $recs=execSQL('select pkey,parent from '.$this->table_name.' '.$usl_select,$options['debug']) ;
    fixed_time($t1,'Получен массив записей в работу ('._sizeof($recs).' строк)') ; $t1=fixed_time() ;
    $this->convert_list_to_tree($recs,$arr) ;
    fixed_time($t1,'Построено дерево объектов') ; $t1=fixed_time() ;
    $this->parent_matrix=[] ;
      //$options['debug'] =0 ;
    if ($options['from_id'])
    {   list($arr2,$from_level)=$this->find_id($options['from_id'],$arr) ;
        //echo 'from_id='.$options['from_id'].' level='.$from_level.'<br>' ;
        //damp_array($arr2,1,-1);
        $from_level++ ;
        if ($arr2!=null and _sizeof($arr2)) $this->update_levels_parents_by_arr_tree($arr2,$from_level,$options) ;
    }
    else $this->update_levels_parents_by_arr_tree($arr,1,$options) ;
    fixed_time($t1,'Построена матрица parents ('._sizeof($this->parent_matrix).' строк)') ; $t1=fixed_time() ;

    //print_2x_arr($this->parent_matrix);
    $temp_table_name='temp_parent_ids_'.ENGINE()->generate_UID() ;
    SETUP()->create_temporary_table($temp_table_name,'(parent INT(11) NOT NULL, PRIMARY KEY (parent),level int(11),parent1 int(11),parent2 int(11),parent3 int(11),parent4 int(11),parent5 int(11),parent6 int(11),parent7 int(11),parent8 int(11),parent9 int(11)) ENGINE=Innodb ');
    if (_sizeof($this->parent_matrix)) foreach($this->parent_matrix as $id=>$rec) multiSQL_insert($temp_table_name)->append($rec) ;
    multiSQL_insert($temp_table_name)->flush() ;
    fixed_time($t1,'Подготовлена временная таблицы матрица parents ('._sizeof($this->parent_matrix).' строк)') ; $t1=fixed_time() ;

    //execSQL('select * from '.$temp_table_name,2) ;
    execSQL_update('update '.$this->table_name.' t1
                    left join '.$temp_table_name.' t2 on t2.parent=t1.parent
                    set t1.level=t2.level+1,
                        t1.parent1=t2.parent1,
                        t1.parent2=t2.parent2,
                        t1.parent3=t2.parent3,
                        t1.parent4=t2.parent4,
                        t1.parent5=t2.parent5,
                        t1.parent6=t2.parent6,
                        t1.parent7=t2.parent7,
                        t1.parent8=t2.parent8,
                        t1.parent9=t2.parent9
                    ',['debug'=>0]) ;
    execSQL_update('update '.$this->table_name.' set enabled=0 where parent1=0 and parent!=0',['debug'=>0]) ;
    execSQL_update('update '.$this->table_name.' set level=1 where parent=0',['debug'=>0]) ;
    fixed_time($t1,'Обновлены parents таблицы  '.$this->table_name.' ') ; $t1=fixed_time() ;
    if ($options['ext_table'])
    {   execSQL_update('update '.$options['ext_table'].' t1
                        left join '.$temp_table_name.' t2 on t2.parent=t1.parent
                        set t1.level=t2.level+1,
                            t1.parent1=t2.parent1,
                            t1.parent2=t2.parent2,
                            t1.parent3=t2.parent3,
                            t1.parent4=t2.parent4,
                            t1.parent5=t2.parent5,
                            t1.parent6=t2.parent6,
                            t1.parent7=t2.parent7,
                            t1.parent8=t2.parent8,
                            t1.parent9=t2.parent9
                        ',['debug'=>2]) ;
        execSQL_update('update '.$options['ext_table'].' set enabled=0 where parent1=0 and parent!=0',['debug'=>0]) ;
        fixed_time($t1,'Обновлены parents таблицы  '.$options['ext_table'].' ') ;
    }

    fixed_time($t_all,'Обновление parents завершено, итого ') ;
  }

  function find_id($id,$arr,$level=1)
  { $res=null ;
    if (isset($arr[$id])) $res=[$arr[$id],$level] ;
    else if (_sizeof($arr))
    {   $level++ ;
        foreach($arr as $child_arr)
        { $res=$this->find_id($id,$child_arr,$level) ;
          if ($res!=null) break ;
        }
    }
    return($res) ;
  }

  function convert_list_to_tree(&$recs,&$arr,$parent=0,$arr_allow_ids=array())
  { if (_sizeof($recs))  foreach($recs as $id=>$rec) if ($rec['parent']==$parent) { $arr[$rec['pkey']]=array() ; unset($recs[$id]) ; }
	if (_sizeof($arr)) foreach ($arr as $child_pkey=>$arr_child_ids) $this->convert_list_to_tree($recs,$arr[$child_pkey],$child_pkey,$arr_allow_ids) ;
  }

  function convert_recs_to_tree(&$recs,&$arr,$parent=0,$arr_allow_ids=array())
	{ if (_sizeof($recs))  foreach($recs as $id=>$rec) if ($rec['parent']==$parent) { $arr[$rec['pkey']]=array('name'=>$rec['obj_name']) ; unset($recs[$id]) ; }
	  if (_sizeof($arr)) foreach ($arr as $child_pkey=>$arr_child_ids) if ($child_pkey!='name') $this->convert_recs_to_tree($recs,$arr[$child_pkey],$child_pkey,$arr_allow_ids) ;
	}

	function convert_tree_to_select_options_arr($arr_tree,&$recs,&$options_list,$parent_name='',$create_name_callback=null,$options=array())
	{  if (_sizeof($arr_tree)) foreach ($arr_tree as $id=>$arr_childs)
	   {   $title=($create_name_callback!=null)? $create_name_callback($recs[$id]):$recs[$id]['obj_name'] ;
	       //$pref=($options['hide_parent_name'])? '.':$parent_name.' / ' ;
	       $pref=($parent_name)? $parent_name.' / ':$parent_name ;
	   	   $options_list[$id]=($pref)? $pref.$title:$title  ;
	   	   $pref_next=($options['hide_parent_name'])? $pref.'.':$options_list[$id] ;
	       if (_sizeof($arr_childs)) $this->convert_tree_to_select_options_arr($arr_childs,$recs,$options_list,$pref_next,$create_name_callback,$options) ;
	   }
	}

	function convert_tree_to_list($arr,&$recs,$name)
     { if (is_array($arr) and _sizeof($arr)) foreach($arr as $id=>$node_info)
       { //var_dump($name) ; echo '<br>' ;
         $node_name=(isset($node_info['name']))? $node_info['name']:'' ;
         $recs[$id]=($name)? $name.' / '.$node_name:$node_name;
         if (isset($node_info['name'])) unset($node_info['name']) ;
         $aa=(isset($recs[$id]))? $recs[$id]:'' ;
         if ($node_info!='' and _sizeof($node_info)) $this->convert_tree_to_list($node_info,$recs,$aa) ;
       }
     }

  // новый релиз функции, в нем нет отдельных SQL запрсов для обновления каждого parent
  // собирается матрица для всех parent, затем одном запросом идет обновление parent1...9 исходной таблицы
  function update_levels_parents_by_arr_tree($arr,$level,$options=array())
  { $arr_lev=[] ;
    for($i=$level+1;$i<=9;$i++) $arr_lev[]='parent'.$i.'=0' ;
     if (_sizeof($arr)) foreach($arr as $id=>$recs)
      { $ids=$this->get_arr_child($recs) ;
        $ids[]=$id ;
        $this->parent_matrix[$id]['level']=$level ;
        $this->parent_matrix[$id]['parent']=$id ;
        //$this->parent_matrix[$id]['parent'.$level]=$id ; // включить есть надо чтобы в parentX указывал на себя
        foreach($ids as $child_id)
        { $this->parent_matrix[$child_id]['parent'.$level]=$id ;
          for($i=$level+1;$i<=9;$i++) $this->parent_matrix[$id]['parent'.$i]=0 ;
        }
        $this->update_levels_parents_by_arr_tree($recs,$level+1,$options) ;
      }
  }

	// возвращается ids дочерних элементов в виде массива
	function get_arr_child($arr_tree)
	  {  $res=[] ;
	     if (_sizeof($arr_tree)) foreach ($arr_tree as $id=>$recs)
	     { $res[]=$id ;
	       $_res=$this->get_arr_child($recs) ;
	       if (_sizeof($_res)) $res=array_merge($res,$_res) ;
	     }
	     return($res) ;
	  }


	function get_arr_parents_names($id,$options=array())
	{ $arr=array() ;
	  $recs=$this->DB_get_list_parents_recs($id) ;
	  //damp_array($recs,1,-1) ;
	  if (_sizeof($recs)) foreach($recs as $level=>$rec) if (!$options['from_level'] or ($options['from_level'] and $level>=$options['from_level']))
	      {
	          if ($options['use_pkey_in_indx']) $arr[$rec['pkey']]=$rec['obj_name'] ;
	          else $arr[]=$rec['obj_name'] ;
        }
	  return($arr) ;
	}

	function get_arr_parents_ids($id)
	{ $arr=array() ;
	  $recs=$this->DB_get_list_parents_recs($id) ;
	  if (_sizeof($recs)) foreach($recs as $rec) $arr[]=$rec['pkey'] ;
	  return($arr) ;
	}

	function DB_get_list_parents_recs($id)
	 {  if (!$id) return(array()) ;

	     $rec=execSQL_van('select t1.pkey as id,t2.pkey as id2,t3.pkey as id3,t4.pkey as id4,t5.pkey as id5,t6.pkey as id6,t7.pkey as id7,t8.pkey as id8,t9.pkey as id9 from '.$this->table_name.' t1
	                       left join '.$this->table_name.' t2 on t2.pkey=t1.parent
	                       left join '.$this->table_name.' t3 on t3.pkey=t2.parent
	                       left join '.$this->table_name.' t4 on t4.pkey=t3.parent
	                       left join '.$this->table_name.' t5 on t5.pkey=t4.parent
	                       left join '.$this->table_name.' t6 on t6.pkey=t5.parent
	                       left join '.$this->table_name.' t7 on t7.pkey=t6.parent
	                       left join '.$this->table_name.' t8 on t8.pkey=t7.parent
	                       left join '.$this->table_name.' t9 on t9.pkey=t8.parent
	                       where t1.pkey in ('.$id.')')    ;
	     $res=array_values($rec) ;
	     if (_sizeof($res))
	     { //foreach($res as $id=>$value) if (!$value) unset($res[$id]) ;
	       $res=array_unique($res) ;
	       $res=array_reverse($res) ;
	       if (!$res[0]) unset($res[0]) ;
	       $recs=execSQL('select * from '.$this->table_name.' where pkey in ('.implode(',',$res).')')    ;
	       if (_sizeof($res)) foreach($res as $level=>$id) $res[$level]=$recs[$id] ;
	     }
	     //print_r($res) ; echo '<br>' ;
	     return($res) ;
	 }

	 // пример вызова
	 // $list_zones=TREE('obj_site_skd')->get_list_strukture('(clss in (600) or (clss=601 and type in (3,4)))',function($rec){return($rec['obj_name'].' ['.$rec['clss'].'/'.$rec['type'].']');}) ;
	 // $list_zones=TREE('obj_site_skd')->get_list_strukture('(clss in (600) or (clss=601 and type in (3,4)))',function($rec){return($rec['obj_name']);}) ;
	 // $list_zones=TREE('obj_site_skd')->get_list_strukture('(clss in (600) or (clss=601 and type in (3,4)))') ;

     function get_list_strukture($usl,$create_name_callback=null,$from_id=1,$options=[])
     {  //trace() ;
         $t1=fixed_time() ;
	     $this->upload_tree_objects($usl,array('debug'=>0)) ;
	     // в процессе создания дерева массив $recs будет очищен
	     $recs=$this->recs ;
	     $parent_id=($options['parent_id'])? $options['parent_id']:0 ;
	     $this->convert_list_to_tree($recs,$arr_tree,$parent_id) ;
	     //damp_array($arr_tree) ;
	     // создаем на основе дерева и записей по объектам иехический список
	     if ($from_id) $this->convert_tree_to_select_options_arr($arr_tree[$from_id],$this->recs,$groups_list,'',$create_name_callback,$options) ;
	     else          $this->convert_tree_to_select_options_arr($arr_tree,$this->recs,$groups_list,'',$create_name_callback,$options) ;
	     //fixed_time($t1,'Построение дерева организаций/подразделений ('._sizeof($this->recs).' элементов)') ;
         return($groups_list) ;
     }



}



?>
