ILIAS  release_7 Revision v7.30-3-g800a261c036
All Data Structures Namespaces Files Functions Variables Modules 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  public const TREE_TYPE_MATERIALIZED_PATH = 'mp';
27  public const TREE_TYPE_NESTED_SET = 'ns';
28 
29  const POS_LAST_NODE = -2;
30  const POS_FIRST_NODE = -1;
31 
32 
33  const RELATION_CHILD = 1; // including grand child
34  const RELATION_PARENT = 2; // including grand child
35  const RELATION_SIBLING = 3;
36  const RELATION_EQUALS = 4;
37  const RELATION_NONE = 5;
38 
39 
45  public $ilias;
46 
47 
53  public $log;
54 
60  public $root_id;
61 
67  public $tree_id;
68 
74  public $table_tree;
75 
82 
89 
95  public $ref_pk;
96 
102  public $obj_pk;
103 
109  public $tree_pk;
110 
135  public $gap;
136 
137  protected $depth_cache = array();
138  protected $parent_cache = array();
139  protected $in_tree_cache = array();
140 
141  private $tree_impl = null;
142 
143 
151  public function __construct($a_tree_id, $a_root_id = 0)
152  {
153  global $DIC;
154 
155  $ilDB = $DIC['ilDB'];
156 
157  // set db
158  $this->ilDB = $ilDB;
159 
160  $this->lang_code = "en";
161 
162  // CREATE LOGGER INSTANCE
163  $this->log = ilLoggerFactory::getLogger('tree');
164 
165  if (!isset($a_tree_id) or (func_num_args() == 0)) {
166  $this->log->error("No tree_id given!");
167  $this->log->logStack(ilLogLevel::DEBUG);
168  throw new InvalidArgumentException("No tree_id given!");
169  }
170 
171  if (func_num_args() > 2) {
172  $this->log->error("Wrong parameter count!");
173  throw new InvalidArgumentException("Wrong parameter count!");
174  }
175 
176  //init variables
177  if (empty($a_root_id)) {
178  $a_root_id = ROOT_FOLDER_ID;
179  }
180 
181  $this->tree_id = $a_tree_id;
182  $this->root_id = $a_root_id;
183  $this->table_tree = 'tree';
184  $this->table_obj_data = 'object_data';
185  $this->table_obj_reference = 'object_reference';
186  $this->ref_pk = 'ref_id';
187  $this->obj_pk = 'obj_id';
188  $this->tree_pk = 'tree';
189 
190  $this->use_cache = true;
191 
192  // If cache is activated, cache object translations to improve performance
193  $this->translation_cache = array();
194  $this->parent_type_cache = array();
195 
196  // By default, we create gaps in the tree sequence numbering for 50 nodes
197  $this->gap = 50;
198 
199 
200  // init tree implementation
201  $this->initTreeImplementation();
202  }
203 
208  public static function lookupTreesForNode(int $node_id) : array
209  {
210  global $DIC;
211 
212  $db = $DIC->database();
213 
214  $query = 'select tree from tree ' .
215  'where child = ' . $db->quote($node_id, \ilDBConstants::T_INTEGER);
216  $res = $db->query($query);
217 
218  $trees = [];
219  while ($row = $res->fetchRow(\ilDBConstants::FETCHMODE_OBJECT)) {
220  $trees[] = $row->tree;
221  }
222  return $trees;
223  }
224 
228  public function initTreeImplementation()
229  {
230  global $DIC;
231 
232  if (!$DIC->isDependencyAvailable('settings') || $DIC->settings()->getModule() != 'common') {
233  include_once './Services/Administration/classes/class.ilSetting.php';
234  $setting = new ilSetting('common');
235  } else {
236  $setting = $DIC->settings();
237  }
238 
239  if ($this->__isMainTree()) {
240  if ($setting->get('main_tree_impl', 'ns') == 'ns') {
241  #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Using nested set.');
242  include_once './Services/Tree/classes/class.ilNestedSetTree.php';
243  $this->tree_impl = new ilNestedSetTree($this);
244  } else {
245  #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Using materialized path.');
246  include_once './Services/Tree/classes/class.ilMaterializedPathTree.php';
247  $this->tree_impl = new ilMaterializedPathTree($this);
248  }
249  } else {
250  #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Using netsted set for non main tree.');
251  include_once './Services/Tree/classes/class.ilNestedSetTree.php';
252  $this->tree_impl = new ilNestedSetTree($this);
253  }
254  }
255 
260  public function getTreeImplementation()
261  {
262  return $this->tree_impl;
263  }
264 
268  public function useCache($a_use = true)
269  {
270  $this->use_cache = $a_use;
271  }
272 
277  public function isCacheUsed()
278  {
279  return $this->__isMainTree() and $this->use_cache;
280  }
281 
286  public function getDepthCache()
287  {
288  return (array) $this->depth_cache;
289  }
290 
295  public function getParentCache()
296  {
297  return (array) $this->parent_cache;
298  }
299 
304  public function initLangCode()
305  {
306  global $DIC;
307 
308  // lang_code is only required in $this->fetchnodedata
309  try {
310  $ilUser = $DIC['ilUser'];
311  $this->lang_code = $ilUser->getCurrentLanguage();
312  } catch (\InvalidArgumentException $e) {
313  $this->lang_code = "en";
314  }
315  }
316 
321  public function getTreeTable()
322  {
323  return $this->table_tree;
324  }
325 
330  public function getObjectDataTable()
331  {
332  return $this->table_obj_data;
333  }
334 
339  public function getTreePk()
340  {
341  return $this->tree_pk;
342  }
343 
347  public function getTableReference()
348  {
350  }
351 
355  public function getGap()
356  {
357  return $this->gap;
358  }
359 
360  /***
361  * reset in tree cache
362  */
363  public function resetInTreeCache()
364  {
365  $this->in_tree_cache = array();
366  }
367 
368 
385  public function setTableNames($a_table_tree, $a_table_obj_data, $a_table_obj_reference = "")
386  {
387  if (!isset($a_table_tree) or !isset($a_table_obj_data)) {
388  $message = "Missing parameter! " .
389  "tree table: " . $a_table_tree . " object data table: " . $a_table_obj_data;
390  $this->log->error($message);
392  }
393 
394  $this->table_tree = $a_table_tree;
395  $this->table_obj_data = $a_table_obj_data;
396  $this->table_obj_reference = $a_table_obj_reference;
397 
398  $this->initTreeImplementation();
399 
400  return true;
401  }
402 
410  public function setReferenceTablePK($a_column_name)
411  {
412  if (!isset($a_column_name)) {
413  $message = "No column name given!";
414  $this->log->error($message);
416  }
417 
418  $this->ref_pk = $a_column_name;
419  return true;
420  }
421 
429  public function setObjectTablePK($a_column_name)
430  {
431  if (!isset($a_column_name)) {
432  $message = "No column name given!";
433  $this->log->error($message);
435  }
436 
437  $this->obj_pk = $a_column_name;
438  return true;
439  }
440 
448  public function setTreeTablePK($a_column_name)
449  {
450  if (!isset($a_column_name)) {
451  $message = "No column name given!";
452  $this->log->error($message);
454  }
455 
456  $this->tree_pk = $a_column_name;
457  return true;
458  }
459 
465  public function buildJoin()
466  {
467  if ($this->table_obj_reference) {
468  // Use inner join instead of left join to improve performance
469  return "JOIN " . $this->table_obj_reference . " ON " . $this->table_tree . ".child=" . $this->table_obj_reference . "." . $this->ref_pk . " " .
470  "JOIN " . $this->table_obj_data . " ON " . $this->table_obj_reference . "." . $this->obj_pk . "=" . $this->table_obj_data . "." . $this->obj_pk . " ";
471  } else {
472  // Use inner join instead of left join to improve performance
473  return "JOIN " . $this->table_obj_data . " ON " . $this->table_tree . ".child=" . $this->table_obj_data . "." . $this->obj_pk . " ";
474  }
475  }
476 
482  public function getRelation($a_node_a, $a_node_b)
483  {
484  return $this->getRelationOfNodes(
485  $this->getNodeTreeData($a_node_a),
486  $this->getNodeTreeData($a_node_b)
487  );
488  }
489 
496  public function getRelationOfNodes($a_node_a_arr, $a_node_b_arr)
497  {
498  return $this->getTreeImplementation()->getRelation($a_node_a_arr, $a_node_b_arr);
499  }
500 
507  public function getChildIds($a_node)
508  {
509  global $DIC;
510 
511  $ilDB = $DIC['ilDB'];
512 
513  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
514  'WHERE parent = ' . $ilDB->quote($a_node, 'integer') . ' ' .
515  'AND tree = ' . $ilDB->quote($this->tree_id, 'integer' . ' ' .
516  'ORDER BY lft');
517  $res = $ilDB->query($query);
518 
519  $childs = array();
520  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
521  $childs[] = $row->child;
522  }
523  return $childs;
524  }
525 
535  public function getChilds($a_node_id, $a_order = "", $a_direction = "ASC")
536  {
537  global $DIC;
538 
539  $ilBench = $DIC['ilBench'];
540  $ilDB = $DIC['ilDB'];
541  $ilObjDataCache = $DIC['ilObjDataCache'];
542  $ilUser = $DIC['ilUser'];
543 
544  if (!isset($a_node_id)) {
545  $message = "No node_id given!";
546  $this->log->error($message);
548  }
549 
550  // init childs
551  $childs = array();
552 
553  // number of childs
554  $count = 0;
555 
556  // init order_clause
557  $order_clause = "";
558 
559  // set order_clause if sort order parameter is given
560  if (!empty($a_order)) {
561  $order_clause = "ORDER BY " . $a_order . " " . $a_direction;
562  } else {
563  $order_clause = "ORDER BY " . $this->table_tree . ".lft";
564  }
565 
566 
567  $query = sprintf(
568  'SELECT * FROM ' . $this->table_tree . ' ' .
569  $this->buildJoin() .
570  "WHERE parent = %s " .
571  "AND " . $this->table_tree . "." . $this->tree_pk . " = %s " .
572  $order_clause,
573  $ilDB->quote($a_node_id, 'integer'),
574  $ilDB->quote($this->tree_id, 'integer')
575  );
576 
577  $res = $ilDB->query($query);
578 
579  if (!$count = $res->numRows()) {
580  return array();
581  }
582 
583  // get rows and object ids
584  $rows = array();
585  while ($r = $ilDB->fetchAssoc($res)) {
586  $rows[] = $r;
587  $obj_ids[] = $r["obj_id"];
588  }
589 
590  // preload object translation information
591  if ($this->__isMainTree() && $this->isCacheUsed() && is_object($ilObjDataCache) &&
592  is_object($ilUser) && $this->lang_code == $ilUser->getLanguage() && !$this->oc_preloaded[$a_node_id]) {
593  // $ilObjDataCache->preloadTranslations($obj_ids, $this->lang_code);
594  $ilObjDataCache->preloadObjectCache($obj_ids, $this->lang_code);
595  $this->fetchTranslationFromObjectDataCache($obj_ids);
596  $this->oc_preloaded[$a_node_id] = true;
597  }
598 
599  foreach ($rows as $row) {
600  $childs[] = $this->fetchNodeData($row);
601 
602  // Update cache of main tree
603  if ($this->__isMainTree()) {
604  #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child'].' = true');
605  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
606  }
607  }
608  $childs[$count - 1]["last"] = true;
609  return $childs;
610  }
611 
621  public function getFilteredChilds($a_filter, $a_node, $a_order = "", $a_direction = "ASC")
622  {
623  $childs = $this->getChilds($a_node, $a_order, $a_direction);
624 
625  foreach ($childs as $child) {
626  if (!in_array($child["type"], $a_filter)) {
627  $filtered[] = $child;
628  }
629  }
630  return $filtered ? $filtered : array();
631  }
632 
633 
642  public function getChildsByType($a_node_id, $a_type)
643  {
644  global $DIC;
645 
646  $ilDB = $DIC['ilDB'];
647 
648  if (!isset($a_node_id) or !isset($a_type)) {
649  $message = "Missing parameter! node_id:" . $a_node_id . " type:" . $a_type;
650  $this->log->error($message);
652  }
653 
654  if ($a_type == 'rolf' && $this->table_obj_reference) {
655  // Performance optimization: A node can only have exactly one
656  // role folder as its child. Therefore we don't need to sort the
657  // results, and we can let the database know about the expected limit.
658  $ilDB->setLimit(1, 0);
659  $query = sprintf(
660  "SELECT * FROM " . $this->table_tree . " " .
661  $this->buildJoin() .
662  "WHERE parent = %s " .
663  "AND " . $this->table_tree . "." . $this->tree_pk . " = %s " .
664  "AND " . $this->table_obj_data . ".type = %s ",
665  $ilDB->quote($a_node_id, 'integer'),
666  $ilDB->quote($this->tree_id, 'integer'),
667  $ilDB->quote($a_type, 'text')
668  );
669  } else {
670  $query = sprintf(
671  "SELECT * FROM " . $this->table_tree . " " .
672  $this->buildJoin() .
673  "WHERE parent = %s " .
674  "AND " . $this->table_tree . "." . $this->tree_pk . " = %s " .
675  "AND " . $this->table_obj_data . ".type = %s " .
676  "ORDER BY " . $this->table_tree . ".lft",
677  $ilDB->quote($a_node_id, 'integer'),
678  $ilDB->quote($this->tree_id, 'integer'),
679  $ilDB->quote($a_type, 'text')
680  );
681  }
682  $res = $ilDB->query($query);
683 
684  // init childs
685  $childs = array();
686  while ($row = $ilDB->fetchAssoc($res)) {
687  $childs[] = $this->fetchNodeData($row);
688  }
689 
690  return $childs ? $childs : array();
691  }
692 
693 
702  public function getChildsByTypeFilter($a_node_id, $a_types, $a_order = "", $a_direction = "ASC")
703  {
704  global $DIC;
705 
706  $ilDB = $DIC['ilDB'];
707 
708  if (!isset($a_node_id) or !$a_types) {
709  $message = "Missing parameter! node_id:" . $a_node_id . " type:" . $a_types;
710  $this->log->error($message);
712  }
713 
714  $filter = ' ';
715  if ($a_types) {
716  $filter = 'AND ' . $this->table_obj_data . '.type IN(' . implode(',', ilUtil::quoteArray($a_types)) . ') ';
717  }
718 
719  // set order_clause if sort order parameter is given
720  if (!empty($a_order)) {
721  $order_clause = "ORDER BY " . $a_order . " " . $a_direction;
722  } else {
723  $order_clause = "ORDER BY " . $this->table_tree . ".lft";
724  }
725 
726  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
727  $this->buildJoin() .
728  'WHERE parent = ' . $ilDB->quote($a_node_id, 'integer') . ' ' .
729  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = ' . $ilDB->quote($this->tree_id, 'integer') . ' ' .
730  $filter .
731  $order_clause;
732 
733  $res = $ilDB->query($query);
734  while ($row = $ilDB->fetchAssoc($res)) {
735  $childs[] = $this->fetchNodeData($row);
736  }
737 
738  return $childs ? $childs : array();
739  }
740 
752  public function insertNodeFromTrash($a_source_id, $a_target_id, $a_tree_id, $a_pos = IL_LAST_NODE, $a_reset_deleted_date = false)
753  {
754  global $DIC;
755 
756  $ilDB = $DIC['ilDB'];
757 
758  if ($this->__isMainTree()) {
759  if ($a_source_id <= 1 or $a_target_id <= 0) {
760  ilLoggerFactory::getLogger('tree')->logStack(ilLogLevel::INFO);
761  throw new InvalidArgumentException('Invalid parameter given for ilTree::insertNodeFromTrash');
762  }
763  }
764  if (!isset($a_source_id) or !isset($a_target_id)) {
765  ilLoggerFactory::getLogger('tree')->logStack(ilLogLevel::INFO);
766  throw new InvalidArgumentException('Missing parameter for ilTree::insertNodeFromTrash');
767  }
768  if ($this->isInTree($a_source_id)) {
769  ilLoggerFactory::getLogger('tree')->error('Node already in tree');
770  ilLoggerFactory::getLogger('tree')->logStack(ilLogLevel::INFO);
771  throw new InvalidArgumentException('Node already in tree.');
772  }
773 
774  $query = 'DELETE from tree ' .
775  'WHERE tree = ' . $ilDB->quote($a_tree_id, 'integer') . ' ' .
776  'AND child = ' . $ilDB->quote($a_source_id, 'integer');
777  $ilDB->manipulate($query);
778 
779  $this->insertNode($a_source_id, $a_target_id, IL_LAST_NODE, $a_reset_deleted_date);
780  }
781 
782 
791  public function insertNode($a_node_id, $a_parent_id, $a_pos = IL_LAST_NODE, $a_reset_deletion_date = false)
792  {
793  global $DIC;
794 
795  $ilDB = $DIC['ilDB'];
796 
797  //echo "+$a_node_id+$a_parent_id+";
798  // CHECK node_id and parent_id > 0 if in main tree
799  if ($this->__isMainTree()) {
800  if ($a_node_id <= 1 or $a_parent_id <= 0) {
801  $message = sprintf(
802  'Invalid parameters! $a_node_id: %s $a_parent_id: %s',
803  $a_node_id,
804  $a_parent_id
805  );
806  $this->log->logStack(ilLogLevel::ERROR, $message);
808  }
809  }
810 
811 
812  if (!isset($a_node_id) or !isset($a_parent_id)) {
813  $this->log->logStack(ilLogLevel::ERROR);
814  throw new InvalidArgumentException("Missing parameter! " .
815  "node_id: " . $a_node_id . " parent_id: " . $a_parent_id);
816  }
817  if ($this->isInTree($a_node_id)) {
818  throw new InvalidArgumentException("Node " . $a_node_id . " already in tree " .
819  $this->table_tree . "!");
820  }
821 
822  $this->getTreeImplementation()->insertNode($a_node_id, $a_parent_id, $a_pos);
823 
824  $this->in_tree_cache[$a_node_id] = true;
825 
826  // reset deletion date
827  if ($a_reset_deletion_date) {
828  ilObject::_resetDeletedDate($a_node_id);
829  }
830 
831  if (isset($GLOBALS['DIC']["ilAppEventHandler"]) && $this->__isMainTree()) {
832  $GLOBALS['DIC']['ilAppEventHandler']->raise(
833  "Services/Tree",
834  "insertNode",
835  array(
836  'tree' => $this->table_tree,
837  'node_id' => $a_node_id,
838  'parent_id' => $a_parent_id)
839  );
840  }
841  }
842 
855  public function getFilteredSubTree($a_node_id, $a_filter = array())
856  {
857  $node = $this->getNodeData($a_node_id);
858 
859  $first = true;
860  $depth = 0;
861  foreach ($this->getSubTree($node) as $subnode) {
862  if ($depth and $subnode['depth'] > $depth) {
863  continue;
864  }
865  if (!$first and in_array($subnode['type'], $a_filter)) {
866  $depth = $subnode['depth'];
867  $first = false;
868  continue;
869  }
870  $depth = 0;
871  $first = false;
872  $filtered[] = $subnode;
873  }
874  return $filtered ? $filtered : array();
875  }
876 
882  public function getSubTreeIds($a_ref_id)
883  {
884  return $this->getTreeImplementation()->getSubTreeIds($a_ref_id);
885  }
886 
887 
897  public function getSubTree($a_node, $a_with_data = true, $a_type = "")
898  {
899  global $DIC;
900 
901  $ilDB = $DIC['ilDB'];
902 
903  if (!is_array($a_node)) {
904  $this->log->logStack(ilLogLevel::ERROR);
905  throw new InvalidArgumentException(__METHOD__ . ': wrong datatype for node data given');
906  }
907 
908  /*
909  if($a_node['lft'] < 1 or $a_node['rgt'] < 2)
910  {
911  $GLOBALS['DIC']['ilLog']->logStack();
912  $message = sprintf('%s: Invalid node given! $a_node["lft"]: %s $a_node["rgt"]: %s',
913  __METHOD__,
914  $a_node['lft'],
915  $a_node['rgt']);
916 
917  throw new InvalidArgumentException($message);
918  }
919  */
920 
921  $query = $this->getTreeImplementation()->getSubTreeQuery($a_node, $a_type);
922  $res = $ilDB->query($query);
923  while ($row = $ilDB->fetchAssoc($res)) {
924  if ($a_with_data) {
925  $subtree[] = $this->fetchNodeData($row);
926  } else {
927  $subtree[] = $row['child'];
928  }
929  // the lm_data "hack" should be removed in the trunk during an alpha
930  if ($this->__isMainTree() || $this->table_tree == "lm_tree") {
931  $this->in_tree_cache[$row['child']] = true;
932  }
933  }
934  return $subtree ? $subtree : array();
935  }
936 
945  public function getSubTreeTypes($a_node, $a_filter = 0)
946  {
947  $a_filter = $a_filter ? $a_filter : array();
948 
949  foreach ($this->getSubtree($this->getNodeData($a_node)) as $node) {
950  if (in_array($node["type"], $a_filter)) {
951  continue;
952  }
953  $types["$node[type]"] = $node["type"];
954  }
955  return $types ? $types : array();
956  }
957 
965  public function deleteTree($a_node)
966  {
967  global $DIC;
968 
969  $ilDB = $DIC['ilDB'];
970 
971  $this->log->debug('Delete tree with node ' . $a_node);
972 
973  if (!is_array($a_node)) {
974  $this->log->logStack(ilLogLevel::ERROR);
975  throw new InvalidArgumentException(__METHOD__ . ': Wrong datatype for node data!');
976  }
977 
978  $this->log->debug($this->tree_pk);
979 
980  if ($this->__isMainTree()) {
981  // @todo normally this part is not executed, since the subtree is first
982  // moved to trash and then deleted.
983  if (!$this->__checkDelete($a_node)) {
984  $this->log->logStack(ilLogLevel::ERROR);
985  throw new ilInvalidTreeStructureException('Deletion canceled due to invalid tree structure.' . print_r($a_node, true));
986  }
987  }
988 
989  $this->getTreeImplementation()->deleteTree($a_node['child']);
990 
991  $this->resetInTreeCache();
992  }
993 
998  public function validateParentRelations()
999  {
1000  return $this->getTreeImplementation()->validateParentRelations();
1001  }
1002 
1013  public function getPathFull($a_endnode_id, $a_startnode_id = 0)
1014  {
1015  $pathIds = $this->getPathId($a_endnode_id, $a_startnode_id);
1016 
1017  // We retrieve the full path in a single query to improve performance
1018  global $DIC;
1019 
1020  $ilDB = $DIC['ilDB'];
1021 
1022  // Abort if no path ids were found
1023  if (count($pathIds) == 0) {
1024  return null;
1025  }
1026 
1027  $inClause = 'child IN (';
1028  for ($i = 0; $i < count($pathIds); $i++) {
1029  if ($i > 0) {
1030  $inClause .= ',';
1031  }
1032  $inClause .= $ilDB->quote($pathIds[$i], 'integer');
1033  }
1034  $inClause .= ')';
1035 
1036  $q = 'SELECT * ' .
1037  'FROM ' . $this->table_tree . ' ' .
1038  $this->buildJoin() . ' ' .
1039  'WHERE ' . $inClause . ' ' .
1040  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = ' . $this->ilDB->quote($this->tree_id, 'integer') . ' ' .
1041  'ORDER BY depth';
1042  $r = $ilDB->query($q);
1043 
1044  $pathFull = array();
1045  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
1046  $pathFull[] = $this->fetchNodeData($row);
1047 
1048  // Update cache
1049  if ($this->__isMainTree()) {
1050  #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child']);
1051  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
1052  }
1053  }
1054  return $pathFull;
1055  }
1056 
1057 
1064  public function preloadDepthParent($a_node_ids)
1065  {
1066  global $DIC;
1067 
1068  $ilDB = $DIC['ilDB'];
1069 
1070  if (!$this->__isMainTree() || !is_array($a_node_ids) || !$this->isCacheUsed()) {
1071  return;
1072  }
1073 
1074  $res = $ilDB->query('SELECT t.depth, t.parent, t.child ' .
1075  'FROM ' . $this->table_tree . ' t ' .
1076  'WHERE ' . $ilDB->in("child", $a_node_ids, false, "integer") .
1077  'AND ' . $this->tree_pk . ' = ' . $ilDB->quote($this->tree_id, "integer"));
1078  while ($row = $ilDB->fetchAssoc($res)) {
1079  $this->depth_cache[$row["child"]] = $row["depth"];
1080  $this->parent_cache[$row["child"]] = $row["parent"];
1081  }
1082  }
1083 
1093  public function getPathId($a_endnode_id, $a_startnode_id = 0)
1094  {
1095  if (!$a_endnode_id) {
1096  $this->log->logStack(ilLogLevel::ERROR);
1097  throw new InvalidArgumentException(__METHOD__ . ': No endnode given!');
1098  }
1099 
1100  // path id cache
1101  if ($this->isCacheUsed() && isset($this->path_id_cache[$a_endnode_id][$a_startnode_id])) {
1102  //echo "<br>getPathIdhit";
1103  return $this->path_id_cache[$a_endnode_id][$a_startnode_id];
1104  }
1105  //echo "<br>miss";
1106 
1107  $pathIds = $this->getTreeImplementation()->getPathIds($a_endnode_id, $a_startnode_id);
1108 
1109  if ($this->__isMainTree()) {
1110  $this->path_id_cache[$a_endnode_id][$a_startnode_id] = $pathIds;
1111  }
1112  return $pathIds;
1113  }
1114 
1115  // BEGIN WebDAV: getNodePathForTitlePath function added
1133  public function getNodePathForTitlePath($titlePath, $a_startnode_id = null)
1134  {
1135  global $DIC;
1136 
1137  $ilDB = $DIC['ilDB'];
1138  $log = $DIC['log'];
1139  //$log->write('getNodePathForTitlePath('.implode('/',$titlePath));
1140 
1141  // handle empty title path
1142  if ($titlePath == null || count($titlePath) == 0) {
1143  if ($a_startnode_id == 0) {
1144  return null;
1145  } else {
1146  return $this->getNodePath($a_startnode_id);
1147  }
1148  }
1149 
1150  // fetch the node path up to the startnode
1151  if ($a_startnode_id != null && $a_startnode_id != 0) {
1152  // Start using the node path to the root of the relative path
1153  $nodePath = $this->getNodePath($a_startnode_id);
1154  $parent = $a_startnode_id;
1155  } else {
1156  // Start using the root of the tree
1157  $nodePath = array();
1158  $parent = 0;
1159  }
1160 
1161 
1162  // Convert title path into Unicode Normal Form C
1163  // This is needed to ensure that we can compare title path strings with
1164  // strings from the database.
1165  require_once('include/Unicode/UtfNormal.php');
1166  include_once './Services/Utilities/classes/class.ilStr.php';
1167  $inClause = 'd.title IN (';
1168  for ($i = 0; $i < count($titlePath); $i++) {
1169  $titlePath[$i] = ilStr::strToLower(UtfNormal::toNFC($titlePath[$i]));
1170  if ($i > 0) {
1171  $inClause .= ',';
1172  }
1173  $inClause .= $ilDB->quote($titlePath[$i], 'text');
1174  }
1175  $inClause .= ')';
1176 
1177  // Fetch all rows that are potential path elements
1178  if ($this->table_obj_reference) {
1179  $joinClause = 'JOIN ' . $this->table_obj_reference . ' r ON t.child = r.' . $this->ref_pk . ' ' .
1180  'JOIN ' . $this->table_obj_data . ' d ON r.' . $this->obj_pk . ' = d.' . $this->obj_pk;
1181  } else {
1182  $joinClause = 'JOIN ' . $this->table_obj_data . ' d ON t.child = d.' . $this->obj_pk;
1183  }
1184  // The ORDER BY clause in the following SQL statement ensures that,
1185  // in case of a multiple objects with the same title, always the Object
1186  // with the oldest ref_id is chosen.
1187  // This ensure, that, if a new object with the same title is added,
1188  // WebDAV clients can still work with the older object.
1189  $q = 'SELECT t.depth, t.parent, t.child, d.' . $this->obj_pk . ' obj_id, d.type, d.title ' .
1190  'FROM ' . $this->table_tree . ' t ' .
1191  $joinClause . ' ' .
1192  'WHERE ' . $inClause . ' ' .
1193  'AND t.depth <= ' . (count($titlePath) + count($nodePath)) . ' ' .
1194  'AND t.tree = 1 ' .
1195  'ORDER BY t.depth, t.child ASC';
1196  $r = $ilDB->query($q);
1197 
1198  $rows = array();
1199  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
1200  $row['title'] = UtfNormal::toNFC($row['title']);
1201  $row['ref_id'] = $row['child'];
1202  $rows[] = $row;
1203  }
1204 
1205  // Extract the path elements from the fetched rows
1206  for ($i = 0; $i < count($titlePath); $i++) {
1207  $pathElementFound = false;
1208  foreach ($rows as $row) {
1209  if ($row['parent'] == $parent &&
1210  ilStr::strToLower($row['title']) == $titlePath[$i]) {
1211  // FIXME - We should test here, if the user has
1212  // 'visible' permission for the object.
1213  $nodePath[] = $row;
1214  $parent = $row['child'];
1215  $pathElementFound = true;
1216  break;
1217  }
1218  }
1219  // Abort if we haven't found a path element for the current depth
1220  if (!$pathElementFound) {
1221  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):null');
1222  return null;
1223  }
1224  }
1225  // Return the node path
1226  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):'.var_export($nodePath,true));
1227  return $nodePath;
1228  }
1229  // END WebDAV: getNodePathForTitlePath function added
1230  // END WebDAV: getNodePath function added
1247  public function getNodePath($a_endnode_id, $a_startnode_id = 0)
1248  {
1249  global $DIC;
1250 
1251  $ilDB = $DIC['ilDB'];
1252 
1253  $pathIds = $this->getPathId($a_endnode_id, $a_startnode_id);
1254 
1255  // Abort if no path ids were found
1256  if (count($pathIds) == 0) {
1257  return null;
1258  }
1259 
1260 
1261  $types = array();
1262  $data = array();
1263  for ($i = 0; $i < count($pathIds); $i++) {
1264  $types[] = 'integer';
1265  $data[] = $pathIds[$i];
1266  }
1267 
1268  $query = 'SELECT t.depth,t.parent,t.child,d.obj_id,d.type,d.title ' .
1269  'FROM ' . $this->table_tree . ' t ' .
1270  'JOIN ' . $this->table_obj_reference . ' r ON r.ref_id = t.child ' .
1271  'JOIN ' . $this->table_obj_data . ' d ON d.obj_id = r.obj_id ' .
1272  'WHERE ' . $ilDB->in('t.child', $data, false, 'integer') . ' ' .
1273  'ORDER BY t.depth ';
1274 
1275  $res = $ilDB->queryF($query, $types, $data);
1276 
1277  $titlePath = array();
1278  while ($row = $ilDB->fetchAssoc($res)) {
1279  $titlePath[] = $row;
1280  }
1281  return $titlePath;
1282  }
1283  // END WebDAV: getNodePath function added
1284 
1292  public function checkTree()
1293  {
1294  global $DIC;
1295 
1296  $ilDB = $DIC['ilDB'];
1297 
1298  $types = array('integer');
1299  $query = 'SELECT lft,rgt FROM ' . $this->table_tree . ' ' .
1300  'WHERE ' . $this->tree_pk . ' = %s ';
1301 
1302  $res = $ilDB->queryF($query, $types, array($this->tree_id));
1303  while ($row = $ilDB->fetchObject($res)) {
1304  $lft[] = $row->lft;
1305  $rgt[] = $row->rgt;
1306  }
1307 
1308  $all = array_merge($lft, $rgt);
1309  $uni = array_unique($all);
1310 
1311  if (count($all) != count($uni)) {
1312  $message = 'Tree is corrupted!';
1313 
1314  $this->log->error($message);
1316  }
1317 
1318  return true;
1319  }
1320 
1328  public function checkTreeChilds($a_no_zero_child = true)
1329  {
1330  global $DIC;
1331 
1332  $ilDB = $DIC['ilDB'];
1333 
1334  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1335  'WHERE ' . $this->tree_pk . ' = %s ' .
1336  'ORDER BY lft';
1337  $r1 = $ilDB->queryF($query, array('integer'), array($this->tree_id));
1338 
1339  while ($row = $ilDB->fetchAssoc($r1)) {
1340  //echo "tree:".$row[$this->tree_pk].":lft:".$row["lft"].":rgt:".$row["rgt"].":child:".$row["child"].":<br>";
1341  if (($row["child"] == 0) && $a_no_zero_child) {
1342  $message = "Tree contains child with ID 0!";
1343  $this->log->error($message);
1345  }
1346 
1347  if ($this->table_obj_reference) {
1348  // get object reference data
1349  $query = 'SELECT * FROM ' . $this->table_obj_reference . ' WHERE ' . $this->ref_pk . ' = %s ';
1350  $r2 = $ilDB->queryF($query, array('integer'), array($row['child']));
1351 
1352  //echo "num_childs:".$r2->numRows().":<br>";
1353  if ($r2->numRows() == 0) {
1354  $message = "No Object-to-Reference entry found for ID " . $row["child"] . "!";
1355  $this->log->error($message);
1357  }
1358  if ($r2->numRows() > 1) {
1359  $message = "More Object-to-Reference entries found for ID " . $row["child"] . "!";
1360  $this->log->error($message);
1362  }
1363 
1364  // get object data
1365  $obj_ref = $ilDB->fetchAssoc($r2);
1366 
1367  $query = 'SELECT * FROM ' . $this->table_obj_data . ' WHERE ' . $this->obj_pk . ' = %s';
1368  $r3 = $ilDB->queryF($query, array('integer'), array($obj_ref[$this->obj_pk]));
1369  if ($r3->numRows() == 0) {
1370  $message = " No child found for ID " . $obj_ref[$this->obj_pk] . "!";
1371  $this->log->error($message);
1373  }
1374  if ($r3->numRows() > 1) {
1375  $message = "More childs found for ID " . $obj_ref[$this->obj_pk] . "!";
1376  $this->log->error($message);
1378  }
1379  } else {
1380  // get only object data
1381  $query = 'SELECT * FROM ' . $this->table_obj_data . ' WHERE ' . $this->obj_pk . ' = %s';
1382  $r2 = $ilDB->queryF($query, array('integer'), array($row['child']));
1383  //echo "num_childs:".$r2->numRows().":<br>";
1384  if ($r2->numRows() == 0) {
1385  $message = "No child found for ID " . $row["child"] . "!";
1386  $this->log->error($message);
1388  }
1389  if ($r2->numRows() > 1) {
1390  $message = "More childs found for ID " . $row["child"] . "!";
1391  $this->log->error($message);
1393  }
1394  }
1395  }
1396 
1397  return true;
1398  }
1399 
1405  public function getMaximumDepth()
1406  {
1407  global $DIC;
1408 
1409  $ilDB = $DIC['ilDB'];
1410 
1411  $query = 'SELECT MAX(depth) depth FROM ' . $this->table_tree;
1412  $res = $ilDB->query($query);
1413 
1414  $row = $ilDB->fetchAssoc($res);
1415  return $row['depth'];
1416  }
1417 
1424  public function getDepth($a_node_id)
1425  {
1426  global $DIC;
1427 
1428  $ilDB = $DIC['ilDB'];
1429 
1430  if ($a_node_id) {
1431  if ($this->__isMainTree()) {
1432  $query = 'SELECT depth FROM ' . $this->table_tree . ' ' .
1433  'WHERE child = %s ';
1434  $res = $ilDB->queryF($query, array('integer'), array($a_node_id));
1435  $row = $ilDB->fetchObject($res);
1436  } else {
1437  $query = 'SELECT depth FROM ' . $this->table_tree . ' ' .
1438  'WHERE child = %s ' .
1439  'AND ' . $this->tree_pk . ' = %s ';
1440  $res = $ilDB->queryF($query, array('integer','integer'), array($a_node_id,$this->tree_id));
1441  $row = $ilDB->fetchObject($res);
1442  }
1443 
1444  return $row->depth;
1445  } else {
1446  return 1;
1447  }
1448  }
1449 
1457  public function getNodeTreeData($a_node_id)
1458  {
1459  global $DIC;
1460 
1461  $ilDB = $DIC['ilDB'];
1462 
1463  if (!$a_node_id) {
1464  $this->log->logStack(ilLogLevel::ERROR);
1465  throw new InvalidArgumentException('Missing or empty parameter $a_node_id: ' . $a_node_id);
1466  }
1467 
1468  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1469  'WHERE child = ' . $ilDB->quote($a_node_id, 'integer');
1470  $res = $ilDB->query($query);
1471  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
1472  return $row;
1473  }
1474  return array();
1475  }
1476 
1477 
1486  // BEGIN WebDAV: Pass tree id to this method
1487  //function getNodeData($a_node_id)
1488  public function getNodeData($a_node_id, $a_tree_pk = null)
1489  // END PATCH WebDAV: Pass tree id to this method
1490  {
1491  global $DIC;
1492 
1493  $ilDB = $DIC['ilDB'];
1494 
1495  if (!isset($a_node_id)) {
1496  $this->log->logStack(ilLogLevel::ERROR);
1497  throw new InvalidArgumentException("No node_id given!");
1498  }
1499  if ($this->__isMainTree()) {
1500  if ($a_node_id < 1) {
1501  $message = 'No valid parameter given! $a_node_id: %s' . $a_node_id;
1502 
1503  $this->log->error($message);
1505  }
1506  }
1507 
1508  // BEGIN WebDAV: Pass tree id to this method
1509  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1510  $this->buildJoin() .
1511  'WHERE ' . $this->table_tree . '.child = %s ' .
1512  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
1513  $res = $ilDB->queryF($query, array('integer','integer'), array(
1514  $a_node_id,
1515  $a_tree_pk === null ? $this->tree_id : $a_tree_pk));
1516  // END WebDAV: Pass tree id to this method
1517  $row = $ilDB->fetchAssoc($res);
1519 
1520  return $this->fetchNodeData($row);
1521  }
1522 
1530  public function fetchNodeData($a_row)
1531  {
1532  global $DIC;
1533 
1534  $objDefinition = $DIC['objDefinition'];
1535  $lng = $DIC['lng'];
1536  $ilBench = $DIC['ilBench'];
1537  $ilDB = $DIC['ilDB'];
1538 
1539  //$ilBench->start("Tree", "fetchNodeData_getRow");
1540  $data = $a_row;
1541  $data["desc"] = $a_row["description"]; // for compability
1542  //$ilBench->stop("Tree", "fetchNodeData_getRow");
1543 
1544  // multilingual support systemobjects (sys) & categories (db)
1545  //$ilBench->start("Tree", "fetchNodeData_readDefinition");
1546  if (is_object($objDefinition)) {
1547  $translation_type = $objDefinition->getTranslationType($data["type"]);
1548  }
1549  //$ilBench->stop("Tree", "fetchNodeData_readDefinition");
1550 
1551  if ($translation_type == "sys") {
1552  //$ilBench->start("Tree", "fetchNodeData_getLangData");
1553  if ($data["type"] == "rolf" and $data["obj_id"] != ROLE_FOLDER_ID) {
1554  $data["description"] = $lng->txt("obj_" . $data["type"] . "_local_desc") . $data["title"] . $data["desc"];
1555  $data["desc"] = $lng->txt("obj_" . $data["type"] . "_local_desc") . $data["title"] . $data["desc"];
1556  $data["title"] = $lng->txt("obj_" . $data["type"] . "_local");
1557  } else {
1558  $data["title"] = $lng->txt("obj_" . $data["type"]);
1559  $data["description"] = $lng->txt("obj_" . $data["type"] . "_desc");
1560  $data["desc"] = $lng->txt("obj_" . $data["type"] . "_desc");
1561  }
1562  //$ilBench->stop("Tree", "fetchNodeData_getLangData");
1563  } elseif ($translation_type == "db") {
1564 
1565  // Try to retrieve object translation from cache
1566  if ($this->isCacheUsed() &&
1567  array_key_exists($data["obj_id"] . '.' . $lang_code, $this->translation_cache)) {
1568  $key = $data["obj_id"] . '.' . $lang_code;
1569  $data["title"] = $this->translation_cache[$key]['title'];
1570  $data["description"] = $this->translation_cache[$key]['description'];
1571  $data["desc"] = $this->translation_cache[$key]['desc'];
1572  } else {
1573  // Object translation is not in cache, read it from database
1574  //$ilBench->start("Tree", "fetchNodeData_getTranslation");
1575  $query = 'SELECT title,description FROM object_translation ' .
1576  'WHERE obj_id = %s ' .
1577  'AND lang_code = %s ';
1578 
1579  $res = $ilDB->queryF($query, array('integer','text'), array(
1580  $data['obj_id'],
1581  $this->lang_code));
1582  $row = $ilDB->fetchObject($res);
1583 
1584  if ($row) {
1585  $data["title"] = $row->title;
1586  $data["description"] = ilUtil::shortenText($row->description, ilObject::DESC_LENGTH, true);
1587  $data["desc"] = $row->description;
1588  }
1589  //$ilBench->stop("Tree", "fetchNodeData_getTranslation");
1590 
1591  // Store up to 1000 object translations in cache
1592  if ($this->isCacheUsed() && count($this->translation_cache) < 1000) {
1593  $key = $data["obj_id"] . '.' . $lang_code;
1594  $this->translation_cache[$key] = array();
1595  $this->translation_cache[$key]['title'] = $data["title"] ;
1596  $this->translation_cache[$key]['description'] = $data["description"];
1597  $this->translation_cache[$key]['desc'] = $data["desc"];
1598  }
1599  }
1600  }
1601 
1602  // TODO: Handle this switch by module.xml definitions
1603  if ($data['type'] == 'crsr' or $data['type'] == 'catr' or $data['type'] == 'grpr' or $data['type'] === 'prgr') {
1604  include_once('./Services/ContainerReference/classes/class.ilContainerReference.php');
1605  $data['title'] = ilContainerReference::_lookupTitle($data['obj_id']);
1606  }
1607 
1608  return $data ? $data : array();
1609  }
1610 
1616  protected function fetchTranslationFromObjectDataCache($a_obj_ids)
1617  {
1618  global $DIC;
1619 
1620  $ilObjDataCache = $DIC['ilObjDataCache'];
1621 
1622  if ($this->isCacheUsed() && is_array($a_obj_ids) && is_object($ilObjDataCache)) {
1623  foreach ($a_obj_ids as $id) {
1624  $this->translation_cache[$id . '.']['title'] = $ilObjDataCache->lookupTitle($id);
1625  $this->translation_cache[$id . '.']['description'] = $ilObjDataCache->lookupDescription($id);
1626  ;
1627  $this->translation_cache[$id . '.']['desc'] =
1628  $this->translation_cache[$id . '.']['description'];
1629  }
1630  }
1631  }
1632 
1633 
1641  public function isInTree($a_node_id)
1642  {
1643  global $DIC;
1644 
1645  $ilDB = $DIC['ilDB'];
1646 
1647  if (!isset($a_node_id)) {
1648  return false;
1649  #$this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1650  }
1651  // is in tree cache
1652  if ($this->isCacheUsed() && isset($this->in_tree_cache[$a_node_id])) {
1653  #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Using in tree cache '.$a_node_id);
1654  //echo "<br>in_tree_hit";
1655  return $this->in_tree_cache[$a_node_id];
1656  }
1657 
1658  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1659  'WHERE ' . $this->table_tree . '.child = %s ' .
1660  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s';
1661 
1662  $res = $ilDB->queryF($query, array('integer','integer'), array(
1663  $a_node_id,
1664  $this->tree_id));
1665 
1666  if ($res->numRows() > 0) {
1667  if ($this->__isMainTree()) {
1668  #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
1669  $this->in_tree_cache[$a_node_id] = true;
1670  }
1671  return true;
1672  } else {
1673  if ($this->__isMainTree()) {
1674  #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = false');
1675  $this->in_tree_cache[$a_node_id] = false;
1676  }
1677  return false;
1678  }
1679  }
1680 
1688  public function getParentNodeData($a_node_id)
1689  {
1690  global $DIC;
1691 
1692  $ilDB = $DIC['ilDB'];
1693  global $DIC;
1694 
1695  $ilLog = $DIC['ilLog'];
1696 
1697  if (!isset($a_node_id)) {
1698  $ilLog->logStack();
1699  throw new InvalidArgumentException(__METHOD__ . ': No node_id given!');
1700  }
1701 
1702  if ($this->table_obj_reference) {
1703  // Use inner join instead of left join to improve performance
1704  $innerjoin = "JOIN " . $this->table_obj_reference . " ON v.child=" . $this->table_obj_reference . "." . $this->ref_pk . " " .
1705  "JOIN " . $this->table_obj_data . " ON " . $this->table_obj_reference . "." . $this->obj_pk . "=" . $this->table_obj_data . "." . $this->obj_pk . " ";
1706  } else {
1707  // Use inner join instead of left join to improve performance
1708  $innerjoin = "JOIN " . $this->table_obj_data . " ON v.child=" . $this->table_obj_data . "." . $this->obj_pk . " ";
1709  }
1710 
1711  $query = 'SELECT * FROM ' . $this->table_tree . ' s, ' . $this->table_tree . ' v ' .
1712  $innerjoin .
1713  'WHERE s.child = %s ' .
1714  'AND s.parent = v.child ' .
1715  'AND s.' . $this->tree_pk . ' = %s ' .
1716  'AND v.' . $this->tree_pk . ' = %s';
1717  $res = $ilDB->queryF($query, array('integer','integer','integer'), array(
1718  $a_node_id,
1719  $this->tree_id,
1720  $this->tree_id));
1721  $row = $ilDB->fetchAssoc($res);
1722  return $this->fetchNodeData($row);
1723  }
1724 
1732  public function isGrandChild($a_startnode_id, $a_querynode_id)
1733  {
1734  return $this->getRelation($a_startnode_id, $a_querynode_id) == self::RELATION_PARENT;
1735  }
1736 
1746  public function addTree($a_tree_id, $a_node_id = -1)
1747  {
1748  global $DIC;
1749 
1750  $ilDB = $DIC['ilDB'];
1751 
1752  // FOR SECURITY addTree() IS NOT ALLOWED ON MAIN TREE
1753  if ($this->__isMainTree()) {
1754  $message = sprintf(
1755  'Operation not allowed on main tree! $a_tree_if: %s $a_node_id: %s',
1756  $a_tree_id,
1757  $a_node_id
1758  );
1759  $this->log->error($message);
1761  }
1762 
1763  if (!isset($a_tree_id)) {
1764  $message = "No tree_id given!";
1765  $this->log->error($message);
1767  }
1768 
1769  if ($a_node_id <= 0) {
1770  $a_node_id = $a_tree_id;
1771  }
1772 
1773  $query = 'INSERT INTO ' . $this->table_tree . ' (' .
1774  $this->tree_pk . ', child,parent,lft,rgt,depth) ' .
1775  'VALUES ' .
1776  '(%s,%s,%s,%s,%s,%s)';
1777  $res = $ilDB->manipulateF($query, array('integer','integer','integer','integer','integer','integer'), array(
1778  $a_tree_id,
1779  $a_node_id,
1780  0,
1781  1,
1782  2,
1783  1));
1784 
1785  return true;
1786  }
1787 
1797  public function getNodeDataByType($a_type)
1798  {
1799  global $DIC;
1800 
1801  $ilDB = $DIC['ilDB'];
1802 
1803  if (!isset($a_type) or (!is_string($a_type))) {
1804  $this->log->logStack(ilLogLevel::ERROR);
1805  throw new InvalidArgumentException('Type not given or wrong datatype');
1806  }
1807 
1808  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1809  $this->buildJoin() .
1810  'WHERE ' . $this->table_obj_data . '.type = ' . $this->ilDB->quote($a_type, 'text') .
1811  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = ' . $this->ilDB->quote($this->tree_id, 'integer');
1812 
1813  $res = $ilDB->query($query);
1814  $data = array();
1815  while ($row = $ilDB->fetchAssoc($res)) {
1816  $data[] = $this->fetchNodeData($row);
1817  }
1818 
1819  return $data;
1820  }
1821 
1830  public function removeTree($a_tree_id)
1831  {
1832  global $DIC;
1833 
1834  $ilDB = $DIC['ilDB'];
1835 
1836  // OPERATION NOT ALLOWED ON MAIN TREE
1837  if ($this->__isMainTree()) {
1838  $this->log->logStack(ilLogLevel::ERROR);
1839  throw new InvalidArgumentException('Operation not allowed on main tree');
1840  }
1841  if (!$a_tree_id) {
1842  $this->log->logStack(ilLogLevel::ERROR);
1843  throw new InvalidArgumentException('Missing parameter tree id');
1844  }
1845 
1846  $query = 'DELETE FROM ' . $this->table_tree .
1847  ' WHERE ' . $this->tree_pk . ' = %s ';
1848  $ilDB->manipulateF($query, array('integer'), array($a_tree_id));
1849  return true;
1850  }
1851 
1860  public function moveToTrash($a_node_id, $a_set_deleted = false, $a_deleted_by = 0)
1861  {
1862  global $DIC;
1863 
1864  $ilDB = $DIC->database();
1865  $user = $DIC->user();
1866  if(!$a_deleted_by) {
1867  $a_deleted_by = $user->getId();
1868  }
1869 
1870  if (!$a_node_id) {
1871  $this->log->logStack(ilLogLevel::ERROR);
1872  throw new InvalidArgumentException('No valid parameter given! $a_node_id: ' . $a_node_id);
1873  }
1874 
1875 
1876  $query = $this->getTreeImplementation()->getSubTreeQuery($this->getNodeTreeData($a_node_id), '', false);
1877  $res = $ilDB->query($query);
1878 
1879  $subnodes = array();
1880  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
1881  $subnodes[] = $row['child'];
1882  }
1883 
1884  if (!count($subnodes)) {
1885  // Possibly already deleted
1886  return false;
1887  }
1888 
1889  if ($a_set_deleted) {
1890  ilObject::setDeletedDates($subnodes, $a_deleted_by);
1891  }
1892 
1893  // netsted set <=> mp
1894  $this->getTreeImplementation()->moveToTrash($a_node_id);
1895 
1896  return true;
1897  }
1898 
1903  public function isDeleted($a_node_id)
1904  {
1905  return $this->isSaved($a_node_id);
1906  }
1907 
1913  public function isSaved($a_node_id)
1914  {
1915  global $DIC;
1916 
1917  $ilDB = $DIC['ilDB'];
1918 
1919  // is saved cache
1920  if ($this->isCacheUsed() && isset($this->is_saved_cache[$a_node_id])) {
1921  //echo "<br>issavedhit";
1922  return $this->is_saved_cache[$a_node_id];
1923  }
1924 
1925  $query = 'SELECT ' . $this->tree_pk . ' FROM ' . $this->table_tree . ' ' .
1926  'WHERE child = %s ';
1927  $res = $ilDB->queryF($query, array('integer'), array($a_node_id));
1928  $row = $ilDB->fetchAssoc($res);
1929 
1930  if ($row[$this->tree_pk] < 0) {
1931  if ($this->__isMainTree()) {
1932  $this->is_saved_cache[$a_node_id] = true;
1933  }
1934  return true;
1935  } else {
1936  if ($this->__isMainTree()) {
1937  $this->is_saved_cache[$a_node_id] = false;
1938  }
1939  return false;
1940  }
1941  }
1942 
1949  public function preloadDeleted($a_node_ids)
1950  {
1951  global $DIC;
1952 
1953  $ilDB = $DIC['ilDB'];
1954 
1955  if (!is_array($a_node_ids) || !$this->isCacheUsed()) {
1956  return;
1957  }
1958 
1959  $query = 'SELECT ' . $this->tree_pk . ', child FROM ' . $this->table_tree . ' ' .
1960  'WHERE ' . $ilDB->in("child", $a_node_ids, false, "integer");
1961 
1962  $res = $ilDB->query($query);
1963  while ($row = $ilDB->fetchAssoc($res)) {
1964  if ($row[$this->tree_pk] < 0) {
1965  if ($this->__isMainTree()) {
1966  $this->is_saved_cache[$row["child"]] = true;
1967  }
1968  } else {
1969  if ($this->__isMainTree()) {
1970  $this->is_saved_cache[$row["child"]] = false;
1971  }
1972  }
1973  }
1974  }
1975 
1976 
1984  public function getSavedNodeData($a_parent_id)
1985  {
1986  global $DIC;
1987 
1988  $ilDB = $DIC['ilDB'];
1989 
1990  if (!isset($a_parent_id)) {
1991  $message = "No node_id given!";
1992  $this->log->error($message);
1994  }
1995 
1996  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1997  $this->buildJoin() .
1998  'WHERE ' . $this->table_tree . '.' . $this->tree_pk . ' < %s ' .
1999  'AND ' . $this->table_tree . '.parent = %s';
2000  $res = $ilDB->queryF($query, array('integer','integer'), array(
2001  0,
2002  $a_parent_id));
2003 
2004  while ($row = $ilDB->fetchAssoc($res)) {
2005  $saved[] = $this->fetchNodeData($row);
2006  }
2007 
2008  return $saved ? $saved : array();
2009  }
2010 
2017  public function getSavedNodeObjIds(array $a_obj_ids)
2018  {
2019  global $DIC;
2020 
2021  $ilDB = $DIC['ilDB'];
2022 
2023  $query = 'SELECT ' . $this->table_obj_data . '.obj_id FROM ' . $this->table_tree . ' ' .
2024  $this->buildJoin() .
2025  'WHERE ' . $this->table_tree . '.' . $this->tree_pk . ' < ' . $ilDB->quote(0, 'integer') . ' ' .
2026  'AND ' . $ilDB->in($this->table_obj_data . '.obj_id', $a_obj_ids, '', 'integer');
2027  $res = $ilDB->query($query);
2028  while ($row = $ilDB->fetchAssoc($res)) {
2029  $saved[] = $row['obj_id'];
2030  }
2031 
2032  return $saved ? $saved : array();
2033  }
2034 
2042  public function getParentId($a_node_id)
2043  {
2044  global $DIC;
2045 
2046  $ilDB = $DIC['ilDB'];
2047 
2048  if (!isset($a_node_id)) {
2049  $message = "No node_id given!";
2050  $this->log->error($message);
2052  }
2053 
2054  if ($this->__isMainTree()) {
2055  $query = 'SELECT parent FROM ' . $this->table_tree . ' ' .
2056  'WHERE child = %s ';
2057  $res = $ilDB->queryF(
2058  $query,
2059  ['integer'],
2060  [$a_node_id]
2061  );
2062  } else {
2063  $query = 'SELECT parent FROM ' . $this->table_tree . ' ' .
2064  'WHERE child = %s ' .
2065  'AND ' . $this->tree_pk . ' = %s ';
2066  $res = $ilDB->queryF($query, array('integer','integer'), array(
2067  $a_node_id,
2068  $this->tree_id));
2069  }
2070 
2071  $row = $ilDB->fetchObject($res);
2072  return $row->parent;
2073  }
2074 
2082  public function getLeftValue($a_node_id)
2083  {
2084  global $DIC;
2085 
2086  $ilDB = $DIC['ilDB'];
2087 
2088  if (!isset($a_node_id)) {
2089  $message = "No node_id given!";
2090  $this->log->error($message);
2092  }
2093 
2094  $query = 'SELECT lft FROM ' . $this->table_tree . ' ' .
2095  'WHERE child = %s ' .
2096  'AND ' . $this->tree_pk . ' = %s ';
2097  $res = $ilDB->queryF($query, array('integer','integer'), array(
2098  $a_node_id,
2099  $this->tree_id));
2100  $row = $ilDB->fetchObject($res);
2101  return $row->lft;
2102  }
2103 
2111  public function getChildSequenceNumber($a_node, $type = "")
2112  {
2113  global $DIC;
2114 
2115  $ilDB = $DIC['ilDB'];
2116 
2117  if (!isset($a_node)) {
2118  $message = "No node_id given!";
2119  $this->log->error($message);
2121  }
2122 
2123  if ($type) {
2124  $query = 'SELECT count(*) cnt FROM ' . $this->table_tree . ' ' .
2125  $this->buildJoin() .
2126  'WHERE lft <= %s ' .
2127  'AND type = %s ' .
2128  'AND parent = %s ' .
2129  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
2130 
2131  $res = $ilDB->queryF($query, array('integer','text','integer','integer'), array(
2132  $a_node['lft'],
2133  $type,
2134  $a_node['parent'],
2135  $this->tree_id));
2136  } else {
2137  $query = 'SELECT count(*) cnt FROM ' . $this->table_tree . ' ' .
2138  $this->buildJoin() .
2139  'WHERE lft <= %s ' .
2140  'AND parent = %s ' .
2141  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
2142 
2143  $res = $ilDB->queryF($query, array('integer','integer','integer'), array(
2144  $a_node['lft'],
2145  $a_node['parent'],
2146  $this->tree_id));
2147  }
2148  $row = $ilDB->fetchAssoc($res);
2149  return $row["cnt"];
2150  }
2151 
2158  public function readRootId()
2159  {
2160  global $DIC;
2161 
2162  $ilDB = $DIC['ilDB'];
2163 
2164  $query = 'SELECT child FROM ' . $this->table_tree . ' ' .
2165  'WHERE parent = %s ' .
2166  'AND ' . $this->tree_pk . ' = %s ';
2167  $res = $ilDB->queryF($query, array('integer','integer'), array(
2168  0,
2169  $this->tree_id));
2170  $row = $ilDB->fetchObject($res);
2171  $this->root_id = $row->child;
2172  return $this->root_id;
2173  }
2174 
2180  public function getRootId()
2181  {
2182  return $this->root_id;
2183  }
2184  public function setRootId($a_root_id)
2185  {
2186  $this->root_id = $a_root_id;
2187  }
2188 
2194  public function getTreeId()
2195  {
2196  return $this->tree_id;
2197  }
2198 
2204  public function setTreeId($a_tree_id)
2205  {
2206  $this->tree_id = $a_tree_id;
2207  }
2208 
2217  public function fetchSuccessorNode($a_node_id, $a_type = "")
2218  {
2219  global $DIC;
2220 
2221  $ilDB = $DIC['ilDB'];
2222 
2223  if (!isset($a_node_id)) {
2224  $message = "No node_id given!";
2225  $this->log->error($message);
2227  }
2228 
2229  // get lft value for current node
2230  $query = 'SELECT lft FROM ' . $this->table_tree . ' ' .
2231  'WHERE ' . $this->table_tree . '.child = %s ' .
2232  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
2233  $res = $ilDB->queryF($query, array('integer','integer'), array(
2234  $a_node_id,
2235  $this->tree_id));
2236  $curr_node = $ilDB->fetchAssoc($res);
2237 
2238  if ($a_type) {
2239  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2240  $this->buildJoin() .
2241  'WHERE lft > %s ' .
2242  'AND ' . $this->table_obj_data . '.type = %s ' .
2243  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ' .
2244  'ORDER BY lft ';
2245  $ilDB->setLimit(1);
2246  $res = $ilDB->queryF($query, array('integer','text','integer'), array(
2247  $curr_node['lft'],
2248  $a_type,
2249  $this->tree_id));
2250  } else {
2251  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2252  $this->buildJoin() .
2253  'WHERE lft > %s ' .
2254  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ' .
2255  'ORDER BY lft ';
2256  $ilDB->setLimit(1);
2257  $res = $ilDB->queryF($query, array('integer','integer'), array(
2258  $curr_node['lft'],
2259  $this->tree_id));
2260  }
2261 
2262  if ($res->numRows() < 1) {
2263  return false;
2264  } else {
2265  $row = $ilDB->fetchAssoc($res);
2266  return $this->fetchNodeData($row);
2267  }
2268  }
2269 
2278  public function fetchPredecessorNode($a_node_id, $a_type = "")
2279  {
2280  global $DIC;
2281 
2282  $ilDB = $DIC['ilDB'];
2283 
2284  if (!isset($a_node_id)) {
2285  $message = "No node_id given!";
2286  $this->log->error($message);
2288  }
2289 
2290  // get lft value for current node
2291  $query = 'SELECT lft FROM ' . $this->table_tree . ' ' .
2292  'WHERE ' . $this->table_tree . '.child = %s ' .
2293  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
2294  $res = $ilDB->queryF($query, array('integer','integer'), array(
2295  $a_node_id,
2296  $this->tree_id));
2297 
2298  $curr_node = $ilDB->fetchAssoc($res);
2299 
2300  if ($a_type) {
2301  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2302  $this->buildJoin() .
2303  'WHERE lft < %s ' .
2304  'AND ' . $this->table_obj_data . '.type = %s ' .
2305  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ' .
2306  'ORDER BY lft DESC';
2307  $ilDB->setLimit(1);
2308  $res = $ilDB->queryF($query, array('integer','text','integer'), array(
2309  $curr_node['lft'],
2310  $a_type,
2311  $this->tree_id));
2312  } else {
2313  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2314  $this->buildJoin() .
2315  'WHERE lft < %s ' .
2316  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ' .
2317  'ORDER BY lft DESC';
2318  $ilDB->setLimit(1);
2319  $res = $ilDB->queryF($query, array('integer','integer'), array(
2320  $curr_node['lft'],
2321  $this->tree_id));
2322  }
2323 
2324  if ($res->numRows() < 1) {
2325  return false;
2326  } else {
2327  $row = $ilDB->fetchAssoc($res);
2328  return $this->fetchNodeData($row);
2329  }
2330  }
2331 
2340  public function renumber($node_id = 1, $i = 1)
2341  {
2342  global $DIC;
2343 
2344  $ilDB = $DIC['ilDB'];
2345 
2346  $renumber_callable = function (ilDBInterface $ilDB) use ($node_id,$i,&$return) {
2347  $return = $this->__renumber($node_id, $i);
2348  };
2349 
2350  // LOCKED ###################################
2351  if ($this->__isMainTree()) {
2352  $ilAtomQuery = $ilDB->buildAtomQuery();
2353  $ilAtomQuery->addTableLock($this->table_tree);
2354 
2355  $ilAtomQuery->addQueryCallable($renumber_callable);
2356  $ilAtomQuery->run();
2357  } else {
2358  $renumber_callable($ilDB);
2359  }
2360  return $return;
2361  }
2362 
2363  // PRIVATE
2373  public function __renumber($node_id = 1, $i = 1)
2374  {
2375  global $DIC;
2376 
2377  $ilDB = $DIC['ilDB'];
2378 
2379  if ($this->isRepositoryTree()) {
2380  $query = 'UPDATE ' . $this->table_tree . ' SET lft = %s WHERE child = %s';
2381  $ilDB->manipulateF(
2382  $query,
2383  array('integer','integer'),
2384  array(
2385  $i,
2386  $node_id)
2387  );
2388  } else {
2389  $query = 'UPDATE ' . $this->table_tree . ' SET lft = %s WHERE child = %s AND tree = %s';
2390  $ilDB->manipulateF(
2391  $query,
2392  array('integer','integer','integer'),
2393  array(
2394  $i,
2395  $node_id,
2396  $this->tree_id)
2397  );
2398  }
2399 
2400  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2401  'WHERE parent = ' . $ilDB->quote($node_id, 'integer') . ' ' .
2402  'ORDER BY lft';
2403  $res = $ilDB->query($query);
2404 
2405  $childs = [];
2406  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
2407  $childs[] = $row->child;
2408  }
2409 
2410  foreach ($childs as $child) {
2411  $i = $this->__renumber($child, $i + 1);
2412  }
2413  $i++;
2414 
2415  // Insert a gap at the end of node, if the node has children
2416  if (count($childs) > 0) {
2417  $i += $this->gap * 2;
2418  }
2419 
2420 
2421  if ($this->isRepositoryTree()) {
2422  $query = 'UPDATE ' . $this->table_tree . ' SET rgt = %s WHERE child = %s';
2423  $res = $ilDB->manipulateF(
2424  $query,
2425  array('integer','integer'),
2426  array(
2427  $i,
2428  $node_id)
2429  );
2430  } else {
2431  $query = 'UPDATE ' . $this->table_tree . ' SET rgt = %s WHERE child = %s AND tree = %s';
2432  $res = $ilDB->manipulateF($query, array('integer','integer', 'integer'), array(
2433  $i,
2434  $node_id,
2435  $this->tree_id));
2436  }
2437  return $i;
2438  }
2439 
2440 
2451  public function checkForParentType($a_ref_id, $a_type, $a_exclude_source_check = false)
2452  {
2453  // #12577
2454  $cache_key = $a_ref_id . '.' . $a_type . '.' . ((int) $a_exclude_source_check);
2455 
2456  // Try to return a cached result
2457  if ($this->isCacheUsed() &&
2458  array_key_exists($cache_key, $this->parent_type_cache)) {
2459  return $this->parent_type_cache[$cache_key];
2460  }
2461 
2462  // Store up to 1000 results in cache
2463  $do_cache = ($this->__isMainTree() && count($this->parent_type_cache) < 1000);
2464 
2465  // ref_id is not in tree
2466  if (!$this->isInTree($a_ref_id)) {
2467  if ($do_cache) {
2468  $this->parent_type_cache[$cache_key] = false;
2469  }
2470  return false;
2471  }
2472 
2473  $path = array_reverse($this->getPathFull($a_ref_id));
2474 
2475  // remove first path entry as it is requested node
2476  if ($a_exclude_source_check) {
2477  array_shift($path);
2478  }
2479 
2480  foreach ($path as $node) {
2481  // found matching parent
2482  if ($node["type"] == $a_type) {
2483  if ($do_cache) {
2484  $this->parent_type_cache[$cache_key] = $node["child"];
2485  }
2486  return $node["child"];
2487  }
2488  }
2489 
2490  if ($do_cache) {
2491  $this->parent_type_cache[$cache_key] = false;
2492  }
2493  return 0;
2494  }
2495 
2506  public static function _removeEntry($a_tree, $a_child, $a_db_table = "tree")
2507  {
2508  global $DIC;
2509 
2510  $ilDB = $DIC['ilDB'];
2511 
2512  if ($a_db_table === 'tree') {
2513  if ($a_tree == 1 and $a_child == ROOT_FOLDER_ID) {
2514  $message = sprintf(
2515  'Tried to delete root node! $a_tree: %s $a_child: %s',
2516  $a_tree,
2517  $a_child
2518  );
2519  ilLoggerFactory::getLogger('tree')->error($message);
2521  }
2522  }
2523 
2524  $query = 'DELETE FROM ' . $a_db_table . ' ' .
2525  'WHERE tree = %s ' .
2526  'AND child = %s ';
2527  $res = $ilDB->manipulateF($query, array('integer','integer'), array(
2528  $a_tree,
2529  $a_child));
2530  }
2531 
2538  public function __isMainTree()
2539  {
2540  return $this->table_tree === 'tree';
2541  }
2542 
2554  public function __checkDelete($a_node)
2555  {
2556  global $DIC;
2557 
2558  $ilDB = $DIC['ilDB'];
2559 
2560 
2561  $query = $this->getTreeImplementation()->getSubTreeQuery($a_node, array(), false);
2562  $this->log->debug($query);
2563  $res = $ilDB->query($query);
2564 
2565  $counter = (int) $lft_childs = array();
2566  while ($row = $ilDB->fetchObject($res)) {
2567  $lft_childs[$row->child] = $row->parent;
2568  ++$counter;
2569  }
2570 
2571  // CHECK FOR DUPLICATE CHILD IDS
2572  if ($counter != count($lft_childs)) {
2573  $message = 'Duplicate entries for "child" in maintree! $a_node_id: ' . $a_node['child'];
2574 
2575  $this->log->error($message);
2577  }
2578 
2579  // GET SUBTREE BY PARENT RELATION
2580  $parent_childs = array();
2581  $this->__getSubTreeByParentRelation($a_node['child'], $parent_childs);
2582  $this->__validateSubtrees($lft_childs, $parent_childs);
2583 
2584  return true;
2585  }
2586 
2596  public function __getSubTreeByParentRelation($a_node_id, &$parent_childs)
2597  {
2598  global $DIC;
2599 
2600  $ilDB = $DIC['ilDB'];
2601 
2602  // GET PARENT ID
2603  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2604  'WHERE child = %s ' .
2605  'AND tree = %s ';
2606  $res = $ilDB->queryF($query, array('integer','integer'), array(
2607  $a_node_id,
2608  $this->tree_id));
2609 
2610  $counter = 0;
2611  while ($row = $ilDB->fetchObject($res)) {
2612  $parent_childs[$a_node_id] = $row->parent;
2613  ++$counter;
2614  }
2615  // MULTIPLE ENTRIES
2616  if ($counter > 1) {
2617  $message = 'Multiple entries in maintree! $a_node_id: ' . $a_node_id;
2618 
2619  $this->log->error($message);
2621  }
2622 
2623  // GET ALL CHILDS
2624  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2625  'WHERE parent = %s ';
2626  $res = $ilDB->queryF($query, array('integer'), array($a_node_id));
2627 
2628  while ($row = $ilDB->fetchObject($res)) {
2629  // RECURSION
2630  $this->__getSubTreeByParentRelation($row->child, $parent_childs);
2631  }
2632  return true;
2633  }
2634 
2642  public function __validateSubtrees(&$lft_childs, $parent_childs)
2643  {
2644  // SORT BY KEY
2645  ksort($lft_childs);
2646  ksort($parent_childs);
2647 
2648  $this->log->debug('left childs ' . print_r($lft_childs, true));
2649  $this->log->debug('parent childs ' . print_r($parent_childs, true));
2650 
2651  if (count($lft_childs) != count($parent_childs)) {
2652  $message = '(COUNT) Tree is corrupted! Left/Right subtree does not comply with parent relation';
2653  $this->log->error($message);
2655  }
2656 
2657 
2658  foreach ($lft_childs as $key => $value) {
2659  if ($parent_childs[$key] != $value) {
2660  $message = '(COMPARE) Tree is corrupted! Left/Right subtree does not comply with parent relation';
2661  $this->log->error($message);
2663  }
2664  if ($key == ROOT_FOLDER_ID) {
2665  $message = '(ROOT_FOLDER) Tree is corrupted! Tried to delete root folder';
2666  $this->log->error($message);
2668  }
2669  }
2670  return true;
2671  }
2672 
2682  public function moveTree($a_source_id, $a_target_id, $a_location = self::POS_LAST_NODE)
2683  {
2684  $old_parent_id = $this->getParentId($a_source_id);
2685  $this->getTreeImplementation()->moveTree($a_source_id, $a_target_id, $a_location);
2686  if (isset($GLOBALS['DIC']["ilAppEventHandler"]) && $this->__isMainTree()) {
2687  $GLOBALS['DIC']['ilAppEventHandler']->raise(
2688  "Services/Tree",
2689  "moveTree",
2690  array(
2691  'tree' => $this->table_tree,
2692  'source_id' => $a_source_id,
2693  'target_id' => $a_target_id,
2694  'old_parent_id' => $old_parent_id
2695  )
2696  );
2697  }
2698  return true;
2699  }
2700 
2701 
2702 
2703 
2711  public function getRbacSubtreeInfo($a_endnode_id)
2712  {
2713  return $this->getTreeImplementation()->getSubtreeInfo($a_endnode_id);
2714  }
2715 
2716 
2724  public function getSubTreeQuery($a_node_id, $a_fields = array(), $a_types = '', $a_force_join_reference = false)
2725  {
2726  return $this->getTreeImplementation()->getSubTreeQuery(
2727  $this->getNodeTreeData($a_node_id),
2728  $a_types,
2729  $a_force_join_reference,
2730  $a_fields
2731  );
2732  }
2733 
2737  public function getTrashSubTreeQuery($a_node_id, $a_fields = [], $a_types = '', $a_force_join_reference = false)
2738  {
2739  return $this->getTreeImplementation()->getTrashSubTreeQuery(
2740  $this->getNodeTreeData($a_node_id),
2741  $a_types,
2742  $a_force_join_reference,
2743  $a_fields
2744  );
2745  }
2746 
2747 
2756  public function getSubTreeFilteredByObjIds($a_node_id, array $a_obj_ids, array $a_fields = array())
2757  {
2758  global $DIC;
2759 
2760  $ilDB = $DIC['ilDB'];
2761 
2762  $node = $this->getNodeData($a_node_id);
2763  if (!sizeof($node)) {
2764  return;
2765  }
2766 
2767  $res = array();
2768 
2769  $query = $this->getTreeImplementation()->getSubTreeQuery($node, '', true, array($this->ref_pk));
2770 
2771  $fields = '*';
2772  if (count($a_fields)) {
2773  $fields = implode(',', $a_fields);
2774  }
2775 
2776  $query = "SELECT " . $fields .
2777  " FROM " . $this->getTreeTable() .
2778  " " . $this->buildJoin() .
2779  " WHERE " . $this->getTableReference() . "." . $this->ref_pk . " IN (" . $query . ")" .
2780  " AND " . $ilDB->in($this->getObjectDataTable() . "." . $this->obj_pk, $a_obj_ids, "", "integer");
2781  $set = $ilDB->query($query);
2782  while ($row = $ilDB->fetchAssoc($set)) {
2783  $res[] = $row;
2784  }
2785 
2786  return $res;
2787  }
2788 
2789  public function deleteNode($a_tree_id, $a_node_id)
2790  {
2791  global $DIC;
2792 
2793  $ilDB = $DIC['ilDB'];
2794  $ilAppEventHandler = $DIC['ilAppEventHandler'];
2795 
2796  $query = 'DELETE FROM tree where ' .
2797  'child = ' . $ilDB->quote($a_node_id, 'integer') . ' ' .
2798  'AND tree = ' . $ilDB->quote($a_tree_id, 'integer');
2799  $ilDB->manipulate($query);
2800 
2801  $ilAppEventHandler->raise(
2802  "Services/Tree",
2803  "deleteNode",
2804  array('tree' => $this->table_tree,
2805  'node_id' => $a_node_id,
2806  'tree_id' => $a_tree_id
2807  )
2808  );
2809  }
2810 
2816  public function lookupTrashedObjectTypes()
2817  {
2818  global $DIC;
2819 
2820  $ilDB = $DIC['ilDB'];
2821 
2822  $query = 'SELECT DISTINCT(o.type) ' . $ilDB->quoteIdentifier('type') . ' FROM tree t JOIN object_reference r ON child = r.ref_id ' .
2823  'JOIN object_data o on r.obj_id = o.obj_id ' .
2824  'WHERE tree < ' . $ilDB->quote(0, 'integer') . ' ' .
2825  'AND child = -tree ' .
2826  'GROUP BY o.type';
2827  $res = $ilDB->query($query);
2828 
2829  $types_deleted = array();
2830  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
2831  $types_deleted[] = $row->type;
2832  }
2833  return $types_deleted;
2834  }
2835 
2839  public function isRepositoryTree()
2840  {
2841  if ($this->table_tree == 'tree') {
2842  return true;
2843  }
2844  return false;
2845  }
2846 } // END class.tree
isRepositoryTree()
check if current tree instance operates on repository tree table
removeTree($a_tree_id)
remove an existing tree
Thrown if invalid tree strucutes are found.
static _resetDeletedDate($a_ref_id)
only called in ilObjectGUI::insertSavedNodes
getSubTreeFilteredByObjIds($a_node_id, array $a_obj_ids, array $a_fields=array())
get all node ids in the subtree under specified node id, filter by object ids
initTreeImplementation()
Init tree implementation.
getChilds($a_node_id, $a_order="", $a_direction="ASC")
get child nodes of given node public
static shortenText( $a_str, $a_len, $a_dots=false, $a_next_blank=false, $a_keep_extension=false)
shorten a string to given length.
$data
Definition: storeScorm.php:23
preloadDeleted($a_node_ids)
Preload deleted information.
$type
const ROOT_FOLDER_ID
Definition: constants.php:30
isGrandChild($a_startnode_id, $a_querynode_id)
checks if a node is in the path of an other node public
isDeleted($a_node_id)
This is a wrapper for isSaved() with a more useful name.
renumber($node_id=1, $i=1)
Wrapper for renumber.
static setDeletedDates($a_ref_ids, $a_user_id)
Set deleted date.
getFilteredChilds($a_filter, $a_node, $a_order="", $a_direction="ASC")
get child nodes of given node (exclude filtered obj_types) public
getSavedNodeObjIds(array $a_obj_ids)
get object id of saved/deleted nodes
fetchNodeData($a_row)
get data of parent node from tree and object_data private
static _removeEntry($a_tree, $a_child, $a_db_table="tree")
STATIC METHOD Removes a single entry from a tree.
getNodeTreeData($a_node_id)
return all columns of tabel tree
getRelation($a_node_a, $a_node_b)
Get relation of two nodes.
const DESC_LENGTH
fetchPredecessorNode($a_node_id, $a_type="")
get node data of predecessor node
getTreeId()
get tree id public
getParentCache()
Get parent cache.
buildJoin()
build join depending on table settings private
getChildsByType($a_node_id, $a_type)
get child nodes of given node by object type public
getDepth($a_node_id)
return depth of a node in tree private
deleteTree($a_node)
delete node and the whole subtree under this node public
setObjectTablePK($a_column_name)
set column containing primary key in object table public
const RELATION_PARENT
static lookupTreesForNode(int $node_id)
getPathId($a_endnode_id, $a_startnode_id=0)
get path from a given startnode to a given endnode if startnode is not given the rootnode is startnod...
static strToLower($a_string)
Definition: class.ilStr.php:87
getTreePk()
Get tree primary key.
__validateSubtrees(&$lft_childs, $parent_childs)
Base class for nested set path based trees.
deleteNode($a_tree_id, $a_node_id)
getSubTreeTypes($a_node, $a_filter=0)
get types of nodes in the subtree under specified node
const TREE_TYPE_MATERIALIZED_PATH
__construct($a_tree_id, $a_root_id=0)
Constructor public.
getChildsByTypeFilter($a_node_id, $a_types, $a_order="", $a_direction="ASC")
get child nodes of given node by object type public
getFilteredSubTree($a_node_id, $a_filter=array())
get filtered subtree
getObjectDataTable()
Get object data table.
validateParentRelations()
Validate parent relations of tree.
__checkDelete($a_node)
Check for deleteTree() compares a subtree of a given node by checking lft, rgt against parent relatio...
static toNFC($string)
Convert a UTF-8 string to normal form C, canonical composition.
Definition: UtfNormal.php:159
const POS_FIRST_NODE
getPathFull($a_endnode_id, $a_startnode_id=0)
get path from a given startnode to a given endnode if startnode is not given the rootnode is startnod...
foreach($_POST as $key=> $value) $res
getLeftValue($a_node_id)
get left value of given node public
static _lookupTitle($a_obj_id)
Overwitten from base class.
$lng
getTreeTable()
Get tree table name.
checkTree()
check consistence of tree all left & right values are checked if they are exists only once public ...
getRootId()
get the root id of tree public
setTreeId($a_tree_id)
set tree id public
getNodeData($a_node_id, $a_tree_pk=null)
get all information of a node.
getNodePath($a_endnode_id, $a_startnode_id=0)
Returns the node path for the specified object reference.
$table_obj_reference
global $DIC
Definition: goto.php:24
lookupTrashedObjectTypes()
Lookup object types in trash type $ilDB.
if(!defined('PATH_SEPARATOR')) $GLOBALS['_PEAR_default_error_mode']
Definition: PEAR.php:64
setRootId($a_root_id)
getParentId($a_node_id)
get parent id of given node public
moveToTrash($a_node_id, $a_set_deleted=false, $a_deleted_by=0)
Move node to trash bin.
getTableReference()
Get reference table if available.
$query
setTableNames($a_table_tree, $a_table_obj_data, $a_table_obj_reference="")
set table names The primary key of the table containing your object_data must be &#39;obj_id&#39; You may use...
resetInTreeCache()
const RELATION_EQUALS
insertNodeFromTrash($a_source_id, $a_target_id, $a_tree_id, $a_pos=IL_LAST_NODE, $a_reset_deleted_date=false)
Insert node from trash deletes trash entry.
const RELATION_CHILD
preloadDepthParent($a_node_ids)
Preload depth/parent.
const RELATION_NONE
__renumber($node_id=1, $i=1)
This method is private.
const ROLE_FOLDER_ID
Definition: constants.php:32
setTreeTablePK($a_column_name)
set column containing primary key in tree table public
setReferenceTablePK($a_column_name)
set column containing primary key in reference table public
$rows
Definition: xhr_table.php:10
__getSubTreeByParentRelation($a_node_id, &$parent_childs)
type $ilDB
getDepthCache()
Get depth cache.
const POS_LAST_NODE
static quoteArray($a_array)
Quotes all members of an array for usage in DB query statement.
initLangCode()
Store user language.
getNodePathForTitlePath($titlePath, $a_startnode_id=null)
Converts a path consisting of object titles into a path consisting of tree nodes. ...
const IL_LAST_NODE
Definition: class.ilTree.php:4
const TREE_TYPE_NESTED_SET
getChildIds($a_node)
Get node child ids type $ilDB.
getMaximumDepth()
Return the current maximum depth in the tree public.
insertNode($a_node_id, $a_parent_id, $a_pos=IL_LAST_NODE, $a_reset_deletion_date=false)
insert new node with node_id under parent node with parent_id public
getRbacSubtreeInfo($a_endnode_id)
This method is used for change existing objects and returns all necessary information for this action...
getGap()
Get default gap *.
fetchTranslationFromObjectDataCache($a_obj_ids)
Get translation data from object cache (trigger in object cache on preload)
global $ilBench
Definition: ilias.php:21
global $ilDB
getSubTreeQuery($a_node_id, $a_fields=array(), $a_types='', $a_force_join_reference=false)
Get tree subtree query.
Base class for materialize path based trees Based on implementation of Werner Randelshofer.
getParentNodeData($a_node_id)
get data of parent node from tree and object_data public
$message
Definition: xapiexit.php:14
getSubTree($a_node, $a_with_data=true, $a_type="")
get all nodes in the subtree under specified node
const RELATION_SIBLING
getNodeDataByType($a_type)
get nodes by type
getTrashSubTreeQuery($a_node_id, $a_fields=[], $a_types='', $a_force_join_reference=false)
static getLogger($a_component_id)
Get component logger.
isCacheUsed()
Check if cache is active.
$ilUser
Definition: imgupload.php:18
getSubTreeIds($a_ref_id)
Get all ids of subnodes.
getChildSequenceNumber($a_node, $type="")
get sequence number of node in sibling sequence public
getRelationOfNodes($a_node_a_arr, $a_node_b_arr)
get relation of two nodes by node data
fetchSuccessorNode($a_node_id, $a_type="")
get node data of successor node
useCache($a_use=true)
Use Cache (usually activated)
__isMainTree()
Check if operations are done on main tree.
getTreeImplementation()
Get tree implementation.
isSaved($a_node_id)
Use method isDeleted check if node is saved.
addTree($a_tree_id, $a_node_id=-1)
create a new tree to do: ???
checkTreeChilds($a_no_zero_child=true)
check, if all childs of tree nodes exist in object table
isInTree($a_node_id)
get all information of a node.
moveTree($a_source_id, $a_target_id, $a_location=self::POS_LAST_NODE)
Move Tree Implementation.
checkForParentType($a_ref_id, $a_type, $a_exclude_source_check=false)
Check for parent type e.g check if a folder (ref_id 3) is in a parent course obj => checkForParentTyp...
$i
Definition: metadata.php:24
getSavedNodeData($a_parent_id)
get data saved/deleted nodes
readRootId()
read root id from database