19declare(strict_types=1);
140 $this->db =
$db ??
$DIC->database();
143 if (isset(
$DIC[
'ilAppEventHandler'])) {
144 $this->eventHandler =
$DIC[
'ilAppEventHandler'];
149 if (func_num_args() > 3) {
150 $this->
logger->error(
"Wrong parameter count!");
152 throw new InvalidArgumentException(
"Wrong parameter count!");
155 if ($a_root_id > 0) {
156 $this->root_id = $a_root_id;
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';
169 $this->use_cache =
true;
188 $query =
'select tree from tree ' .
194 $trees[] = (
int) $row->tree;
206 if (!
$DIC->isDependencyAvailable(
'settings') ||
$DIC->settings()->getModule() !=
'common') {
209 $setting =
$DIC->settings();
213 if ($setting->get(
'main_tree_impl',
'ns') ==
'ns') {
237 $this->use_cache = $a_use;
274 if (
$DIC->offsetExists(
'ilUser')) {
275 $this->lang_code =
$DIC->user()->getCurrentLanguage() ?
327 $this->in_tree_cache = array();
339 string $a_table_tree,
340 string $a_table_obj_data,
341 string $a_table_obj_reference =
""
343 $this->table_tree = $a_table_tree;
344 $this->table_obj_data = $a_table_obj_data;
345 $this->table_obj_reference = $a_table_obj_reference;
356 $this->ref_pk = $a_column_name;
364 $this->obj_pk = $a_column_name;
372 $this->tree_pk = $a_column_name;
382 if ($this->table_obj_reference) {
384 return "JOIN " . $this->table_obj_reference .
" ON " . $this->table_tree .
".child=" . $this->table_obj_reference .
"." . $this->ref_pk .
" " .
385 "JOIN " . $this->table_obj_data .
" ON " . $this->table_obj_reference .
"." . $this->obj_pk .
"=" . $this->table_obj_data .
"." . $this->obj_pk .
" ";
388 return "JOIN " . $this->table_obj_data .
" ON " . $this->table_tree .
".child=" . $this->table_obj_data .
"." . $this->obj_pk .
" ";
398 return $this->getRelationOfNodes(
399 $this->getNodeTreeData($a_node_a),
400 $this->getNodeTreeData($a_node_b)
409 return $this->getTreeImplementation()->getRelation($a_node_a_arr, $a_node_b_arr);
418 $query =
'SELECT * FROM ' . $this->table_tree .
' ' .
419 'WHERE parent = ' . $this->db->quote($a_node,
'integer') .
' ' .
420 'AND tree = ' . $this->db->quote($this->tree_id,
'integer' .
' ' .
422 $res = $this->db->query($query);
426 $childs[] = (
int) $row->child;
435 public function getChilds(
int $a_node_id,
string $a_order =
"",
string $a_direction =
"ASC"): array
439 $ilObjDataCache =
$DIC[
'ilObjDataCache'];
440 $ilUser =
$DIC[
'ilUser'];
452 if (!empty($a_order)) {
453 $order_clause =
"ORDER BY " . $a_order .
" " . $a_direction;
455 $order_clause =
"ORDER BY " . $this->table_tree .
".lft";
459 'SELECT * FROM ' . $this->table_tree .
' ' .
461 "WHERE parent = %s " .
462 "AND " . $this->table_tree .
"." . $this->tree_pk .
" = %s " .
464 $this->db->quote($a_node_id,
'integer'),
465 $this->db->quote($this->tree_id,
'integer')
468 $res = $this->db->query($query);
470 if (!$count =
$res->numRows()) {
477 while ($r = $this->db->fetchAssoc(
$res)) {
479 $obj_ids[] = (
int) $r[
"obj_id"];
483 if ($this->__isMainTree() && $this->isCacheUsed() && is_object($ilObjDataCache) &&
484 is_object($ilUser) && $this->lang_code == $ilUser->getLanguage() && !isset($this->oc_preloaded[$a_node_id])) {
486 $ilObjDataCache->preloadObjectCache($obj_ids, $this->lang_code);
487 $this->fetchTranslationFromObjectDataCache($obj_ids);
488 $this->oc_preloaded[$a_node_id] =
true;
491 foreach ($rows as $row) {
492 $childs[] = $this->fetchNodeData($row);
495 if ($this->__isMainTree()) {
496 #$GLOBALS['DIC']['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child'].' = true');
497 $this->in_tree_cache[$row[
'child']] = $row[
'tree'] == 1;
500 $childs[$count - 1][
"last"] =
true;
515 string $a_order =
"",
516 string $a_direction =
"ASC"
518 $childs = $this->getChilds($a_node, $a_order, $a_direction);
521 foreach ($childs as $child) {
522 if (!in_array($child[
"type"], $a_filter)) {
523 $filtered[] = $child;
535 if ($a_type ==
'rolf' && $this->table_obj_reference) {
539 $this->db->setLimit(1, 0);
541 "SELECT * FROM " . $this->table_tree .
" " .
543 "WHERE parent = %s " .
544 "AND " . $this->table_tree .
"." . $this->tree_pk .
" = %s " .
545 "AND " . $this->table_obj_data .
".type = %s ",
546 $this->db->quote($a_node_id,
'integer'),
547 $this->db->quote($this->tree_id,
'integer'),
548 $this->db->quote($a_type,
'text')
552 "SELECT * FROM " . $this->table_tree .
" " .
554 "WHERE parent = %s " .
555 "AND " . $this->table_tree .
"." . $this->tree_pk .
" = %s " .
556 "AND " . $this->table_obj_data .
".type = %s " .
557 "ORDER BY " . $this->table_tree .
".lft",
558 $this->db->quote($a_node_id,
'integer'),
559 $this->db->quote($this->tree_id,
'integer'),
560 $this->db->quote($a_type,
'text')
563 $res = $this->db->query($query);
567 while ($row = $this->db->fetchAssoc(
$res)) {
568 $childs[] = $this->fetchNodeData($row);
579 string $a_order =
"",
580 string $a_direction =
"ASC"
584 $filter =
'AND ' . $this->table_obj_data .
'.type IN(' . implode(
',',
ilArrayUtil::quoteArray($a_types)) .
') ';
588 if (!empty($a_order)) {
589 $order_clause =
"ORDER BY " . $a_order .
" " . $a_direction;
591 $order_clause =
"ORDER BY " . $this->table_tree .
".lft";
594 $query =
'SELECT * FROM ' . $this->table_tree .
' ' .
596 'WHERE parent = ' . $this->db->quote($a_node_id,
'integer') .
' ' .
597 'AND ' . $this->table_tree .
'.' . $this->tree_pk .
' = ' . $this->db->quote(
604 $res = $this->db->query($query);
607 while ($row = $this->db->fetchAssoc(
$res)) {
608 $childs[] = $this->fetchNodeData($row);
624 int $a_pos = self::POS_LAST_NODE,
625 bool $a_reset_deleted_date =
false
627 if ($this->__isMainTree()) {
628 if ($a_source_id <= 1 || $a_target_id <= 0) {
630 throw new InvalidArgumentException(
'Invalid parameter given for ilTree::insertNodeFromTrash');
633 if ($this->isInTree($a_source_id)) {
636 throw new InvalidArgumentException(
'Node already in tree.');
640 $this->insertNode($a_source_id, $a_target_id, self::POS_LAST_NODE, $a_reset_deleted_date);
650 int $a_pos = self::POS_LAST_NODE,
651 bool $a_reset_deletion_date =
false
654 if ($this->__isMainTree()) {
655 if ($a_node_id <= 1 || $a_parent_id <= 0) {
657 'Invalid parameters! $a_node_id: %s $a_parent_id: %s',
662 throw new InvalidArgumentException(
$message);
665 if ($this->isInTree($a_node_id)) {
666 throw new InvalidArgumentException(
"Node " . $a_node_id .
" already in tree " .
667 $this->table_tree .
"!");
670 $this->getTreeImplementation()->insertNode($a_node_id, $a_parent_id, $a_pos);
672 $this->in_tree_cache[$a_node_id] =
true;
675 if ($a_reset_deletion_date) {
678 if (isset($this->eventHandler) && ($this->eventHandler instanceof
ilAppEventHandler) && $this->__isMainTree()) {
679 $this->eventHandler->raise(
680 'components/ILIAS/Tree',
683 'tree' => $this->table_tree,
684 'node_id' => $a_node_id,
685 'parent_id' => $a_parent_id
699 $node = $this->getNodeData($a_node_id);
704 foreach ($this->getSubTree($node) as $subnode) {
705 if ($depth && $subnode[
'depth'] > $depth) {
708 if (!$first && in_array($subnode[
'type'], $a_filter)) {
709 $depth = $subnode[
'depth'];
715 $filtered[] = $subnode;
727 return $this->getTreeImplementation()->getSubTreeIds($a_ref_id);
735 public function getSubTree(array $a_node,
bool $a_with_data =
true, array $a_type = []): array
737 $query = $this->getTreeImplementation()->getSubTreeQuery($a_node, $a_type);
739 $res = $this->db->query($query);
741 while ($row = $this->db->fetchAssoc(
$res)) {
743 $subtree[] = $this->fetchNodeData($row);
745 $subtree[] = (
int) $row[
'child'];
748 if ($this->__isMainTree() || $this->table_tree ==
"lm_tree") {
749 $this->in_tree_cache[$row[
'child']] =
true;
760 if ($this->__isMainTree()) {
762 if (!$this->__checkDelete($a_node)) {
770 $this->getTreeImplementation()->deleteTree((
int) $a_node[
'child']);
771 $this->resetInTreeCache();
780 return $this->getTreeImplementation()->validateParentRelations();
788 public function getPathFull(
int $a_endnode_id,
int $a_startnode_id = 0): array
790 $pathIds = $this->getPathId($a_endnode_id, $a_startnode_id);
794 if (count($pathIds) == 0) {
798 $inClause =
'child IN (';
799 for ($i = 0; $i < count($pathIds); $i++) {
803 $inClause .= $this->db->quote($pathIds[$i],
'integer');
808 'FROM ' . $this->table_tree .
' ' .
809 $this->buildJoin() .
' ' .
810 'WHERE ' . $inClause .
' ' .
811 'AND ' . $this->table_tree .
'.' . $this->tree_pk .
' = ' . $this->db->quote(
816 $r = $this->db->query(
$q);
820 $pathFull[] = $this->fetchNodeData($row);
823 if ($this->__isMainTree()) {
824 $this->in_tree_cache[$row[
'child']] = $row[
'tree'] == 1;
838 if (!$this->__isMainTree() || !$this->isCacheUsed()) {
842 $res = $this->db->query(
'SELECT t.depth, t.parent, t.child ' .
843 'FROM ' . $this->table_tree .
' t ' .
844 'WHERE ' . $this->db->in(
"child", $a_node_ids,
false,
"integer") .
845 'AND ' . $this->tree_pk .
' = ' . $this->db->quote($this->tree_id,
"integer"));
846 while ($row = $this->db->fetchAssoc(
$res)) {
847 $this->depth_cache[$row[
"child"]] = (
int) $row[
"depth"];
848 $this->parent_cache[$row[
"child"]] = (
int) $row[
"parent"];
857 public function getPathId(
int $a_endnode_id,
int $a_startnode_id = 0): array
859 if (!$a_endnode_id) {
861 throw new InvalidArgumentException(__METHOD__ .
': No endnode given!');
865 if ($this->isCacheUsed() && isset($this->path_id_cache[$a_endnode_id][$a_startnode_id])) {
866 return $this->path_id_cache[$a_endnode_id][$a_startnode_id];
869 $pathIds = $this->getTreeImplementation()->getPathIds($a_endnode_id, $a_startnode_id);
871 if ($this->__isMainTree()) {
872 $this->path_id_cache[$a_endnode_id][$a_startnode_id] = $pathIds;
887 public function getNodePath(
int $a_endnode_id,
int $a_startnode_id = 0): array
889 $pathIds = $this->getPathId($a_endnode_id, $a_startnode_id);
892 if (count($pathIds) == 0) {
898 for ($i = 0; $i < count($pathIds); $i++) {
899 $types[] =
'integer';
900 $data[] = $pathIds[$i];
903 $query =
'SELECT t.depth,t.parent,t.child,d.obj_id,d.type,d.title ' .
904 'FROM ' . $this->table_tree .
' t ' .
905 'JOIN ' . $this->table_obj_reference .
' r ON r.ref_id = t.child ' .
906 'JOIN ' . $this->table_obj_data .
' d ON d.obj_id = r.obj_id ' .
907 'WHERE ' . $this->db->in(
't.child',
$data,
false,
'integer') .
' ' .
910 $res = $this->db->queryF($query, $types,
$data);
913 while ($row = $this->db->fetchAssoc(
$res)) {
926 $types = array(
'integer');
927 $query =
'SELECT lft,rgt FROM ' . $this->table_tree .
' ' .
928 'WHERE ' . $this->tree_pk .
' = %s ';
930 $res = $this->db->queryF($query, $types, array($this->tree_id));
932 while ($row = $this->db->fetchObject(
$res)) {
937 $all = array_merge($lft, $rgt);
938 $uni = array_unique($all);
940 if (count($all) != count($uni)) {
954 $query =
'SELECT * FROM ' . $this->table_tree .
' ' .
955 'WHERE ' . $this->tree_pk .
' = %s ' .
957 $r1 = $this->db->queryF($query, array(
'integer'), array($this->tree_id));
959 while ($row = $this->db->fetchAssoc($r1)) {
961 if (($row[
"child"] == 0) && $a_no_zero_child) {
962 $message =
"Tree contains child with ID 0!";
967 if ($this->table_obj_reference) {
969 $query =
'SELECT * FROM ' . $this->table_obj_reference .
' WHERE ' . $this->ref_pk .
' = %s ';
970 $r2 = $this->db->queryF($query, array(
'integer'), array($row[
'child']));
973 if ($r2->numRows() == 0) {
974 $message =
"No Object-to-Reference entry found for ID " . $row[
"child"] .
"!";
978 if ($r2->numRows() > 1) {
979 $message =
"More Object-to-Reference entries found for ID " . $row[
"child"] .
"!";
985 $obj_ref = $this->db->fetchAssoc($r2);
987 $query =
'SELECT * FROM ' . $this->table_obj_data .
' WHERE ' . $this->obj_pk .
' = %s';
988 $r3 = $this->db->queryF($query, array(
'integer'), array($obj_ref[$this->obj_pk]));
989 if ($r3->numRows() == 0) {
990 $message =
" No child found for ID " . $obj_ref[$this->obj_pk] .
"!";
994 if ($r3->numRows() > 1) {
995 $message =
"More childs found for ID " . $obj_ref[$this->obj_pk] .
"!";
1001 $query =
'SELECT * FROM ' . $this->table_obj_data .
' WHERE ' . $this->obj_pk .
' = %s';
1002 $r2 = $this->db->queryF($query, array(
'integer'), array($row[
'child']));
1004 if ($r2->numRows() == 0) {
1005 $message =
"No child found for ID " . $row[
"child"] .
"!";
1009 if ($r2->numRows() > 1) {
1010 $message =
"More childs found for ID " . $row[
"child"] .
"!";
1026 $query =
'SELECT MAX(depth) depth FROM ' . $this->table_tree;
1027 $res = $this->db->query($query);
1029 $row = $this->db->fetchAssoc(
$res);
1030 return (
int) $row[
'depth'];
1041 if ($this->__isMainTree()) {
1042 $query =
'SELECT depth FROM ' . $this->table_tree .
' ' .
1043 'WHERE child = %s ';
1044 $res = $this->db->queryF($query, array(
'integer'), array($a_node_id));
1045 $row = $this->db->fetchObject(
$res);
1047 $query =
'SELECT depth FROM ' . $this->table_tree .
' ' .
1048 'WHERE child = %s ' .
1049 'AND ' . $this->tree_pk .
' = %s ';
1050 $res = $this->db->queryF($query, array(
'integer',
'integer'), array($a_node_id, $this->tree_id));
1051 $row = $this->db->fetchObject(
$res);
1053 return (
int) ($row->depth ?? 0);
1068 throw new InvalidArgumentException(
'Missing or empty parameter $a_node_id: ' . $a_node_id);
1071 $query =
'SELECT * FROM ' . $this->table_tree .
' ' .
1072 'WHERE child = ' . $this->db->quote($a_node_id,
'integer');
1073 $res = $this->db->query($query);
1085 public function getNodeData(
int $a_node_id, ?
int $a_tree_pk =
null): array
1087 if ($this->__isMainTree()) {
1088 if ($a_node_id < 1) {
1089 $message =
'No valid parameter given! $a_node_id: %s' . $a_node_id;
1091 throw new InvalidArgumentException(
$message);
1095 $query =
'SELECT * FROM ' . $this->table_tree .
' ' .
1096 $this->buildJoin() .
1097 'WHERE ' . $this->table_tree .
'.child = %s ' .
1098 'AND ' . $this->table_tree .
'.' . $this->tree_pk .
' = %s ';
1099 $res = $this->db->queryF($query, array(
'integer',
'integer'), array(
1101 $a_tree_pk ===
null ? $this->tree_id : $a_tree_pk
1103 $row = $this->db->fetchAssoc(
$res);
1104 $row[$this->tree_pk] = $this->tree_id;
1106 return $this->fetchNodeData($row);
1116 $objDefinition =
$DIC[
'objDefinition'];
1120 $data[
"desc"] = (string) ($a_row[
"description"] ??
'');
1123 $translation_type =
'';
1124 if (is_object($objDefinition)) {
1125 $translation_type = $objDefinition->getTranslationType(
$data[
"type"] ??
'');
1128 if ($translation_type ==
"sys") {
1130 $data[
"description"] = (string)
$lng->txt(
"obj_" .
$data[
"type"] .
"_local_desc") .
$data[
"title"] .
$data[
"desc"];
1135 $data[
"description"] =
$lng->txt(
"obj_" .
$data[
"type"] .
"_desc");
1138 } elseif ($translation_type ==
"db") {
1141 if ($this->isCacheUsed() &&
1142 array_key_exists(
$data[
"obj_id"] .
'.' . $lang_code, $this->translation_cache)) {
1143 $key =
$data[
"obj_id"] .
'.' . $lang_code;
1144 $data[
"title"] = $this->translation_cache[$key][
'title'];
1145 $data[
"description"] = $this->translation_cache[$key][
'description'];
1146 $data[
"desc"] = $this->translation_cache[$key][
'desc'];
1149 $query =
'SELECT title,description FROM object_translation ' .
1150 'WHERE obj_id = %s ' .
1151 'AND lang_code = %s ';
1153 $res = $this->db->queryF($query, array(
'integer',
'text'), array(
1157 $row = $this->db->fetchObject(
$res);
1160 $data[
"title"] = (string) $row->title;
1162 $data[
"desc"] = (string) $row->description;
1166 if ($this->isCacheUsed() && count($this->translation_cache) < 1000) {
1167 $key =
$data[
"obj_id"] .
'.' . $lang_code;
1168 $this->translation_cache[$key] = [];
1169 $this->translation_cache[$key][
'title'] =
$data[
"title"];
1170 $this->translation_cache[$key][
'description'] =
$data[
"description"];
1171 $this->translation_cache[$key][
'desc'] =
$data[
"desc"];
1177 if (isset(
$data[
'type']) && (
$data[
'type'] ==
'crsr' ||
$data[
'type'] ==
'catr' ||
$data[
'type'] ==
'grpr' ||
$data[
'type'] ===
'prgr')) {
1192 $ilObjDataCache =
$DIC[
'ilObjDataCache'];
1194 if ($this->isCacheUsed() && is_array($a_obj_ids) && is_object($ilObjDataCache)) {
1195 foreach ($a_obj_ids as
$id) {
1196 $this->translation_cache[
$id .
'.'][
'title'] = $ilObjDataCache->lookupTitle((
int)
$id);
1197 $this->translation_cache[
$id .
'.'][
'description'] = $ilObjDataCache->lookupDescription((
int)
$id);
1198 $this->translation_cache[
$id .
'.'][
'desc'] =
1199 $this->translation_cache[
$id .
'.'][
'description'];
1210 if (is_null($a_node_id) || !$a_node_id) {
1214 if ($this->isCacheUsed() && isset($this->in_tree_cache[$a_node_id])) {
1215 return $this->in_tree_cache[$a_node_id];
1218 $query =
'SELECT * FROM ' . $this->table_tree .
' ' .
1219 'WHERE ' . $this->table_tree .
'.child = %s ' .
1220 'AND ' . $this->table_tree .
'.' . $this->tree_pk .
' = %s';
1222 $res = $this->db->queryF($query, array(
'integer',
'integer'), array(
1227 if (
$res->numRows() > 0) {
1228 if ($this->__isMainTree()) {
1229 $this->in_tree_cache[$a_node_id] =
true;
1233 if ($this->__isMainTree()) {
1234 $this->in_tree_cache[$a_node_id] =
false;
1247 $ilLog =
$DIC[
'ilLog'];
1248 if ($this->table_obj_reference) {
1250 $innerjoin =
"JOIN " . $this->table_obj_reference .
" ON v.child=" . $this->table_obj_reference .
"." . $this->ref_pk .
" " .
1251 "JOIN " . $this->table_obj_data .
" ON " . $this->table_obj_reference .
"." . $this->obj_pk .
"=" . $this->table_obj_data .
"." . $this->obj_pk .
" ";
1254 $innerjoin =
"JOIN " . $this->table_obj_data .
" ON v.child=" . $this->table_obj_data .
"." . $this->obj_pk .
" ";
1257 $query =
'SELECT * FROM ' . $this->table_tree .
' s, ' . $this->table_tree .
' v ' .
1259 'WHERE s.child = %s ' .
1260 'AND s.parent = v.child ' .
1261 'AND s.' . $this->tree_pk .
' = %s ' .
1262 'AND v.' . $this->tree_pk .
' = %s';
1263 $res = $this->db->queryF($query, array(
'integer',
'integer',
'integer'), array(
1268 $row = $this->db->fetchAssoc(
$res);
1269 if (is_array($row)) {
1270 return $this->fetchNodeData($row);
1278 public function isGrandChild(
int $a_startnode_id,
int $a_querynode_id): bool
1280 return $this->getRelation($a_startnode_id, $a_querynode_id) == self::RELATION_PARENT;
1287 public function addTree(
int $a_tree_id,
int $a_node_id = -1): bool
1292 if ($this->__isMainTree()) {
1294 'Operation not allowed on main tree! $a_tree_if: %s $a_node_id: %s',
1299 throw new InvalidArgumentException(
$message);
1302 if ($a_node_id <= 0) {
1303 $a_node_id = $a_tree_id;
1306 $query =
'INSERT INTO ' . $this->table_tree .
' (' .
1307 $this->tree_pk .
', child,parent,lft,rgt,depth) ' .
1309 '(%s,%s,%s,%s,%s,%s)';
1310 $res = $this->db->manipulateF(
1312 array(
'integer',
'integer',
'integer',
'integer',
'integer',
'integer'),
1331 if ($this->__isMainTree()) {
1333 throw new InvalidArgumentException(
'Operation not allowed on main tree');
1337 throw new InvalidArgumentException(
'Missing parameter tree id');
1340 $query =
'DELETE FROM ' . $this->table_tree .
1341 ' WHERE ' . $this->tree_pk .
' = %s ';
1342 $this->db->manipulateF($query, array(
'integer'), array($a_tree_id));
1351 public function moveToTrash(
int $a_node_id,
bool $a_set_deleted =
false,
int $a_deleted_by = 0): bool
1355 $user =
$DIC->user();
1356 if (!$a_deleted_by) {
1357 $a_deleted_by = $user->getId();
1362 throw new InvalidArgumentException(
'No valid parameter given! $a_node_id: ' . $a_node_id);
1365 $query = $this->getTreeImplementation()->getSubTreeQuery($this->getNodeTreeData($a_node_id), [],
false);
1366 $res = $this->db->query($query);
1370 $subnodes[] = (
int) $row[
'child'];
1373 if (!count($subnodes)) {
1378 if ($a_set_deleted) {
1382 $this->getTreeImplementation()->moveToTrash($a_node_id);
1392 if ($this->isCacheUsed() && isset($this->is_saved_cache[$a_node_id])) {
1393 return $this->is_saved_cache[$a_node_id];
1396 $query =
'SELECT ' . $this->tree_pk .
' FROM ' . $this->table_tree .
' ' .
1397 'WHERE child = %s ';
1398 $res = $this->db->queryF($query, array(
'integer'), array($a_node_id));
1399 $row = $this->db->fetchAssoc(
$res);
1401 $tree_id = $row[$this->tree_pk] ?? 0;
1403 if ($this->__isMainTree()) {
1404 $this->is_saved_cache[$a_node_id] =
true;
1408 if ($this->__isMainTree()) {
1409 $this->is_saved_cache[$a_node_id] =
false;
1420 if (!is_array($a_node_ids) || !$this->isCacheUsed()) {
1424 $query =
'SELECT ' . $this->tree_pk .
', child FROM ' . $this->table_tree .
' ' .
1425 'WHERE ' . $this->db->in(
"child", $a_node_ids,
false,
"integer");
1427 $res = $this->db->query($query);
1428 while ($row = $this->db->fetchAssoc(
$res)) {
1429 if ($row[$this->tree_pk] < 0) {
1430 if ($this->__isMainTree()) {
1431 $this->is_saved_cache[$row[
"child"]] =
true;
1434 if ($this->__isMainTree()) {
1435 $this->is_saved_cache[$row[
"child"]] =
false;
1449 if (!isset($a_parent_id)) {
1452 throw new InvalidArgumentException(
$message);
1455 $query =
'SELECT * FROM ' . $this->table_tree .
' ' .
1456 $this->buildJoin() .
1457 'WHERE ' . $this->table_tree .
'.' . $this->tree_pk .
' < %s ' .
1458 'AND ' . $this->table_tree .
'.parent = %s';
1459 $res = $this->db->queryF($query, array(
'integer',
'integer'), array(
1465 while ($row = $this->db->fetchAssoc(
$res)) {
1466 $saved[] = $this->fetchNodeData($row);
1479 $query =
'SELECT ' . $this->table_obj_data .
'.obj_id FROM ' . $this->table_tree .
' ' .
1480 $this->buildJoin() .
1481 'WHERE ' . $this->table_tree .
'.' . $this->tree_pk .
' < ' . $this->db->quote(0,
'integer') .
' ' .
1482 'AND ' . $this->db->in($this->table_obj_data .
'.obj_id', $a_obj_ids,
false,
'integer');
1483 $res = $this->db->query($query);
1485 while ($row = $this->db->fetchAssoc(
$res)) {
1486 $saved[] = (
int) $row[
'obj_id'];
1499 if ($this->__isMainTree()) {
1500 $query =
'SELECT parent FROM ' . $this->table_tree .
' ' .
1501 'WHERE child = %s ';
1502 $res = $this->db->queryF(
1508 $query =
'SELECT parent FROM ' . $this->table_tree .
' ' .
1509 'WHERE child = %s ' .
1510 'AND ' . $this->tree_pk .
' = %s ';
1511 $res = $this->db->queryF($query, array(
'integer',
'integer'), array(
1517 if ($row = $this->db->fetchObject(
$res)) {
1518 return (
int) $row->parent;
1529 $tree_implementation = $this->getTreeImplementation();
1531 return $tree_implementation->getChildSequenceNumber($a_node, $type);
1533 $message =
"This tree is not part of ilNestedSetTree.";
1535 throw new LogicException(
$message);
1541 $query =
'SELECT child FROM ' . $this->table_tree .
' ' .
1542 'WHERE parent = %s ' .
1543 'AND ' . $this->tree_pk .
' = %s ';
1544 $res = $this->db->queryF($query, array(
'integer',
'integer'), array(
1549 if ($row = $this->db->fetchObject(
$res)) {
1550 $this->root_id = (
int) $row->child;
1552 return $this->root_id;
1557 return $this->root_id;
1562 $this->root_id = $a_root_id;
1567 return $this->tree_id;
1576 $tree_implementation = $this->getTreeImplementation();
1578 return $tree_implementation->fetchSuccessorNode($a_node_id, $a_type);
1580 $message =
"This tree is not part of ilNestedSetTree.";
1582 throw new LogicException(
$message);
1592 $tree_implementation = $this->getTreeImplementation();
1594 return $tree_implementation->fetchPredecessorNode($a_node_id, $a_type);
1596 $message =
"This tree is not part of ilNestedSetTree.";
1598 throw new LogicException(
$message);
1608 $renumber_callable =
function (
ilDBInterface $db) use ($node_id, $i, &$return) {
1609 $return = $this->__renumber($node_id, $i);
1613 if ($this->__isMainTree()) {
1614 $ilAtomQuery = $this->db->buildAtomQuery();
1615 $ilAtomQuery->addTableLock($this->table_tree);
1617 $ilAtomQuery->addQueryCallable($renumber_callable);
1618 $ilAtomQuery->run();
1620 $renumber_callable($this->db);
1632 if ($this->isRepositoryTree()) {
1633 $query =
'UPDATE ' . $this->table_tree .
' SET lft = %s WHERE child = %s';
1634 $this->db->manipulateF(
1636 array(
'integer',
'integer'),
1643 $query =
'UPDATE ' . $this->table_tree .
' SET lft = %s WHERE child = %s AND tree = %s';
1644 $this->db->manipulateF(
1646 array(
'integer',
'integer',
'integer'),
1655 $query =
'SELECT * FROM ' . $this->table_tree .
' ' .
1656 'WHERE parent = ' . $this->db->quote($node_id,
'integer') .
' ' .
1658 $res = $this->db->query($query);
1662 $childs[] = (
int) $row->child;
1665 foreach ($childs as $child) {
1666 $i = $this->__renumber($child, $i + 1);
1671 if (count($childs) > 0) {
1672 $i += $this->gap * 2;
1675 if ($this->isRepositoryTree()) {
1676 $query =
'UPDATE ' . $this->table_tree .
' SET rgt = %s WHERE child = %s';
1677 $res = $this->db->manipulateF(
1679 array(
'integer',
'integer'),
1686 $query =
'UPDATE ' . $this->table_tree .
' SET rgt = %s WHERE child = %s AND tree = %s';
1687 $res = $this->db->manipulateF($query, array(
'integer',
'integer',
'integer'), array(
1703 $cache_key = $a_ref_id .
'.' . $a_type .
'.' . ((
int) $a_exclude_source_check);
1706 if ($this->isCacheUsed() &&
1707 array_key_exists($cache_key, $this->parent_type_cache)) {
1708 return (
int) $this->parent_type_cache[$cache_key];
1712 $do_cache = ($this->__isMainTree() && count($this->parent_type_cache) < 1000);
1715 if (!$this->isInTree($a_ref_id)) {
1717 $this->parent_type_cache[$cache_key] =
false;
1722 $path = array_reverse($this->getPathFull($a_ref_id));
1725 if ($a_exclude_source_check) {
1729 foreach (
$path as $node) {
1731 if ($node[
"type"] == $a_type) {
1733 $this->parent_type_cache[$cache_key] = (
int) $node[
"child"];
1735 return (
int) $node[
"child"];
1740 $this->parent_type_cache[$cache_key] =
false;
1750 public static function _removeEntry(
int $a_tree,
int $a_child,
string $a_db_table =
"tree"): void
1754 $db =
$DIC->database();
1756 if ($a_db_table ===
'tree') {
1759 'Tried to delete root node! $a_tree: %s $a_child: %s',
1764 throw new InvalidArgumentException(
$message);
1768 $query =
'DELETE FROM ' . $a_db_table .
' ' .
1769 'WHERE tree = %s ' .
1782 return $this->table_tree ===
'tree';
1793 $query = $this->getTreeImplementation()->getSubTreeQuery($a_node, [],
false);
1794 $this->
logger->debug($query);
1795 $res = $this->db->query($query);
1798 while ($row = $this->db->fetchObject(
$res)) {
1799 $lft_childs[$row->child] = (
int) $row->parent;
1804 if (
$counter != count($lft_childs)) {
1805 $message =
'Duplicate entries for "child" in maintree! $a_node_id: ' . $a_node[
'child'];
1812 $parent_childs = [];
1813 $this->__getSubTreeByParentRelation((
int)$a_node[
'child'], $parent_childs);
1814 $this->__validateSubtrees($lft_childs, $parent_childs);
1826 $query =
'SELECT * FROM ' . $this->table_tree .
' ' .
1827 'WHERE child = %s ' .
1829 $res = $this->db->queryF($query, array(
'integer',
'integer'), array(
1835 while ($row = $this->db->fetchObject(
$res)) {
1836 $parent_childs[$a_node_id] = (
int) $row->parent;
1841 $message =
'Multiple entries in maintree! $a_node_id: ' . $a_node_id;
1848 $query =
'SELECT * FROM ' . $this->table_tree .
' ' .
1849 'WHERE parent = %s ';
1850 $res = $this->db->queryF($query, array(
'integer'), array($a_node_id));
1852 while ($row = $this->db->fetchObject(
$res)) {
1854 $this->__getSubTreeByParentRelation((
int) $row->child, $parent_childs);
1870 ksort($parent_childs);
1872 $this->
logger->debug(
'left childs ' . print_r($lft_childs,
true));
1873 $this->
logger->debug(
'parent childs ' . print_r($parent_childs,
true));
1875 if (count($lft_childs) != count($parent_childs)) {
1876 $message =
'(COUNT) Tree is corrupted! Left/Right subtree does not comply with parent relation';
1881 foreach ($lft_childs as $key => $value) {
1882 if ($parent_childs[$key] != $value) {
1883 $message =
'(COMPARE) Tree is corrupted! Left/Right subtree does not comply with parent relation';
1888 $message =
'(ROOT_FOLDER) Tree is corrupted! Tried to delete root folder';
1904 public function moveTree(
int $a_source_id,
int $a_target_id,
int $a_location = self::POS_LAST_NODE): void
1906 $old_parent_id = $this->getParentId($a_source_id);
1907 $this->getTreeImplementation()->moveTree($a_source_id, $a_target_id, $a_location);
1908 if (isset(
$GLOBALS[
'DIC'][
"ilAppEventHandler"]) && $this->__isMainTree()) {
1909 $GLOBALS[
'DIC'][
'ilAppEventHandler']->raise(
1910 "components/ILIAS/Tree",
1913 'tree' => $this->table_tree,
1914 'source_id' => $a_source_id,
1915 'target_id' => $a_target_id,
1916 'old_parent_id' => $old_parent_id
1929 return $this->getTreeImplementation()->getSubtreeInfo($a_endnode_id);
1937 array $a_fields = [],
1938 array $a_types = [],
1939 bool $a_force_join_reference =
false
1941 return $this->getTreeImplementation()->getSubTreeQuery(
1942 $this->getNodeTreeData($a_node_id),
1944 $a_force_join_reference,
1951 array $a_fields = [],
1952 array $a_types = [],
1953 bool $a_force_join_reference =
false
1955 return $this->getTreeImplementation()->getTrashSubTreeQuery(
1956 $this->getNodeTreeData($a_node_id),
1958 $a_force_join_reference,
1971 $node = $this->getNodeData($a_node_id);
1972 if (!count($node)) {
1978 $query = $this->getTreeImplementation()->getSubTreeQuery($node, [],
true, array($this->ref_pk));
1981 if (count($a_fields)) {
1982 $fields = implode(
',', $a_fields);
1985 $query =
"SELECT " . $fields .
1986 " FROM " . $this->getTreeTable() .
1987 " " . $this->buildJoin() .
1988 " WHERE " . $this->getTableReference() .
"." . $this->ref_pk .
" IN (" . $query .
")" .
1989 " AND " . $this->db->in($this->getObjectDataTable() .
"." . $this->obj_pk, $a_obj_ids,
false,
"integer");
1990 $set = $this->db->query($query);
1991 while ($row = $this->db->fetchAssoc($set)) {
2000 $query =
'DELETE FROM tree where ' .
2001 'child = ' . $this->db->quote($a_node_id,
'integer') .
' ' .
2002 'AND tree = ' . $this->db->quote($a_tree_id,
'integer');
2003 $this->db->manipulate($query);
2005 $this->eventHandler->raise(
2006 "components/ILIAS/Tree",
2009 'tree' => $this->table_tree,
2010 'node_id' => $a_node_id,
2011 'tree_id' => $a_tree_id
2022 $query =
'SELECT DISTINCT(o.type) ' . $this->db->quoteIdentifier(
'type') .
2023 ' FROM tree t JOIN object_reference r ON child = r.ref_id ' .
2024 'JOIN object_data o on r.obj_id = o.obj_id ' .
2025 'WHERE tree < ' . $this->db->quote(0,
'integer') .
' ' .
2026 'AND child = -tree ' .
2028 $res = $this->db->query($query);
2030 $types_deleted = [];
2032 $types_deleted[] = (string) $row->type;
2034 return $types_deleted;
2042 return $this->table_tree ==
'tree';
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
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.
Base class for materialize path based trees Based on implementation of Werner Randelshofer.
Base class for nested set path based trees.
static setDeletedDates(array $ref_ids, int $user_id)
static _resetDeletedDate(int $ref_id)
static shortenTextExtended(string $a_str, int $a_len, bool $a_dots=false, bool $a_next_blank=false, bool $a_keep_extension=false)
Tree class data representation in hierachical trees using the Nested Set Model with Gaps by Joe Celco...
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
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.
getTreeTable()
Get tree table name.
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.
string $obj_pk
column name containing primary key in object table
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.
__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.
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
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
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.
__construct(int $a_tree_id, int $a_root_id=0, ?ilDBInterface $db=null)
deleteNode(int $a_tree_id, int $a_node_id)
initLangCode()
Do not use it Store user language.
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.
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.
getRelation(int $a_node_a, int $a_node_b)
Get relation of two nodes.
getNodePath(int $a_endnode_id, int $a_startnode_id=0)
Returns the node path for the specified object reference.
const TREE_TYPE_MATERIALIZED_PATH
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.
__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
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
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
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.
if(!file_exists('../ilias.ini.php'))