3 declare(strict_types=1);
    44         $this->tree = $a_tree;
    45         $this->db = $DIC->database();
    47             $this->
logger = $DIC->logger()->tree();
    56         return self::MAXIMUM_POSSIBLE_DEPTH;
    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(
   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'];
   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(
   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(
   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) {
   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 . 
'.') {
   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(
   517             $treeClause2 = 
' AND t2.' . $this->
getTree()->getTreePk() . 
' = ' . $this->db->quote(
   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()];
 
Thrown if invalid tree strucutes are found. 
 
deleteTree(int $a_node_id)
Delete tree. 
 
getMaximumPossibleDepth()
Get maximum possible depth. 
 
manipulateF(string $query, array $types, array $values)
 
fetchAssoc(ilDBStatement $statement)
 
moveTree(int $a_source_id, int $a_target_id, int $a_position)
Move a source subtree to target. 
 
quote($value, string $type)
 
insertNode(int $a_node_id, int $a_parent_id, int $a_pos)
 
 
getPathIds(int $a_endnode, int $a_startnode=0)
Get path ids from a startnode to a given endnode.int[] 
 
moveToTrash(int $a_node_id)
Move subtree to trash. 
 
validateParentRelations()
Validate the parent relations of the tree implementation For nested set, validate the lft...
 
const MAXIMUM_POSSIBLE_DEPTH
 
cast(string $a_field_name, string $a_dest_type)
 
getSubTreeIds(int $a_node_id)
Get subtree ids. 
 
queryF(string $query, array $types, array $values)
 
getTree()
Get tree object. 
 
getSubtreeInfo(int $a_endnode_id)
 
getSubTreeQuery(array $a_node, array $a_types=[], bool $a_force_join_reference=true, array $a_fields=[])
Get subtree query. 
 
getRelation(array $a_node_a, array $a_node_b)
Get relation of two nodes. 
 
static createMaterializedPath(ilDBInterface $db, int $parent, string $parentPath)
 
getTrashSubTreeQuery(array $a_node, array $a_types, bool $a_force_join_reference=true, array $a_fields=[])
Get subtree query for trashed tree items. 
 
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples 
 
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
 
static createFromParentRelation(ilDBInterface $db)
 
static getType()
Get context type. 
 
Interface for tree implementations Currrently nested set or materialized path. 
 
__construct(ilTree $a_tree)
Constructor.