ILIAS  Release_5_0_x_branch Revision 61816
 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 
7 include_once './Services/Tree/exceptions/class.ilInvalidTreeStructureException.php';
8 
24 class ilTree
25 {
26  const POS_LAST_NODE = -2;
27  const POS_FIRST_NODE = -1;
28 
29 
30  const RELATION_CHILD = 1; // including grand child
31  const RELATION_PARENT = 2; // including grand child
32  const RELATION_SIBLING = 3;
33  const RELATION_EQUALS = 4;
34  const RELATION_NONE = 5;
35 
36 
42  var $ilias;
43 
44 
50  var $log;
51 
57  var $root_id;
58 
64  var $tree_id;
65 
72 
79 
86 
92  var $ref_pk;
93 
99  var $obj_pk;
100 
106  var $tree_pk;
107 
132  var $gap;
133 
134  protected $depth_cache = array();
135  protected $parent_cache = array();
136  protected $in_tree_cache = array();
137 
138  private $tree_impl = NULL;
139 
140 
147  function ilTree($a_tree_id, $a_root_id = 0)
148  {
149  global $ilDB,$ilErr,$ilias,$ilLog;
150 
151  // set db & error handler
152  $this->ilDB = $ilDB;
153 
154  if (!isset($ilErr))
155  {
156  $ilErr = new ilErrorHandling();
157  $ilErr->setErrorHandling(PEAR_ERROR_CALLBACK,array($ilErr,'errorHandler'));
158  }
159  else
160  {
161  $this->ilErr = $ilErr;
162  }
163 
164  $this->lang_code = "en";
165 
166  if (!isset($a_tree_id) or (func_num_args() == 0) )
167  {
168  $this->ilErr->raiseError(get_class($this)."::Constructor(): No tree_id given!",$this->ilErr->WARNING);
169  }
170 
171  if (func_num_args() > 2)
172  {
173  $this->ilErr->raiseError(get_class($this)."::Constructor(): Wrong parameter count!",$this->ilErr->WARNING);
174  }
175 
176  // CREATE LOGGER INSTANCE
177  $this->log = $ilLog;
178 
179  //init variables
180  if (empty($a_root_id))
181  {
182  $a_root_id = ROOT_FOLDER_ID;
183  }
184 
185  $this->tree_id = $a_tree_id;
186  $this->root_id = $a_root_id;
187  $this->table_tree = 'tree';
188  $this->table_obj_data = 'object_data';
189  $this->table_obj_reference = 'object_reference';
190  $this->ref_pk = 'ref_id';
191  $this->obj_pk = 'obj_id';
192  $this->tree_pk = 'tree';
193 
194  $this->use_cache = true;
195 
196  // If cache is activated, cache object translations to improve performance
197  $this->translation_cache = array();
198  $this->parent_type_cache = array();
199 
200  // By default, we create gaps in the tree sequence numbering for 50 nodes
201  $this->gap = 50;
202 
203 
204  // init tree implementation
205  $this->initTreeImplementation();
206  }
207 
211  public function initTreeImplementation()
212  {
213  global $ilDB;
214 
215 
216  if(!is_object($GLOBALS['ilSetting']) or $GLOBALS['ilSetting']->getModule() != 'common')
217  {
218  include_once './Services/Administration/classes/class.ilSetting.php';
219  $setting = new ilSetting('common');
220  }
221  else
222  {
223  $setting = $GLOBALS['ilSetting'];
224  }
225 
226  if($this->__isMainTree())
227  {
228  if($setting->get('main_tree_impl','ns') == 'ns')
229  {
230  #$GLOBALS['ilLog']->write(__METHOD__.': Using nested set.');
231  include_once './Services/Tree/classes/class.ilNestedSetTree.php';
232  $this->tree_impl = new ilNestedSetTree($this);
233  }
234  else
235  {
236  #$GLOBALS['ilLog']->write(__METHOD__.': Using materialized path.');
237  include_once './Services/Tree/classes/class.ilMaterializedPathTree.php';
238  $this->tree_impl = new ilMaterializedPathTree($this);
239  }
240  }
241  else
242  {
243  #$GLOBALS['ilLog']->write(__METHOD__.': Using netsted set for non main tree.');
244  include_once './Services/Tree/classes/class.ilNestedSetTree.php';
245  $this->tree_impl = new ilNestedSetTree($this);
246  }
247  }
248 
253  public function getTreeImplementation()
254  {
255  return $this->tree_impl;
256  }
257 
261  public function useCache($a_use = true)
262  {
263  $this->use_cache = $a_use;
264  }
265 
270  public function isCacheUsed()
271  {
272  return $this->__isMainTree() and $this->use_cache;
273  }
274 
279  public function getDepthCache()
280  {
281  return (array) $this->depth_cache;
282  }
283 
288  public function getParentCache()
289  {
290  return (array) $this->parent_cache;
291  }
292 
297  function initLangCode()
298  {
299  global $ilUser;
300 
301  // lang_code is only required in $this->fetchnodedata
302  if (!is_object($ilUser))
303  {
304  $this->lang_code = "en";
305  }
306  else
307  {
308  $this->lang_code = $ilUser->getCurrentLanguage();
309  }
310  }
311 
316  public function getTreeTable()
317  {
318  return $this->table_tree;
319  }
320 
325  public function getObjectDataTable()
326  {
327  return $this->table_obj_data;
328  }
329 
334  public function getTreePk()
335  {
336  return $this->tree_pk;
337  }
338 
342  public function getTableReference()
343  {
345  }
346 
350  public function getGap()
351  {
352  return $this->gap;
353  }
354 
355  /***
356  * reset in tree cache
357  */
358  public function resetInTreeCache()
359  {
360  $this->in_tree_cache = array();
361  }
362 
363 
378  function setTableNames($a_table_tree,$a_table_obj_data,$a_table_obj_reference = "")
379  {
380  if (!isset($a_table_tree) or !isset($a_table_obj_data))
381  {
382  $this->ilErr->raiseError(get_class($this)."::setTableNames(): Missing parameter! ".
383  "tree table: ".$a_table_tree." object data table: ".$a_table_obj_data,$this->ilErr->WARNING);
384  }
385 
386  $this->table_tree = $a_table_tree;
387  $this->table_obj_data = $a_table_obj_data;
388  $this->table_obj_reference = $a_table_obj_reference;
389 
390  $this->initTreeImplementation();
391 
392  return true;
393  }
394 
401  function setReferenceTablePK($a_column_name)
402  {
403  if (!isset($a_column_name))
404  {
405  $this->ilErr->raiseError(get_class($this)."::setReferenceTablePK(): No column name given!",$this->ilErr->WARNING);
406  }
407 
408  $this->ref_pk = $a_column_name;
409  return true;
410  }
411 
418  function setObjectTablePK($a_column_name)
419  {
420  if (!isset($a_column_name))
421  {
422  $this->ilErr->raiseError(get_class($this)."::setObjectTablePK(): No column name given!",$this->ilErr->WARNING);
423  }
424 
425  $this->obj_pk = $a_column_name;
426  return true;
427  }
428 
435  function setTreeTablePK($a_column_name)
436  {
437  if (!isset($a_column_name))
438  {
439  $this->ilErr->raiseError(get_class($this)."::setTreeTablePK(): No column name given!",$this->ilErr->WARNING);
440  }
441 
442  $this->tree_pk = $a_column_name;
443  return true;
444  }
445 
451  function buildJoin()
452  {
453  if ($this->table_obj_reference)
454  {
455  // Use inner join instead of left join to improve performance
456  return "JOIN ".$this->table_obj_reference." ON ".$this->table_tree.".child=".$this->table_obj_reference.".".$this->ref_pk." ".
457  "JOIN ".$this->table_obj_data." ON ".$this->table_obj_reference.".".$this->obj_pk."=".$this->table_obj_data.".".$this->obj_pk." ";
458  }
459  else
460  {
461  // Use inner join instead of left join to improve performance
462  return "JOIN ".$this->table_obj_data." ON ".$this->table_tree.".child=".$this->table_obj_data.".".$this->obj_pk." ";
463  }
464  }
465 
471  public function getRelation($a_node_a, $a_node_b)
472  {
473  return $this->getRelationOfNodes(
474  $this->getNodeTreeData($a_node_a),
475  $this->getNodeTreeData($a_node_b)
476  );
477  }
478 
485  public function getRelationOfNodes($a_node_a_arr, $a_node_b_arr)
486  {
487  return $this->getTreeImplementation()->getRelation($a_node_a_arr, $a_node_b_arr);
488  }
489 
496  public function getChildIds($a_node)
497  {
498  global $ilDB;
499 
500  $query = 'SELECT * FROM tree '.
501  'WHERE parent = '.$ilDB->quote($a_node,'integer').' '.
502  'AND tree > '.$ilDB->quote(0,'integer');
503  $res = $ilDB->query($query);
504 
505  $childs = array();
506  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
507  {
508  $childs[] = $row->child;
509  }
510  return $childs;
511  }
512 
521  function getChilds($a_node_id, $a_order = "", $a_direction = "ASC")
522  {
523  global $ilBench,$ilDB, $ilObjDataCache, $ilUser;
524 
525  if (!isset($a_node_id))
526  {
527  $message = get_class($this)."::getChilds(): No node_id given!";
528  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
529  }
530 
531  // init childs
532  $childs = array();
533 
534  // number of childs
535  $count = 0;
536 
537  // init order_clause
538  $order_clause = "";
539 
540  // set order_clause if sort order parameter is given
541  if (!empty($a_order))
542  {
543  $order_clause = "ORDER BY ".$a_order." ".$a_direction;
544  }
545  else
546  {
547  $order_clause = "ORDER BY ".$this->table_tree.".lft";
548  }
549 
550 
551  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
552  $this->buildJoin().
553  "WHERE parent = %s " .
554  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
555  $order_clause,
556  $ilDB->quote($a_node_id,'integer'),
557  $ilDB->quote($this->tree_id,'integer'));
558 
559  $res = $ilDB->query($query);
560 
561  if(!$count = $res->numRows())
562  {
563  return array();
564  }
565 
566  // get rows and object ids
567  $rows = array();
568  while($r = $ilDB->fetchAssoc($res))
569  {
570  $rows[] = $r;
571  $obj_ids[] = $r["obj_id"];
572  }
573 
574  // preload object translation information
575  if ($this->__isMainTree() && $this->isCacheUsed() && is_object($ilObjDataCache) &&
576  is_object($ilUser) && $this->lang_code == $ilUser->getLanguage() && !$this->oc_preloaded[$a_node_id])
577  {
578 // $ilObjDataCache->preloadTranslations($obj_ids, $this->lang_code);
579  $ilObjDataCache->preloadObjectCache($obj_ids, $this->lang_code);
580  $this->fetchTranslationFromObjectDataCache($obj_ids);
581  $this->oc_preloaded[$a_node_id] = true;
582  }
583 
584  foreach ($rows as $row)
585  {
586  $childs[] = $this->fetchNodeData($row);
587 
588  // Update cache of main tree
589  if ($this->__isMainTree())
590  {
591  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child'].' = true');
592  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
593  }
594  }
595  $childs[$count - 1]["last"] = true;
596  return $childs;
597  }
598 
608  function getFilteredChilds($a_filter,$a_node,$a_order = "",$a_direction = "ASC")
609  {
610  $childs = $this->getChilds($a_node,$a_order,$a_direction);
611 
612  foreach($childs as $child)
613  {
614  if(!in_array($child["type"],$a_filter))
615  {
616  $filtered[] = $child;
617  }
618  }
619  return $filtered ? $filtered : array();
620  }
621 
622 
630  function getChildsByType($a_node_id,$a_type)
631  {
632  global $ilDB;
633 
634  if (!isset($a_node_id) or !isset($a_type))
635  {
636  $message = get_class($this)."::getChildsByType(): Missing parameter! node_id:".$a_node_id." type:".$a_type;
637  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
638  }
639 
640  if ($a_type=='rolf' && $this->table_obj_reference) {
641  // Performance optimization: A node can only have exactly one
642  // role folder as its child. Therefore we don't need to sort the
643  // results, and we can let the database know about the expected limit.
644  $ilDB->setLimit(1,0);
645  $query = sprintf("SELECT * FROM ".$this->table_tree." ".
646  $this->buildJoin().
647  "WHERE parent = %s ".
648  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
649  "AND ".$this->table_obj_data.".type = %s ",
650  $ilDB->quote($a_node_id,'integer'),
651  $ilDB->quote($this->tree_id,'integer'),
652  $ilDB->quote($a_type,'text'));
653  } else {
654  $query = sprintf("SELECT * FROM ".$this->table_tree." ".
655  $this->buildJoin().
656  "WHERE parent = %s ".
657  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
658  "AND ".$this->table_obj_data.".type = %s ".
659  "ORDER BY ".$this->table_tree.".lft",
660  $ilDB->quote($a_node_id,'integer'),
661  $ilDB->quote($this->tree_id,'integer'),
662  $ilDB->quote($a_type,'text'));
663  }
664  $res = $ilDB->query($query);
665 
666  // init childs
667  $childs = array();
668  while($row = $ilDB->fetchAssoc($res))
669  {
670  $childs[] = $this->fetchNodeData($row);
671  }
672 
673  return $childs ? $childs : array();
674  }
675 
676 
684  public function getChildsByTypeFilter($a_node_id,$a_types,$a_order = "",$a_direction = "ASC")
685  {
686  global $ilDB;
687 
688  if (!isset($a_node_id) or !$a_types)
689  {
690  $message = get_class($this)."::getChildsByType(): Missing parameter! node_id:".$a_node_id." type:".$a_types;
691  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
692  }
693 
694  $filter = ' ';
695  if($a_types)
696  {
697  $filter = 'AND '.$this->table_obj_data.'.type IN('.implode(',',ilUtil::quoteArray($a_types)).') ';
698  }
699 
700  // set order_clause if sort order parameter is given
701  if (!empty($a_order))
702  {
703  $order_clause = "ORDER BY ".$a_order." ".$a_direction;
704  }
705  else
706  {
707  $order_clause = "ORDER BY ".$this->table_tree.".lft";
708  }
709 
710  $query = 'SELECT * FROM '.$this->table_tree.' '.
711  $this->buildJoin().
712  'WHERE parent = '.$ilDB->quote($a_node_id,'integer').' '.
713  'AND '.$this->table_tree.'.'.$this->tree_pk.' = '.$ilDB->quote($this->tree_id,'integer').' '.
714  $filter.
715  $order_clause;
716 
717  $res = $ilDB->query($query);
718  while($row = $ilDB->fetchAssoc($res))
719  {
720  $childs[] = $this->fetchNodeData($row);
721  }
722 
723  return $childs ? $childs : array();
724  }
725 
733  public function insertNode($a_node_id, $a_parent_id, $a_pos = IL_LAST_NODE, $a_reset_deletion_date = false)
734  {
735  global $ilDB;
736 
737 //echo "+$a_node_id+$a_parent_id+";
738  // CHECK node_id and parent_id > 0 if in main tree
739  if($this->__isMainTree())
740  {
741  if($a_node_id <= 1 or $a_parent_id <= 0)
742  {
743  $GLOBALS['ilLog']->logStack();
744  $message = sprintf('%s::insertNode(): Invalid parameters! $a_node_id: %s $a_parent_id: %s',
745  get_class($this),
746  $a_node_id,
747  $a_parent_id);
748  $this->log->write($message,$this->log->FATAL);
749  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
750  }
751  }
752 
753 
754  if (!isset($a_node_id) or !isset($a_parent_id))
755  {
756  $GLOBALS['ilLog']->logStack();
757  $this->ilErr->raiseError(get_class($this)."::insertNode(): Missing parameter! ".
758  "node_id: ".$a_node_id." parent_id: ".$a_parent_id,$this->ilErr->WARNING);
759  }
760  if ($this->isInTree($a_node_id))
761  {
762  $this->ilErr->raiseError(get_class($this)."::insertNode(): Node ".$a_node_id." already in tree ".
763  $this->table_tree."!",$this->ilErr->WARNING);
764  }
765 
766  $this->getTreeImplementation()->insertNode($a_node_id, $a_parent_id, $a_pos);
767 
768  $this->in_tree_cache[$a_node_id] = true;
769 
770  // reset deletion date
771  if ($a_reset_deletion_date)
772  {
773  ilObject::_resetDeletedDate($a_node_id);
774  }
775  }
776 
789  public function getFilteredSubTree($a_node_id,$a_filter = array())
790  {
791  $node = $this->getNodeData($a_node_id);
792 
793  $first = true;
794  $depth = 0;
795  foreach($this->getSubTree($node) as $subnode)
796  {
797  if($depth and $subnode['depth'] > $depth)
798  {
799  continue;
800  }
801  if(!$first and in_array($subnode['type'],$a_filter))
802  {
803  $depth = $subnode['depth'];
804  $first = false;
805  continue;
806  }
807  $depth = 0;
808  $first = false;
809  $filtered[] = $subnode;
810  }
811  return $filtered ? $filtered : array();
812  }
813 
819  public function getSubTreeIds($a_ref_id)
820  {
821  return $this->getTreeImplementation()->getSubTreeIds($a_ref_id);
822  }
823 
824 
834  function getSubTree($a_node,$a_with_data = true, $a_type = "")
835  {
836  global $ilDB;
837 
838  if (!is_array($a_node))
839  {
840  $GLOBALS['ilLog']->logStack();
841  throw new InvalidArgumentException(__METHOD__.': wrong datatype for node data given');
842  }
843 
844  /*
845  if($a_node['lft'] < 1 or $a_node['rgt'] < 2)
846  {
847  $GLOBALS['ilLog']->logStack();
848  $message = sprintf('%s: Invalid node given! $a_node["lft"]: %s $a_node["rgt"]: %s',
849  __METHOD__,
850  $a_node['lft'],
851  $a_node['rgt']);
852 
853  throw new InvalidArgumentException($message);
854  }
855  */
856 
857  $query = $this->getTreeImplementation()->getSubTreeQuery($a_node, $a_type);
858  $res = $ilDB->query($query);
859  while($row = $ilDB->fetchAssoc($res))
860  {
861  if($a_with_data)
862  {
863  $subtree[] = $this->fetchNodeData($row);
864  }
865  else
866  {
867  $subtree[] = $row['child'];
868  }
869  // the lm_data "hack" should be removed in the trunk during an alpha
870  if($this->__isMainTree() || $this->table_tree == "lm_tree")
871  {
872  $this->in_tree_cache[$row['child']] = true;
873  }
874  }
875  return $subtree ? $subtree : array();
876  }
877 
886  function getSubTreeTypes($a_node,$a_filter = 0)
887  {
888  $a_filter = $a_filter ? $a_filter : array();
889 
890  foreach($this->getSubtree($this->getNodeData($a_node)) as $node)
891  {
892  if(in_array($node["type"],$a_filter))
893  {
894  continue;
895  }
896  $types["$node[type]"] = $node["type"];
897  }
898  return $types ? $types : array();
899  }
900 
907  function deleteTree($a_node)
908  {
909  global $ilDB;
910 
911  $GLOBALS['ilLog']->write(__METHOD__.': Delete tree with node '. $a_node);
912 
913  if (!is_array($a_node))
914  {
915  $GLOBALS['ilLog']->logStack();
916  throw new InvalidArgumentException(__METHOD__.': Wrong datatype for node data!');
917  }
918 
919  $GLOBALS['ilLog']->write(__METHOD__.': '. $this->tree_pk);
920 
921  if($this->__isMainTree() )
922  {
923  // @todo normally this part is not executed, since the subtree is first
924  // moved to trash and then deleted.
925  if(!$this->__checkDelete($a_node))
926  {
927  $GLOBALS['ilLog']->logStack();
928  throw new ilInvalidTreeStructureException('Deletion canceled due to invalid tree structure.' . print_r($a_node,true));
929  }
930  }
931 
932  $this->getTreeImplementation()->deleteTree($a_node['child']);
933 
934  $this->resetInTreeCache();
935 
936  }
937 
948  function getPathFull($a_endnode_id, $a_startnode_id = 0)
949  {
950  $pathIds =& $this->getPathId($a_endnode_id, $a_startnode_id);
951 
952  // We retrieve the full path in a single query to improve performance
953  global $ilDB;
954 
955  // Abort if no path ids were found
956  if (count($pathIds) == 0)
957  {
958  return null;
959  }
960 
961  $inClause = 'child IN (';
962  for ($i=0; $i < count($pathIds); $i++)
963  {
964  if ($i > 0) $inClause .= ',';
965  $inClause .= $ilDB->quote($pathIds[$i],'integer');
966  }
967  $inClause .= ')';
968 
969  $q = 'SELECT * '.
970  'FROM '.$this->table_tree.' '.
971  $this->buildJoin().' '.
972  'WHERE '.$inClause.' '.
973  'AND '.$this->table_tree.'.'.$this->tree_pk.' = '.$this->ilDB->quote($this->tree_id,'integer').' '.
974  'ORDER BY depth';
975  $r = $ilDB->query($q);
976 
977  $pathFull = array();
978  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
979  {
980  $pathFull[] = $this->fetchNodeData($row);
981 
982  // Update cache
983  if ($this->__isMainTree())
984  {
985  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child']);
986  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
987  }
988  }
989  return $pathFull;
990  }
991 
992 
999  function preloadDepthParent($a_node_ids)
1000  {
1001  global $ilDB;
1002 
1003  if (!$this->__isMainTree() || !is_array($a_node_ids) || !$this->isCacheUsed())
1004  {
1005  return;
1006  }
1007 
1008  $res = $ilDB->query('SELECT t.depth, t.parent, t.child '.
1009  'FROM '.$this->table_tree.' t '.
1010  'WHERE '.$ilDB->in("child", $a_node_ids, false, "integer").
1011  'AND '.$this->tree_pk.' = '.$ilDB->quote($this->tree_id, "integer"));
1012  while ($row = $ilDB->fetchAssoc($res))
1013  {
1014  $this->depth_cache[$row["child"]] = $row["depth"];
1015  $this->parent_cache[$row["child"]] = $row["parent"];
1016  }
1017  }
1018 
1028  public function getPathId($a_endnode_id, $a_startnode_id = 0)
1029  {
1030  if(!$a_endnode_id)
1031  {
1032  $GLOBALS['ilLog']->logStack();
1033  throw new InvalidArgumentException(__METHOD__.': No endnode given!');
1034  }
1035 
1036  // path id cache
1037  if ($this->isCacheUsed() && isset($this->path_id_cache[$a_endnode_id][$a_startnode_id]))
1038  {
1039 //echo "<br>getPathIdhit";
1040  return $this->path_id_cache[$a_endnode_id][$a_startnode_id];
1041  }
1042 //echo "<br>miss";
1043 
1044  $pathIds = $this->getTreeImplementation()->getPathIds($a_endnode_id, $a_startnode_id);
1045 
1046  if($this->__isMainTree())
1047  {
1048  $this->path_id_cache[$a_endnode_id][$a_startnode_id] = $pathIds;
1049  }
1050  return $pathIds;
1051  }
1052 
1053  // BEGIN WebDAV: getNodePathForTitlePath function added
1071  function getNodePathForTitlePath($titlePath, $a_startnode_id = null)
1072  {
1073  global $ilDB, $log;
1074  //$log->write('getNodePathForTitlePath('.implode('/',$titlePath));
1075 
1076  // handle empty title path
1077  if ($titlePath == null || count($titlePath) == 0)
1078  {
1079  if ($a_startnode_id == 0)
1080  {
1081  return null;
1082  }
1083  else
1084  {
1085  return $this->getNodePath($a_startnode_id);
1086  }
1087  }
1088 
1089  // fetch the node path up to the startnode
1090  if ($a_startnode_id != null && $a_startnode_id != 0)
1091  {
1092  // Start using the node path to the root of the relative path
1093  $nodePath = $this->getNodePath($a_startnode_id);
1094  $parent = $a_startnode_id;
1095  }
1096  else
1097  {
1098  // Start using the root of the tree
1099  $nodePath = array();
1100  $parent = 0;
1101  }
1102 
1103 
1104  // Convert title path into Unicode Normal Form C
1105  // This is needed to ensure that we can compare title path strings with
1106  // strings from the database.
1107  require_once('include/Unicode/UtfNormal.php');
1108  include_once './Services/Utilities/classes/class.ilStr.php';
1109  $inClause = 'd.title IN (';
1110  for ($i=0; $i < count($titlePath); $i++)
1111  {
1112  $titlePath[$i] = ilStr::strToLower(UtfNormal::toNFC($titlePath[$i]));
1113  if ($i > 0) $inClause .= ',';
1114  $inClause .= $ilDB->quote($titlePath[$i],'text');
1115  }
1116  $inClause .= ')';
1117 
1118  // Fetch all rows that are potential path elements
1119  if ($this->table_obj_reference)
1120  {
1121  $joinClause = 'JOIN '.$this->table_obj_reference.' r ON t.child = r.'.$this->ref_pk.' '.
1122  'JOIN '.$this->table_obj_data.' d ON r.'.$this->obj_pk.' = d.'.$this->obj_pk;
1123  }
1124  else
1125  {
1126  $joinClause = 'JOIN '.$this->table_obj_data.' d ON t.child = d.'.$this->obj_pk;
1127  }
1128  // The ORDER BY clause in the following SQL statement ensures that,
1129  // in case of a multiple objects with the same title, always the Object
1130  // with the oldest ref_id is chosen.
1131  // This ensure, that, if a new object with the same title is added,
1132  // WebDAV clients can still work with the older object.
1133  $q = 'SELECT t.depth, t.parent, t.child, d.'.$this->obj_pk.' obj_id, d.type, d.title '.
1134  'FROM '.$this->table_tree.' t '.
1135  $joinClause.' '.
1136  'WHERE '.$inClause.' '.
1137  'AND t.depth <= '.(count($titlePath)+count($nodePath)).' '.
1138  'AND t.tree = 1 '.
1139  'ORDER BY t.depth, t.child ASC';
1140  $r = $ilDB->query($q);
1141 
1142  $rows = array();
1143  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1144  {
1145  $row['title'] = UtfNormal::toNFC($row['title']);
1146  $row['ref_id'] = $row['child'];
1147  $rows[] = $row;
1148  }
1149 
1150  // Extract the path elements from the fetched rows
1151  for ($i = 0; $i < count($titlePath); $i++) {
1152  $pathElementFound = false;
1153  foreach ($rows as $row) {
1154  if ($row['parent'] == $parent &&
1155  ilStr::strToLower($row['title']) == $titlePath[$i])
1156  {
1157  // FIXME - We should test here, if the user has
1158  // 'visible' permission for the object.
1159  $nodePath[] = $row;
1160  $parent = $row['child'];
1161  $pathElementFound = true;
1162  break;
1163  }
1164  }
1165  // Abort if we haven't found a path element for the current depth
1166  if (! $pathElementFound)
1167  {
1168  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):null');
1169  return null;
1170  }
1171  }
1172  // Return the node path
1173  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):'.var_export($nodePath,true));
1174  return $nodePath;
1175  }
1176  // END WebDAV: getNodePathForTitlePath function added
1177  // END WebDAV: getNodePath function added
1194  function getNodePath($a_endnode_id, $a_startnode_id = 0)
1195  {
1196  global $ilDB;
1197 
1198  $pathIds = $this->getPathId($a_endnode_id, $a_startnode_id);
1199 
1200  // Abort if no path ids were found
1201  if (count($pathIds) == 0)
1202  {
1203  return null;
1204  }
1205 
1206 
1207  $types = array();
1208  $data = array();
1209  for ($i = 0; $i < count($pathIds); $i++)
1210  {
1211  $types[] = 'integer';
1212  $data[] = $pathIds[$i];
1213  }
1214 
1215  $query = 'SELECT t.depth,t.parent,t.child,d.obj_id,d.type,d.title '.
1216  'FROM '.$this->table_tree.' t '.
1217  'JOIN '.$this->table_obj_reference.' r ON r.ref_id = t.child '.
1218  'JOIN '.$this->table_obj_data.' d ON d.obj_id = r.obj_id '.
1219  'WHERE '.$ilDB->in('t.child',$data,false,'integer').' '.
1220  'ORDER BY t.depth ';
1221 
1222  $res = $ilDB->queryF($query,$types,$data);
1223 
1224  $titlePath = array();
1225  while ($row = $ilDB->fetchAssoc($res))
1226  {
1227  $titlePath[] = $row;
1228  }
1229  return $titlePath;
1230  }
1231  // END WebDAV: getNodePath function added
1232 
1239  function checkTree()
1240  {
1241  global $ilDB;
1242 
1243  $types = array('integer');
1244  $query = 'SELECT lft,rgt FROM '.$this->table_tree.' '.
1245  'WHERE '.$this->tree_pk.' = %s ';
1246 
1247  $res = $ilDB->queryF($query,$types,array($this->tree_id));
1248  while ($row = $ilDB->fetchObject($res))
1249  {
1250  $lft[] = $row->lft;
1251  $rgt[] = $row->rgt;
1252  }
1253 
1254  $all = array_merge($lft,$rgt);
1255  $uni = array_unique($all);
1256 
1257  if (count($all) != count($uni))
1258  {
1259  $message = sprintf('%s::checkTree(): Tree is corrupted!',
1260  get_class($this));
1261 
1262  $this->log->write($message,$this->log->FATAL);
1263  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1264  }
1265 
1266  return true;
1267  }
1268 
1272  function checkTreeChilds($a_no_zero_child = true)
1273  {
1274  global $ilDB;
1275 
1276  $query = 'SELECT * FROM '.$this->table_tree.' '.
1277  'WHERE '.$this->tree_pk.' = %s '.
1278  'ORDER BY lft';
1279  $r1 = $ilDB->queryF($query,array('integer'),array($this->tree_id));
1280 
1281  while ($row = $ilDB->fetchAssoc($r1))
1282  {
1283 //echo "tree:".$row[$this->tree_pk].":lft:".$row["lft"].":rgt:".$row["rgt"].":child:".$row["child"].":<br>";
1284  if (($row["child"] == 0) && $a_no_zero_child)
1285  {
1286  $this->ilErr->raiseError(get_class($this)."::checkTreeChilds(): Tree contains child with ID 0!",$this->ilErr->WARNING);
1287  }
1288 
1289  if ($this->table_obj_reference)
1290  {
1291  // get object reference data
1292  $query = 'SELECT * FROM '.$this->table_obj_reference.' WHERE '.$this->ref_pk.' = %s ';
1293  $r2 = $ilDB->queryF($query,array('integer'),array($row['child']));
1294 
1295 //echo "num_childs:".$r2->numRows().":<br>";
1296  if ($r2->numRows() == 0)
1297  {
1298  $this->ilErr->raiseError(get_class($this)."::checkTree(): No Object-to-Reference entry found for ID ".
1299  $row["child"]."!",$this->ilErr->WARNING);
1300  }
1301  if ($r2->numRows() > 1)
1302  {
1303  $this->ilErr->raiseError(get_class($this)."::checkTree(): More Object-to-Reference entries found for ID ".
1304  $row["child"]."!",$this->ilErr->WARNING);
1305  }
1306 
1307  // get object data
1308  $obj_ref = $ilDB->fetchAssoc($r2);
1309 
1310  $query = 'SELECT * FROM '.$this->table_obj_data.' WHERE '.$this->obj_pk.' = %s';
1311  $r3 = $ilDB->queryF($query,array('integer'),array($obj_ref[$this->obj_pk]));
1312  if ($r3->numRows() == 0)
1313  {
1314  $this->ilErr->raiseError(get_class($this)."::checkTree(): No child found for ID ".
1315  $obj_ref[$this->obj_pk]."!",$this->ilErr->WARNING);
1316  }
1317  if ($r3->numRows() > 1)
1318  {
1319  $this->ilErr->raiseError(get_class($this)."::checkTree(): More childs found for ID ".
1320  $obj_ref[$this->obj_pk]."!",$this->ilErr->WARNING);
1321  }
1322 
1323  }
1324  else
1325  {
1326  // get only object data
1327  $query = 'SELECT * FROM '.$this->table_obj_data.' WHERE '.$this->obj_pk.' = %s';
1328  $r2 = $ilDB->queryF($query,array('integer'),array($row['child']));
1329 //echo "num_childs:".$r2->numRows().":<br>";
1330  if ($r2->numRows() == 0)
1331  {
1332  $this->ilErr->raiseError(get_class($this)."::checkTree(): No child found for ID ".
1333  $row["child"]."!",$this->ilErr->WARNING);
1334  }
1335  if ($r2->numRows() > 1)
1336  {
1337  $this->ilErr->raiseError(get_class($this)."::checkTree(): More childs found for ID ".
1338  $row["child"]."!",$this->ilErr->WARNING);
1339  }
1340  }
1341  }
1342 
1343  return true;
1344  }
1345 
1351  public function getMaximumDepth()
1352  {
1353  global $ilDB;
1354 
1355  $query = 'SELECT MAX(depth) depth FROM '.$this->table_tree;
1356  $res = $ilDB->query($query);
1357 
1358  $row = $ilDB->fetchAssoc($res);
1359  return $row['depth'];
1360  }
1361 
1368  function getDepth($a_node_id)
1369  {
1370  global $ilDB;
1371 
1372  if ($a_node_id)
1373  {
1374  $query = 'SELECT depth FROM '.$this->table_tree.' '.
1375  'WHERE child = %s '.
1376  'AND '.$this->tree_pk.' = %s ';
1377  $res = $ilDB->queryF($query,array('integer','integer'),array($a_node_id,$this->tree_id));
1378  $row = $ilDB->fetchObject($res);
1379 
1380  return $row->depth;
1381  }
1382  else
1383  {
1384  return 1;
1385  }
1386  }
1387 
1395  public function getNodeTreeData($a_node_id)
1396  {
1397  global $ilDB;
1398 
1399  if(!$a_node_id)
1400  {
1401  $GLOBALS['ilLog']->logStack();
1402  throw new InvalidArgumentException('Missing or empty parameter $a_node_id: '. $a_node_id);
1403  }
1404 
1405  $query = 'SELECT * FROM '.$this->table_tree.' '.
1406  'WHERE child = '.$ilDB->quote($a_node_id,'integer');
1407  $res = $ilDB->query($query);
1408  while($row = $res->fetchRow(DB_FETCHMODE_ASSOC))
1409  {
1410  return $row;
1411  }
1412  return array();
1413  }
1414 
1415 
1423  // BEGIN WebDAV: Pass tree id to this method
1424  //function getNodeData($a_node_id)
1425  function getNodeData($a_node_id, $a_tree_pk = null)
1426  // END PATCH WebDAV: Pass tree id to this method
1427  {
1428  global $ilDB;
1429 
1430  if (!isset($a_node_id))
1431  {
1432  $GLOBALS['ilLog']->logStack();
1433  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1434  }
1435  if($this->__isMainTree())
1436  {
1437  if($a_node_id < 1)
1438  {
1439  $message = sprintf('%s::getNodeData(): No valid parameter given! $a_node_id: %s',
1440  get_class($this),
1441  $a_node_id);
1442 
1443  $this->log->write($message,$this->log->FATAL);
1444  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1445  }
1446  }
1447 
1448  // BEGIN WebDAV: Pass tree id to this method
1449  $query = 'SELECT * FROM '.$this->table_tree.' '.
1450  $this->buildJoin().
1451  'WHERE '.$this->table_tree.'.child = %s '.
1452  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
1453  $res = $ilDB->queryF($query,array('integer','integer'),array(
1454  $a_node_id,
1455  $a_tree_pk === null ? $this->tree_id : $a_tree_pk));
1456  // END WebDAV: Pass tree id to this method
1457  $row = $ilDB->fetchAssoc($res);
1459 
1460  return $this->fetchNodeData($row);
1461  }
1462 
1470  function fetchNodeData($a_row)
1471  {
1472  global $objDefinition, $lng, $ilBench,$ilDB;
1473 
1474  //$ilBench->start("Tree", "fetchNodeData_getRow");
1475  $data = $a_row;
1476  $data["desc"] = $a_row["description"]; // for compability
1477  //$ilBench->stop("Tree", "fetchNodeData_getRow");
1478 
1479  // multilingual support systemobjects (sys) & categories (db)
1480  //$ilBench->start("Tree", "fetchNodeData_readDefinition");
1481  if (is_object($objDefinition))
1482  {
1483  $translation_type = $objDefinition->getTranslationType($data["type"]);
1484  }
1485  //$ilBench->stop("Tree", "fetchNodeData_readDefinition");
1486 
1487  if ($translation_type == "sys")
1488  {
1489  //$ilBench->start("Tree", "fetchNodeData_getLangData");
1490  if ($data["type"] == "rolf" and $data["obj_id"] != ROLE_FOLDER_ID)
1491  {
1492  $data["description"] = $lng->txt("obj_".$data["type"]."_local_desc").$data["title"].$data["desc"];
1493  $data["desc"] = $lng->txt("obj_".$data["type"]."_local_desc").$data["title"].$data["desc"];
1494  $data["title"] = $lng->txt("obj_".$data["type"]."_local");
1495  }
1496  else
1497  {
1498  $data["title"] = $lng->txt("obj_".$data["type"]);
1499  $data["description"] = $lng->txt("obj_".$data["type"]."_desc");
1500  $data["desc"] = $lng->txt("obj_".$data["type"]."_desc");
1501  }
1502  //$ilBench->stop("Tree", "fetchNodeData_getLangData");
1503  }
1504  elseif ($translation_type == "db")
1505  {
1506 
1507  // Try to retrieve object translation from cache
1508  if ($this->isCacheUsed() &&
1509  array_key_exists($data["obj_id"].'.'.$lang_code, $this->translation_cache)) {
1510 
1511  $key = $data["obj_id"].'.'.$lang_code;
1512  $data["title"] = $this->translation_cache[$key]['title'];
1513  $data["description"] = $this->translation_cache[$key]['description'];
1514  $data["desc"] = $this->translation_cache[$key]['desc'];
1515  }
1516  else
1517  {
1518  // Object translation is not in cache, read it from database
1519  //$ilBench->start("Tree", "fetchNodeData_getTranslation");
1520  $query = 'SELECT title,description FROM object_translation '.
1521  'WHERE obj_id = %s '.
1522  'AND lang_code = %s '.
1523  'AND NOT lang_default = %s';
1524 
1525  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
1526  $data['obj_id'],
1527  $this->lang_code,
1528  1));
1529  $row = $ilDB->fetchObject($res);
1530 
1531  if ($row)
1532  {
1533  $data["title"] = $row->title;
1534  $data["description"] = ilUtil::shortenText($row->description,ilObject::DESC_LENGTH,true);
1535  $data["desc"] = $row->description;
1536  }
1537  //$ilBench->stop("Tree", "fetchNodeData_getTranslation");
1538 
1539  // Store up to 1000 object translations in cache
1540  if ($this->isCacheUsed() && count($this->translation_cache) < 1000)
1541  {
1542  $key = $data["obj_id"].'.'.$lang_code;
1543  $this->translation_cache[$key] = array();
1544  $this->translation_cache[$key]['title'] = $data["title"] ;
1545  $this->translation_cache[$key]['description'] = $data["description"];
1546  $this->translation_cache[$key]['desc'] = $data["desc"];
1547  }
1548  }
1549  }
1550 
1551  // TODO: Handle this switch by module.xml definitions
1552  if($data['type'] == 'crsr' or $data['type'] == 'catr')
1553  {
1554  include_once('./Services/ContainerReference/classes/class.ilContainerReference.php');
1555  $data['title'] = ilContainerReference::_lookupTitle($data['obj_id']);
1556  }
1557 
1558  return $data ? $data : array();
1559  }
1560 
1566  protected function fetchTranslationFromObjectDataCache($a_obj_ids)
1567  {
1568  global $ilObjDataCache;
1569 
1570  if ($this->isCacheUsed() && is_array($a_obj_ids) && is_object($ilObjDataCache))
1571  {
1572  foreach ($a_obj_ids as $id)
1573  {
1574  $this->translation_cache[$id.'.']['title'] = $ilObjDataCache->lookupTitle($id);
1575  $this->translation_cache[$id.'.']['description'] = $ilObjDataCache->lookupDescription($id);;
1576  $this->translation_cache[$id.'.']['desc'] =
1577  $this->translation_cache[$id.'.']['description'];
1578  }
1579  }
1580  }
1581 
1582 
1590  function isInTree($a_node_id)
1591  {
1592  global $ilDB;
1593 
1594  if (!isset($a_node_id))
1595  {
1596  return false;
1597  #$this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1598  }
1599  // is in tree cache
1600  if ($this->isCacheUsed() && isset($this->in_tree_cache[$a_node_id]))
1601  {
1602  #$GLOBALS['ilLog']->write(__METHOD__.': Using in tree cache '.$a_node_id);
1603 //echo "<br>in_tree_hit";
1604  return $this->in_tree_cache[$a_node_id];
1605  }
1606 
1607  $query = 'SELECT * FROM '.$this->table_tree.' '.
1608  'WHERE '.$this->table_tree.'.child = %s '.
1609  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s';
1610 
1611  $res = $ilDB->queryF($query,array('integer','integer'),array(
1612  $a_node_id,
1613  $this->tree_id));
1614 
1615  if ($res->numRows() > 0)
1616  {
1617  if($this->__isMainTree())
1618  {
1619  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
1620  $this->in_tree_cache[$a_node_id] = true;
1621  }
1622  return true;
1623  }
1624  else
1625  {
1626  if($this->__isMainTree())
1627  {
1628  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = false');
1629  $this->in_tree_cache[$a_node_id] = false;
1630  }
1631  return false;
1632  }
1633  }
1634 
1642  public function getParentNodeData($a_node_id)
1643  {
1644  global $ilDB;
1645  global $ilLog;
1646 
1647  if (!isset($a_node_id))
1648  {
1649  $ilLog->logStack();
1650  throw new InvalidArgumentException(__METHOD__.': No node_id given!');
1651  }
1652 
1653  if ($this->table_obj_reference)
1654  {
1655  // Use inner join instead of left join to improve performance
1656  $innerjoin = "JOIN ".$this->table_obj_reference." ON v.child=".$this->table_obj_reference.".".$this->ref_pk." ".
1657  "JOIN ".$this->table_obj_data." ON ".$this->table_obj_reference.".".$this->obj_pk."=".$this->table_obj_data.".".$this->obj_pk." ";
1658  }
1659  else
1660  {
1661  // Use inner join instead of left join to improve performance
1662  $innerjoin = "JOIN ".$this->table_obj_data." ON v.child=".$this->table_obj_data.".".$this->obj_pk." ";
1663  }
1664 
1665  $query = 'SELECT * FROM '.$this->table_tree.' s, '.$this->table_tree.' v '.
1666  $innerjoin.
1667  'WHERE s.child = %s '.
1668  'AND s.parent = v.child '.
1669  'AND s.'.$this->tree_pk.' = %s '.
1670  'AND v.'.$this->tree_pk.' = %s';
1671  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
1672  $a_node_id,
1673  $this->tree_id,
1674  $this->tree_id));
1675  $row = $ilDB->fetchAssoc($res);
1676  return $this->fetchNodeData($row);
1677  }
1678 
1686  public function isGrandChild($a_startnode_id,$a_querynode_id)
1687  {
1688  return $this->getRelation($a_startnode_id, $a_querynode_id) == self::RELATION_PARENT;
1689  }
1690 
1699  function addTree($a_tree_id,$a_node_id = -1)
1700  {
1701  global $ilDB;
1702 
1703  // FOR SECURITY addTree() IS NOT ALLOWED ON MAIN TREE
1704  if($this->__isMainTree())
1705  {
1706  $message = sprintf('%s::addTree(): Operation not allowed on main tree! $a_tree_if: %s $a_node_id: %s',
1707  get_class($this),
1708  $a_tree_id,
1709  $a_node_id);
1710  $this->log->write($message,$this->log->FATAL);
1711  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1712  }
1713 
1714  if (!isset($a_tree_id))
1715  {
1716  $this->ilErr->raiseError(get_class($this)."::addTree(): No tree_id given! ",$this->ilErr->WARNING);
1717  }
1718 
1719  if ($a_node_id <= 0)
1720  {
1721  $a_node_id = $a_tree_id;
1722  }
1723 
1724  $query = 'INSERT INTO '.$this->table_tree.' ('.
1725  $this->tree_pk.', child,parent,lft,rgt,depth) '.
1726  'VALUES '.
1727  '(%s,%s,%s,%s,%s,%s)';
1728  $res = $ilDB->manipulateF($query,array('integer','integer','integer','integer','integer','integer'),array(
1729  $a_tree_id,
1730  $a_node_id,
1731  0,
1732  1,
1733  2,
1734  1));
1735 
1736  return true;
1737  }
1738 
1747  public function getNodeDataByType($a_type)
1748  {
1749  global $ilDB;
1750 
1751  if(!isset($a_type) or (!is_string($a_type)))
1752  {
1753  $GLOBALS['ilLog']->logStack();
1754  throw new InvalidArgumentException('Type not given or wrong datatype');
1755  }
1756 
1757  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1758  $this->buildJoin().
1759  'WHERE ' . $this->table_obj_data . '.type = ' . $this->ilDB->quote($a_type, 'text').
1760  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = ' . $this->ilDB->quote($this->tree_id, 'integer');
1761 
1762  $res = $ilDB->query($query);
1763  $data = array();
1764  while($row = $ilDB->fetchAssoc($res))
1765  {
1766  $data[] = $this->fetchNodeData($row);
1767  }
1768 
1769  return $data;
1770  }
1771 
1779  public function removeTree($a_tree_id)
1780  {
1781  global $ilDB;
1782 
1783  // OPERATION NOT ALLOWED ON MAIN TREE
1784  if($this->__isMainTree())
1785  {
1786  $GLOBALS['ilLog']->logStack();
1787  throw new InvalidArgumentException('Operation not allowed on main tree');
1788  }
1789  if (!$a_tree_id)
1790  {
1791  $GLOBALS['ilLog']->logStack();
1792  throw new InvalidArgumentException('Missing parameter tree id');
1793  }
1794 
1795  $query = 'DELETE FROM '.$this->table_tree.
1796  ' WHERE '.$this->tree_pk.' = %s ';
1797  $ilDB->manipulateF($query,array('integer'),array($a_tree_id));
1798  return true;
1799  }
1800 
1807  public function moveToTrash($a_node_id, $a_set_deleted = false)
1808  {
1809  return $this->saveSubTree($a_node_id, $a_set_deleted);
1810  }
1811 
1822  public function saveSubTree($a_node_id, $a_set_deleted = false)
1823  {
1824  global $ilDB;
1825 
1826  if(!$a_node_id)
1827  {
1828  $GLOBALS['ilLog']->logStack();
1829  throw new InvalidArgumentException('No valid parameter given! $a_node_id: '.$a_node_id);
1830  }
1831 
1832  // LOCKED ###############################################
1833  if($this->__isMainTree())
1834  {
1835  $ilDB->lockTables(
1836  array(
1837  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE),
1838  1 => array('name' => 'object_reference', 'type' => ilDB::LOCK_WRITE)));
1839  }
1840 
1841  $query = $this->getTreeImplementation()->getSubTreeQuery($this->getNodeTreeData($a_node_id),'',false);
1842  $res = $ilDB->query($query);
1843 
1844  $subnodes = array();
1845  while($row = $res->fetchRow(DB_FETCHMODE_ASSOC))
1846  {
1847  $subnodes[] = $row['child'];
1848  }
1849 
1850  if(!count($subnodes))
1851  {
1852  // Possibly already deleted
1853  // Unlock locked tables before returning
1854  if($this->__isMainTree())
1855  {
1856  $ilDB->unlockTables();
1857  }
1858  return false;
1859  }
1860 
1861  if($a_set_deleted)
1862  {
1863  include_once './Services/Object/classes/class.ilObject.php';
1864  ilObject::setDeletedDates($subnodes);
1865  }
1866 
1867  // netsted set <=> mp
1868  $this->getTreeImplementation()->moveToTrash($a_node_id);
1869 
1870  if($this->__isMainTree())
1871  {
1872  $ilDB->unlockTables();
1873  }
1874 
1875  // LOCKED ###############################################
1876  return true;
1877  }
1878 
1883  public function isDeleted($a_node_id)
1884  {
1885  return $this->isSaved($a_node_id);
1886  }
1887 
1893  public function isSaved($a_node_id)
1894  {
1895  global $ilDB;
1896 
1897  // is saved cache
1898  if ($this->isCacheUsed() && isset($this->is_saved_cache[$a_node_id]))
1899  {
1900 //echo "<br>issavedhit";
1901  return $this->is_saved_cache[$a_node_id];
1902  }
1903 
1904  $query = 'SELECT '.$this->tree_pk.' FROM '.$this->table_tree.' '.
1905  'WHERE child = %s ';
1906  $res = $ilDB->queryF($query,array('integer'),array($a_node_id));
1907  $row = $ilDB->fetchAssoc($res);
1908 
1909  if ($row[$this->tree_pk] < 0)
1910  {
1911  if($this->__isMainTree())
1912  {
1913  $this->is_saved_cache[$a_node_id] = true;
1914  }
1915  return true;
1916  }
1917  else
1918  {
1919  if($this->__isMainTree())
1920  {
1921  $this->is_saved_cache[$a_node_id] = false;
1922  }
1923  return false;
1924  }
1925  }
1926 
1933  public function preloadDeleted($a_node_ids)
1934  {
1935  global $ilDB;
1936 
1937  if (!is_array($a_node_ids) || !$this->isCacheUsed())
1938  {
1939  return;
1940  }
1941 
1942  $query = 'SELECT '.$this->tree_pk.', child FROM '.$this->table_tree.' '.
1943  'WHERE '.$ilDB->in("child", $a_node_ids, false, "integer");
1944 
1945  $res = $ilDB->query($query);
1946  while ($row = $ilDB->fetchAssoc($res))
1947  {
1948  if ($row[$this->tree_pk] < 0)
1949  {
1950  if($this->__isMainTree())
1951  {
1952  $this->is_saved_cache[$row["child"]] = true;
1953  }
1954  }
1955  else
1956  {
1957  if($this->__isMainTree())
1958  {
1959  $this->is_saved_cache[$row["child"]] = false;
1960  }
1961  }
1962  }
1963  }
1964 
1965 
1972  function getSavedNodeData($a_parent_id)
1973  {
1974  global $ilDB;
1975 
1976  if (!isset($a_parent_id))
1977  {
1978  $this->ilErr->raiseError(get_class($this)."::getSavedNodeData(): No node_id given!",$this->ilErr->WARNING);
1979  }
1980 
1981  $query = 'SELECT * FROM '.$this->table_tree.' '.
1982  $this->buildJoin().
1983  'WHERE '.$this->table_tree.'.'.$this->tree_pk.' < %s '.
1984  'AND '.$this->table_tree.'.parent = %s';
1985  $res = $ilDB->queryF($query,array('integer','integer'),array(
1986  0,
1987  $a_parent_id));
1988 
1989  while($row = $ilDB->fetchAssoc($res))
1990  {
1991  $saved[] = $this->fetchNodeData($row);
1992  }
1993 
1994  return $saved ? $saved : array();
1995  }
1996 
2003  function getSavedNodeObjIds(array $a_obj_ids)
2004  {
2005  global $ilDB;
2006 
2007  $query = 'SELECT '.$this->table_obj_data.'.obj_id FROM '.$this->table_tree.' '.
2008  $this->buildJoin().
2009  'WHERE '.$this->table_tree.'.'.$this->tree_pk.' < '.$ilDB->quote(0, 'integer').' '.
2010  'AND '.$ilDB->in($this->table_obj_data.'.obj_id', $a_obj_ids, '', 'integer');
2011  $res = $ilDB->query($query);
2012  while($row = $ilDB->fetchAssoc($res))
2013  {
2014  $saved[] = $row['obj_id'];
2015  }
2016 
2017  return $saved ? $saved : array();
2018  }
2019 
2026  function getParentId($a_node_id)
2027  {
2028  global $ilDB;
2029 
2030  if (!isset($a_node_id))
2031  {
2032  $this->ilErr->raiseError(get_class($this)."::getParentId(): No node_id given! ",$this->ilErr->WARNING);
2033  }
2034 
2035  $query = 'SELECT parent FROM '.$this->table_tree.' '.
2036  'WHERE child = %s '.
2037  'AND '.$this->tree_pk.' = %s ';
2038  $res = $ilDB->queryF($query,array('integer','integer'),array(
2039  $a_node_id,
2040  $this->tree_id));
2041 
2042  $row = $ilDB->fetchObject($res);
2043  return $row->parent;
2044  }
2045 
2052  function getLeftValue($a_node_id)
2053  {
2054  global $ilDB;
2055 
2056  if (!isset($a_node_id))
2057  {
2058  $this->ilErr->raiseError(get_class($this)."::getLeftValued(): No node_id given! ",$this->ilErr->WARNING);
2059  }
2060 
2061  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2062  'WHERE child = %s '.
2063  'AND '.$this->tree_pk.' = %s ';
2064  $res = $ilDB->queryF($query,array('integer','integer'),array(
2065  $a_node_id,
2066  $this->tree_id));
2067  $row = $ilDB->fetchObject($res);
2068  return $row->lft;
2069  }
2070 
2077  function getChildSequenceNumber($a_node, $type = "")
2078  {
2079  global $ilDB;
2080 
2081  if (!isset($a_node))
2082  {
2083  $this->ilErr->raiseError(get_class($this)."::getChildSequenceNumber(): No node_id given! ",$this->ilErr->WARNING);
2084  }
2085 
2086  if($type)
2087  {
2088  $query = 'SELECT count(*) cnt FROM '.$this->table_tree.' '.
2089  $this->buildJoin().
2090  'WHERE lft <= %s '.
2091  'AND type = %s '.
2092  'AND parent = %s '.
2093  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2094 
2095  $res = $ilDB->queryF($query,array('integer','text','integer','integer'),array(
2096  $a_node['lft'],
2097  $type,
2098  $a_node['parent'],
2099  $this->tree_id));
2100  }
2101  else
2102  {
2103  $query = 'SELECT count(*) cnt FROM '.$this->table_tree.' '.
2104  $this->buildJoin().
2105  'WHERE lft <= %s '.
2106  'AND parent = %s '.
2107  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2108 
2109  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2110  $a_node['lft'],
2111  $a_node['parent'],
2112  $this->tree_id));
2113 
2114  }
2115  $row = $ilDB->fetchAssoc($res);
2116  return $row["cnt"];
2117  }
2118 
2125  function readRootId()
2126  {
2127  global $ilDB;
2128 
2129  $query = 'SELECT child FROM '.$this->table_tree.' '.
2130  'WHERE parent = %s '.
2131  'AND '.$this->tree_pk.' = %s ';
2132  $res = $ilDB->queryF($query,array('integer','integer'),array(
2133  0,
2134  $this->tree_id));
2135  $row = $ilDB->fetchObject($res);
2136  $this->root_id = $row->child;
2137  return $this->root_id;
2138  }
2139 
2145  function getRootId()
2146  {
2147  return $this->root_id;
2148  }
2149  function setRootId($a_root_id)
2150  {
2151  $this->root_id = $a_root_id;
2152  }
2153 
2159  function getTreeId()
2160  {
2161  return $this->tree_id;
2162  }
2163 
2169  function setTreeId($a_tree_id)
2170  {
2171  $this->tree_id = $a_tree_id;
2172  }
2173 
2181  function fetchSuccessorNode($a_node_id, $a_type = "")
2182  {
2183  global $ilDB;
2184 
2185  if (!isset($a_node_id))
2186  {
2187  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
2188  }
2189 
2190  // get lft value for current node
2191  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2192  'WHERE '.$this->table_tree.'.child = %s '.
2193  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2194  $res = $ilDB->queryF($query,array('integer','integer'),array(
2195  $a_node_id,
2196  $this->tree_id));
2197  $curr_node = $ilDB->fetchAssoc($res);
2198 
2199  if($a_type)
2200  {
2201  $query = 'SELECT * FROM '.$this->table_tree.' '.
2202  $this->buildJoin().
2203  'WHERE lft > %s '.
2204  'AND '.$this->table_obj_data.'.type = %s '.
2205  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2206  'ORDER BY lft ';
2207  $ilDB->setLimit(1);
2208  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
2209  $curr_node['lft'],
2210  $a_type,
2211  $this->tree_id));
2212  }
2213  else
2214  {
2215  $query = 'SELECT * FROM '.$this->table_tree.' '.
2216  $this->buildJoin().
2217  'WHERE lft > %s '.
2218  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2219  'ORDER BY lft ';
2220  $ilDB->setLimit(1);
2221  $res = $ilDB->queryF($query,array('integer','integer'),array(
2222  $curr_node['lft'],
2223  $this->tree_id));
2224  }
2225 
2226  if ($res->numRows() < 1)
2227  {
2228  return false;
2229  }
2230  else
2231  {
2232  $row = $ilDB->fetchAssoc($res);
2233  return $this->fetchNodeData($row);
2234  }
2235  }
2236 
2244  function fetchPredecessorNode($a_node_id, $a_type = "")
2245  {
2246  global $ilDB;
2247 
2248  if (!isset($a_node_id))
2249  {
2250  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
2251  }
2252 
2253  // get lft value for current node
2254  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2255  'WHERE '.$this->table_tree.'.child = %s '.
2256  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2257  $res = $ilDB->queryF($query,array('integer','integer'),array(
2258  $a_node_id,
2259  $this->tree_id));
2260 
2261  $curr_node = $ilDB->fetchAssoc($res);
2262 
2263  if($a_type)
2264  {
2265  $query = 'SELECT * FROM '.$this->table_tree.' '.
2266  $this->buildJoin().
2267  'WHERE lft < %s '.
2268  'AND '.$this->table_obj_data.'.type = %s '.
2269  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2270  'ORDER BY lft DESC';
2271  $ilDB->setLimit(1);
2272  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
2273  $curr_node['lft'],
2274  $a_type,
2275  $this->tree_id));
2276  }
2277  else
2278  {
2279  $query = 'SELECT * FROM '.$this->table_tree.' '.
2280  $this->buildJoin().
2281  'WHERE lft < %s '.
2282  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2283  'ORDER BY lft DESC';
2284  $ilDB->setLimit(1);
2285  $res = $ilDB->queryF($query,array('integer','integer'),array(
2286  $curr_node['lft'],
2287  $this->tree_id));
2288  }
2289 
2290  if ($res->numRows() < 1)
2291  {
2292  return false;
2293  }
2294  else
2295  {
2296  $row = $ilDB->fetchAssoc($res);
2297  return $this->fetchNodeData($row);
2298  }
2299  }
2300 
2309  function renumber($node_id = 1, $i = 1)
2310  {
2311  global $ilDB;
2312 
2313  // LOCKED ###################################
2314  if($this->__isMainTree())
2315  {
2316  /*
2317  ilDB::_lockTables(array($this->table_tree => 'WRITE',
2318  $this->table_obj_data => 'WRITE',
2319  $this->table_obj_reference => 'WRITE',
2320  'object_translation' => 'WRITE',
2321  'object_data od' => 'WRITE',
2322  'container_reference cr' => 'WRITE'));
2323  */
2324  $ilDB->lockTables(
2325  array(
2326  0 => array('name' => $this->table_tree, 'type' => ilDB::LOCK_WRITE),
2327  1 => array('name' => $this->table_obj_data, 'type' => ilDB::LOCK_WRITE),
2328  2 => array('name' => $this->table_obj_reference, 'type' => ilDB::LOCK_WRITE),
2329  3 => array('name' => 'object_translation', 'type' => ilDB::LOCK_WRITE),
2330  4 => array('name' => 'object_data', 'type' => ilDB::LOCK_WRITE, 'alias' => 'od'),
2331  5 => array('name' => 'container_reference', 'type' => ilDB::LOCK_WRITE, 'alias' => 'cr')
2332  ));
2333  }
2334  $return = $this->__renumber($node_id,$i);
2335  if($this->__isMainTree())
2336  {
2337  $ilDB->unlockTables();
2338  }
2339  // LOCKED ###################################
2340  return $return;
2341  }
2342 
2343  // PRIVATE
2353  function __renumber($node_id = 1, $i = 1)
2354  {
2355  global $ilDB;
2356 
2357  $query = 'UPDATE '.$this->table_tree.' SET lft = %s WHERE child = %s';
2358  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2359  $i,
2360  $node_id));
2361 
2362  $childs = $this->getChilds($node_id);
2363 
2364  foreach ($childs as $child)
2365  {
2366  $i = $this->__renumber($child["child"],$i+1);
2367  }
2368  $i++;
2369 
2370  // Insert a gap at the end of node, if the node has children
2371  if (count($childs) > 0)
2372  {
2373  $i += $this->gap * 2;
2374  }
2375 
2376 
2377  $query = 'UPDATE '.$this->table_tree.' SET rgt = %s WHERE child = %s';
2378  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2379  $i,
2380  $node_id));
2381  return $i;
2382  }
2383 
2384 
2395  function checkForParentType($a_ref_id,$a_type,$a_exclude_source_check = false)
2396  {
2397  // #12577
2398  $cache_key = $a_ref_id.'.'.$a_type.'.'.((int)$a_exclude_source_check);
2399 
2400  // Try to return a cached result
2401  if($this->isCacheUsed() &&
2402  array_key_exists($cache_key, $this->parent_type_cache))
2403  {
2404  return $this->parent_type_cache[$cache_key];
2405  }
2406 
2407  // Store up to 1000 results in cache
2408  $do_cache = ($this->__isMainTree() && count($this->parent_type_cache) < 1000);
2409 
2410  // ref_id is not in tree
2411  if(!$this->isInTree($a_ref_id))
2412  {
2413  if($do_cache)
2414  {
2415  $this->parent_type_cache[$cache_key] = false;
2416  }
2417  return false;
2418  }
2419 
2420  $path = array_reverse($this->getPathFull($a_ref_id));
2421 
2422  // remove first path entry as it is requested node
2423  if($a_exclude_source_check)
2424  {
2425  array_shift($path);
2426  }
2427 
2428  foreach($path as $node)
2429  {
2430  // found matching parent
2431  if($node["type"] == $a_type)
2432  {
2433  if($do_cache)
2434  {
2435  $this->parent_type_cache[$cache_key] = $node["child"];
2436  }
2437  return $node["child"];
2438  }
2439  }
2440 
2441  if($do_cache)
2442  {
2443  $this->parent_type_cache[$cache_key] = false;
2444  }
2445  return 0;
2446  }
2447 
2457  function _removeEntry($a_tree,$a_child,$a_db_table = "tree")
2458  {
2459  global $ilDB,$ilLog,$ilErr;
2460 
2461  if($a_db_table === 'tree')
2462  {
2463  if($a_tree == 1 and $a_child == ROOT_FOLDER_ID)
2464  {
2465  $message = sprintf('%s::_removeEntry(): Tried to delete root node! $a_tree: %s $a_child: %s',
2466  get_class($this),
2467  $a_tree,
2468  $a_child);
2469  $ilLog->write($message,$ilLog->FATAL);
2470  $ilErr->raiseError($message,$ilErr->WARNING);
2471  }
2472  }
2473 
2474  $query = 'DELETE FROM '.$a_db_table.' '.
2475  'WHERE tree = %s '.
2476  'AND child = %s ';
2477  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2478  $a_tree,
2479  $a_child));
2480 
2481  }
2482 
2489  public function __isMainTree()
2490  {
2491  return $this->table_tree === 'tree';
2492  }
2493 
2504  function __checkDelete($a_node)
2505  {
2506  global $ilDB;
2507 
2508 
2509  $query = $this->getTreeImplementation()->getSubTreeQuery($a_node, array(),false);
2510  $GLOBALS['ilLog']->write(__METHOD__.': '.$query);
2511  $res = $ilDB->query($query);
2512 
2513  $counter = (int) $lft_childs = array();
2514  while($row = $ilDB->fetchObject($res))
2515  {
2516  $lft_childs[$row->child] = $row->parent;
2517  ++$counter;
2518  }
2519 
2520  // CHECK FOR DUPLICATE CHILD IDS
2521  if($counter != count($lft_childs))
2522  {
2523  $message = sprintf('%s::__checkTree(): Duplicate entries for "child" in maintree! $a_node_id: %s',
2524  get_class($this),
2525  $a_node['child']);
2526  $this->log->write($message,$this->log->FATAL);
2527  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2528  }
2529 
2530  // GET SUBTREE BY PARENT RELATION
2531  $parent_childs = array();
2532  $this->__getSubTreeByParentRelation($a_node['child'],$parent_childs);
2533  $this->__validateSubtrees($lft_childs,$parent_childs);
2534 
2535  return true;
2536  }
2537 
2546  function __getSubTreeByParentRelation($a_node_id,&$parent_childs)
2547  {
2548  global $ilDB;
2549 
2550  // GET PARENT ID
2551  $query = 'SELECT * FROM '.$this->table_tree.' '.
2552  'WHERE child = %s '.
2553  'AND tree = %s ';
2554  $res = $ilDB->queryF($query,array('integer','integer'),array(
2555  $a_node_id,
2556  $this->tree_id));
2557 
2558  $counter = 0;
2559  while($row = $ilDB->fetchObject($res))
2560  {
2561  $parent_childs[$a_node_id] = $row->parent;
2562  ++$counter;
2563  }
2564  // MULTIPLE ENTRIES
2565  if($counter > 1)
2566  {
2567  $message = sprintf('%s::__getSubTreeByParentRelation(): Multiple entries in maintree! $a_node_id: %s',
2568  get_class($this),
2569  $a_node_id);
2570  $this->log->write($message,$this->log->FATAL);
2571  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2572  }
2573 
2574  // GET ALL CHILDS
2575  $query = 'SELECT * FROM '.$this->table_tree.' '.
2576  'WHERE parent = %s ';
2577  $res = $ilDB->queryF($query,array('integer'),array($a_node_id));
2578 
2579  while($row = $ilDB->fetchObject($res))
2580  {
2581  // RECURSION
2582  $this->__getSubTreeByParentRelation($row->child,$parent_childs);
2583  }
2584  return true;
2585  }
2586 
2587  function __validateSubtrees(&$lft_childs,$parent_childs)
2588  {
2589  // SORT BY KEY
2590  ksort($lft_childs);
2591  ksort($parent_childs);
2592 
2593  $GLOBALS['ilLog']->write(__METHOD__.': left childs '. print_r($lft_childs,true));
2594  $GLOBALS['ilLog']->write(__METHOD__.': parent childs '. print_r($parent_childs,true));
2595 
2596  if(count($lft_childs) != count($parent_childs))
2597  {
2598  $message = sprintf('%s::__validateSubtrees(): (COUNT) Tree is corrupted! Left/Right subtree does not comply .'.
2599  'with parent relation',
2600  get_class($this));
2601  $this->log->write($message,$this->log->FATAL);
2602  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2603  }
2604 
2605 
2606  foreach($lft_childs as $key => $value)
2607  {
2608  if($parent_childs[$key] != $value)
2609  {
2610  $message = sprintf('%s::__validateSubtrees(): (COMPARE) Tree is corrupted! Left/Right subtree does not comply '.
2611  'with parent relation',
2612  get_class($this));
2613  $this->log->write($message,$this->log->FATAL);
2614  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2615  }
2616  if($key == ROOT_FOLDER_ID)
2617  {
2618  $message = sprintf('%s::__validateSubtrees(): (ROOT_FOLDER) Tree is corrupted! Tried to delete root folder',
2619  get_class($this));
2620  $this->log->write($message,$this->log->FATAL);
2621  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2622  }
2623  }
2624  return true;
2625  }
2626 
2636  public function moveTree($a_source_id, $a_target_id, $a_location = self::POS_LAST_NODE)
2637  {
2638  $this->getTreeImplementation()->moveTree($a_source_id,$a_target_id,$a_location);
2639  $GLOBALS['ilAppEventHandler']->raise(
2640  "Services/Tree",
2641  "moveTree",
2642  array(
2643  'tree' => $this->table_tree,
2644  'source_id' => $a_source_id,
2645  'target_id' => $a_target_id)
2646  );
2647  return true;
2648  }
2649 
2650 
2651 
2652 
2660  public function getRbacSubtreeInfo($a_endnode_id)
2661  {
2662  return $this->getTreeImplementation()->getSubtreeInfo($a_endnode_id);
2663  }
2664 
2665 
2673  public function getSubTreeQuery($a_node_id,$a_fields = array(), $a_types = '', $a_force_join_reference = false)
2674  {
2675  return $this->getTreeImplementation()->getSubTreeQuery(
2676  $this->getNodeTreeData($a_node_id),
2677  $a_types,
2678  $a_force_join_reference,
2679  $a_fields);
2680  }
2681 
2682 
2691  public function getSubTreeFilteredByObjIds($a_node_id, array $a_obj_ids, array $a_fields = array())
2692  {
2693  global $ilDB;
2694 
2695  $node = $this->getNodeData($a_node_id);
2696  if(!sizeof($node))
2697  {
2698  return;
2699  }
2700 
2701  $res = array();
2702 
2703  $query = $this->getTreeImplementation()->getSubTreeQuery($node, '', true, array($this->ref_pk));
2704 
2705  $fields = '*';
2706  if(count($a_fields))
2707  {
2708  $fields = implode(',',$a_fields);
2709  }
2710 
2711  $query = "SELECT ".$fields.
2712  " FROM ".$this->getTreeTable().
2713  " ".$this->buildJoin().
2714  " WHERE ".$this->getTableReference().".".$this->ref_pk." IN (".$query.")".
2715  " AND ".$ilDB->in($this->getObjectDataTable().".".$this->obj_pk, $a_obj_ids, "", "integer");
2716  $set = $ilDB->query($query);
2717  while($row = $ilDB->fetchAssoc($set))
2718  {
2719  $res[] = $row;
2720  }
2721 
2722  return $res;
2723  }
2724 
2725  public function deleteNode($a_tree_id,$a_node_id)
2726  {
2727  global $ilDB;
2728 
2729  $query = 'DELETE FROM tree where '.
2730  'child = '.$ilDB->quote($a_node_id,'integer').' '.
2731  'AND tree = '.$ilDB->quote($a_tree_id,'integer');
2732  $ilDB->manipulate($query);
2733  }
2734 
2735 
2736 } // END class.tree
2737 ?>