ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
class.ilMaterializedPathTree.php
Go to the documentation of this file.
1<?php
2/* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4include_once './Services/Tree/interfaces/interface.ilTreeImplementation.php';
5
17{
19 private $tree = null;
20
25 public function __construct(ilTree $a_tree)
26 {
27 $this->tree = $a_tree;
28 }
29
30
35 protected function getMaximumPossibleDepth()
36 {
38 }
39
44 public function getTree()
45 {
46 return $this->tree;
47 }
48
54 public function getSubTreeIds($a_node_id)
55 {
56 global $DIC;
57
58 $ilDB = $DIC['ilDB'];
59
60 $node = $this->getTree()->getNodeTreeData($a_node_id);
61 $query = 'SELECT child FROM ' . $this->getTree()->getTreeTable() . ' ' .
62 'WHERE path BETWEEN ' .
63 $ilDB->quote($node['path'], 'text') . ' AND ' .
64 $ilDB->quote($node['path'] . '.Z', 'text') . ' ' .
65 'AND child != %s ' .
66 'AND ' . $this->getTree()->getTreePk() . ' = %s';
67
68 $res = $ilDB->queryF(
69 $query,
70 array('integer', 'integer'),
71 array($a_node_id, $this->getTree()->getTreeId())
72 );
73 while ($row = $ilDB->fetchAssoc($res)) {
74 $childs[] = $row['child'];
75 }
76 return $childs ? $childs : array();
77 }
78
85 public function getRelation($a_node_a, $a_node_b)
86 {
87 if ($a_node_a['child'] == $a_node_b['child']) {
88 ilLoggerFactory::getLogger('tree')->debug('EQUALS');
90 }
91 if (stripos($a_node_a['path'], $a_node_b['path'] . '.') === 0) {
92 ilLoggerFactory::getLogger('tree')->debug('CHILD');
94 }
95 if (stripos($a_node_b['path'], $a_node_a['path'] . '.') === 0) {
96 ilLoggerFactory::getLogger('tree')->debug('PARENT');
98 }
99 $path_a = substr($a_node_a['path'], 0, strrpos($a_node_a['path'], '.'));
100 $path_b = substr($a_node_b['path'], 0, strrpos($a_node_b['path'], '.'));
101
102 ilLoggerFactory::getLogger('tree')->debug('Comparing ' . $path_a . ' ' . 'with ' . $path_b);
103
104 if ($a_node_a['path'] and (strcmp($path_a, $path_b) === 0)) {
105 ilLoggerFactory::getLogger('tree')->debug('SIBLING');
107 }
108
109 ilLoggerFactory::getLogger('tree')->debug('NONE');
111 }
112
122 public function getSubTreeQuery($a_node, $a_types = '', $a_force_join_reference = true, $a_fields = array())
123 {
124 global $DIC;
125
126 $ilDB = $DIC['ilDB'];
127
128 $type_str = '';
129 if (is_array($a_types)) {
130 if ($a_types) {
131 $type_str = "AND " . $ilDB->in($this->getTree()->getObjectDataTable() . ".type", $a_types, false, "text");
132 }
133 } elseif (strlen($a_types)) {
134 $type_str = "AND " . $this->getTree()->getObjectDataTable() . ".type = " . $ilDB->quote($a_types, "text");
135 }
136
137 $join = '';
138 if ($type_str or $a_force_join_reference) {
139 $join = $this->getTree()->buildJoin();
140 }
141
142 $fields = '* ';
143 if (count($a_fields)) {
144 $fields = implode(',', $a_fields);
145 }
146
147 // @todo order by
148 $query = 'SELECT ' .
149 $fields . ' ' .
150 'FROM ' . $this->getTree()->getTreeTable() . ' ' .
151 $join . ' ' .
152 'WHERE ' . $this->getTree()->getTreeTable() . '.path ' .
153 'BETWEEN ' .
154 $ilDB->quote($a_node['path'], 'text') . ' AND ' .
155 $ilDB->quote($a_node['path'] . '.Z', 'text') . ' ' .
156 'AND ' . $this->getTree()->getTreeTable() . '.' . $this->getTree()->getTreePk() . ' = ' . $ilDB->quote($this->getTree()->getTreeId(), 'integer') . ' ' .
157 $type_str . ' ' .
158 'ORDER BY ' . $this->getTree()->getTreeTable() . '.path';
159
160 return $query;
161 }
162
169 public function getPathIds($a_endnode, $a_startnode = 0)
170 {
171 global $DIC;
172
173 $ilDB = $DIC['ilDB'];
174
175 $ilDB->setLimit(1);
176 $query = 'SELECT path FROM ' . $this->getTree()->getTreeTable() . ' ' .
177 'WHERE child = ' . $ilDB->quote($a_endnode, 'integer') . ' ';
178 $res = $ilDB->query($query);
179
180 $path = null;
181 while ($row = $ilDB->fetchAssoc($res)) {
182 $path = $row['path'];
183 }
184
185 $pathIds = explode('.', $path);
186
187 if ($a_startnode != 0) {
188 while (count($pathIds) > 0 && $pathIds[0] != $a_startnode) {
189 array_shift($pathIds);
190 }
191 }
192 return $pathIds;
193 }
194
203 public function insertNode($a_node_id, $a_parent_id, $a_pos)
204 {
205 global $DIC;
206
207 $ilDB = $DIC['ilDB'];
208
209 $insert_node_callable = function (ilDBInterface $ilDB) use ($a_node_id, $a_parent_id, $a_pos) {
210 // get path and depth of parent
211 $ilDB->setLimit(1);
212
213 $res = $ilDB->queryF(
214 'SELECT parent, depth, path FROM ' . $this->getTree()->getTreeTable() . ' ' .
215 'WHERE child = %s ' . ' ' .
216 'AND ' . $this->getTree()->getTreePk() . ' = %s',
217 array('integer', 'integer'),
218 array($a_parent_id, $this->getTree()->getTreeId())
219 );
220
221
222 $r = $ilDB->fetchObject($res);
223
224 if ($r->parent === null) {
226 throw new ilInvalidTreeStructureException('Parent node not found in tree');
227 }
228
229 if ($r->depth >= $this->getMaximumPossibleDepth()) {
231 throw new ilInvalidTreeStructureException('Maximum tree depth exceeded');
232 }
233
234 $parentPath = $r->path;
235 $depth = $r->depth + 1;
236 $lft = 0;
237 $rgt = 0;
238
239
240 $ilDB->insert($this->getTree()->getTreeTable(), array($this->getTree()->getTreePk() => array('integer', $this->getTree()->getTreeId()),
241 'child' => array('integer', $a_node_id),
242 'parent' => array('integer', $a_parent_id),
243 'lft' => array('integer', $lft),
244 'rgt' => array('integer', $rgt),
245 'depth' => array('integer', $depth),
246 'path' => array('text', $parentPath . "." . $a_node_id)));
247 };
248
249 // use ilAtomQuery to lock tables if tree is main tree
250 // otherwise just call this closure without locking
251 if ($this-> getTree()->__isMainTree()) {
252 $ilAtomQuery = $ilDB->buildAtomQuery();
253 $ilAtomQuery->addTableLock("tree");
254
255 $ilAtomQuery->addQueryCallable($insert_node_callable);
256
257 $ilAtomQuery->run();
258 } else {
259 $insert_node_callable($ilDB);
260 }
261 }
262
263
270 public function deleteTree($a_node_id)
271 {
272 global $DIC;
273
274 $ilDB = $DIC['ilDB'];
275 $delete_tree_callable = function (ilDBInterface $ilDB) use ($a_node_id) {
276 $query = 'SELECT * FROM ' . $this->getTree()->getTreeTable() . ' ' .
277 'WHERE ' . $this->getTree()->getTreeTable() . '.child = %s ' .
278 'AND ' . $this->getTree()->getTreeTable() . '.' . $this->getTree()->getTreePk() . ' = %s ';
279 $res = $ilDB->queryF($query, array('integer','integer'), array(
280 $a_node_id,
281 $this->getTree()->getTreeId()));
282 $row = $ilDB->fetchAssoc($res);
283
284 $query = 'DELETE FROM ' . $this->getTree()->getTreeTable() . ' ' .
285 'WHERE path BETWEEN ' . $ilDB->quote($row['path'], 'text') . ' ' .
286 'AND ' . $ilDB->quote($row['path'] . '.Z', 'text') . ' ' .
287 'AND ' . $this->getTree()->getTreePk() . ' = ' . $ilDB->quote($this->getTree()->getTreeId(), 'integer');
288 $ilDB->manipulate($query);
289 };
290
291 // get lft and rgt values. Don't trust parameter lft/rgt values of $a_node
292 if ($this->getTree()->__isMainTree()) {
293 $ilAtomQuery = $ilDB->buildAtomQuery();
294 $ilAtomQuery->addTableLock('tree');
295 $ilAtomQuery->addQueryCallable($delete_tree_callable);
296 $ilAtomQuery->run();
297 } else {
298 $delete_tree_callable($ilDB);
299 }
300
301 return true;
302 }
303
310 public function moveToTrash($a_node_id)
311 {
312 global $DIC;
313
314 $ilDB = $DIC['ilDB'];
315
316 $move_to_trash_callable = function (ilDBInterface $ilDB) use ($a_node_id) {
317 $node = $this->getTree()->getNodeTreeData($a_node_id);
318
319 // Set the nodes deleted (negative tree id)
320 $ilDB->manipulateF(
321 '
322 UPDATE ' . $this->getTree()->getTreeTable() . ' ' .
323 'SET tree = %s' . ' ' .
324 'WHERE ' . $this->getTree()->getTreePk() . ' = %s ' .
325 'AND path BETWEEN %s AND %s',
326 array('integer', 'integer', 'text', 'text'),
327 array(-$a_node_id, $this->getTree()->getTreeId(), $node['path'], $node['path'] . '.Z')
328 );
329 };
330
331 // use ilAtomQuery to lock tables if tree is main tree
332 // otherwise just call this closure without locking
333 if ($this->getTree()->__isMainTree()) {
334 $ilAtomQuery = $ilDB->buildAtomQuery();
335 $ilAtomQuery->addTableLock("tree");
336
337 $ilAtomQuery->addQueryCallable($move_to_trash_callable);
338
339 $ilAtomQuery->run();
340 } else {
341 $move_to_trash_callable($ilDB);
342 }
343
344 return true;
345 }
346
356 public function moveTree($a_source_id, $a_target_id, $a_position)
357 {
358 global $DIC;
359
360 $ilDB = $DIC['ilDB'];
361
362 $move_tree_callable = function (ilDBInterface $ilDB) use ($a_source_id, $a_target_id, $a_position) {
363 // Receive node infos for source and target
364 $ilDB->setLimit(2);
365
366 $res = $ilDB->query(
367 'SELECT depth, child, parent, path FROM ' . $this->getTree()->getTreeTable() . ' ' .
368 'WHERE ' . $ilDB->in('child', array($a_source_id, $a_target_id), false, 'integer') . ' ' .
369 'AND tree = ' . $ilDB->quote($this->getTree()->getTreeId(), 'integer')
370 );
371
372 // Check in tree
373 if ($ilDB->numRows($res) != 2) {
374 ilLoggerFactory::getLogger('tree')->logStack(ilLogLevel::ERROR, 'Objects not found in tree');
375 throw new InvalidArgumentException('Error moving subtree');
376 }
377
378 while ($row = $ilDB->fetchObject($res)) {
379 if ($row->child == $a_source_id) {
380 $source_path = $row->path;
381 $source_depth = $row->depth;
382 $source_parent = $row->parent;
383 } else {
384 $target_path = $row->path;
385 $target_depth = $row->depth;
386 }
387 }
388
389 if ($target_depth >= $source_depth) {
390 // We move nodes deeper into the tree. Therefore we need to
391 // check whether we might exceed the maximal path length.
392 // We use FOR UPDATE here, because we don't want anyone to
393 // insert new nodes while we move the subtree.
394
395 $res = $ilDB->queryF(
396 'SELECT MAX(depth) max_depth ' .
397 'FROM ' . $this->getTree()->getTreeTable() . ' ' .
398 'WHERE path BETWEEN %s AND %s' . ' ' .
399 'AND tree = %s ',
400 array('text', 'text', 'integer'),
401 array($source_path, $source_path . '.Z', $this->getTree()->getTreeId())
402 );
403
404 $row = $ilDB->fetchObject($res);
405
406 if ($row->max_depth - $source_depth + $target_depth + 1 > $this->getMaximumPossibleDepth()) {
407 ilLoggerFactory::getLogger('tree')->logStack(ilLogLevel::ERROR, 'Objects not found in tree');
408 throw new ilInvalidTreeStructureException('Maximum tree depth exceeded');
409 }
410 }
411 // Check target not child of source
412 if (substr($target_path . '.', 0, strlen($source_path) . '.') == $source_path . '.') {
413 ilLoggerFactory::getLogger('tree')->logStack(ilLogLevel::ERROR, 'Target is child of source');
414 throw new InvalidArgumentException('Error moving subtree: target is child of source');
415 }
416 $depth_diff = $target_depth - $source_depth + 1;
417
418 // move subtree:
419 $query = '
420 UPDATE ' . $this->getTree()->getTreeTable() . '
421 SET parent = CASE WHEN parent = ' . $ilDB->quote($source_parent, 'integer') . '
422 THEN ' . $ilDB->quote($a_target_id, 'integer') . '
423 ELSE parent END,
424
425 path = ' . $ilDB->concat(array(
426 array($ilDB->quote($target_path, 'text'), 'text'),
427 array($ilDB->substr('path', strrpos('.' . $source_path, '.')), 'text'))) . ' ,
428
429 depth = depth + ' . $ilDB->quote($depth_diff, 'integer') . '
430
431 WHERE path BETWEEN ' . $ilDB->quote($source_path, 'text') . '
432 AND ' . $ilDB->quote($source_path . '.Z', 'text') . '
433
434 AND tree = ' . $ilDB->quote($this->getTree()->getTreeId(), 'integer');
435
436 ilLoggerFactory::getLogger('tree')->debug('Query is ' . $query);
437
438 $ilDB->manipulate($query);
439 };
440
441
442 if ($this->getTree()->__isMainTree()) {
443 $ilAtomQuery = $ilDB->buildAtomQuery();
444 $ilAtomQuery->addTableLock("tree");
445 $ilAtomQuery->addQueryCallable($move_tree_callable);
446 $ilAtomQuery->run();
447 } else {
448 $move_tree_callable($ilDB);
449 }
450
451 return true;
452 }
453
454
455 public static function createFromParentReleation()
456 {
457 global $DIC;
458
459 $ilDB = $DIC['ilDB'];
460
461 $r = $ilDB->queryF('SELECT DISTINCT * FROM tree WHERE parent = %s', array('integer'), array(0));
462
463 while ($row = $ilDB->fetchAssoc($r)) {
465
466 if ($success !== true) {
467 }
468 }
469 }
470
476 private static function createMaterializedPath($parent, $parentPath)
477 {
478 global $DIC;
479
480 $ilDB = $DIC['ilDB'];
481 $q = ' UPDATE tree
482 SET path = CONCAT(COALESCE(' . $ilDB->quote($parentPath, 'text') . ', \'\'), COALESCE( ' . $ilDB->cast("child", "text") . ' , \'\'))
483 WHERE parent = %s';
484 $r = $ilDB->manipulateF($q, array('integer'), array($parent));
485
486 $r = $ilDB->queryF('SELECT child FROM tree WHERE parent = %s', array('integer'), array($parent));
487
488 while ($row = $ilDB->fetchAssoc($r)) {
489 self::createMaterializedPath($row['child'], $parentPath . $row['child'] . '.');
490 }
491
492 return true;
493 }
494
499 public function getSubtreeInfo($a_endnode_id)
500 {
501 global $DIC;
502
503 $ilDB = $DIC['ilDB'];
504
505 // This is an optimization without the temporary tables become too big for our system.
506 // The idea is to use a subquery to join and filter the trees, and only the result
507 // is joined to obj_reference and obj_data.
508
509 $query = "SELECT t2.child child, type, t2.path path " .
510 "FROM " . $this->getTree()->getTreeTable() . " t1 " .
511 "JOIN " . $this->getTree()->getTreeTable() . " t2 ON (t2.path BETWEEN t1.path AND CONCAT(t1.path, '.Z')) " .
512 "JOIN " . $this->getTree()->getTableReference() . " obr ON t2.child = obr.ref_id " .
513 "JOIN " . $this->getTree()->getObjectDataTable() . " obd ON obr.obj_id = obd.obj_id " .
514 "WHERE t1.child = " . $ilDB->quote($a_endnode_id, 'integer') . " " .
515 "AND t1." . $this->getTree()->getTreePk() . " = " . $ilDB->quote($this->getTree()->getTreeId(), 'integer') . " " .
516 "AND t2." . $this->getTree()->getTreePk() . " = " . $ilDB->quote($this->getTree()->getTreeId(), 'integer') . " " .
517 "ORDER BY t2.path";
518
519
520 $res = $ilDB->query($query);
521 $nodes = array();
522 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
523 #$nodes[$row->child]['lft'] = $row->lft;
524 #$nodes[$row->child]['rgt'] = $row->rgt;
525 $nodes[$row->child]['child'] = $row->child;
526 $nodes[$row->child]['type'] = $row->type;
527 $nodes[$row->child]['path'] = $row->path;
528 }
529
530 $depth_first_compare = function ($a, $b) {
531 $a_exploded = explode('.', $a['path']);
532 #ilLoggerFactory::getLogger('tree')->debug(print_r($a_exploded,TRUE));
533 $b_exploded = explode('.', $b['path']);
534
535 $a_padded = '';
536 foreach ($a_exploded as $num) {
537 $a_padded .= (str_pad((string) $num, 14, '0', STR_PAD_LEFT));
538 }
539 $b_padded = '';
540 foreach ($b_exploded as $num) {
541 $b_padded .= (str_pad((string) $num, 14, '0', STR_PAD_LEFT));
542 }
543
544 #ilLoggerFactory::getLogger('tree')->debug($a_padded);
545 return strcasecmp($a_padded, $b_padded);
546 };
547
548 #ilLoggerFactory::getLogger('tree')->debug(print_r($nodes,TRUE));
549
550 uasort($nodes, $depth_first_compare);
551
552 #ilLoggerFactory::getLogger('tree')->debug(print_r($nodes,TRUE));
553
554 return (array) $nodes;
555 }
556
561 public function validateParentRelations()
562 {
563 global $DIC;
564
565 $ilDB = $DIC['ilDB'];
566
567 $query = 'select child from ' . $this->getTree()->getTreeTable() . ' child where not exists ' .
568 '( ' .
569 'select child from ' . $this->getTree()->getTreeTable() . ' parent where child.parent = parent.child and ' .
570 '(child.path BETWEEN parent.path AND CONCAT(parent.path,' . $ilDB->quote('Z', 'text') . ') )' . ')' .
571 'and ' . $this->getTree()->getTreePk() . ' = ' . $this->getTree()->getTreeId() . ' and child <> 1';
572 $res = $ilDB->query($query);
573
574 ilLoggerFactory::getLogger('tree')->debug($query);
575
576 $failures = array();
577 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
578 $failures[] = $row[$this->getTree()->getTreePk()];
579 }
580 return $failures;
581 }
582}
$success
Definition: Utf8Test.php:86
$path
Definition: aliased.php:25
An exception for terminatinating execution or to throw for unit testing.
Thrown if invalid tree strucutes are found.
static getLogger($a_component_id)
Get component logger.
Base class for materialize path based trees Based on implementation of Werner Randelshofer.
moveTree($a_source_id, $a_target_id, $a_position)
move source subtree to target node
getPathIds($a_endnode, $a_startnode=0)
Get path ids.
validateParentRelations()
Validaate parent relations.
getSubTreeIds($a_node_id)
Get subtree ids.
deleteTree($a_node_id)
Delete a subtree.
getSubTreeQuery($a_node, $a_types='', $a_force_join_reference=true, $a_fields=array())
Get subtree query.
__construct(ilTree $a_tree)
Constructor.
getMaximumPossibleDepth()
Get maximum possible depth.
moveToTrash($a_node_id)
Move subtree to trash.
getRelation($a_node_a, $a_node_b)
Get relation of two nodes.
insertNode($a_node_id, $a_parent_id, $a_pos)
Insert new node under parent node.
static createMaterializedPath($parent, $parentPath)
Tree class data representation in hierachical trees using the Nested Set Model with Gaps by Joe Celco...
const RELATION_EQUALS
const RELATION_PARENT
const RELATION_NONE
const RELATION_SIBLING
const RELATION_CHILD
$r
Definition: example_031.php:79
Interface ilDBInterface.
Interface for tree implementations Currrently nested set or materialize path.
$row
$query
global $DIC
Definition: saml.php:7
foreach($_POST as $key=> $value) $res
global $ilDB