ILIAS  release_6 Revision v6.24-5-g0c8bfefb3b8
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
4define("IL_LAST_NODE", -2);
5define("IL_FIRST_NODE", -1);
6
7include_once './Services/Tree/exceptions/class.ilInvalidTreeStructureException.php';
8
24class 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
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
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 {
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);
391 throw new InvalidArgumentException($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);
415 throw new InvalidArgumentException($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);
434 throw new InvalidArgumentException($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);
453 throw new InvalidArgumentException($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);
547 throw new InvalidArgumentException($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);
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);
651 throw new InvalidArgumentException($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);
711 throw new InvalidArgumentException($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) {
761 throw new InvalidArgumentException('Invalid parameter given for ilTree::insertNodeFromTrash');
762 }
763 }
764 if (!isset($a_source_id) or !isset($a_target_id)) {
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');
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);
807 throw new InvalidArgumentException($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);
1504 throw new InvalidArgumentException($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);
1762 throw new InvalidArgumentException($message);
1763 }
1764
1765 if (!isset($a_tree_id)) {
1766 $message = "No tree_id given!";
1767 $this->log->error($message);
1768 throw new InvalidArgumentException($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
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);
1995 throw new InvalidArgumentException($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);
2053 throw new InvalidArgumentException($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);
2093 throw new InvalidArgumentException($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);
2122 throw new InvalidArgumentException($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);
2228 throw new InvalidArgumentException($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);
2289 throw new InvalidArgumentException($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);
2522 throw new InvalidArgumentException($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
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
if(!defined('PATH_SEPARATOR')) $GLOBALS['_PEAR_default_error_mode']
Definition: PEAR.php:64
An exception for terminatinating execution or to throw for unit testing.
static toNFC($string)
Convert a UTF-8 string to normal form C, canonical composition.
Definition: UtfNormal.php:159
const IL_LAST_NODE
Definition: class.ilTree.php:4
static _lookupTitle($a_obj_id)
Overwitten from base class.
Thrown if invalid tree strucutes are found.
static getLogger($a_component_id)
Get component logger.
Base class for materialize path based trees Based on implementation of Werner Randelshofer.
Base class for nested set path based trees.
static setDeletedDates($a_ref_ids, $a_user_id)
Set deleted date.
static _resetDeletedDate($a_ref_id)
only called in ilObjectGUI::insertSavedNodes
const DESC_LENGTH
ILIAS Setting Class.
static strToLower($a_string)
Definition: class.ilStr.php:87
Tree class data representation in hierachical trees using the Nested Set Model with Gaps by Joe Celco...
isCacheUsed()
Check if cache is active.
fetchPredecessorNode($a_node_id, $a_type="")
get node data of predecessor node
lookupTrashedObjectTypes()
Lookup object types in trash @global type $ilDB.
getRelation($a_node_a, $a_node_b)
Get relation of two nodes.
getSubTree($a_node, $a_with_data=true, $a_type="")
get all nodes in the subtree under specified node
getFilteredChilds($a_filter, $a_node, $a_order="", $a_direction="ASC")
get child nodes of given node (exclude filtered obj_types) @access public
isGrandChild($a_startnode_id, $a_querynode_id)
checks if a node is in the path of an other node @access public
getSubTreeTypes($a_node, $a_filter=0)
get types of nodes in the subtree under specified node
getRelationOfNodes($a_node_a_arr, $a_node_b_arr)
get relation of two nodes by node data
setObjectTablePK($a_column_name)
set column containing primary key in object table @access public
getTreePk()
Get tree primary key.
getParentId($a_node_id)
get parent id of given node @access public
getRbacSubtreeInfo($a_endnode_id)
This method is used for change existing objects and returns all necessary information for this action...
getParentCache()
Get parent cache.
setReferenceTablePK($a_column_name)
set column containing primary key in reference table @access public
checkTreeChilds($a_no_zero_child=true)
check, if all childs of tree nodes exist in object table
getDepth($a_node_id)
return depth of a node in tree @access private
getSavedNodeData($a_parent_id)
get data saved/deleted nodes
getChildSequenceNumber($a_node, $type="")
get sequence number of node in sibling sequence @access public
isRepositoryTree()
check if current tree instance operates on repository tree table
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.
useCache($a_use=true)
Use Cache (usually activated)
deleteTree($a_node)
delete node and the whole subtree under this node @access public
getLeftValue($a_node_id)
get left value of given node @access public
__checkDelete($a_node)
Check for deleteTree() compares a subtree of a given node by checking lft, rgt against parent relatio...
fetchSuccessorNode($a_node_id, $a_type="")
get node data of successor node
getDepthCache()
Get depth cache.
getTreeId()
get tree id @access public
getChildIds($a_node)
Get node child ids @global type $ilDB.
getNodeDataByType($a_type)
get nodes by type
getTreeTable()
Get tree table name.
static _removeEntry($a_tree, $a_child, $a_db_table="tree")
STATIC METHOD Removes a single entry from a tree.
getSubTreeQuery($a_node_id, $a_fields=array(), $a_types='', $a_force_join_reference=false)
Get tree subtree query.
__renumber($node_id=1, $i=1)
This method is private.
setTreeTablePK($a_column_name)
set column containing primary key in tree table @access public
const POS_LAST_NODE
isDeleted($a_node_id)
This is a wrapper for isSaved() with a more useful name.
getNodeTreeData($a_node_id)
return all columns of tabel tree
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 'obj_id' You may use...
getTableReference()
Get reference table if available.
const TREE_TYPE_NESTED_SET
fetchTranslationFromObjectDataCache($a_obj_ids)
Get translation data from object cache (trigger in object cache on preload)
readRootId()
read root id from database
removeTree($a_tree_id)
remove an existing tree
static lookupTreesForNode(int $node_id)
fetchNodeData($a_row)
get data of parent node from tree and object_data @access private
checkTree()
check consistence of tree all left & right values are checked if they are exists only once @access pu...
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
renumber($node_id=1, $i=1)
Wrapper for renumber.
getNodePath($a_endnode_id, $a_startnode_id=0)
Returns the node path for the specified object reference.
resetInTreeCache()
getChildsByType($a_node_id, $a_type)
get child nodes of given node by object type @access public
getNodeData($a_node_id, $a_tree_pk=null)
get all information of a node.
setTreeId($a_tree_id)
set tree id @access public
getSavedNodeObjIds(array $a_obj_ids)
get object id of saved/deleted nodes
getChildsByTypeFilter($a_node_id, $a_types, $a_order="", $a_direction="ASC")
get child nodes of given node by object type @access public
deleteNode($a_tree_id, $a_node_id)
$table_obj_reference
getMaximumDepth()
Return the current maximum depth in the tree @access public.
moveTree($a_source_id, $a_target_id, $a_location=self::POS_LAST_NODE)
Move Tree Implementation.
preloadDepthParent($a_node_ids)
Preload depth/parent.
const POS_FIRST_NODE
isSaved($a_node_id)
Use method isDeleted check if node is saved.
const RELATION_PARENT
initLangCode()
Store user language.
const RELATION_NONE
buildJoin()
build join depending on table settings @access private
getTreeImplementation()
Get tree implementation.
getNodePathForTitlePath($titlePath, $a_startnode_id=null)
Converts a path consisting of object titles into a path consisting of tree nodes.
initTreeImplementation()
Init tree implementation.
const RELATION_SIBLING
getTrashSubTreeQuery($a_node_id, $a_fields=[], $a_types='', $a_force_join_reference=false)
preloadDeleted($a_node_ids)
Preload deleted information.
__construct($a_tree_id, $a_root_id=0)
Constructor @access public.
getFilteredSubTree($a_node_id, $a_filter=array())
get filtered subtree
getObjectDataTable()
Get object data table.
getRootId()
get the root id of tree @access public
const TREE_TYPE_MATERIALIZED_PATH
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 @access public
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...
__isMainTree()
Check if operations are done on main tree.
moveToTrash($a_node_id, $a_set_deleted=false, $a_deleted_by=0)
Move node to trash bin.
__getSubTreeByParentRelation($a_node_id, &$parent_childs)
@global type $ilDB
isInTree($a_node_id)
get all information of a node.
getGap()
Get default gap *.
const RELATION_CHILD
__validateSubtrees(&$lft_childs, $parent_childs)
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...
addTree($a_tree_id, $a_node_id=-1)
create a new tree to do: ???
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...
getParentNodeData($a_node_id)
get data of parent node from tree and object_data @access public
getChilds($a_node_id, $a_order="", $a_direction="ASC")
get child nodes of given node @access public
setRootId($a_root_id)
validateParentRelations()
Validate parent relations of tree.
getSubTreeIds($a_ref_id)
Get all ids of subnodes.
static quoteArray($a_array)
Quotes all members of an array for usage in DB query statement.
static shortenText( $a_str, $a_len, $a_dots=false, $a_next_blank=false, $a_keep_extension=false)
shorten a string to given length.
global $ilBench
Definition: ilias.php:18
Interface ilDBInterface.
$i
Definition: metadata.php:24
$query
$type
$lng
foreach($_POST as $key=> $value) $res
global $ilDB
$data
Definition: storeScorm.php:23
$ilUser
Definition: imgupload.php:18
$a_type
Definition: workflow.php:92
$message
Definition: xapiexit.php:14
$DIC
Definition: xapitoken.php:46
$rows
Definition: xhr_table.php:10