ACC SHELL
<?php
/**
* stara se o ukladani a vypis dat ulozenych ve stromove strukture
* a vraci infromace o predchozich a nasledujicich prvcich
* a vraci drobeckovou navigaci
*
*/
class Relations_model extends Base_model{
public $table = "relations";
protected $relations_model = true; // for recognize if model is extending this
// public $no_bc=false; // kdyz je true tak z drobeckovky vyhodi polozky, ktere maji tag "no_bc"
public $zero_sitemap=true;// kdyz je true tak vrati vsechny uzly vcetne tech, ktere maji nastaveno sitemap na 0
public static $repair_on_insert = true; // if its false, it won't be repaired after insert
/**
* root node.
* Have influence to relations->breadcrumbs() and sites->tag() NOT TO tree()
*/
public $root_relation = array();
//////// Variables for tree() ////////
// limit, order are extended from base_model()
// where is for sure not used
public $offset;
public $range;
public $idr;
public function set_offset($offset) { $this->offset = $offset; return $this; }
public function set_range($range) { $this->range = $range; return $this; }
public function set_root($root) { $this->idr = $root; return $this; }
public function set_zero_sitemap($zero_sitemap) { $this->zero_sitemap = $zero_sitemap; return $this; }
public function __construct(){
parent::__construct();
$this->order = "lft";
$this->set_useful_columns(array('depth','relations.id as idr','parent_id','lft','rgt'));
$this->set_joins("LEFT JOIN relations ON relations.id_".$this->table."=".$this->table.".id");
}
public $last_inserted_id; // obsahuje id posledniho vytvoreneho zaznamu v tabulce relations
private $traversal_lft_rgt; // pomucka pri opravach stromu
/**
* docs in Base_model()
*/
public function new_one($data=NULL,$call_get_one=false){
$result = parent::new_one($data,$call_get_one);
$result->range = $this->range;
$result->offset = $this->offset;
return $result;
}
/**
* docs in Base_model()
*/
public function load($data=array(),$id=NULL,$idr=NULL){
parent::load($data,$id);
if(!empty($data['idr']) && is_numeric($data['idr'])){
$this->idr = $this->a['idr'];
}
if(is_numeric($idr) && $idr){
$this->idr = $idr;
}
return $this;
}
/**
* vraci strom
* @param bool $call_get_one udela/neudělá get_one() na každém prvku
* @return array pole objektů
*/
final public function tree($call_get_one=false){
$cond=array();
$cond[]="relations.table='".$this->table."'";
//if(empty($this->idr)) $this->idr = $this->root_relation['id'];
if(!empty($this->idr)){
$root_rel=$this->get_relation($this->idr);
if(empty($root_rel)){ Tools::flash("Tree() má chybně definován kořen, idr=$this->idr pravděpodobně neexistuje","critical"); return;}
$cond[]="relations.lft>=".$root_rel['lft']."";
$cond[]="relations.rgt<=".$root_rel['rgt']."";
if(!empty($this->offset)){
$cond[]="relations.depth>=" . ($root_rel['depth']+$this->offset);
}
if(!empty($this->range)){
$cond[]="relations.depth<=" . ($root_rel['depth']+$this->offset+$this->range-1);
}
if(!$this->zero_sitemap){
$cond[]=$this->table.".sitemap=1";
}
}else{
Tools::critical("tree() was called without \$idr");
return array();
}
if(!empty($this->where)){
$cond[] = $this->where;
}
$this->set_where(implode("&&",$cond));
return $this->get_all($call_get_one);
}
// hepler method, returning tree of childrens
public function childrens($call_get_one=false) { // offset and range in one
$this->range = 1;
$this->offset = 1;
return $this->tree($call_get_one);
}
/**
* vraci data pro drobeckovou navigaci
*
* @param integer $idr [optional]
* @param boolean $zero_sitemap [optional] false - vynecha polozky, ktere maji nastavene sitemap na 0; true - vrati vse
* @return mixed pokud je co vratit tak array, jinak false
*/
public function breadcrumbs($idr=false,$zero_sitemap=false){
$idr=($idr ? $idr : $this->idr);
$return=array();
if($idr){
$relation=$this->get_relation($idr,false,false);
$bc = $this->new_one()->set_where(" relations.table='".$this->table."' &&
relations.lft<='".$relation['lft']."'
AND relations.rgt>='".$relation['rgt'] . "'" .
(!empty($this->root_relation)?"&& relations.lft > '".$this->root_relation['lft']."' &&
relations.rgt<'".$this->root_relation['rgt']."'":"") .
(!$zero_sitemap ? " AND ".$this->table.".sitemap=1 " : false)
);
$bc->set_order("depth");
return $bc->get_all();
}
return $return;
}
// returns array width idr like keys
public function breadcrumbs_enum(){
foreach($this->breadcrumbs() as $b){
$result[$b->idr] = true;
}
return @$result;
}
public function parent(){
if(!empty($this->a['parent'])){
if($this->a['parent']->idr == $this->a['parent_id']) return $this->a['parent'];
}
if(empty($this->a['parent_id'])){
Tools::debug("relations_model::parent() callet without \$this->a['parent_id']. Probably it's root and returned \$this");
return $this;
}
Tools::log("relations_model::parent() of idr=".$this->a['parent_id']." calling get_one()");
$this->a['parent'] = $this->new_one()->get_one($this->a['parent_id']);
return $this->a['parent'];
}
public function children_count($idr=null){
if(!$idr) $idr = $this->idr;
$c = $this->db->query("select count(id) as c from relations where parent_id='$idr'")->row_array();
return @$c['c'];
}
// returns count of relations related with site of this id
public function relations_count($id_table=null){
if(!$id_table) $id_table = $this->id;
$c = $this->db->query("select count(id) as c from relations where id_$this->table = '$id_table' && `table` = '$this->table'")->row_array();
return @$c['c'];
}
/**
* doplni/opravi nalezitosi potrebne k traverzovani - level, lft, rgt
* i ostatni, pokud nejsou - list
* povinne jsou jen id a parent_id
*
* todo: zredukovat parametry
* todo: dělá chybu pokud se spouští repair tree z constructu
*
* @param boolean $transaction [optional] zda se ma provest ve vlastni transakci
* @param integer $root_idr [optional] kdyz je zadano tak se opravi strom jen od zadaneho id
* @return boolean
*/
final public function repair_tree($transaction=true,$root_idr=false){
$debug=false;
$return=false;
$root_idr=($root_idr ? intval($root_idr) : false);
$root_relation=($root_idr ? $this->get_relation($root_idr,false,false) : false);
$root_lft=(!empty($root_relation) ? $root_relation['lft'] : false);
$root_depth=(!empty($root_relation) ? $root_relation['depth'] : 0);
$query="
# zjisti detaily jednotlivych zaznamu - rozdil je pokud zdroj neni traverzovaci tabulka, ale jina plosna se zdrojovymi daty
SELECT
id, id_".$this->table.",
IFNULL(parent_id,'NULL') AS parent_id,
IFNULL(parent_site_id,'NULL') AS parent_site_id,
list,
'' AS depth,
'' AS lft,
'' AS rgt
FROM relations WHERE `table`='".$this->table."'".(!empty($root_relation) ? "AND `lft`>=".$root_relation['lft']." AND `rgt`<=".$root_relation['rgt'] : false)."
ORDER BY (list),(id)";
// Tools::debug($query);
// echo '<pre>'.$query.'</pre>';
$linear_traversal_structure=$this->prepare_linear_traversal_structure($this->db->query($query)->result_array(),$root_lft,$root_depth);
// samotna oprava
if(is_array($linear_traversal_structure) && count($linear_traversal_structure)){
if($transaction){
$this->db->query('START TRANSACTION');
}
$result=array();
$result['ok']=0;
$result['bad']=0;
$last_inserted_id=array();
foreach($linear_traversal_structure as $item){
$query=false;
$query="
UPDATE relations
SET
parent_site_id=".$item['parent_site_id'].",
list=".$item['list'].",
depth=".$item['depth'].",
lft=".$item['lft'].",
rgt=".$item['rgt']."
WHERE id=".$item['id'].";";
// pocita uspesne/neuspesne updaty
if($this->db->query($query)){
$result['ok']++;
// protoze id predka v tabulce result nemusi byt id predka v odkazovane tabulce (plati pri pocatecnim plneni daty)
$last_inserted=$this->db->query("SELECT LAST_INSERT_ID() AS id")->row_array();
$last_inserted_id[$item['id']]=$last_inserted['id'];
}
else{
$result['bad']++;
break;
}
echo ($debug ? '<hr>' : false);
}
// pokud nastala nejaka chyba tak vrati vse zpet
if($result['bad']){
if($transaction){
$this->db->query('ROLLBACK');
}
}
else{
if($transaction){
$this->db->query('COMMIT');
}
$return=true;
}
}
return $return;
}
/**
* @param array $data array containing id_table, parent_id, list (table should be in $this->table)
*/
public function save_relation($data=NULL,$idr=null){
$this->load($data,NULL,$idr);
$return=null;
// pri insertu vynulluju idr
if($idr==='0'){$this->idr=null;}
if(!empty($this->idr)){
$return=$this->move_relation($this->idr,$this->a['parent_id'],@$this->a['list']);
// k proměnným se přistupuje např takto: $this->a['parent_id']
}else{
$return=$this->insert_relation($this->id,$this->a['parent_id']);
$this->idr=$this->last_inserted_id;
}
if($return){
return $this->idr;
}
return false;
}
/**
* delete relations with all childrens
* working with $this->table.... calling $this->del_{$this->table} method
*
* @param type $idr
* @param type $lang TODO: passing to tree()
* @param type $force TODO: delete all sites in tree, even they are also in other relation
* @return array formated array with results. Example: array{deleted_relations=>10; deleted_table_items=>8; diff=2}
*/
public function del_relations($idr=NULL,$lang=NULL,$force=false){
$result = array('deleted_relations'=>0,'deleted_table_items'=>0); // result init
$this_rel = $this->get_relation($idr);
if(!empty($this_rel)){
$tree = $this->load(NULL,NULL,$idr)->tree();
foreach($tree as $rel){ // go throw the tree
$result['deleted_relations']++;
if($rel->relations_count()<=1){
$del_method_name = "del_".$this->table;
$rel->$del_method_name(); // call for example del_sites()
$result['deleted_table_items']++;
}elseif($force){ // todo: vychytat podmínku... pokud relations_count()==1 neznamená to ještě, že se nenachází v jiné větvi!
}
// in InnoDB delete this node and all childrens
$this->db->query("delete from relations where id='$rel->idr'");
}
$diff = $this_rel['rgt']-$this_rel['lft']+1;
$this->db->query("update relations set lft=(lft-$diff), rgt=(rgt-$diff)
where lft>".$this_rel['lft']." && rgt>".$this_rel['rgt']." && relations.table='$this->table'");
$result['diff'] = $result['deleted_relations'] - $result['deleted_table_items'];
Tools::debug("deleted relation with this result: ".var_export($result,1));
return $result;
}else{
Tools::critical("Delete relation id=$this->idr failed.");
return false;
}
}
/**
* novy uzel stromu
*
* @param integer $id_sites id pridavane polozky (id ze zdrojove tabulky)
* @param integer $parent_id id uzlu predka (id v tabulce relations)
* @param integer $list [optional] poradi null/false/0 - zaradi na konec
* @return boolean
*
* @todo Vlahovic, 22.12.11 15:33: nedela pomoci repair tree, ale jen pomoci sql
*/
public function insert_relation($id_sites,$parent_id=null,$list=false){
$return=false;
$res=array();
$list=intval($list);
$lid=false;
// vytvoreni rootu
if(!$parent_id){
$c=$this->db->query(" # select in insert_relation()
SELECT COUNT(*) AS count FROM relations WHERE `table`='".$this->table."';")->row_array();
if(!$c['count']){
$query="# vytvoreni rootu
INSERT INTO relations
(id_".$this->table.",`table`,list,depth,lft,rgt) VALUES
(".$id_sites.",'".$this->table."',1,0,1,2);";
if($this->db->query($query)){
$lid=$this->db->insert_id();
$return=($lid ? true : false);
}
}
}
// jakykoliv zanoreny uzel
else{
$parent=$this->get_relation($parent_id,false,false);
$transaction=$this->db->query("START TRANSACTION");
if($transaction && !empty($parent)){
// vlozi na prvni pozici v dane vetvi
if($list){
$query="# uvolni misto pro novy uzel na konkretni pozici
UPDATE `relations` SET `list`=(`list`+1)
WHERE `table`='".$this->table."' AND `parent_id`=".$parent_id." AND `list`>=".$list;
$res[]=$this->db->query($query);
}
else{
$list=($this->children_count($parent_id))+1;
}
$query="# vytvori novy uzel
INSERT INTO relations
(id_".$this->table.",parent_id,parent_site_id,`table`,list) VALUES
(".$id_sites.",".$parent['id'].",".$parent['id_'.$this->table].",'".$this->table."',".$list.")";
$res[]=$this->db->query($query);
$res[]=$lid=$this->db->insert_id();
if(self::$repair_on_insert){
$res[]=$this->repair_tree(false);
}
// ukonceni transakce
if(in_array(false, $res)){
$this->db->query("ROLLBACK");
}
else{
$return=true;
$return=$this->db->query("COMMIT");
}
}
}
if($return){
$this->last_inserted_id=$lid;
}
else{
$this->last_inserted_id=false;
}
return $return;
}
/**
* presunuti uzlu
*
* @param integer $idr presouvany uzel
* @param type $parent_id novy rodic
* @param integer $list [optional] poradi null/false/0 - zaradi na konec
* @return boolean
*/
final public function move_relation($idr,$parent_id,$list=false){
if(empty($parent_id)){
Tools::debug("move_relation() with no parent_id was called. If you were saving root, it's ok");
return true;
}
$debug=false;
$return=false;
$res=array();
$list=intval($list);
$actual=$this->get_relation($idr,false,false);
$actual_parent=$this->get_relation($actual['parent_id'],false,false);
$parent=$this->get_relation($parent_id,false,false);
$transaction=$this->db->query("START TRANSACTION");
if($transaction && !empty($parent) && !empty($actual)){
// pripravi konkreni umisteni na vetvi
if($list){
$query="# uvolni misto pro novy uzel na konkretni pozici
UPDATE `relations` SET `list`=(`list`+1) WHERE `table`='".$this->table."' AND `parent_id`=".$parent_id." AND `list`>=".$list;
$res['create_free_place']=$this->db->query($query);
}
// umisti na konec vetve
else{
$list=($this->children_count($parent_id))+1;
}
// presune uzel na nove misto
$query="# presune uzel
UPDATE `relations` SET `parent_id`=".$parent_id.",`list`=".$list." WHERE `id`=".$idr.";";
$res['move_node']=$this->db->query($query);
// opravi cely strom, pokud jeden z rodicu je root
if(!$actual_parent['depth'] || !$parent['depth']){
$res['repair_tree_1']=$this->repair_tree(false);
echo ($debug ? 'repair_tree 1' : false);
}
// pokud jsou stejni
elseif($actual_parent['id']==$parent['id']){
$res['repair_tree_2']=$this->repair_tree(false, $parent['id']);
echo ($debug ? 'repair_tree 2 > root_idr: '.$parent['id'] : false);
}
// stavajici rodic je potomkem nastavacijiho
elseif($parent['lft']<$actual_parent['lft'] && $parent['rgt']>$actual_parent['rgt']){
$res['repair_tree_3']=$this->repair_tree(false, $parent['id']);
echo ($debug ? 'repair_tree 3' : false);
}
// stavajici rodic je predkem nastavacijiho
elseif($parent['lft']>$actual_parent['lft'] && $parent['rgt']<$actual_parent['rgt']){
$res['repair_tree_4']=$this->repair_tree(false, $actual_parent['id']);
echo ($debug ? 'repair_tree 4' : false);
}
// rodice k sobe nemaji vztah :-) opravi se strom od prvniho spolecneho predka
else{
$res['common_node']=$common_node=$this->common_node($parent['id'],$actual_parent['id']);
$res['repair_tree_5']=$this->repair_tree(false, $common_node);
echo ($debug ? 'repair_tree 5' : false);
}
if($debug){
exit('<hr>exit in file '.__FILE__.', line '.__LINE__);
}
// ukonceni transakce
if(in_array(false, $res)){
foreach($res as $err => $value){
if(!$value){
Tools::log('error during moving node - '.$err.' (file: '.__FILE__.'; line: '.__LINE__.')');
}
}
$this->db->query("ROLLBACK");
}
else{
$return=($this->db->query("COMMIT") ? true : false);
}
}
return $return;
}
/**
* najde prvni spolecny uzel dvou zadanych
*
* @param integer $node1
* @param integer $node2
* @return integer id spolecneho predka, v pripade nejakeho neuspechu false
*/
public function common_node($node1, $node2){
$return=false;
$index=false;
$i=false;
$bc1=false;
$bc2=false;
$node1=intval($node1);
$node2=intval($node2);
if($node1 && $node2){
$bc1=$this->breadcrumbs($node1,true);
$bc2=$this->breadcrumbs($node2,true);
foreach($bc1 as $i => $o){
$o;
if($o->idr == $bc2[$i]->idr){
$index=$i;
}
else{
break;
}
}
if($index!==false){
$return=$bc1[$index]->idr;
}
}
return $return;
}
/**
* * from relation by id_sites
* nedava moc smysl, protoze id_sites muze byt v tabulce relations nekolikrat
*
* nasel jsem pouziti pri unit testech (Vlahovic) - nemazat, ale ani nepouzivat jinde
*/
public function get_relation_by_site($id,$table=false){
if(!$id) return false;
$table=($table ? $table : $this->table);
return $this->db->query(" # select in get_relation_by_site();
SELECT * FROM relations WHERE `table`='".$table."' AND id_".$table."='".$id."'")->row_array();
}
/** return lft,rgt,depth from relation and set $this->id
*
* @param integer $idr
* @param string $table
* @param boolean $load
* @return array
*/
public function get_relation($idr=null,$table=false,$load=true){
$table = ($table ? $table : $this->table);
$idr = ($idr ? $idr : $this->idr);
if($idr){
$data = $this->db->query(" # get relation
SELECT id,lft,rgt,depth,id_".$table.",parent_id,list FROM relations WHERE relations.id=".$idr)->row_array();
if($load){
$this->load(NULL,@$data['id_'.$table],$idr);
foreach($data as $i=>$d){
$this->a[$i] = $d;
}
} // set $this->id
return $data;
}else{
Tools::debug("relations_model::get_relation() get no parameter \$idr. This might be a problem!");
return false;
}
}
public function relations_join(){
return 'LEFT JOIN '.$this->table.' ON '.$this->table.'.id=relations.id_'.$this->table;
}
/**
* ze stromove strukturovaneho pole udela linearni
*
* @param array $tree
* @return mixed pokud nedostane na vstupu pole pak vrati false jinak array
*/
protected function tree_to_linear($tree){
$return=array();
if(is_array($tree)){
foreach($tree as $item){
// potomky si schovam, abych je pripojil za rodice
$childrens=false;
if(isset($item['childrens']) && is_array($item['childrens']) && count($item['childrens'])){
$childrens=$item['childrens'];
unset($item['childrens']);
}
// rodic
$return[]=$item;
// pripojeni potomku
if($childrens!==false){
$return=array_merge($return,$this->tree_to_linear($childrens));
}
}
return $return;
}
return false;
}
/**
* dodane pole doplni o polozky potrebne k traverzovani
*
* @param array $tree_items
* @param integer $first_lft [optional]
* @param integer $first_depth [optional]
* @return array
*/
protected function create_traversal_structure($tree_items,$first_lft=false,$first_depth=0){
$return=$this->create_tree_structure($tree_items);
$return=$this->add_depth_and_list_to_tree_structure($return,$first_depth);
$this->traversal_lft_rgt=($first_lft ? $first_lft : 1);
$return=$this->add_lft_rgt_to_tree_structure($return);
return $return;
}
/**
* do pole se stromovou strukturou ke kazdemu prvku doplni informaci o levem a pravem prvku
* podminkou je aby vstupni pole melo jen jeden korenovy prvek
*
* @param array $tree
* @return mixed pokud vstupni pole nesplnuje podminky pak vraci false jinak array
*/
private function add_lft_rgt_to_tree_structure($tree){
$return=array();
if(is_array($tree)){
foreach($tree as $index => $arr){
$arr['lft']=$this->traversal_lft_rgt;
$this->traversal_lft_rgt++;
// neni koncovym bodem
if(isset($arr['childrens']) && is_array($arr['childrens']) && count($arr['childrens'])){
$arr['childrens']=$this->add_lft_rgt_to_tree_structure($arr['childrens']);
}
$arr['rgt']=$this->traversal_lft_rgt;
$return[$index]=$arr;
$this->traversal_lft_rgt++;
}
return $return;
}
return false;
}
/**
* do pole doplni informaci o hloube zanoreni jednotlivych prvku
*
* @param array $tree
* @param integer $depth [optional]
* @return mixed pokud nedostane ke zpracovani pole pak vrati false, jinak array
*/
private function add_depth_and_list_to_tree_structure($tree, $depth=0){
$return=array();
$list=1;
if(is_array($tree) && count($tree)){
foreach($tree as $index => $arr){
$arr['depth']=$depth;
$arr['list']=$list;
if(isset($arr['childrens']) && is_array($arr['childrens']) && count($arr['childrens'])){
$arr['childrens']=$this->add_depth_and_list_to_tree_structure($arr['childrens'],$depth+1);
}
$return[$index]=$arr;
$list++;
}
return $return;
}
return false;
}
/**
* vytvori stromovou strukturu z pole kde kazda polozka ma svoje id a id predka
*
* @param array $tree_items
* @return array
*/
private function create_tree_structure($tree_items){
$return=array();
if(is_array($tree_items) && count($tree_items)){
foreach($tree_items as $index => $tree_item){
// spojuje po parech
if(isset($tree_items[$tree_item['parent_id']])){
// aby se neprepisovalo novejsim zaznamem
$return[$tree_item['parent_id']]=(!isset($return[$tree_item['parent_id']]) ? $tree_items[$tree_item['parent_id']] : $return[$tree_item['parent_id']]);
if(!isset($return[$tree_item['parent_id']]['childrens'])){
$return[$tree_item['parent_id']]['childrens']=array();
}
// doplni poradi korenoveho prvku
if(!$return[$tree_item['parent_id']]['list']){
$return[$tree_item['parent_id']]['list']=1;
}
// spoji do pole s predkem
$return[$tree_item['parent_id']]['childrens'][$index]=$tree_item;
}
}
}
if(count($return)>1){
$return=$this->create_tree_structure($return);
}
return $return;
}
/**
* pripravi pole s daty traverzovaneho stromu
*
* @param array $tree_items
* @param integer $root_lft lft hodnota korenove polozky
* @param integer $root_depth zanoreni korenove polozky
* @return array
*/
protected function prepare_linear_traversal_structure($tree_items,$root_lft,$root_depth){
$tree_items_id_index=array();
$linear_traversal_structure=array();
if(is_array($tree_items) && count($tree_items)){
foreach($tree_items as $tree_item){
$tree_items_id_index[$tree_item['id']]=$tree_item;
}
$tree_traversal_structure=$this->create_traversal_structure($tree_items_id_index,$root_lft,$root_depth);
// echo '<pre>'.print_r($tree_traversal_structure,true).'</pre>';
$linear_traversal_structure=$this->tree_to_linear($tree_traversal_structure);
}
return $linear_traversal_structure;
}
}
?>
ACC SHELL 2018