ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
ilNestedSetTree Class Reference

Base class for nested set path based trees. More...

+ Inheritance diagram for ilNestedSetTree:
+ Collaboration diagram for ilNestedSetTree:

Public Member Functions

 __construct (ilTree $a_tree)
 Constructor. More...
 
 getTree ()
 Get tree object. More...
 
 getSubTreeIds ($a_node_id)
 Get subtree ids. More...
 
 getSubTreeQuery ($a_node, $a_types='', $a_force_join_reference=true, $a_fields=array())
 Get subtree. More...
 
 getRelation ($a_node_a, $a_node_b)
 Get relation. More...
 
 getPathIds ($a_endnode, $a_startnode=0)
 Get path ids. More...
 
 insertNode ($a_node_id, $a_parent_id, $a_pos)
 Insert tree node. More...
 
 deleteTree ($a_node_id)
 Delete a subtree. More...
 
 moveToTrash ($a_node_id)
 Move to trash. More...
 
 getPathIdsUsingNestedSets ($a_endnode_id, $a_startnode_id=0)
 get path from a given startnode to a given endnode if startnode is not given the rootnode is startnode @access public More...
 
 moveTree ($a_source_id, $a_target_id, $a_position)
 Move source subtree to target. More...
 
 getSubtreeInfo ($a_endnode_id)
 Get rbac subtree info @global type $ilDB. More...
 
 validateParentRelations ()
 Validate the parent relations of the tree implementation For nested set, validate the lft, rgt against child <-> parent For materialized path validate path against child <-> parent. More...
 
 getSubTreeIds ($a_node_id)
 Get subtree ids for a specific node. More...
 
 getSubTreeQuery ($a_node, $a_types='', $a_force_join_reference=true, $a_fields=array())
 Get subtree. More...
 
 getRelation ($a_node_a, $a_node_b)
 Get relation of two nodes. More...
 
 getPathIds ($a_endnode, $a_startnode=0)
 Get path ids from a startnode to a given endnode. More...
 
 insertNode ($a_node_id, $a_parent_id, $a_pos)
 
 deleteTree ($a_node_id)
 Delete tree. More...
 
 moveToTrash ($a_node_id)
 Move subtree to trash. More...
 
 moveTree ($a_source_id, $a_target_id, $a_position)
 Move a source subtree to target. More...
 
 getSubtreeInfo ($a_endnode_id)
 Get subtree info lft, rgt, path, child, type. More...
 
 validateParentRelations ()
 Validate the parent relations of the tree implementation For nested set, validate the lft, rgt against child <-> parent For materialized path validate path against child <-> parent. More...
 

Protected Member Functions

 getPathIdsUsingAdjacencyMap ($a_endnode_id, $a_startnode_id=0)
 get path from a given startnode to a given endnode if startnode is not given the rootnode is startnode @access public More...
 

Private Attributes

 $tree = null
 

Detailed Description

Base class for nested set path based trees.

Author
Stefan Meyer meyer.nosp@m.@lei.nosp@m.fos.c.nosp@m.om
Version
$Id$

Definition at line 16 of file class.ilNestedSetTree.php.

Constructor & Destructor Documentation

◆ __construct()

ilNestedSetTree::__construct ( ilTree  $a_tree)

Constructor.

Parameters
ilTree$a_tree

Definition at line 24 of file class.ilNestedSetTree.php.

25 {
26 $this->tree = $a_tree;
27 }

Member Function Documentation

◆ deleteTree()

ilNestedSetTree::deleteTree (   $a_node_id)

Delete a subtree.

Parameters
int$a_node_id
Returns
bool

Implements ilTreeImplementation.

Definition at line 394 of file class.ilNestedSetTree.php.

395 {
396 global $DIC;
397
398 $ilDB = $DIC['ilDB'];
399
400 $delete_tree_callable = function (ilDBInterface $ilDB) use ($a_node_id) {
401
402 // Fetch lft, rgt directly (without fetchNodeData) to avoid unnecessary table locks
403 // (object_reference, object_data)
404 $query = 'SELECT * FROM ' . $this->getTree()->getTreeTable() . ' ' .
405 'WHERE child = ' . $ilDB->quote($a_node_id, 'integer') . ' ' .
406 'AND ' . $this->getTree()->getTreePk() . ' = ' . $ilDB->quote($this->getTree()->getTreeId(), 'integer');
407 $res = $ilDB->query($query);
408 $a_node = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC);
409
410 // delete subtree
411 $query = sprintf(
412 'DELETE FROM ' . $this->getTree()->getTreeTable() . ' ' .
413 'WHERE lft BETWEEN %s AND %s ' .
414 'AND rgt BETWEEN %s AND %s ' .
415 'AND ' . $this->getTree()->getTreePk() . ' = %s',
416 $ilDB->quote($a_node['lft'], 'integer'),
417 $ilDB->quote($a_node['rgt'], 'integer'),
418 $ilDB->quote($a_node['lft'], 'integer'),
419 $ilDB->quote($a_node['rgt'], 'integer'),
420 $ilDB->quote($a_node[$this->getTree()->getTreePk()], 'integer')
421 );
422 $res = $ilDB->manipulate($query);
423
424 // Performance improvement: We only close the gap, if the node
425 // is not in a trash tree, and if the resulting gap will be
426 // larger than twice the gap value
427
428 $diff = $a_node["rgt"] - $a_node["lft"] + 1;
429 if (
430 $a_node[$this->getTree()->getTreePk()] >= 0 &&
431 $a_node['rgt'] - $a_node['lft'] >= $this->getTree()->getGap() * 2
432 ) {
433 // close gaps
434 $query = sprintf(
435 'UPDATE ' . $this->getTree()->getTreeTable() . ' SET ' .
436 'lft = CASE WHEN lft > %s THEN lft - %s ELSE lft END, ' .
437 'rgt = CASE WHEN rgt > %s THEN rgt - %s ELSE rgt END ' .
438 'WHERE ' . $this->getTree()->getTreePk() . ' = %s ',
439 $ilDB->quote($a_node['lft'], 'integer'),
440 $ilDB->quote($diff, 'integer'),
441 $ilDB->quote($a_node['lft'], 'integer'),
442 $ilDB->quote($diff, 'integer'),
443 $ilDB->quote($a_node[$this->getTree()->getTreePk()], 'integer')
444 );
445
446 $res = $ilDB->manipulate($query);
447 }
448 };
449
450 // get lft and rgt values. Don't trust parameter lft/rgt values of $a_node
451 if ($this->getTree()->__isMainTree()) {
452 $ilAtomQuery = $ilDB->buildAtomQuery();
453 $ilAtomQuery->addTableLock('tree');
454 $ilAtomQuery->addQueryCallable($delete_tree_callable);
455 $ilAtomQuery->run();
456 } else {
457 $delete_tree_callable($ilDB);
458 }
459
460 return true;
461 }
getTree()
Get tree object.
Interface ilDBInterface.
$query
global $DIC
Definition: saml.php:7
foreach($_POST as $key=> $value) $res
global $ilDB

References $DIC, $ilDB, $query, $res, ilDBConstants\FETCHMODE_ASSOC, and getTree().

+ Here is the call graph for this function:

◆ getPathIds()

ilNestedSetTree::getPathIds (   $a_endnode,
  $a_startnode = 0 
)

Get path ids.

Parameters
int$a_endnode
int$a_startnode
Returns
int[]

Implements ilTreeImplementation.

Definition at line 155 of file class.ilNestedSetTree.php.

156 {
157 return $this->getPathIdsUsingAdjacencyMap($a_endnode, $a_startnode);
158 }
getPathIdsUsingAdjacencyMap($a_endnode_id, $a_startnode_id=0)
get path from a given startnode to a given endnode if startnode is not given the rootnode is startnod...

References getPathIdsUsingAdjacencyMap().

+ Here is the call graph for this function:

◆ getPathIdsUsingAdjacencyMap()

ilNestedSetTree::getPathIdsUsingAdjacencyMap (   $a_endnode_id,
  $a_startnode_id = 0 
)
protected

get path from a given startnode to a given endnode if startnode is not given the rootnode is startnode @access public

Parameters
integernode_id of endnode
integernode_id of startnode (optional)
Returns
array all path ids from startnode to endnode

Definition at line 513 of file class.ilNestedSetTree.php.

514 {
515 global $DIC;
516
517 $ilDB = $DIC['ilDB'];
518
519 // The adjacency map algorithm is harder to implement than the nested sets algorithm.
520 // This algorithms performs an index search for each of the path element.
521 // This algorithms performs well for large trees which are not deeply nested.
522
523 // The $takeId variable is used, to determine if a given id shall be included in the path
524 $takeId = $a_startnode_id == 0;
525
526 $depth_cache = $this->getTree()->getDepthCache();
527 $parent_cache = $this->getTree()->getParentCache();
528
529 if (
530 $this->getTree()->__isMainTree() &&
531 isset($depth_cache[$a_endnode_id]) &&
532 isset($parent_cache[$a_endnode_id])) {
533 $nodeDepth = $depth_cache[$a_endnode_id];
534 $parentId = $parent_cache[$a_endnode_id];
535 } else {
536 $nodeDepth = $this->getTree()->getDepth($a_endnode_id);
537 $parentId = $this->getTree()->getParentId($a_endnode_id);
538 }
539
540 // Fetch the node ids. For shallow depths we can fill in the id's directly.
541 $pathIds = array();
542
543 // backward compatible check for nodes not in tree
544 if (!$nodeDepth) {
545 return array();
546 } elseif ($nodeDepth == 1) {
547 $takeId = $takeId || $a_endnode_id == $a_startnode_id;
548 if ($takeId) {
549 $pathIds[] = $a_endnode_id;
550 }
551 } elseif ($nodeDepth == 2) {
552 $takeId = $takeId || $parentId == $a_startnode_id;
553 if ($takeId) {
554 $pathIds[] = $parentId;
555 }
556 $takeId = $takeId || $a_endnode_id == $a_startnode_id;
557 if ($takeId) {
558 $pathIds[] = $a_endnode_id;
559 }
560 } elseif ($nodeDepth == 3) {
561 $takeId = $takeId || $this->getTree()->getRootId() == $a_startnode_id;
562 if ($takeId) {
563 $pathIds[] = $this->getTree()->getRootId();
564 }
565 $takeId = $takeId || $parentId == $a_startnode_id;
566 if ($takeId) {
567 $pathIds[] = $parentId;
568 }
569 $takeId = $takeId || $a_endnode_id == $a_startnode_id;
570 if ($takeId) {
571 $pathIds[] = $a_endnode_id;
572 }
573 } elseif ($nodeDepth < 32) {
574 // Adjacency Map Tree performs better than
575 // Nested Sets Tree even for very deep trees.
576 // The following code construct nested self-joins
577 // Since we already know the root-id of the tree and
578 // we also know the id and parent id of the current node,
579 // we only need to perform $nodeDepth - 3 self-joins.
580 // We can further reduce the number of self-joins by 1
581 // by taking into account, that each row in table tree
582 // contains the id of itself and of its parent.
583 $qSelect = 't1.child c0';
584 $qJoin = '';
585 for ($i = 1; $i < $nodeDepth - 2; $i++) {
586 $qSelect .= ', t' . $i . '.parent c' . $i;
587 $qJoin .= ' JOIN ' . $this->getTree()->getTreeTable() . ' t' . $i . ' ON ' .
588 't' . $i . '.child=t' . ($i - 1) . '.parent AND ' .
589 't' . $i . '.' . $this->getTree()->getTreePk() . ' = ' . (int) $this->getTree()->getTreeId();
590 }
591
592 $types = array('integer','integer');
593 $data = array($this->getTree()->getTreeId(),$parentId);
594 $query = 'SELECT ' . $qSelect . ' ' .
595 'FROM ' . $this->getTree()->getTreeTable() . ' t0 ' . $qJoin . ' ' .
596 'WHERE t0.' . $this->getTree()->getTreePk() . ' = %s ' .
597 'AND t0.child = %s ';
598
599 $ilDB->setLimit(1);
600 $res = $ilDB->queryF($query, $types, $data);
601
602 if ($res->numRows() == 0) {
603 return array();
604 }
605
606 $row = $ilDB->fetchAssoc($res);
607
608 $takeId = $takeId || $this->getTree()->getRootId() == $a_startnode_id;
609 if ($takeId) {
610 $pathIds[] = $this->getTree()->getRootId();
611 }
612 for ($i = $nodeDepth - 4; $i >= 0; $i--) {
613 $takeId = $takeId || $row['c' . $i] == $a_startnode_id;
614 if ($takeId) {
615 $pathIds[] = $row['c' . $i];
616 }
617 }
618 $takeId = $takeId || $parentId == $a_startnode_id;
619 if ($takeId) {
620 $pathIds[] = $parentId;
621 }
622 $takeId = $takeId || $a_endnode_id == $a_startnode_id;
623 if ($takeId) {
624 $pathIds[] = $a_endnode_id;
625 }
626 } else {
627 // Fall back to nested sets tree for extremely deep tree structures
628 return $this->getPathIdsUsingNestedSets($a_endnode_id, $a_startnode_id);
629 }
630 return $pathIds;
631 }
getPathIdsUsingNestedSets($a_endnode_id, $a_startnode_id=0)
get path from a given startnode to a given endnode if startnode is not given the rootnode is startnod...
$i
Definition: disco.tpl.php:19
$row
$data
Definition: bench.php:6

References $data, $DIC, $i, $ilDB, $query, $res, $row, getPathIdsUsingNestedSets(), and getTree().

Referenced by getPathIds().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ getPathIdsUsingNestedSets()

ilNestedSetTree::getPathIdsUsingNestedSets (   $a_endnode_id,
  $a_startnode_id = 0 
)

get path from a given startnode to a given endnode if startnode is not given the rootnode is startnode @access public

Parameters
integernode_id of endnode
integernode_id of startnode (optional)
Returns
array all path ids from startnode to endnode

Definition at line 641 of file class.ilNestedSetTree.php.

642 {
643 global $DIC;
644
645 $ilDB = $DIC['ilDB'];
646
647 // The nested sets algorithm is very easy to implement.
648 // Unfortunately it always does a full table space scan to retrieve the path
649 // regardless whether indices on lft and rgt are set or not.
650 // (At least, this is what happens on MySQL 4.1).
651 // This algorithms performs well for small trees which are deeply nested.
652
653 $fields = array('integer','integer','integer');
654 $data = array($a_endnode_id,$this->getTree()->getTreeId(),$this->getTree()->getTreeId());
655
656 $query = "SELECT T2.child " .
657 "FROM " . $this->getTree()->getTreeTable() . " T1, " . $this->getTree()->getTreeTable() . " T2 " .
658 "WHERE T1.child = %s " .
659 "AND T1.lft BETWEEN T2.lft AND T2.rgt " .
660 "AND T1." . $this->getTree()->getTreePk() . " = %s " .
661 "AND T2." . $this->getTree()->getTreePk() . " = %s " .
662 "ORDER BY T2.depth";
663
664 $res = $ilDB->queryF($query, $fields, $data);
665
666 $takeId = $a_startnode_id == 0;
667 while ($row = $ilDB->fetchAssoc($res)) {
668 if ($takeId || $row['child'] == $a_startnode_id) {
669 $takeId = true;
670 $pathIds[] = $row['child'];
671 }
672 }
673 return $pathIds ? $pathIds : array();
674 }

References $data, $DIC, $ilDB, $query, $res, $row, and getTree().

Referenced by getPathIdsUsingAdjacencyMap().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ getRelation()

ilNestedSetTree::getRelation (   $a_node_a,
  $a_node_b 
)

Get relation.

Parameters
type$a_node_a
type$a_node_b
Returns
int

Implements ilTreeImplementation.

Definition at line 125 of file class.ilNestedSetTree.php.

126 {
127 if ($a_node_a['child'] == $a_node_b['child']) {
128 ilLoggerFactory::getLogger('tree')->debug('EQUALS');
130 }
131 if ($a_node_a['lft'] < $a_node_b['lft'] and $a_node_a['rgt'] > $a_node_b['rgt']) {
132 ilLoggerFactory::getLogger('tree')->debug('PARENT');
134 }
135 if ($a_node_b['lft'] < $a_node_a['lft'] and $a_node_b['rgt'] > $a_node_a['rgt']) {
136 ilLoggerFactory::getLogger('tree')->debug('CHILD');
138 }
139
140 // if node is also parent of node b => sibling
141 if ($a_node_a['parent'] == $a_node_b['parent']) {
142 ilLoggerFactory::getLogger('tree')->debug('SIBLING');
144 }
145 ilLoggerFactory::getLogger('tree')->debug('NONE');
147 }
static getLogger($a_component_id)
Get component logger.
const RELATION_EQUALS
const RELATION_PARENT
const RELATION_NONE
const RELATION_SIBLING
const RELATION_CHILD

References ilLoggerFactory\getLogger(), ilTree\RELATION_CHILD, ilTree\RELATION_EQUALS, ilTree\RELATION_NONE, ilTree\RELATION_PARENT, and ilTree\RELATION_SIBLING.

+ Here is the call graph for this function:

◆ getSubTreeIds()

ilNestedSetTree::getSubTreeIds (   $a_node_id)

Get subtree ids.

Parameters
type$a_node_id
Returns
int[]

Implements ilTreeImplementation.

Definition at line 43 of file class.ilNestedSetTree.php.

44 {
45 global $DIC;
46
47 $ilDB = $DIC['ilDB'];
48
49 $query = 'SELECT s.child FROM ' .
50 $this->getTree()->getTreeTable() . ' s, ' .
51 $this->getTree()->getTreeTable() . ' t ' .
52 'WHERE t.child = %s ' .
53 'AND s.lft > t.lft ' .
54 'AND s.rgt < t.rgt ' .
55 'AND s.' . $this->getTree()->getTreePk() . ' = %s';
56
57 $res = $ilDB->queryF(
58 $query,
59 array('integer','integer'),
60 array($a_node_id,$this->getTree()->getTreeId())
61 );
62 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
63 $childs[] = $row->child;
64 }
65 return $childs ? $childs : array();
66 }

References $DIC, $ilDB, $query, $res, $row, ilDBConstants\FETCHMODE_OBJECT, and getTree().

+ Here is the call graph for this function:

◆ getSubtreeInfo()

ilNestedSetTree::getSubtreeInfo (   $a_endnode_id)

Get rbac subtree info @global type $ilDB.

Parameters
type$a_endnode_id
Returns
array

Implements ilTreeImplementation.

Definition at line 802 of file class.ilNestedSetTree.php.

803 {
804 global $DIC;
805
806 $ilDB = $DIC['ilDB'];
807
808 $query = "SELECT t2.lft lft, t2.rgt rgt, t2.child child, type " .
809 "FROM " . $this->getTree()->getTreeTable() . " t1 " .
810 "JOIN " . $this->getTree()->getTreeTable() . " t2 ON (t2.lft BETWEEN t1.lft AND t1.rgt) " .
811 "JOIN " . $this->getTree()->getTableReference() . " obr ON t2.child = obr.ref_id " .
812 "JOIN " . $this->getTree()->getObjectDataTable() . " obd ON obr.obj_id = obd.obj_id " .
813 "WHERE t1.child = " . $ilDB->quote($a_endnode_id, 'integer') . " " .
814 "AND t1." . $this->getTree()->getTreePk() . " = " . $ilDB->quote($this->getTree()->getTreeId(), 'integer') . " " .
815 "AND t2." . $this->getTree()->getTreePk() . " = " . $ilDB->quote($this->getTree()->getTreeId(), 'integer') . " " .
816 "ORDER BY t2.lft";
817
818 ilLoggerFactory::getLogger('tree')->debug($query);
819
820
821 $res = $ilDB->query($query);
822 $nodes = array();
823 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
824 $nodes[$row->child]['lft'] = $row->lft;
825 $nodes[$row->child]['rgt'] = $row->rgt;
826 $nodes[$row->child]['child'] = $row->child;
827 $nodes[$row->child]['type'] = $row->type;
828 }
829 return (array) $nodes;
830 }

References $DIC, $ilDB, $nodes, $query, $res, $row, ilDBConstants\FETCHMODE_OBJECT, ilLoggerFactory\getLogger(), and getTree().

+ Here is the call graph for this function:

◆ getSubTreeQuery()

ilNestedSetTree::getSubTreeQuery (   $a_node,
  $a_types = '',
  $a_force_join_reference = true,
  $a_fields = array() 
)

Get subtree.

Parameters
type$a_node
string$a_types
bool$a_force_join_reference
array$a_fields
Returns
string query

Implements ilTreeImplementation.

Definition at line 77 of file class.ilNestedSetTree.php.

78 {
79 global $DIC;
80
81 $ilDB = $DIC['ilDB'];
82
83 $type_str = '';
84 if (is_array($a_types)) {
85 if ($a_types) {
86 $type_str = "AND " . $ilDB->in($this->getTree()->getObjectDataTable() . ".type", $a_types, false, "text");
87 }
88 } elseif (strlen($a_types)) {
89 $type_str = "AND " . $this->getTree()->getObjectDataTable() . ".type = " . $ilDB->quote($a_types, "text");
90 }
91
92 $join = '';
93 if ($type_str or $a_force_join_reference) {
94 $join = $this->getTree()->buildJoin();
95 }
96
97 $fields = '* ';
98 if (count($a_fields)) {
99 $fields = implode(',', $a_fields);
100 }
101
102 $query = 'SELECT ' .
103 $fields . ' ' .
104 "FROM " . $this->getTree()->getTreeTable() . " " .
105 $join . ' ' .
106 "WHERE " . $this->getTree()->getTreeTable() . '.lft ' .
107 'BETWEEN ' . $ilDB->quote($a_node['lft'], 'integer') . ' ' .
108 'AND ' . $ilDB->quote($a_node['rgt'], 'integer') . ' ' .
109 "AND " . $this->getTree()->getTreeTable() . "." . $this->getTree()->getTreePk() . " = " . $ilDB->quote($this->getTree()->getTreeId(), 'integer') . ' ' .
110 $type_str . ' ' .
111 "ORDER BY " . $this->getTree()->getTreeTable() . ".lft";
112
113 #ilLoggerFactory::getLogger('tree')->debug('-----------------: '. $query);
114
115 return $query;
116 }

References $DIC, $ilDB, $query, and getTree().

+ Here is the call graph for this function:

◆ getTree()

ilNestedSetTree::getTree ( )

Get tree object.

Returns
ilTree $tree

Definition at line 33 of file class.ilNestedSetTree.php.

34 {
35 return $this->tree;
36 }

References $tree.

Referenced by deleteTree(), getPathIdsUsingAdjacencyMap(), getPathIdsUsingNestedSets(), getSubTreeIds(), getSubtreeInfo(), getSubTreeQuery(), insertNode(), moveToTrash(), moveTree(), and validateParentRelations().

+ Here is the caller graph for this function:

◆ insertNode()

ilNestedSetTree::insertNode (   $a_node_id,
  $a_parent_id,
  $a_pos 
)

Insert tree node.

Parameters
type$a_node_id
type$a_parent_id
type$a_pos
Exceptions
ilInvalidTreeStructureException

Implements ilTreeImplementation.

Definition at line 168 of file class.ilNestedSetTree.php.

169 {
170 global $DIC;
171
172 $ilDB = $DIC['ilDB'];
173
174 $insert_node_callable = function (ilDBInterface $ilDB) use ($a_node_id, $a_parent_id, $a_pos) {
175 switch ($a_pos) {
177
178 // get left value of parent
179 $query = sprintf(
180 'SELECT * FROM ' . $this->getTree()->getTreeTable() . ' ' .
181 'WHERE child = %s ' .
182 'AND ' . $this->getTree()->getTreePk() . ' = %s ',
183 $ilDB->quote($a_parent_id, 'integer'),
184 $ilDB->quote($this->getTree()->getTreeId(), 'integer')
185 );
186
187 $res = $ilDB->query($query);
188 $r = $ilDB->fetchObject($res);
189
190 if ($r->parent === null) {
192 throw new ilInvalidTreeStructureException('Parent with id ' . $a_parent_id . ' not found in tree');
193 }
194
195 $left = $r->lft;
196 $lft = $left + 1;
197 $rgt = $left + 2;
198
199 // spread tree
200 $query = sprintf(
201 'UPDATE ' . $this->getTree()->getTreeTable() . ' SET ' .
202 'lft = CASE WHEN lft > %s THEN lft + 2 ELSE lft END, ' .
203 'rgt = CASE WHEN rgt > %s THEN rgt + 2 ELSE rgt END ' .
204 'WHERE ' . $this->getTree()->getTreePk() . ' = %s ',
205 $ilDB->quote($left, 'integer'),
206 $ilDB->quote($left, 'integer'),
207 $ilDB->quote($this->getTree()->getTreeId(), 'integer')
208 );
209 $res = $ilDB->manipulate($query);
210 break;
211
212 case IL_LAST_NODE:
213 // Special treatment for trees with gaps
214 if ($this->getTree()->getGap() > 0) {
215 // get lft and rgt value of parent
216 $query = sprintf(
217 'SELECT rgt,lft,parent FROM ' . $this->getTree()->getTreeTable() . ' ' .
218 'WHERE child = %s ' .
219 'AND ' . $this->getTree()->getTreePk() . ' = %s',
220 $ilDB->quote($a_parent_id, 'integer'),
221 $ilDB->quote($this->getTree()->getTreeId(), 'integer')
222 );
223 $res = $ilDB->query($query);
224 $r = $ilDB->fetchAssoc($res);
225
226 if ($r['parent'] === null) {
228 throw new ilInvalidTreeStructureException('Parent with id ' . $a_parent_id . ' not found in tree');
229 }
230 $parentRgt = $r['rgt'];
231 $parentLft = $r['lft'];
232
233 // Get the available space, without taking children into account yet
234 $availableSpace = $parentRgt - $parentLft;
235 if ($availableSpace < 2) {
236 // If there is not enough space between parent lft and rgt, we don't need
237 // to look any further, because we must spread the tree.
238 $lft = $parentRgt;
239 } else {
240 // If there is space between parent lft and rgt, we need to check
241 // whether there is space left between the rightmost child of the
242 // parent and parent rgt.
243 $query = sprintf(
244 'SELECT MAX(rgt) max_rgt FROM ' . $this->getTree()->getTreeTable() . ' ' .
245 'WHERE parent = %s ' .
246 'AND ' . $this->getTree()->getTreePk() . ' = %s',
247 $ilDB->quote($a_parent_id, 'integer'),
248 $ilDB->quote($this->getTree()->getTreeId(), 'integer')
249 );
250 $res = $ilDB->query($query);
251 $r = $ilDB->fetchAssoc($res);
252
253 if (isset($r['max_rgt'])) {
254 // If the parent has children, we compute the available space
255 // between rgt of the rightmost child and parent rgt.
256 $availableSpace = $parentRgt - $r['max_rgt'];
257 $lft = $r['max_rgt'] + 1;
258 } else {
259 // If the parent has no children, we know now, that we can
260 // add the new node at parent lft + 1 without having to spread
261 // the tree.
262 $lft = $parentLft + 1;
263 }
264 }
265 $rgt = $lft + 1;
266
267
268 // spread tree if there is not enough space to insert the new node
269 if ($availableSpace < 2) {
270 //$this->log->write('ilTree.insertNode('.$a_node_id.','.$a_parent_id.') creating gap at '.$a_parent_id.' '.$parentLft.'..'.$parentRgt.'+'.(2 + $this->gap * 2));
271 $query = sprintf(
272 'UPDATE ' . $this->getTree()->getTreeTable() . ' SET ' .
273 'lft = CASE WHEN lft > %s THEN lft + %s ELSE lft END, ' .
274 'rgt = CASE WHEN rgt >= %s THEN rgt + %s ELSE rgt END ' .
275 'WHERE ' . $this->getTree()->getTreePk() . ' = %s ',
276 $ilDB->quote($parentRgt, 'integer'),
277 $ilDB->quote((2 + $this->getTree()->getGap() * 2), 'integer'),
278 $ilDB->quote($parentRgt, 'integer'),
279 $ilDB->quote((2 + $this->getTree()->getGap() * 2), 'integer'),
280 $ilDB->quote($this->getTree()->getTreeId(), 'integer')
281 );
282 $res = $ilDB->manipulate($query);
283 }
284 }
285 // Treatment for trees without gaps
286 else {
287
288 // get right value of parent
289 $query = sprintf(
290 'SELECT * FROM ' . $this->getTree()->getTreeTable() . ' ' .
291 'WHERE child = %s ' .
292 'AND ' . $this->getTree()->getTreePk() . ' = %s ',
293 $ilDB->quote($a_parent_id, 'integer'),
294 $ilDB->quote($this->getTree()->getTreeId(), 'integer')
295 );
296 $res = $ilDB->query($query);
297 $r = $ilDB->fetchObject($res);
298
299 if ($r->parent === null) {
301 throw new ilInvalidTreeStructureException('Parent with id ' . $a_parent_id . ' not found in tree');
302 }
303
304 $right = $r->rgt;
305 $lft = $right;
306 $rgt = $right + 1;
307
308 // spread tree
309 $query = sprintf(
310 'UPDATE ' . $this->getTree()->getTreeTable() . ' SET ' .
311 'lft = CASE WHEN lft > %s THEN lft + 2 ELSE lft END, ' .
312 'rgt = CASE WHEN rgt >= %s THEN rgt + 2 ELSE rgt END ' .
313 'WHERE ' . $this->getTree()->getTreePk() . ' = %s',
314 $ilDB->quote($right, 'integer'),
315 $ilDB->quote($right, 'integer'),
316 $ilDB->quote($this->getTree()->getTreeId(), 'integer')
317 );
318 $res = $ilDB->manipulate($query);
319 }
320
321 break;
322
323 default:
324
325 // get right value of preceeding child
326 $query = sprintf(
327 'SELECT * FROM ' . $this->getTree()->getTreeTable() . ' ' .
328 'WHERE child = %s ' .
329 'AND ' . $this->getTree()->getTreePk() . ' = %s ',
330 $ilDB->quote($a_pos, 'integer'),
331 $ilDB->quote($this->getTree()->getTreeId(), 'integer')
332 );
333 $res = $ilDB->query($query);
334 $r = $ilDB->fetchObject($res);
335
336 // crosscheck parents of sibling and new node (must be identical)
337 if ($r->parent != $a_parent_id) {
339 throw new ilInvalidTreeStructureException('Parent with id ' . $a_parent_id . ' not found in tree');
340 }
341
342 $right = $r->rgt;
343 $lft = $right + 1;
344 $rgt = $right + 2;
345
346 // update lft/rgt values
347 $query = sprintf(
348 'UPDATE ' . $this->getTree()->getTreeTable() . ' SET ' .
349 'lft = CASE WHEN lft > %s THEN lft + 2 ELSE lft END, ' .
350 'rgt = CASE WHEN rgt > %s THEN rgt + 2 ELSE rgt END ' .
351 'WHERE ' . $this->getTree()->getTreePk() . ' = %s',
352 $ilDB->quote($right, 'integer'),
353 $ilDB->quote($right, 'integer'),
354 $ilDB->quote($this->getTree()->getTreeId(), 'integer')
355 );
356 $res = $ilDB->manipulate($query);
357 break;
358
359 }
360
361 // get depth
362 $depth = $this->getTree()->getDepth($a_parent_id) + 1;
363
364 // insert node
365 $query = sprintf(
366 'INSERT INTO ' . $this->getTree()->getTreeTable() . ' (' . $this->getTree()->getTreePk() . ',child,parent,lft,rgt,depth) ' .
367 'VALUES (%s,%s,%s,%s,%s,%s)',
368 $ilDB->quote($this->getTree()->getTreeId(), 'integer'),
369 $ilDB->quote($a_node_id, 'integer'),
370 $ilDB->quote($a_parent_id, 'integer'),
371 $ilDB->quote($lft, 'integer'),
372 $ilDB->quote($rgt, 'integer'),
373 $ilDB->quote($depth, 'integer')
374 );
375 $res = $ilDB->manipulate($query);
376 };
377
378 if ($this->getTree()->__isMainTree()) {
379 $ilAtomQuery = $ilDB->buildAtomQuery();
380 $ilAtomQuery->addTableLock('tree');
381 $ilAtomQuery->addQueryCallable($insert_node_callable);
382 $ilAtomQuery->run();
383 } else {
384 $insert_node_callable($ilDB);
385 }
386 }
const IL_LAST_NODE
Definition: class.ilTree.php:4
Thrown if invalid tree strucutes are found.
const POS_FIRST_NODE
$r
Definition: example_031.php:79

References $DIC, $ilDB, $query, $r, $res, ilLogLevel\ERROR, ilLoggerFactory\getLogger(), getTree(), IL_LAST_NODE, and ilTree\POS_FIRST_NODE.

+ Here is the call graph for this function:

◆ moveToTrash()

ilNestedSetTree::moveToTrash (   $a_node_id)

Move to trash.

Parameters
int$a_node_id
Returns
bool
Todo:
lock table locktable already active due to calling method

Implements ilTreeImplementation.

Definition at line 471 of file class.ilNestedSetTree.php.

472 {
473 global $DIC;
474
475 $ilDB = $DIC['ilDB'];
476
477 $move_to_trash_callable = function (ilDBInterface $ilDB) use ($a_node_id) {
478 $node = $this->getTree()->getNodeTreeData($a_node_id);
479
480 $query = 'UPDATE ' . $this->getTree()->getTreeTable() . ' ' .
481 'SET tree = ' . $ilDB->quote(-1 * $node['child'], 'integer') . ' ' .
482 'WHERE ' . $this->getTree()->getTreePk() . ' = ' . $ilDB->quote($this->getTree()->getTreeId(), 'integer') . ' ' .
483 'AND lft BETWEEN ' . $ilDB->quote($node['lft'], 'integer') . ' AND ' . $ilDB->quote($node['rgt'], 'integer') . ' ';
484
485 $ilDB->manipulate($query);
486 };
487
488 // use ilAtomQuery to lock tables if tree is main tree
489 // otherwise just call this closure without locking
490 if ($this->getTree()->__isMainTree()) {
491 $ilAtomQuery = $ilDB->buildAtomQuery();
492 $ilAtomQuery->addTableLock("tree");
493
494 $ilAtomQuery->addQueryCallable($move_to_trash_callable);
495
496 $ilAtomQuery->run();
497 } else {
498 $move_to_trash_callable($ilDB);
499 }
500
501 return true;
502 }

References $DIC, $ilDB, $query, and getTree().

+ Here is the call graph for this function:

◆ moveTree()

ilNestedSetTree::moveTree (   $a_source_id,
  $a_target_id,
  $a_position 
)

Move source subtree to target.

Parameters
type$a_source_id
type$a_target_id
type$a_position
Exceptions
InvalidArgumentException

Implements ilTreeImplementation.

Definition at line 685 of file class.ilNestedSetTree.php.

686 {
687 global $DIC;
688
689 $ilDB = $DIC['ilDB'];
690
691 $move_tree_callable = function (ilDBInterface $ilDB) use ($a_source_id, $a_target_id, $a_position) {
692 // Receive node infos for source and target
693 $query = 'SELECT * FROM ' . $this->getTree()->getTreeTable() . ' ' .
694 'WHERE ( child = %s OR child = %s ) ' .
695 'AND ' . $this->getTree()->getTreePk() . ' = %s ';
696 $res = $ilDB->queryF($query, array('integer', 'integer', 'integer'), array(
697 $a_source_id,
698 $a_target_id,
699 $this->getTree()->getTreeId()));
700
701 // Check in tree
702 if ($res->numRows() != 2) {
703 ilLoggerFactory::getLogger('tree')->logStack(ilLogLevel::ERROR, 'Objects not found in tree');
704 throw new InvalidArgumentException('Error moving subtree');
705 }
706 while ($row = $ilDB->fetchObject($res)) {
707 if ($row->child == $a_source_id) {
708 $source_lft = $row->lft;
709 $source_rgt = $row->rgt;
710 $source_depth = $row->depth;
711 $source_parent = $row->parent;
712 } else {
713 $target_lft = $row->lft;
714 $target_rgt = $row->rgt;
715 $target_depth = $row->depth;
716 }
717 }
718
719 // Check target not child of source
720 if ($target_lft >= $source_lft and $target_rgt <= $source_rgt) {
721 ilLoggerFactory::getLogger('tree')->logStack(ilLogLevel::ERROR, 'Target is child of source');
722 throw new InvalidArgumentException('Error moving subtree: target is child of source');
723 }
724
725 // Now spread the tree at the target location. After this update the table should be still in a consistent state.
726 // implementation for IL_LAST_NODE
727 $spread_diff = $source_rgt - $source_lft + 1;
728 #var_dump("<pre>","SPREAD_DIFF: ",$spread_diff,"<pre>");
729
730 $query = 'UPDATE ' . $this->getTree()->getTreeTable() . ' SET ' .
731 'lft = CASE WHEN lft > %s THEN lft + %s ELSE lft END, ' .
732 'rgt = CASE WHEN rgt >= %s THEN rgt + %s ELSE rgt END ' .
733 'WHERE ' . $this->getTree()->getTreePk() . ' = %s ';
734 $res = $ilDB->manipulateF($query, array('integer', 'integer', 'integer', 'integer', 'integer'), array(
735 $target_rgt,
736 $spread_diff,
737 $target_rgt,
738 $spread_diff,
739 $this->getTree()->getTreeId()));
740
741 // Maybe the source node has been updated, too.
742 // Check this:
743 if ($source_lft > $target_rgt) {
744 $where_offset = $spread_diff;
745 $move_diff = $target_rgt - $source_lft - $spread_diff;
746 } else {
747 $where_offset = 0;
748 $move_diff = $target_rgt - $source_lft;
749 }
750 $depth_diff = $target_depth - $source_depth + 1;
751
752
753 $query = 'UPDATE ' . $this->getTree()->getTreeTable() . ' SET ' .
754 'parent = CASE WHEN parent = %s THEN %s ELSE parent END, ' .
755 'rgt = rgt + %s, ' .
756 'lft = lft + %s, ' .
757 'depth = depth + %s ' .
758 'WHERE lft >= %s ' .
759 'AND rgt <= %s ' .
760 'AND ' . $this->getTree()->getTreePk() . ' = %s ';
761 $res = $ilDB->manipulateF($query, array('integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer'), array(
762 $source_parent,
763 $a_target_id,
764 $move_diff,
765 $move_diff,
766 $depth_diff,
767 $source_lft + $where_offset,
768 $source_rgt + $where_offset,
769 $this->getTree()->getTreeId()));
770
771 // done: close old gap
772 $query = 'UPDATE ' . $this->getTree()->getTreeTable() . ' SET ' .
773 'lft = CASE WHEN lft >= %s THEN lft - %s ELSE lft END, ' .
774 'rgt = CASE WHEN rgt >= %s THEN rgt - %s ELSE rgt END ' .
775 'WHERE ' . $this->getTree()->getTreePk() . ' = %s ';
776
777 $res = $ilDB->manipulateF($query, array('integer', 'integer', 'integer', 'integer', 'integer'), array(
778 $source_lft + $where_offset,
779 $spread_diff,
780 $source_rgt + $where_offset,
781 $spread_diff,
782 $this->getTree()->getTreeId()));
783 };
784
785
786 if ($this->getTree()->__isMainTree()) {
787 $ilAtomQuery = $ilDB->buildAtomQuery();
788 $ilAtomQuery->addTableLock('tree');
789 $ilAtomQuery->addQueryCallable($move_tree_callable);
790 $ilAtomQuery->run();
791 } else {
792 $move_tree_callable($ilDB);
793 }
794 }

References $DIC, $ilDB, $query, $res, $row, ilLogLevel\ERROR, ilLoggerFactory\getLogger(), and getTree().

+ Here is the call graph for this function:

◆ validateParentRelations()

ilNestedSetTree::validateParentRelations ( )

Validate the parent relations of the tree implementation For nested set, validate the lft, rgt against child <-> parent For materialized path validate path against child <-> parent.

Returns
int[] array of failure nodes

Implements ilTreeImplementation.

Definition at line 835 of file class.ilNestedSetTree.php.

836 {
837 global $DIC;
838
839 $ilDB = $DIC['ilDB'];
840
841 $query = 'select child from ' . $this->getTree()->getTreeTable() . ' child where not exists ' .
842 '( ' .
843 'select child from ' . $this->getTree()->getTreeTable() . ' parent where child.parent = parent.child and (parent.lft < child.lft) and (parent.rgt > child.rgt) ' .
844 ')' .
845 'and ' . $this->getTree()->getTreePk() . ' = ' . $this->getTree()->getTreeId() . ' and child <> 1';
846 $res = $ilDB->query($query);
847
848 ilLoggerFactory::getLogger('tree')->debug($query);
849
850 $failures = array();
851 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
852 $failures[] = $row[$this->getTree()->getTreePk()];
853 }
854 return $failures;
855 }

References $DIC, $ilDB, $query, $res, $row, ilDBConstants\FETCHMODE_ASSOC, ilLoggerFactory\getLogger(), and getTree().

+ Here is the call graph for this function:

Field Documentation

◆ $tree

ilNestedSetTree::$tree = null
private

Definition at line 18 of file class.ilNestedSetTree.php.

Referenced by getTree().


The documentation for this class was generated from the following file: