ILIAS  Release_4_4_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 
498  function getChilds($a_node_id, $a_order = "", $a_direction = "ASC")
499  {
500  global $ilBench,$ilDB, $ilObjDataCache, $ilUser;
501 
502  if (!isset($a_node_id))
503  {
504  $message = get_class($this)."::getChilds(): No node_id given!";
505  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
506  }
507 
508  // init childs
509  $childs = array();
510 
511  // number of childs
512  $count = 0;
513 
514  // init order_clause
515  $order_clause = "";
516 
517  // set order_clause if sort order parameter is given
518  if (!empty($a_order))
519  {
520  $order_clause = "ORDER BY ".$a_order." ".$a_direction;
521  }
522  else
523  {
524  $order_clause = "ORDER BY ".$this->table_tree.".lft";
525  }
526 
527 
528  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
529  $this->buildJoin().
530  "WHERE parent = %s " .
531  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
532  $order_clause,
533  $ilDB->quote($a_node_id,'integer'),
534  $ilDB->quote($this->tree_id,'integer'));
535 
536  $res = $ilDB->query($query);
537 
538  if(!$count = $res->numRows())
539  {
540  return array();
541  }
542 
543  // get rows and object ids
544  $rows = array();
545  while($r = $ilDB->fetchAssoc($res))
546  {
547  $rows[] = $r;
548  $obj_ids[] = $r["obj_id"];
549  }
550 
551  // preload object translation information
552  if ($this->__isMainTree() && $this->isCacheUsed() && is_object($ilObjDataCache) &&
553  is_object($ilUser) && $this->lang_code == $ilUser->getLanguage() && !$this->oc_preloaded[$a_node_id])
554  {
555 // $ilObjDataCache->preloadTranslations($obj_ids, $this->lang_code);
556  $ilObjDataCache->preloadObjectCache($obj_ids, $this->lang_code);
557  $this->fetchTranslationFromObjectDataCache($obj_ids);
558  $this->oc_preloaded[$a_node_id] = true;
559  }
560 
561  foreach ($rows as $row)
562  {
563  $childs[] = $this->fetchNodeData($row);
564 
565  // Update cache of main tree
566  if ($this->__isMainTree())
567  {
568  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child'].' = true');
569  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
570  }
571  }
572  $childs[$count - 1]["last"] = true;
573  return $childs;
574  }
575 
585  function getFilteredChilds($a_filter,$a_node,$a_order = "",$a_direction = "ASC")
586  {
587  $childs = $this->getChilds($a_node,$a_order,$a_direction);
588 
589  foreach($childs as $child)
590  {
591  if(!in_array($child["type"],$a_filter))
592  {
593  $filtered[] = $child;
594  }
595  }
596  return $filtered ? $filtered : array();
597  }
598 
599 
607  function getChildsByType($a_node_id,$a_type)
608  {
609  global $ilDB;
610 
611  if (!isset($a_node_id) or !isset($a_type))
612  {
613  $message = get_class($this)."::getChildsByType(): Missing parameter! node_id:".$a_node_id." type:".$a_type;
614  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
615  }
616 
617  if ($a_type=='rolf' && $this->table_obj_reference) {
618  // Performance optimization: A node can only have exactly one
619  // role folder as its child. Therefore we don't need to sort the
620  // results, and we can let the database know about the expected limit.
621  $ilDB->setLimit(1,0);
622  $query = sprintf("SELECT * FROM ".$this->table_tree." ".
623  $this->buildJoin().
624  "WHERE parent = %s ".
625  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
626  "AND ".$this->table_obj_data.".type = %s ",
627  $ilDB->quote($a_node_id,'integer'),
628  $ilDB->quote($this->tree_id,'integer'),
629  $ilDB->quote($a_type,'text'));
630  } else {
631  $query = sprintf("SELECT * FROM ".$this->table_tree." ".
632  $this->buildJoin().
633  "WHERE parent = %s ".
634  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
635  "AND ".$this->table_obj_data.".type = %s ".
636  "ORDER BY ".$this->table_tree.".lft",
637  $ilDB->quote($a_node_id,'integer'),
638  $ilDB->quote($this->tree_id,'integer'),
639  $ilDB->quote($a_type,'text'));
640  }
641  $res = $ilDB->query($query);
642 
643  // init childs
644  $childs = array();
645  while($row = $ilDB->fetchAssoc($res))
646  {
647  $childs[] = $this->fetchNodeData($row);
648  }
649 
650  return $childs ? $childs : array();
651  }
652 
653 
661  public function getChildsByTypeFilter($a_node_id,$a_types,$a_order = "",$a_direction = "ASC")
662  {
663  global $ilDB;
664 
665  if (!isset($a_node_id) or !$a_types)
666  {
667  $message = get_class($this)."::getChildsByType(): Missing parameter! node_id:".$a_node_id." type:".$a_types;
668  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
669  }
670 
671  $filter = ' ';
672  if($a_types)
673  {
674  $filter = 'AND '.$this->table_obj_data.'.type IN('.implode(',',ilUtil::quoteArray($a_types)).') ';
675  }
676 
677  // set order_clause if sort order parameter is given
678  if (!empty($a_order))
679  {
680  $order_clause = "ORDER BY ".$a_order." ".$a_direction;
681  }
682  else
683  {
684  $order_clause = "ORDER BY ".$this->table_tree.".lft";
685  }
686 
687  $query = 'SELECT * FROM '.$this->table_tree.' '.
688  $this->buildJoin().
689  'WHERE parent = '.$ilDB->quote($a_node_id,'integer').' '.
690  'AND '.$this->table_tree.'.'.$this->tree_pk.' = '.$ilDB->quote($this->tree_id,'integer').' '.
691  $filter.
692  $order_clause;
693 
694  $res = $ilDB->query($query);
695  while($row = $ilDB->fetchAssoc($res))
696  {
697  $childs[] = $this->fetchNodeData($row);
698  }
699 
700  return $childs ? $childs : array();
701  }
702 
710  public function insertNode($a_node_id, $a_parent_id, $a_pos = IL_LAST_NODE, $a_reset_deletion_date = false)
711  {
712  global $ilDB;
713 
714 //echo "+$a_node_id+$a_parent_id+";
715  // CHECK node_id and parent_id > 0 if in main tree
716  if($this->__isMainTree())
717  {
718  if($a_node_id <= 1 or $a_parent_id <= 0)
719  {
720  $GLOBALS['ilLog']->logStack();
721  $message = sprintf('%s::insertNode(): Invalid parameters! $a_node_id: %s $a_parent_id: %s',
722  get_class($this),
723  $a_node_id,
724  $a_parent_id);
725  $this->log->write($message,$this->log->FATAL);
726  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
727  }
728  }
729 
730 
731  if (!isset($a_node_id) or !isset($a_parent_id))
732  {
733  $GLOBALS['ilLog']->logStack();
734  $this->ilErr->raiseError(get_class($this)."::insertNode(): Missing parameter! ".
735  "node_id: ".$a_node_id." parent_id: ".$a_parent_id,$this->ilErr->WARNING);
736  }
737  if ($this->isInTree($a_node_id))
738  {
739  $this->ilErr->raiseError(get_class($this)."::insertNode(): Node ".$a_node_id." already in tree ".
740  $this->table_tree."!",$this->ilErr->WARNING);
741  }
742 
743  $this->getTreeImplementation()->insertNode($a_node_id, $a_parent_id, $a_pos);
744 
745  $this->in_tree_cache[$a_node_id] = true;
746 
747  // reset deletion date
748  if ($a_reset_deletion_date)
749  {
750  ilObject::_resetDeletedDate($a_node_id);
751  }
752  }
753 
766  public function getFilteredSubTree($a_node_id,$a_filter = array())
767  {
768  $node = $this->getNodeData($a_node_id);
769 
770  $first = true;
771  $depth = 0;
772  foreach($this->getSubTree($node) as $subnode)
773  {
774  if($depth and $subnode['depth'] > $depth)
775  {
776  continue;
777  }
778  if(!$first and in_array($subnode['type'],$a_filter))
779  {
780  $depth = $subnode['depth'];
781  $first = false;
782  continue;
783  }
784  $depth = 0;
785  $first = false;
786  $filtered[] = $subnode;
787  }
788  return $filtered ? $filtered : array();
789  }
790 
796  public function getSubTreeIds($a_ref_id)
797  {
798  return $this->getTreeImplementation()->getSubTreeIds($a_ref_id);
799  }
800 
801 
811  function getSubTree($a_node,$a_with_data = true, $a_type = "")
812  {
813  global $ilDB;
814 
815  if (!is_array($a_node))
816  {
817  $GLOBALS['ilLog']->logStack();
818  throw new InvalidArgumentException(__METHOD__.': wrong datatype for node data given');
819  }
820 
821  /*
822  if($a_node['lft'] < 1 or $a_node['rgt'] < 2)
823  {
824  $GLOBALS['ilLog']->logStack();
825  $message = sprintf('%s: Invalid node given! $a_node["lft"]: %s $a_node["rgt"]: %s',
826  __METHOD__,
827  $a_node['lft'],
828  $a_node['rgt']);
829 
830  throw new InvalidArgumentException($message);
831  }
832  */
833 
834  $query = $this->getTreeImplementation()->getSubTreeQuery($a_node, $a_type);
835  $res = $ilDB->query($query);
836  while($row = $ilDB->fetchAssoc($res))
837  {
838  if($a_with_data)
839  {
840  $subtree[] = $this->fetchNodeData($row);
841  }
842  else
843  {
844  $subtree[] = $row['child'];
845  }
846  // the lm_data "hack" should be removed in the trunk during an alpha
847  if($this->__isMainTree() || $this->table_tree == "lm_tree")
848  {
849  $this->in_tree_cache[$row['child']] = true;
850  }
851  }
852  return $subtree ? $subtree : array();
853  }
854 
863  function getSubTreeTypes($a_node,$a_filter = 0)
864  {
865  $a_filter = $a_filter ? $a_filter : array();
866 
867  foreach($this->getSubtree($this->getNodeData($a_node)) as $node)
868  {
869  if(in_array($node["type"],$a_filter))
870  {
871  continue;
872  }
873  $types["$node[type]"] = $node["type"];
874  }
875  return $types ? $types : array();
876  }
877 
884  function deleteTree($a_node)
885  {
886  global $ilDB;
887 
888  $GLOBALS['ilLog']->write(__METHOD__.': Delete tree with node '. $a_node);
889 
890  if (!is_array($a_node))
891  {
892  $GLOBALS['ilLog']->logStack();
893  throw new InvalidArgumentException(__METHOD__.': Wrong datatype for node data!');
894  }
895 
896  $GLOBALS['ilLog']->write(__METHOD__.': '. $this->tree_pk);
897 
898  if($this->__isMainTree() )
899  {
900  // @todo normally this part is not executed, since the subtree is first
901  // moved to trash and then deleted.
902  if(!$this->__checkDelete($a_node))
903  {
904  $GLOBALS['ilLog']->logStack();
905  throw new ilInvalidTreeStructureException('Deletion canceled due to invalid tree structure.' . print_r($a_node,true));
906  }
907  }
908 
909  $this->getTreeImplementation()->deleteTree($a_node['child']);
910 
911  $this->resetInTreeCache();
912 
913  }
914 
925  function getPathFull($a_endnode_id, $a_startnode_id = 0)
926  {
927  $pathIds =& $this->getPathId($a_endnode_id, $a_startnode_id);
928 
929  // We retrieve the full path in a single query to improve performance
930  global $ilDB;
931 
932  // Abort if no path ids were found
933  if (count($pathIds) == 0)
934  {
935  return null;
936  }
937 
938  $inClause = 'child IN (';
939  for ($i=0; $i < count($pathIds); $i++)
940  {
941  if ($i > 0) $inClause .= ',';
942  $inClause .= $ilDB->quote($pathIds[$i],'integer');
943  }
944  $inClause .= ')';
945 
946  $q = 'SELECT * '.
947  'FROM '.$this->table_tree.' '.
948  $this->buildJoin().' '.
949  'WHERE '.$inClause.' '.
950  'AND '.$this->table_tree.'.'.$this->tree_pk.' = '.$this->ilDB->quote($this->tree_id,'integer').' '.
951  'ORDER BY depth';
952  $r = $ilDB->query($q);
953 
954  $pathFull = array();
955  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
956  {
957  $pathFull[] = $this->fetchNodeData($row);
958 
959  // Update cache
960  if ($this->__isMainTree())
961  {
962  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child']);
963  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
964  }
965  }
966  return $pathFull;
967  }
968 
969 
976  function preloadDepthParent($a_node_ids)
977  {
978  global $ilDB;
979 
980  if (!$this->__isMainTree() || !is_array($a_node_ids) || !$this->isCacheUsed())
981  {
982  return;
983  }
984 
985  $res = $ilDB->query('SELECT t.depth, t.parent, t.child '.
986  'FROM '.$this->table_tree.' t '.
987  'WHERE '.$ilDB->in("child", $a_node_ids, false, "integer").
988  'AND '.$this->tree_pk.' = '.$ilDB->quote($this->tree_id, "integer"));
989  while ($row = $ilDB->fetchAssoc($res))
990  {
991  $this->depth_cache[$row["child"]] = $row["depth"];
992  $this->parent_cache[$row["child"]] = $row["parent"];
993  }
994  }
995 
1005  public function getPathId($a_endnode_id, $a_startnode_id = 0)
1006  {
1007  if(!$a_endnode_id)
1008  {
1009  $GLOBALS['ilLog']->logStack();
1010  throw new InvalidArgumentException(__METHOD__.': No endnode given!');
1011  }
1012 
1013  // path id cache
1014  if ($this->isCacheUsed() && isset($this->path_id_cache[$a_endnode_id][$a_startnode_id]))
1015  {
1016 //echo "<br>getPathIdhit";
1017  return $this->path_id_cache[$a_endnode_id][$a_startnode_id];
1018  }
1019 //echo "<br>miss";
1020 
1021  $pathIds = $this->getTreeImplementation()->getPathIds($a_endnode_id, $a_startnode_id);
1022 
1023  if($this->__isMainTree())
1024  {
1025  $this->path_id_cache[$a_endnode_id][$a_startnode_id] = $pathIds;
1026  }
1027  return $pathIds;
1028  }
1029 
1030  // BEGIN WebDAV: getNodePathForTitlePath function added
1048  function getNodePathForTitlePath($titlePath, $a_startnode_id = null)
1049  {
1050  global $ilDB, $log;
1051  //$log->write('getNodePathForTitlePath('.implode('/',$titlePath));
1052 
1053  // handle empty title path
1054  if ($titlePath == null || count($titlePath) == 0)
1055  {
1056  if ($a_startnode_id == 0)
1057  {
1058  return null;
1059  }
1060  else
1061  {
1062  return $this->getNodePath($a_startnode_id);
1063  }
1064  }
1065 
1066  // fetch the node path up to the startnode
1067  if ($a_startnode_id != null && $a_startnode_id != 0)
1068  {
1069  // Start using the node path to the root of the relative path
1070  $nodePath = $this->getNodePath($a_startnode_id);
1071  $parent = $a_startnode_id;
1072  }
1073  else
1074  {
1075  // Start using the root of the tree
1076  $nodePath = array();
1077  $parent = 0;
1078  }
1079 
1080 
1081  // Convert title path into Unicode Normal Form C
1082  // This is needed to ensure that we can compare title path strings with
1083  // strings from the database.
1084  require_once('include/Unicode/UtfNormal.php');
1085  include_once './Services/Utilities/classes/class.ilStr.php';
1086  $inClause = 'd.title IN (';
1087  for ($i=0; $i < count($titlePath); $i++)
1088  {
1089  $titlePath[$i] = ilStr::strToLower(UtfNormal::toNFC($titlePath[$i]));
1090  if ($i > 0) $inClause .= ',';
1091  $inClause .= $ilDB->quote($titlePath[$i],'text');
1092  }
1093  $inClause .= ')';
1094 
1095  // Fetch all rows that are potential path elements
1096  if ($this->table_obj_reference)
1097  {
1098  $joinClause = 'JOIN '.$this->table_obj_reference.' r ON t.child = r.'.$this->ref_pk.' '.
1099  'JOIN '.$this->table_obj_data.' d ON r.'.$this->obj_pk.' = d.'.$this->obj_pk;
1100  }
1101  else
1102  {
1103  $joinClause = 'JOIN '.$this->table_obj_data.' d ON t.child = d.'.$this->obj_pk;
1104  }
1105  // The ORDER BY clause in the following SQL statement ensures that,
1106  // in case of a multiple objects with the same title, always the Object
1107  // with the oldest ref_id is chosen.
1108  // This ensure, that, if a new object with the same title is added,
1109  // WebDAV clients can still work with the older object.
1110  $q = 'SELECT t.depth, t.parent, t.child, d.'.$this->obj_pk.' obj_id, d.type, d.title '.
1111  'FROM '.$this->table_tree.' t '.
1112  $joinClause.' '.
1113  'WHERE '.$inClause.' '.
1114  'AND t.depth <= '.(count($titlePath)+count($nodePath)).' '.
1115  'AND t.tree = 1 '.
1116  'ORDER BY t.depth, t.child ASC';
1117  $r = $ilDB->query($q);
1118 
1119  $rows = array();
1120  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1121  {
1122  $row['title'] = UtfNormal::toNFC($row['title']);
1123  $row['ref_id'] = $row['child'];
1124  $rows[] = $row;
1125  }
1126 
1127  // Extract the path elements from the fetched rows
1128  for ($i = 0; $i < count($titlePath); $i++) {
1129  $pathElementFound = false;
1130  foreach ($rows as $row) {
1131  if ($row['parent'] == $parent &&
1132  ilStr::strToLower($row['title']) == $titlePath[$i])
1133  {
1134  // FIXME - We should test here, if the user has
1135  // 'visible' permission for the object.
1136  $nodePath[] = $row;
1137  $parent = $row['child'];
1138  $pathElementFound = true;
1139  break;
1140  }
1141  }
1142  // Abort if we haven't found a path element for the current depth
1143  if (! $pathElementFound)
1144  {
1145  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):null');
1146  return null;
1147  }
1148  }
1149  // Return the node path
1150  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):'.var_export($nodePath,true));
1151  return $nodePath;
1152  }
1153  // END WebDAV: getNodePathForTitlePath function added
1154  // END WebDAV: getNodePath function added
1171  function getNodePath($a_endnode_id, $a_startnode_id = 0)
1172  {
1173  global $ilDB;
1174 
1175  $pathIds = $this->getPathId($a_endnode_id, $a_startnode_id);
1176 
1177  // Abort if no path ids were found
1178  if (count($pathIds) == 0)
1179  {
1180  return null;
1181  }
1182 
1183 
1184  $types = array();
1185  $data = array();
1186  for ($i = 0; $i < count($pathIds); $i++)
1187  {
1188  $types[] = 'integer';
1189  $data[] = $pathIds[$i];
1190  }
1191 
1192  $query = 'SELECT t.depth,t.parent,t.child,d.obj_id,d.type,d.title '.
1193  'FROM '.$this->table_tree.' t '.
1194  'JOIN '.$this->table_obj_reference.' r ON r.ref_id = t.child '.
1195  'JOIN '.$this->table_obj_data.' d ON d.obj_id = r.obj_id '.
1196  'WHERE '.$ilDB->in('t.child',$data,false,'integer').' '.
1197  'ORDER BY t.depth ';
1198 
1199  $res = $ilDB->queryF($query,$types,$data);
1200 
1201  $titlePath = array();
1202  while ($row = $ilDB->fetchAssoc($res))
1203  {
1204  $titlePath[] = $row;
1205  }
1206  return $titlePath;
1207  }
1208  // END WebDAV: getNodePath function added
1209 
1216  function checkTree()
1217  {
1218  global $ilDB;
1219 
1220  $types = array('integer');
1221  $query = 'SELECT lft,rgt FROM '.$this->table_tree.' '.
1222  'WHERE '.$this->tree_pk.' = %s ';
1223 
1224  $res = $ilDB->queryF($query,$types,array($this->tree_id));
1225  while ($row = $ilDB->fetchObject($res))
1226  {
1227  $lft[] = $row->lft;
1228  $rgt[] = $row->rgt;
1229  }
1230 
1231  $all = array_merge($lft,$rgt);
1232  $uni = array_unique($all);
1233 
1234  if (count($all) != count($uni))
1235  {
1236  $message = sprintf('%s::checkTree(): Tree is corrupted!',
1237  get_class($this));
1238 
1239  $this->log->write($message,$this->log->FATAL);
1240  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1241  }
1242 
1243  return true;
1244  }
1245 
1249  function checkTreeChilds($a_no_zero_child = true)
1250  {
1251  global $ilDB;
1252 
1253  $query = 'SELECT * FROM '.$this->table_tree.' '.
1254  'WHERE '.$this->tree_pk.' = %s '.
1255  'ORDER BY lft';
1256  $r1 = $ilDB->queryF($query,array('integer'),array($this->tree_id));
1257 
1258  while ($row = $ilDB->fetchAssoc($r1))
1259  {
1260 //echo "tree:".$row[$this->tree_pk].":lft:".$row["lft"].":rgt:".$row["rgt"].":child:".$row["child"].":<br>";
1261  if (($row["child"] == 0) && $a_no_zero_child)
1262  {
1263  $this->ilErr->raiseError(get_class($this)."::checkTreeChilds(): Tree contains child with ID 0!",$this->ilErr->WARNING);
1264  }
1265 
1266  if ($this->table_obj_reference)
1267  {
1268  // get object reference data
1269  $query = 'SELECT * FROM '.$this->table_obj_reference.' WHERE '.$this->ref_pk.' = %s ';
1270  $r2 = $ilDB->queryF($query,array('integer'),array($row['child']));
1271 
1272 //echo "num_childs:".$r2->numRows().":<br>";
1273  if ($r2->numRows() == 0)
1274  {
1275  $this->ilErr->raiseError(get_class($this)."::checkTree(): No Object-to-Reference entry found for ID ".
1276  $row["child"]."!",$this->ilErr->WARNING);
1277  }
1278  if ($r2->numRows() > 1)
1279  {
1280  $this->ilErr->raiseError(get_class($this)."::checkTree(): More Object-to-Reference entries found for ID ".
1281  $row["child"]."!",$this->ilErr->WARNING);
1282  }
1283 
1284  // get object data
1285  $obj_ref = $ilDB->fetchAssoc($r2);
1286 
1287  $query = 'SELECT * FROM '.$this->table_obj_data.' WHERE '.$this->obj_pk.' = %s';
1288  $r3 = $ilDB->queryF($query,array('integer'),array($obj_ref[$this->obj_pk]));
1289  if ($r3->numRows() == 0)
1290  {
1291  $this->ilErr->raiseError(get_class($this)."::checkTree(): No child found for ID ".
1292  $obj_ref[$this->obj_pk]."!",$this->ilErr->WARNING);
1293  }
1294  if ($r3->numRows() > 1)
1295  {
1296  $this->ilErr->raiseError(get_class($this)."::checkTree(): More childs found for ID ".
1297  $obj_ref[$this->obj_pk]."!",$this->ilErr->WARNING);
1298  }
1299 
1300  }
1301  else
1302  {
1303  // get only object data
1304  $query = 'SELECT * FROM '.$this->table_obj_data.' WHERE '.$this->obj_pk.' = %s';
1305  $r2 = $ilDB->queryF($query,array('integer'),array($row['child']));
1306 //echo "num_childs:".$r2->numRows().":<br>";
1307  if ($r2->numRows() == 0)
1308  {
1309  $this->ilErr->raiseError(get_class($this)."::checkTree(): No child found for ID ".
1310  $row["child"]."!",$this->ilErr->WARNING);
1311  }
1312  if ($r2->numRows() > 1)
1313  {
1314  $this->ilErr->raiseError(get_class($this)."::checkTree(): More childs found for ID ".
1315  $row["child"]."!",$this->ilErr->WARNING);
1316  }
1317  }
1318  }
1319 
1320  return true;
1321  }
1322 
1328  public function getMaximumDepth()
1329  {
1330  global $ilDB;
1331 
1332  $query = 'SELECT MAX(depth) depth FROM '.$this->table_tree;
1333  $res = $ilDB->query($query);
1334 
1335  $row = $ilDB->fetchAssoc($res);
1336  return $row['depth'];
1337  }
1338 
1345  function getDepth($a_node_id)
1346  {
1347  global $ilDB;
1348 
1349  if ($a_node_id)
1350  {
1351  $query = 'SELECT depth FROM '.$this->table_tree.' '.
1352  'WHERE child = %s '.
1353  'AND '.$this->tree_pk.' = %s ';
1354  $res = $ilDB->queryF($query,array('integer','integer'),array($a_node_id,$this->tree_id));
1355  $row = $ilDB->fetchObject($res);
1356 
1357  return $row->depth;
1358  }
1359  else
1360  {
1361  return 1;
1362  }
1363  }
1364 
1372  public function getNodeTreeData($a_node_id)
1373  {
1374  global $ilDB;
1375 
1376  if(!$a_node_id)
1377  {
1378  $GLOBALS['ilLog']->logStack();
1379  throw new InvalidArgumentException('Missing or empty parameter $a_node_id: '. $a_node_id);
1380  }
1381 
1382  $query = 'SELECT * FROM '.$this->table_tree.' '.
1383  'WHERE child = '.$ilDB->quote($a_node_id,'integer');
1384  $res = $ilDB->query($query);
1385  while($row = $res->fetchRow(DB_FETCHMODE_ASSOC))
1386  {
1387  return $row;
1388  }
1389  return array();
1390  }
1391 
1392 
1400  // BEGIN WebDAV: Pass tree id to this method
1401  //function getNodeData($a_node_id)
1402  function getNodeData($a_node_id, $a_tree_pk = null)
1403  // END PATCH WebDAV: Pass tree id to this method
1404  {
1405  global $ilDB;
1406 
1407  if (!isset($a_node_id))
1408  {
1409  $GLOBALS['ilLog']->logStack();
1410  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1411  }
1412  if($this->__isMainTree())
1413  {
1414  if($a_node_id < 1)
1415  {
1416  $message = sprintf('%s::getNodeData(): No valid parameter given! $a_node_id: %s',
1417  get_class($this),
1418  $a_node_id);
1419 
1420  $this->log->write($message,$this->log->FATAL);
1421  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1422  }
1423  }
1424 
1425  // BEGIN WebDAV: Pass tree id to this method
1426  $query = 'SELECT * FROM '.$this->table_tree.' '.
1427  $this->buildJoin().
1428  'WHERE '.$this->table_tree.'.child = %s '.
1429  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
1430  $res = $ilDB->queryF($query,array('integer','integer'),array(
1431  $a_node_id,
1432  $a_tree_pk === null ? $this->tree_id : $a_tree_pk));
1433  // END WebDAV: Pass tree id to this method
1434  $row = $ilDB->fetchAssoc($res);
1436 
1437  return $this->fetchNodeData($row);
1438  }
1439 
1447  function fetchNodeData($a_row)
1448  {
1449  global $objDefinition, $lng, $ilBench,$ilDB;
1450 
1451  //$ilBench->start("Tree", "fetchNodeData_getRow");
1452  $data = $a_row;
1453  $data["desc"] = $a_row["description"]; // for compability
1454  //$ilBench->stop("Tree", "fetchNodeData_getRow");
1455 
1456  // multilingual support systemobjects (sys) & categories (db)
1457  //$ilBench->start("Tree", "fetchNodeData_readDefinition");
1458  if (is_object($objDefinition))
1459  {
1460  $translation_type = $objDefinition->getTranslationType($data["type"]);
1461  }
1462  //$ilBench->stop("Tree", "fetchNodeData_readDefinition");
1463 
1464  if ($translation_type == "sys")
1465  {
1466  //$ilBench->start("Tree", "fetchNodeData_getLangData");
1467  if ($data["type"] == "rolf" and $data["obj_id"] != ROLE_FOLDER_ID)
1468  {
1469  $data["description"] = $lng->txt("obj_".$data["type"]."_local_desc").$data["title"].$data["desc"];
1470  $data["desc"] = $lng->txt("obj_".$data["type"]."_local_desc").$data["title"].$data["desc"];
1471  $data["title"] = $lng->txt("obj_".$data["type"]."_local");
1472  }
1473  else
1474  {
1475  $data["title"] = $lng->txt("obj_".$data["type"]);
1476  $data["description"] = $lng->txt("obj_".$data["type"]."_desc");
1477  $data["desc"] = $lng->txt("obj_".$data["type"]."_desc");
1478  }
1479  //$ilBench->stop("Tree", "fetchNodeData_getLangData");
1480  }
1481  elseif ($translation_type == "db")
1482  {
1483 
1484  // Try to retrieve object translation from cache
1485  if ($this->isCacheUsed() &&
1486  array_key_exists($data["obj_id"].'.'.$lang_code, $this->translation_cache)) {
1487 
1488  $key = $data["obj_id"].'.'.$lang_code;
1489  $data["title"] = $this->translation_cache[$key]['title'];
1490  $data["description"] = $this->translation_cache[$key]['description'];
1491  $data["desc"] = $this->translation_cache[$key]['desc'];
1492  }
1493  else
1494  {
1495  // Object translation is not in cache, read it from database
1496  //$ilBench->start("Tree", "fetchNodeData_getTranslation");
1497  $query = 'SELECT title,description FROM object_translation '.
1498  'WHERE obj_id = %s '.
1499  'AND lang_code = %s '.
1500  'AND NOT lang_default = %s';
1501 
1502  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
1503  $data['obj_id'],
1504  $this->lang_code,
1505  1));
1506  $row = $ilDB->fetchObject($res);
1507 
1508  if ($row)
1509  {
1510  $data["title"] = $row->title;
1511  $data["description"] = ilUtil::shortenText($row->description,ilObject::DESC_LENGTH,true);
1512  $data["desc"] = $row->description;
1513  }
1514  //$ilBench->stop("Tree", "fetchNodeData_getTranslation");
1515 
1516  // Store up to 1000 object translations in cache
1517  if ($this->isCacheUsed() && count($this->translation_cache) < 1000)
1518  {
1519  $key = $data["obj_id"].'.'.$lang_code;
1520  $this->translation_cache[$key] = array();
1521  $this->translation_cache[$key]['title'] = $data["title"] ;
1522  $this->translation_cache[$key]['description'] = $data["description"];
1523  $this->translation_cache[$key]['desc'] = $data["desc"];
1524  }
1525  }
1526  }
1527 
1528  // TODO: Handle this switch by module.xml definitions
1529  if($data['type'] == 'crsr' or $data['type'] == 'catr')
1530  {
1531  include_once('./Services/ContainerReference/classes/class.ilContainerReference.php');
1532  $data['title'] = ilContainerReference::_lookupTargetTitle($data['obj_id']);
1533  }
1534 
1535  return $data ? $data : array();
1536  }
1537 
1543  protected function fetchTranslationFromObjectDataCache($a_obj_ids)
1544  {
1545  global $ilObjDataCache;
1546 
1547  if ($this->isCacheUsed() && is_array($a_obj_ids) && is_object($ilObjDataCache))
1548  {
1549  foreach ($a_obj_ids as $id)
1550  {
1551  $this->translation_cache[$id.'.']['title'] = $ilObjDataCache->lookupTitle($id);
1552  $this->translation_cache[$id.'.']['description'] = $ilObjDataCache->lookupDescription($id);;
1553  $this->translation_cache[$id.'.']['desc'] =
1554  $this->translation_cache[$id.'.']['description'];
1555  }
1556  }
1557  }
1558 
1559 
1567  function isInTree($a_node_id)
1568  {
1569  global $ilDB;
1570 
1571  if (!isset($a_node_id))
1572  {
1573  return false;
1574  #$this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1575  }
1576  // is in tree cache
1577  if ($this->isCacheUsed() && isset($this->in_tree_cache[$a_node_id]))
1578  {
1579  #$GLOBALS['ilLog']->write(__METHOD__.': Using in tree cache '.$a_node_id);
1580 //echo "<br>in_tree_hit";
1581  return $this->in_tree_cache[$a_node_id];
1582  }
1583 
1584  $query = 'SELECT * FROM '.$this->table_tree.' '.
1585  'WHERE '.$this->table_tree.'.child = %s '.
1586  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s';
1587 
1588  $res = $ilDB->queryF($query,array('integer','integer'),array(
1589  $a_node_id,
1590  $this->tree_id));
1591 
1592  if ($res->numRows() > 0)
1593  {
1594  if($this->__isMainTree())
1595  {
1596  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
1597  $this->in_tree_cache[$a_node_id] = true;
1598  }
1599  return true;
1600  }
1601  else
1602  {
1603  if($this->__isMainTree())
1604  {
1605  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = false');
1606  $this->in_tree_cache[$a_node_id] = false;
1607  }
1608  return false;
1609  }
1610  }
1611 
1619  public function getParentNodeData($a_node_id)
1620  {
1621  global $ilDB;
1622  global $ilLog;
1623 
1624  if (!isset($a_node_id))
1625  {
1626  $ilLog->logStack();
1627  throw new InvalidArgumentException(__METHOD__.': No node_id given!');
1628  }
1629 
1630  if ($this->table_obj_reference)
1631  {
1632  // Use inner join instead of left join to improve performance
1633  $innerjoin = "JOIN ".$this->table_obj_reference." ON v.child=".$this->table_obj_reference.".".$this->ref_pk." ".
1634  "JOIN ".$this->table_obj_data." ON ".$this->table_obj_reference.".".$this->obj_pk."=".$this->table_obj_data.".".$this->obj_pk." ";
1635  }
1636  else
1637  {
1638  // Use inner join instead of left join to improve performance
1639  $innerjoin = "JOIN ".$this->table_obj_data." ON v.child=".$this->table_obj_data.".".$this->obj_pk." ";
1640  }
1641 
1642  $query = 'SELECT * FROM '.$this->table_tree.' s, '.$this->table_tree.' v '.
1643  $innerjoin.
1644  'WHERE s.child = %s '.
1645  'AND s.parent = v.child '.
1646  'AND s.'.$this->tree_pk.' = %s '.
1647  'AND v.'.$this->tree_pk.' = %s';
1648  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
1649  $a_node_id,
1650  $this->tree_id,
1651  $this->tree_id));
1652  $row = $ilDB->fetchAssoc($res);
1653  return $this->fetchNodeData($row);
1654  }
1655 
1663  public function isGrandChild($a_startnode_id,$a_querynode_id)
1664  {
1665  return $this->getRelation($a_startnode_id, $a_querynode_id) == self::RELATION_PARENT;
1666  }
1667 
1676  function addTree($a_tree_id,$a_node_id = -1)
1677  {
1678  global $ilDB;
1679 
1680  // FOR SECURITY addTree() IS NOT ALLOWED ON MAIN TREE
1681  if($this->__isMainTree())
1682  {
1683  $message = sprintf('%s::addTree(): Operation not allowed on main tree! $a_tree_if: %s $a_node_id: %s',
1684  get_class($this),
1685  $a_tree_id,
1686  $a_node_id);
1687  $this->log->write($message,$this->log->FATAL);
1688  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1689  }
1690 
1691  if (!isset($a_tree_id))
1692  {
1693  $this->ilErr->raiseError(get_class($this)."::addTree(): No tree_id given! ",$this->ilErr->WARNING);
1694  }
1695 
1696  if ($a_node_id <= 0)
1697  {
1698  $a_node_id = $a_tree_id;
1699  }
1700 
1701  $query = 'INSERT INTO '.$this->table_tree.' ('.
1702  $this->tree_pk.', child,parent,lft,rgt,depth) '.
1703  'VALUES '.
1704  '(%s,%s,%s,%s,%s,%s)';
1705  $res = $ilDB->manipulateF($query,array('integer','integer','integer','integer','integer','integer'),array(
1706  $a_tree_id,
1707  $a_node_id,
1708  0,
1709  1,
1710  2,
1711  1));
1712 
1713  return true;
1714  }
1715 
1724  public function getNodeDataByType($a_type)
1725  {
1726  global $ilDB;
1727 
1728  if(!isset($a_type) or (!is_string($a_type)))
1729  {
1730  $GLOBALS['ilLog']->logStack();
1731  throw new InvalidArgumentException('Type not given or wrong datatype');
1732  }
1733 
1734  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1735  $this->buildJoin().
1736  'WHERE ' . $this->table_obj_data . '.type = ' . $this->ilDB->quote($a_type, 'text').
1737  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = ' . $this->ilDB->quote($this->tree_id, 'integer');
1738 
1739  $res = $ilDB->query($query);
1740  $data = array();
1741  while($row = $ilDB->fetchAssoc($res))
1742  {
1743  $data[] = $this->fetchNodeData($row);
1744  }
1745 
1746  return $data;
1747  }
1748 
1756  public function removeTree($a_tree_id)
1757  {
1758  global $ilDB;
1759 
1760  // OPERATION NOT ALLOWED ON MAIN TREE
1761  if($this->__isMainTree())
1762  {
1763  $GLOBALS['ilLog']->logStack();
1764  throw new InvalidArgumentException('Operation not allowed on main tree');
1765  }
1766  if (!$a_tree_id)
1767  {
1768  $GLOBALS['ilLog']->logStack();
1769  throw new InvalidArgumentException('Missing parameter tree id');
1770  }
1771 
1772  $query = 'DELETE FROM '.$this->table_tree.
1773  ' WHERE '.$this->tree_pk.' = %s ';
1774  $ilDB->manipulateF($query,array('integer'),array($a_tree_id));
1775  return true;
1776  }
1777 
1784  public function moveToTrash($a_node_id, $a_set_deleted = false)
1785  {
1786  return $this->saveSubTree($a_node_id, $a_set_deleted);
1787  }
1788 
1799  public function saveSubTree($a_node_id, $a_set_deleted = false)
1800  {
1801  global $ilDB;
1802 
1803  if(!$a_node_id)
1804  {
1805  $GLOBALS['ilLog']->logStack();
1806  throw new InvalidArgumentException('No valid parameter given! $a_node_id: '.$a_node_id);
1807  }
1808 
1809  // LOCKED ###############################################
1810  if($this->__isMainTree())
1811  {
1812  $ilDB->lockTables(
1813  array(
1814  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE),
1815  1 => array('name' => 'object_reference', 'type' => ilDB::LOCK_WRITE)));
1816  }
1817 
1818  $query = $this->getTreeImplementation()->getSubTreeQuery($this->getNodeTreeData($a_node_id),'',false);
1819  $res = $ilDB->query($query);
1820 
1821  $subnodes = array();
1822  while($row = $res->fetchRow(DB_FETCHMODE_ASSOC))
1823  {
1824  $subnodes[] = $row['child'];
1825  }
1826 
1827  if(!count($subnodes))
1828  {
1829  // Possibly already deleted
1830  // Unlock locked tables before returning
1831  if($this->__isMainTree())
1832  {
1833  $ilDB->unlockTables();
1834  }
1835  return false;
1836  }
1837 
1838  if($a_set_deleted)
1839  {
1840  include_once './Services/Object/classes/class.ilObject.php';
1841  ilObject::setDeletedDates($subnodes);
1842  }
1843 
1844  // netsted set <=> mp
1845  $this->getTreeImplementation()->moveToTrash($a_node_id);
1846 
1847  if($this->__isMainTree())
1848  {
1849  $ilDB->unlockTables();
1850  }
1851 
1852  // LOCKED ###############################################
1853  return true;
1854  }
1855 
1860  public function isDeleted($a_node_id)
1861  {
1862  return $this->isSaved($a_node_id);
1863  }
1864 
1870  public function isSaved($a_node_id)
1871  {
1872  global $ilDB;
1873 
1874  // is saved cache
1875  if ($this->isCacheUsed() && isset($this->is_saved_cache[$a_node_id]))
1876  {
1877 //echo "<br>issavedhit";
1878  return $this->is_saved_cache[$a_node_id];
1879  }
1880 
1881  $query = 'SELECT '.$this->tree_pk.' FROM '.$this->table_tree.' '.
1882  'WHERE child = %s ';
1883  $res = $ilDB->queryF($query,array('integer'),array($a_node_id));
1884  $row = $ilDB->fetchAssoc($res);
1885 
1886  if ($row[$this->tree_pk] < 0)
1887  {
1888  if($this->__isMainTree())
1889  {
1890  $this->is_saved_cache[$a_node_id] = true;
1891  }
1892  return true;
1893  }
1894  else
1895  {
1896  if($this->__isMainTree())
1897  {
1898  $this->is_saved_cache[$a_node_id] = false;
1899  }
1900  return false;
1901  }
1902  }
1903 
1910  public function preloadDeleted($a_node_ids)
1911  {
1912  global $ilDB;
1913 
1914  if (!is_array($a_node_ids) || !$this->isCacheUsed())
1915  {
1916  return;
1917  }
1918 
1919  $query = 'SELECT '.$this->tree_pk.', child FROM '.$this->table_tree.' '.
1920  'WHERE '.$ilDB->in("child", $a_node_ids, false, "integer");
1921 
1922  $res = $ilDB->query($query);
1923  while ($row = $ilDB->fetchAssoc($res))
1924  {
1925  if ($row[$this->tree_pk] < 0)
1926  {
1927  if($this->__isMainTree())
1928  {
1929  $this->is_saved_cache[$row["child"]] = true;
1930  }
1931  }
1932  else
1933  {
1934  if($this->__isMainTree())
1935  {
1936  $this->is_saved_cache[$row["child"]] = false;
1937  }
1938  }
1939  }
1940  }
1941 
1942 
1949  function getSavedNodeData($a_parent_id)
1950  {
1951  global $ilDB;
1952 
1953  if (!isset($a_parent_id))
1954  {
1955  $this->ilErr->raiseError(get_class($this)."::getSavedNodeData(): No node_id given!",$this->ilErr->WARNING);
1956  }
1957 
1958  $query = 'SELECT * FROM '.$this->table_tree.' '.
1959  $this->buildJoin().
1960  'WHERE '.$this->table_tree.'.'.$this->tree_pk.' < %s '.
1961  'AND '.$this->table_tree.'.parent = %s';
1962  $res = $ilDB->queryF($query,array('integer','integer'),array(
1963  0,
1964  $a_parent_id));
1965 
1966  while($row = $ilDB->fetchAssoc($res))
1967  {
1968  $saved[] = $this->fetchNodeData($row);
1969  }
1970 
1971  return $saved ? $saved : array();
1972  }
1973 
1980  function getSavedNodeObjIds(array $a_obj_ids)
1981  {
1982  global $ilDB;
1983 
1984  $query = 'SELECT '.$this->table_obj_data.'.obj_id FROM '.$this->table_tree.' '.
1985  $this->buildJoin().
1986  'WHERE '.$this->table_tree.'.'.$this->tree_pk.' < '.$ilDB->quote(0, 'integer').' '.
1987  'AND '.$ilDB->in($this->table_obj_data.'.obj_id', $a_obj_ids, '', 'integer');
1988  $res = $ilDB->query($query);
1989  while($row = $ilDB->fetchAssoc($res))
1990  {
1991  $saved[] = $row['obj_id'];
1992  }
1993 
1994  return $saved ? $saved : array();
1995  }
1996 
2003  function getParentId($a_node_id)
2004  {
2005  global $ilDB;
2006 
2007  if (!isset($a_node_id))
2008  {
2009  $this->ilErr->raiseError(get_class($this)."::getParentId(): No node_id given! ",$this->ilErr->WARNING);
2010  }
2011 
2012  $query = 'SELECT parent FROM '.$this->table_tree.' '.
2013  'WHERE child = %s '.
2014  'AND '.$this->tree_pk.' = %s ';
2015  $res = $ilDB->queryF($query,array('integer','integer'),array(
2016  $a_node_id,
2017  $this->tree_id));
2018 
2019  $row = $ilDB->fetchObject($res);
2020  return $row->parent;
2021  }
2022 
2029  function getLeftValue($a_node_id)
2030  {
2031  global $ilDB;
2032 
2033  if (!isset($a_node_id))
2034  {
2035  $this->ilErr->raiseError(get_class($this)."::getLeftValued(): No node_id given! ",$this->ilErr->WARNING);
2036  }
2037 
2038  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2039  'WHERE child = %s '.
2040  'AND '.$this->tree_pk.' = %s ';
2041  $res = $ilDB->queryF($query,array('integer','integer'),array(
2042  $a_node_id,
2043  $this->tree_id));
2044  $row = $ilDB->fetchObject($res);
2045  return $row->lft;
2046  }
2047 
2054  function getChildSequenceNumber($a_node, $type = "")
2055  {
2056  global $ilDB;
2057 
2058  if (!isset($a_node))
2059  {
2060  $this->ilErr->raiseError(get_class($this)."::getChildSequenceNumber(): No node_id given! ",$this->ilErr->WARNING);
2061  }
2062 
2063  if($type)
2064  {
2065  $query = 'SELECT count(*) cnt FROM '.$this->table_tree.' '.
2066  $this->buildJoin().
2067  'WHERE lft <= %s '.
2068  'AND type = %s '.
2069  'AND parent = %s '.
2070  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2071 
2072  $res = $ilDB->queryF($query,array('integer','text','integer','integer'),array(
2073  $a_node['lft'],
2074  $type,
2075  $a_node['parent'],
2076  $this->tree_id));
2077  }
2078  else
2079  {
2080  $query = 'SELECT count(*) cnt FROM '.$this->table_tree.' '.
2081  $this->buildJoin().
2082  'WHERE lft <= %s '.
2083  'AND parent = %s '.
2084  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2085 
2086  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2087  $a_node['lft'],
2088  $a_node['parent'],
2089  $this->tree_id));
2090 
2091  }
2092  $row = $ilDB->fetchAssoc($res);
2093  return $row["cnt"];
2094  }
2095 
2102  function readRootId()
2103  {
2104  global $ilDB;
2105 
2106  $query = 'SELECT child FROM '.$this->table_tree.' '.
2107  'WHERE parent = %s '.
2108  'AND '.$this->tree_pk.' = %s ';
2109  $res = $ilDB->queryF($query,array('integer','integer'),array(
2110  0,
2111  $this->tree_id));
2112  $row = $ilDB->fetchObject($res);
2113  $this->root_id = $row->child;
2114  return $this->root_id;
2115  }
2116 
2122  function getRootId()
2123  {
2124  return $this->root_id;
2125  }
2126  function setRootId($a_root_id)
2127  {
2128  $this->root_id = $a_root_id;
2129  }
2130 
2136  function getTreeId()
2137  {
2138  return $this->tree_id;
2139  }
2140 
2146  function setTreeId($a_tree_id)
2147  {
2148  $this->tree_id = $a_tree_id;
2149  }
2150 
2158  function fetchSuccessorNode($a_node_id, $a_type = "")
2159  {
2160  global $ilDB;
2161 
2162  if (!isset($a_node_id))
2163  {
2164  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
2165  }
2166 
2167  // get lft value for current node
2168  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2169  'WHERE '.$this->table_tree.'.child = %s '.
2170  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2171  $res = $ilDB->queryF($query,array('integer','integer'),array(
2172  $a_node_id,
2173  $this->tree_id));
2174  $curr_node = $ilDB->fetchAssoc($res);
2175 
2176  if($a_type)
2177  {
2178  $query = 'SELECT * FROM '.$this->table_tree.' '.
2179  $this->buildJoin().
2180  'WHERE lft > %s '.
2181  'AND '.$this->table_obj_data.'.type = %s '.
2182  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2183  'ORDER BY lft ';
2184  $ilDB->setLimit(1);
2185  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
2186  $curr_node['lft'],
2187  $a_type,
2188  $this->tree_id));
2189  }
2190  else
2191  {
2192  $query = 'SELECT * FROM '.$this->table_tree.' '.
2193  $this->buildJoin().
2194  'WHERE lft > %s '.
2195  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2196  'ORDER BY lft ';
2197  $ilDB->setLimit(1);
2198  $res = $ilDB->queryF($query,array('integer','integer'),array(
2199  $curr_node['lft'],
2200  $this->tree_id));
2201  }
2202 
2203  if ($res->numRows() < 1)
2204  {
2205  return false;
2206  }
2207  else
2208  {
2209  $row = $ilDB->fetchAssoc($res);
2210  return $this->fetchNodeData($row);
2211  }
2212  }
2213 
2221  function fetchPredecessorNode($a_node_id, $a_type = "")
2222  {
2223  global $ilDB;
2224 
2225  if (!isset($a_node_id))
2226  {
2227  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
2228  }
2229 
2230  // get lft value for current node
2231  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2232  'WHERE '.$this->table_tree.'.child = %s '.
2233  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2234  $res = $ilDB->queryF($query,array('integer','integer'),array(
2235  $a_node_id,
2236  $this->tree_id));
2237 
2238  $curr_node = $ilDB->fetchAssoc($res);
2239 
2240  if($a_type)
2241  {
2242  $query = 'SELECT * FROM '.$this->table_tree.' '.
2243  $this->buildJoin().
2244  'WHERE lft < %s '.
2245  'AND '.$this->table_obj_data.'.type = %s '.
2246  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2247  'ORDER BY lft DESC';
2248  $ilDB->setLimit(1);
2249  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
2250  $curr_node['lft'],
2251  $a_type,
2252  $this->tree_id));
2253  }
2254  else
2255  {
2256  $query = 'SELECT * FROM '.$this->table_tree.' '.
2257  $this->buildJoin().
2258  'WHERE lft < %s '.
2259  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2260  'ORDER BY lft DESC';
2261  $ilDB->setLimit(1);
2262  $res = $ilDB->queryF($query,array('integer','integer'),array(
2263  $curr_node['lft'],
2264  $this->tree_id));
2265  }
2266 
2267  if ($res->numRows() < 1)
2268  {
2269  return false;
2270  }
2271  else
2272  {
2273  $row = $ilDB->fetchAssoc($res);
2274  return $this->fetchNodeData($row);
2275  }
2276  }
2277 
2286  function renumber($node_id = 1, $i = 1)
2287  {
2288  global $ilDB;
2289 
2290  // LOCKED ###################################
2291  if($this->__isMainTree())
2292  {
2293  /*
2294  ilDB::_lockTables(array($this->table_tree => 'WRITE',
2295  $this->table_obj_data => 'WRITE',
2296  $this->table_obj_reference => 'WRITE',
2297  'object_translation' => 'WRITE',
2298  'object_data od' => 'WRITE',
2299  'container_reference cr' => 'WRITE'));
2300  */
2301  $ilDB->lockTables(
2302  array(
2303  0 => array('name' => $this->table_tree, 'type' => ilDB::LOCK_WRITE),
2304  1 => array('name' => $this->table_obj_data, 'type' => ilDB::LOCK_WRITE),
2305  2 => array('name' => $this->table_obj_reference, 'type' => ilDB::LOCK_WRITE),
2306  3 => array('name' => 'object_translation', 'type' => ilDB::LOCK_WRITE),
2307  4 => array('name' => 'object_data', 'type' => ilDB::LOCK_WRITE, 'alias' => 'od'),
2308  5 => array('name' => 'container_reference', 'type' => ilDB::LOCK_WRITE, 'alias' => 'cr')
2309  ));
2310  }
2311  $return = $this->__renumber($node_id,$i);
2312  if($this->__isMainTree())
2313  {
2314  $ilDB->unlockTables();
2315  }
2316  // LOCKED ###################################
2317  return $return;
2318  }
2319 
2320  // PRIVATE
2330  function __renumber($node_id = 1, $i = 1)
2331  {
2332  global $ilDB;
2333 
2334  $query = 'UPDATE '.$this->table_tree.' SET lft = %s WHERE child = %s';
2335  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2336  $i,
2337  $node_id));
2338 
2339  $childs = $this->getChilds($node_id);
2340 
2341  foreach ($childs as $child)
2342  {
2343  $i = $this->__renumber($child["child"],$i+1);
2344  }
2345  $i++;
2346 
2347  // Insert a gap at the end of node, if the node has children
2348  if (count($childs) > 0)
2349  {
2350  $i += $this->gap * 2;
2351  }
2352 
2353 
2354  $query = 'UPDATE '.$this->table_tree.' SET rgt = %s WHERE child = %s';
2355  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2356  $i,
2357  $node_id));
2358  return $i;
2359  }
2360 
2361 
2372  function checkForParentType($a_ref_id,$a_type,$a_exclude_source_check = false)
2373  {
2374  // #12577
2375  $cache_key = $a_ref_id.'.'.$a_type.'.'.((int)$a_exclude_source_check);
2376 
2377  // Try to return a cached result
2378  if($this->isCacheUsed() &&
2379  array_key_exists($cache_key, $this->parent_type_cache))
2380  {
2381  return $this->parent_type_cache[$cache_key];
2382  }
2383 
2384  // Store up to 1000 results in cache
2385  $do_cache = ($this->__isMainTree() && count($this->parent_type_cache) < 1000);
2386 
2387  // ref_id is not in tree
2388  if(!$this->isInTree($a_ref_id))
2389  {
2390  if($do_cache)
2391  {
2392  $this->parent_type_cache[$cache_key] = false;
2393  }
2394  return false;
2395  }
2396 
2397  $path = array_reverse($this->getPathFull($a_ref_id));
2398 
2399  // remove first path entry as it is requested node
2400  if($a_exclude_source_check)
2401  {
2402  array_shift($path);
2403  }
2404 
2405  foreach($path as $node)
2406  {
2407  // found matching parent
2408  if($node["type"] == $a_type)
2409  {
2410  if($do_cache)
2411  {
2412  $this->parent_type_cache[$cache_key] = $node["child"];
2413  }
2414  return $node["child"];
2415  }
2416  }
2417 
2418  if($do_cache)
2419  {
2420  $this->parent_type_cache[$cache_key] = false;
2421  }
2422  return 0;
2423  }
2424 
2434  function _removeEntry($a_tree,$a_child,$a_db_table = "tree")
2435  {
2436  global $ilDB,$ilLog,$ilErr;
2437 
2438  if($a_db_table === 'tree')
2439  {
2440  if($a_tree == 1 and $a_child == ROOT_FOLDER_ID)
2441  {
2442  $message = sprintf('%s::_removeEntry(): Tried to delete root node! $a_tree: %s $a_child: %s',
2443  get_class($this),
2444  $a_tree,
2445  $a_child);
2446  $ilLog->write($message,$ilLog->FATAL);
2447  $ilErr->raiseError($message,$ilErr->WARNING);
2448  }
2449  }
2450 
2451  $query = 'DELETE FROM '.$a_db_table.' '.
2452  'WHERE tree = %s '.
2453  'AND child = %s ';
2454  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2455  $a_tree,
2456  $a_child));
2457 
2458  }
2459 
2466  public function __isMainTree()
2467  {
2468  return $this->table_tree === 'tree';
2469  }
2470 
2481  function __checkDelete($a_node)
2482  {
2483  global $ilDB;
2484 
2485 
2486  $query = $this->getTreeImplementation()->getSubTreeQuery($a_node, array(),false);
2487  $GLOBALS['ilLog']->write(__METHOD__.': '.$query);
2488  $res = $ilDB->query($query);
2489 
2490  $counter = (int) $lft_childs = array();
2491  while($row = $ilDB->fetchObject($res))
2492  {
2493  $lft_childs[$row->child] = $row->parent;
2494  ++$counter;
2495  }
2496 
2497  // CHECK FOR DUPLICATE CHILD IDS
2498  if($counter != count($lft_childs))
2499  {
2500  $message = sprintf('%s::__checkTree(): Duplicate entries for "child" in maintree! $a_node_id: %s',
2501  get_class($this),
2502  $a_node['child']);
2503  $this->log->write($message,$this->log->FATAL);
2504  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2505  }
2506 
2507  // GET SUBTREE BY PARENT RELATION
2508  $parent_childs = array();
2509  $this->__getSubTreeByParentRelation($a_node['child'],$parent_childs);
2510  $this->__validateSubtrees($lft_childs,$parent_childs);
2511 
2512  return true;
2513  }
2514 
2523  function __getSubTreeByParentRelation($a_node_id,&$parent_childs)
2524  {
2525  global $ilDB;
2526 
2527  // GET PARENT ID
2528  $query = 'SELECT * FROM '.$this->table_tree.' '.
2529  'WHERE child = %s '.
2530  'AND tree = %s ';
2531  $res = $ilDB->queryF($query,array('integer','integer'),array(
2532  $a_node_id,
2533  $this->tree_id));
2534 
2535  $counter = 0;
2536  while($row = $ilDB->fetchObject($res))
2537  {
2538  $parent_childs[$a_node_id] = $row->parent;
2539  ++$counter;
2540  }
2541  // MULTIPLE ENTRIES
2542  if($counter > 1)
2543  {
2544  $message = sprintf('%s::__getSubTreeByParentRelation(): Multiple entries in maintree! $a_node_id: %s',
2545  get_class($this),
2546  $a_node_id);
2547  $this->log->write($message,$this->log->FATAL);
2548  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2549  }
2550 
2551  // GET ALL CHILDS
2552  $query = 'SELECT * FROM '.$this->table_tree.' '.
2553  'WHERE parent = %s ';
2554  $res = $ilDB->queryF($query,array('integer'),array($a_node_id));
2555 
2556  while($row = $ilDB->fetchObject($res))
2557  {
2558  // RECURSION
2559  $this->__getSubTreeByParentRelation($row->child,$parent_childs);
2560  }
2561  return true;
2562  }
2563 
2564  function __validateSubtrees(&$lft_childs,$parent_childs)
2565  {
2566  // SORT BY KEY
2567  ksort($lft_childs);
2568  ksort($parent_childs);
2569 
2570  $GLOBALS['ilLog']->write(__METHOD__.': left childs '. print_r($lft_childs,true));
2571  $GLOBALS['ilLog']->write(__METHOD__.': parent childs '. print_r($parent_childs,true));
2572 
2573  if(count($lft_childs) != count($parent_childs))
2574  {
2575  $message = sprintf('%s::__validateSubtrees(): (COUNT) Tree is corrupted! Left/Right subtree does not comply .'.
2576  'with parent relation',
2577  get_class($this));
2578  $this->log->write($message,$this->log->FATAL);
2579  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2580  }
2581 
2582 
2583  foreach($lft_childs as $key => $value)
2584  {
2585  if($parent_childs[$key] != $value)
2586  {
2587  $message = sprintf('%s::__validateSubtrees(): (COMPARE) Tree is corrupted! Left/Right subtree does not comply '.
2588  'with parent relation',
2589  get_class($this));
2590  $this->log->write($message,$this->log->FATAL);
2591  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2592  }
2593  if($key == ROOT_FOLDER_ID)
2594  {
2595  $message = sprintf('%s::__validateSubtrees(): (ROOT_FOLDER) Tree is corrupted! Tried to delete root folder',
2596  get_class($this));
2597  $this->log->write($message,$this->log->FATAL);
2598  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2599  }
2600  }
2601  return true;
2602  }
2603 
2613  public function moveTree($a_source_id, $a_target_id, $a_location = self::POS_LAST_NODE)
2614  {
2615  $this->getTreeImplementation()->moveTree($a_source_id,$a_target_id,$a_location);
2616  $GLOBALS['ilAppEventHandler']->raise(
2617  "Services/Tree",
2618  "moveTree",
2619  array(
2620  'tree' => $this->table_tree,
2621  'source_id' => $a_source_id,
2622  'target_id' => $a_target_id)
2623  );
2624  return true;
2625  }
2626 
2627 
2628 
2629 
2637  public function getRbacSubtreeInfo($a_endnode_id)
2638  {
2639  return $this->getTreeImplementation()->getSubtreeInfo($a_endnode_id);
2640  }
2641 
2642 
2650  public function getSubTreeQuery($a_node_id,$a_fields = array(), $a_types = '', $a_force_join_reference = false)
2651  {
2652  return $this->getTreeImplementation()->getSubTreeQuery(
2653  $this->getNodeTreeData($a_node_id),
2654  $a_types,
2655  $a_force_join_reference,
2656  $a_fields);
2657  }
2658 } // END class.tree
2659 ?>