ILIAS  release_6 Revision v6.24-5-g0c8bfefb3b8
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  'AND NOT lang_default = %s';
1579 
1580  $res = $ilDB->queryF($query, array('integer','text','integer'), array(
1581  $data['obj_id'],
1582  $this->lang_code,
1583  1));
1584  $row = $ilDB->fetchObject($res);
1585 
1586  if ($row) {
1587  $data["title"] = $row->title;
1588  $data["description"] = ilUtil::shortenText($row->description, ilObject::DESC_LENGTH, true);
1589  $data["desc"] = $row->description;
1590  }
1591  //$ilBench->stop("Tree", "fetchNodeData_getTranslation");
1592 
1593  // Store up to 1000 object translations in cache
1594  if ($this->isCacheUsed() && count($this->translation_cache) < 1000) {
1595  $key = $data["obj_id"] . '.' . $lang_code;
1596  $this->translation_cache[$key] = array();
1597  $this->translation_cache[$key]['title'] = $data["title"] ;
1598  $this->translation_cache[$key]['description'] = $data["description"];
1599  $this->translation_cache[$key]['desc'] = $data["desc"];
1600  }
1601  }
1602  }
1603 
1604  // TODO: Handle this switch by module.xml definitions
1605  if ($data['type'] == 'crsr' or $data['type'] == 'catr' or $data['type'] == 'grpr' or $data['type'] === 'prgr') {
1606  include_once('./Services/ContainerReference/classes/class.ilContainerReference.php');
1607  $data['title'] = ilContainerReference::_lookupTitle($data['obj_id']);
1608  }
1609 
1610  return $data ? $data : array();
1611  }
1612 
1618  protected function fetchTranslationFromObjectDataCache($a_obj_ids)
1619  {
1620  global $DIC;
1621 
1622  $ilObjDataCache = $DIC['ilObjDataCache'];
1623 
1624  if ($this->isCacheUsed() && is_array($a_obj_ids) && is_object($ilObjDataCache)) {
1625  foreach ($a_obj_ids as $id) {
1626  $this->translation_cache[$id . '.']['title'] = $ilObjDataCache->lookupTitle($id);
1627  $this->translation_cache[$id . '.']['description'] = $ilObjDataCache->lookupDescription($id);
1628  ;
1629  $this->translation_cache[$id . '.']['desc'] =
1630  $this->translation_cache[$id . '.']['description'];
1631  }
1632  }
1633  }
1634 
1635 
1643  public function isInTree($a_node_id)
1644  {
1645  global $DIC;
1646 
1647  $ilDB = $DIC['ilDB'];
1648 
1649  if (!isset($a_node_id)) {
1650  return false;
1651  #$this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1652  }
1653  // is in tree cache
1654  if ($this->isCacheUsed() && isset($this->in_tree_cache[$a_node_id])) {
1655  #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Using in tree cache '.$a_node_id);
1656  //echo "<br>in_tree_hit";
1657  return $this->in_tree_cache[$a_node_id];
1658  }
1659 
1660  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1661  'WHERE ' . $this->table_tree . '.child = %s ' .
1662  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s';
1663 
1664  $res = $ilDB->queryF($query, array('integer','integer'), array(
1665  $a_node_id,
1666  $this->tree_id));
1667 
1668  if ($res->numRows() > 0) {
1669  if ($this->__isMainTree()) {
1670  #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
1671  $this->in_tree_cache[$a_node_id] = true;
1672  }
1673  return true;
1674  } else {
1675  if ($this->__isMainTree()) {
1676  #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = false');
1677  $this->in_tree_cache[$a_node_id] = false;
1678  }
1679  return false;
1680  }
1681  }
1682 
1690  public function getParentNodeData($a_node_id)
1691  {
1692  global $DIC;
1693 
1694  $ilDB = $DIC['ilDB'];
1695  global $DIC;
1696 
1697  $ilLog = $DIC['ilLog'];
1698 
1699  if (!isset($a_node_id)) {
1700  $ilLog->logStack();
1701  throw new InvalidArgumentException(__METHOD__ . ': No node_id given!');
1702  }
1703 
1704  if ($this->table_obj_reference) {
1705  // Use inner join instead of left join to improve performance
1706  $innerjoin = "JOIN " . $this->table_obj_reference . " ON v.child=" . $this->table_obj_reference . "." . $this->ref_pk . " " .
1707  "JOIN " . $this->table_obj_data . " ON " . $this->table_obj_reference . "." . $this->obj_pk . "=" . $this->table_obj_data . "." . $this->obj_pk . " ";
1708  } else {
1709  // Use inner join instead of left join to improve performance
1710  $innerjoin = "JOIN " . $this->table_obj_data . " ON v.child=" . $this->table_obj_data . "." . $this->obj_pk . " ";
1711  }
1712 
1713  $query = 'SELECT * FROM ' . $this->table_tree . ' s, ' . $this->table_tree . ' v ' .
1714  $innerjoin .
1715  'WHERE s.child = %s ' .
1716  'AND s.parent = v.child ' .
1717  'AND s.' . $this->tree_pk . ' = %s ' .
1718  'AND v.' . $this->tree_pk . ' = %s';
1719  $res = $ilDB->queryF($query, array('integer','integer','integer'), array(
1720  $a_node_id,
1721  $this->tree_id,
1722  $this->tree_id));
1723  $row = $ilDB->fetchAssoc($res);
1724  return $this->fetchNodeData($row);
1725  }
1726 
1734  public function isGrandChild($a_startnode_id, $a_querynode_id)
1735  {
1736  return $this->getRelation($a_startnode_id, $a_querynode_id) == self::RELATION_PARENT;
1737  }
1738 
1748  public function addTree($a_tree_id, $a_node_id = -1)
1749  {
1750  global $DIC;
1751 
1752  $ilDB = $DIC['ilDB'];
1753 
1754  // FOR SECURITY addTree() IS NOT ALLOWED ON MAIN TREE
1755  if ($this->__isMainTree()) {
1756  $message = sprintf(
1757  'Operation not allowed on main tree! $a_tree_if: %s $a_node_id: %s',
1758  $a_tree_id,
1759  $a_node_id
1760  );
1761  $this->log->error($message);
1763  }
1764 
1765  if (!isset($a_tree_id)) {
1766  $message = "No tree_id given!";
1767  $this->log->error($message);
1769  }
1770 
1771  if ($a_node_id <= 0) {
1772  $a_node_id = $a_tree_id;
1773  }
1774 
1775  $query = 'INSERT INTO ' . $this->table_tree . ' (' .
1776  $this->tree_pk . ', child,parent,lft,rgt,depth) ' .
1777  'VALUES ' .
1778  '(%s,%s,%s,%s,%s,%s)';
1779  $res = $ilDB->manipulateF($query, array('integer','integer','integer','integer','integer','integer'), array(
1780  $a_tree_id,
1781  $a_node_id,
1782  0,
1783  1,
1784  2,
1785  1));
1786 
1787  return true;
1788  }
1789 
1799  public function getNodeDataByType($a_type)
1800  {
1801  global $DIC;
1802 
1803  $ilDB = $DIC['ilDB'];
1804 
1805  if (!isset($a_type) or (!is_string($a_type))) {
1806  $this->log->logStack(ilLogLevel::ERROR);
1807  throw new InvalidArgumentException('Type not given or wrong datatype');
1808  }
1809 
1810  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1811  $this->buildJoin() .
1812  'WHERE ' . $this->table_obj_data . '.type = ' . $this->ilDB->quote($a_type, 'text') .
1813  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = ' . $this->ilDB->quote($this->tree_id, 'integer');
1814 
1815  $res = $ilDB->query($query);
1816  $data = array();
1817  while ($row = $ilDB->fetchAssoc($res)) {
1818  $data[] = $this->fetchNodeData($row);
1819  }
1820 
1821  return $data;
1822  }
1823 
1832  public function removeTree($a_tree_id)
1833  {
1834  global $DIC;
1835 
1836  $ilDB = $DIC['ilDB'];
1837 
1838  // OPERATION NOT ALLOWED ON MAIN TREE
1839  if ($this->__isMainTree()) {
1840  $this->log->logStack(ilLogLevel::ERROR);
1841  throw new InvalidArgumentException('Operation not allowed on main tree');
1842  }
1843  if (!$a_tree_id) {
1844  $this->log->logStack(ilLogLevel::ERROR);
1845  throw new InvalidArgumentException('Missing parameter tree id');
1846  }
1847 
1848  $query = 'DELETE FROM ' . $this->table_tree .
1849  ' WHERE ' . $this->tree_pk . ' = %s ';
1850  $ilDB->manipulateF($query, array('integer'), array($a_tree_id));
1851  return true;
1852  }
1853 
1862  public function moveToTrash($a_node_id, $a_set_deleted = false, $a_deleted_by = 0)
1863  {
1864  global $DIC;
1865 
1866  $ilDB = $DIC->database();
1867  $user = $DIC->user();
1868  if(!$a_deleted_by) {
1869  $a_deleted_by = $user->getId();
1870  }
1871 
1872  if (!$a_node_id) {
1873  $this->log->logStack(ilLogLevel::ERROR);
1874  throw new InvalidArgumentException('No valid parameter given! $a_node_id: ' . $a_node_id);
1875  }
1876 
1877 
1878  $query = $this->getTreeImplementation()->getSubTreeQuery($this->getNodeTreeData($a_node_id), '', false);
1879  $res = $ilDB->query($query);
1880 
1881  $subnodes = array();
1882  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
1883  $subnodes[] = $row['child'];
1884  }
1885 
1886  if (!count($subnodes)) {
1887  // Possibly already deleted
1888  return false;
1889  }
1890 
1891  if ($a_set_deleted) {
1892  ilObject::setDeletedDates($subnodes, $a_deleted_by);
1893  }
1894 
1895  // netsted set <=> mp
1896  $this->getTreeImplementation()->moveToTrash($a_node_id);
1897 
1898  return true;
1899  }
1900 
1905  public function isDeleted($a_node_id)
1906  {
1907  return $this->isSaved($a_node_id);
1908  }
1909 
1915  public function isSaved($a_node_id)
1916  {
1917  global $DIC;
1918 
1919  $ilDB = $DIC['ilDB'];
1920 
1921  // is saved cache
1922  if ($this->isCacheUsed() && isset($this->is_saved_cache[$a_node_id])) {
1923  //echo "<br>issavedhit";
1924  return $this->is_saved_cache[$a_node_id];
1925  }
1926 
1927  $query = 'SELECT ' . $this->tree_pk . ' FROM ' . $this->table_tree . ' ' .
1928  'WHERE child = %s ';
1929  $res = $ilDB->queryF($query, array('integer'), array($a_node_id));
1930  $row = $ilDB->fetchAssoc($res);
1931 
1932  if ($row[$this->tree_pk] < 0) {
1933  if ($this->__isMainTree()) {
1934  $this->is_saved_cache[$a_node_id] = true;
1935  }
1936  return true;
1937  } else {
1938  if ($this->__isMainTree()) {
1939  $this->is_saved_cache[$a_node_id] = false;
1940  }
1941  return false;
1942  }
1943  }
1944 
1951  public function preloadDeleted($a_node_ids)
1952  {
1953  global $DIC;
1954 
1955  $ilDB = $DIC['ilDB'];
1956 
1957  if (!is_array($a_node_ids) || !$this->isCacheUsed()) {
1958  return;
1959  }
1960 
1961  $query = 'SELECT ' . $this->tree_pk . ', child FROM ' . $this->table_tree . ' ' .
1962  'WHERE ' . $ilDB->in("child", $a_node_ids, false, "integer");
1963 
1964  $res = $ilDB->query($query);
1965  while ($row = $ilDB->fetchAssoc($res)) {
1966  if ($row[$this->tree_pk] < 0) {
1967  if ($this->__isMainTree()) {
1968  $this->is_saved_cache[$row["child"]] = true;
1969  }
1970  } else {
1971  if ($this->__isMainTree()) {
1972  $this->is_saved_cache[$row["child"]] = false;
1973  }
1974  }
1975  }
1976  }
1977 
1978 
1986  public function getSavedNodeData($a_parent_id)
1987  {
1988  global $DIC;
1989 
1990  $ilDB = $DIC['ilDB'];
1991 
1992  if (!isset($a_parent_id)) {
1993  $message = "No node_id given!";
1994  $this->log->error($message);
1996  }
1997 
1998  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1999  $this->buildJoin() .
2000  'WHERE ' . $this->table_tree . '.' . $this->tree_pk . ' < %s ' .
2001  'AND ' . $this->table_tree . '.parent = %s';
2002  $res = $ilDB->queryF($query, array('integer','integer'), array(
2003  0,
2004  $a_parent_id));
2005 
2006  while ($row = $ilDB->fetchAssoc($res)) {
2007  $saved[] = $this->fetchNodeData($row);
2008  }
2009 
2010  return $saved ? $saved : array();
2011  }
2012 
2019  public function getSavedNodeObjIds(array $a_obj_ids)
2020  {
2021  global $DIC;
2022 
2023  $ilDB = $DIC['ilDB'];
2024 
2025  $query = 'SELECT ' . $this->table_obj_data . '.obj_id FROM ' . $this->table_tree . ' ' .
2026  $this->buildJoin() .
2027  'WHERE ' . $this->table_tree . '.' . $this->tree_pk . ' < ' . $ilDB->quote(0, 'integer') . ' ' .
2028  'AND ' . $ilDB->in($this->table_obj_data . '.obj_id', $a_obj_ids, '', 'integer');
2029  $res = $ilDB->query($query);
2030  while ($row = $ilDB->fetchAssoc($res)) {
2031  $saved[] = $row['obj_id'];
2032  }
2033 
2034  return $saved ? $saved : array();
2035  }
2036 
2044  public function getParentId($a_node_id)
2045  {
2046  global $DIC;
2047 
2048  $ilDB = $DIC['ilDB'];
2049 
2050  if (!isset($a_node_id)) {
2051  $message = "No node_id given!";
2052  $this->log->error($message);
2054  }
2055 
2056  if ($this->__isMainTree()) {
2057  $query = 'SELECT parent FROM ' . $this->table_tree . ' ' .
2058  'WHERE child = %s ';
2059  $res = $ilDB->queryF(
2060  $query,
2061  ['integer'],
2062  [$a_node_id]
2063  );
2064  } else {
2065  $query = 'SELECT parent FROM ' . $this->table_tree . ' ' .
2066  'WHERE child = %s ' .
2067  'AND ' . $this->tree_pk . ' = %s ';
2068  $res = $ilDB->queryF($query, array('integer','integer'), array(
2069  $a_node_id,
2070  $this->tree_id));
2071  }
2072 
2073  $row = $ilDB->fetchObject($res);
2074  return $row->parent;
2075  }
2076 
2084  public function getLeftValue($a_node_id)
2085  {
2086  global $DIC;
2087 
2088  $ilDB = $DIC['ilDB'];
2089 
2090  if (!isset($a_node_id)) {
2091  $message = "No node_id given!";
2092  $this->log->error($message);
2094  }
2095 
2096  $query = 'SELECT lft FROM ' . $this->table_tree . ' ' .
2097  'WHERE child = %s ' .
2098  'AND ' . $this->tree_pk . ' = %s ';
2099  $res = $ilDB->queryF($query, array('integer','integer'), array(
2100  $a_node_id,
2101  $this->tree_id));
2102  $row = $ilDB->fetchObject($res);
2103  return $row->lft;
2104  }
2105 
2113  public function getChildSequenceNumber($a_node, $type = "")
2114  {
2115  global $DIC;
2116 
2117  $ilDB = $DIC['ilDB'];
2118 
2119  if (!isset($a_node)) {
2120  $message = "No node_id given!";
2121  $this->log->error($message);
2123  }
2124 
2125  if ($type) {
2126  $query = 'SELECT count(*) cnt FROM ' . $this->table_tree . ' ' .
2127  $this->buildJoin() .
2128  'WHERE lft <= %s ' .
2129  'AND type = %s ' .
2130  'AND parent = %s ' .
2131  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
2132 
2133  $res = $ilDB->queryF($query, array('integer','text','integer','integer'), array(
2134  $a_node['lft'],
2135  $type,
2136  $a_node['parent'],
2137  $this->tree_id));
2138  } else {
2139  $query = 'SELECT count(*) cnt FROM ' . $this->table_tree . ' ' .
2140  $this->buildJoin() .
2141  'WHERE lft <= %s ' .
2142  'AND parent = %s ' .
2143  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
2144 
2145  $res = $ilDB->queryF($query, array('integer','integer','integer'), array(
2146  $a_node['lft'],
2147  $a_node['parent'],
2148  $this->tree_id));
2149  }
2150  $row = $ilDB->fetchAssoc($res);
2151  return $row["cnt"];
2152  }
2153 
2160  public function readRootId()
2161  {
2162  global $DIC;
2163 
2164  $ilDB = $DIC['ilDB'];
2165 
2166  $query = 'SELECT child FROM ' . $this->table_tree . ' ' .
2167  'WHERE parent = %s ' .
2168  'AND ' . $this->tree_pk . ' = %s ';
2169  $res = $ilDB->queryF($query, array('integer','integer'), array(
2170  0,
2171  $this->tree_id));
2172  $row = $ilDB->fetchObject($res);
2173  $this->root_id = $row->child;
2174  return $this->root_id;
2175  }
2176 
2182  public function getRootId()
2183  {
2184  return $this->root_id;
2185  }
2186  public function setRootId($a_root_id)
2187  {
2188  $this->root_id = $a_root_id;
2189  }
2190 
2196  public function getTreeId()
2197  {
2198  return $this->tree_id;
2199  }
2200 
2206  public function setTreeId($a_tree_id)
2207  {
2208  $this->tree_id = $a_tree_id;
2209  }
2210 
2219  public function fetchSuccessorNode($a_node_id, $a_type = "")
2220  {
2221  global $DIC;
2222 
2223  $ilDB = $DIC['ilDB'];
2224 
2225  if (!isset($a_node_id)) {
2226  $message = "No node_id given!";
2227  $this->log->error($message);
2229  }
2230 
2231  // get lft value for current node
2232  $query = 'SELECT lft FROM ' . $this->table_tree . ' ' .
2233  'WHERE ' . $this->table_tree . '.child = %s ' .
2234  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
2235  $res = $ilDB->queryF($query, array('integer','integer'), array(
2236  $a_node_id,
2237  $this->tree_id));
2238  $curr_node = $ilDB->fetchAssoc($res);
2239 
2240  if ($a_type) {
2241  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2242  $this->buildJoin() .
2243  'WHERE lft > %s ' .
2244  'AND ' . $this->table_obj_data . '.type = %s ' .
2245  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ' .
2246  'ORDER BY lft ';
2247  $ilDB->setLimit(1);
2248  $res = $ilDB->queryF($query, array('integer','text','integer'), array(
2249  $curr_node['lft'],
2250  $a_type,
2251  $this->tree_id));
2252  } else {
2253  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2254  $this->buildJoin() .
2255  'WHERE lft > %s ' .
2256  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ' .
2257  'ORDER BY lft ';
2258  $ilDB->setLimit(1);
2259  $res = $ilDB->queryF($query, array('integer','integer'), array(
2260  $curr_node['lft'],
2261  $this->tree_id));
2262  }
2263 
2264  if ($res->numRows() < 1) {
2265  return false;
2266  } else {
2267  $row = $ilDB->fetchAssoc($res);
2268  return $this->fetchNodeData($row);
2269  }
2270  }
2271 
2280  public function fetchPredecessorNode($a_node_id, $a_type = "")
2281  {
2282  global $DIC;
2283 
2284  $ilDB = $DIC['ilDB'];
2285 
2286  if (!isset($a_node_id)) {
2287  $message = "No node_id given!";
2288  $this->log->error($message);
2290  }
2291 
2292  // get lft value for current node
2293  $query = 'SELECT lft FROM ' . $this->table_tree . ' ' .
2294  'WHERE ' . $this->table_tree . '.child = %s ' .
2295  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
2296  $res = $ilDB->queryF($query, array('integer','integer'), array(
2297  $a_node_id,
2298  $this->tree_id));
2299 
2300  $curr_node = $ilDB->fetchAssoc($res);
2301 
2302  if ($a_type) {
2303  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2304  $this->buildJoin() .
2305  'WHERE lft < %s ' .
2306  'AND ' . $this->table_obj_data . '.type = %s ' .
2307  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ' .
2308  'ORDER BY lft DESC';
2309  $ilDB->setLimit(1);
2310  $res = $ilDB->queryF($query, array('integer','text','integer'), array(
2311  $curr_node['lft'],
2312  $a_type,
2313  $this->tree_id));
2314  } else {
2315  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2316  $this->buildJoin() .
2317  'WHERE lft < %s ' .
2318  'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ' .
2319  'ORDER BY lft DESC';
2320  $ilDB->setLimit(1);
2321  $res = $ilDB->queryF($query, array('integer','integer'), array(
2322  $curr_node['lft'],
2323  $this->tree_id));
2324  }
2325 
2326  if ($res->numRows() < 1) {
2327  return false;
2328  } else {
2329  $row = $ilDB->fetchAssoc($res);
2330  return $this->fetchNodeData($row);
2331  }
2332  }
2333 
2342  public function renumber($node_id = 1, $i = 1)
2343  {
2344  global $DIC;
2345 
2346  $ilDB = $DIC['ilDB'];
2347 
2348  $renumber_callable = function (ilDBInterface $ilDB) use ($node_id,$i,&$return) {
2349  $return = $this->__renumber($node_id, $i);
2350  };
2351 
2352  // LOCKED ###################################
2353  if ($this->__isMainTree()) {
2354  $ilAtomQuery = $ilDB->buildAtomQuery();
2355  $ilAtomQuery->addTableLock($this->table_tree);
2356 
2357  $ilAtomQuery->addQueryCallable($renumber_callable);
2358  $ilAtomQuery->run();
2359  } else {
2360  $renumber_callable($ilDB);
2361  }
2362  return $return;
2363  }
2364 
2365  // PRIVATE
2375  public function __renumber($node_id = 1, $i = 1)
2376  {
2377  global $DIC;
2378 
2379  $ilDB = $DIC['ilDB'];
2380 
2381  if ($this->isRepositoryTree()) {
2382  $query = 'UPDATE ' . $this->table_tree . ' SET lft = %s WHERE child = %s';
2383  $ilDB->manipulateF(
2384  $query,
2385  array('integer','integer'),
2386  array(
2387  $i,
2388  $node_id)
2389  );
2390  } else {
2391  $query = 'UPDATE ' . $this->table_tree . ' SET lft = %s WHERE child = %s AND tree = %s';
2392  $ilDB->manipulateF(
2393  $query,
2394  array('integer','integer','integer'),
2395  array(
2396  $i,
2397  $node_id,
2398  $this->tree_id)
2399  );
2400  }
2401 
2402  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2403  'WHERE parent = ' . $ilDB->quote($node_id, 'integer') . ' ' .
2404  'ORDER BY lft';
2405  $res = $ilDB->query($query);
2406 
2407  $childs = [];
2408  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
2409  $childs[] = $row->child;
2410  }
2411 
2412  foreach ($childs as $child) {
2413  $i = $this->__renumber($child, $i + 1);
2414  }
2415  $i++;
2416 
2417  // Insert a gap at the end of node, if the node has children
2418  if (count($childs) > 0) {
2419  $i += $this->gap * 2;
2420  }
2421 
2422 
2423  if ($this->isRepositoryTree()) {
2424  $query = 'UPDATE ' . $this->table_tree . ' SET rgt = %s WHERE child = %s';
2425  $res = $ilDB->manipulateF(
2426  $query,
2427  array('integer','integer'),
2428  array(
2429  $i,
2430  $node_id)
2431  );
2432  } else {
2433  $query = 'UPDATE ' . $this->table_tree . ' SET rgt = %s WHERE child = %s AND tree = %s';
2434  $res = $ilDB->manipulateF($query, array('integer','integer', 'integer'), array(
2435  $i,
2436  $node_id,
2437  $this->tree_id));
2438  }
2439  return $i;
2440  }
2441 
2442 
2453  public function checkForParentType($a_ref_id, $a_type, $a_exclude_source_check = false)
2454  {
2455  // #12577
2456  $cache_key = $a_ref_id . '.' . $a_type . '.' . ((int) $a_exclude_source_check);
2457 
2458  // Try to return a cached result
2459  if ($this->isCacheUsed() &&
2460  array_key_exists($cache_key, $this->parent_type_cache)) {
2461  return $this->parent_type_cache[$cache_key];
2462  }
2463 
2464  // Store up to 1000 results in cache
2465  $do_cache = ($this->__isMainTree() && count($this->parent_type_cache) < 1000);
2466 
2467  // ref_id is not in tree
2468  if (!$this->isInTree($a_ref_id)) {
2469  if ($do_cache) {
2470  $this->parent_type_cache[$cache_key] = false;
2471  }
2472  return false;
2473  }
2474 
2475  $path = array_reverse($this->getPathFull($a_ref_id));
2476 
2477  // remove first path entry as it is requested node
2478  if ($a_exclude_source_check) {
2479  array_shift($path);
2480  }
2481 
2482  foreach ($path as $node) {
2483  // found matching parent
2484  if ($node["type"] == $a_type) {
2485  if ($do_cache) {
2486  $this->parent_type_cache[$cache_key] = $node["child"];
2487  }
2488  return $node["child"];
2489  }
2490  }
2491 
2492  if ($do_cache) {
2493  $this->parent_type_cache[$cache_key] = false;
2494  }
2495  return 0;
2496  }
2497 
2508  public static function _removeEntry($a_tree, $a_child, $a_db_table = "tree")
2509  {
2510  global $DIC;
2511 
2512  $ilDB = $DIC['ilDB'];
2513 
2514  if ($a_db_table === 'tree') {
2515  if ($a_tree == 1 and $a_child == ROOT_FOLDER_ID) {
2516  $message = sprintf(
2517  'Tried to delete root node! $a_tree: %s $a_child: %s',
2518  $a_tree,
2519  $a_child
2520  );
2521  ilLoggerFactory::getLogger('tree')->error($message);
2523  }
2524  }
2525 
2526  $query = 'DELETE FROM ' . $a_db_table . ' ' .
2527  'WHERE tree = %s ' .
2528  'AND child = %s ';
2529  $res = $ilDB->manipulateF($query, array('integer','integer'), array(
2530  $a_tree,
2531  $a_child));
2532  }
2533 
2540  public function __isMainTree()
2541  {
2542  return $this->table_tree === 'tree';
2543  }
2544 
2556  public function __checkDelete($a_node)
2557  {
2558  global $DIC;
2559 
2560  $ilDB = $DIC['ilDB'];
2561 
2562 
2563  $query = $this->getTreeImplementation()->getSubTreeQuery($a_node, array(), false);
2564  $this->log->debug($query);
2565  $res = $ilDB->query($query);
2566 
2567  $counter = (int) $lft_childs = array();
2568  while ($row = $ilDB->fetchObject($res)) {
2569  $lft_childs[$row->child] = $row->parent;
2570  ++$counter;
2571  }
2572 
2573  // CHECK FOR DUPLICATE CHILD IDS
2574  if ($counter != count($lft_childs)) {
2575  $message = 'Duplicate entries for "child" in maintree! $a_node_id: ' . $a_node['child'];
2576 
2577  $this->log->error($message);
2579  }
2580 
2581  // GET SUBTREE BY PARENT RELATION
2582  $parent_childs = array();
2583  $this->__getSubTreeByParentRelation($a_node['child'], $parent_childs);
2584  $this->__validateSubtrees($lft_childs, $parent_childs);
2585 
2586  return true;
2587  }
2588 
2598  public function __getSubTreeByParentRelation($a_node_id, &$parent_childs)
2599  {
2600  global $DIC;
2601 
2602  $ilDB = $DIC['ilDB'];
2603 
2604  // GET PARENT ID
2605  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2606  'WHERE child = %s ' .
2607  'AND tree = %s ';
2608  $res = $ilDB->queryF($query, array('integer','integer'), array(
2609  $a_node_id,
2610  $this->tree_id));
2611 
2612  $counter = 0;
2613  while ($row = $ilDB->fetchObject($res)) {
2614  $parent_childs[$a_node_id] = $row->parent;
2615  ++$counter;
2616  }
2617  // MULTIPLE ENTRIES
2618  if ($counter > 1) {
2619  $message = 'Multiple entries in maintree! $a_node_id: ' . $a_node_id;
2620 
2621  $this->log->error($message);
2623  }
2624 
2625  // GET ALL CHILDS
2626  $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2627  'WHERE parent = %s ';
2628  $res = $ilDB->queryF($query, array('integer'), array($a_node_id));
2629 
2630  while ($row = $ilDB->fetchObject($res)) {
2631  // RECURSION
2632  $this->__getSubTreeByParentRelation($row->child, $parent_childs);
2633  }
2634  return true;
2635  }
2636 
2644  public function __validateSubtrees(&$lft_childs, $parent_childs)
2645  {
2646  // SORT BY KEY
2647  ksort($lft_childs);
2648  ksort($parent_childs);
2649 
2650  $this->log->debug('left childs ' . print_r($lft_childs, true));
2651  $this->log->debug('parent childs ' . print_r($parent_childs, true));
2652 
2653  if (count($lft_childs) != count($parent_childs)) {
2654  $message = '(COUNT) Tree is corrupted! Left/Right subtree does not comply with parent relation';
2655  $this->log->error($message);
2657  }
2658 
2659 
2660  foreach ($lft_childs as $key => $value) {
2661  if ($parent_childs[$key] != $value) {
2662  $message = '(COMPARE) Tree is corrupted! Left/Right subtree does not comply with parent relation';
2663  $this->log->error($message);
2665  }
2666  if ($key == ROOT_FOLDER_ID) {
2667  $message = '(ROOT_FOLDER) Tree is corrupted! Tried to delete root folder';
2668  $this->log->error($message);
2670  }
2671  }
2672  return true;
2673  }
2674 
2684  public function moveTree($a_source_id, $a_target_id, $a_location = self::POS_LAST_NODE)
2685  {
2686  $old_parent_id = $this->getParentId($a_source_id);
2687  $this->getTreeImplementation()->moveTree($a_source_id, $a_target_id, $a_location);
2688  if (isset($GLOBALS['DIC']["ilAppEventHandler"]) && $this->__isMainTree()) {
2689  $GLOBALS['DIC']['ilAppEventHandler']->raise(
2690  "Services/Tree",
2691  "moveTree",
2692  array(
2693  'tree' => $this->table_tree,
2694  'source_id' => $a_source_id,
2695  'target_id' => $a_target_id,
2696  'old_parent_id' => $old_parent_id
2697  )
2698  );
2699  }
2700  return true;
2701  }
2702 
2703 
2704 
2705 
2713  public function getRbacSubtreeInfo($a_endnode_id)
2714  {
2715  return $this->getTreeImplementation()->getSubtreeInfo($a_endnode_id);
2716  }
2717 
2718 
2726  public function getSubTreeQuery($a_node_id, $a_fields = array(), $a_types = '', $a_force_join_reference = false)
2727  {
2728  return $this->getTreeImplementation()->getSubTreeQuery(
2729  $this->getNodeTreeData($a_node_id),
2730  $a_types,
2731  $a_force_join_reference,
2732  $a_fields
2733  );
2734  }
2735 
2739  public function getTrashSubTreeQuery($a_node_id, $a_fields = [], $a_types = '', $a_force_join_reference = false)
2740  {
2741  return $this->getTreeImplementation()->getTrashSubTreeQuery(
2742  $this->getNodeTreeData($a_node_id),
2743  $a_types,
2744  $a_force_join_reference,
2745  $a_fields
2746  );
2747  }
2748 
2749 
2758  public function getSubTreeFilteredByObjIds($a_node_id, array $a_obj_ids, array $a_fields = array())
2759  {
2760  global $DIC;
2761 
2762  $ilDB = $DIC['ilDB'];
2763 
2764  $node = $this->getNodeData($a_node_id);
2765  if (!sizeof($node)) {
2766  return;
2767  }
2768 
2769  $res = array();
2770 
2771  $query = $this->getTreeImplementation()->getSubTreeQuery($node, '', true, array($this->ref_pk));
2772 
2773  $fields = '*';
2774  if (count($a_fields)) {
2775  $fields = implode(',', $a_fields);
2776  }
2777 
2778  $query = "SELECT " . $fields .
2779  " FROM " . $this->getTreeTable() .
2780  " " . $this->buildJoin() .
2781  " WHERE " . $this->getTableReference() . "." . $this->ref_pk . " IN (" . $query . ")" .
2782  " AND " . $ilDB->in($this->getObjectDataTable() . "." . $this->obj_pk, $a_obj_ids, "", "integer");
2783  $set = $ilDB->query($query);
2784  while ($row = $ilDB->fetchAssoc($set)) {
2785  $res[] = $row;
2786  }
2787 
2788  return $res;
2789  }
2790 
2791  public function deleteNode($a_tree_id, $a_node_id)
2792  {
2793  global $DIC;
2794 
2795  $ilDB = $DIC['ilDB'];
2796  $ilAppEventHandler = $DIC['ilAppEventHandler'];
2797 
2798  $query = 'DELETE FROM tree where ' .
2799  'child = ' . $ilDB->quote($a_node_id, 'integer') . ' ' .
2800  'AND tree = ' . $ilDB->quote($a_tree_id, 'integer');
2801  $ilDB->manipulate($query);
2802 
2803  $ilAppEventHandler->raise(
2804  "Services/Tree",
2805  "deleteNode",
2806  array('tree' => $this->table_tree,
2807  'node_id' => $a_node_id,
2808  'tree_id' => $a_tree_id
2809  )
2810  );
2811  }
2812 
2818  public function lookupTrashedObjectTypes()
2819  {
2820  global $DIC;
2821 
2822  $ilDB = $DIC['ilDB'];
2823 
2824  $query = 'SELECT DISTINCT(o.type) ' . $ilDB->quoteIdentifier('type') . ' FROM tree t JOIN object_reference r ON child = r.ref_id ' .
2825  'JOIN object_data o on r.obj_id = o.obj_id ' .
2826  'WHERE tree < ' . $ilDB->quote(0, 'integer') . ' ' .
2827  'AND child = -tree ' .
2828  'GROUP BY o.type';
2829  $res = $ilDB->query($query);
2830 
2831  $types_deleted = array();
2832  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
2833  $types_deleted[] = $row->type;
2834  }
2835  return $types_deleted;
2836  }
2837 
2841  public function isRepositoryTree()
2842  {
2843  if ($this->table_tree == 'tree') {
2844  return true;
2845  }
2846  return false;
2847  }
2848 } // 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
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
Interface ilDBInterface.
$a_type
Definition: workflow.php:92
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
lookupTrashedObjectTypes()
Lookup object types in trash type $ilDB.
if(!defined('PATH_SEPARATOR')) $GLOBALS['_PEAR_default_error_mode']
Definition: PEAR.php:64
$ilUser
Definition: imgupload.php:18
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.
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:18
global $ilDB
$DIC
Definition: xapitoken.php:46
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.
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