19declare(strict_types=1);
44 $this->tree = $a_tree;
45 $this->db =
$DIC->database();
47 $this->
logger = $DIC->logger()->tree();
74 $node = $this->
getTree()->getNodeTreeData($a_node_id);
75 $query =
'SELECT child FROM ' . $this->
getTree()->getTreeTable() .
' ' .
76 'WHERE path BETWEEN ' .
77 $this->db->quote($node[
'path'],
'text') .
' AND ' .
78 $this->db->quote($node[
'path'] .
'.Z',
'text') .
' ' .
80 'AND ' . $this->
getTree()->getTreePk() .
' = %s';
82 $res = $this->db->queryF(
84 array(
'integer',
'integer'),
85 array($a_node_id, $this->
getTree()->getTreeId())
88 while ($row = $this->db->fetchAssoc(
$res)) {
89 $childs[] = (
int) $row[
'child'];
100 if ($a_node_a === [] || $a_node_b === []) {
103 if ($a_node_a[
'child'] == $a_node_b[
'child']) {
106 if (stripos($a_node_a[
'path'], $a_node_b[
'path'] .
'.') === 0) {
109 if (stripos($a_node_b[
'path'], $a_node_a[
'path'] .
'.') === 0) {
112 $path_a = substr($a_node_a[
'path'], 0, strrpos($a_node_a[
'path'],
'.'));
113 $path_b = substr($a_node_b[
'path'], 0, strrpos($a_node_b[
'path'],
'.'));
114 if ($a_node_a[
'path'] && $path_a === $path_b) {
126 bool $a_force_join_reference =
true,
130 if (is_array($a_types)) {
132 $type_str =
"AND " . $this->db->in(
133 $this->
getTree()->getObjectDataTable() .
".type",
142 if ($type_str || $a_force_join_reference) {
143 $join = $this->
getTree()->buildJoin();
147 if (count($a_fields)) {
148 $fields = implode(
',', $a_fields);
154 'FROM ' . $this->
getTree()->getTreeTable() .
' ' .
156 'WHERE ' . $this->
getTree()->getTreeTable() .
'.path ' .
158 $this->db->quote($a_node[
'path'],
'text') .
' AND ' .
159 $this->db->quote($a_node[
'path'] .
'.Z',
'text') .
' ' .
160 'AND ' . $this->
getTree()->getTreeTable() .
'.' . $this->
getTree()->getTreePk() .
' < 0 ' .
162 'ORDER BY ' . $this->
getTree()->getTreeTable() .
'.path';
178 bool $a_force_join_reference =
true,
182 if (count($a_types)) {
184 $type_str =
"AND " . $this->db->in(
185 $this->getTree()->getObjectDataTable() .
".type",
194 if ($type_str || $a_force_join_reference) {
195 $join = $this->getTree()->buildJoin();
199 if (count($a_fields)) {
200 $fields = implode(
',', $a_fields);
206 'FROM ' . $this->getTree()->getTreeTable() .
' ' .
208 'WHERE ' . $this->getTree()->getTreeTable() .
'.path ' .
210 $this->db->quote($a_node[
'path'],
'text') .
' AND ' .
211 $this->db->quote($a_node[
'path'] .
'.Z',
'text') .
' ' .
212 'AND ' . $this->getTree()->getTreeTable() .
'.' . $this->getTree()->getTreePk() .
' = ' . $this->db->quote(
213 $this->getTree()->getTreeId(),
217 'ORDER BY ' . $this->getTree()->getTreeTable() .
'.path';
225 public function getPathIds(
int $a_endnode,
int $a_startnode = 0): array
227 $this->db->setLimit(1, 0);
228 $query =
'SELECT path FROM ' . $this->getTree()->getTreeTable() .
' ' .
229 'WHERE child = ' . $this->db->quote($a_endnode,
'integer') .
' ';
230 $res = $this->db->query($query);
233 while ($row = $this->db->fetchAssoc(
$res)) {
234 $path = (string) $row[
'path'];
237 $pathIds = array_map(
'intval', explode(
'.',
$path));
239 if ($a_startnode != 0) {
240 while (count($pathIds) > 0 && $pathIds[0] != $a_startnode) {
241 array_shift($pathIds);
250 public function insertNode(
int $a_node_id,
int $a_parent_id,
int $a_pos): void
252 $insert_node_callable =
function (
ilDBInterface $ilDB) use ($a_node_id, $a_parent_id, $a_pos):
void {
254 $this->db->setLimit(1, 0);
256 $res = $this->db->queryF(
257 'SELECT parent, depth, path FROM ' . $this->getTree()->getTreeTable() .
' ' .
258 'WHERE child = %s ' .
' ' .
259 'AND ' . $this->getTree()->getTreePk() .
' = %s',
260 array(
'integer',
'integer'),
261 array($a_parent_id, $this->getTree()->getTreeId())
264 $r = $this->db->fetchObject(
$res);
266 if ($r->parent ===
null) {
271 if ($r->depth >= $this->getMaximumPossibleDepth()) {
276 $parentPath = $r->path;
277 $depth = (
int) $r->depth + 1;
282 $this->getTree()->getTreeTable(),
283 array($this->getTree()->getTreePk() => array(
'integer', $this->getTree()->getTreeId()),
284 'child' => array(
'integer', $a_node_id),
285 'parent' => array(
'integer', $a_parent_id),
286 'lft' => array(
'integer', $lft),
287 'rgt' => array(
'integer', $rgt),
288 'depth' => array(
'integer', $depth),
289 'path' => array(
'text', $parentPath .
"." . $a_node_id)
296 if ($this->getTree()->__isMainTree()) {
297 $ilAtomQuery = $this->db->buildAtomQuery();
298 $ilAtomQuery->addTableLock(
"tree");
299 $ilAtomQuery->addQueryCallable($insert_node_callable);
302 $insert_node_callable($this->db);
312 $query =
'SELECT * FROM ' . $this->getTree()->getTreeTable() .
' ' .
313 'WHERE ' . $this->getTree()->getTreeTable() .
'.child = %s ' .
314 'AND ' . $this->getTree()->getTreeTable() .
'.' . $this->getTree()->getTreePk() .
' = %s ';
315 $res = $this->db->queryF($query, array(
'integer',
'integer'), array(
317 $this->getTree()->getTreeId()
319 $node = $this->db->fetchAssoc(
$res);
325 $query =
'DELETE FROM ' . $this->getTree()->getTreeTable() .
' ' .
326 'WHERE path BETWEEN ' . $this->db->quote($node[
'path'],
'text') .
' ' .
327 'AND ' . $this->db->quote($node[
'path'] .
'.Z',
'text') .
' ' .
328 'AND ' . $this->getTree()->getTreePk() .
' = ' . $this->db->quote(
329 $this->getTree()->getTreeId(),
332 $this->db->manipulate($query);
336 if ($this->getTree()->__isMainTree()) {
337 $ilAtomQuery = $this->db->buildAtomQuery();
338 $ilAtomQuery->addTableLock(
'tree');
339 $ilAtomQuery->addQueryCallable($delete_tree_callable);
342 $delete_tree_callable($this->db);
352 $node = $this->getTree()->getNodeTreeData($a_node_id);
355 $this->db->manipulateF(
357 UPDATE ' . $this->getTree()->getTreeTable() .
' ' .
358 'SET tree = %s' .
' ' .
359 'WHERE ' . $this->getTree()->getTreePk() .
' = %s ' .
360 'AND path BETWEEN %s AND %s',
361 array(
'integer',
'integer',
'text',
'text'),
362 array(-$a_node_id, $this->getTree()->getTreeId(), $node[
'path'], $node[
'path'] .
'.Z')
368 if ($this->getTree()->__isMainTree()) {
369 $ilAtomQuery = $this->db->buildAtomQuery();
370 $ilAtomQuery->addTableLock(
"tree");
372 $ilAtomQuery->addQueryCallable($move_to_trash_callable);
376 $move_to_trash_callable($this->db);
384 public function moveTree(
int $a_source_id,
int $a_target_id,
int $a_position): void
386 $move_tree_callable =
function (
ilDBInterface $ilDB) use ($a_source_id, $a_target_id, $a_position):
void {
388 $this->db->setLimit(2, 0);
390 $res = $this->db->query(
391 'SELECT depth, child, parent, path FROM ' . $this->getTree()->getTreeTable() .
' ' .
392 'WHERE ' . $this->db->in(
'child', array($a_source_id, $a_target_id),
false,
'integer') .
' ' .
393 'AND tree = ' . $this->db->quote($this->getTree()->getTreeId(),
'integer')
397 if ($this->db->numRows(
$res) != 2) {
399 throw new InvalidArgumentException(
'Error moving subtree');
402 $source_depth = $target_depth = 0;
403 $source_path = $target_path =
'';
405 while ($row = $this->db->fetchObject(
$res)) {
406 if ($row->child == $a_source_id) {
407 $source_path = $row->path;
408 $source_depth = $row->depth;
409 $source_parent = $row->parent;
411 $target_path = $row->path;
412 $target_depth = $row->depth;
416 if ($target_depth >= $source_depth) {
422 $res = $this->db->queryF(
423 'SELECT MAX(depth) max_depth ' .
424 'FROM ' . $this->getTree()->getTreeTable() .
' ' .
425 'WHERE path BETWEEN %s AND %s' .
' ' .
427 array(
'text',
'text',
'integer'),
428 array($source_path, $source_path .
'.Z', $this->getTree()->getTreeId())
431 $row = $this->db->fetchObject(
$res);
433 if ($row->max_depth - $source_depth + $target_depth + 1 > $this->getMaximumPossibleDepth()) {
439 if ((substr($target_path .
'.', 0, strlen($source_path)) .
'.') == $source_path .
'.') {
441 throw new InvalidArgumentException(
'Error moving subtree: target is child of source');
443 $depth_diff = $target_depth - $source_depth + 1;
447 'UPDATE ' . $this->getTree()->getTreeTable() .
' ' .
448 'SET parent = CASE WHEN parent = ' . $this->db->quote($source_parent,
'integer') .
' ' .
449 'THEN ' . $this->db->quote($a_target_id,
'integer') .
' ' .
450 'ELSE parent END, path = ' .
451 $this->db->concat(array(
452 array($this->db->quote($target_path,
'text'),
'text'),
453 array($this->db->substr(
'path', strrpos(
'.' . $source_path,
'.')),
'text')
455 ',depth = depth + ' . $this->db->quote($depth_diff,
'integer') .
' ' .
456 'WHERE path BETWEEN ' . $this->db->quote($source_path,
'text') .
' ' .
457 'AND ' . $this->db->quote($source_path .
'.Z',
'text') .
' ';
459 if (!$this->getTree()->__isMainTree()) {
462 $this->db->manipulate($query);
465 if ($this->getTree()->__isMainTree()) {
466 $ilAtomQuery = $this->db->buildAtomQuery();
467 $ilAtomQuery->addTableLock(
"tree");
468 $ilAtomQuery->addQueryCallable($move_tree_callable);
471 $move_tree_callable($this->db);
477 $result = $db->
queryF(
'SELECT DISTINCT * FROM tree WHERE parent = %s', [
'integer'], [0]);
480 self::createMaterializedPath($db, 0,
'');
487 SET path = CONCAT(COALESCE(' . $db->
quote($parentPath,
'text') .
', \'\'), COALESCE( ' . $db->
cast(
490 ) .
' , \'\')) WHERE parent = %s';
492 $result = $db->
queryF(
'SELECT child FROM tree WHERE parent = %s', [
'integer'], [$parent]);
495 self::createMaterializedPath(
498 $parentPath . $row[
'child'] .
'.'
509 if ($this->getTree()->__isMainTree() && $this->getTree()->getTreeId() == 1) {
513 $treeClause1 =
' AND t1.' . $this->getTree()->getTreePk() .
' = ' . $this->db->quote(
514 $this->getTree()->getTreeId(),
517 $treeClause2 =
' AND t2.' . $this->getTree()->getTreePk() .
' = ' . $this->db->quote(
518 $this->getTree()->getTreeId(),
525 SELECT t1." . $this->getTree()->getTreePk() .
", t1.path
526 FROM " . $this->getTree()->getTreeTable() .
" t1
527 WHERE t1.child = " . $this->db->quote($a_endnode_id,
'integer') .
530 $res = $this->db->query($query);
531 $row = $this->db->fetchAssoc(
$res);
532 if ($row[$this->getTree()->getTreePk()] ??
null == $this->getTree()->getTreeId()) {
533 $path = (string) $row[
'path'];
539 $query =
"SELECT t2." . $this->getTree()->getTreePk() .
", t2.child child, t2.parent parent, type, t2.path path " .
540 "FROM " . $this->getTree()->getTreeTable() .
" t2 " .
541 "JOIN " . $this->getTree()->getTableReference() .
" obr ON t2.child = obr.ref_id " .
542 "JOIN " . $this->getTree()->getObjectDataTable() .
" obd ON obr.obj_id = obd.obj_id " .
543 "WHERE t2.path BETWEEN " . $this->db->quote(
$path,
'text') .
" AND " . $this->db->quote(
550 $res = $this->db->query($query);
552 while ($row = $this->db->fetchAssoc(
$res)) {
554 if ($row[$this->getTree()->getTreePk()] != $this->getTree()->getTreeId()) {
558 $nodes[$row[
'child']][
'child'] = (
int) $row[
'child'];
559 $nodes[$row[
'child']][
'parent'] = (
int) $row[
'parent'];
560 $nodes[$row[
'child']][
'type'] = (string) $row[
'type'];
561 $nodes[$row[
'child']][
'path'] = (string) $row[
'path'];
564 $depth_first_compare =
static function (array
$a, array
$b):
int {
565 $a_exploded = explode(
'.',
$a[
'path']);
566 $b_exploded = explode(
'.',
$b[
'path']);
569 foreach ($a_exploded as $num) {
570 $a_padded .= (str_pad((
string) $num, 14,
'0', STR_PAD_LEFT));
573 foreach ($b_exploded as $num) {
574 $b_padded .= (str_pad((
string) $num, 14,
'0', STR_PAD_LEFT));
577 return strcasecmp($a_padded, $b_padded);
580 uasort($nodes, $depth_first_compare);
590 $query =
'select ' . $this->getTree()->getTreePk() .
', child from ' . $this->getTree()->getTreeTable() .
' child where not exists ' .
592 'select child from ' . $this->getTree()->getTreeTable() .
' parent where child.parent = parent.child and ' .
593 '(child.path BETWEEN parent.path AND CONCAT(parent.path,' . $this->db->quote(
'Z',
'text') .
') )' .
')' .
594 'and ' . $this->getTree()->getTreePk() .
' = ' . $this->getTree()->getTreeId() .
' and child <> 1';
595 $res = $this->db->query($query);
598 $failures[] = $row[$this->getTree()->getTreePk()];
static getType()
Get context type.
Thrown if invalid tree strucutes are found.
Component logger with individual log levels by component id.
Base class for materialize path based trees Based on implementation of Werner Randelshofer.
deleteTree(int $a_node_id)
Delete tree.
getTrashSubTreeQuery(array $a_node, array $a_types, bool $a_force_join_reference=true, array $a_fields=[])
Get subtree query for trashed tree items.
validateParentRelations()
Validate the parent relations of the tree implementation For nested set, validate the lft,...
getTree()
Get tree object.
static createMaterializedPath(ilDBInterface $db, int $parent, string $parentPath)
__construct(ilTree $a_tree)
Constructor.
getMaximumPossibleDepth()
Get maximum possible depth.
getPathIds(int $a_endnode, int $a_startnode=0)
Get path ids from a startnode to a given endnode.int[]
moveTree(int $a_source_id, int $a_target_id, int $a_position)
Move a source subtree to target.
insertNode(int $a_node_id, int $a_parent_id, int $a_pos)
getSubTreeQuery(array $a_node, array $a_types=[], bool $a_force_join_reference=true, array $a_fields=[])
Get subtree query.
getSubtreeInfo(int $a_endnode_id)
moveToTrash(int $a_node_id)
Move subtree to trash.
getRelation(array $a_node_a, array $a_node_b)
Get relation of two nodes.
getSubTreeIds(int $a_node_id)
Get subtree ids.
static createFromParentRelation(ilDBInterface $db)
const MAXIMUM_POSSIBLE_DEPTH
Tree class data representation in hierachical trees using the Nested Set Model with Gaps by Joe Celco...
cast(string $a_field_name, string $a_dest_type)
quote($value, string $type)
manipulateF(string $query, array $types, array $values)
fetchAssoc(ilDBStatement $statement)
queryF(string $query, array $types, array $values)
Interface for tree implementations Currrently nested set or materialized path.
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples