ILIAS  release_8 Revision v8.24
class.ilTree.php
Go to the documentation of this file.
1<?php
2
3declare(strict_types=1);
29class ilTree
30{
31 public const TREE_TYPE_MATERIALIZED_PATH = 'mp';
32 public const TREE_TYPE_NESTED_SET = 'ns';
33
34 public const POS_LAST_NODE = -2;
35 public const POS_FIRST_NODE = -1;
36
37 public const RELATION_CHILD = 1;
38 public const RELATION_PARENT = 2;
39 public const RELATION_SIBLING = 3;
40 public const RELATION_EQUALS = 4;
41 public const RELATION_NONE = 5;
42
43 protected const DEFAULT_LANGUAGE = 'en';
44 protected const DEFAULT_GAP = 50;
45
46 protected ilLogger $logger;
47 protected ilDBInterface $db;
49
54 private string $lang_code;
55
59 protected int $root_id;
60
64 protected int $tree_id;
65
69 protected string $table_tree;
70
74 protected string $table_obj_data;
75
79 protected string $table_obj_reference;
80
84 protected string $ref_pk;
85
89 protected string $obj_pk;
90
94 protected string $tree_pk;
95
114 private int $gap;
115
116 protected bool $use_cache;
118 protected array $oc_preloaded = [];
119 protected array $depth_cache = [];
120 protected array $parent_cache = [];
121 protected array $in_tree_cache = [];
122 protected array $translation_cache = [];
123 protected array $parent_type_cache = [];
124 protected array $is_saved_cache = [];
125
127
128 private array $path_id_cache = [];
129
133 public function __construct(
134 int $a_tree_id,
135 int $a_root_id = 0,
136 ilDBInterface $db = null
137 ) {
138 global $DIC;
139
140 $this->db = $db ?? $DIC->database();
141 $this->logger = ilLoggerFactory::getLogger('tree');
142 //$this->logger = $DIC->logger()->tree();
143 if (isset($DIC['ilAppEventHandler'])) {
144 $this->eventHandler = $DIC['ilAppEventHandler'];
145 }
146
147 $this->lang_code = self::DEFAULT_LANGUAGE;
148
149 if (func_num_args() > 3) {
150 $this->logger->error("Wrong parameter count!");
151 $this->logger->logStack(ilLogLevel::ERROR);
152 throw new InvalidArgumentException("Wrong parameter count!");
153 }
154
155 if ($a_root_id > 0) {
156 $this->root_id = $a_root_id;
157 } else {
158 $this->root_id = ROOT_FOLDER_ID;
159 }
160
161 $this->tree_id = $a_tree_id;
162 $this->table_tree = 'tree';
163 $this->table_obj_data = 'object_data';
164 $this->table_obj_reference = 'object_reference';
165 $this->ref_pk = 'ref_id';
166 $this->obj_pk = 'obj_id';
167 $this->tree_pk = 'tree';
168
169 $this->use_cache = true;
170
171 // By default, we create gaps in the tree sequence numbering for 50 nodes
172 $this->gap = self::DEFAULT_GAP;
173
174 // init tree implementation
175 $this->initTreeImplementation();
176 }
177
182 public static function lookupTreesForNode(int $node_id): array
183 {
184 global $DIC;
185
186 $db = $DIC->database();
187
188 $query = 'select tree from tree ' .
189 'where child = ' . $db->quote($node_id, \ilDBConstants::T_INTEGER);
190 $res = $db->query($query);
191
192 $trees = [];
193 while ($row = $res->fetchRow(\ilDBConstants::FETCHMODE_OBJECT)) {
194 $trees[] = (int) $row->tree;
195 }
196 return $trees;
197 }
198
202 public function initTreeImplementation(): void
203 {
204 global $DIC;
205
206 if (!$DIC->isDependencyAvailable('settings') || $DIC->settings()->getModule() != 'common') {
207 $setting = new ilSetting('common');
208 } else {
209 $setting = $DIC->settings();
210 }
211
212 if ($this->__isMainTree()) {
213 if ($setting->get('main_tree_impl', 'ns') == 'ns') {
214 $this->tree_impl = new ilNestedSetTree($this);
215 } else {
216 $this->tree_impl = new ilMaterializedPathTree($this);
217 }
218 } else {
219 $this->tree_impl = new ilNestedSetTree($this);
220 }
221 }
222
228 {
229 return $this->tree_impl;
230 }
231
235 public function useCache(bool $a_use = true): void
236 {
237 $this->use_cache = $a_use;
238 }
239
243 public function isCacheUsed(): bool
244 {
245 return $this->__isMainTree() && $this->use_cache;
246 }
247
251 public function getDepthCache(): array
252 {
253 return $this->depth_cache;
254 }
255
259 public function getParentCache(): array
260 {
261 return $this->parent_cache;
262 }
263
264 protected function getLangCode(): string
265 {
266 return $this->lang_code;
267 }
268
275 public function initLangCode(): void
276 {
277 global $DIC;
278
279 if ($DIC->offsetExists('ilUser')) {
280 $this->lang_code = $DIC->user()->getCurrentLanguage() ?
281 $DIC->user()->getCurrentLanguage() : self::DEFAULT_LANGUAGE;
282 } else {
283 $this->lang_code = self::DEFAULT_LANGUAGE;
284 }
285 }
286
290 public function getTreeTable(): string
291 {
292 return $this->table_tree;
293 }
294
298 public function getObjectDataTable(): string
299 {
301 }
302
306 public function getTreePk(): string
307 {
308 return $this->tree_pk;
309 }
310
314 public function getTableReference(): string
315 {
317 }
318
322 public function getGap(): int
323 {
324 return $this->gap;
325 }
326
330 public function resetInTreeCache(): void
331 {
332 $this->in_tree_cache = array();
333 }
334
343 public function setTableNames(
344 string $a_table_tree,
345 string $a_table_obj_data,
346 string $a_table_obj_reference = ""
347 ): void {
348 $this->table_tree = $a_table_tree;
349 $this->table_obj_data = $a_table_obj_data;
350 $this->table_obj_reference = $a_table_obj_reference;
351
352 // reconfigure tree implementation
353 $this->initTreeImplementation();
354 }
355
359 public function setReferenceTablePK(string $a_column_name): void
360 {
361 $this->ref_pk = $a_column_name;
362 }
363
367 public function setObjectTablePK(string $a_column_name): void
368 {
369 $this->obj_pk = $a_column_name;
370 }
371
375 public function setTreeTablePK(string $a_column_name): void
376 {
377 $this->tree_pk = $a_column_name;
378 }
379
385 public function buildJoin(): string
386 {
387 if ($this->table_obj_reference) {
388 // Use inner join instead of left join to improve performance
389 return "JOIN " . $this->table_obj_reference . " ON " . $this->table_tree . ".child=" . $this->table_obj_reference . "." . $this->ref_pk . " " .
390 "JOIN " . $this->table_obj_data . " ON " . $this->table_obj_reference . "." . $this->obj_pk . "=" . $this->table_obj_data . "." . $this->obj_pk . " ";
391 } else {
392 // Use inner join instead of left join to improve performance
393 return "JOIN " . $this->table_obj_data . " ON " . $this->table_tree . ".child=" . $this->table_obj_data . "." . $this->obj_pk . " ";
394 }
395 }
396
401 public function getRelation(int $a_node_a, int $a_node_b): int
402 {
403 return $this->getRelationOfNodes(
404 $this->getNodeTreeData($a_node_a),
405 $this->getNodeTreeData($a_node_b)
406 );
407 }
408
412 public function getRelationOfNodes(array $a_node_a_arr, array $a_node_b_arr): int
413 {
414 return $this->getTreeImplementation()->getRelation($a_node_a_arr, $a_node_b_arr);
415 }
416
421 public function getChildIds(int $a_node): array
422 {
423 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
424 'WHERE parent = ' . $this->db->quote($a_node, 'integer') . ' ' .
425 'AND tree = ' . $this->db->quote($this->tree_id, 'integer' . ' ' .
426 'ORDER BY lft');
427 $res = $this->db->query($query);
428
429 $childs = array();
430 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
431 $childs[] = (int) $row->child;
432 }
433 return $childs;
434 }
435
440 public function getChilds(int $a_node_id, string $a_order = "", string $a_direction = "ASC"): array
441 {
442 global $DIC;
443
444 $ilObjDataCache = $DIC['ilObjDataCache'];
445 $ilUser = $DIC['ilUser'];
446
447 // init childs
448 $childs = [];
449
450 // number of childs
451 $count = 0;
452
453 // init order_clause
454 $order_clause = "";
455
456 // set order_clause if sort order parameter is given
457 if (!empty($a_order)) {
458 $order_clause = "ORDER BY " . $a_order . " " . $a_direction;
459 } else {
460 $order_clause = "ORDER BY " . $this->table_tree . ".lft";
461 }
462
463 $query = sprintf(
464 'SELECT * FROM ' . $this->table_tree . ' ' .
465 $this->buildJoin() .
466 "WHERE parent = %s " .
467 "AND " . $this->table_tree . "." . $this->tree_pk . " = %s " .
468 $order_clause,
469 $this->db->quote($a_node_id, 'integer'),
470 $this->db->quote($this->tree_id, 'integer')
471 );
472
473 $res = $this->db->query($query);
474
475 if (!$count = $res->numRows()) {
476 return [];
477 }
478
479 // get rows and object ids
480 $rows = [];
481 $obj_ids = [];
482 while ($r = $this->db->fetchAssoc($res)) {
483 $rows[] = $r;
484 $obj_ids[] = (int) $r["obj_id"];
485 }
486
487 // preload object translation information
488 if ($this->__isMainTree() && $this->isCacheUsed() && is_object($ilObjDataCache) &&
489 is_object($ilUser) && $this->lang_code == $ilUser->getLanguage() && !isset($this->oc_preloaded[$a_node_id])) {
490 // $ilObjDataCache->preloadTranslations($obj_ids, $this->lang_code);
491 $ilObjDataCache->preloadObjectCache($obj_ids, $this->lang_code);
492 $this->fetchTranslationFromObjectDataCache($obj_ids);
493 $this->oc_preloaded[$a_node_id] = true;
494 }
495
496 foreach ($rows as $row) {
497 $childs[] = $this->fetchNodeData($row);
498
499 // Update cache of main tree
500 if ($this->__isMainTree()) {
501 #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child'].' = true');
502 $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
503 }
504 }
505 $childs[$count - 1]["last"] = true;
506 return $childs;
507 }
508
517 public function getFilteredChilds(
518 array $a_filter,
519 int $a_node,
520 string $a_order = "",
521 string $a_direction = "ASC"
522 ): array {
523 $childs = $this->getChilds($a_node, $a_order, $a_direction);
524
525 $filtered = [];
526 foreach ($childs as $child) {
527 if (!in_array($child["type"], $a_filter)) {
528 $filtered[] = $child;
529 }
530 }
531 return $filtered;
532 }
533
538 public function getChildsByType(int $a_node_id, string $a_type): array
539 {
540 if ($a_type == 'rolf' && $this->table_obj_reference) {
541 // Performance optimization: A node can only have exactly one
542 // role folder as its child. Therefore we don't need to sort the
543 // results, and we can let the database know about the expected limit.
544 $this->db->setLimit(1, 0);
545 $query = sprintf(
546 "SELECT * FROM " . $this->table_tree . " " .
547 $this->buildJoin() .
548 "WHERE parent = %s " .
549 "AND " . $this->table_tree . "." . $this->tree_pk . " = %s " .
550 "AND " . $this->table_obj_data . ".type = %s ",
551 $this->db->quote($a_node_id, 'integer'),
552 $this->db->quote($this->tree_id, 'integer'),
553 $this->db->quote($a_type, 'text')
554 );
555 } else {
556 $query = sprintf(
557 "SELECT * FROM " . $this->table_tree . " " .
558 $this->buildJoin() .
559 "WHERE parent = %s " .
560 "AND " . $this->table_tree . "." . $this->tree_pk . " = %s " .
561 "AND " . $this->table_obj_data . ".type = %s " .
562 "ORDER BY " . $this->table_tree . ".lft",
563 $this->db->quote($a_node_id, 'integer'),
564 $this->db->quote($this->tree_id, 'integer'),
565 $this->db->quote($a_type, 'text')
566 );
567 }
568 $res = $this->db->query($query);
569
570 // init childs
571 $childs = [];
572 while ($row = $this->db->fetchAssoc($res)) {
573 $childs[] = $this->fetchNodeData($row);
574 }
575 return $childs;
576 }
577
581 public function getChildsByTypeFilter(
582 int $a_node_id,
583 array $a_types,
584 string $a_order = "",
585 string $a_direction = "ASC"
586 ): array {
587 $filter = ' ';
588 if ($a_types) {
589 $filter = 'AND ' . $this->table_obj_data . '.type IN(' . implode(',', ilArrayUtil::quoteArray($a_types)) . ') ';
590 }
591
592 // set order_clause if sort order parameter is given
593 if (!empty($a_order)) {
594 $order_clause = "ORDER BY " . $a_order . " " . $a_direction;
595 } else {
596 $order_clause = "ORDER BY " . $this->table_tree . ".lft";
597 }
598
599 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
600 $this->buildJoin() .
601 'WHERE parent = ' . $this->db->quote($a_node_id, 'integer') . ' ' .
602 'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = ' . $this->db->quote(
603 $this->tree_id,
604 'integer'
605 ) . ' ' .
606 $filter .
607 $order_clause;
608
609 $res = $this->db->query($query);
610
611 $childs = [];
612 while ($row = $this->db->fetchAssoc($res)) {
613 $childs[] = $this->fetchNodeData($row);
614 }
615
616 return $childs;
617 }
618
625 public function insertNodeFromTrash(
626 int $a_source_id,
627 int $a_target_id,
628 int $a_tree_id,
629 int $a_pos = self::POS_LAST_NODE,
630 bool $a_reset_deleted_date = false
631 ): void {
632 if ($this->__isMainTree()) {
633 if ($a_source_id <= 1 || $a_target_id <= 0) {
634 $this->logger->logStack(ilLogLevel::WARNING);
635 throw new InvalidArgumentException('Invalid parameter given for ilTree::insertNodeFromTrash');
636 }
637 }
638 if ($this->isInTree($a_source_id)) {
639 ilLoggerFactory::getLogger('tree')->error('Node already in tree');
641 throw new InvalidArgumentException('Node already in tree.');
642 }
643
644 $query = 'DELETE from tree ' .
645 'WHERE tree = ' . $this->db->quote($a_tree_id, 'integer') . ' ' .
646 'AND child = ' . $this->db->quote($a_source_id, 'integer');
647 $this->db->manipulate($query);
648
649 $this->insertNode($a_source_id, $a_target_id, self::POS_LAST_NODE, $a_reset_deleted_date);
650 }
651
656 public function insertNode(
657 int $a_node_id,
658 int $a_parent_id,
659 int $a_pos = self::POS_LAST_NODE,
660 bool $a_reset_deletion_date = false
661 ): void {
662 // CHECK node_id and parent_id > 0 if in main tree
663 if ($this->__isMainTree()) {
664 if ($a_node_id <= 1 || $a_parent_id <= 0) {
665 $message = sprintf(
666 'Invalid parameters! $a_node_id: %s $a_parent_id: %s',
667 $a_node_id,
668 $a_parent_id
669 );
670 $this->logger->logStack(ilLogLevel::ERROR, $message);
671 throw new InvalidArgumentException($message);
672 }
673 }
674 if ($this->isInTree($a_node_id)) {
675 throw new InvalidArgumentException("Node " . $a_node_id . " already in tree " .
676 $this->table_tree . "!");
677 }
678
679 $this->getTreeImplementation()->insertNode($a_node_id, $a_parent_id, $a_pos);
680
681 $this->in_tree_cache[$a_node_id] = true;
682
683 // reset deletion date
684 if ($a_reset_deletion_date) {
685 ilObject::_resetDeletedDate($a_node_id);
686 }
687 if (isset($this->eventHandler) && ($this->eventHandler instanceof ilAppEventHandler) && $this->__isMainTree()) {
688 $this->eventHandler->raise(
689 'Services/Tree',
690 'insertNode',
691 [
692 'tree' => $this->table_tree,
693 'node_id' => $a_node_id,
694 'parent_id' => $a_parent_id
695 ]
696 );
697 }
698 }
699
706 public function getFilteredSubTree(int $a_node_id, array $a_filter = []): array
707 {
708 $node = $this->getNodeData($a_node_id);
709
710 $first = true;
711 $depth = 0;
712 $filtered = [];
713 foreach ($this->getSubTree($node) as $subnode) {
714 if ($depth && $subnode['depth'] > $depth) {
715 continue;
716 }
717 if (!$first && in_array($subnode['type'], $a_filter)) {
718 $depth = $subnode['depth'];
719 $first = false;
720 continue;
721 }
722 $depth = 0;
723 $first = false;
724 $filtered[] = $subnode;
725 }
726 return $filtered;
727 }
728
734 public function getSubTreeIds(int $a_ref_id): array
735 {
736 return $this->getTreeImplementation()->getSubTreeIds($a_ref_id);
737 }
738
744 public function getSubTree(array $a_node, bool $a_with_data = true, array $a_type = []): array
745 {
746 $query = $this->getTreeImplementation()->getSubTreeQuery($a_node, $a_type);
747
748 $res = $this->db->query($query);
749 $subtree = [];
750 while ($row = $this->db->fetchAssoc($res)) {
751 if ($a_with_data) {
752 $subtree[] = $this->fetchNodeData($row);
753 } else {
754 $subtree[] = (int) $row['child'];
755 }
756 // the lm_data "hack" should be removed in the trunk during an alpha
757 if ($this->__isMainTree() || $this->table_tree == "lm_tree") {
758 $this->in_tree_cache[$row['child']] = true;
759 }
760 }
761 return $subtree;
762 }
763
767 public function deleteTree(array $a_node): void
768 {
769 if ($this->__isMainTree()) {
770 // moved to trash and then deleted.
771 if (!$this->__checkDelete($a_node)) {
772 $this->logger->logStack(ilLogLevel::ERROR);
773 throw new ilInvalidTreeStructureException('Deletion canceled due to invalid tree structure.' . print_r(
774 $a_node,
775 true
776 ));
777 }
778 }
779 $this->getTreeImplementation()->deleteTree((int) $a_node['child']);
780 $this->resetInTreeCache();
781 }
782
787 public function validateParentRelations(): array
788 {
789 return $this->getTreeImplementation()->validateParentRelations();
790 }
791
797 public function getPathFull(int $a_endnode_id, int $a_startnode_id = 0): array
798 {
799 $pathIds = $this->getPathId($a_endnode_id, $a_startnode_id);
800
801 // We retrieve the full path in a single query to improve performance
802 // Abort if no path ids were found
803 if (count($pathIds) == 0) {
804 return [];
805 }
806
807 $inClause = 'child IN (';
808 for ($i = 0; $i < count($pathIds); $i++) {
809 if ($i > 0) {
810 $inClause .= ',';
811 }
812 $inClause .= $this->db->quote($pathIds[$i], 'integer');
813 }
814 $inClause .= ')';
815
816 $q = 'SELECT * ' .
817 'FROM ' . $this->table_tree . ' ' .
818 $this->buildJoin() . ' ' .
819 'WHERE ' . $inClause . ' ' .
820 'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = ' . $this->db->quote(
821 $this->tree_id,
822 'integer'
823 ) . ' ' .
824 'ORDER BY depth';
825 $r = $this->db->query($q);
826
827 $pathFull = [];
828 while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
829 $pathFull[] = $this->fetchNodeData($row);
830
831 // Update cache
832 if ($this->__isMainTree()) {
833 $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
834 }
835 }
836 return $pathFull;
837 }
838
843 public function preloadDepthParent(array $a_node_ids): void
844 {
845 global $DIC;
846
847 if (!$this->__isMainTree() || !$this->isCacheUsed()) {
848 return;
849 }
850
851 $res = $this->db->query('SELECT t.depth, t.parent, t.child ' .
852 'FROM ' . $this->table_tree . ' t ' .
853 'WHERE ' . $this->db->in("child", $a_node_ids, false, "integer") .
854 'AND ' . $this->tree_pk . ' = ' . $this->db->quote($this->tree_id, "integer"));
855 while ($row = $this->db->fetchAssoc($res)) {
856 $this->depth_cache[$row["child"]] = (int) $row["depth"];
857 $this->parent_cache[$row["child"]] = (int) $row["parent"];
858 }
859 }
860
866 public function getPathId(int $a_endnode_id, int $a_startnode_id = 0): array
867 {
868 if (!$a_endnode_id) {
869 $this->logger->logStack(ilLogLevel::ERROR);
870 throw new InvalidArgumentException(__METHOD__ . ': No endnode given!');
871 }
872
873 // path id cache
874 if ($this->isCacheUsed() && isset($this->path_id_cache[$a_endnode_id][$a_startnode_id])) {
875 return $this->path_id_cache[$a_endnode_id][$a_startnode_id];
876 }
877
878 $pathIds = $this->getTreeImplementation()->getPathIds($a_endnode_id, $a_startnode_id);
879
880 if ($this->__isMainTree()) {
881 $this->path_id_cache[$a_endnode_id][$a_startnode_id] = $pathIds;
882 }
883 return $pathIds;
884 }
885
896 public function getNodePath(int $a_endnode_id, int $a_startnode_id = 0): array
897 {
898 $pathIds = $this->getPathId($a_endnode_id, $a_startnode_id);
899
900 // Abort if no path ids were found
901 if (count($pathIds) == 0) {
902 return [];
903 }
904
905 $types = [];
906 $data = [];
907 for ($i = 0; $i < count($pathIds); $i++) {
908 $types[] = 'integer';
909 $data[] = $pathIds[$i];
910 }
911
912 $query = 'SELECT t.depth,t.parent,t.child,d.obj_id,d.type,d.title ' .
913 'FROM ' . $this->table_tree . ' t ' .
914 'JOIN ' . $this->table_obj_reference . ' r ON r.ref_id = t.child ' .
915 'JOIN ' . $this->table_obj_data . ' d ON d.obj_id = r.obj_id ' .
916 'WHERE ' . $this->db->in('t.child', $data, false, 'integer') . ' ' .
917 'ORDER BY t.depth ';
918
919 $res = $this->db->queryF($query, $types, $data);
920
921 $titlePath = [];
922 while ($row = $this->db->fetchAssoc($res)) {
923 $titlePath[] = $row;
924 }
925 return $titlePath;
926 }
927
933 public function checkTree(): bool
934 {
935 $types = array('integer');
936 $query = 'SELECT lft,rgt FROM ' . $this->table_tree . ' ' .
937 'WHERE ' . $this->tree_pk . ' = %s ';
938
939 $res = $this->db->queryF($query, $types, array($this->tree_id));
940 $lft = $rgt = [];
941 while ($row = $this->db->fetchObject($res)) {
942 $lft[] = $row->lft;
943 $rgt[] = $row->rgt;
944 }
945
946 $all = array_merge($lft, $rgt);
947 $uni = array_unique($all);
948
949 if (count($all) != count($uni)) {
950 $message = 'Tree is corrupted!';
951 $this->logger->error($message);
953 }
954 return true;
955 }
956
961 public function checkTreeChilds(bool $a_no_zero_child = true): bool
962 {
963 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
964 'WHERE ' . $this->tree_pk . ' = %s ' .
965 'ORDER BY lft';
966 $r1 = $this->db->queryF($query, array('integer'), array($this->tree_id));
967
968 while ($row = $this->db->fetchAssoc($r1)) {
969 //echo "tree:".$row[$this->tree_pk].":lft:".$row["lft"].":rgt:".$row["rgt"].":child:".$row["child"].":<br>";
970 if (($row["child"] == 0) && $a_no_zero_child) {
971 $message = "Tree contains child with ID 0!";
972 $this->logger->error($message);
974 }
975
976 if ($this->table_obj_reference) {
977 // get object reference data
978 $query = 'SELECT * FROM ' . $this->table_obj_reference . ' WHERE ' . $this->ref_pk . ' = %s ';
979 $r2 = $this->db->queryF($query, array('integer'), array($row['child']));
980
981 //echo "num_childs:".$r2->numRows().":<br>";
982 if ($r2->numRows() == 0) {
983 $message = "No Object-to-Reference entry found for ID " . $row["child"] . "!";
984 $this->logger->error($message);
986 }
987 if ($r2->numRows() > 1) {
988 $message = "More Object-to-Reference entries found for ID " . $row["child"] . "!";
989 $this->logger->error($message);
991 }
992
993 // get object data
994 $obj_ref = $this->db->fetchAssoc($r2);
995
996 $query = 'SELECT * FROM ' . $this->table_obj_data . ' WHERE ' . $this->obj_pk . ' = %s';
997 $r3 = $this->db->queryF($query, array('integer'), array($obj_ref[$this->obj_pk]));
998 if ($r3->numRows() == 0) {
999 $message = " No child found for ID " . $obj_ref[$this->obj_pk] . "!";
1000 $this->logger->error($message);
1002 }
1003 if ($r3->numRows() > 1) {
1004 $message = "More childs found for ID " . $obj_ref[$this->obj_pk] . "!";
1005 $this->logger->error($message);
1007 }
1008 } else {
1009 // get only object data
1010 $query = 'SELECT * FROM ' . $this->table_obj_data . ' WHERE ' . $this->obj_pk . ' = %s';
1011 $r2 = $this->db->queryF($query, array('integer'), array($row['child']));
1012 //echo "num_childs:".$r2->numRows().":<br>";
1013 if ($r2->numRows() == 0) {
1014 $message = "No child found for ID " . $row["child"] . "!";
1015 $this->logger->error($message);
1017 }
1018 if ($r2->numRows() > 1) {
1019 $message = "More childs found for ID " . $row["child"] . "!";
1020 $this->logger->error($message);
1022 }
1023 }
1024 }
1025 return true;
1026 }
1027
1031 public function getMaximumDepth(): int
1032 {
1033 global $DIC;
1034
1035 $query = 'SELECT MAX(depth) depth FROM ' . $this->table_tree;
1036 $res = $this->db->query($query);
1037
1038 $row = $this->db->fetchAssoc($res);
1039 return (int) $row['depth'];
1040 }
1041
1045 public function getDepth(int $a_node_id): int
1046 {
1047 global $DIC;
1048
1049 if ($a_node_id) {
1050 if ($this->__isMainTree()) {
1051 $query = 'SELECT depth FROM ' . $this->table_tree . ' ' .
1052 'WHERE child = %s ';
1053 $res = $this->db->queryF($query, array('integer'), array($a_node_id));
1054 $row = $this->db->fetchObject($res);
1055 } else {
1056 $query = 'SELECT depth FROM ' . $this->table_tree . ' ' .
1057 'WHERE child = %s ' .
1058 'AND ' . $this->tree_pk . ' = %s ';
1059 $res = $this->db->queryF($query, array('integer', 'integer'), array($a_node_id, $this->tree_id));
1060 $row = $this->db->fetchObject($res);
1061 }
1062 return (int) ($row->depth ?? 0);
1063 }
1064 return 1;
1065 }
1066
1071 public function getNodeTreeData(int $a_node_id): array
1072 {
1073 global $DIC;
1074
1075 if (!$a_node_id) {
1076 $this->logger->logStack(ilLogLevel::ERROR);
1077 throw new InvalidArgumentException('Missing or empty parameter $a_node_id: ' . $a_node_id);
1078 }
1079
1080 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1081 'WHERE child = ' . $this->db->quote($a_node_id, 'integer');
1082 $res = $this->db->query($query);
1083 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
1084 return $row;
1085 }
1086 return [];
1087 }
1088
1094 public function getNodeData(int $a_node_id, ?int $a_tree_pk = null): array
1095 {
1096 if ($this->__isMainTree()) {
1097 if ($a_node_id < 1) {
1098 $message = 'No valid parameter given! $a_node_id: %s' . $a_node_id;
1099 $this->logger->error($message);
1100 throw new InvalidArgumentException($message);
1101 }
1102 }
1103
1104 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1105 $this->buildJoin() .
1106 'WHERE ' . $this->table_tree . '.child = %s ' .
1107 'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
1108 $res = $this->db->queryF($query, array('integer', 'integer'), array(
1109 $a_node_id,
1110 $a_tree_pk === null ? $this->tree_id : $a_tree_pk
1111 ));
1112 $row = $this->db->fetchAssoc($res);
1113 $row[$this->tree_pk] = $this->tree_id;
1114
1115 return $this->fetchNodeData($row);
1116 }
1117
1121 public function fetchNodeData(array $a_row): array
1122 {
1123 global $DIC;
1124
1125 $objDefinition = $DIC['objDefinition'];
1126 $lng = $DIC['lng'];
1127
1128 $data = $a_row;
1129 $data["desc"] = (string) ($a_row["description"] ?? ''); // for compability
1130
1131 // multilingual support systemobjects (sys) & categories (db)
1132 $translation_type = '';
1133 if (is_object($objDefinition)) {
1134 $translation_type = $objDefinition->getTranslationType($data["type"] ?? '');
1135 }
1136
1137 if ($translation_type == "sys") {
1138 if ($data["type"] == "rolf" && $data["obj_id"] != ROLE_FOLDER_ID) {
1139 $data["description"] = (string) $lng->txt("obj_" . $data["type"] . "_local_desc") . $data["title"] . $data["desc"];
1140 $data["desc"] = $lng->txt("obj_" . $data["type"] . "_local_desc") . $data["title"] . $data["desc"];
1141 $data["title"] = $lng->txt("obj_" . $data["type"] . "_local");
1142 } else {
1143 $data["title"] = $lng->txt("obj_" . $data["type"]);
1144 $data["description"] = $lng->txt("obj_" . $data["type"] . "_desc");
1145 $data["desc"] = $lng->txt("obj_" . $data["type"] . "_desc");
1146 }
1147 } elseif ($translation_type == "db") {
1148
1149 // Try to retrieve object translation from cache
1150 $lang_code = ''; // This did never work, because it was undefined before
1151 if ($this->isCacheUsed() &&
1152 array_key_exists($data["obj_id"] . '.' . $lang_code, $this->translation_cache)) {
1153 $key = $data["obj_id"] . '.' . $lang_code;
1154 $data["title"] = $this->translation_cache[$key]['title'];
1155 $data["description"] = $this->translation_cache[$key]['description'];
1156 $data["desc"] = $this->translation_cache[$key]['desc'];
1157 } else {
1158 // Object translation is not in cache, read it from database
1159 $query = 'SELECT title,description FROM object_translation ' .
1160 'WHERE obj_id = %s ' .
1161 'AND lang_code = %s ';
1162
1163 $res = $this->db->queryF($query, array('integer', 'text'), array(
1164 $data['obj_id'],
1165 $this->lang_code
1166 ));
1167 $row = $this->db->fetchObject($res);
1168
1169 if ($row) {
1170 $data["title"] = (string) $row->title;
1171 $data["description"] = ilStr::shortenTextExtended((string) $row->description, ilObject::DESC_LENGTH, true);
1172 $data["desc"] = (string) $row->description;
1173 }
1174
1175 // Store up to 1000 object translations in cache
1176 if ($this->isCacheUsed() && count($this->translation_cache) < 1000) {
1177 $key = $data["obj_id"] . '.' . $lang_code;
1178 $this->translation_cache[$key] = [];
1179 $this->translation_cache[$key]['title'] = $data["title"];
1180 $this->translation_cache[$key]['description'] = $data["description"];
1181 $this->translation_cache[$key]['desc'] = $data["desc"];
1182 }
1183 }
1184 }
1185
1186 // TODO: Handle this switch by module.xml definitions
1187 if (isset($data['type']) && ($data['type'] == 'crsr' || $data['type'] == 'catr' || $data['type'] == 'grpr' || $data['type'] === 'prgr')) {
1188 $data['title'] = ilContainerReference::_lookupTitle((int) $data['obj_id']);
1189 }
1190 return $data;
1191 }
1192
1198 protected function fetchTranslationFromObjectDataCache(array $a_obj_ids): void
1199 {
1200 global $DIC;
1201
1202 $ilObjDataCache = $DIC['ilObjDataCache'];
1203
1204 if ($this->isCacheUsed() && is_array($a_obj_ids) && is_object($ilObjDataCache)) {
1205 foreach ($a_obj_ids as $id) {
1206 $this->translation_cache[$id . '.']['title'] = $ilObjDataCache->lookupTitle((int) $id);
1207 $this->translation_cache[$id . '.']['description'] = $ilObjDataCache->lookupDescription((int) $id);
1208 $this->translation_cache[$id . '.']['desc'] =
1209 $this->translation_cache[$id . '.']['description'];
1210 }
1211 }
1212 }
1213
1218 public function isInTree(?int $a_node_id): bool
1219 {
1220 if (is_null($a_node_id) || !$a_node_id) {
1221 return false;
1222 }
1223 // is in tree cache
1224 if ($this->isCacheUsed() && isset($this->in_tree_cache[$a_node_id])) {
1225 return $this->in_tree_cache[$a_node_id];
1226 }
1227
1228 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1229 'WHERE ' . $this->table_tree . '.child = %s ' .
1230 'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s';
1231
1232 $res = $this->db->queryF($query, array('integer', 'integer'), array(
1233 $a_node_id,
1234 $this->tree_id
1235 ));
1236
1237 if ($res->numRows() > 0) {
1238 if ($this->__isMainTree()) {
1239 $this->in_tree_cache[$a_node_id] = true;
1240 }
1241 return true;
1242 } else {
1243 if ($this->__isMainTree()) {
1244 $this->in_tree_cache[$a_node_id] = false;
1245 }
1246 return false;
1247 }
1248 }
1249
1253 public function getParentNodeData(int $a_node_id): array
1254 {
1255 global $DIC;
1256
1257 $ilLog = $DIC['ilLog'];
1258
1259 if ($this->table_obj_reference) {
1260 // Use inner join instead of left join to improve performance
1261 $innerjoin = "JOIN " . $this->table_obj_reference . " ON v.child=" . $this->table_obj_reference . "." . $this->ref_pk . " " .
1262 "JOIN " . $this->table_obj_data . " ON " . $this->table_obj_reference . "." . $this->obj_pk . "=" . $this->table_obj_data . "." . $this->obj_pk . " ";
1263 } else {
1264 // Use inner join instead of left join to improve performance
1265 $innerjoin = "JOIN " . $this->table_obj_data . " ON v.child=" . $this->table_obj_data . "." . $this->obj_pk . " ";
1266 }
1267
1268 $query = 'SELECT * FROM ' . $this->table_tree . ' s, ' . $this->table_tree . ' v ' .
1269 $innerjoin .
1270 'WHERE s.child = %s ' .
1271 'AND s.parent = v.child ' .
1272 'AND s.' . $this->tree_pk . ' = %s ' .
1273 'AND v.' . $this->tree_pk . ' = %s';
1274 $res = $this->db->queryF($query, array('integer', 'integer', 'integer'), array(
1275 $a_node_id,
1276 $this->tree_id,
1277 $this->tree_id
1278 ));
1279 $row = $this->db->fetchAssoc($res);
1280 if (is_array($row)) {
1281 return $this->fetchNodeData($row);
1282 }
1283 return [];
1284 }
1285
1289 public function isGrandChild(int $a_startnode_id, int $a_querynode_id): bool
1290 {
1291 return $this->getRelation($a_startnode_id, $a_querynode_id) == self::RELATION_PARENT;
1292 }
1293
1298 public function addTree(int $a_tree_id, int $a_node_id = -1): bool
1299 {
1300 global $DIC;
1301
1302 // FOR SECURITY addTree() IS NOT ALLOWED ON MAIN TREE
1303 if ($this->__isMainTree()) {
1304 $message = sprintf(
1305 'Operation not allowed on main tree! $a_tree_if: %s $a_node_id: %s',
1306 $a_tree_id,
1307 $a_node_id
1308 );
1309 $this->logger->error($message);
1310 throw new InvalidArgumentException($message);
1311 }
1312
1313 if ($a_node_id <= 0) {
1314 $a_node_id = $a_tree_id;
1315 }
1316
1317 $query = 'INSERT INTO ' . $this->table_tree . ' (' .
1318 $this->tree_pk . ', child,parent,lft,rgt,depth) ' .
1319 'VALUES ' .
1320 '(%s,%s,%s,%s,%s,%s)';
1321 $res = $this->db->manipulateF(
1322 $query,
1323 array('integer', 'integer', 'integer', 'integer', 'integer', 'integer'),
1324 array(
1325 $a_tree_id,
1326 $a_node_id,
1327 0,
1328 1,
1329 2,
1330 1
1331 )
1332 );
1333
1334 return true;
1335 }
1336
1340 public function removeTree(int $a_tree_id): bool
1341 {
1342 if ($this->__isMainTree()) {
1343 $this->logger->logStack(ilLogLevel::ERROR);
1344 throw new InvalidArgumentException('Operation not allowed on main tree');
1345 }
1346 if (!$a_tree_id) {
1347 $this->logger->logStack(ilLogLevel::ERROR);
1348 throw new InvalidArgumentException('Missing parameter tree id');
1349 }
1350
1351 $query = 'DELETE FROM ' . $this->table_tree .
1352 ' WHERE ' . $this->tree_pk . ' = %s ';
1353 $this->db->manipulateF($query, array('integer'), array($a_tree_id));
1354 return true;
1355 }
1356
1362 public function moveToTrash(int $a_node_id, bool $a_set_deleted = false, int $a_deleted_by = 0): bool
1363 {
1364 global $DIC;
1365
1366 $user = $DIC->user();
1367 if (!$a_deleted_by) {
1368 $a_deleted_by = $user->getId();
1369 }
1370
1371 if (!$a_node_id) {
1372 $this->logger->logStack(ilLogLevel::ERROR);
1373 throw new InvalidArgumentException('No valid parameter given! $a_node_id: ' . $a_node_id);
1374 }
1375
1376 $query = $this->getTreeImplementation()->getSubTreeQuery($this->getNodeTreeData($a_node_id), [], false);
1377 $res = $this->db->query($query);
1378
1379 $subnodes = [];
1380 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
1381 $subnodes[] = (int) $row['child'];
1382 }
1383
1384 if (!count($subnodes)) {
1385 // Possibly already deleted
1386 return false;
1387 }
1388
1389 if ($a_set_deleted) {
1390 ilObject::setDeletedDates($subnodes, $a_deleted_by);
1391 }
1392 // netsted set <=> mp
1393 $this->getTreeImplementation()->moveToTrash($a_node_id);
1394 return true;
1395 }
1396
1400 public function isDeleted(int $a_node_id): bool
1401 {
1402 return $this->isSaved($a_node_id);
1403 }
1404
1409 public function isSaved(int $a_node_id): bool
1410 {
1411 if ($this->isCacheUsed() && isset($this->is_saved_cache[$a_node_id])) {
1412 return $this->is_saved_cache[$a_node_id];
1413 }
1414
1415 $query = 'SELECT ' . $this->tree_pk . ' FROM ' . $this->table_tree . ' ' .
1416 'WHERE child = %s ';
1417 $res = $this->db->queryF($query, array('integer'), array($a_node_id));
1418 $row = $this->db->fetchAssoc($res);
1419
1420 $tree_id = $row[$this->tree_pk] ?? 0;
1421 if ($tree_id < 0) {
1422 if ($this->__isMainTree()) {
1423 $this->is_saved_cache[$a_node_id] = true;
1424 }
1425 return true;
1426 } else {
1427 if ($this->__isMainTree()) {
1428 $this->is_saved_cache[$a_node_id] = false;
1429 }
1430 return false;
1431 }
1432 }
1433
1437 public function preloadDeleted(array $a_node_ids): void
1438 {
1439 if (!is_array($a_node_ids) || !$this->isCacheUsed()) {
1440 return;
1441 }
1442
1443 $query = 'SELECT ' . $this->tree_pk . ', child FROM ' . $this->table_tree . ' ' .
1444 'WHERE ' . $this->db->in("child", $a_node_ids, false, "integer");
1445
1446 $res = $this->db->query($query);
1447 while ($row = $this->db->fetchAssoc($res)) {
1448 if ($row[$this->tree_pk] < 0) {
1449 if ($this->__isMainTree()) {
1450 $this->is_saved_cache[$row["child"]] = true;
1451 }
1452 } else {
1453 if ($this->__isMainTree()) {
1454 $this->is_saved_cache[$row["child"]] = false;
1455 }
1456 }
1457 }
1458 }
1459
1464 public function getSavedNodeData(int $a_parent_id): array
1465 {
1466 global $DIC;
1467
1468 if (!isset($a_parent_id)) {
1469 $message = "No node_id given!";
1470 $this->logger->error($message);
1471 throw new InvalidArgumentException($message);
1472 }
1473
1474 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1475 $this->buildJoin() .
1476 'WHERE ' . $this->table_tree . '.' . $this->tree_pk . ' < %s ' .
1477 'AND ' . $this->table_tree . '.parent = %s';
1478 $res = $this->db->queryF($query, array('integer', 'integer'), array(
1479 0,
1480 $a_parent_id
1481 ));
1482
1483 $saved = [];
1484 while ($row = $this->db->fetchAssoc($res)) {
1485 $saved[] = $this->fetchNodeData($row);
1486 }
1487
1488 return $saved;
1489 }
1490
1494 public function getSavedNodeObjIds(array $a_obj_ids): array
1495 {
1496 global $DIC;
1497
1498 $query = 'SELECT ' . $this->table_obj_data . '.obj_id FROM ' . $this->table_tree . ' ' .
1499 $this->buildJoin() .
1500 'WHERE ' . $this->table_tree . '.' . $this->tree_pk . ' < ' . $this->db->quote(0, 'integer') . ' ' .
1501 'AND ' . $this->db->in($this->table_obj_data . '.obj_id', $a_obj_ids, false, 'integer');
1502 $res = $this->db->query($query);
1503 $saved = [];
1504 while ($row = $this->db->fetchAssoc($res)) {
1505 $saved[] = (int) $row['obj_id'];
1506 }
1507
1508 return $saved;
1509 }
1510
1515 public function getParentId(int $a_node_id): ?int
1516 {
1517 global $DIC;
1518 if ($this->__isMainTree()) {
1519 $query = 'SELECT parent FROM ' . $this->table_tree . ' ' .
1520 'WHERE child = %s ';
1521 $res = $this->db->queryF(
1522 $query,
1523 ['integer'],
1524 [$a_node_id]
1525 );
1526 } else {
1527 $query = 'SELECT parent FROM ' . $this->table_tree . ' ' .
1528 'WHERE child = %s ' .
1529 'AND ' . $this->tree_pk . ' = %s ';
1530 $res = $this->db->queryF($query, array('integer', 'integer'), array(
1531 $a_node_id,
1532 $this->tree_id
1533 ));
1534 }
1535
1536 if ($row = $this->db->fetchObject($res)) {
1537 return (int) $row->parent;
1538 }
1539 return null;
1540 }
1541
1547 public function getLeftValue(int $a_node_id): int
1548 {
1549 global $DIC;
1550
1551 if (!isset($a_node_id)) {
1552 $message = "No node_id given!";
1553 $this->logger->error($message);
1554 throw new InvalidArgumentException($message);
1555 }
1556
1557 $query = 'SELECT lft FROM ' . $this->table_tree . ' ' .
1558 'WHERE child = %s ' .
1559 'AND ' . $this->tree_pk . ' = %s ';
1560 $res = $this->db->queryF($query, array('integer', 'integer'), array(
1561 $a_node_id,
1562 $this->tree_id
1563 ));
1564 $row = $this->db->fetchObject($res);
1565 return (int) $row->lft;
1566 }
1567
1573 public function getChildSequenceNumber(array $a_node, string $type = ""): int
1574 {
1575 if (!isset($a_node)) {
1576 $message = "No node_id given!";
1577 $this->logger->error($message);
1578 throw new InvalidArgumentException($message);
1579 }
1580
1581 if ($type) {
1582 $query = 'SELECT count(*) cnt FROM ' . $this->table_tree . ' ' .
1583 $this->buildJoin() .
1584 'WHERE lft <= %s ' .
1585 'AND type = %s ' .
1586 'AND parent = %s ' .
1587 'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
1588
1589 $res = $this->db->queryF($query, array('integer', 'text', 'integer', 'integer'), array(
1590 $a_node['lft'],
1591 $type,
1592 $a_node['parent'],
1593 $this->tree_id
1594 ));
1595 } else {
1596 $query = 'SELECT count(*) cnt FROM ' . $this->table_tree . ' ' .
1597 $this->buildJoin() .
1598 'WHERE lft <= %s ' .
1599 'AND parent = %s ' .
1600 'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
1601
1602 $res = $this->db->queryF($query, array('integer', 'integer', 'integer'), array(
1603 $a_node['lft'],
1604 $a_node['parent'],
1605 $this->tree_id
1606 ));
1607 }
1608 $row = $this->db->fetchAssoc($res);
1609 return (int) $row["cnt"];
1610 }
1611
1612 public function readRootId(): int
1613 {
1614 $query = 'SELECT child FROM ' . $this->table_tree . ' ' .
1615 'WHERE parent = %s ' .
1616 'AND ' . $this->tree_pk . ' = %s ';
1617 $res = $this->db->queryF($query, array('integer', 'integer'), array(
1618 0,
1619 $this->tree_id
1620 ));
1621 $this->root_id = 0;
1622 if ($row = $this->db->fetchObject($res)) {
1623 $this->root_id = (int) $row->child;
1624 }
1625 return $this->root_id;
1626 }
1627
1628 public function getRootId(): int
1629 {
1630 return $this->root_id;
1631 }
1632
1633 public function setRootId(int $a_root_id): void
1634 {
1635 $this->root_id = $a_root_id;
1636 }
1637
1638 public function getTreeId(): int
1639 {
1640 return $this->tree_id;
1641 }
1642
1643 public function setTreeId(int $a_tree_id): void
1644 {
1645 $this->tree_id = $a_tree_id;
1646 }
1647
1654 public function fetchSuccessorNode(int $a_node_id, string $a_type = ""): ?array
1655 {
1656 // get lft value for current node
1657 $query = 'SELECT lft FROM ' . $this->table_tree . ' ' .
1658 'WHERE ' . $this->table_tree . '.child = %s ' .
1659 'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
1660 $res = $this->db->queryF($query, array('integer', 'integer'), array(
1661 $a_node_id,
1662 $this->tree_id
1663 ));
1664 $curr_node = $this->db->fetchAssoc($res);
1665
1666 if ($a_type) {
1667 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1668 $this->buildJoin() .
1669 'WHERE lft > %s ' .
1670 'AND ' . $this->table_obj_data . '.type = %s ' .
1671 'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ' .
1672 'ORDER BY lft ';
1673 $this->db->setLimit(1, 0);
1674 $res = $this->db->queryF($query, array('integer', 'text', 'integer'), array(
1675 $curr_node['lft'],
1676 $a_type,
1677 $this->tree_id
1678 ));
1679 } else {
1680 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1681 $this->buildJoin() .
1682 'WHERE lft > %s ' .
1683 'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ' .
1684 'ORDER BY lft ';
1685 $this->db->setLimit(1, 0);
1686 $res = $this->db->queryF($query, array('integer', 'integer'), array(
1687 $curr_node['lft'],
1688 $this->tree_id
1689 ));
1690 }
1691
1692 if ($res->numRows() < 1) {
1693 return null;
1694 } else {
1695 $row = $this->db->fetchAssoc($res);
1696 return $this->fetchNodeData($row);
1697 }
1698 }
1699
1706 public function fetchPredecessorNode(int $a_node_id, string $a_type = ""): ?array
1707 {
1708 if (!isset($a_node_id)) {
1709 $message = "No node_id given!";
1710 $this->logger->error($message);
1711 throw new InvalidArgumentException($message);
1712 }
1713
1714 // get lft value for current node
1715 $query = 'SELECT lft FROM ' . $this->table_tree . ' ' .
1716 'WHERE ' . $this->table_tree . '.child = %s ' .
1717 'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ';
1718 $res = $this->db->queryF($query, array('integer', 'integer'), array(
1719 $a_node_id,
1720 $this->tree_id
1721 ));
1722
1723 $curr_node = $this->db->fetchAssoc($res);
1724
1725 if ($a_type) {
1726 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1727 $this->buildJoin() .
1728 'WHERE lft < %s ' .
1729 'AND ' . $this->table_obj_data . '.type = %s ' .
1730 'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ' .
1731 'ORDER BY lft DESC';
1732 $this->db->setLimit(1, 0);
1733 $res = $this->db->queryF($query, array('integer', 'text', 'integer'), array(
1734 $curr_node['lft'],
1735 $a_type,
1736 $this->tree_id
1737 ));
1738 } else {
1739 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1740 $this->buildJoin() .
1741 'WHERE lft < %s ' .
1742 'AND ' . $this->table_tree . '.' . $this->tree_pk . ' = %s ' .
1743 'ORDER BY lft DESC';
1744 $this->db->setLimit(1, 0);
1745 $res = $this->db->queryF($query, array('integer', 'integer'), array(
1746 $curr_node['lft'],
1747 $this->tree_id
1748 ));
1749 }
1750
1751 if ($res->numRows() < 1) {
1752 return null;
1753 } else {
1754 $row = $this->db->fetchAssoc($res);
1755 return $this->fetchNodeData($row);
1756 }
1757 }
1758
1763 public function renumber(int $node_id = 1, int $i = 1): int
1764 {
1765 $renumber_callable = function (ilDBInterface $db) use ($node_id, $i, &$return) {
1766 $return = $this->__renumber($node_id, $i);
1767 };
1768
1769 // LOCKED ###################################
1770 if ($this->__isMainTree()) {
1771 $ilAtomQuery = $this->db->buildAtomQuery();
1772 $ilAtomQuery->addTableLock($this->table_tree);
1773
1774 $ilAtomQuery->addQueryCallable($renumber_callable);
1775 $ilAtomQuery->run();
1776 } else {
1777 $renumber_callable($this->db);
1778 }
1779 return $return;
1780 }
1781
1787 private function __renumber(int $node_id = 1, int $i = 1): int
1788 {
1789 if ($this->isRepositoryTree()) {
1790 $query = 'UPDATE ' . $this->table_tree . ' SET lft = %s WHERE child = %s';
1791 $this->db->manipulateF(
1792 $query,
1793 array('integer', 'integer'),
1794 array(
1795 $i,
1796 $node_id
1797 )
1798 );
1799 } else {
1800 $query = 'UPDATE ' . $this->table_tree . ' SET lft = %s WHERE child = %s AND tree = %s';
1801 $this->db->manipulateF(
1802 $query,
1803 array('integer', 'integer', 'integer'),
1804 array(
1805 $i,
1806 $node_id,
1807 $this->tree_id
1808 )
1809 );
1810 }
1811
1812 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1813 'WHERE parent = ' . $this->db->quote($node_id, 'integer') . ' ' .
1814 'ORDER BY lft';
1815 $res = $this->db->query($query);
1816
1817 $childs = [];
1818 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
1819 $childs[] = (int) $row->child;
1820 }
1821
1822 foreach ($childs as $child) {
1823 $i = $this->__renumber($child, $i + 1);
1824 }
1825 $i++;
1826
1827 // Insert a gap at the end of node, if the node has children
1828 if (count($childs) > 0) {
1829 $i += $this->gap * 2;
1830 }
1831
1832 if ($this->isRepositoryTree()) {
1833 $query = 'UPDATE ' . $this->table_tree . ' SET rgt = %s WHERE child = %s';
1834 $res = $this->db->manipulateF(
1835 $query,
1836 array('integer', 'integer'),
1837 array(
1838 $i,
1839 $node_id
1840 )
1841 );
1842 } else {
1843 $query = 'UPDATE ' . $this->table_tree . ' SET rgt = %s WHERE child = %s AND tree = %s';
1844 $res = $this->db->manipulateF($query, array('integer', 'integer', 'integer'), array(
1845 $i,
1846 $node_id,
1847 $this->tree_id
1848 ));
1849 }
1850 return $i;
1851 }
1852
1857 public function checkForParentType(int $a_ref_id, string $a_type, bool $a_exclude_source_check = false): int
1858 {
1859 // #12577
1860 $cache_key = $a_ref_id . '.' . $a_type . '.' . ((int) $a_exclude_source_check);
1861
1862 // Try to return a cached result
1863 if ($this->isCacheUsed() &&
1864 array_key_exists($cache_key, $this->parent_type_cache)) {
1865 return (int) $this->parent_type_cache[$cache_key];
1866 }
1867
1868 // Store up to 1000 results in cache
1869 $do_cache = ($this->__isMainTree() && count($this->parent_type_cache) < 1000);
1870
1871 // ref_id is not in tree
1872 if (!$this->isInTree($a_ref_id)) {
1873 if ($do_cache) {
1874 $this->parent_type_cache[$cache_key] = false;
1875 }
1876 return 0;
1877 }
1878
1879 $path = array_reverse($this->getPathFull($a_ref_id));
1880
1881 // remove first path entry as it is requested node
1882 if ($a_exclude_source_check) {
1883 array_shift($path);
1884 }
1885
1886 foreach ($path as $node) {
1887 // found matching parent
1888 if ($node["type"] == $a_type) {
1889 if ($do_cache) {
1890 $this->parent_type_cache[$cache_key] = (int) $node["child"];
1891 }
1892 return (int) $node["child"];
1893 }
1894 }
1895
1896 if ($do_cache) {
1897 $this->parent_type_cache[$cache_key] = false;
1898 }
1899 return 0;
1900 }
1901
1907 public static function _removeEntry(int $a_tree, int $a_child, string $a_db_table = "tree"): void
1908 {
1909 global $DIC;
1910
1911 $db = $DIC->database();
1912
1913 if ($a_db_table === 'tree') {
1914 if ($a_tree == 1 && $a_child == ROOT_FOLDER_ID) {
1915 $message = sprintf(
1916 'Tried to delete root node! $a_tree: %s $a_child: %s',
1917 $a_tree,
1918 $a_child
1919 );
1920 ilLoggerFactory::getLogger('tree')->error($message);
1921 throw new InvalidArgumentException($message);
1922 }
1923 }
1924
1925 $query = 'DELETE FROM ' . $a_db_table . ' ' .
1926 'WHERE tree = %s ' .
1927 'AND child = %s ';
1928 $res = $db->manipulateF($query, array('integer', 'integer'), array(
1929 $a_tree,
1930 $a_child
1931 ));
1932 }
1933
1937 public function __isMainTree(): bool
1938 {
1939 return $this->table_tree === 'tree';
1940 }
1941
1948 public function __checkDelete(array $a_node): bool
1949 {
1950 $query = $this->getTreeImplementation()->getSubTreeQuery($a_node, [], false);
1951 $this->logger->debug($query);
1952 $res = $this->db->query($query);
1953
1954 $counter = (int) $lft_childs = [];
1955 while ($row = $this->db->fetchObject($res)) {
1956 $lft_childs[$row->child] = (int) $row->parent;
1957 ++$counter;
1958 }
1959
1960 // CHECK FOR DUPLICATE CHILD IDS
1961 if ($counter != count($lft_childs)) {
1962 $message = 'Duplicate entries for "child" in maintree! $a_node_id: ' . $a_node['child'];
1963
1964 $this->logger->error($message);
1966 }
1967
1968 // GET SUBTREE BY PARENT RELATION
1969 $parent_childs = [];
1970 $this->__getSubTreeByParentRelation((int)$a_node['child'], $parent_childs);
1971 $this->__validateSubtrees($lft_childs, $parent_childs);
1972
1973 return true;
1974 }
1975
1980 public function __getSubTreeByParentRelation(int $a_node_id, array &$parent_childs): bool
1981 {
1982 // GET PARENT ID
1983 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
1984 'WHERE child = %s ' .
1985 'AND tree = %s ';
1986 $res = $this->db->queryF($query, array('integer', 'integer'), array(
1987 $a_node_id,
1988 $this->tree_id
1989 ));
1990
1991 $counter = 0;
1992 while ($row = $this->db->fetchObject($res)) {
1993 $parent_childs[$a_node_id] = (int) $row->parent;
1994 ++$counter;
1995 }
1996 // MULTIPLE ENTRIES
1997 if ($counter > 1) {
1998 $message = 'Multiple entries in maintree! $a_node_id: ' . $a_node_id;
1999
2000 $this->logger->error($message);
2002 }
2003
2004 // GET ALL CHILDS
2005 $query = 'SELECT * FROM ' . $this->table_tree . ' ' .
2006 'WHERE parent = %s ';
2007 $res = $this->db->queryF($query, array('integer'), array($a_node_id));
2008
2009 while ($row = $this->db->fetchObject($res)) {
2010 // RECURSION
2011 $this->__getSubTreeByParentRelation((int) $row->child, $parent_childs);
2012 }
2013 return true;
2014 }
2015
2023 public function __validateSubtrees(array &$lft_childs, array $parent_childs): bool
2024 {
2025 // SORT BY KEY
2026 ksort($lft_childs);
2027 ksort($parent_childs);
2028
2029 $this->logger->debug('left childs ' . print_r($lft_childs, true));
2030 $this->logger->debug('parent childs ' . print_r($parent_childs, true));
2031
2032 if (count($lft_childs) != count($parent_childs)) {
2033 $message = '(COUNT) Tree is corrupted! Left/Right subtree does not comply with parent relation';
2034 $this->logger->error($message);
2036 }
2037
2038 foreach ($lft_childs as $key => $value) {
2039 if ($parent_childs[$key] != $value) {
2040 $message = '(COMPARE) Tree is corrupted! Left/Right subtree does not comply with parent relation';
2041 $this->logger->error($message);
2043 }
2044 if ($key == ROOT_FOLDER_ID) {
2045 $message = '(ROOT_FOLDER) Tree is corrupted! Tried to delete root folder';
2046 $this->logger->error($message);
2048 }
2049 }
2050 return true;
2051 }
2052
2061 public function moveTree(int $a_source_id, int $a_target_id, int $a_location = self::POS_LAST_NODE): void
2062 {
2063 $old_parent_id = $this->getParentId($a_source_id);
2064 $this->getTreeImplementation()->moveTree($a_source_id, $a_target_id, $a_location);
2065 if (isset($GLOBALS['DIC']["ilAppEventHandler"]) && $this->__isMainTree()) {
2066 $GLOBALS['DIC']['ilAppEventHandler']->raise(
2067 "Services/Tree",
2068 "moveTree",
2069 array(
2070 'tree' => $this->table_tree,
2071 'source_id' => $a_source_id,
2072 'target_id' => $a_target_id,
2073 'old_parent_id' => $old_parent_id
2074 )
2075 );
2076 }
2077 }
2078
2084 public function getRbacSubtreeInfo(int $a_endnode_id): array
2085 {
2086 return $this->getTreeImplementation()->getSubtreeInfo($a_endnode_id);
2087 }
2088
2092 public function getSubTreeQuery(
2093 int $a_node_id,
2094 array $a_fields = [],
2095 array $a_types = [],
2096 bool $a_force_join_reference = false
2097 ): string {
2098 return $this->getTreeImplementation()->getSubTreeQuery(
2099 $this->getNodeTreeData($a_node_id),
2100 $a_types,
2101 $a_force_join_reference,
2102 $a_fields
2103 );
2104 }
2105
2106 public function getTrashSubTreeQuery(
2107 int $a_node_id,
2108 array $a_fields = [],
2109 array $a_types = [],
2110 bool $a_force_join_reference = false
2111 ): string {
2112 return $this->getTreeImplementation()->getTrashSubTreeQuery(
2113 $this->getNodeTreeData($a_node_id),
2114 $a_types,
2115 $a_force_join_reference,
2116 $a_fields
2117 );
2118 }
2119
2126 public function getSubTreeFilteredByObjIds(int $a_node_id, array $a_obj_ids, array $a_fields = []): array
2127 {
2128 $node = $this->getNodeData($a_node_id);
2129 if (!count($node)) {
2130 return [];
2131 }
2132
2133 $res = [];
2134
2135 $query = $this->getTreeImplementation()->getSubTreeQuery($node, [], true, array($this->ref_pk));
2136
2137 $fields = '*';
2138 if (count($a_fields)) {
2139 $fields = implode(',', $a_fields);
2140 }
2141
2142 $query = "SELECT " . $fields .
2143 " FROM " . $this->getTreeTable() .
2144 " " . $this->buildJoin() .
2145 " WHERE " . $this->getTableReference() . "." . $this->ref_pk . " IN (" . $query . ")" .
2146 " AND " . $this->db->in($this->getObjectDataTable() . "." . $this->obj_pk, $a_obj_ids, false, "integer");
2147 $set = $this->db->query($query);
2148 while ($row = $this->db->fetchAssoc($set)) {
2149 $res[] = $row;
2150 }
2151
2152 return $res;
2153 }
2154
2155 public function deleteNode(int $a_tree_id, int $a_node_id): void
2156 {
2157 $query = 'DELETE FROM tree where ' .
2158 'child = ' . $this->db->quote($a_node_id, 'integer') . ' ' .
2159 'AND tree = ' . $this->db->quote($a_tree_id, 'integer');
2160 $this->db->manipulate($query);
2161
2162 $this->eventHandler->raise(
2163 "Services/Tree",
2164 "deleteNode",
2165 [
2166 'tree' => $this->table_tree,
2167 'node_id' => $a_node_id,
2168 'tree_id' => $a_tree_id
2169 ]
2170 );
2171 }
2172
2177 public function lookupTrashedObjectTypes(): array
2178 {
2179 $query = 'SELECT DISTINCT(o.type) ' . $this->db->quoteIdentifier('type') .
2180 ' FROM tree t JOIN object_reference r ON child = r.ref_id ' .
2181 'JOIN object_data o on r.obj_id = o.obj_id ' .
2182 'WHERE tree < ' . $this->db->quote(0, 'integer') . ' ' .
2183 'AND child = -tree ' .
2184 'GROUP BY o.type';
2185 $res = $this->db->query($query);
2186
2187 $types_deleted = [];
2188 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
2189 $types_deleted[] = (string) $row->type;
2190 }
2191 return $types_deleted;
2192 }
2193
2197 public function isRepositoryTree(): bool
2198 {
2199 return $this->table_tree == 'tree';
2200 }
2201} // END class.tree
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
if(!defined('PATH_SEPARATOR')) $GLOBALS['_PEAR_default_error_mode']
Definition: PEAR.php:64
Global event handler.
static quoteArray(array $a_array)
Quotes all members of an array for usage in DB query statement.
static _lookupTitle(int $obj_id)
Thrown if invalid tree strucutes are found.
static getLogger(string $a_component_id)
Get component logger.
Component logger with individual log levels by component id.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Base class for nested set path based trees.
static setDeletedDates(array $ref_ids, int $user_id)
const DESC_LENGTH
static _resetDeletedDate(int $ref_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static shortenTextExtended(string $a_str, int $a_len, bool $a_dots=false, bool $a_next_blank=false, bool $a_keep_extension=false)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
isCacheUsed()
Check if cache is active.
lookupTrashedObjectTypes()
Lookup object types in trash.
string $tree_pk
column name containing tree id in tree table
string $table_tree
table name of tree table
fetchNodeData(array $a_row)
get data of parent node from tree and object_data
getRbacSubtreeInfo(int $a_endnode_id)
This method is used for change existing objects and returns all necessary information for this action...
setReferenceTablePK(string $a_column_name)
set column containing primary key in reference table
getTreePk()
Get tree primary key.
getParentCache()
Get parent cache.
getChildSequenceNumber(array $a_node, string $type="")
get sequence number of node in sibling sequence
fetchPredecessorNode(int $a_node_id, string $a_type="")
get node data of predecessor node
insertNodeFromTrash(int $a_source_id, int $a_target_id, int $a_tree_id, int $a_pos=self::POS_LAST_NODE, bool $a_reset_deleted_date=false)
Insert node from trash deletes trash entry.
isInTree(?int $a_node_id)
get all information of a node.
setRootId(int $a_root_id)
isGrandChild(int $a_startnode_id, int $a_querynode_id)
checks if a node is in the path of an other node
getPathFull(int $a_endnode_id, int $a_startnode_id=0)
get path from a given startnode to a given endnode if startnode is not given the rootnode is startnod...
isDeleted(int $a_node_id)
This is a wrapper for isSaved() with a more useful name.
getNodeTreeData(int $a_node_id)
return all columns of tabel tree
fetchTranslationFromObjectDataCache(array $a_obj_ids)
Get translation data from object cache (trigger in object cache on preload)
isRepositoryTree()
check if current tree instance operates on repository tree table
array $parent_cache
const RELATION_EQUALS
array $parent_type_cache
const DEFAULT_GAP
insertNode(int $a_node_id, int $a_parent_id, int $a_pos=self::POS_LAST_NODE, bool $a_reset_deletion_date=false)
insert new node with node_id under parent node with parent_id
addTree(int $a_tree_id, int $a_node_id=-1)
create a new tree to do: ???
getDepthCache()
Get depth cache.
static _removeEntry(int $a_tree, int $a_child, string $a_db_table="tree")
STATIC METHOD Removes a single entry from a tree.
getNodeData(int $a_node_id, ?int $a_tree_pk=null)
get all information of a node.
string $ref_pk
column name containing primary key in reference table
renumber(int $node_id=1, int $i=1)
Wrapper for renumber.
setTreeId(int $a_tree_id)
getTreeTable()
Get tree table name.
array $path_id_cache
useCache(bool $a_use=true)
Use Cache (usually activated)
getRelationOfNodes(array $a_node_a_arr, array $a_node_b_arr)
get relation of two nodes by node data
moveTree(int $a_source_id, int $a_target_id, int $a_location=self::POS_LAST_NODE)
Move Tree Implementation @access public.
ilDBInterface $db
array $is_saved_cache
const POS_LAST_NODE
string $obj_pk
column name containing primary key in object table
bool $use_cache
array $oc_preloaded
int $root_id
points to root node (may be a subtree)
setTreeTablePK(string $a_column_name)
set column containing primary key in tree table
ilTreeImplementation $tree_impl
setObjectTablePK(string $a_column_name)
set column containing primary key in object table
getTableReference()
Get reference table if available.
const TREE_TYPE_NESTED_SET
getSubTreeQuery(int $a_node_id, array $a_fields=[], array $a_types=[], bool $a_force_join_reference=false)
Get tree subtree query.
__renumber(int $node_id=1, int $i=1)
This method is private.
array $depth_cache
__validateSubtrees(array &$lft_childs, array $parent_childs)
setTableNames(string $a_table_tree, string $a_table_obj_data, string $a_table_obj_reference="")
set table names The primary key of the table containing your object_data must be 'obj_id' You may use...
getSubTreeIds(int $a_ref_id)
Get all ids of subnodes.
int $gap
Size of the gaps to be created in the nested sets sequence numbering of the tree nodes.
array $in_tree_cache
getChilds(int $a_node_id, string $a_order="", string $a_direction="ASC")
get child nodes of given node
static lookupTreesForNode(int $node_id)
checkTree()
check consistence of tree all left & right values are checked if they are exists only once
ilLogger $logger
getSubTree(array $a_node, bool $a_with_data=true, array $a_type=[])
get all nodes in the subtree under specified node
resetInTreeCache()
reset in tree cache
getLeftValue(int $a_node_id)
get left value of given node
getParentNodeData(int $a_node_id)
get data of parent node from tree and object_data
checkForParentType(int $a_ref_id, string $a_type, bool $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...
getSavedNodeObjIds(array $a_obj_ids)
get object id of saved/deleted nodes
deleteTree(array $a_node)
delete node and the whole subtree under this node
getMaximumDepth()
Return the current maximum depth in the tree.
getChildsByType(int $a_node_id, string $a_type)
get child nodes of given node by object type
moveToTrash(int $a_node_id, bool $a_set_deleted=false, int $a_deleted_by=0)
Move node to trash bin.
const POS_FIRST_NODE
const RELATION_PARENT
deleteNode(int $a_tree_id, int $a_node_id)
initLangCode()
Do not use it Store user language.
const RELATION_NONE
buildJoin()
build join depending on table settings @access private
getTreeImplementation()
Get tree implementation.
getPathId(int $a_endnode_id, int $a_startnode_id=0)
get path from a given startnode to a given endnode if startnode is not given the rootnode is startnod...
initTreeImplementation()
Init tree implementation.
const RELATION_SIBLING
getChildsByTypeFilter(int $a_node_id, array $a_types, string $a_order="", string $a_direction="ASC")
get child nodes of given node by object type
getObjectDataTable()
Get object data table.
string $lang_code
getRelation(int $a_node_a, int $a_node_b)
Get relation of two nodes.
getChildIds(int $a_node)
getNodePath(int $a_endnode_id, int $a_startnode_id=0)
Returns the node path for the specified object reference.
isSaved(int $a_node_id)
Use method isDeleted.
const TREE_TYPE_MATERIALIZED_PATH
const DEFAULT_LANGUAGE
string $table_obj_data
table name of object_data table
getTrashSubTreeQuery(int $a_node_id, array $a_fields=[], array $a_types=[], bool $a_force_join_reference=false)
preloadDeleted(array $a_node_ids)
Preload deleted information.
array $translation_cache
__getSubTreeByParentRelation(int $a_node_id, array &$parent_childs)
__isMainTree()
Check if operations are done on main tree.
getFilteredSubTree(int $a_node_id, array $a_filter=[])
get filtered subtree get all subtree nodes beginning at a specific node excluding specific object typ...
string $table_obj_reference
table name of object_reference table
checkTreeChilds(bool $a_no_zero_child=true)
check, if all childs of tree nodes exist in object table
getGap()
Get default gap.
const RELATION_CHILD
__construct(int $a_tree_id, int $a_root_id=0, ilDBInterface $db=null)
int $tree_id
to use different trees in one db-table
removeTree(int $a_tree_id)
remove an existing tree
getDepth(int $a_node_id)
return depth of a node in tree
getParentId(int $a_node_id)
get parent id of given node
preloadDepthParent(array $a_node_ids)
Preload depth/parent.
getFilteredChilds(array $a_filter, int $a_node, string $a_order="", string $a_direction="ASC")
get child nodes of given node (exclude filtered obj_types)
getSavedNodeData(int $a_parent_id)
get data saved/deleted nodes
fetchSuccessorNode(int $a_node_id, string $a_type="")
get node data of successor node
__checkDelete(array $a_node)
Check for deleteTree() compares a subtree of a given node by checking lft, rgt against parent relatio...
validateParentRelations()
Validate parent relations of tree.
ilAppEventHandler $eventHandler
getSubTreeFilteredByObjIds(int $a_node_id, array $a_obj_ids, array $a_fields=[])
get all node ids in the subtree under specified node id, filter by object ids
if(!file_exists(getcwd() . '/ilias.ini.php'))
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: confirmReg.php:20
const ROLE_FOLDER_ID
Definition: constants.php:34
const ROOT_FOLDER_ID
Definition: constants.php:32
return['3gp', '7z', 'ai', 'aif', 'aifc', 'aiff', 'au', 'arw', 'avi', 'backup', 'bak', 'bas', 'bpmn', 'bpmn2', 'bmp', 'bib', 'bibtex', 'bz', 'bz2', 'c', 'c++', 'cc', 'cct', 'cdf', 'cer', 'class', 'cls', 'conf', 'cpp', 'crt', 'crs', 'crw', 'cr2', 'css', 'cst', 'csv', 'cur', 'db', 'dcr', 'des', 'dng', 'doc', 'docx', 'dot', 'dotx', 'dtd', 'dvi', 'el', 'eps', 'epub', 'f', 'f77', 'f90', 'flv', 'for', 'g3', 'gif', 'gl', 'gan', 'ggb', 'gsd', 'gsm', 'gtar', 'gz', 'gzip', 'h', 'hpp', 'htm', 'html', 'htmls', 'ibooks', 'ico', 'ics', 'ini', 'ipynb', 'java', 'jbf', 'jpeg', 'jpg', 'js', 'jsf', 'jso', 'json', 'latex', 'lang', 'less', 'log', 'lsp', 'ltx', 'm1v', 'm2a', 'm2v', 'm3u', 'm4a', 'm4v', 'markdown', 'm', 'mat', 'md', 'mdl', 'mdown', 'mid', 'min', 'midi', 'mobi', 'mod', 'mov', 'movie', 'mp2', 'mp3', 'mp4', 'mpa', 'mpeg', 'mpg', 'mph', 'mpga', 'mpp', 'mpt', 'mpv', 'mpx', 'mv', 'mw', 'mv4', 'nb', 'nbp', 'nef', 'nif', 'niff', 'obj', 'obm', 'odt', 'ods', 'odp', 'odg', 'odf', 'oga', 'ogg', 'ogv', 'old', 'p', 'pas', 'pbm', 'pcl', 'pct', 'pcx', 'pdf', 'pgm', 'pic', 'pict', 'png', 'por', 'pov', 'project', 'properties', 'ppa', 'ppm', 'pps', 'ppsx', 'ppt', 'pptx', 'ppz', 'ps', 'psd', 'pwz', 'qt', 'qtc', 'qti', 'qtif', 'r', 'ra', 'ram', 'rar', 'rast', 'rda', 'rev', 'rexx', 'ris', 'rf', 'rgb', 'rm', 'rmd', 'rmi', 'rmm', 'rmp', 'rt', 'rtf', 'rtx', 'rv', 's', 's3m', 'sav', 'sbs', 'sec', 'sdml', 'sgm', 'sgml', 'smi', 'smil', 'srt', 'sps', 'spv', 'stl', 'svg', 'swa', 'swf', 'swz', 'tar', 'tex', 'texi', 'texinfo', 'text', 'tgz', 'tif', 'tiff', 'ttf', 'txt', 'tmp', 'uvproj', 'vdf', 'vimeo', 'viv', 'vivo', 'vrml', 'vsdx', 'wav', 'webm', 'wmv', 'wmx', 'wmz', 'woff', 'wwd', 'xhtml', 'xif', 'xls', 'xlsx', 'xmind', 'xml', 'xsl', 'xsd', 'zip']
global $DIC
Definition: feed.php:28
$ilUser
Definition: imgupload.php:34
Interface ilDBInterface.
quote($value, string $type)
manipulateF(string $query, array $types, array $values)
query(string $query)
Run a (read-only) Query on the database.
Interface for tree implementations Currrently nested set or materialized path.
$path
Definition: ltiservices.php:32
$res
Definition: ltiservices.php:69
$i
Definition: metadata.php:41
string $key
Consumer key/client ID value.
Definition: System.php:193
$query
$type
$lng
$message
Definition: xapiexit.php:32
$rows
Definition: xhr_table.php:10