ILIAS  release_4-3 Revision
 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' => $this->table_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' => $this->table_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' => $this->table_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' => $this->table_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  if (DEVMODE == 1)
925  {
926  try
927  {
928  throw new Exception("e");
929  }
930  catch(Exception $e)
931  {
932  echo $e->getTraceAsString();
933  exit;
934  }
935  }
936 
937  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
938  }
939 
940 
941  $fields = array('integer','integer','integer');
942  $data = array($a_node['lft'],$a_node['rgt'],$this->tree_id);
943  $type_str = '';
944 
945  if (is_array($a_type))
946  {
947  $type_str = "AND ".$ilDB->in($this->table_obj_data.".type", $a_type, false, "text");
948  }
949  else if(strlen($a_type))
950  {
951  //$fields[] = 'text';
952  //$data[] = $a_type;
953  $type_str = "AND ".$this->table_obj_data.".type = ".
954  $ilDB->quote($a_type, "text");
955  }
956 
957  $query = "SELECT * FROM ".$this->table_tree." ".
958  $this->buildJoin().
959  "WHERE ".$this->table_tree.".lft BETWEEN %s AND %s ".
960  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
961  $type_str.
962  "ORDER BY ".$this->table_tree.".lft";
963  $res = $ilDB->queryF($query,$fields,$data);
964  while($row = $ilDB->fetchAssoc($res))
965  {
966  if($a_with_data)
967  {
968  $subtree[] = $this->fetchNodeData($row);
969  }
970  else
971  {
972  $subtree[] = $row['child'];
973  }
974  if($this->__isMainTree())
975  {
976  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
977  $this->in_tree_cache[$row['child']] = true;
978  }
979  }
980 
981  return $subtree ? $subtree : array();
982  }
983 
992  function getSubTreeTypes($a_node,$a_filter = 0)
993  {
994  $a_filter = $a_filter ? $a_filter : array();
995 
996  foreach($this->getSubtree($this->getNodeData($a_node)) as $node)
997  {
998  if(in_array($node["type"],$a_filter))
999  {
1000  continue;
1001  }
1002  $types["$node[type]"] = $node["type"];
1003  }
1004  return $types ? $types : array();
1005  }
1006 
1012  function deleteTree($a_node)
1013  {
1014  global $ilDB;
1015 
1016  $GLOBALS['ilLog']->write(__METHOD__.': Delete tree with node '. $a_node);
1017 
1018  if (!is_array($a_node))
1019  {
1020  $this->ilErr->raiseError(get_class($this)."::deleteTree(): Wrong datatype for node_data! ",$this->ilErr->WARNING);
1021  }
1022  if($this->__isMainTree() and $a_node[$this->tree_pk] === 1)
1023  {
1024  if($a_node['lft'] <= 1 or $a_node['rgt'] <= 2)
1025  {
1026  $message = sprintf('%s::deleteTree(): Invalid parameters given: $a_node["lft"]: %s, $a_node["rgt"] %s',
1027  get_class($this),
1028  $a_node['lft'],
1029  $a_node['rgt']);
1030 
1031  $this->log->write($message,$this->log->FATAL);
1032  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1033  }
1034  else if(!$this->__checkDelete($a_node))
1035  {
1036  $message = sprintf('%s::deleteTree(): Check delete failed: $a_node["lft"]: %s, $a_node["rgt"] %s',
1037  get_class($this),
1038  $a_node['lft'],
1039  $a_node['rgt']);
1040  $this->log->write($message,$this->log->FATAL);
1041  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1042  }
1043 
1044  }
1045  $diff = $a_node["rgt"] - $a_node["lft"] + 1;
1046 
1047 
1048  // LOCKED ###########################################################
1049  // get lft and rgt values. Don't trust parameter lft/rgt values of $a_node
1050  if($this->__isMainTree())
1051  {
1052  #ilDB::_lockTables(array('tree' => 'WRITE'));
1053  $ilDB->lockTables(
1054  array(
1055  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
1056 
1057  }
1058 
1059  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
1060  'WHERE child = %s '.
1061  'AND '.$this->tree_pk.' = %s ',
1062  $ilDB->quote($a_node['child'],'integer'),
1063  $ilDB->quote($a_node[$this->tree_pk],'integer'));
1064  $res = $ilDB->query($query);
1065  while($row = $ilDB->fetchObject($res))
1066  {
1067  $a_node['lft'] = $row->lft;
1068  $a_node['rgt'] = $row->rgt;
1069  $diff = $a_node["rgt"] - $a_node["lft"] + 1;
1070  }
1071 
1072  // delete subtree
1073  $query = sprintf('DELETE FROM '.$this->table_tree.' '.
1074  'WHERE lft BETWEEN %s AND %s '.
1075  'AND rgt BETWEEN %s AND %s '.
1076  'AND '.$this->tree_pk.' = %s',
1077  $ilDB->quote($a_node['lft'],'integer'),
1078  $ilDB->quote($a_node['rgt'],'integer'),
1079  $ilDB->quote($a_node['lft'],'integer'),
1080  $ilDB->quote($a_node['rgt'],'integer'),
1081  $ilDB->quote($a_node[$this->tree_pk],'integer'));
1082  $res = $ilDB->manipulate($query);
1083 
1084  // Performance improvement: We only close the gap, if the node
1085  // is not in a trash tree, and if the resulting gap will be
1086  // larger than twice the gap value
1087  if ($a_node[$this->tree_pk] >= 0 && $a_node['rgt'] - $a_node['lft'] >= $this->gap * 2)
1088  {
1089  //$this->log->write('ilTree.deleteTree('.$a_node['child'].') closing gap at '.$a_node['lft'].'...'.$a_node['rgt']);
1090  // close gaps
1091  $query = sprintf('UPDATE '.$this->table_tree.' SET '.
1092  'lft = CASE WHEN lft > %s THEN lft - %s ELSE lft END, '.
1093  'rgt = CASE WHEN rgt > %s THEN rgt - %s ELSE rgt END '.
1094  'WHERE '.$this->tree_pk.' = %s ',
1095  $ilDB->quote($a_node['lft'],'integer'),
1096  $ilDB->quote($diff,'integer'),
1097  $ilDB->quote($a_node['lft'],'integer'),
1098  $ilDB->quote($diff,'integer'),
1099  $ilDB->quote($a_node[$this->tree_pk],'integer'));
1100 
1101  $res = $ilDB->manipulate($query);
1102  }
1103  else
1104  {
1105  //$this->log->write('ilTree.deleteTree('.$a_node['child'].') leaving gap open '.$a_node['lft'].'...'.$a_node['rgt']);
1106  }
1107 
1108  if($this->__isMainTree())
1109  {
1110  #$GLOBALS['ilLog']->write(__METHOD__.': Resetting in tree cache ');
1111  $ilDB->unlockTables();
1112  $this->in_tree_cache = array();
1113  }
1114  // LOCKED ###########################################################
1115  }
1116 
1127  function getPathFull($a_endnode_id, $a_startnode_id = 0)
1128  {
1129  $pathIds =& $this->getPathId($a_endnode_id, $a_startnode_id);
1130 
1131  // We retrieve the full path in a single query to improve performance
1132  global $ilDB;
1133 
1134  // Abort if no path ids were found
1135  if (count($pathIds) == 0)
1136  {
1137  return null;
1138  }
1139 
1140  $inClause = 'child IN (';
1141  for ($i=0; $i < count($pathIds); $i++)
1142  {
1143  if ($i > 0) $inClause .= ',';
1144  $inClause .= $ilDB->quote($pathIds[$i],'integer');
1145  }
1146  $inClause .= ')';
1147 
1148  $q = 'SELECT * '.
1149  'FROM '.$this->table_tree.' '.
1150  $this->buildJoin().' '.
1151  'WHERE '.$inClause.' '.
1152  'AND '.$this->table_tree.'.'.$this->tree_pk.' = '.$this->ilDB->quote($this->tree_id,'integer').' '.
1153  'ORDER BY depth';
1154  $r = $ilDB->query($q);
1155 
1156  $pathFull = array();
1157  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1158  {
1159  $pathFull[] = $this->fetchNodeData($row);
1160 
1161  // Update cache
1162  if ($this->__isMainTree())
1163  {
1164  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child']);
1165  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
1166  }
1167  }
1168  return $pathFull;
1169  }
1178  function getPathIdsUsingNestedSets($a_endnode_id, $a_startnode_id = 0)
1179  {
1180  global $ilDB;
1181 
1182  // The nested sets algorithm is very easy to implement.
1183  // Unfortunately it always does a full table space scan to retrieve the path
1184  // regardless whether indices on lft and rgt are set or not.
1185  // (At least, this is what happens on MySQL 4.1).
1186  // This algorithms performs well for small trees which are deeply nested.
1187 
1188  if (!isset($a_endnode_id))
1189  {
1190  $this->ilErr->raiseError(get_class($this)."::getPathId(): No endnode_id given! ",$this->ilErr->WARNING);
1191  }
1192 
1193  $fields = array('integer','integer','integer');
1194  $data = array($a_endnode_id,$this->tree_id,$this->tree_id);
1195 
1196  $query = "SELECT T2.child ".
1197  "FROM ".$this->table_tree." T1, ".$this->table_tree." T2 ".
1198  "WHERE T1.child = %s ".
1199  "AND T1.lft BETWEEN T2.lft AND T2.rgt ".
1200  "AND T1.".$this->tree_pk." = %s ".
1201  "AND T2.".$this->tree_pk." = %s ".
1202  "ORDER BY T2.depth";
1203  $res = $ilDB->queryF($query,$fields,$data);
1204 
1205  $takeId = $a_startnode_id == 0;
1206  while($row = $ilDB->fetchAssoc($res))
1207  {
1208  if ($takeId || $row['child'] == $a_startnode_id)
1209  {
1210  $takeId = true;
1211  $pathIds[] = $row['child'];
1212  }
1213  }
1214  return $pathIds ? $pathIds : array();
1215  }
1216 
1225  function getPathIdsUsingAdjacencyMap($a_endnode_id, $a_startnode_id = 0)
1226  {
1227  // The adjacency map algorithm is harder to implement than the nested sets algorithm.
1228  // This algorithms performs an index search for each of the path element.
1229  // This algorithms performs well for large trees which are not deeply nested.
1230 
1231  // The $takeId variable is used, to determine if a given id shall be included in the path
1232  $takeId = $a_startnode_id == 0;
1233 
1234  if (!isset($a_endnode_id))
1235  {
1236  $this->ilErr->raiseError(get_class($this)."::getPathId(): No endnode_id given! ",$this->ilErr->WARNING);
1237  }
1238 
1239  global $log, $ilDB;
1240 
1241  if ($this->__isMainTree() && isset($this->depth_cache[$a_endnode_id])
1242  && isset($this->parent_cache[$a_endnode_id]))
1243  {
1244  $nodeDepth = $this->depth_cache[$a_endnode_id];
1245  $parentId = $this->parent_cache[$a_endnode_id];
1246  }
1247  else
1248  {
1249  $types = array('integer','integer');
1250  $data = array($a_endnode_id,$this->tree_id);
1251  $query = 'SELECT t.depth, t.parent '.
1252  'FROM '.$this->table_tree.' t '.
1253  'WHERE child = %s '.
1254  'AND '.$this->tree_pk.' = %s ';
1255  $res = $ilDB->queryF($query,$types,$data);
1256 
1257  if($res->numRows() == 0)
1258  {
1259  return array();
1260  }
1261 
1262  $row = $ilDB->fetchAssoc($res);
1263  $nodeDepth = $row['depth'];
1264  $parentId = $row['parent'];
1265  }
1266 
1267  //$this->writelog('getIdsUsingAdjacencyMap depth='.$nodeDepth);
1268 
1269  // Fetch the node ids. For shallow depths we can fill in the id's directly.
1270  $pathIds = array();
1271  if ($nodeDepth == 1)
1272  {
1273  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1274  if ($takeId) $pathIds[] = $a_endnode_id;
1275  }
1276  else if ($nodeDepth == 2)
1277  {
1278  $takeId = $takeId || $parentId == $a_startnode_id;
1279  if ($takeId) $pathIds[] = $parentId;
1280  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1281  if ($takeId) $pathIds[] = $a_endnode_id;
1282  }
1283  else if ($nodeDepth == 3)
1284  {
1285  $takeId = $takeId || $this->root_id == $a_startnode_id;
1286  if ($takeId) $pathIds[] = $this->root_id;
1287  $takeId = $takeId || $parentId == $a_startnode_id;
1288  if ($takeId) $pathIds[] = $parentId;
1289  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1290  if ($takeId) $pathIds[] = $a_endnode_id;
1291  }
1292  else if ($nodeDepth < 32)
1293  {
1294  // Adjacency Map Tree performs better than
1295  // Nested Sets Tree even for very deep trees.
1296  // The following code construct nested self-joins
1297  // Since we already know the root-id of the tree and
1298  // we also know the id and parent id of the current node,
1299  // we only need to perform $nodeDepth - 3 self-joins.
1300  // We can further reduce the number of self-joins by 1
1301  // by taking into account, that each row in table tree
1302  // contains the id of itself and of its parent.
1303  $qSelect = 't1.child c0';
1304  $qJoin = '';
1305  for ($i = 1; $i < $nodeDepth - 2; $i++)
1306  {
1307  $qSelect .= ', t'.$i.'.parent c'.$i;
1308  $qJoin .= ' JOIN '.$this->table_tree.' t'.$i.' ON '.
1309  't'.$i.'.child=t'.($i - 1).'.parent AND '.
1310  't'.$i.'.'.$this->tree_pk.' = '.(int) $this->tree_id;
1311  }
1312 
1313  $types = array('integer','integer');
1314  $data = array($this->tree_id,$parentId);
1315  $query = 'SELECT '.$qSelect.' '.
1316  'FROM '.$this->table_tree.' t0 '.$qJoin.' '.
1317  'WHERE t0.'.$this->tree_pk.' = %s '.
1318  'AND t0.child = %s ';
1319 
1320  $ilDB->setLimit(1);
1321  $res = $ilDB->queryF($query,$types,$data);
1322 
1323  if ($res->numRows() == 0)
1324  {
1325  return array();
1326  }
1327  $row = $ilDB->fetchAssoc($res);
1328 
1329  $takeId = $takeId || $this->root_id == $a_startnode_id;
1330  if ($takeId) $pathIds[] = $this->root_id;
1331  for ($i = $nodeDepth - 4; $i >=0; $i--)
1332  {
1333  $takeId = $takeId || $row['c'.$i] == $a_startnode_id;
1334  if ($takeId) $pathIds[] = $row['c'.$i];
1335  }
1336  $takeId = $takeId || $parentId == $a_startnode_id;
1337  if ($takeId) $pathIds[] = $parentId;
1338  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1339  if ($takeId) $pathIds[] = $a_endnode_id;
1340  }
1341  else
1342  {
1343  // Fall back to nested sets tree for extremely deep tree structures
1344  return $this->getPathIdsUsingNestedSets($a_endnode_id, $a_startnode_id);
1345  }
1346 
1347  return $pathIds;
1348  }
1349 
1356  function preloadDepthParent($a_node_ids)
1357  {
1358  global $ilDB;
1359 
1360  if (!$this->__isMainTree() || !is_array($a_node_ids) || !$this->isCacheUsed())
1361  {
1362  return;
1363  }
1364 
1365  $res = $ilDB->query('SELECT t.depth, t.parent, t.child '.
1366  'FROM '.$this->table_tree.' t '.
1367  'WHERE '.$ilDB->in("child", $a_node_ids, false, "integer").
1368  'AND '.$this->tree_pk.' = '.$ilDB->quote($this->tree_id, "integer"));
1369  while ($row = $ilDB->fetchAssoc($res))
1370  {
1371  $this->depth_cache[$row["child"]] = $row["depth"];
1372  $this->parent_cache[$row["child"]] = $row["parent"];
1373  }
1374  }
1375 
1384  function getPathId($a_endnode_id, $a_startnode_id = 0)
1385  {
1386  // path id cache
1387  if ($this->isCacheUsed() && isset($this->path_id_cache[$a_endnode_id][$a_startnode_id]))
1388  {
1389 //echo "<br>getPathIdhit";
1390  return $this->path_id_cache[$a_endnode_id][$a_startnode_id];
1391  }
1392 //echo "<br>miss";
1393 
1394  $pathIds =& $this->getPathIdsUsingAdjacencyMap($a_endnode_id, $a_startnode_id);
1395 
1396  if($this->__isMainTree())
1397  {
1398  $this->path_id_cache[$a_endnode_id][$a_startnode_id] = $pathIds;
1399  }
1400  return $pathIds;
1401  }
1402 
1403  // BEGIN WebDAV: getNodePathForTitlePath function added
1421  function getNodePathForTitlePath($titlePath, $a_startnode_id = null)
1422  {
1423  global $ilDB, $log;
1424  //$log->write('getNodePathForTitlePath('.implode('/',$titlePath));
1425 
1426  // handle empty title path
1427  if ($titlePath == null || count($titlePath) == 0)
1428  {
1429  if ($a_startnode_id == 0)
1430  {
1431  return null;
1432  }
1433  else
1434  {
1435  return $this->getNodePath($a_startnode_id);
1436  }
1437  }
1438 
1439  // fetch the node path up to the startnode
1440  if ($a_startnode_id != null && $a_startnode_id != 0)
1441  {
1442  // Start using the node path to the root of the relative path
1443  $nodePath = $this->getNodePath($a_startnode_id);
1444  $parent = $a_startnode_id;
1445  }
1446  else
1447  {
1448  // Start using the root of the tree
1449  $nodePath = array();
1450  $parent = 0;
1451  }
1452 
1453 
1454  // Convert title path into Unicode Normal Form C
1455  // This is needed to ensure that we can compare title path strings with
1456  // strings from the database.
1457  require_once('include/Unicode/UtfNormal.php');
1458  include_once './Services/Utilities/classes/class.ilStr.php';
1459  $inClause = 'd.title IN (';
1460  for ($i=0; $i < count($titlePath); $i++)
1461  {
1462  $titlePath[$i] = ilStr::strToLower(UtfNormal::toNFC($titlePath[$i]));
1463  if ($i > 0) $inClause .= ',';
1464  $inClause .= $ilDB->quote($titlePath[$i],'text');
1465  }
1466  $inClause .= ')';
1467 
1468  // Fetch all rows that are potential path elements
1469  if ($this->table_obj_reference)
1470  {
1471  $joinClause = 'JOIN '.$this->table_obj_reference.' r ON t.child = r.'.$this->ref_pk.' '.
1472  'JOIN '.$this->table_obj_data.' d ON r.'.$this->obj_pk.' = d.'.$this->obj_pk;
1473  }
1474  else
1475  {
1476  $joinClause = 'JOIN '.$this->table_obj_data.' d ON t.child = d.'.$this->obj_pk;
1477  }
1478  // The ORDER BY clause in the following SQL statement ensures that,
1479  // in case of a multiple objects with the same title, always the Object
1480  // with the oldest ref_id is chosen.
1481  // This ensure, that, if a new object with the same title is added,
1482  // WebDAV clients can still work with the older object.
1483  $q = 'SELECT t.depth, t.parent, t.child, d.'.$this->obj_pk.' obj_id, d.type, d.title '.
1484  'FROM '.$this->table_tree.' t '.
1485  $joinClause.' '.
1486  'WHERE '.$inClause.' '.
1487  'AND t.depth <= '.(count($titlePath)+count($nodePath)).' '.
1488  'AND t.tree = 1 '.
1489  'ORDER BY t.depth, t.child ASC';
1490  $r = $ilDB->query($q);
1491 
1492  $rows = array();
1493  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1494  {
1495  $row['title'] = UtfNormal::toNFC($row['title']);
1496  $row['ref_id'] = $row['child'];
1497  $rows[] = $row;
1498  }
1499 
1500  // Extract the path elements from the fetched rows
1501  for ($i = 0; $i < count($titlePath); $i++) {
1502  $pathElementFound = false;
1503  foreach ($rows as $row) {
1504  if ($row['parent'] == $parent &&
1505  ilStr::strToLower($row['title']) == $titlePath[$i])
1506  {
1507  // FIXME - We should test here, if the user has
1508  // 'visible' permission for the object.
1509  $nodePath[] = $row;
1510  $parent = $row['child'];
1511  $pathElementFound = true;
1512  break;
1513  }
1514  }
1515  // Abort if we haven't found a path element for the current depth
1516  if (! $pathElementFound)
1517  {
1518  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):null');
1519  return null;
1520  }
1521  }
1522  // Return the node path
1523  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):'.var_export($nodePath,true));
1524  return $nodePath;
1525  }
1526  // END WebDAV: getNodePathForTitlePath function added
1527  // END WebDAV: getNodePath function added
1544  function getNodePath($a_endnode_id, $a_startnode_id = 0)
1545  {
1546  global $ilDB;
1547 
1548  $pathIds = $this->getPathId($a_endnode_id, $a_startnode_id);
1549 
1550  // Abort if no path ids were found
1551  if (count($pathIds) == 0)
1552  {
1553  return null;
1554  }
1555 
1556 
1557  $types = array();
1558  $data = array();
1559  for ($i = 0; $i < count($pathIds); $i++)
1560  {
1561  $types[] = 'integer';
1562  $data[] = $pathIds[$i];
1563  }
1564 
1565  $query = 'SELECT t.depth,t.parent,t.child,d.obj_id,d.type,d.title '.
1566  'FROM '.$this->table_tree.' t '.
1567  'JOIN '.$this->table_obj_reference.' r ON r.ref_id = t.child '.
1568  'JOIN '.$this->table_obj_data.' d ON d.obj_id = r.obj_id '.
1569  'WHERE '.$ilDB->in('t.child',$data,false,'integer').' '.
1570  'ORDER BY t.depth ';
1571 
1572  $res = $ilDB->queryF($query,$types,$data);
1573 
1574  $titlePath = array();
1575  while ($row = $ilDB->fetchAssoc($res))
1576  {
1577  $titlePath[] = $row;
1578  }
1579  return $titlePath;
1580  }
1581  // END WebDAV: getNodePath function added
1582 
1589  function checkTree()
1590  {
1591  global $ilDB;
1592 
1593  $types = array('integer');
1594  $query = 'SELECT lft,rgt FROM '.$this->table_tree.' '.
1595  'WHERE '.$this->tree_pk.' = %s ';
1596 
1597  $res = $ilDB->queryF($query,$types,array($this->tree_id));
1598  while ($row = $ilDB->fetchObject($res))
1599  {
1600  $lft[] = $row->lft;
1601  $rgt[] = $row->rgt;
1602  }
1603 
1604  $all = array_merge($lft,$rgt);
1605  $uni = array_unique($all);
1606 
1607  if (count($all) != count($uni))
1608  {
1609  $message = sprintf('%s::checkTree(): Tree is corrupted!',
1610  get_class($this));
1611 
1612  $this->log->write($message,$this->log->FATAL);
1613  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1614  }
1615 
1616  return true;
1617  }
1618 
1622  function checkTreeChilds($a_no_zero_child = true)
1623  {
1624  global $ilDB;
1625 
1626  $query = 'SELECT * FROM '.$this->table_tree.' '.
1627  'WHERE '.$this->tree_pk.' = %s '.
1628  'ORDER BY lft';
1629  $r1 = $ilDB->queryF($query,array('integer'),array($this->tree_id));
1630 
1631  while ($row = $ilDB->fetchAssoc($r1))
1632  {
1633 //echo "tree:".$row[$this->tree_pk].":lft:".$row["lft"].":rgt:".$row["rgt"].":child:".$row["child"].":<br>";
1634  if (($row["child"] == 0) && $a_no_zero_child)
1635  {
1636  $this->ilErr->raiseError(get_class($this)."::checkTreeChilds(): Tree contains child with ID 0!",$this->ilErr->WARNING);
1637  }
1638 
1639  if ($this->table_obj_reference)
1640  {
1641  // get object reference data
1642  $query = 'SELECT * FROM '.$this->table_obj_reference.' WHERE '.$this->ref_pk.' = %s ';
1643  $r2 = $ilDB->queryF($query,array('integer'),array($row['child']));
1644 
1645 //echo "num_childs:".$r2->numRows().":<br>";
1646  if ($r2->numRows() == 0)
1647  {
1648  $this->ilErr->raiseError(get_class($this)."::checkTree(): No Object-to-Reference entry found for ID ".
1649  $row["child"]."!",$this->ilErr->WARNING);
1650  }
1651  if ($r2->numRows() > 1)
1652  {
1653  $this->ilErr->raiseError(get_class($this)."::checkTree(): More Object-to-Reference entries found for ID ".
1654  $row["child"]."!",$this->ilErr->WARNING);
1655  }
1656 
1657  // get object data
1658  $obj_ref = $ilDB->fetchAssoc($r2);
1659 
1660  $query = 'SELECT * FROM '.$this->table_obj_data.' WHERE '.$this->obj_pk.' = %s';
1661  $r3 = $ilDB->queryF($query,array('integer'),array($obj_ref[$this->obj_pk]));
1662  if ($r3->numRows() == 0)
1663  {
1664  $this->ilErr->raiseError(get_class($this)."::checkTree(): No child found for ID ".
1665  $obj_ref[$this->obj_pk]."!",$this->ilErr->WARNING);
1666  }
1667  if ($r3->numRows() > 1)
1668  {
1669  $this->ilErr->raiseError(get_class($this)."::checkTree(): More childs found for ID ".
1670  $obj_ref[$this->obj_pk]."!",$this->ilErr->WARNING);
1671  }
1672 
1673  }
1674  else
1675  {
1676  // get only object data
1677  $query = 'SELECT * FROM '.$this->table_obj_data.' WHERE '.$this->obj_pk.' = %s';
1678  $r2 = $ilDB->queryF($query,array('integer'),array($row['child']));
1679 //echo "num_childs:".$r2->numRows().":<br>";
1680  if ($r2->numRows() == 0)
1681  {
1682  $this->ilErr->raiseError(get_class($this)."::checkTree(): No child found for ID ".
1683  $row["child"]."!",$this->ilErr->WARNING);
1684  }
1685  if ($r2->numRows() > 1)
1686  {
1687  $this->ilErr->raiseError(get_class($this)."::checkTree(): More childs found for ID ".
1688  $row["child"]."!",$this->ilErr->WARNING);
1689  }
1690  }
1691  }
1692 
1693  return true;
1694  }
1695 
1701  function getMaximumDepth()
1702  {
1703  global $ilDB;
1704 
1705  $query = 'SELECT MAX(depth) depth FROM '.$this->table_tree;
1706  $res = $ilDB->query($query);
1707 
1708  $row = $ilDB->fetchAssoc($res);
1709  return $row['depth'];
1710  }
1711 
1718  function getDepth($a_node_id)
1719  {
1720  global $ilDB;
1721 
1722  if ($a_node_id)
1723  {
1724  $query = 'SELECT depth FROM '.$this->table_tree.' '.
1725  'WHERE child = %s '.
1726  'AND '.$this->tree_pk.' = %s ';
1727  $res = $ilDB->queryF($query,array('integer','integer'),array($a_node_id,$this->tree_id));
1728  $row = $ilDB->fetchObject($res);
1729 
1730  return $row->depth;
1731  }
1732  else
1733  {
1734  return 1;
1735  }
1736  }
1737 
1738 
1746  // BEGIN WebDAV: Pass tree id to this method
1747  //function getNodeData($a_node_id)
1748  function getNodeData($a_node_id, $a_tree_pk = null)
1749  // END PATCH WebDAV: Pass tree id to this method
1750  {
1751  global $ilDB;
1752 
1753  if (!isset($a_node_id))
1754  {
1755  $GLOBALS['ilLog']->logStack();
1756  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1757  }
1758  if($this->__isMainTree())
1759  {
1760  if($a_node_id < 1)
1761  {
1762  $message = sprintf('%s::getNodeData(): No valid parameter given! $a_node_id: %s',
1763  get_class($this),
1764  $a_node_id);
1765 
1766  $this->log->write($message,$this->log->FATAL);
1767  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1768  }
1769  }
1770 
1771  // BEGIN WebDAV: Pass tree id to this method
1772  $query = 'SELECT * FROM '.$this->table_tree.' '.
1773  $this->buildJoin().
1774  'WHERE '.$this->table_tree.'.child = %s '.
1775  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
1776  $res = $ilDB->queryF($query,array('integer','integer'),array(
1777  $a_node_id,
1778  $a_tree_pk === null ? $this->tree_id : $a_tree_pk));
1779  // END WebDAV: Pass tree id to this method
1780  $row = $ilDB->fetchAssoc($res);
1782 
1783  return $this->fetchNodeData($row);
1784  }
1785 
1793  function fetchNodeData($a_row)
1794  {
1795  global $objDefinition, $lng, $ilBench,$ilDB;
1796 
1797  //$ilBench->start("Tree", "fetchNodeData_getRow");
1798  $data = $a_row;
1799  $data["desc"] = $a_row["description"]; // for compability
1800  //$ilBench->stop("Tree", "fetchNodeData_getRow");
1801 
1802  // multilingual support systemobjects (sys) & categories (db)
1803  //$ilBench->start("Tree", "fetchNodeData_readDefinition");
1804  if (is_object($objDefinition))
1805  {
1806  $translation_type = $objDefinition->getTranslationType($data["type"]);
1807  }
1808  //$ilBench->stop("Tree", "fetchNodeData_readDefinition");
1809 
1810  if ($translation_type == "sys")
1811  {
1812  //$ilBench->start("Tree", "fetchNodeData_getLangData");
1813  if ($data["type"] == "rolf" and $data["obj_id"] != ROLE_FOLDER_ID)
1814  {
1815  $data["description"] = $lng->txt("obj_".$data["type"]."_local_desc").$data["title"].$data["desc"];
1816  $data["desc"] = $lng->txt("obj_".$data["type"]."_local_desc").$data["title"].$data["desc"];
1817  $data["title"] = $lng->txt("obj_".$data["type"]."_local");
1818  }
1819  else
1820  {
1821  $data["title"] = $lng->txt("obj_".$data["type"]);
1822  $data["description"] = $lng->txt("obj_".$data["type"]."_desc");
1823  $data["desc"] = $lng->txt("obj_".$data["type"]."_desc");
1824  }
1825  //$ilBench->stop("Tree", "fetchNodeData_getLangData");
1826  }
1827  elseif ($translation_type == "db")
1828  {
1829 
1830  // Try to retrieve object translation from cache
1831  if ($this->isCacheUsed() &&
1832  array_key_exists($data["obj_id"].'.'.$lang_code, $this->translation_cache)) {
1833 
1834  $key = $data["obj_id"].'.'.$lang_code;
1835  $data["title"] = $this->translation_cache[$key]['title'];
1836  $data["description"] = $this->translation_cache[$key]['description'];
1837  $data["desc"] = $this->translation_cache[$key]['desc'];
1838  }
1839  else
1840  {
1841  // Object translation is not in cache, read it from database
1842  //$ilBench->start("Tree", "fetchNodeData_getTranslation");
1843  $query = 'SELECT title,description FROM object_translation '.
1844  'WHERE obj_id = %s '.
1845  'AND lang_code = %s '.
1846  'AND NOT lang_default = %s';
1847 
1848  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
1849  $data['obj_id'],
1850  $this->lang_code,
1851  1));
1852  $row = $ilDB->fetchObject($res);
1853 
1854  if ($row)
1855  {
1856  $data["title"] = $row->title;
1857  $data["description"] = ilUtil::shortenText($row->description,MAXLENGTH_OBJ_DESC,true);
1858  $data["desc"] = $row->description;
1859  }
1860  //$ilBench->stop("Tree", "fetchNodeData_getTranslation");
1861 
1862  // Store up to 1000 object translations in cache
1863  if ($this->isCacheUsed() && count($this->translation_cache) < 1000)
1864  {
1865  $key = $data["obj_id"].'.'.$lang_code;
1866  $this->translation_cache[$key] = array();
1867  $this->translation_cache[$key]['title'] = $data["title"] ;
1868  $this->translation_cache[$key]['description'] = $data["description"];
1869  $this->translation_cache[$key]['desc'] = $data["desc"];
1870  }
1871  }
1872  }
1873 
1874  // TODO: Handle this switch by module.xml definitions
1875  if($data['type'] == 'crsr' or $data['type'] == 'catr')
1876  {
1877  include_once('./Services/ContainerReference/classes/class.ilContainerReference.php');
1879  }
1880 
1881  return $data ? $data : array();
1882  }
1883 
1889  protected function fetchTranslationFromObjectDataCache($a_obj_ids)
1890  {
1891  global $ilObjDataCache;
1892 
1893  if ($this->isCacheUsed() && is_array($a_obj_ids) && is_object($ilObjDataCache))
1894  {
1895  foreach ($a_obj_ids as $id)
1896  {
1897  $this->translation_cache[$id.'.']['title'] = $ilObjDataCache->lookupTitle($id);
1898  $this->translation_cache[$id.'.']['description'] = $ilObjDataCache->lookupDescription($id);;
1899  $this->translation_cache[$id.'.']['desc'] =
1900  $this->translation_cache[$id.'.']['description'];
1901  }
1902  }
1903  }
1904 
1905 
1913  function isInTree($a_node_id)
1914  {
1915  global $ilDB;
1916 
1917  if (!isset($a_node_id))
1918  {
1919  return false;
1920  #$this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1921  }
1922 
1923  // is in tree cache
1924  if ($this->isCacheUsed() && isset($this->in_tree_cache[$a_node_id]))
1925  {
1926  #$GLOBALS['ilLog']->write(__METHOD__.': Using in tree cache '.$a_node_id);
1927 //echo "<br>in_tree_hit";
1928  return $this->in_tree_cache[$a_node_id];
1929  }
1930 
1931  $query = 'SELECT * FROM '.$this->table_tree.' '.
1932  'WHERE '.$this->table_tree.'.child = %s '.
1933  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s';
1934 
1935  $res = $ilDB->queryF($query,array('integer','integer'),array(
1936  $a_node_id,
1937  $this->tree_id));
1938 
1939  if ($res->numRows() > 0)
1940  {
1941  if($this->__isMainTree())
1942  {
1943  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
1944  $this->in_tree_cache[$a_node_id] = true;
1945  }
1946  return true;
1947  }
1948  else
1949  {
1950  if($this->__isMainTree())
1951  {
1952  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = false');
1953  $this->in_tree_cache[$a_node_id] = false;
1954  }
1955  return false;
1956  }
1957  }
1958 
1965  function getParentNodeData($a_node_id)
1966  {
1967  global $ilDB;
1968  global $ilLog;
1969 
1970  if (!isset($a_node_id))
1971  {
1972  $ilLog->logStack();
1973  $this->ilErr->raiseError(get_class($this)."::getParentNodeData(): No node_id given! ",$this->ilErr->WARNING);
1974  }
1975 
1976  if ($this->table_obj_reference)
1977  {
1978  // Use inner join instead of left join to improve performance
1979  $innerjoin = "JOIN ".$this->table_obj_reference." ON v.child=".$this->table_obj_reference.".".$this->ref_pk." ".
1980  "JOIN ".$this->table_obj_data." ON ".$this->table_obj_reference.".".$this->obj_pk."=".$this->table_obj_data.".".$this->obj_pk." ";
1981  }
1982  else
1983  {
1984  // Use inner join instead of left join to improve performance
1985  $innerjoin = "JOIN ".$this->table_obj_data." ON v.child=".$this->table_obj_data.".".$this->obj_pk." ";
1986  }
1987 
1988  $query = 'SELECT * FROM '.$this->table_tree.' s, '.$this->table_tree.' v '.
1989  $innerjoin.
1990  'WHERE s.child = %s '.
1991  'AND s.parent = v.child '.
1992  'AND s.lft > v.lft '.
1993  'AND s.rgt < v.rgt '.
1994  'AND s.'.$this->tree_pk.' = %s '.
1995  'AND v.'.$this->tree_pk.' = %s';
1996  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
1997  $a_node_id,
1998  $this->tree_id,
1999  $this->tree_id));
2000  $row = $ilDB->fetchAssoc($res);
2001  return $this->fetchNodeData($row);
2002  }
2003 
2011  function isGrandChild($a_startnode_id,$a_querynode_id)
2012  {
2013  global $ilDB;
2014 
2015  if (!isset($a_startnode_id) or !isset($a_querynode_id))
2016  {
2017  return false;
2018  }
2019 
2020  $query = 'SELECT * FROM '.$this->table_tree.' s, '.$this->table_tree.' v '.
2021  'WHERE s.child = %s '.
2022  'AND v.child = %s '.
2023  'AND s.'.$this->tree_pk.' = %s '.
2024  'AND v.'.$this->tree_pk.' = %s '.
2025  'AND v.lft BETWEEN s.lft AND s.rgt '.
2026  'AND v.rgt BETWEEN s.lft AND s.rgt';
2027  $res = $ilDB->queryF(
2028  $query,
2029  array('integer','integer','integer','integer'),
2030  array(
2031  $a_startnode_id,
2032  $a_querynode_id,
2033  $this->tree_id,
2034  $this->tree_id));
2035 
2036  return $res->numRows();
2037  }
2038 
2047  function addTree($a_tree_id,$a_node_id = -1)
2048  {
2049  global $ilDB;
2050 
2051  // FOR SECURITY addTree() IS NOT ALLOWED ON MAIN TREE
2052  if($this->__isMainTree())
2053  {
2054  $message = sprintf('%s::addTree(): Operation not allowed on main tree! $a_tree_if: %s $a_node_id: %s',
2055  get_class($this),
2056  $a_tree_id,
2057  $a_node_id);
2058  $this->log->write($message,$this->log->FATAL);
2059  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2060  }
2061 
2062  if (!isset($a_tree_id))
2063  {
2064  $this->ilErr->raiseError(get_class($this)."::addTree(): No tree_id given! ",$this->ilErr->WARNING);
2065  }
2066 
2067  if ($a_node_id <= 0)
2068  {
2069  $a_node_id = $a_tree_id;
2070  }
2071 
2072  $query = 'INSERT INTO '.$this->table_tree.' ('.
2073  $this->tree_pk.', child,parent,lft,rgt,depth) '.
2074  'VALUES '.
2075  '(%s,%s,%s,%s,%s,%s)';
2076  $res = $ilDB->manipulateF($query,array('integer','integer','integer','integer','integer','integer'),array(
2077  $a_tree_id,
2078  $a_node_id,
2079  0,
2080  1,
2081  2,
2082  1));
2083 
2084  return true;
2085  }
2086 
2094  function getNodeDataByType($a_type)
2095  {
2096  global $ilDB;
2097 
2098  if (!isset($a_type) or (!is_string($a_type)))
2099  {
2100  $this->ilErr->raiseError(get_class($this)."::getNodeDataByType(): Type not given or wrong datatype!",$this->ilErr->WARNING);
2101  }
2102 
2103  $data = array(); // node_data
2104  $row = ""; // fetched row
2105  $left = ""; // tree_left
2106  $right = ""; // tree_right
2107 
2108  $query = 'SELECT * FROM '.$this->table_tree.' '.
2109  'WHERE '.$this->tree_pk.' = %s '.
2110  'AND parent = %s ';
2111  $res = $ilDB->queryF($query,array('integer','integer'),array(
2112  $this->tree_id,
2113  0));
2114 
2115  while ($row = $ilDB->fetchObject($res))
2116  {
2117  $left = $row->lft;
2118  $right = $row->rgt;
2119  }
2120 
2121  $query = 'SELECT * FROM '.$this->table_tree.' '.
2122  $this->buildJoin().
2123  'WHERE '.$this->table_obj_data.'.type = %s '.
2124  'AND '.$this->table_tree.'.lft BETWEEN %s AND %s '.
2125  'AND '.$this->table_tree.'.rgt BETWEEN %s AND %s '.
2126  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2127  $res = $ilDB->queryF($query,array('text','integer','integer','integer','integer','integer'),array(
2128  $a_type,
2129  $left,
2130  $right,
2131  $left,
2132  $right,
2133  $this->tree_id));
2134 
2135  while($row = $ilDB->fetchAssoc($res))
2136  {
2137  $data[] = $this->fetchNodeData($row);
2138  }
2139 
2140  return $data;
2141  }
2142 
2150  function removeTree($a_tree_id)
2151  {
2152  global $ilDB;
2153 
2154  // OPERATION NOT ALLOWED ON MAIN TREE
2155  if($this->__isMainTree())
2156  {
2157  $message = sprintf('%s::removeTree(): Operation not allowed on main tree! $a_tree_if: %s',
2158  get_class($this),
2159  $a_tree_id);
2160  $this->log->write($message,$this->log->FATAL);
2161  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2162  }
2163  if (!$a_tree_id)
2164  {
2165  $this->ilErr->raiseError(get_class($this)."::removeTree(): No tree_id given! Action aborted",$this->ilErr->MESSAGE);
2166  }
2167 
2168  $query = 'DELETE FROM '.$this->table_tree.
2169  ' WHERE '.$this->tree_pk.' = %s ';
2170  $res = $ilDB->manipulateF($query,array('integer'),array($a_tree_id));
2171  return true;
2172  }
2173 
2181  function saveSubTree($a_node_id, $a_set_deleted = false)
2182  {
2183  global $ilDB;
2184 
2185  if (!$a_node_id)
2186  {
2187  $message = sprintf('%s::saveSubTree(): No valid parameter given! $a_node_id: %s',
2188  get_class($this),
2189  $a_node_id);
2190  $this->log->write($message,$this->log->FATAL);
2191  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2192  }
2193 
2194  // LOCKED ###############################################
2195  if($this->__isMainTree())
2196  {
2197  $ilDB->lockTables(
2198  array(
2199  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE),
2200  1 => array('name' => 'object_reference', 'type' => ilDB::LOCK_WRITE)));
2201 
2202  #ilDB::_lockTables(array('tree' => 'WRITE',
2203  # 'object_reference' => 'WRITE'));
2204 
2205  }
2206 
2207  // GET LEFT AND RIGHT VALUE
2208  $query = 'SELECT * FROM '.$this->table_tree.' '.
2209  'WHERE '.$this->tree_pk.' = %s '.
2210  'AND child = %s ';
2211  $res = $ilDB->queryF($query,array('integer','integer'),array(
2212  $this->tree_id,
2213  $a_node_id));
2214 
2215  while($row = $ilDB->fetchObject($res))
2216  {
2217  $lft = $row->lft;
2218  $rgt = $row->rgt;
2219  }
2220 
2221  // GET ALL SUBNODES
2222  $query = 'SELECT child FROM '.$this->table_tree.' '.
2223  'WHERE '.$this->tree_pk.' = %s '.
2224  'AND lft BETWEEN %s AND %s ';
2225  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2226  $this->tree_id,
2227  $lft,
2228  $rgt));
2229 
2230  $subnodes = array();
2231  while($row = $ilDB->fetchAssoc($res))
2232  {
2233  $subnodes[] = $row['child'];
2234  }
2235 
2236  if(!count($subnodes))
2237  {
2238  // possibly already deleted
2239 
2240  // Unlock locked tables before returning
2241  if($this->__isMainTree())
2242  {
2243  $ilDB->unlockTables();
2244  }
2245 
2246  return false;
2247  }
2248 
2249  // SAVE SUBTREE
2250  foreach($subnodes as $child)
2251  {
2252  // set node as deleted
2253  if ($a_set_deleted)
2254  {
2255  // TODO: new method that expects an array of ids
2256  ilObject::_setDeletedDate($child);
2257  }
2258  }
2259 
2260  // Set the nodes deleted (negative tree id)
2261  $query = 'UPDATE '.$this->table_tree.' '.
2262  'SET tree = %s '.
2263  'WHERE '.$this->tree_pk.' = %s '.
2264  'AND lft BETWEEN %s AND %s ';
2265  $res = $ilDB->manipulateF($query,array('integer','integer','integer','integer'),array(
2266  -$a_node_id,
2267  $this->tree_id,
2268  $lft,
2269  $rgt));
2270 
2271  if($this->__isMainTree())
2272  {
2273  $ilDB->unlockTables();
2274  }
2275 
2276  // LOCKED ###############################################
2277  return true;
2278  }
2279 
2283  function isDeleted($a_node_id)
2284  {
2285  return $this->isSaved($a_node_id);
2286  }
2287 
2291  function isSaved($a_node_id)
2292  {
2293  global $ilDB;
2294 
2295  // is saved cache
2296  if ($this->isCacheUsed() && isset($this->is_saved_cache[$a_node_id]))
2297  {
2298 //echo "<br>issavedhit";
2299  return $this->is_saved_cache[$a_node_id];
2300  }
2301 
2302  $query = 'SELECT '.$this->tree_pk.' FROM '.$this->table_tree.' '.
2303  'WHERE child = %s ';
2304  $res = $ilDB->queryF($query,array('integer'),array($a_node_id));
2305  $row = $ilDB->fetchAssoc($res);
2306 
2307  if ($row[$this->tree_pk] < 0)
2308  {
2309  if($this->__isMainTree())
2310  {
2311  $this->is_saved_cache[$a_node_id] = true;
2312  }
2313  return true;
2314  }
2315  else
2316  {
2317  if($this->__isMainTree())
2318  {
2319  $this->is_saved_cache[$a_node_id] = false;
2320  }
2321  return false;
2322  }
2323  }
2324 
2331  function preloadDeleted($a_node_ids)
2332  {
2333  global $ilDB;
2334 
2335  if (!is_array($a_node_ids) || !$this->isCacheUsed())
2336  {
2337  return;
2338  }
2339 
2340  $query = 'SELECT '.$this->tree_pk.', child FROM '.$this->table_tree.' '.
2341  'WHERE '.$ilDB->in("child", $a_node_ids, false, "integer");
2342 
2343  $res = $ilDB->query($query);
2344  while ($row = $ilDB->fetchAssoc($res))
2345  {
2346  if ($row[$this->tree_pk] < 0)
2347  {
2348  if($this->__isMainTree())
2349  {
2350  $this->is_saved_cache[$row["child"]] = true;
2351  }
2352  }
2353  else
2354  {
2355  if($this->__isMainTree())
2356  {
2357  $this->is_saved_cache[$row["child"]] = false;
2358  }
2359  }
2360  }
2361  }
2362 
2363 
2370  function getSavedNodeData($a_parent_id)
2371  {
2372  global $ilDB;
2373 
2374  if (!isset($a_parent_id))
2375  {
2376  $this->ilErr->raiseError(get_class($this)."::getSavedNodeData(): No node_id given!",$this->ilErr->WARNING);
2377  }
2378 
2379  $query = 'SELECT * FROM '.$this->table_tree.' '.
2380  $this->buildJoin().
2381  'WHERE '.$this->table_tree.'.'.$this->tree_pk.' < %s '.
2382  'AND '.$this->table_tree.'.parent = %s';
2383  $res = $ilDB->queryF($query,array('integer','integer'),array(
2384  0,
2385  $a_parent_id));
2386 
2387  while($row = $ilDB->fetchAssoc($res))
2388  {
2389  $saved[] = $this->fetchNodeData($row);
2390  }
2391 
2392  return $saved ? $saved : array();
2393  }
2394 
2401  function getSavedNodeObjIds(array $a_obj_ids)
2402  {
2403  global $ilDB;
2404 
2405  $query = 'SELECT '.$this->table_obj_data.'.obj_id FROM '.$this->table_tree.' '.
2406  $this->buildJoin().
2407  'WHERE '.$this->table_tree.'.'.$this->tree_pk.' < '.$ilDB->quote(0, 'integer').' '.
2408  'AND '.$ilDB->in($this->table_obj_data.'.obj_id', $a_obj_ids, '', 'integer');
2409  $res = $ilDB->query($query);
2410  while($row = $ilDB->fetchAssoc($res))
2411  {
2412  $saved[] = $row['obj_id'];
2413  }
2414 
2415  return $saved ? $saved : array();
2416  }
2417 
2424  function getParentId($a_node_id)
2425  {
2426  global $ilDB;
2427 
2428  if (!isset($a_node_id))
2429  {
2430  $this->ilErr->raiseError(get_class($this)."::getParentId(): No node_id given! ",$this->ilErr->WARNING);
2431  }
2432 
2433  $query = 'SELECT parent FROM '.$this->table_tree.' '.
2434  'WHERE child = %s '.
2435  'AND '.$this->tree_pk.' = %s ';
2436  $res = $ilDB->queryF($query,array('integer','integer'),array(
2437  $a_node_id,
2438  $this->tree_id));
2439 
2440  $row = $ilDB->fetchObject($res);
2441  return $row->parent;
2442  }
2443 
2450  function getLeftValue($a_node_id)
2451  {
2452  global $ilDB;
2453 
2454  if (!isset($a_node_id))
2455  {
2456  $this->ilErr->raiseError(get_class($this)."::getLeftValued(): No node_id given! ",$this->ilErr->WARNING);
2457  }
2458 
2459  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2460  'WHERE child = %s '.
2461  'AND '.$this->tree_pk.' = %s ';
2462  $res = $ilDB->queryF($query,array('integer','integer'),array(
2463  $a_node_id,
2464  $this->tree_id));
2465  $row = $ilDB->fetchObject($res);
2466  return $row->lft;
2467  }
2468 
2475  function getChildSequenceNumber($a_node, $type = "")
2476  {
2477  global $ilDB;
2478 
2479  if (!isset($a_node))
2480  {
2481  $this->ilErr->raiseError(get_class($this)."::getChildSequenceNumber(): No node_id given! ",$this->ilErr->WARNING);
2482  }
2483 
2484  if($type)
2485  {
2486  $query = 'SELECT count(*) cnt FROM '.$this->table_tree.' '.
2487  $this->buildJoin().
2488  'WHERE lft <= %s '.
2489  'AND type = %s '.
2490  'AND parent = %s '.
2491  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2492 
2493  $res = $ilDB->queryF($query,array('integer','text','integer','integer'),array(
2494  $a_node['lft'],
2495  $type,
2496  $a_node['parent'],
2497  $this->tree_id));
2498  }
2499  else
2500  {
2501  $query = 'SELECT count(*) cnt FROM '.$this->table_tree.' '.
2502  $this->buildJoin().
2503  'WHERE lft <= %s '.
2504  'AND parent = %s '.
2505  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2506 
2507  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2508  $a_node['lft'],
2509  $a_node['parent'],
2510  $this->tree_id));
2511 
2512  }
2513  $row = $ilDB->fetchAssoc($res);
2514  return $row["cnt"];
2515  }
2516 
2523  function readRootId()
2524  {
2525  global $ilDB;
2526 
2527  $query = 'SELECT child FROM '.$this->table_tree.' '.
2528  'WHERE parent = %s '.
2529  'AND '.$this->tree_pk.' = %s ';
2530  $res = $ilDB->queryF($query,array('integer','integer'),array(
2531  0,
2532  $this->tree_id));
2533  $row = $ilDB->fetchObject($res);
2534  $this->root_id = $row->child;
2535  return $this->root_id;
2536  }
2537 
2543  function getRootId()
2544  {
2545  return $this->root_id;
2546  }
2547  function setRootId($a_root_id)
2548  {
2549  $this->root_id = $a_root_id;
2550  }
2551 
2557  function getTreeId()
2558  {
2559  return $this->tree_id;
2560  }
2561 
2567  function setTreeId($a_tree_id)
2568  {
2569  $this->tree_id = $a_tree_id;
2570  }
2571 
2579  function fetchSuccessorNode($a_node_id, $a_type = "")
2580  {
2581  global $ilDB;
2582 
2583  if (!isset($a_node_id))
2584  {
2585  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
2586  }
2587 
2588  // get lft value for current node
2589  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2590  'WHERE '.$this->table_tree.'.child = %s '.
2591  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2592  $res = $ilDB->queryF($query,array('integer','integer'),array(
2593  $a_node_id,
2594  $this->tree_id));
2595 
2596  $curr_node = $ilDB->fetchAssoc($res);
2597 
2598  if($a_type)
2599  {
2600  $query = 'SELECT * FROM '.$this->table_tree.' '.
2601  $this->buildJoin().
2602  'WHERE lft > %s '.
2603  'AND '.$this->table_obj_data.'.type = %s '.
2604  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2605  'ORDER BY lft ';
2606  $ilDB->setLimit(1);
2607  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
2608  $curr_node['lft'],
2609  $a_type,
2610  $this->tree_id));
2611  }
2612  else
2613  {
2614  $query = 'SELECT * FROM '.$this->table_tree.' '.
2615  $this->buildJoin().
2616  'WHERE lft > %s '.
2617  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2618  'ORDER BY lft ';
2619  $ilDB->setLimit(1);
2620  $res = $ilDB->queryF($query,array('integer','integer'),array(
2621  $curr_node['lft'],
2622  $this->tree_id));
2623  }
2624 
2625  if ($res->numRows() < 1)
2626  {
2627  return false;
2628  }
2629  else
2630  {
2631  $row = $ilDB->fetchAssoc($res);
2632  return $this->fetchNodeData($row);
2633  }
2634  }
2635 
2643  function fetchPredecessorNode($a_node_id, $a_type = "")
2644  {
2645  global $ilDB;
2646 
2647  if (!isset($a_node_id))
2648  {
2649  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
2650  }
2651 
2652  // get lft value for current node
2653  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2654  'WHERE '.$this->table_tree.'.child = %s '.
2655  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2656  $res = $ilDB->queryF($query,array('integer','integer'),array(
2657  $a_node_id,
2658  $this->tree_id));
2659 
2660  $curr_node = $ilDB->fetchAssoc($res);
2661 
2662  if($a_type)
2663  {
2664  $query = 'SELECT * FROM '.$this->table_tree.' '.
2665  $this->buildJoin().
2666  'WHERE lft < %s '.
2667  'AND '.$this->table_obj_data.'.type = %s '.
2668  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2669  'ORDER BY lft DESC';
2670  $ilDB->setLimit(1);
2671  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
2672  $curr_node['lft'],
2673  $a_type,
2674  $this->tree_id));
2675  }
2676  else
2677  {
2678  $query = 'SELECT * FROM '.$this->table_tree.' '.
2679  $this->buildJoin().
2680  'WHERE lft < %s '.
2681  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2682  'ORDER BY lft DESC';
2683  $ilDB->setLimit(1);
2684  $res = $ilDB->queryF($query,array('integer','integer'),array(
2685  $curr_node['lft'],
2686  $this->tree_id));
2687  }
2688 
2689  if ($res->numRows() < 1)
2690  {
2691  return false;
2692  }
2693  else
2694  {
2695  $row = $ilDB->fetchAssoc($res);
2696  return $this->fetchNodeData($row);
2697  }
2698  }
2699 
2708  function renumber($node_id = 1, $i = 1)
2709  {
2710  global $ilDB;
2711 
2712  // LOCKED ###################################
2713  if($this->__isMainTree())
2714  {
2715  /*
2716  ilDB::_lockTables(array($this->table_tree => 'WRITE',
2717  $this->table_obj_data => 'WRITE',
2718  $this->table_obj_reference => 'WRITE',
2719  'object_translation' => 'WRITE',
2720  'object_data od' => 'WRITE',
2721  'container_reference cr' => 'WRITE'));
2722  */
2723  $ilDB->lockTables(
2724  array(
2725  0 => array('name' => $this->table_tree, 'type' => ilDB::LOCK_WRITE),
2726  1 => array('name' => $this->table_obj_data, 'type' => ilDB::LOCK_WRITE),
2727  2 => array('name' => $this->table_obj_reference, 'type' => ilDB::LOCK_WRITE),
2728  3 => array('name' => 'object_translation', 'type' => ilDB::LOCK_WRITE),
2729  4 => array('name' => 'object_data', 'type' => ilDB::LOCK_WRITE, 'alias' => 'od'),
2730  5 => array('name' => 'container_reference', 'type' => ilDB::LOCK_WRITE, 'alias' => 'cr')
2731  ));
2732  }
2733  $return = $this->__renumber($node_id,$i);
2734  if($this->__isMainTree())
2735  {
2736  $ilDB->unlockTables();
2737  }
2738  // LOCKED ###################################
2739  return $return;
2740  }
2741 
2742  // PRIVATE
2752  function __renumber($node_id = 1, $i = 1)
2753  {
2754  global $ilDB;
2755 
2756  $query = 'UPDATE '.$this->table_tree.' SET lft = %s WHERE child = %s';
2757  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2758  $i,
2759  $node_id));
2760 
2761  $childs = $this->getChilds($node_id);
2762 
2763  foreach ($childs as $child)
2764  {
2765  $i = $this->__renumber($child["child"],$i+1);
2766  }
2767  $i++;
2768 
2769  // Insert a gap at the end of node, if the node has children
2770  if (count($childs) > 0)
2771  {
2772  $i += $this->gap * 2;
2773  }
2774 
2775 
2776  $query = 'UPDATE '.$this->table_tree.' SET rgt = %s WHERE child = %s';
2777  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2778  $i,
2779  $node_id));
2780  return $i;
2781  }
2782 
2783 
2794  function checkForParentType($a_ref_id,$a_type,$a_exclude_source_check = false)
2795  {
2796  // #12577
2797  $cache_key = $a_ref_id.'.'.$a_type.'.'.((int)$a_exclude_source_check);
2798 
2799  // Try to return a cached result
2800  if($this->isCacheUsed() &&
2801  array_key_exists($cache_key, $this->parent_type_cache))
2802  {
2803  return $this->parent_type_cache[$cache_key];
2804  }
2805 
2806  // Store up to 1000 results in cache
2807  $do_cache = ($this->__isMainTree() && count($this->parent_type_cache) < 1000);
2808 
2809  // ref_id is not in tree
2810  if(!$this->isInTree($a_ref_id))
2811  {
2812  if($do_cache)
2813  {
2814  $this->parent_type_cache[$cache_key] = false;
2815  }
2816  return false;
2817  }
2818 
2819  $path = array_reverse($this->getPathFull($a_ref_id));
2820 
2821  // remove first path entry as it is requested node
2822  if($a_exclude_source_check)
2823  {
2824  array_shift($path);
2825  }
2826 
2827  foreach($path as $node)
2828  {
2829  // found matching parent
2830  if($node["type"] == $a_type)
2831  {
2832  if($do_cache)
2833  {
2834  $this->parent_type_cache[$cache_key] = $node["child"];
2835  }
2836  return $node["child"];
2837  }
2838  }
2839 
2840  if($do_cache)
2841  {
2842  $this->parent_type_cache[$cache_key] = false;
2843  }
2844  return 0;
2845  }
2846 
2856  function _removeEntry($a_tree,$a_child,$a_db_table = "tree")
2857  {
2858  global $ilDB,$ilLog,$ilErr;
2859 
2860  if($a_db_table === 'tree')
2861  {
2862  if($a_tree == 1 and $a_child == ROOT_FOLDER_ID)
2863  {
2864  $message = sprintf('%s::_removeEntry(): Tried to delete root node! $a_tree: %s $a_child: %s',
2865  get_class($this),
2866  $a_tree,
2867  $a_child);
2868  $ilLog->write($message,$ilLog->FATAL);
2869  $ilErr->raiseError($message,$ilErr->WARNING);
2870  }
2871  }
2872 
2873  $query = 'DELETE FROM '.$a_db_table.' '.
2874  'WHERE tree = %s '.
2875  'AND child = %s ';
2876  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2877  $a_tree,
2878  $a_child));
2879 
2880  }
2881 
2882  // PRIVATE METHODS
2889  function __isMainTree()
2890  {
2891  return $this->table_tree === 'tree';
2892  }
2893 
2902  function __checkDelete($a_node)
2903  {
2904  global $ilDB;
2905 
2906  // get subtree by lft,rgt
2907  $query = 'SELECT * FROM '.$this->table_tree.' '.
2908  'WHERE lft >= %s '.
2909  'AND rgt <= %s '.
2910  'AND '.$this->tree_pk.' = %s ';
2911  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2912  $a_node['lft'],
2913  $a_node['rgt'],
2914  $a_node[$this->tree_pk]));
2915 
2916  $counter = (int) $lft_childs = array();
2917  while($row = $ilDB->fetchObject($res))
2918  {
2919  $lft_childs[$row->child] = $row->parent;
2920  ++$counter;
2921  }
2922 
2923  // CHECK FOR DUPLICATE CHILD IDS
2924  if($counter != count($lft_childs))
2925  {
2926  $message = sprintf('%s::__checkTree(): Duplicate entries for "child" in maintree! $a_node_id: %s',
2927  get_class($this),
2928  $a_node['child']);
2929  $this->log->write($message,$this->log->FATAL);
2930  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2931  }
2932 
2933  // GET SUBTREE BY PARENT RELATION
2934  $parent_childs = array();
2935  $this->__getSubTreeByParentRelation($a_node['child'],$parent_childs);
2936  $this->__validateSubtrees($lft_childs,$parent_childs);
2937 
2938  return true;
2939  }
2940 
2941  function __getSubTreeByParentRelation($a_node_id,&$parent_childs)
2942  {
2943  global $ilDB;
2944 
2945  // GET PARENT ID
2946  $query = 'SELECT * FROM '.$this->table_tree.' '.
2947  'WHERE child = %s '.
2948  'AND tree = %s ';
2949  $res = $ilDB->queryF($query,array('integer','integer'),array(
2950  $a_node_id,
2951  $this->tree_id));
2952 
2953  $counter = 0;
2954  while($row = $ilDB->fetchObject($res))
2955  {
2956  $parent_childs[$a_node_id] = $row->parent;
2957  ++$counter;
2958  }
2959  // MULTIPLE ENTRIES
2960  if($counter > 1)
2961  {
2962  $message = sprintf('%s::__getSubTreeByParentRelation(): Multiple entries in maintree! $a_node_id: %s',
2963  get_class($this),
2964  $a_node_id);
2965  $this->log->write($message,$this->log->FATAL);
2966  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2967  }
2968 
2969  // GET ALL CHILDS
2970  $query = 'SELECT * FROM '.$this->table_tree.' '.
2971  'WHERE parent = %s ';
2972  $res = $ilDB->queryF($query,array('integer'),array($a_node_id));
2973 
2974  while($row = $ilDB->fetchObject($res))
2975  {
2976  // RECURSION
2977  $this->__getSubTreeByParentRelation($row->child,$parent_childs);
2978  }
2979  return true;
2980  }
2981 
2982  function __validateSubtrees(&$lft_childs,$parent_childs)
2983  {
2984  // SORT BY KEY
2985  ksort($lft_childs);
2986  ksort($parent_childs);
2987 
2988  if(count($lft_childs) != count($parent_childs))
2989  {
2990  $message = sprintf('%s::__validateSubtrees(): (COUNT) Tree is corrupted! Left/Right subtree does not comply .'.
2991  'with parent relation',
2992  get_class($this));
2993  $this->log->write($message,$this->log->FATAL);
2994  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2995  }
2996 
2997  foreach($lft_childs as $key => $value)
2998  {
2999  if($parent_childs[$key] != $value)
3000  {
3001  $message = sprintf('%s::__validateSubtrees(): (COMPARE) Tree is corrupted! Left/Right subtree does not comply '.
3002  'with parent relation',
3003  get_class($this));
3004  $this->log->write($message,$this->log->FATAL);
3005  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
3006  }
3007  if($key == ROOT_FOLDER_ID)
3008  {
3009  $message = sprintf('%s::__validateSubtrees(): (ROOT_FOLDER) Tree is corrupted! Tried to delete root folder',
3010  get_class($this));
3011  $this->log->write($message,$this->log->FATAL);
3012  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
3013  }
3014  }
3015  return true;
3016  }
3017 
3027  public function moveTree($a_source_id,$a_target_id,$a_location = IL_LAST_NODE)
3028  {
3029  global $ilDB;
3030 
3031  if($this->__isMainTree())
3032  {
3033  #ilDB::_lockTables(array('tree' => 'WRITE'));
3034  $ilDB->lockTables(
3035  array(
3036  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
3037 
3038  }
3039  // Receive node infos for source and target
3040  $query = 'SELECT * FROM '.$this->table_tree.' '.
3041  'WHERE ( child = %s OR child = %s ) '.
3042  'AND '.$this->tree_pk.' = %s ';
3043  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
3044  $a_source_id,
3045  $a_target_id,
3046  $this->tree_id));
3047 
3048  // Check in tree
3049  if($res->numRows() != 2)
3050  {
3051  if($this->__isMainTree())
3052  {
3053  $ilDB->unlockTables();
3054  }
3055  $this->log->write(__METHOD__.' Objects not found in tree!',$this->log->FATAL);
3056  $this->ilErr->raiseError('Error moving node',$this->ilErr->WARNING);
3057  }
3058  while($row = $ilDB->fetchObject($res))
3059  {
3060  if($row->child == $a_source_id)
3061  {
3062  $source_lft = $row->lft;
3063  $source_rgt = $row->rgt;
3064  $source_depth = $row->depth;
3065  $source_parent = $row->parent;
3066  }
3067  else
3068  {
3069  $target_lft = $row->lft;
3070  $target_rgt = $row->rgt;
3071  $target_depth = $row->depth;
3072  }
3073  }
3074 
3075  #var_dump("<pre>",$source_lft,$source_rgt,$source_depth,$target_lft,$target_rgt,$target_depth,"<pre>");
3076  // Check target not child of source
3077  if($target_lft >= $source_lft and $target_rgt <= $source_rgt)
3078  {
3079  if($this->__isMainTree())
3080  {
3081  $ilDB->unlockTables();
3082  }
3083  $this->log->write(__METHOD__.' Target is child of source',$this->log->FATAL);
3084  $this->ilErr->raiseError('Error moving node',$this->ilErr->WARNING);
3085  }
3086 
3087  // Now spread the tree at the target location. After this update the table should be still in a consistent state.
3088  // implementation for IL_LAST_NODE
3089  $spread_diff = $source_rgt - $source_lft + 1;
3090  #var_dump("<pre>","SPREAD_DIFF: ",$spread_diff,"<pre>");
3091 
3092  $query = 'UPDATE '.$this->table_tree.' SET '.
3093  'lft = CASE WHEN lft > %s THEN lft + %s ELSE lft END, '.
3094  'rgt = CASE WHEN rgt >= %s THEN rgt + %s ELSE rgt END '.
3095  'WHERE '.$this->tree_pk.' = %s ';
3096  $res = $ilDB->manipulateF($query,array('integer','integer','integer','integer','integer'),array(
3097  $target_rgt,
3098  $spread_diff,
3099  $target_rgt,
3100  $spread_diff,
3101  $this->tree_id));
3102 
3103  // Maybe the source node has been updated, too.
3104  // Check this:
3105  if($source_lft > $target_rgt)
3106  {
3107  $where_offset = $spread_diff;
3108  $move_diff = $target_rgt - $source_lft - $spread_diff;
3109  }
3110  else
3111  {
3112  $where_offset = 0;
3113  $move_diff = $target_rgt - $source_lft;
3114  }
3115  $depth_diff = $target_depth - $source_depth + 1;
3116 
3117 
3118  $query = 'UPDATE '.$this->table_tree.' SET '.
3119  'parent = CASE WHEN parent = %s THEN %s ELSE parent END, '.
3120  'rgt = rgt + %s, '.
3121  'lft = lft + %s, '.
3122  'depth = depth + %s '.
3123  'WHERE lft >= %s '.
3124  'AND rgt <= %s '.
3125  'AND '.$this->tree_pk.' = %s ';
3126  $res = $ilDB->manipulateF($query,
3127  array('integer','integer','integer','integer','integer','integer','integer','integer'),
3128  array(
3129  $source_parent,
3130  $a_target_id,
3131  $move_diff,
3132  $move_diff,
3133  $depth_diff,
3134  $source_lft + $where_offset,
3135  $source_rgt + $where_offset,
3136  $this->tree_id));
3137 
3138  // done: close old gap
3139  $query = 'UPDATE '.$this->table_tree.' SET '.
3140  'lft = CASE WHEN lft >= %s THEN lft - %s ELSE lft END, '.
3141  'rgt = CASE WHEN rgt >= %s THEN rgt - %s ELSE rgt END '.
3142  'WHERE '.$this->tree_pk.' = %s ';
3143 
3144  $res = $ilDB->manipulateF($query,
3145  array('integer','integer','integer','integer','integer'),
3146  array(
3147  $source_lft + $where_offset,
3148  $spread_diff,
3149  $source_rgt +$where_offset,
3150  $spread_diff,
3151  $this->tree_id));
3152 
3153  if($this->__isMainTree())
3154  {
3155  $ilDB->unlockTables();
3156  }
3157 
3158  global $ilAppEventHandler;
3159 
3160  $ilAppEventHandler->raise(
3161  "Services/Tree",
3162  "moveTree",
3163  array(
3164  'tree' => $this->table_tree,
3165  'source_id' => $a_source_id,
3166  'target_id' => $a_target_id)
3167  );
3168 
3169  return true;
3170  }
3171 
3179  public function getRbacSubtreeInfo($a_endnode_id)
3180  {
3181  global $ilDB;
3182 
3183  $query = "SELECT t2.lft lft, t2.rgt rgt, t2.child child, type ".
3184  "FROM ".$this->table_tree." t1 ".
3185  "JOIN ".$this->table_tree." t2 ON (t2.lft BETWEEN t1.lft AND t1.rgt) ".
3186  "JOIN ".$this->table_obj_reference." obr ON t2.child = obr.ref_id ".
3187  "JOIN ".$this->table_obj_data." obd ON obr.obj_id = obd.obj_id ".
3188  "WHERE t1.child = ".$ilDB->quote($a_endnode_id,'integer')." ".
3189  "AND t1.".$this->tree_pk." = ".$ilDB->quote($this->tree_id,'integer')." ".
3190  "AND t2.".$this->tree_pk." = ".$ilDB->quote($this->tree_id,'integer')." ".
3191  "ORDER BY t2.lft";
3192 
3193  $res = $ilDB->query($query);
3194  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
3195  {
3196  $nodes[$row->child]['lft'] = $row->lft;
3197  $nodes[$row->child]['rgt'] = $row->rgt;
3198  $nodes[$row->child]['child']= $row->child;
3199  $nodes[$row->child]['type'] = $row->type;
3200 
3201  }
3202  return (array) $nodes;
3203  }
3204 } // END class.tree
3205 ?>