ILIAS  eassessment Revision 61809
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.ilTree.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
4 define("IL_LAST_NODE", -2);
5 define("IL_FIRST_NODE", -1);
6 
21 class ilTree
22 {
28  var $ilias;
29 
30 
36  var $log;
37 
43  var $root_id;
44 
50  var $tree_id;
51 
58 
65 
72 
78  var $ref_pk;
79 
85  var $obj_pk;
86 
92  var $tree_pk;
93 
118  var $gap;
119 
120  protected $depth_cache = array();
121  protected $parent_cache = array();
122 
123 
130  function ilTree($a_tree_id, $a_root_id = 0)
131  {
132  global $ilDB,$ilErr,$ilias,$ilLog;
133 
134  // set db & error handler
135  $this->ilDB = $ilDB;
136 
137  if (!isset($ilErr))
138  {
139  $ilErr = new ilErrorHandling();
140  $ilErr->setErrorHandling(PEAR_ERROR_CALLBACK,array($ilErr,'errorHandler'));
141  }
142  else
143  {
144  $this->ilErr = $ilErr;
145  }
146 
147  $this->lang_code = "en";
148 
149  if (!isset($a_tree_id) or (func_num_args() == 0) )
150  {
151  $this->ilErr->raiseError(get_class($this)."::Constructor(): No tree_id given!",$this->ilErr->WARNING);
152  }
153 
154  if (func_num_args() > 2)
155  {
156  $this->ilErr->raiseError(get_class($this)."::Constructor(): Wrong parameter count!",$this->ilErr->WARNING);
157  }
158 
159  // CREATE LOGGER INSTANCE
160  $this->log = $ilLog;
161 
162  //init variables
163  if (empty($a_root_id))
164  {
165  $a_root_id = ROOT_FOLDER_ID;
166  }
167 
168  $this->tree_id = $a_tree_id;
169  $this->root_id = $a_root_id;
170  $this->table_tree = 'tree';
171  $this->table_obj_data = 'object_data';
172  $this->table_obj_reference = 'object_reference';
173  $this->ref_pk = 'ref_id';
174  $this->obj_pk = 'obj_id';
175  $this->tree_pk = 'tree';
176 
177  $this->use_cache = true;
178 
179  // If cache is activated, cache object translations to improve performance
180  $this->translation_cache = array();
181  $this->parent_type_cache = array();
182 
183  // By default, we create gaps in the tree sequence numbering for 50 nodes
184  $this->gap = 50;
185  }
186 
190  public function useCache($a_use = true)
191  {
192  $this->use_cache = $a_use;
193  }
194 
199  public function isCacheUsed()
200  {
201  return $this->__isMainTree() and $this->use_cache;
202  }
203 
208  function initLangCode()
209  {
210  global $ilUser;
211 
212  // lang_code is only required in $this->fetchnodedata
213  if (!is_object($ilUser))
214  {
215  $this->lang_code = "en";
216  }
217  else
218  {
219  $this->lang_code = $ilUser->getCurrentLanguage();
220  }
221  }
222 
223 
238  function setTableNames($a_table_tree,$a_table_obj_data,$a_table_obj_reference = "")
239  {
240  if (!isset($a_table_tree) or !isset($a_table_obj_data))
241  {
242  $this->ilErr->raiseError(get_class($this)."::setTableNames(): Missing parameter! ".
243  "tree table: ".$a_table_tree." object data table: ".$a_table_obj_data,$this->ilErr->WARNING);
244  }
245 
246  $this->table_tree = $a_table_tree;
247  $this->table_obj_data = $a_table_obj_data;
248  $this->table_obj_reference = $a_table_obj_reference;
249 
250  return true;
251  }
252 
259  function setReferenceTablePK($a_column_name)
260  {
261  if (!isset($a_column_name))
262  {
263  $this->ilErr->raiseError(get_class($this)."::setReferenceTablePK(): No column name given!",$this->ilErr->WARNING);
264  }
265 
266  $this->ref_pk = $a_column_name;
267  return true;
268  }
269 
276  function setObjectTablePK($a_column_name)
277  {
278  if (!isset($a_column_name))
279  {
280  $this->ilErr->raiseError(get_class($this)."::setObjectTablePK(): No column name given!",$this->ilErr->WARNING);
281  }
282 
283  $this->obj_pk = $a_column_name;
284  return true;
285  }
286 
293  function setTreeTablePK($a_column_name)
294  {
295  if (!isset($a_column_name))
296  {
297  $this->ilErr->raiseError(get_class($this)."::setTreeTablePK(): No column name given!",$this->ilErr->WARNING);
298  }
299 
300  $this->tree_pk = $a_column_name;
301  return true;
302  }
303 
309  function buildJoin()
310  {
311  if ($this->table_obj_reference)
312  {
313  // Use inner join instead of left join to improve performance
314  return "JOIN ".$this->table_obj_reference." ON ".$this->table_tree.".child=".$this->table_obj_reference.".".$this->ref_pk." ".
315  "JOIN ".$this->table_obj_data." ON ".$this->table_obj_reference.".".$this->obj_pk."=".$this->table_obj_data.".".$this->obj_pk." ";
316  }
317  else
318  {
319  // Use inner join instead of left join to improve performance
320  return "JOIN ".$this->table_obj_data." ON ".$this->table_tree.".child=".$this->table_obj_data.".".$this->obj_pk." ";
321  }
322  }
323 
332  function getChilds($a_node_id, $a_order = "", $a_direction = "ASC")
333  {
334  global $ilBench,$ilDB, $ilObjDataCache, $ilUser;
335 
336  if (!isset($a_node_id))
337  {
338  $message = get_class($this)."::getChilds(): No node_id given!";
339  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
340  }
341 
342  // init childs
343  $childs = array();
344 
345  // number of childs
346  $count = 0;
347 
348  // init order_clause
349  $order_clause = "";
350 
351  // set order_clause if sort order parameter is given
352  if (!empty($a_order))
353  {
354  $order_clause = "ORDER BY ".$a_order." ".$a_direction;
355  }
356  else
357  {
358  $order_clause = "ORDER BY ".$this->table_tree.".lft";
359  }
360 
361 
362  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
363  $this->buildJoin().
364  "WHERE parent = %s " .
365  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
366  $order_clause,
367  $ilDB->quote($a_node_id,'integer'),
368  $ilDB->quote($this->tree_id,'integer'));
369 
370  $res = $ilDB->query($query);
371 
372  if(!$count = $res->numRows())
373  {
374  return array();
375  }
376 
377  // get rows and object ids
378  $rows = array();
379  while($r = $ilDB->fetchAssoc($res))
380  {
381  $rows[] = $r;
382  $obj_ids[] = $r["obj_id"];
383  }
384 
385  // preload object translation information
386  if ($this->__isMainTree() && $this->isCacheUsed() && is_object($ilObjDataCache) &&
387  is_object($ilUser) && $this->lang_code == $ilUser->getLanguage() && !$this->oc_preloaded[$a_node_id])
388  {
389 // $ilObjDataCache->preloadTranslations($obj_ids, $this->lang_code);
390  $ilObjDataCache->preloadObjectCache($obj_ids, $this->lang_code);
391  $this->fetchTranslationFromObjectDataCache($obj_ids);
392  $this->oc_preloaded[$a_node_id] = true;
393  }
394 
395  foreach ($rows as $row)
396  {
397  $childs[] = $this->fetchNodeData($row);
398 
399  // Update cache of main tree
400  if ($this->__isMainTree())
401  {
402  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child'].' = true');
403  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
404  }
405  }
406  $childs[$count - 1]["last"] = true;
407  return $childs;
408  }
409 
419  function getFilteredChilds($a_filter,$a_node,$a_order = "",$a_direction = "ASC")
420  {
421  $childs = $this->getChilds($a_node,$a_order,$a_direction);
422 
423  foreach($childs as $child)
424  {
425  if(!in_array($child["type"],$a_filter))
426  {
427  $filtered[] = $child;
428  }
429  }
430  return $filtered ? $filtered : array();
431  }
432 
433 
441  function getChildsByType($a_node_id,$a_type)
442  {
443  global $ilDB;
444 
445  if (!isset($a_node_id) or !isset($a_type))
446  {
447  $message = get_class($this)."::getChildsByType(): Missing parameter! node_id:".$a_node_id." type:".$a_type;
448  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
449  }
450 
451  if ($a_type=='rolf' && $this->table_obj_reference) {
452  // Performance optimization: A node can only have exactly one
453  // role folder as its child. Therefore we don't need to sort the
454  // results, and we can let the database know about the expected limit.
455  $ilDB->setLimit(1,0);
456  $query = sprintf("SELECT * FROM ".$this->table_tree." ".
457  $this->buildJoin().
458  "WHERE parent = %s ".
459  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
460  "AND ".$this->table_obj_data.".type = %s ",
461  $ilDB->quote($a_node_id,'integer'),
462  $ilDB->quote($this->tree_id,'integer'),
463  $ilDB->quote($a_type,'text'));
464  } else {
465  $query = sprintf("SELECT * FROM ".$this->table_tree." ".
466  $this->buildJoin().
467  "WHERE parent = %s ".
468  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
469  "AND ".$this->table_obj_data.".type = %s ".
470  "ORDER BY ".$this->table_tree.".lft",
471  $ilDB->quote($a_node_id,'integer'),
472  $ilDB->quote($this->tree_id,'integer'),
473  $ilDB->quote($a_type,'text'));
474  }
475  $res = $ilDB->query($query);
476 
477  // init childs
478  $childs = array();
479  while($row = $ilDB->fetchAssoc($res))
480  {
481  $childs[] = $this->fetchNodeData($row);
482  }
483 
484  return $childs ? $childs : array();
485  }
486 
487 
495  public function getChildsByTypeFilter($a_node_id,$a_types)
496  {
497  global $ilDB;
498 
499  if (!isset($a_node_id) or !$a_types)
500  {
501  $message = get_class($this)."::getChildsByType(): Missing parameter! node_id:".$a_node_id." type:".$a_types;
502  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
503  }
504 
505  $filter = ' ';
506  if($a_types)
507  {
508  $filter = 'AND '.$this->table_obj_data.'.type IN('.implode(',',ilUtil::quoteArray($a_types)).') ';
509  }
510 
511  $query = 'SELECT * FROM '.$this->table_tree.' '.
512  $this->buildJoin().
513  'WHERE parent = '.$ilDB->quote($a_node_id,'integer').' '.
514  'AND '.$this->table_tree.'.'.$this->tree_pk.' = '.$ilDB->quote($this->tree_id,'integer').' '.
515  $filter.
516  'ORDER BY '.$this->table_tree.'.lft';
517 
518  $res = $ilDB->query($query);
519  while($row = $ilDB->fetchAssoc($res))
520  {
521  $childs[] = $this->fetchNodeData($row);
522  }
523 
524  return $childs ? $childs : array();
525  }
526 
534  function insertNode($a_node_id, $a_parent_id, $a_pos = IL_LAST_NODE, $a_reset_deletion_date = false)
535  {
536  global $ilDB;
537 
538 //echo "+$a_node_id+$a_parent_id+";
539  // CHECK node_id and parent_id > 0 if in main tree
540  if($this->__isMainTree())
541  {
542  if($a_node_id <= 1 or $a_parent_id <= 0)
543  {
544  $message = sprintf('%s::insertNode(): Invalid parameters! $a_node_id: %s $a_parent_id: %s',
545  get_class($this),
546  $a_node_id,
547  $a_parent_id);
548  $this->log->write($message,$this->log->FATAL);
549  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
550  }
551  }
552 
553 
554  if (!isset($a_node_id) or !isset($a_parent_id))
555  {
556  $this->ilErr->raiseError(get_class($this)."::insertNode(): Missing parameter! ".
557  "node_id: ".$a_node_id." parent_id: ".$a_parent_id,$this->ilErr->WARNING);
558  }
559  if ($this->isInTree($a_node_id))
560  {
561  $this->ilErr->raiseError(get_class($this)."::insertNode(): Node ".$a_node_id." already in tree ".
562  $this->table_tree."!",$this->ilErr->WARNING);
563  }
564 
565  //
566  switch ($a_pos)
567  {
568  case IL_FIRST_NODE:
569 
570  if($this->__isMainTree())
571  {
572  #ilDB::_lockTables(array('tree' => 'WRITE'));
573  $ilDB->lockTables(
574  array(
575  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
576  }
577 
578  // get left value of parent
579  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
580  'WHERE child = %s '.
581  'AND '.$this->tree_pk.' = %s ',
582  $ilDB->quote($a_parent_id,'integer'),
583  $ilDB->quote($this->tree_id,'integer'));
584 
585  $res = $ilDB->query($query);
586  $r = $ilDB->fetchObject($res);
587 
588  if ($r->parent == NULL)
589  {
590  if($this->__isMainTree())
591  {
592  $ilDB->unlockTables();
593  }
594  $this->ilErr->raiseError(get_class($this)."::insertNode(): Parent with ID ".$a_parent_id." not found in ".
595  $this->table_tree."!",$this->ilErr->WARNING);
596  }
597 
598  $left = $r->lft;
599  $lft = $left + 1;
600  $rgt = $left + 2;
601 
602  // spread tree
603  $query = sprintf('UPDATE '.$this->table_tree.' SET '.
604  'lft = CASE WHEN lft > %s THEN lft + 2 ELSE lft END, '.
605  'rgt = CASE WHEN rgt > %s THEN rgt + 2 ELSE rgt END '.
606  'WHERE '.$this->tree_pk.' = %s ',
607  $ilDB->quote($left,'integer'),
608  $ilDB->quote($left,'integer'),
609  $ilDB->quote($this->tree_id,'integer'));
610  $res = $ilDB->manipulate($query);
611  break;
612 
613  case IL_LAST_NODE:
614  // Special treatment for trees with gaps
615  if ($this->gap > 0)
616  {
617  if($this->__isMainTree())
618  {
619  #ilDB::_lockTables(array('tree' => 'WRITE'));
620  $ilDB->lockTables(
621  array(
622  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
623 
624  }
625 
626  // get lft and rgt value of parent
627  $query = sprintf('SELECT rgt,lft,parent FROM '.$this->table_tree.' '.
628  'WHERE child = %s '.
629  'AND '.$this->tree_pk.' = %s',
630  $ilDB->quote($a_parent_id,'integer'),
631  $ilDB->quote($this->tree_id,'integer'));
632  $res = $ilDB->query($query);
633  $r = $ilDB->fetchAssoc($res);
634 
635  if ($r['parent'] == null)
636  {
637  if($this->__isMainTree())
638  {
639  $ilDB->unlockTables();
640  }
641  $this->ilErr->raiseError(get_class($this)."::insertNode(): Parent with ID ".
642  $a_parent_id." not found in ".$this->table_tree."!",$this->ilErr->WARNING);
643  }
644  $parentRgt = $r['rgt'];
645  $parentLft = $r['lft'];
646 
647  // Get the available space, without taking children into account yet
648  $availableSpace = $parentRgt - $parentLft;
649  if ($availableSpace < 2)
650  {
651  // If there is not enough space between parent lft and rgt, we don't need
652  // to look any further, because we must spread the tree.
653  $lft = $parentRgt;
654  }
655  else
656  {
657  // If there is space between parent lft and rgt, we need to check
658  // whether there is space left between the rightmost child of the
659  // parent and parent rgt.
660  $query = sprintf('SELECT MAX(rgt) max_rgt FROM '.$this->table_tree.' '.
661  'WHERE parent = %s '.
662  'AND '.$this->tree_pk.' = %s',
663  $ilDB->quote($a_parent_id,'integer'),
664  $ilDB->quote($this->tree_id,'integer'));
665  $res = $ilDB->query($query);
666  $r = $ilDB->fetchAssoc($res);
667 
668  if (isset($r['max_rgt']))
669  {
670  // If the parent has children, we compute the available space
671  // between rgt of the rightmost child and parent rgt.
672  $availableSpace = $parentRgt - $r['max_rgt'];
673  $lft = $r['max_rgt'] + 1;
674  }
675  else
676  {
677  // If the parent has no children, we know now, that we can
678  // add the new node at parent lft + 1 without having to spread
679  // the tree.
680  $lft = $parentLft + 1;
681  }
682  }
683  $rgt = $lft + 1;
684 
685 
686  // spread tree if there is not enough space to insert the new node
687  if ($availableSpace < 2)
688  {
689  //$this->log->write('ilTree.insertNode('.$a_node_id.','.$a_parent_id.') creating gap at '.$a_parent_id.' '.$parentLft.'..'.$parentRgt.'+'.(2 + $this->gap * 2));
690  $query = sprintf('UPDATE '.$this->table_tree.' SET '.
691  'lft = CASE WHEN lft > %s THEN lft + %s ELSE lft END, '.
692  'rgt = CASE WHEN rgt >= %s THEN rgt + %s ELSE rgt END '.
693  'WHERE '.$this->tree_pk.' = %s ',
694  $ilDB->quote($parentRgt,'integer'),
695  $ilDB->quote((2 + $this->gap * 2),'integer'),
696  $ilDB->quote($parentRgt,'integer'),
697  $ilDB->quote((2 + $this->gap * 2),'integer'),
698  $ilDB->quote($this->tree_id,'integer'));
699  $res = $ilDB->manipulate($query);
700  }
701  else
702  {
703  //$this->log->write('ilTree.insertNode('.$a_node_id.','.$a_parent_id.') reusing gap at '.$a_parent_id.' '.$parentLft.'..'.$parentRgt.' for node '.$a_node_id.' '.$lft.'..'.$rgt);
704  }
705  }
706  // Treatment for trees without gaps
707  else
708  {
709  if($this->__isMainTree())
710  {
711  #ilDB::_lockTables(array('tree' => 'WRITE'));
712  $ilDB->lockTables(
713  array(
714  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
715 
716  }
717 
718  // get right value of parent
719  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
720  'WHERE child = %s '.
721  'AND '.$this->tree_pk.' = %s ',
722  $ilDB->quote($a_parent_id,'integer'),
723  $ilDB->quote($this->tree_id,'integer'));
724  $res = $ilDB->query($query);
725  $r = $ilDB->fetchObject($res);
726 
727  if ($r->parent == null)
728  {
729  if($this->__isMainTree())
730  {
731  $ilDB->unlockTables();
732  }
733  $this->ilErr->raiseError(get_class($this)."::insertNode(): Parent with ID ".
734  $a_parent_id." not found in ".$this->table_tree."!",$this->ilErr->WARNING);
735  }
736 
737  $right = $r->rgt;
738  $lft = $right;
739  $rgt = $right + 1;
740 
741  // spread tree
742  $query = sprintf('UPDATE '.$this->table_tree.' SET '.
743  'lft = CASE WHEN lft > %s THEN lft + 2 ELSE lft END, '.
744  'rgt = CASE WHEN rgt >= %s THEN rgt + 2 ELSE rgt END '.
745  'WHERE '.$this->tree_pk.' = %s',
746  $ilDB->quote($right,'integer'),
747  $ilDB->quote($right,'integer'),
748  $ilDB->quote($this->tree_id,'integer'));
749  $res = $ilDB->manipulate($query);
750  }
751 
752  break;
753 
754  default:
755 
756  // this code shouldn't be executed
757  if($this->__isMainTree())
758  {
759  #ilDB::_lockTables(array('tree' => 'WRITE'));
760  $ilDB->lockTables(
761  array(
762  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
763 
764  }
765 
766  // get right value of preceeding child
767  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
768  'WHERE child = %s '.
769  'AND '.$this->tree_pk.' = %s ',
770  $ilDB->quote($a_pos,'integer'),
771  $ilDB->quote($this->tree_id,'integer'));
772  $res = $ilDB->query($query);
773  $r = $ilDB->fetchObject($res);
774 
775  // crosscheck parents of sibling and new node (must be identical)
776  if ($r->parent != $a_parent_id)
777  {
778  if($this->__isMainTree())
779  {
780  $ilDB->unlockTables();
781  }
782  $this->ilErr->raiseError(get_class($this)."::insertNode(): Parents mismatch! ".
783  "new node parent: ".$a_parent_id." sibling parent: ".$r->parent,$this->ilErr->WARNING);
784  }
785 
786  $right = $r->rgt;
787  $lft = $right + 1;
788  $rgt = $right + 2;
789 
790  // update lft/rgt values
791  $query = sprintf('UPDATE '.$this->table_tree.' SET '.
792  'lft = CASE WHEN lft > %s THEN lft + 2 ELSE lft END, '.
793  'rgt = CASE WHEN rgt > %s THEN rgt + 2 ELSE rgt END '.
794  'WHERE '.$this->tree_pk.' = %s',
795  $ilDB->quote($right,'integer'),
796  $ilDB->quote($right,'integer'),
797  $ilDB->quote($this->tree_id,'integer'));
798  $res = $ilDB->manipulate($query);
799  break;
800 
801  }
802 
803  // get depth
804  $depth = $this->getDepth($a_parent_id) + 1;
805 
806  // insert node
807  //$this->log->write('ilTree.insertNode('.$a_node_id.','.$a_parent_id.') inserting node:'.$a_node_id.' parent:'.$a_parent_id." ".$lft."..".$rgt." depth:".$depth);
808  $query = sprintf('INSERT INTO '.$this->table_tree.' ('.$this->tree_pk.',child,parent,lft,rgt,depth) '.
809  'VALUES (%s,%s,%s,%s,%s,%s)',
810  $ilDB->quote($this->tree_id,'integer'),
811  $ilDB->quote($a_node_id,'integer'),
812  $ilDB->quote($a_parent_id,'integer'),
813  $ilDB->quote($lft,'integer'),
814  $ilDB->quote($rgt,'integer'),
815  $ilDB->quote($depth,'integer'));
816  $res = $ilDB->manipulate($query);
817 
818  // Finally unlock tables and update cache
819  if($this->__isMainTree())
820  {
821  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
822  $this->in_tree_cache[$a_node_id] = true;
823  $ilDB->unlockTables();
824  }
825 
826  // reset deletion date
827  if ($a_reset_deletion_date)
828  {
829  ilObject::_resetDeletedDate($a_node_id);
830  }
831  }
832 
845  public function getFilteredSubTree($a_node_id,$a_filter = array())
846  {
847  $node = $this->getNodeData($a_node_id);
848 
849  $first = true;
850  $depth = 0;
851  foreach($this->getSubTree($node) as $subnode)
852  {
853  if($depth and $subnode['depth'] > $depth)
854  {
855  continue;
856  }
857  if(!$first and in_array($subnode['type'],$a_filter))
858  {
859  $depth = $subnode['depth'];
860  $first = false;
861  continue;
862  }
863  $depth = 0;
864  $first = false;
865  $filtered[] = $subnode;
866  }
867  return $filtered ? $filtered : array();
868  }
869 
875  public function getSubTreeIds($a_ref_id)
876  {
877  global $ilDB;
878 
879  $query = 'SELECT s.child FROM '.$this->table_tree.' s, '.$this->table_tree.' t '.
880  'WHERE t.child = %s '.
881  'AND s.lft > t.lft '.
882  'AND s.rgt < t.rgt '.
883  'AND s.'.$this->tree_pk.' = %s';
884 
885  $res = $ilDB->queryF(
886  $query,
887  array('integer','integer'),
888  array($a_ref_id,$this->tree_id)
889  );
890  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
891  {
892  $childs[] = $row->child;
893  }
894  return $childs ? $childs : array();
895  }
896 
897 
906  function getSubTree($a_node,$a_with_data = true, $a_type = "")
907  {
908  global $ilDB;
909 
910  if (!is_array($a_node))
911  {
912  $this->ilErr->raiseError(get_class($this)."::getSubTree(): Wrong datatype for node_data! ",$this->ilErr->WARNING);
913  }
914 
915  if($a_node['lft'] < 1 or $a_node['rgt'] < 2)
916  {
917  $message = sprintf('%s::getSubTree(): Invalid node given! $a_node["lft"]: %s $a_node["rgt"]: %s',
918  get_class($this),
919  $a_node['lft'],
920  $a_node['rgt']);
921 
922  $this->log->write($message,$this->log->FATAL);
923 
924  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
925  }
926 
927 
928  $fields = array('integer','integer','integer');
929  $data = array($a_node['lft'],$a_node['rgt'],$this->tree_id);
930  $type_str = '';
931 
932  if(strlen($a_type))
933  {
934  $fields[] = 'text';
935  $data[] = $a_type;
936  $type_str = "AND ".$this->table_obj_data.".type= %s ";
937  }
938 
939  $query = "SELECT * FROM ".$this->table_tree." ".
940  $this->buildJoin().
941  "WHERE ".$this->table_tree.".lft BETWEEN %s AND %s ".
942  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
943  $type_str.
944  "ORDER BY ".$this->table_tree.".lft";
945  $res = $ilDB->queryF($query,$fields,$data);
946  while($row = $ilDB->fetchAssoc($res))
947  {
948  if($a_with_data)
949  {
950  $subtree[] = $this->fetchNodeData($row);
951  }
952  else
953  {
954  $subtree[] = $row['child'];
955  }
956  if($this->__isMainTree())
957  {
958  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
959  $this->in_tree_cache[$row['child']] = true;
960  }
961  }
962 
963  return $subtree ? $subtree : array();
964  }
965 
974  function getSubTreeTypes($a_node,$a_filter = 0)
975  {
976  $a_filter = $a_filter ? $a_filter : array();
977 
978  foreach($this->getSubtree($this->getNodeData($a_node)) as $node)
979  {
980  if(in_array($node["type"],$a_filter))
981  {
982  continue;
983  }
984  $types["$node[type]"] = $node["type"];
985  }
986  return $types ? $types : array();
987  }
988 
994  function deleteTree($a_node)
995  {
996  global $ilDB;
997 
998  if (!is_array($a_node))
999  {
1000  $this->ilErr->raiseError(get_class($this)."::deleteTree(): Wrong datatype for node_data! ",$this->ilErr->WARNING);
1001  }
1002  if($this->__isMainTree() and $a_node[$this->tree_pk] === 1)
1003  {
1004  if($a_node['lft'] <= 1 or $a_node['rgt'] <= 2)
1005  {
1006  $message = sprintf('%s::deleteTree(): Invalid parameters given: $a_node["lft"]: %s, $a_node["rgt"] %s',
1007  get_class($this),
1008  $a_node['lft'],
1009  $a_node['rgt']);
1010 
1011  $this->log->write($message,$this->log->FATAL);
1012  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1013  }
1014  else if(!$this->__checkDelete($a_node))
1015  {
1016  $message = sprintf('%s::deleteTree(): Check delete failed: $a_node["lft"]: %s, $a_node["rgt"] %s',
1017  get_class($this),
1018  $a_node['lft'],
1019  $a_node['rgt']);
1020  $this->log->write($message,$this->log->FATAL);
1021  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1022  }
1023 
1024  }
1025  $diff = $a_node["rgt"] - $a_node["lft"] + 1;
1026 
1027 
1028  // LOCKED ###########################################################
1029  // get lft and rgt values. Don't trust parameter lft/rgt values of $a_node
1030  if($this->__isMainTree())
1031  {
1032  #ilDB::_lockTables(array('tree' => 'WRITE'));
1033  $ilDB->lockTables(
1034  array(
1035  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
1036 
1037  }
1038 
1039  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
1040  'WHERE child = %s '.
1041  'AND '.$this->tree_pk.' = %s ',
1042  $ilDB->quote($a_node['child'],'integer'),
1043  $ilDB->quote($a_node[$this->tree_pk],'integer'));
1044  $res = $ilDB->query($query);
1045  while($row = $ilDB->fetchObject($res))
1046  {
1047  $a_node['lft'] = $row->lft;
1048  $a_node['rgt'] = $row->rgt;
1049  $diff = $a_node["rgt"] - $a_node["lft"] + 1;
1050  }
1051 
1052  // delete subtree
1053  $query = sprintf('DELETE FROM '.$this->table_tree.' '.
1054  'WHERE lft BETWEEN %s AND %s '.
1055  'AND rgt BETWEEN %s AND %s '.
1056  'AND '.$this->tree_pk.' = %s',
1057  $ilDB->quote($a_node['lft'],'integer'),
1058  $ilDB->quote($a_node['rgt'],'integer'),
1059  $ilDB->quote($a_node['lft'],'integer'),
1060  $ilDB->quote($a_node['rgt'],'integer'),
1061  $ilDB->quote($a_node[$this->tree_pk],'integer'));
1062  $res = $ilDB->manipulate($query);
1063 
1064  // Performance improvement: We only close the gap, if the node
1065  // is not in a trash tree, and if the resulting gap will be
1066  // larger than twice the gap value
1067  if ($a_node[$this->tree_pk] >= 0 && $a_node['rgt'] - $a_node['lft'] >= $this->gap * 2)
1068  {
1069  //$this->log->write('ilTree.deleteTree('.$a_node['child'].') closing gap at '.$a_node['lft'].'...'.$a_node['rgt']);
1070  // close gaps
1071  $query = sprintf('UPDATE '.$this->table_tree.' SET '.
1072  'lft = CASE WHEN lft > %s THEN lft - %s ELSE lft END, '.
1073  'rgt = CASE WHEN rgt > %s THEN rgt - %s ELSE rgt END '.
1074  'WHERE '.$this->tree_pk.' = %s ',
1075  $ilDB->quote($a_node['lft'],'integer'),
1076  $ilDB->quote($diff,'integer'),
1077  $ilDB->quote($a_node['lft'],'integer'),
1078  $ilDB->quote($diff,'integer'),
1079  $ilDB->quote($a_node[$this->tree_pk],'integer'));
1080 
1081  $res = $ilDB->manipulate($query);
1082  }
1083  else
1084  {
1085  //$this->log->write('ilTree.deleteTree('.$a_node['child'].') leaving gap open '.$a_node['lft'].'...'.$a_node['rgt']);
1086  }
1087 
1088  if($this->__isMainTree())
1089  {
1090  #$GLOBALS['ilLog']->write(__METHOD__.': Resetting in tree cache ');
1091  $ilDB->unlockTables();
1092  $this->in_tree_cache = array();
1093  }
1094  // LOCKED ###########################################################
1095  }
1096 
1107  function getPathFull($a_endnode_id, $a_startnode_id = 0)
1108  {
1109  $pathIds =& $this->getPathId($a_endnode_id, $a_startnode_id);
1110 
1111  // We retrieve the full path in a single query to improve performance
1112  global $ilDB;
1113 
1114  // Abort if no path ids were found
1115  if (count($pathIds) == 0)
1116  {
1117  return null;
1118  }
1119 
1120  $inClause = 'child IN (';
1121  for ($i=0; $i < count($pathIds); $i++)
1122  {
1123  if ($i > 0) $inClause .= ',';
1124  $inClause .= $ilDB->quote($pathIds[$i],'integer');
1125  }
1126  $inClause .= ')';
1127 
1128  $q = 'SELECT * '.
1129  'FROM '.$this->table_tree.' '.
1130  $this->buildJoin().' '.
1131  'WHERE '.$inClause.' '.
1132  'AND '.$this->table_tree.'.'.$this->tree_pk.' = '.$this->ilDB->quote($this->tree_id,'integer').' '.
1133  'ORDER BY depth';
1134  $r = $ilDB->query($q);
1135 
1136  $pathFull = array();
1137  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1138  {
1139  $pathFull[] = $this->fetchNodeData($row);
1140 
1141  // Update cache
1142  if ($this->__isMainTree())
1143  {
1144  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child']);
1145  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
1146  }
1147  }
1148  return $pathFull;
1149  }
1158  function getPathIdsUsingNestedSets($a_endnode_id, $a_startnode_id = 0)
1159  {
1160  global $ilDB;
1161 
1162  // The nested sets algorithm is very easy to implement.
1163  // Unfortunately it always does a full table space scan to retrieve the path
1164  // regardless whether indices on lft and rgt are set or not.
1165  // (At least, this is what happens on MySQL 4.1).
1166  // This algorithms performs well for small trees which are deeply nested.
1167 
1168  if (!isset($a_endnode_id))
1169  {
1170  $this->ilErr->raiseError(get_class($this)."::getPathId(): No endnode_id given! ",$this->ilErr->WARNING);
1171  }
1172 
1173  $fields = array('integer','integer','integer');
1174  $data = array($a_endnode_id,$this->tree_id,$this->tree_id);
1175 
1176  $query = "SELECT T2.child ".
1177  "FROM ".$this->table_tree." T1, ".$this->table_tree." T2 ".
1178  "WHERE T1.child = %s ".
1179  "AND T1.lft BETWEEN T2.lft AND T2.rgt ".
1180  "AND T1.".$this->tree_pk." = %s ".
1181  "AND T2.".$this->tree_pk." = %s ".
1182  "ORDER BY T2.depth";
1183  $res = $ilDB->queryF($query,$fields,$data);
1184 
1185  $takeId = $a_startnode_id == 0;
1186  while($row = $ilDB->fetchAssoc($res))
1187  {
1188  if ($takeId || $row['child'] == $a_startnode_id)
1189  {
1190  $takeId = true;
1191  $pathIds[] = $row['child'];
1192  }
1193  }
1194  return $pathIds ? $pathIds : array();
1195  }
1196 
1205  function getPathIdsUsingAdjacencyMap($a_endnode_id, $a_startnode_id = 0)
1206  {
1207  // The adjacency map algorithm is harder to implement than the nested sets algorithm.
1208  // This algorithms performs an index search for each of the path element.
1209  // This algorithms performs well for large trees which are not deeply nested.
1210 
1211  // The $takeId variable is used, to determine if a given id shall be included in the path
1212  $takeId = $a_startnode_id == 0;
1213 
1214  if (!isset($a_endnode_id))
1215  {
1216  $this->ilErr->raiseError(get_class($this)."::getPathId(): No endnode_id given! ",$this->ilErr->WARNING);
1217  }
1218 
1219  global $log, $ilDB;
1220 
1221  if ($this->__isMainTree() && isset($this->depth_cache[$a_endnode_id])
1222  && isset($this->parent_cache[$a_endnode_id]))
1223  {
1224  $nodeDepth = $this->depth_cache[$a_endnode_id];
1225  $parentId = $this->parent_cache[$a_endnode_id];
1226  }
1227  else
1228  {
1229  $types = array('integer','integer');
1230  $data = array($a_endnode_id,$this->tree_id);
1231  $query = 'SELECT t.depth, t.parent '.
1232  'FROM '.$this->table_tree.' t '.
1233  'WHERE child = %s '.
1234  'AND '.$this->tree_pk.' = %s ';
1235  $res = $ilDB->queryF($query,$types,$data);
1236 
1237  if($res->numRows() == 0)
1238  {
1239  return array();
1240  }
1241 
1242  $row = $ilDB->fetchAssoc($res);
1243  $nodeDepth = $row['depth'];
1244  $parentId = $row['parent'];
1245  }
1246 
1247  //$this->writelog('getIdsUsingAdjacencyMap depth='.$nodeDepth);
1248 
1249  // Fetch the node ids. For shallow depths we can fill in the id's directly.
1250  $pathIds = array();
1251  if ($nodeDepth == 1)
1252  {
1253  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1254  if ($takeId) $pathIds[] = $a_endnode_id;
1255  }
1256  else if ($nodeDepth == 2)
1257  {
1258  $takeId = $takeId || $parentId == $a_startnode_id;
1259  if ($takeId) $pathIds[] = $parentId;
1260  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1261  if ($takeId) $pathIds[] = $a_endnode_id;
1262  }
1263  else if ($nodeDepth == 3)
1264  {
1265  $takeId = $takeId || $this->root_id == $a_startnode_id;
1266  if ($takeId) $pathIds[] = $this->root_id;
1267  $takeId = $takeId || $parentId == $a_startnode_id;
1268  if ($takeId) $pathIds[] = $parentId;
1269  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1270  if ($takeId) $pathIds[] = $a_endnode_id;
1271  }
1272  else if ($nodeDepth < 32)
1273  {
1274  // Adjacency Map Tree performs better than
1275  // Nested Sets Tree even for very deep trees.
1276  // The following code construct nested self-joins
1277  // Since we already know the root-id of the tree and
1278  // we also know the id and parent id of the current node,
1279  // we only need to perform $nodeDepth - 3 self-joins.
1280  // We can further reduce the number of self-joins by 1
1281  // by taking into account, that each row in table tree
1282  // contains the id of itself and of its parent.
1283  $qSelect = 't1.child c0';
1284  $qJoin = '';
1285  for ($i = 1; $i < $nodeDepth - 2; $i++)
1286  {
1287  $qSelect .= ', t'.$i.'.parent c'.$i;
1288  $qJoin .= ' JOIN '.$this->table_tree.' t'.$i.' ON '.
1289  't'.$i.'.child=t'.($i - 1).'.parent AND '.
1290  't'.$i.'.'.$this->tree_pk.' = '.(int) $this->tree_id;
1291  }
1292 
1293  $types = array('integer','integer');
1294  $data = array($this->tree_id,$parentId);
1295  $query = 'SELECT '.$qSelect.' '.
1296  'FROM '.$this->table_tree.' t0 '.$qJoin.' '.
1297  'WHERE t0.'.$this->tree_pk.' = %s '.
1298  'AND t0.child = %s ';
1299 
1300  $ilDB->setLimit(1);
1301  $res = $ilDB->queryF($query,$types,$data);
1302 
1303  if ($res->numRows() == 0)
1304  {
1305  return array();
1306  }
1307  $row = $ilDB->fetchAssoc($res);
1308 
1309  $takeId = $takeId || $this->root_id == $a_startnode_id;
1310  if ($takeId) $pathIds[] = $this->root_id;
1311  for ($i = $nodeDepth - 4; $i >=0; $i--)
1312  {
1313  $takeId = $takeId || $row['c'.$i] == $a_startnode_id;
1314  if ($takeId) $pathIds[] = $row['c'.$i];
1315  }
1316  $takeId = $takeId || $parentId == $a_startnode_id;
1317  if ($takeId) $pathIds[] = $parentId;
1318  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1319  if ($takeId) $pathIds[] = $a_endnode_id;
1320  }
1321  else
1322  {
1323  // Fall back to nested sets tree for extremely deep tree structures
1324  return $this->getPathIdsUsingNestedSets($a_endnode_id, $a_startnode_id);
1325  }
1326 
1327  return $pathIds;
1328  }
1329 
1336  function preloadDepthParent($a_node_ids)
1337  {
1338  global $ilDB;
1339 
1340  if (!$this->__isMainTree() || !is_array($a_node_ids) || !$this->isCacheUsed())
1341  {
1342  return;
1343  }
1344 
1345  $res = $ilDB->query('SELECT t.depth, t.parent, t.child '.
1346  'FROM '.$this->table_tree.' t '.
1347  'WHERE '.$ilDB->in("child", $a_node_ids, false, "integer").
1348  'AND '.$this->tree_pk.' = '.$ilDB->quote($this->tree_id, "integer"));
1349  while ($row = $ilDB->fetchAssoc($res))
1350  {
1351  $this->depth_cache[$row["child"]] = $row["depth"];
1352  $this->parent_cache[$row["child"]] = $row["parent"];
1353  }
1354  }
1355 
1364  function getPathId($a_endnode_id, $a_startnode_id = 0)
1365  {
1366  // path id cache
1367  if ($this->isCacheUsed() && isset($this->path_id_cache[$a_endnode_id][$a_startnode_id]))
1368  {
1369 //echo "<br>getPathIdhit";
1370  return $this->path_id_cache[$a_endnode_id][$a_startnode_id];
1371  }
1372 //echo "<br>miss";
1373 
1374  $pathIds =& $this->getPathIdsUsingAdjacencyMap($a_endnode_id, $a_startnode_id);
1375 
1376  if($this->__isMainTree())
1377  {
1378  $this->path_id_cache[$a_endnode_id][$a_startnode_id] = $pathIds;
1379  }
1380  return $pathIds;
1381  }
1382 
1383  // BEGIN WebDAV: getNodePathForTitlePath function added
1401  function getNodePathForTitlePath($titlePath, $a_startnode_id = null)
1402  {
1403  global $ilDB, $log;
1404  //$log->write('getNodePathForTitlePath('.implode('/',$titlePath));
1405 
1406  // handle empty title path
1407  if ($titlePath == null || count($titlePath) == 0)
1408  {
1409  if ($a_startnode_id == 0)
1410  {
1411  return null;
1412  }
1413  else
1414  {
1415  return $this->getNodePath($a_startnode_id);
1416  }
1417  }
1418 
1419  // fetch the node path up to the startnode
1420  if ($a_startnode_id != null && $a_startnode_id != 0)
1421  {
1422  // Start using the node path to the root of the relative path
1423  $nodePath = $this->getNodePath($a_startnode_id);
1424  $parent = $a_startnode_id;
1425  }
1426  else
1427  {
1428  // Start using the root of the tree
1429  $nodePath = array();
1430  $parent = 0;
1431  }
1432 
1433 
1434  // Convert title path into Unicode Normal Form C
1435  // This is needed to ensure that we can compare title path strings with
1436  // strings from the database.
1437  require_once('include/Unicode/UtfNormal.php');
1438  include_once './Services/Utilities/classes/class.ilStr.php';
1439  $inClause = 'd.title IN (';
1440  for ($i=0; $i < count($titlePath); $i++)
1441  {
1442  $titlePath[$i] = ilStr::strToLower(UtfNormal::toNFC($titlePath[$i]));
1443  if ($i > 0) $inClause .= ',';
1444  $inClause .= $ilDB->quote($titlePath[$i],'text');
1445  }
1446  $inClause .= ')';
1447 
1448  // Fetch all rows that are potential path elements
1449  if ($this->table_obj_reference)
1450  {
1451  $joinClause = 'JOIN '.$this->table_obj_reference.' r ON t.child = r.'.$this->ref_pk.' '.
1452  'JOIN '.$this->table_obj_data.' d ON r.'.$this->obj_pk.' = d.'.$this->obj_pk;
1453  }
1454  else
1455  {
1456  $joinClause = 'JOIN '.$this->table_obj_data.' d ON t.child = d.'.$this->obj_pk;
1457  }
1458  // The ORDER BY clause in the following SQL statement ensures that,
1459  // in case of a multiple objects with the same title, always the Object
1460  // with the oldest ref_id is chosen.
1461  // This ensure, that, if a new object with the same title is added,
1462  // WebDAV clients can still work with the older object.
1463  $q = 'SELECT t.depth, t.parent, t.child, d.'.$this->obj_pk.' obj_id, d.type, d.title '.
1464  'FROM '.$this->table_tree.' t '.
1465  $joinClause.' '.
1466  'WHERE '.$inClause.' '.
1467  'AND t.depth <= '.(count($titlePath)+count($nodePath)).' '.
1468  'AND t.tree = 1 '.
1469  'ORDER BY t.depth, t.child ASC';
1470  $r = $ilDB->query($q);
1471 
1472  $rows = array();
1473  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1474  {
1475  $row['title'] = UtfNormal::toNFC($row['title']);
1476  $row['ref_id'] = $row['child'];
1477  $rows[] = $row;
1478  }
1479 
1480  // Extract the path elements from the fetched rows
1481  for ($i = 0; $i < count($titlePath); $i++) {
1482  $pathElementFound = false;
1483  foreach ($rows as $row) {
1484  if ($row['parent'] == $parent &&
1485  ilStr::strToLower($row['title']) == $titlePath[$i])
1486  {
1487  // FIXME - We should test here, if the user has
1488  // 'visible' permission for the object.
1489  $nodePath[] = $row;
1490  $parent = $row['child'];
1491  $pathElementFound = true;
1492  break;
1493  }
1494  }
1495  // Abort if we haven't found a path element for the current depth
1496  if (! $pathElementFound)
1497  {
1498  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):null');
1499  return null;
1500  }
1501  }
1502  // Return the node path
1503  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):'.var_export($nodePath,true));
1504  return $nodePath;
1505  }
1506  // END WebDAV: getNodePathForTitlePath function added
1507  // END WebDAV: getNodePath function added
1524  function getNodePath($a_endnode_id, $a_startnode_id = 0)
1525  {
1526  global $ilDB;
1527 
1528  $pathIds = $this->getPathId($a_endnode_id, $a_startnode_id);
1529 
1530  // Abort if no path ids were found
1531  if (count($pathIds) == 0)
1532  {
1533  return null;
1534  }
1535 
1536 
1537  $types = array();
1538  $data = array();
1539  for ($i = 0; $i < count($pathIds); $i++)
1540  {
1541  $types[] = 'integer';
1542  $data[] = $pathIds[$i];
1543  }
1544 
1545  $query = 'SELECT t.depth,t.parent,t.child,d.obj_id,d.type,d.title '.
1546  'FROM '.$this->table_tree.' t '.
1547  'JOIN '.$this->table_obj_reference.' r ON r.ref_id = t.child '.
1548  'JOIN '.$this->table_obj_data.' d ON d.obj_id = r.obj_id '.
1549  'WHERE '.$ilDB->in('t.child',$data,false,'integer').' '.
1550  'ORDER BY t.depth ';
1551 
1552  $res = $ilDB->queryF($query,$types,$data);
1553 
1554  $titlePath = array();
1555  while ($row = $ilDB->fetchAssoc($res))
1556  {
1557  $titlePath[] = $row;
1558  }
1559  return $titlePath;
1560  }
1561  // END WebDAV: getNodePath function added
1562 
1569  function checkTree()
1570  {
1571  global $ilDB;
1572 
1573  $types = array('integer');
1574  $query = 'SELECT lft,rgt FROM '.$this->table_tree.' '.
1575  'WHERE '.$this->tree_pk.' = %s ';
1576 
1577  $res = $ilDB->queryF($query,$types,array($this->tree_id));
1578  while ($row = $ilDB->fetchObject($res))
1579  {
1580  $lft[] = $row->lft;
1581  $rgt[] = $row->rgt;
1582  }
1583 
1584  $all = array_merge($lft,$rgt);
1585  $uni = array_unique($all);
1586 
1587  if (count($all) != count($uni))
1588  {
1589  $message = sprintf('%s::checkTree(): Tree is corrupted!',
1590  get_class($this));
1591 
1592  $this->log->write($message,$this->log->FATAL);
1593  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1594  }
1595 
1596  return true;
1597  }
1598 
1602  function checkTreeChilds($a_no_zero_child = true)
1603  {
1604  global $ilDB;
1605 
1606  $query = 'SELECT * FROM '.$this->table_tree.' '.
1607  'WHERE '.$this->tree_pk.' = %s '.
1608  'ORDER BY lft';
1609  $r1 = $ilDB->queryF($query,array('integer'),array($this->tree_id));
1610 
1611  while ($row = $ilDB->fetchAssoc($r1))
1612  {
1613 //echo "tree:".$row[$this->tree_pk].":lft:".$row["lft"].":rgt:".$row["rgt"].":child:".$row["child"].":<br>";
1614  if (($row["child"] == 0) && $a_no_zero_child)
1615  {
1616  $this->ilErr->raiseError(get_class($this)."::checkTreeChilds(): Tree contains child with ID 0!",$this->ilErr->WARNING);
1617  }
1618 
1619  if ($this->table_obj_reference)
1620  {
1621  // get object reference data
1622  $query = 'SELECT * FROM '.$this->table_obj_reference.' WHERE '.$this->ref_pk.' = %s ';
1623  $r2 = $ilDB->queryF($query,array('integer'),array($row['child']));
1624 
1625 //echo "num_childs:".$r2->numRows().":<br>";
1626  if ($r2->numRows() == 0)
1627  {
1628  $this->ilErr->raiseError(get_class($this)."::checkTree(): No Object-to-Reference entry found for ID ".
1629  $row["child"]."!",$this->ilErr->WARNING);
1630  }
1631  if ($r2->numRows() > 1)
1632  {
1633  $this->ilErr->raiseError(get_class($this)."::checkTree(): More Object-to-Reference entries found for ID ".
1634  $row["child"]."!",$this->ilErr->WARNING);
1635  }
1636 
1637  // get object data
1638  $obj_ref = $ilDB->fetchAssoc($r2);
1639 
1640  $query = 'SELECT * FROM '.$this->table_obj_data.' WHERE '.$this->obj_pk.' = %s';
1641  $r3 = $ilDB->queryF($query,array('integer'),array($obj_ref[$this->obj_pk]));
1642  if ($r3->numRows() == 0)
1643  {
1644  $this->ilErr->raiseError(get_class($this)."::checkTree(): No child found for ID ".
1645  $obj_ref[$this->obj_pk]."!",$this->ilErr->WARNING);
1646  }
1647  if ($r3->numRows() > 1)
1648  {
1649  $this->ilErr->raiseError(get_class($this)."::checkTree(): More childs found for ID ".
1650  $obj_ref[$this->obj_pk]."!",$this->ilErr->WARNING);
1651  }
1652 
1653  }
1654  else
1655  {
1656  // get only object data
1657  $query = 'SELECT * FROM '.$this->table_obj_data.' WHERE '.$this->obj_pk.' = %s';
1658  $r2 = $ilDB->queryF($query,array('integer'),array($row['child']));
1659 //echo "num_childs:".$r2->numRows().":<br>";
1660  if ($r2->numRows() == 0)
1661  {
1662  $this->ilErr->raiseError(get_class($this)."::checkTree(): No child found for ID ".
1663  $row["child"]."!",$this->ilErr->WARNING);
1664  }
1665  if ($r2->numRows() > 1)
1666  {
1667  $this->ilErr->raiseError(get_class($this)."::checkTree(): More childs found for ID ".
1668  $row["child"]."!",$this->ilErr->WARNING);
1669  }
1670  }
1671  }
1672 
1673  return true;
1674  }
1675 
1681  function getMaximumDepth()
1682  {
1683  global $ilDB;
1684 
1685  $query = 'SELECT MAX(depth) depth FROM '.$this->table_tree;
1686  $res = $ilDB->query($query);
1687 
1688  $row = $ilDB->fetchAssoc($res);
1689  return $row['depth'];
1690  }
1691 
1698  function getDepth($a_node_id)
1699  {
1700  global $ilDB;
1701 
1702  if ($a_node_id)
1703  {
1704  $query = 'SELECT depth FROM '.$this->table_tree.' '.
1705  'WHERE child = %s '.
1706  'AND '.$this->tree_pk.' = %s ';
1707  $res = $ilDB->queryF($query,array('integer','integer'),array($a_node_id,$this->tree_id));
1708  $row = $ilDB->fetchObject($res);
1709 
1710  return $row->depth;
1711  }
1712  else
1713  {
1714  return 1;
1715  }
1716  }
1717 
1718 
1726  // BEGIN WebDAV: Pass tree id to this method
1727  //function getNodeData($a_node_id)
1728  function getNodeData($a_node_id, $a_tree_pk = null)
1729  // END PATCH WebDAV: Pass tree id to this method
1730  {
1731  global $ilDB;
1732 
1733  if (!isset($a_node_id))
1734  {
1735  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1736  }
1737  if($this->__isMainTree())
1738  {
1739  if($a_node_id < 1)
1740  {
1741  $message = sprintf('%s::getNodeData(): No valid parameter given! $a_node_id: %s',
1742  get_class($this),
1743  $a_node_id);
1744 
1745  $this->log->write($message,$this->log->FATAL);
1746  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1747  }
1748  }
1749 
1750  // BEGIN WebDAV: Pass tree id to this method
1751  $query = 'SELECT * FROM '.$this->table_tree.' '.
1752  $this->buildJoin().
1753  'WHERE '.$this->table_tree.'.child = %s '.
1754  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
1755  $res = $ilDB->queryF($query,array('integer','integer'),array(
1756  $a_node_id,
1757  $a_tree_pk === null ? $this->tree_id : $a_tree_pk));
1758  // END WebDAV: Pass tree id to this method
1759  $row = $ilDB->fetchAssoc($res);
1761 
1762  return $this->fetchNodeData($row);
1763  }
1764 
1772  function fetchNodeData($a_row)
1773  {
1774  global $objDefinition, $lng, $ilBench,$ilDB;
1775 
1776  //$ilBench->start("Tree", "fetchNodeData_getRow");
1777  $data = $a_row;
1778  $data["desc"] = $a_row["description"]; // for compability
1779  //$ilBench->stop("Tree", "fetchNodeData_getRow");
1780 
1781  // multilingual support systemobjects (sys) & categories (db)
1782  //$ilBench->start("Tree", "fetchNodeData_readDefinition");
1783  if (is_object($objDefinition))
1784  {
1785  $translation_type = $objDefinition->getTranslationType($data["type"]);
1786  }
1787  //$ilBench->stop("Tree", "fetchNodeData_readDefinition");
1788 
1789  if ($translation_type == "sys")
1790  {
1791  //$ilBench->start("Tree", "fetchNodeData_getLangData");
1792  if ($data["type"] == "rolf" and $data["obj_id"] != ROLE_FOLDER_ID)
1793  {
1794  $data["description"] = $lng->txt("obj_".$data["type"]."_local_desc").$data["title"].$data["desc"];
1795  $data["desc"] = $lng->txt("obj_".$data["type"]."_local_desc").$data["title"].$data["desc"];
1796  $data["title"] = $lng->txt("obj_".$data["type"]."_local");
1797  }
1798  else
1799  {
1800  $data["title"] = $lng->txt("obj_".$data["type"]);
1801  $data["description"] = $lng->txt("obj_".$data["type"]."_desc");
1802  $data["desc"] = $lng->txt("obj_".$data["type"]."_desc");
1803  }
1804  //$ilBench->stop("Tree", "fetchNodeData_getLangData");
1805  }
1806  elseif ($translation_type == "db")
1807  {
1808 
1809  // Try to retrieve object translation from cache
1810  if ($this->isCacheUsed() &&
1811  array_key_exists($data["obj_id"].'.'.$lang_code, $this->translation_cache)) {
1812 
1813  $key = $data["obj_id"].'.'.$lang_code;
1814  $data["title"] = $this->translation_cache[$key]['title'];
1815  $data["description"] = $this->translation_cache[$key]['description'];
1816  $data["desc"] = $this->translation_cache[$key]['desc'];
1817  }
1818  else
1819  {
1820  // Object translation is not in cache, read it from database
1821  //$ilBench->start("Tree", "fetchNodeData_getTranslation");
1822  $query = 'SELECT title,description FROM object_translation '.
1823  'WHERE obj_id = %s '.
1824  'AND lang_code = %s '.
1825  'AND NOT lang_default = %s';
1826 
1827  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
1828  $data['obj_id'],
1829  $this->lang_code,
1830  1));
1831  $row = $ilDB->fetchObject($res);
1832 
1833  if ($row)
1834  {
1835  $data["title"] = $row->title;
1836  $data["description"] = ilUtil::shortenText($row->description,MAXLENGTH_OBJ_DESC,true);
1837  $data["desc"] = $row->description;
1838  }
1839  //$ilBench->stop("Tree", "fetchNodeData_getTranslation");
1840 
1841  // Store up to 1000 object translations in cache
1842  if ($this->isCacheUsed() && count($this->translation_cache) < 1000)
1843  {
1844  $key = $data["obj_id"].'.'.$lang_code;
1845  $this->translation_cache[$key] = array();
1846  $this->translation_cache[$key]['title'] = $data["title"] ;
1847  $this->translation_cache[$key]['description'] = $data["description"];
1848  $this->translation_cache[$key]['desc'] = $data["desc"];
1849  }
1850  }
1851  }
1852 
1853  // TODO: Handle this switch by module.xml definitions
1854  if($data['type'] == 'crsr' or $data['type'] == 'catr')
1855  {
1856  include_once('./Services/ContainerReference/classes/class.ilContainerReference.php');
1858  }
1859 
1860  return $data ? $data : array();
1861  }
1862 
1868  protected function fetchTranslationFromObjectDataCache($a_obj_ids)
1869  {
1870  global $ilObjDataCache;
1871 
1872  if ($this->isCacheUsed() && is_array($a_obj_ids) && is_object($ilObjDataCache))
1873  {
1874  foreach ($a_obj_ids as $id)
1875  {
1876  $this->translation_cache[$id.'.']['title'] = $ilObjDataCache->lookupTitle($id);
1877  $this->translation_cache[$id.'.']['description'] = $ilObjDataCache->lookupDescription($id);;
1878  $this->translation_cache[$id.'.']['desc'] =
1879  $this->translation_cache[$id.'.']['description'];
1880  }
1881  }
1882  }
1883 
1884 
1892  function isInTree($a_node_id)
1893  {
1894  global $ilDB;
1895 
1896  if (!isset($a_node_id))
1897  {
1898  return false;
1899  #$this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1900  }
1901 
1902  // is in tree cache
1903  if ($this->isCacheUsed() && isset($this->in_tree_cache[$a_node_id]))
1904  {
1905  #$GLOBALS['ilLog']->write(__METHOD__.': Using in tree cache '.$a_node_id);
1906 //echo "<br>in_tree_hit";
1907  return $this->in_tree_cache[$a_node_id];
1908  }
1909 
1910  $query = 'SELECT * FROM '.$this->table_tree.' '.
1911  'WHERE '.$this->table_tree.'.child = %s '.
1912  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s';
1913 
1914  $res = $ilDB->queryF($query,array('integer','integer'),array(
1915  $a_node_id,
1916  $this->tree_id));
1917 
1918  if ($res->numRows() > 0)
1919  {
1920  if($this->__isMainTree())
1921  {
1922  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
1923  $this->in_tree_cache[$a_node_id] = true;
1924  }
1925  return true;
1926  }
1927  else
1928  {
1929  if($this->__isMainTree())
1930  {
1931  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = false');
1932  $this->in_tree_cache[$a_node_id] = false;
1933  }
1934  return false;
1935  }
1936  }
1937 
1944  function getParentNodeData($a_node_id)
1945  {
1946  global $ilDB;
1947  global $ilLog;
1948 
1949  if (!isset($a_node_id))
1950  {
1951  $ilLog->logStack();
1952  $this->ilErr->raiseError(get_class($this)."::getParentNodeData(): No node_id given! ",$this->ilErr->WARNING);
1953  }
1954 
1955  if ($this->table_obj_reference)
1956  {
1957  // Use inner join instead of left join to improve performance
1958  $innerjoin = "JOIN ".$this->table_obj_reference." ON v.child=".$this->table_obj_reference.".".$this->ref_pk." ".
1959  "JOIN ".$this->table_obj_data." ON ".$this->table_obj_reference.".".$this->obj_pk."=".$this->table_obj_data.".".$this->obj_pk." ";
1960  }
1961  else
1962  {
1963  // Use inner join instead of left join to improve performance
1964  $innerjoin = "JOIN ".$this->table_obj_data." ON v.child=".$this->table_obj_data.".".$this->obj_pk." ";
1965  }
1966 
1967  $query = 'SELECT * FROM '.$this->table_tree.' s, '.$this->table_tree.' v '.
1968  $innerjoin.
1969  'WHERE s.child = %s '.
1970  'AND s.parent = v.child '.
1971  'AND s.lft > v.lft '.
1972  'AND s.rgt < v.rgt '.
1973  'AND s.'.$this->tree_pk.' = %s '.
1974  'AND v.'.$this->tree_pk.' = %s';
1975  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
1976  $a_node_id,
1977  $this->tree_id,
1978  $this->tree_id));
1979  $row = $ilDB->fetchAssoc($res);
1980  return $this->fetchNodeData($row);
1981  }
1982 
1990  function isGrandChild($a_startnode_id,$a_querynode_id)
1991  {
1992  global $ilDB;
1993 
1994  if (!isset($a_startnode_id) or !isset($a_querynode_id))
1995  {
1996  return false;
1997  }
1998 
1999  $query = 'SELECT * FROM '.$this->table_tree.' s, '.$this->table_tree.' v '.
2000  'WHERE s.child = %s '.
2001  'AND v.child = %s '.
2002  'AND s.'.$this->tree_pk.' = %s '.
2003  'AND v.'.$this->tree_pk.' = %s '.
2004  'AND v.lft BETWEEN s.lft AND s.rgt '.
2005  'AND v.rgt BETWEEN s.lft AND s.rgt';
2006  $res = $ilDB->queryF(
2007  $query,
2008  array('integer','integer','integer','integer'),
2009  array(
2010  $a_startnode_id,
2011  $a_querynode_id,
2012  $this->tree_id,
2013  $this->tree_id));
2014 
2015  return $res->numRows();
2016  }
2017 
2026  function addTree($a_tree_id,$a_node_id = -1)
2027  {
2028  global $ilDB;
2029 
2030  // FOR SECURITY addTree() IS NOT ALLOWED ON MAIN TREE
2031  if($this->__isMainTree())
2032  {
2033  $message = sprintf('%s::addTree(): Operation not allowed on main tree! $a_tree_if: %s $a_node_id: %s',
2034  get_class($this),
2035  $a_tree_id,
2036  $a_node_id);
2037  $this->log->write($message,$this->log->FATAL);
2038  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2039  }
2040 
2041  if (!isset($a_tree_id))
2042  {
2043  $this->ilErr->raiseError(get_class($this)."::addTree(): No tree_id given! ",$this->ilErr->WARNING);
2044  }
2045 
2046  if ($a_node_id <= 0)
2047  {
2048  $a_node_id = $a_tree_id;
2049  }
2050 
2051  $query = 'INSERT INTO '.$this->table_tree.' ('.
2052  $this->tree_pk.', child,parent,lft,rgt,depth) '.
2053  'VALUES '.
2054  '(%s,%s,%s,%s,%s,%s)';
2055  $res = $ilDB->manipulateF($query,array('integer','integer','integer','integer','integer','integer'),array(
2056  $a_tree_id,
2057  $a_node_id,
2058  0,
2059  1,
2060  2,
2061  1));
2062 
2063  return true;
2064  }
2065 
2073  function getNodeDataByType($a_type)
2074  {
2075  global $ilDB;
2076 
2077  if (!isset($a_type) or (!is_string($a_type)))
2078  {
2079  $this->ilErr->raiseError(get_class($this)."::getNodeDataByType(): Type not given or wrong datatype!",$this->ilErr->WARNING);
2080  }
2081 
2082  $data = array(); // node_data
2083  $row = ""; // fetched row
2084  $left = ""; // tree_left
2085  $right = ""; // tree_right
2086 
2087  $query = 'SELECT * FROM '.$this->table_tree.' '.
2088  'WHERE '.$this->tree_pk.' = %s '.
2089  'AND parent = %s ';
2090  $res = $ilDB->queryF($query,array('integer','integer'),array(
2091  $this->tree_id,
2092  0));
2093 
2094  while ($row = $ilDB->fetchObject($res))
2095  {
2096  $left = $row->lft;
2097  $right = $row->rgt;
2098  }
2099 
2100  $query = 'SELECT * FROM '.$this->table_tree.' '.
2101  $this->buildJoin().
2102  'WHERE '.$this->table_obj_data.'.type = %s '.
2103  'AND '.$this->table_tree.'.lft BETWEEN %s AND %s '.
2104  'AND '.$this->table_tree.'.rgt BETWEEN %s AND %s '.
2105  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2106  $res = $ilDB->queryF($query,array('text','integer','integer','integer','integer','integer'),array(
2107  $a_type,
2108  $left,
2109  $right,
2110  $left,
2111  $right,
2112  $this->tree_id));
2113 
2114  while($row = $ilDB->fetchAssoc($res))
2115  {
2116  $data[] = $this->fetchNodeData($row);
2117  }
2118 
2119  return $data;
2120  }
2121 
2129  function removeTree($a_tree_id)
2130  {
2131  global $ilDB;
2132 
2133  // OPERATION NOT ALLOWED ON MAIN TREE
2134  if($this->__isMainTree())
2135  {
2136  $message = sprintf('%s::removeTree(): Operation not allowed on main tree! $a_tree_if: %s',
2137  get_class($this),
2138  $a_tree_id);
2139  $this->log->write($message,$this->log->FATAL);
2140  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2141  }
2142  if (!$a_tree_id)
2143  {
2144  $this->ilErr->raiseError(get_class($this)."::removeTree(): No tree_id given! Action aborted",$this->ilErr->MESSAGE);
2145  }
2146 
2147  $query = 'DELETE FROM '.$this->table_tree.
2148  ' WHERE '.$this->tree_pk.' = %s ';
2149  $res = $ilDB->manipulateF($query,array('integer'),array($a_tree_id));
2150  return true;
2151  }
2152 
2160  function saveSubTree($a_node_id, $a_set_deleted = false)
2161  {
2162  global $ilDB;
2163 
2164  if (!$a_node_id)
2165  {
2166  $message = sprintf('%s::saveSubTree(): No valid parameter given! $a_node_id: %s',
2167  get_class($this),
2168  $a_node_id);
2169  $this->log->write($message,$this->log->FATAL);
2170  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2171  }
2172 
2173  // LOCKED ###############################################
2174  if($this->__isMainTree())
2175  {
2176  $ilDB->lockTables(
2177  array(
2178  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE),
2179  1 => array('name' => 'object_reference', 'type' => ilDB::LOCK_WRITE)));
2180 
2181  #ilDB::_lockTables(array('tree' => 'WRITE',
2182  # 'object_reference' => 'WRITE'));
2183 
2184  }
2185 
2186  // GET LEFT AND RIGHT VALUE
2187  $query = 'SELECT * FROM '.$this->table_tree.' '.
2188  'WHERE '.$this->tree_pk.' = %s '.
2189  'AND child = %s ';
2190  $res = $ilDB->queryF($query,array('integer','integer'),array(
2191  $this->tree_id,
2192  $a_node_id));
2193 
2194  while($row = $ilDB->fetchObject($res))
2195  {
2196  $lft = $row->lft;
2197  $rgt = $row->rgt;
2198  }
2199 
2200  // GET ALL SUBNODES
2201  $query = 'SELECT child FROM '.$this->table_tree.' '.
2202  'WHERE '.$this->tree_pk.' = %s '.
2203  'AND lft BETWEEN %s AND %s ';
2204  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2205  $this->tree_id,
2206  $lft,
2207  $rgt));
2208 
2209  $subnodes = array();
2210  while($row = $ilDB->fetchAssoc($res))
2211  {
2212  $subnodes[] = $row['child'];
2213  }
2214 
2215  if(!count($subnodes))
2216  {
2217  // possibly already deleted
2218 
2219  // Unlock locked tables before returning
2220  if($this->__isMainTree())
2221  {
2222  $ilDB->unlockTables();
2223  }
2224 
2225  return false;
2226  }
2227 
2228  // SAVE SUBTREE
2229  foreach($subnodes as $child)
2230  {
2231  // set node as deleted
2232  if ($a_set_deleted)
2233  {
2234  // TODO: new method that expects an array of ids
2235  ilObject::_setDeletedDate($child);
2236  }
2237  }
2238 
2239  // Set the nodes deleted (negative tree id)
2240  $query = 'UPDATE '.$this->table_tree.' '.
2241  'SET tree = %s '.
2242  'WHERE '.$this->tree_pk.' = %s '.
2243  'AND lft BETWEEN %s AND %s ';
2244  $res = $ilDB->manipulateF($query,array('integer','integer','integer','integer'),array(
2245  -$a_node_id,
2246  $this->tree_id,
2247  $lft,
2248  $rgt));
2249 
2250  if($this->__isMainTree())
2251  {
2252  $ilDB->unlockTables();
2253  }
2254 
2255  // LOCKED ###############################################
2256  return true;
2257  }
2258 
2262  function isDeleted($a_node_id)
2263  {
2264  return $this->isSaved($a_node_id);
2265  }
2266 
2270  function isSaved($a_node_id)
2271  {
2272  global $ilDB;
2273 
2274  // is saved cache
2275  if ($this->isCacheUsed() && isset($this->is_saved_cache[$a_node_id]))
2276  {
2277 //echo "<br>issavedhit";
2278  return $this->is_saved_cache[$a_node_id];
2279  }
2280 
2281  $query = 'SELECT '.$this->tree_pk.' FROM '.$this->table_tree.' '.
2282  'WHERE child = %s ';
2283  $res = $ilDB->queryF($query,array('integer'),array($a_node_id));
2284  $row = $ilDB->fetchAssoc($res);
2285 
2286  if ($row[$this->tree_pk] < 0)
2287  {
2288  if($this->__isMainTree())
2289  {
2290  $this->is_saved_cache[$a_node_id] = true;
2291  }
2292  return true;
2293  }
2294  else
2295  {
2296  if($this->__isMainTree())
2297  {
2298  $this->is_saved_cache[$a_node_id] = false;
2299  }
2300  return false;
2301  }
2302  }
2303 
2310  function preloadDeleted($a_node_ids)
2311  {
2312  global $ilDB;
2313 
2314  if (!is_array($a_node_ids) || !$this->isCacheUsed())
2315  {
2316  return;
2317  }
2318 
2319  $query = 'SELECT '.$this->tree_pk.', child FROM '.$this->table_tree.' '.
2320  'WHERE '.$ilDB->in("child", $a_node_ids, false, "integer");
2321 
2322  $res = $ilDB->query($query);
2323  while ($row = $ilDB->fetchAssoc($res))
2324  {
2325  if ($row[$this->tree_pk] < 0)
2326  {
2327  if($this->__isMainTree())
2328  {
2329  $this->is_saved_cache[$row["child"]] = true;
2330  }
2331  }
2332  else
2333  {
2334  if($this->__isMainTree())
2335  {
2336  $this->is_saved_cache[$row["child"]] = false;
2337  }
2338  }
2339  }
2340  }
2341 
2342 
2349  function getSavedNodeData($a_parent_id)
2350  {
2351  global $ilDB;
2352 
2353  if (!isset($a_parent_id))
2354  {
2355  $this->ilErr->raiseError(get_class($this)."::getSavedNodeData(): No node_id given!",$this->ilErr->WARNING);
2356  }
2357 
2358  $query = 'SELECT * FROM '.$this->table_tree.' '.
2359  $this->buildJoin().
2360  'WHERE '.$this->table_tree.'.'.$this->tree_pk.' < %s '.
2361  'AND '.$this->table_tree.'.parent = %s';
2362  $res = $ilDB->queryF($query,array('integer','integer'),array(
2363  0,
2364  $a_parent_id));
2365 
2366  while($row = $ilDB->fetchAssoc($res))
2367  {
2368  $saved[] = $this->fetchNodeData($row);
2369  }
2370 
2371  return $saved ? $saved : array();
2372  }
2373 
2380  function getParentId($a_node_id)
2381  {
2382  global $ilDB;
2383 
2384  if (!isset($a_node_id))
2385  {
2386  $this->ilErr->raiseError(get_class($this)."::getParentId(): No node_id given! ",$this->ilErr->WARNING);
2387  }
2388 
2389  $query = 'SELECT parent FROM '.$this->table_tree.' '.
2390  'WHERE child = %s '.
2391  'AND '.$this->tree_pk.' = %s ';
2392  $res = $ilDB->queryF($query,array('integer','integer'),array(
2393  $a_node_id,
2394  $this->tree_id));
2395 
2396  $row = $ilDB->fetchObject($res);
2397  return $row->parent;
2398  }
2399 
2406  function getLeftValue($a_node_id)
2407  {
2408  global $ilDB;
2409 
2410  if (!isset($a_node_id))
2411  {
2412  $this->ilErr->raiseError(get_class($this)."::getLeftValued(): No node_id given! ",$this->ilErr->WARNING);
2413  }
2414 
2415  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2416  'WHERE child = %s '.
2417  'AND '.$this->tree_pk.' = %s ';
2418  $res = $ilDB->queryF($query,array('integer','integer'),array(
2419  $a_node_id,
2420  $this->tree_id));
2421  $row = $ilDB->fetchObject($res);
2422  return $row->lft;
2423  }
2424 
2431  function getChildSequenceNumber($a_node, $type = "")
2432  {
2433  global $ilDB;
2434 
2435  if (!isset($a_node))
2436  {
2437  $this->ilErr->raiseError(get_class($this)."::getChildSequenceNumber(): No node_id given! ",$this->ilErr->WARNING);
2438  }
2439 
2440  if($type)
2441  {
2442  $query = 'SELECT count(*) cnt FROM '.$this->table_tree.' '.
2443  $this->buildJoin().
2444  'WHERE lft <= %s '.
2445  'AND type = %s '.
2446  'AND parent = %s '.
2447  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2448 
2449  $res = $ilDB->queryF($query,array('integer','text','integer','integer'),array(
2450  $a_node['lft'],
2451  $type,
2452  $a_node['parent'],
2453  $this->tree_id));
2454  }
2455  else
2456  {
2457  $query = 'SELECT count(*) cnt FROM '.$this->table_tree.' '.
2458  $this->buildJoin().
2459  'WHERE lft <= %s '.
2460  'AND parent = %s '.
2461  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2462 
2463  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2464  $a_node['lft'],
2465  $a_node['parent'],
2466  $this->tree_id));
2467 
2468  }
2469  $row = $ilDB->fetchAssoc($res);
2470  return $row["cnt"];
2471  }
2472 
2479  function readRootId()
2480  {
2481  global $ilDB;
2482 
2483  $query = 'SELECT child FROM '.$this->table_tree.' '.
2484  'WHERE parent = %s '.
2485  'AND '.$this->tree_pk.' = %s ';
2486  $res = $ilDB->queryF($query,array('integer','integer'),array(
2487  0,
2488  $this->tree_id));
2489  $row = $ilDB->fetchObject($res);
2490  $this->root_id = $row->child;
2491  return $this->root_id;
2492  }
2493 
2499  function getRootId()
2500  {
2501  return $this->root_id;
2502  }
2503  function setRootId($a_root_id)
2504  {
2505  $this->root_id = $a_root_id;
2506  }
2507 
2513  function getTreeId()
2514  {
2515  return $this->tree_id;
2516  }
2517 
2523  function setTreeId($a_tree_id)
2524  {
2525  $this->tree_id = $a_tree_id;
2526  }
2527 
2535  function fetchSuccessorNode($a_node_id, $a_type = "")
2536  {
2537  global $ilDB;
2538 
2539  if (!isset($a_node_id))
2540  {
2541  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
2542  }
2543 
2544  // get lft value for current node
2545  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2546  'WHERE '.$this->table_tree.'.child = %s '.
2547  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2548  $res = $ilDB->queryF($query,array('integer','integer'),array(
2549  $a_node_id,
2550  $this->tree_id));
2551 
2552  $curr_node = $ilDB->fetchAssoc($res);
2553 
2554  if($a_type)
2555  {
2556  $query = 'SELECT * FROM '.$this->table_tree.' '.
2557  $this->buildJoin().
2558  'WHERE lft > %s '.
2559  'AND '.$this->table_obj_data.'.type = %s '.
2560  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2561  'ORDER BY lft ';
2562  $ilDB->setLimit(1);
2563  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
2564  $curr_node['lft'],
2565  $a_type,
2566  $this->tree_id));
2567  }
2568  else
2569  {
2570  $query = 'SELECT * FROM '.$this->table_tree.' '.
2571  $this->buildJoin().
2572  'WHERE lft > %s '.
2573  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2574  'ORDER BY lft ';
2575  $ilDB->setLimit(1);
2576  $res = $ilDB->queryF($query,array('integer','integer'),array(
2577  $curr_node['lft'],
2578  $this->tree_id));
2579  }
2580 
2581  if ($res->numRows() < 1)
2582  {
2583  return false;
2584  }
2585  else
2586  {
2587  $row = $ilDB->fetchAssoc($res);
2588  return $this->fetchNodeData($row);
2589  }
2590  }
2591 
2599  function fetchPredecessorNode($a_node_id, $a_type = "")
2600  {
2601  global $ilDB;
2602 
2603  if (!isset($a_node_id))
2604  {
2605  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
2606  }
2607 
2608  // get lft value for current node
2609  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2610  'WHERE '.$this->table_tree.'.child = %s '.
2611  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2612  $res = $ilDB->queryF($query,array('integer','integer'),array(
2613  $a_node_id,
2614  $this->tree_id));
2615 
2616  $curr_node = $ilDB->fetchAssoc($res);
2617 
2618  if($a_type)
2619  {
2620  $query = 'SELECT * FROM '.$this->table_tree.' '.
2621  $this->buildJoin().
2622  'WHERE lft < %s '.
2623  'AND '.$this->table_obj_data.'.type = %s '.
2624  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2625  'ORDER BY lft DESC';
2626  $ilDB->setLimit(1);
2627  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
2628  $curr_node['lft'],
2629  $a_type,
2630  $this->tree_id));
2631  }
2632  else
2633  {
2634  $query = 'SELECT * FROM '.$this->table_tree.' '.
2635  $this->buildJoin().
2636  'WHERE lft < %s '.
2637  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2638  'ORDER BY lft DESC';
2639  $ilDB->setLimit(1);
2640  $res = $ilDB->queryF($query,array('integer','integer'),array(
2641  $curr_node['lft'],
2642  $this->tree_id));
2643  }
2644 
2645  if ($res->numRows() < 1)
2646  {
2647  return false;
2648  }
2649  else
2650  {
2651  $row = $ilDB->fetchAssoc($res);
2652  return $this->fetchNodeData($row);
2653  }
2654  }
2655 
2664  function renumber($node_id = 1, $i = 1)
2665  {
2666  global $ilDB;
2667 
2668  // LOCKED ###################################
2669  if($this->__isMainTree())
2670  {
2671  /*
2672  ilDB::_lockTables(array($this->table_tree => 'WRITE',
2673  $this->table_obj_data => 'WRITE',
2674  $this->table_obj_reference => 'WRITE',
2675  'object_translation' => 'WRITE',
2676  'object_data od' => 'WRITE',
2677  'container_reference cr' => 'WRITE'));
2678  */
2679  $ilDB->lockTables(
2680  array(
2681  0 => array('name' => $this->table_tree, 'type' => ilDB::LOCK_WRITE),
2682  1 => array('name' => $this->table_obj_data, 'type' => ilDB::LOCK_WRITE),
2683  2 => array('name' => $this->table_obj_reference, 'type' => ilDB::LOCK_WRITE),
2684  3 => array('name' => 'object_translation', 'type' => ilDB::LOCK_WRITE),
2685  4 => array('name' => 'object_data', 'type' => ilDB::LOCK_WRITE, 'alias' => 'od'),
2686  5 => array('name' => 'container_reference', 'type' => ilDB::LOCK_WRITE, 'alias' => 'cr')
2687  ));
2688  }
2689  $return = $this->__renumber($node_id,$i);
2690  if($this->__isMainTree())
2691  {
2692  $ilDB->unlockTables();
2693  }
2694  // LOCKED ###################################
2695  return $return;
2696  }
2697 
2698  // PRIVATE
2708  function __renumber($node_id = 1, $i = 1)
2709  {
2710  global $ilDB;
2711 
2712  $query = 'UPDATE '.$this->table_tree.' SET lft = %s WHERE child = %s';
2713  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2714  $i,
2715  $node_id));
2716 
2717  $childs = $this->getChilds($node_id);
2718 
2719  foreach ($childs as $child)
2720  {
2721  $i = $this->__renumber($child["child"],$i+1);
2722  }
2723  $i++;
2724 
2725  // Insert a gap at the end of node, if the node has children
2726  if (count($childs) > 0)
2727  {
2728  $i += $this->gap * 2;
2729  }
2730 
2731 
2732  $query = 'UPDATE '.$this->table_tree.' SET rgt = %s WHERE child = %s';
2733  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2734  $i,
2735  $node_id));
2736  return $i;
2737  }
2738 
2739 
2750  function checkForParentType($a_ref_id,$a_type,$a_exclude_source_check = false)
2751  {
2752  // Try to return a cached result
2753  if ($this->isCacheUsed() &&
2754  array_key_exists($a_ref_id.'.'.$a_type, $this->parent_type_cache)) {
2755  return $this->parent_type_cache[$a_ref_id.'.'.$a_type];
2756  }
2757 
2758  if(!$this->isInTree($a_ref_id))
2759  {
2760  // Store up to 1000 results in cache
2761  if ($this->__isMainTree() && count($this->parent_type_cache) < 1000) {
2762  $this->parent_type_cache[$a_ref_id.'.'.$a_type] = false;
2763  }
2764  return false;
2765  }
2766  $path = array_reverse($this->getPathFull($a_ref_id));
2767 
2768  // remove first path entry as it is requested node
2769  if($a_exclude_source_check)
2770  {
2771  array_shift($path);
2772  }
2773 
2774  foreach($path as $node)
2775  {
2776  if($node["type"] == $a_type)
2777  {
2778  // Store up to 1000 results in cache
2779  if ($this->__isMainTree() && count($this->parent_type_cache) < 1000) {
2780  $this->parent_type_cache[$a_ref_id.'.'.$a_type] = $node["child"];
2781  }
2782  return $node["child"];
2783  }
2784  }
2785  // Store up to 1000 results in cache
2786  if ($this->__isMainTree() && count($this->parent_type_cache) < 1000) {
2787  $this->parent_type_cache[$a_ref_id.'.'.$a_type] = false;
2788  }
2789  return 0;
2790  }
2791 
2801  function _removeEntry($a_tree,$a_child,$a_db_table = "tree")
2802  {
2803  global $ilDB,$ilLog,$ilErr;
2804 
2805  if($a_db_table === 'tree')
2806  {
2807  if($a_tree == 1 and $a_child == ROOT_FOLDER_ID)
2808  {
2809  $message = sprintf('%s::_removeEntry(): Tried to delete root node! $a_tree: %s $a_child: %s',
2810  get_class($this),
2811  $a_tree,
2812  $a_child);
2813  $ilLog->write($message,$ilLog->FATAL);
2814  $ilErr->raiseError($message,$ilErr->WARNING);
2815  }
2816  }
2817 
2818  $query = 'DELETE FROM '.$a_db_table.' '.
2819  'WHERE tree = %s '.
2820  'AND child = %s ';
2821  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2822  $a_tree,
2823  $a_child));
2824 
2825  }
2826 
2827  // PRIVATE METHODS
2834  function __isMainTree()
2835  {
2836  return $this->table_tree === 'tree';
2837  }
2838 
2847  function __checkDelete($a_node)
2848  {
2849  global $ilDB;
2850 
2851  // get subtree by lft,rgt
2852  $query = 'SELECT * FROM '.$this->table_tree.' '.
2853  'WHERE lft >= %s '.
2854  'AND rgt <= %s '.
2855  'AND '.$this->tree_pk.' = %s ';
2856  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2857  $a_node['lft'],
2858  $a_node['rgt'],
2859  $a_node[$this->tree_pk]));
2860 
2861  $counter = (int) $lft_childs = array();
2862  while($row = $ilDB->fetchObject($res))
2863  {
2864  $lft_childs[$row->child] = $row->parent;
2865  ++$counter;
2866  }
2867 
2868  // CHECK FOR DUPLICATE CHILD IDS
2869  if($counter != count($lft_childs))
2870  {
2871  $message = sprintf('%s::__checkTree(): Duplicate entries for "child" in maintree! $a_node_id: %s',
2872  get_class($this),
2873  $a_node['child']);
2874  $this->log->write($message,$this->log->FATAL);
2875  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2876  }
2877 
2878  // GET SUBTREE BY PARENT RELATION
2879  $parent_childs = array();
2880  $this->__getSubTreeByParentRelation($a_node['child'],$parent_childs);
2881  $this->__validateSubtrees($lft_childs,$parent_childs);
2882 
2883  return true;
2884  }
2885 
2886  function __getSubTreeByParentRelation($a_node_id,&$parent_childs)
2887  {
2888  global $ilDB;
2889 
2890  // GET PARENT ID
2891  $query = 'SELECT * FROM '.$this->table_tree.' '.
2892  'WHERE child = %s '.
2893  'AND tree = %s ';
2894  $res = $ilDB->queryF($query,array('integer','integer'),array(
2895  $a_node_id,
2896  $this->tree_id));
2897 
2898  $counter = 0;
2899  while($row = $ilDB->fetchObject($res))
2900  {
2901  $parent_childs[$a_node_id] = $row->parent;
2902  ++$counter;
2903  }
2904  // MULTIPLE ENTRIES
2905  if($counter > 1)
2906  {
2907  $message = sprintf('%s::__getSubTreeByParentRelation(): Multiple entries in maintree! $a_node_id: %s',
2908  get_class($this),
2909  $a_node_id);
2910  $this->log->write($message,$this->log->FATAL);
2911  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2912  }
2913 
2914  // GET ALL CHILDS
2915  $query = 'SELECT * FROM '.$this->table_tree.' '.
2916  'WHERE parent = %s ';
2917  $res = $ilDB->queryF($query,array('integer'),array($a_node_id));
2918 
2919  while($row = $ilDB->fetchObject($res))
2920  {
2921  // RECURSION
2922  $this->__getSubTreeByParentRelation($row->child,$parent_childs);
2923  }
2924  return true;
2925  }
2926 
2927  function __validateSubtrees(&$lft_childs,$parent_childs)
2928  {
2929  // SORT BY KEY
2930  ksort($lft_childs);
2931  ksort($parent_childs);
2932 
2933  if(count($lft_childs) != count($parent_childs))
2934  {
2935  $message = sprintf('%s::__validateSubtrees(): (COUNT) Tree is corrupted! Left/Right subtree does not comply .'.
2936  'with parent relation',
2937  get_class($this));
2938  $this->log->write($message,$this->log->FATAL);
2939  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2940  }
2941 
2942  foreach($lft_childs as $key => $value)
2943  {
2944  if($parent_childs[$key] != $value)
2945  {
2946  $message = sprintf('%s::__validateSubtrees(): (COMPARE) Tree is corrupted! Left/Right subtree does not comply '.
2947  'with parent relation',
2948  get_class($this));
2949  $this->log->write($message,$this->log->FATAL);
2950  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2951  }
2952  if($key == ROOT_FOLDER_ID)
2953  {
2954  $message = sprintf('%s::__validateSubtrees(): (ROOT_FOLDER) Tree is corrupted! Tried to delete root folder',
2955  get_class($this));
2956  $this->log->write($message,$this->log->FATAL);
2957  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2958  }
2959  }
2960  return true;
2961  }
2962 
2972  public function moveTree($a_source_id,$a_target_id,$a_location = IL_LAST_NODE)
2973  {
2974  global $ilDB;
2975 
2976  if($this->__isMainTree())
2977  {
2978  #ilDB::_lockTables(array('tree' => 'WRITE'));
2979  $ilDB->lockTables(
2980  array(
2981  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
2982 
2983  }
2984  // Receive node infos for source and target
2985  $query = 'SELECT * FROM '.$this->table_tree.' '.
2986  'WHERE ( child = %s OR child = %s ) '.
2987  'AND tree = %s ';
2988  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2989  $a_source_id,
2990  $a_target_id,
2991  $this->tree_id));
2992 
2993  // Check in tree
2994  if($res->numRows() != 2)
2995  {
2996  if($this->__isMainTree())
2997  {
2998  $ilDB->unlockTables();
2999  }
3000  $this->log->write(__METHOD__.' Objects not found in tree!',$this->log->FATAL);
3001  $this->ilErr->raiseError('Error moving node',$this->ilErr->WARNING);
3002  }
3003  while($row = $ilDB->fetchObject($res))
3004  {
3005  if($row->child == $a_source_id)
3006  {
3007  $source_lft = $row->lft;
3008  $source_rgt = $row->rgt;
3009  $source_depth = $row->depth;
3010  $source_parent = $row->parent;
3011  }
3012  else
3013  {
3014  $target_lft = $row->lft;
3015  $target_rgt = $row->rgt;
3016  $target_depth = $row->depth;
3017  }
3018  }
3019 
3020  #var_dump("<pre>",$source_lft,$source_rgt,$source_depth,$target_lft,$target_rgt,$target_depth,"<pre>");
3021  // Check target not child of source
3022  if($target_lft >= $source_lft and $target_rgt <= $source_rgt)
3023  {
3024  if($this->__isMainTree())
3025  {
3026  $ilDB->unlockTables();
3027  }
3028  $this->log->write(__METHOD__.' Target is child of source',$this->log->FATAL);
3029  $this->ilErr->raiseError('Error moving node',$this->ilErr->WARNING);
3030  }
3031 
3032  // Now spread the tree at the target location. After this update the table should be still in a consistent state.
3033  // implementation for IL_LAST_NODE
3034  $spread_diff = $source_rgt - $source_lft + 1;
3035  #var_dump("<pre>","SPREAD_DIFF: ",$spread_diff,"<pre>");
3036 
3037  $query = 'UPDATE '.$this->table_tree.' SET '.
3038  'lft = CASE WHEN lft > %s THEN lft + %s ELSE lft END, '.
3039  'rgt = CASE WHEN rgt >= %s THEN rgt + %s ELSE rgt END '.
3040  'WHERE tree = %s ';
3041  $res = $ilDB->manipulateF($query,array('integer','integer','integer','integer','integer'),array(
3042  $target_rgt,
3043  $spread_diff,
3044  $target_rgt,
3045  $spread_diff,
3046  $this->tree_id));
3047 
3048  // Maybe the source node has been updated, too.
3049  // Check this:
3050  if($source_lft > $target_rgt)
3051  {
3052  $where_offset = $spread_diff;
3053  $move_diff = $target_rgt - $source_lft - $spread_diff;
3054  }
3055  else
3056  {
3057  $where_offset = 0;
3058  $move_diff = $target_rgt - $source_lft;
3059  }
3060  $depth_diff = $target_depth - $source_depth + 1;
3061 
3062 
3063  $query = 'UPDATE '.$this->table_tree.' SET '.
3064  'parent = CASE WHEN parent = %s THEN %s ELSE parent END, '.
3065  'rgt = rgt + %s, '.
3066  'lft = lft + %s, '.
3067  'depth = depth + %s '.
3068  'WHERE lft >= %s '.
3069  'AND rgt <= %s '.
3070  'AND tree = %s ';
3071  $res = $ilDB->manipulateF($query,
3072  array('integer','integer','integer','integer','integer','integer','integer','integer'),
3073  array(
3074  $source_parent,
3075  $a_target_id,
3076  $move_diff,
3077  $move_diff,
3078  $depth_diff,
3079  $source_lft + $where_offset,
3080  $source_rgt + $where_offset,
3081  $this->tree_id));
3082 
3083  // done: close old gap
3084  $query = 'UPDATE '.$this->table_tree.' SET '.
3085  'lft = CASE WHEN lft >= %s THEN lft - %s ELSE lft END, '.
3086  'rgt = CASE WHEN rgt >= %s THEN rgt - %s ELSE rgt END '.
3087  'WHERE tree = %s ';
3088 
3089  $res = $ilDB->manipulateF($query,
3090  array('integer','integer','integer','integer','integer'),
3091  array(
3092  $source_lft + $where_offset,
3093  $spread_diff,
3094  $source_rgt +$where_offset,
3095  $spread_diff,
3096  $this->tree_id));
3097 
3098  if($this->__isMainTree())
3099  {
3100  $ilDB->unlockTables();
3101  }
3102  return true;
3103  }
3104 
3112  public function getRbacSubtreeInfo($a_endnode_id)
3113  {
3114  global $ilDB;
3115 
3116  $query = "SELECT t2.lft lft, t2.rgt rgt, t2.child child, type ".
3117  "FROM ".$this->table_tree." t1 ".
3118  "JOIN ".$this->table_tree." t2 ON (t2.lft BETWEEN t1.lft AND t1.rgt) ".
3119  "JOIN ".$this->table_obj_reference." obr ON t2.child = obr.ref_id ".
3120  "JOIN ".$this->table_obj_data." obd ON obr.obj_id = obd.obj_id ".
3121  "WHERE t1.child = ".$ilDB->quote($a_endnode_id,'integer')." ".
3122  "AND t1.".$this->tree_pk." = ".$ilDB->quote($this->tree_id,'integer')." ".
3123  "AND t2.".$this->tree_pk." = ".$ilDB->quote($this->tree_id,'integer')." ".
3124  "ORDER BY t2.lft";
3125 
3126  $res = $ilDB->query($query);
3127  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
3128  {
3129  $nodes[$row->child]['lft'] = $row->lft;
3130  $nodes[$row->child]['rgt'] = $row->rgt;
3131  $nodes[$row->child]['child']= $row->child;
3132  $nodes[$row->child]['type'] = $row->type;
3133 
3134  }
3135  return (array) $nodes;
3136  }
3137 } // END class.tree
3138 ?>