ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
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 $ilDB;
57
58 $node = $this->getTree()->getNodeTreeData($a_node_id);
59 $query = 'SELECT child FROM '.$this->getTree()->getTreeTable().' '.
60 'WHERE path BETWEEN '.
61 $ilDB->quote($node['path'], 'text').' AND '.
62 $ilDB->quote($node['path'].'.Z', 'text').' '.
63 'AND child != %s '.
64 'AND '.$this->getTree()->getTreePk().' = %s';
65
66 $res = $ilDB->queryF(
67 $query,
68 array('integer', 'integer'),
69 array($a_node_id, $this->getTree()->getTreeId())
70 );
71 while($row = $ilDB->fetchAssoc($res))
72 {
73 $childs[] = $row['child'];
74 }
75 return $childs ? $childs : array();
76 }
77
84 public function getRelation($a_node_a, $a_node_b)
85 {
86 if($a_node_a['child'] == $a_node_b['child'])
87 {
88 ilLoggerFactory::getLogger('tree')->debug('EQUALS');
90 }
91 if(stripos($a_node_a['path'], $a_node_b['path']. '.') === 0)
92 {
93 ilLoggerFactory::getLogger('tree')->debug('CHILD');
95 }
96 if(stripos($a_node_b['path'], $a_node_a['path'] . '.') === 0)
97 {
98 ilLoggerFactory::getLogger('tree')->debug('PARENT');
100 }
101 $path_a = substr($a_node_a['path'],0,strrpos($a_node_a['path'],'.'));
102 $path_b = substr($a_node_b['path'],0,strrpos($a_node_b['path'],'.'));
103
104 ilLoggerFactory::getLogger('tree')->debug('Comparing '.$path_a .' '. 'with '.$path_b);
105
106 if($a_node_a['path'] and (strcmp($path_a,$path_b) === 0))
107 {
108 ilLoggerFactory::getLogger('tree')->debug('SIBLING');
110 }
111
112 ilLoggerFactory::getLogger('tree')->debug('NONE');
114 }
115
125 public function getSubTreeQuery($a_node, $a_types = '', $a_force_join_reference = true, $a_fields = array())
126 {
127 global $ilDB;
128
129 $type_str = '';
130 if(is_array($a_types))
131 {
132 if($a_types)
133 {
134 $type_str = "AND ".$ilDB->in($this->getTree()->getObjectDataTable().".type", $a_types, false, "text");
135 }
136 }
137 else if(strlen($a_types))
138 {
139 $type_str = "AND ".$this->getTree()->getObjectDataTable().".type = ".$ilDB->quote($a_types, "text");
140 }
141
142 $join = '';
143 if($type_str or $a_force_join_reference)
144 {
145 $join = $this->getTree()->buildJoin();
146 }
147
148 $fields = '* ';
149 if(count($a_fields))
150 {
151 $fields = implode(',',$a_fields);
152 }
153
154 // @todo order by
155 $query = 'SELECT '.
156 $fields.' '.
157 'FROM '.$this->getTree()->getTreeTable().' '.
158 $join.' '.
159 'WHERE '.$this->getTree()->getTreeTable().'.path '.
160 'BETWEEN '.
161 $ilDB->quote($a_node['path'],'text').' AND '.
162 $ilDB->quote($a_node['path'].'.Z','text').' '.
163 'AND '.$this->getTree()->getTreeTable().'.'.$this->getTree()->getTreePk().' = '.$ilDB->quote($this->getTree()->getTreeId(),'integer').' '.
164 $type_str.' '.
165 'ORDER BY '.$this->getTree()->getTreeTable().'.path';
166
167 return $query;
168 }
169
176 public function getPathIds($a_endnode, $a_startnode = 0)
177 {
178 global $ilDB;
179
180 $ilDB->setLimit(1);
181 $query = 'SELECT path FROM ' . $this->getTree()->getTreeTable() .' '.
182 'WHERE child = '. $ilDB->quote($a_endnode,'integer').' ';
183 $res = $ilDB->query($query);
184
185 $path = null;
186 while ($row = $ilDB->fetchAssoc($res))
187 {
188 $path = $row['path'];
189 }
190
191 $pathIds = explode('.', $path);
192
193 if ($a_startnode != 0)
194 {
195 while (count($pathIds) > 0 && $pathIds[0] != $a_startnode)
196 {
197 array_shift($pathIds);
198 }
199 }
200 return $pathIds;
201 }
202
211 public function insertNode($a_node_id, $a_parent_id, $a_pos)
212 {
213 global $ilDB;
214
215 $insert_node_callable = function(ilDBInterface $ilDB) use ($a_node_id, $a_parent_id, $a_pos)
216 {
217 // get path and depth of parent
218 $ilDB->setLimit(1);
219
220 $res = $ilDB->queryF(
221 'SELECT parent, depth, path FROM ' . $this->getTree()->getTreeTable() . ' ' .
222 'WHERE child = %s '. ' '.
223 'AND ' . $this->getTree()->getTreePk() . ' = %s', array('integer', 'integer'),
224 array($a_parent_id, $this->getTree()->getTreeId()));
225
226
227 $r = $ilDB->fetchObject($res);
228
229 if ($r->parent === NULL)
230 {
232 throw new ilInvalidTreeStructureException('Parent node not found in tree');
233
234 }
235
236 if ($r->depth >= $this->getMaximumPossibleDepth())
237 {
239 throw new ilInvalidTreeStructureException('Maximum tree depth exceeded');
240 }
241
242 $parentPath = $r->path;
243 $depth = $r->depth + 1;
244 $lft = 0;
245 $rgt = 0;
246
247
248 $ilDB->insert($this->getTree()->getTreeTable(), array($this->getTree()->getTreePk() => array('integer', $this->getTree()->getTreeId()),
249 'child' => array('integer', $a_node_id),
250 'parent' => array('integer', $a_parent_id),
251 'lft' => array('integer', $lft),
252 'rgt' => array('integer', $rgt),
253 'depth' => array('integer', $depth),
254 'path' => array('text', $parentPath . "." . $a_node_id)));
255
256 };
257
258 // use ilAtomQuery to lock tables if tree is main tree
259 // otherwise just call this closure without locking
260 if ($this-> getTree()->__isMainTree())
261 {
262 $ilAtomQuery = $ilDB->buildAtomQuery();
263 $ilAtomQuery->addTableLock("tree");
264
265 $ilAtomQuery->addQueryCallable($insert_node_callable);
266
267 $ilAtomQuery->run();
268 }
269 else
270 {
271 $insert_node_callable($ilDB);
272 }
273 }
274
275
282 public function deleteTree($a_node_id)
283 {
284 global $ilDB;
285 $delete_tree_callable = function(ilDBInterface $ilDB) use($a_node_id)
286 {
287 $query = 'SELECT * FROM '.$this->getTree()->getTreeTable().' '.
288 'WHERE '.$this->getTree()->getTreeTable().'.child = %s '.
289 'AND '.$this->getTree()->getTreeTable().'.'.$this->getTree()->getTreePk().' = %s ';
290 $res = $ilDB->queryF($query,array('integer','integer'),array(
291 $a_node_id,
292 $this->getTree()->getTreeId()));
293 $row = $ilDB->fetchAssoc($res);
294
295 $query = 'DELETE FROM '.$this->getTree()->getTreeTable().' '.
296 'WHERE path BETWEEN '.$ilDB->quote($row['path'],'text').' '.
297 'AND '.$ilDB->quote($row['path'].'.Z','text').' '.
298 'AND '.$this->getTree()->getTreePk().' = '.$ilDB->quote($this->getTree()->getTreeId(), 'integer');
299 $ilDB->manipulate($query);
300 };
301
302 // get lft and rgt values. Don't trust parameter lft/rgt values of $a_node
303 if($this->getTree()->__isMainTree())
304 {
305 $ilAtomQuery = $ilDB->buildAtomQuery();
306 $ilAtomQuery->addTableLock('tree');
307 $ilAtomQuery->addQueryCallable($delete_tree_callable);
308 $ilAtomQuery->run();
309 }
310 else
311 {
312 $delete_tree_callable($ilDB);
313 }
314
315 return true;
316 }
317
324 public function moveToTrash($a_node_id)
325 {
326 global $ilDB;
327
328 $move_to_trash_callable = function(ilDBInterface $ilDB) use($a_node_id)
329 {
330 $node = $this->getTree()->getNodeTreeData($a_node_id);
331
332 // Set the nodes deleted (negative tree id)
333 $ilDB->manipulateF('
334 UPDATE ' . $this->getTree()->getTreeTable().' '.
335 'SET tree = %s' .' '.
336 'WHERE ' . $this->getTree()->getTreePk() . ' = %s ' .
337 'AND path BETWEEN %s AND %s',
338 array('integer', 'integer', 'text', 'text'),
339 array(-$a_node_id, $this->getTree()->getTreeId(), $node['path'], $node['path'] . '.Z'));
340
341 };
342
343 // use ilAtomQuery to lock tables if tree is main tree
344 // otherwise just call this closure without locking
345 if ($this->getTree()->__isMainTree())
346 {
347 $ilAtomQuery = $ilDB->buildAtomQuery();
348 $ilAtomQuery->addTableLock("tree");
349
350 $ilAtomQuery->addQueryCallable($move_to_trash_callable);
351
352 $ilAtomQuery->run();
353 }
354 else
355 {
356 $move_to_trash_callable($ilDB);
357 }
358
359 return true;
360 }
361
371 public function moveTree($a_source_id, $a_target_id, $a_position)
372 {
373 global $ilDB;
374
375 $move_tree_callable = function(ilDBInterface $ilDB) use ($a_source_id, $a_target_id, $a_position)
376 {
377 // Receive node infos for source and target
378 $ilDB->setLimit(2);
379
380 $res = $ilDB->query(
381 'SELECT depth, child, parent, path FROM ' . $this->getTree()->getTreeTable() . ' '.
382 'WHERE ' . $ilDB->in('child', array($a_source_id, $a_target_id), false, 'integer') . ' '.
383 'AND tree = ' . $ilDB->quote($this->getTree()->getTreeId(), 'integer')
384 );
385
386 // Check in tree
387 if ($ilDB->numRows($res) != 2)
388 {
389 ilLoggerFactory::getLogger('tree')->logStack(ilLogLevel::ERROR, 'Objects not found in tree');
390 throw new InvalidArgumentException('Error moving subtree');
391 }
392
393 while ($row = $ilDB->fetchObject($res))
394 {
395 if ($row->child == $a_source_id)
396 {
397 $source_path = $row->path;
398 $source_depth = $row->depth;
399 $source_parent = $row->parent;
400 }
401 else
402 {
403 $target_path = $row->path;
404 $target_depth = $row->depth;
405 }
406 }
407
408 if ($target_depth >= $source_depth)
409 {
410 // We move nodes deeper into the tree. Therefore we need to
411 // check whether we might exceed the maximal path length.
412 // We use FOR UPDATE here, because we don't want anyone to
413 // insert new nodes while we move the subtree.
414
415 $res = $ilDB->queryF(
416 'SELECT MAX(depth) max_depth '.
417 'FROM ' . $this->getTree()->getTreeTable() . ' '.
418 'WHERE path BETWEEN %s AND %s'.' '.
419 'AND tree = %s ',
420 array('text', 'text', 'integer'), array($source_path, $source_path . '.Z', $this->getTree()->getTreeId()));
421
422 $row = $ilDB->fetchObject($res);
423
424 if ($row->max_depth - $source_depth + $target_depth + 1 > $this->getMaximumPossibleDepth())
425 {
426 ilLoggerFactory::getLogger('tree')->logStack(ilLogLevel::ERROR, 'Objects not found in tree');
427 throw new ilInvalidTreeStructureException('Maximum tree depth exceeded');
428 }
429 }
430 // Check target not child of source
431 if (substr($target_path . '.', 0, strlen($source_path) . '.') == $source_path . '.')
432 {
433 ilLoggerFactory::getLogger('tree')->logStack(ilLogLevel::ERROR, 'Target is child of source');
434 throw new InvalidArgumentException('Error moving subtree: target is child of source');
435 }
436 $depth_diff = $target_depth - $source_depth + 1;
437
438 // move subtree:
439 $query = '
440 UPDATE ' . $this->getTree()->getTreeTable() . '
441 SET parent = CASE WHEN parent = ' . $ilDB->quote($source_parent, 'integer') . '
442 THEN ' . $ilDB->quote($a_target_id, 'integer') . '
443 ELSE parent END,
444
445 path = ' . $ilDB->concat(array(
446 array($ilDB->quote($target_path, 'text'), 'text'),
447 array($ilDB->substr('path', strrpos('.' . $source_path, '.')), 'text'))) . ' ,
448
449 depth = depth + ' . $ilDB->quote($depth_diff, 'integer') . '
450
451 WHERE path BETWEEN ' . $ilDB->quote($source_path, 'text') . '
452 AND ' . $ilDB->quote($source_path . '.Z', 'text') . '
453
454 AND tree = ' . $ilDB->quote($this->getTree()->getTreeId(), 'integer');
455
456 ilLoggerFactory::getLogger('tree')->debug('Query is ' . $query);
457
458 $ilDB->manipulate($query);
459 };
460
461
462 if ($this->getTree()->__isMainTree())
463 {
464 $ilAtomQuery = $ilDB->buildAtomQuery();
465 $ilAtomQuery->addTableLock("tree");
466 $ilAtomQuery->addQueryCallable($move_tree_callable);
467 $ilAtomQuery->run();
468 }
469 else{
470 $move_tree_callable($ilDB);
471 }
472
473 return true;
474 }
475
476
477 public static function createFromParentReleation()
478 {
479 global $ilDB;
480
481 $r = $ilDB->queryF('SELECT DISTINCT * FROM tree WHERE parent = %s', array('integer'), array(0));
482
483 while ($row = $ilDB->fetchAssoc($r))
484 {
486
487 if ($success !== true)
488 {
489 }
490 }
491 }
492
498 private static function createMaterializedPath($parent, $parentPath)
499 {
500 global $ilDB;
501 $q = ' UPDATE tree
502 SET path = CONCAT(COALESCE(' . $ilDB->quote($parentPath, 'text') . ', \'\'), COALESCE( ' . $ilDB->cast("child","text") . ' , \'\'))
503 WHERE parent = %s';
504 $r = $ilDB->manipulateF($q, array('integer'), array($parent));
505
506 $r = $ilDB->queryF('SELECT child FROM tree WHERE parent = %s', array('integer'), array($parent));
507
508 while ($row = $ilDB->fetchAssoc($r))
509 {
510 self::createMaterializedPath($row['child'], $parentPath . $row['child'] . '.');
511 }
512
513 return true;
514 }
515
520 public function getSubtreeInfo($a_endnode_id)
521 {
522 global $ilDB;
523
524 // This is an optimization without the temporary tables become too big for our system.
525 // The idea is to use a subquery to join and filter the trees, and only the result
526 // is joined to obj_reference and obj_data.
527
528 $query = "SELECT t2.child child, type, t2.path path " .
529 "FROM " . $this->getTree()->getTreeTable() . " t1 " .
530 "JOIN " . $this->getTree()->getTreeTable() . " t2 ON (t2.path BETWEEN t1.path AND CONCAT(t1.path, '.Z')) " .
531 "JOIN " . $this->getTree()->getTableReference() . " obr ON t2.child = obr.ref_id " .
532 "JOIN " . $this->getTree()->getObjectDataTable() . " obd ON obr.obj_id = obd.obj_id " .
533 "WHERE t1.child = " . $ilDB->quote($a_endnode_id, 'integer') . " " .
534 "AND t1." . $this->getTree()->getTreePk() . " = " . $ilDB->quote($this->getTree()->getTreeId(), 'integer') . " " .
535 "AND t2." . $this->getTree()->getTreePk() . " = " . $ilDB->quote($this->getTree()->getTreeId(), 'integer') . " " .
536 "ORDER BY t2.path";
537
538
539 $res = $ilDB->query($query);
540 $nodes = array();
541 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT))
542 {
543 #$nodes[$row->child]['lft'] = $row->lft;
544 #$nodes[$row->child]['rgt'] = $row->rgt;
545 $nodes[$row->child]['child'] = $row->child;
546 $nodes[$row->child]['type'] = $row->type;
547 $nodes[$row->child]['path'] = $row->path;
548 }
549
550 $depth_first_compare = function($a, $b)
551 {
552 $a_exploded = explode('.', $a['path']);
553 #ilLoggerFactory::getLogger('tree')->debug(print_r($a_exploded,TRUE));
554 $b_exploded = explode('.', $b['path']);
555
556 $a_padded = '';
557 foreach($a_exploded as $num)
558 {
559 $a_padded .= (str_pad((string) $num, 14,'0', STR_PAD_LEFT));
560 }
561 $b_padded = '';
562 foreach($b_exploded as $num)
563 {
564 $b_padded .= (str_pad((string) $num, 14, '0', STR_PAD_LEFT));
565 }
566
567 #ilLoggerFactory::getLogger('tree')->debug($a_padded);
568 return strcasecmp($a_padded, $b_padded);
569 };
570
571 #ilLoggerFactory::getLogger('tree')->debug(print_r($nodes,TRUE));
572
573 uasort($nodes,$depth_first_compare);
574
575 #ilLoggerFactory::getLogger('tree')->debug(print_r($nodes,TRUE));
576
577 return (array) $nodes;
578 }
579
584 public function validateParentRelations()
585 {
586 global $ilDB;
587
588 $query = 'select child from '.$this->getTree()->getTreeTable().' child where not exists '.
589 '( '.
590 'select child from '.$this->getTree()->getTreeTable().' parent where child.parent = parent.child and '.
591 '(child.path BETWEEN parent.path AND CONCAT(parent.path,'.$ilDB->quote('Z','text').') )'. ')'.
592 'and '.$this->getTree()->getTreePk().' = '.$this->getTree()->getTreeId().' and child <> 1';
593 $res = $ilDB->query($query);
594
595 ilLoggerFactory::getLogger('tree')->debug($query);
596
597 $failures = array();
598 while($row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC))
599 {
600 $failures[] = $row[$this->getTree()->getTreePk()];
601 }
602 return $failures;
603 }
604}
605
606?>
$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.
global $ilDB