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