ILIAS  Release_4_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.ilTree.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
4 define("IL_LAST_NODE", -2);
5 define("IL_FIRST_NODE", -1);
6 
21 class ilTree
22 {
28  var $ilias;
29 
30 
36  var $log;
37 
43  var $root_id;
44 
50  var $tree_id;
51 
58 
65 
72 
78  var $ref_pk;
79 
85  var $obj_pk;
86 
92  var $tree_pk;
93 
118  var $gap;
119 
126  function ilTree($a_tree_id, $a_root_id = 0)
127  {
128  global $ilDB,$ilErr,$ilias,$ilLog;
129 
130  // set db & error handler
131  $this->ilDB = $ilDB;
132 
133  if (!isset($ilErr))
134  {
135  $ilErr = new ilErrorHandling();
136  $ilErr->setErrorHandling(PEAR_ERROR_CALLBACK,array($ilErr,'errorHandler'));
137  }
138  else
139  {
140  $this->ilErr = $ilErr;
141  }
142 
143  $this->lang_code = "en";
144 
145  if (!isset($a_tree_id) or (func_num_args() == 0) )
146  {
147  $this->ilErr->raiseError(get_class($this)."::Constructor(): No tree_id given!",$this->ilErr->WARNING);
148  }
149 
150  if (func_num_args() > 2)
151  {
152  $this->ilErr->raiseError(get_class($this)."::Constructor(): Wrong parameter count!",$this->ilErr->WARNING);
153  }
154 
155  // CREATE LOGGER INSTANCE
156  $this->log = $ilLog;
157 
158  //init variables
159  if (empty($a_root_id))
160  {
161  $a_root_id = ROOT_FOLDER_ID;
162  }
163 
164  $this->tree_id = $a_tree_id;
165  $this->root_id = $a_root_id;
166  $this->table_tree = 'tree';
167  $this->table_obj_data = 'object_data';
168  $this->table_obj_reference = 'object_reference';
169  $this->ref_pk = 'ref_id';
170  $this->obj_pk = 'obj_id';
171  $this->tree_pk = 'tree';
172 
173  $this->use_cache = true;
174 
175  // If cache is activated, cache object translations to improve performance
176  $this->translation_cache = array();
177  $this->parent_type_cache = array();
178 
179  // By default, we create gaps in the tree sequence numbering for 50 nodes
180  $this->gap = 50;
181  }
182 
186  public function useCache($a_use = true)
187  {
188  $this->use_cache = $a_use;
189  }
190 
195  public function isCacheUsed()
196  {
197  return $this->__isMainTree() and $this->use_cache;
198  }
199 
204  function initLangCode()
205  {
206  global $ilUser;
207 
208  // lang_code is only required in $this->fetchnodedata
209  if (!is_object($ilUser))
210  {
211  $this->lang_code = "en";
212  }
213  else
214  {
215  $this->lang_code = $ilUser->getCurrentLanguage();
216  }
217  }
218 
219 
234  function setTableNames($a_table_tree,$a_table_obj_data,$a_table_obj_reference = "")
235  {
236  if (!isset($a_table_tree) or !isset($a_table_obj_data))
237  {
238  $this->ilErr->raiseError(get_class($this)."::setTableNames(): Missing parameter! ".
239  "tree table: ".$a_table_tree." object data table: ".$a_table_obj_data,$this->ilErr->WARNING);
240  }
241 
242  $this->table_tree = $a_table_tree;
243  $this->table_obj_data = $a_table_obj_data;
244  $this->table_obj_reference = $a_table_obj_reference;
245 
246  return true;
247  }
248 
255  function setReferenceTablePK($a_column_name)
256  {
257  if (!isset($a_column_name))
258  {
259  $this->ilErr->raiseError(get_class($this)."::setReferenceTablePK(): No column name given!",$this->ilErr->WARNING);
260  }
261 
262  $this->ref_pk = $a_column_name;
263  return true;
264  }
265 
272  function setObjectTablePK($a_column_name)
273  {
274  if (!isset($a_column_name))
275  {
276  $this->ilErr->raiseError(get_class($this)."::setObjectTablePK(): No column name given!",$this->ilErr->WARNING);
277  }
278 
279  $this->obj_pk = $a_column_name;
280  return true;
281  }
282 
289  function setTreeTablePK($a_column_name)
290  {
291  if (!isset($a_column_name))
292  {
293  $this->ilErr->raiseError(get_class($this)."::setTreeTablePK(): No column name given!",$this->ilErr->WARNING);
294  }
295 
296  $this->tree_pk = $a_column_name;
297  return true;
298  }
299 
305  function buildJoin()
306  {
307  if ($this->table_obj_reference)
308  {
309  // Use inner join instead of left join to improve performance
310  return "JOIN ".$this->table_obj_reference." ON ".$this->table_tree.".child=".$this->table_obj_reference.".".$this->ref_pk." ".
311  "JOIN ".$this->table_obj_data." ON ".$this->table_obj_reference.".".$this->obj_pk."=".$this->table_obj_data.".".$this->obj_pk." ";
312  }
313  else
314  {
315  // Use inner join instead of left join to improve performance
316  return "JOIN ".$this->table_obj_data." ON ".$this->table_tree.".child=".$this->table_obj_data.".".$this->obj_pk." ";
317  }
318  }
319 
328  function getChilds($a_node_id, $a_order = "", $a_direction = "ASC")
329  {
330  global $ilBench,$ilDB;
331 
332  if (!isset($a_node_id))
333  {
334  $message = get_class($this)."::getChilds(): No node_id given!";
335  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
336  }
337 
338  // init childs
339  $childs = array();
340 
341  // number of childs
342  $count = 0;
343 
344  // init order_clause
345  $order_clause = "";
346 
347  // set order_clause if sort order parameter is given
348  if (!empty($a_order))
349  {
350  $order_clause = "ORDER BY ".$a_order." ".$a_direction;
351  }
352  else
353  {
354  $order_clause = "ORDER BY ".$this->table_tree.".lft";
355  }
356 
357 
358  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
359  $this->buildJoin().
360  "WHERE parent = %s " .
361  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
362  $order_clause,
363  $ilDB->quote($a_node_id,'integer'),
364  $ilDB->quote($this->tree_id,'integer'));
365 
366  $res = $ilDB->query($query);
367 
368  if(!$count = $res->numRows())
369  {
370  return array();
371  }
372 
373  while($row = $ilDB->fetchAssoc($res))
374  {
375  $childs[] = $this->fetchNodeData($row);
376 
377  // Update cache of main tree
378  if ($this->__isMainTree())
379  {
380  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child'].' = true');
381  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
382  }
383  }
384  $childs[$count - 1]["last"] = true;
385  return $childs;
386  }
387 
397  function getFilteredChilds($a_filter,$a_node,$a_order = "",$a_direction = "ASC")
398  {
399  $childs = $this->getChilds($a_node,$a_order,$a_direction);
400 
401  foreach($childs as $child)
402  {
403  if(!in_array($child["type"],$a_filter))
404  {
405  $filtered[] = $child;
406  }
407  }
408  return $filtered ? $filtered : array();
409  }
410 
411 
419  function getChildsByType($a_node_id,$a_type)
420  {
421  global $ilDB;
422 
423  if (!isset($a_node_id) or !isset($a_type))
424  {
425  $message = get_class($this)."::getChildsByType(): Missing parameter! node_id:".$a_node_id." type:".$a_type;
426  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
427  }
428 
429  if ($a_type=='rolf' && $this->table_obj_reference) {
430  // Performance optimization: A node can only have exactly one
431  // role folder as its child. Therefore we don't need to sort the
432  // results, and we can let the database know about the expected limit.
433  $ilDB->setLimit(1,0);
434  $query = sprintf("SELECT * FROM ".$this->table_tree." ".
435  $this->buildJoin().
436  "WHERE parent = %s ".
437  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
438  "AND ".$this->table_obj_data.".type = %s ",
439  $ilDB->quote($a_node_id,'integer'),
440  $ilDB->quote($this->tree_id,'integer'),
441  $ilDB->quote($a_type,'text'));
442  } else {
443  $query = sprintf("SELECT * FROM ".$this->table_tree." ".
444  $this->buildJoin().
445  "WHERE parent = %s ".
446  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
447  "AND ".$this->table_obj_data.".type = %s ".
448  "ORDER BY ".$this->table_tree.".lft",
449  $ilDB->quote($a_node_id,'integer'),
450  $ilDB->quote($this->tree_id,'integer'),
451  $ilDB->quote($a_type,'text'));
452  }
453  $res = $ilDB->query($query);
454 
455  // init childs
456  $childs = array();
457  while($row = $ilDB->fetchAssoc($res))
458  {
459  $childs[] = $this->fetchNodeData($row);
460  }
461 
462  return $childs ? $childs : array();
463  }
464 
465 
473  public function getChildsByTypeFilter($a_node_id,$a_types)
474  {
475  global $ilDB;
476 
477  if (!isset($a_node_id) or !$a_types)
478  {
479  $message = get_class($this)."::getChildsByType(): Missing parameter! node_id:".$a_node_id." type:".$a_types;
480  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
481  }
482 
483  $filter = ' ';
484  if($a_types)
485  {
486  $filter = 'AND '.$this->table_obj_data.'.type IN('.implode(',',ilUtil::quoteArray($a_types)).') ';
487  }
488 
489  $query = 'SELECT * FROM '.$this->table_tree.' '.
490  $this->buildJoin().
491  'WHERE parent = '.$ilDB->quote($a_node_id,'integer').' '.
492  'AND '.$this->table_tree.'.'.$this->tree_pk.' = '.$ilDB->quote($this->tree_id,'integer').' '.
493  $filter.
494  'ORDER BY '.$this->table_tree.'.lft';
495 
496  $res = $ilDB->query($query);
497  while($row = $ilDB->fetchAssoc($res))
498  {
499  $childs[] = $this->fetchNodeData($row);
500  }
501 
502  return $childs ? $childs : array();
503  }
504 
512  function insertNode($a_node_id, $a_parent_id, $a_pos = IL_LAST_NODE, $a_reset_deletion_date = false)
513  {
514  global $ilDB;
515 
516 //echo "+$a_node_id+$a_parent_id+";
517  // CHECK node_id and parent_id > 0 if in main tree
518  if($this->__isMainTree())
519  {
520  if($a_node_id <= 1 or $a_parent_id <= 0)
521  {
522  $message = sprintf('%s::insertNode(): Invalid parameters! $a_node_id: %s $a_parent_id: %s',
523  get_class($this),
524  $a_node_id,
525  $a_parent_id);
526  $this->log->write($message,$this->log->FATAL);
527  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
528  }
529  }
530 
531 
532  if (!isset($a_node_id) or !isset($a_parent_id))
533  {
534  $this->ilErr->raiseError(get_class($this)."::insertNode(): Missing parameter! ".
535  "node_id: ".$a_node_id." parent_id: ".$a_parent_id,$this->ilErr->WARNING);
536  }
537  if ($this->isInTree($a_node_id))
538  {
539  $this->ilErr->raiseError(get_class($this)."::insertNode(): Node ".$a_node_id." already in tree ".
540  $this->table_tree."!",$this->ilErr->WARNING);
541  }
542 
543  //
544  switch ($a_pos)
545  {
546  case IL_FIRST_NODE:
547 
548  if($this->__isMainTree())
549  {
550  #ilDB::_lockTables(array('tree' => 'WRITE'));
551  $ilDB->lockTables(
552  array(
553  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
554  }
555 
556  // get left value of parent
557  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
558  'WHERE child = %s '.
559  'AND '.$this->tree_pk.' = %s ',
560  $ilDB->quote($a_parent_id,'integer'),
561  $ilDB->quote($this->tree_id,'integer'));
562 
563  $res = $ilDB->query($query);
564  $r = $ilDB->fetchObject($res);
565 
566  if ($r->parent == NULL)
567  {
568  if($this->__isMainTree())
569  {
570  $ilDB->unlockTables();
571  }
572  $this->ilErr->raiseError(get_class($this)."::insertNode(): Parent with ID ".$a_parent_id." not found in ".
573  $this->table_tree."!",$this->ilErr->WARNING);
574  }
575 
576  $left = $r->lft;
577  $lft = $left + 1;
578  $rgt = $left + 2;
579 
580  // spread tree
581  $query = sprintf('UPDATE '.$this->table_tree.' SET '.
582  'lft = CASE WHEN lft > %s THEN lft + 2 ELSE lft END, '.
583  'rgt = CASE WHEN rgt > %s THEN rgt + 2 ELSE rgt END '.
584  'WHERE '.$this->tree_pk.' = %s ',
585  $ilDB->quote($left,'integer'),
586  $ilDB->quote($left,'integer'),
587  $ilDB->quote($this->tree_id,'integer'));
588  $res = $ilDB->manipulate($query);
589  break;
590 
591  case IL_LAST_NODE:
592  // Special treatment for trees with gaps
593  if ($this->gap > 0)
594  {
595  if($this->__isMainTree())
596  {
597  #ilDB::_lockTables(array('tree' => 'WRITE'));
598  $ilDB->lockTables(
599  array(
600  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
601 
602  }
603 
604  // get lft and rgt value of parent
605  $query = sprintf('SELECT rgt,lft,parent FROM '.$this->table_tree.' '.
606  'WHERE child = %s '.
607  'AND '.$this->tree_pk.' = %s',
608  $ilDB->quote($a_parent_id,'integer'),
609  $ilDB->quote($this->tree_id,'integer'));
610  $res = $ilDB->query($query);
611  $r = $ilDB->fetchAssoc($res);
612 
613  if ($r['parent'] == null)
614  {
615  if($this->__isMainTree())
616  {
617  $ilDB->unlockTables();
618  }
619  $this->ilErr->raiseError(get_class($this)."::insertNode(): Parent with ID ".
620  $a_parent_id." not found in ".$this->table_tree."!",$this->ilErr->WARNING);
621  }
622  $parentRgt = $r['rgt'];
623  $parentLft = $r['lft'];
624 
625  // Get the available space, without taking children into account yet
626  $availableSpace = $parentRgt - $parentLft;
627  if ($availableSpace < 2)
628  {
629  // If there is not enough space between parent lft and rgt, we don't need
630  // to look any further, because we must spread the tree.
631  $lft = $parentRgt;
632  }
633  else
634  {
635  // If there is space between parent lft and rgt, we need to check
636  // whether there is space left between the rightmost child of the
637  // parent and parent rgt.
638  $query = sprintf('SELECT MAX(rgt) max_rgt FROM '.$this->table_tree.' '.
639  'WHERE parent = %s '.
640  'AND '.$this->tree_pk.' = %s',
641  $ilDB->quote($a_parent_id,'integer'),
642  $ilDB->quote($this->tree_id,'integer'));
643  $res = $ilDB->query($query);
644  $r = $ilDB->fetchAssoc($res);
645 
646  if (isset($r['max_rgt']))
647  {
648  // If the parent has children, we compute the available space
649  // between rgt of the rightmost child and parent rgt.
650  $availableSpace = $parentRgt - $r['max_rgt'];
651  $lft = $r['max_rgt'] + 1;
652  }
653  else
654  {
655  // If the parent has no children, we know now, that we can
656  // add the new node at parent lft + 1 without having to spread
657  // the tree.
658  $lft = $parentLft + 1;
659  }
660  }
661  $rgt = $lft + 1;
662 
663 
664  // spread tree if there is not enough space to insert the new node
665  if ($availableSpace < 2)
666  {
667  //$this->log->write('ilTree.insertNode('.$a_node_id.','.$a_parent_id.') creating gap at '.$a_parent_id.' '.$parentLft.'..'.$parentRgt.'+'.(2 + $this->gap * 2));
668  $query = sprintf('UPDATE '.$this->table_tree.' SET '.
669  'lft = CASE WHEN lft > %s THEN lft + %s ELSE lft END, '.
670  'rgt = CASE WHEN rgt >= %s THEN rgt + %s ELSE rgt END '.
671  'WHERE '.$this->tree_pk.' = %s ',
672  $ilDB->quote($parentRgt,'integer'),
673  $ilDB->quote((2 + $this->gap * 2),'integer'),
674  $ilDB->quote($parentRgt,'integer'),
675  $ilDB->quote((2 + $this->gap * 2),'integer'),
676  $ilDB->quote($this->tree_id,'integer'));
677  $res = $ilDB->manipulate($query);
678  }
679  else
680  {
681  //$this->log->write('ilTree.insertNode('.$a_node_id.','.$a_parent_id.') reusing gap at '.$a_parent_id.' '.$parentLft.'..'.$parentRgt.' for node '.$a_node_id.' '.$lft.'..'.$rgt);
682  }
683  }
684  // Treatment for trees without gaps
685  else
686  {
687  if($this->__isMainTree())
688  {
689  #ilDB::_lockTables(array('tree' => 'WRITE'));
690  $ilDB->lockTables(
691  array(
692  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
693 
694  }
695 
696  // get right value of parent
697  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
698  'WHERE child = %s '.
699  'AND '.$this->tree_pk.' = %s ',
700  $ilDB->quote($a_parent_id,'integer'),
701  $ilDB->quote($this->tree_id,'integer'));
702  $res = $ilDB->query($query);
703  $r = $ilDB->fetchObject($res);
704 
705  if ($r->parent == null)
706  {
707  if($this->__isMainTree())
708  {
709  $ilDB->unlockTables();
710  }
711  $this->ilErr->raiseError(get_class($this)."::insertNode(): Parent with ID ".
712  $a_parent_id." not found in ".$this->table_tree."!",$this->ilErr->WARNING);
713  }
714 
715  $right = $r->rgt;
716  $lft = $right;
717  $rgt = $right + 1;
718 
719  // spread tree
720  $query = sprintf('UPDATE '.$this->table_tree.' SET '.
721  'lft = CASE WHEN lft > %s THEN lft + 2 ELSE lft END, '.
722  'rgt = CASE WHEN rgt >= %s THEN rgt + 2 ELSE rgt END '.
723  'WHERE '.$this->tree_pk.' = %s',
724  $ilDB->quote($right,'integer'),
725  $ilDB->quote($right,'integer'),
726  $ilDB->quote($this->tree_id,'integer'));
727  $res = $ilDB->manipulate($query);
728  }
729 
730  break;
731 
732  default:
733 
734  // this code shouldn't be executed
735  if($this->__isMainTree())
736  {
737  #ilDB::_lockTables(array('tree' => 'WRITE'));
738  $ilDB->lockTables(
739  array(
740  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
741 
742  }
743 
744  // get right value of preceeding child
745  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
746  'WHERE child = %s '.
747  'AND '.$this->tree_pk.' = %s ',
748  $ilDB->quote($a_pos,'integer'),
749  $ilDB->quote($this->tree_id,'integer'));
750  $res = $ilDB->query($query);
751  $r = $ilDB->fetchObject($res);
752 
753  // crosscheck parents of sibling and new node (must be identical)
754  if ($r->parent != $a_parent_id)
755  {
756  if($this->__isMainTree())
757  {
758  $ilDB->unlockTables();
759  }
760  $this->ilErr->raiseError(get_class($this)."::insertNode(): Parents mismatch! ".
761  "new node parent: ".$a_parent_id." sibling parent: ".$r->parent,$this->ilErr->WARNING);
762  }
763 
764  $right = $r->rgt;
765  $lft = $right + 1;
766  $rgt = $right + 2;
767 
768  // update lft/rgt values
769  $query = sprintf('UPDATE '.$this->table_tree.' SET '.
770  'lft = CASE WHEN lft > %s THEN lft + 2 ELSE lft END, '.
771  'rgt = CASE WHEN rgt > %s THEN rgt + 2 ELSE rgt END '.
772  'WHERE '.$this->tree_pk.' = %s',
773  $ilDB->quote($right,'integer'),
774  $ilDB->quote($right,'integer'),
775  $ilDB->quote($this->tree_id,'integer'));
776  $res = $ilDB->manipulate($query);
777  break;
778 
779  }
780 
781  // get depth
782  $depth = $this->getDepth($a_parent_id) + 1;
783 
784  // insert node
785  //$this->log->write('ilTree.insertNode('.$a_node_id.','.$a_parent_id.') inserting node:'.$a_node_id.' parent:'.$a_parent_id." ".$lft."..".$rgt." depth:".$depth);
786  $query = sprintf('INSERT INTO '.$this->table_tree.' ('.$this->tree_pk.',child,parent,lft,rgt,depth) '.
787  'VALUES (%s,%s,%s,%s,%s,%s)',
788  $ilDB->quote($this->tree_id,'integer'),
789  $ilDB->quote($a_node_id,'integer'),
790  $ilDB->quote($a_parent_id,'integer'),
791  $ilDB->quote($lft,'integer'),
792  $ilDB->quote($rgt,'integer'),
793  $ilDB->quote($depth,'integer'));
794  $res = $ilDB->manipulate($query);
795 
796  // Finally unlock tables and update cache
797  if($this->__isMainTree())
798  {
799  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
800  $this->in_tree_cache[$a_node_id] = true;
801  $ilDB->unlockTables();
802  }
803 
804  // reset deletion date
805  if ($a_reset_deletion_date)
806  {
807  ilObject::_resetDeletedDate($a_node_id);
808  }
809  }
810 
823  public function getFilteredSubTree($a_node_id,$a_filter = array())
824  {
825  $node = $this->getNodeData($a_node_id);
826 
827  $first = true;
828  $depth = 0;
829  foreach($this->getSubTree($node) as $subnode)
830  {
831  if($depth and $subnode['depth'] > $depth)
832  {
833  continue;
834  }
835  if(!$first and in_array($subnode['type'],$a_filter))
836  {
837  $depth = $subnode['depth'];
838  $first = false;
839  continue;
840  }
841  $depth = 0;
842  $first = false;
843  $filtered[] = $subnode;
844  }
845  return $filtered ? $filtered : array();
846  }
847 
853  public function getSubTreeIds($a_ref_id)
854  {
855  global $ilDB;
856 
857  $query = 'SELECT s.child FROM '.$this->table_tree.' s, '.$this->table_tree.' t '.
858  'WHERE t.child = %s '.
859  'AND s.lft > t.lft '.
860  'AND s.rgt < t.rgt '.
861  'AND s.'.$this->tree_pk.' = %s';
862 
863  $res = $ilDB->queryF(
864  $query,
865  array('integer','integer'),
866  array($a_ref_id,$this->tree_id)
867  );
868  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
869  {
870  $childs[] = $row->child;
871  }
872  return $childs ? $childs : array();
873  }
874 
875 
884  function getSubTree($a_node,$a_with_data = true, $a_type = "")
885  {
886  global $ilDB;
887 
888  if (!is_array($a_node))
889  {
890  $this->ilErr->raiseError(get_class($this)."::getSubTree(): Wrong datatype for node_data! ",$this->ilErr->WARNING);
891  }
892 
893  if($a_node['lft'] < 1 or $a_node['rgt'] < 2)
894  {
895  $message = sprintf('%s::getSubTree(): Invalid node given! $a_node["lft"]: %s $a_node["rgt"]: %s',
896  get_class($this),
897  $a_node['lft'],
898  $a_node['rgt']);
899 
900  $this->log->write($message,$this->log->FATAL);
901 
902  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
903  }
904 
905 
906  $fields = array('integer','integer','integer');
907  $data = array($a_node['lft'],$a_node['rgt'],$this->tree_id);
908  $type_str = '';
909 
910  if(strlen($a_type))
911  {
912  $fields[] = 'text';
913  $data[] = $a_type;
914  $type_str = "AND ".$this->table_obj_data.".type= %s ";
915  }
916 
917  $query = "SELECT * FROM ".$this->table_tree." ".
918  $this->buildJoin().
919  "WHERE ".$this->table_tree.".lft BETWEEN %s AND %s ".
920  "AND ".$this->table_tree.".".$this->tree_pk." = %s ".
921  $type_str.
922  "ORDER BY ".$this->table_tree.".lft";
923  $res = $ilDB->queryF($query,$fields,$data);
924  while($row = $ilDB->fetchAssoc($res))
925  {
926  if($a_with_data)
927  {
928  $subtree[] = $this->fetchNodeData($row);
929  }
930  else
931  {
932  $subtree[] = $row['child'];
933  }
934  if($this->__isMainTree())
935  {
936  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
937  $this->in_tree_cache[$row['child']] = true;
938  }
939  }
940 
941  return $subtree ? $subtree : array();
942  }
943 
952  function getSubTreeTypes($a_node,$a_filter = 0)
953  {
954  $a_filter = $a_filter ? $a_filter : array();
955 
956  foreach($this->getSubtree($this->getNodeData($a_node)) as $node)
957  {
958  if(in_array($node["type"],$a_filter))
959  {
960  continue;
961  }
962  $types["$node[type]"] = $node["type"];
963  }
964  return $types ? $types : array();
965  }
966 
972  function deleteTree($a_node)
973  {
974  global $ilDB;
975 
976  if (!is_array($a_node))
977  {
978  $this->ilErr->raiseError(get_class($this)."::deleteTree(): Wrong datatype for node_data! ",$this->ilErr->WARNING);
979  }
980  if($this->__isMainTree() and $a_node[$this->tree_pk] === 1)
981  {
982  if($a_node['lft'] <= 1 or $a_node['rgt'] <= 2)
983  {
984  $message = sprintf('%s::deleteTree(): Invalid parameters given: $a_node["lft"]: %s, $a_node["rgt"] %s',
985  get_class($this),
986  $a_node['lft'],
987  $a_node['rgt']);
988 
989  $this->log->write($message,$this->log->FATAL);
990  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
991  }
992  else if(!$this->__checkDelete($a_node))
993  {
994  $message = sprintf('%s::deleteTree(): Check delete failed: $a_node["lft"]: %s, $a_node["rgt"] %s',
995  get_class($this),
996  $a_node['lft'],
997  $a_node['rgt']);
998  $this->log->write($message,$this->log->FATAL);
999  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1000  }
1001 
1002  }
1003  $diff = $a_node["rgt"] - $a_node["lft"] + 1;
1004 
1005 
1006  // LOCKED ###########################################################
1007  // get lft and rgt values. Don't trust parameter lft/rgt values of $a_node
1008  if($this->__isMainTree())
1009  {
1010  #ilDB::_lockTables(array('tree' => 'WRITE'));
1011  $ilDB->lockTables(
1012  array(
1013  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
1014 
1015  }
1016 
1017  $query = sprintf('SELECT * FROM '.$this->table_tree.' '.
1018  'WHERE child = %s '.
1019  'AND '.$this->tree_pk.' = %s ',
1020  $ilDB->quote($a_node['child'],'integer'),
1021  $ilDB->quote($a_node[$this->tree_pk],'integer'));
1022  $res = $ilDB->query($query);
1023  while($row = $ilDB->fetchObject($res))
1024  {
1025  $a_node['lft'] = $row->lft;
1026  $a_node['rgt'] = $row->rgt;
1027  $diff = $a_node["rgt"] - $a_node["lft"] + 1;
1028  }
1029 
1030  // delete subtree
1031  $query = sprintf('DELETE FROM '.$this->table_tree.' '.
1032  'WHERE lft BETWEEN %s AND %s '.
1033  'AND rgt BETWEEN %s AND %s '.
1034  'AND '.$this->tree_pk.' = %s',
1035  $ilDB->quote($a_node['lft'],'integer'),
1036  $ilDB->quote($a_node['rgt'],'integer'),
1037  $ilDB->quote($a_node['lft'],'integer'),
1038  $ilDB->quote($a_node['rgt'],'integer'),
1039  $ilDB->quote($a_node[$this->tree_pk],'integer'));
1040  $res = $ilDB->manipulate($query);
1041 
1042  // Performance improvement: We only close the gap, if the node
1043  // is not in a trash tree, and if the resulting gap will be
1044  // larger than twice the gap value
1045  if ($a_node[$this->tree_pk] >= 0 && $a_node['rgt'] - $a_node['lft'] >= $this->gap * 2)
1046  {
1047  //$this->log->write('ilTree.deleteTree('.$a_node['child'].') closing gap at '.$a_node['lft'].'...'.$a_node['rgt']);
1048  // close gaps
1049  $query = sprintf('UPDATE '.$this->table_tree.' SET '.
1050  'lft = CASE WHEN lft > %s THEN lft - %s ELSE lft END, '.
1051  'rgt = CASE WHEN rgt > %s THEN rgt - %s ELSE rgt END '.
1052  'WHERE '.$this->tree_pk.' = %s ',
1053  $ilDB->quote($a_node['lft'],'integer'),
1054  $ilDB->quote($diff,'integer'),
1055  $ilDB->quote($a_node['lft'],'integer'),
1056  $ilDB->quote($diff,'integer'),
1057  $ilDB->quote($a_node[$this->tree_pk],'integer'));
1058 
1059  $res = $ilDB->manipulate($query);
1060  }
1061  else
1062  {
1063  //$this->log->write('ilTree.deleteTree('.$a_node['child'].') leaving gap open '.$a_node['lft'].'...'.$a_node['rgt']);
1064  }
1065 
1066  if($this->__isMainTree())
1067  {
1068  #$GLOBALS['ilLog']->write(__METHOD__.': Resetting in tree cache ');
1069  $ilDB->unlockTables();
1070  $this->in_tree_cache = array();
1071  }
1072  // LOCKED ###########################################################
1073  }
1074 
1085  function getPathFull($a_endnode_id, $a_startnode_id = 0)
1086  {
1087  $pathIds =& $this->getPathId($a_endnode_id, $a_startnode_id);
1088 
1089  // We retrieve the full path in a single query to improve performance
1090  global $ilDB;
1091 
1092  // Abort if no path ids were found
1093  if (count($pathIds) == 0)
1094  {
1095  return null;
1096  }
1097 
1098  $inClause = 'child IN (';
1099  for ($i=0; $i < count($pathIds); $i++)
1100  {
1101  if ($i > 0) $inClause .= ',';
1102  $inClause .= $ilDB->quote($pathIds[$i],'integer');
1103  }
1104  $inClause .= ')';
1105 
1106  $q = 'SELECT * '.
1107  'FROM '.$this->table_tree.' '.
1108  $this->buildJoin().' '.
1109  'WHERE '.$inClause.' '.
1110  'AND '.$this->table_tree.'.'.$this->tree_pk.' = '.$this->ilDB->quote($this->tree_id,'integer').' '.
1111  'ORDER BY depth';
1112  $r = $ilDB->query($q);
1113 
1114  $pathFull = array();
1115  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1116  {
1117  $pathFull[] = $this->fetchNodeData($row);
1118 
1119  // Update cache
1120  if ($this->__isMainTree())
1121  {
1122  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child']);
1123  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
1124  }
1125  }
1126  return $pathFull;
1127  }
1136  function getPathIdsUsingNestedSets($a_endnode_id, $a_startnode_id = 0)
1137  {
1138  global $ilDB;
1139 
1140  // The nested sets algorithm is very easy to implement.
1141  // Unfortunately it always does a full table space scan to retrieve the path
1142  // regardless whether indices on lft and rgt are set or not.
1143  // (At least, this is what happens on MySQL 4.1).
1144  // This algorithms performs well for small trees which are deeply nested.
1145 
1146  if (!isset($a_endnode_id))
1147  {
1148  $this->ilErr->raiseError(get_class($this)."::getPathId(): No endnode_id given! ",$this->ilErr->WARNING);
1149  }
1150 
1151  $fields = array('integer','integer','integer');
1152  $data = array($a_endnode_id,$this->tree_id,$this->tree_id);
1153 
1154  $query = "SELECT T2.child ".
1155  "FROM ".$this->table_tree." T1, ".$this->table_tree." T2 ".
1156  "WHERE T1.child = %s ".
1157  "AND T1.lft BETWEEN T2.lft AND T2.rgt ".
1158  "AND T1.".$this->tree_pk." = %s ".
1159  "AND T2.".$this->tree_pk." = %s ".
1160  "ORDER BY T2.depth";
1161  $res = $ilDB->queryF($query,$fields,$data);
1162 
1163  $takeId = $a_startnode_id == 0;
1164  while($row = $ilDB->fetchAssoc($res))
1165  {
1166  if ($takeId || $row['child'] == $a_startnode_id)
1167  {
1168  $takeId = true;
1169  $pathIds[] = $row['child'];
1170  }
1171  }
1172  return $pathIds ? $pathIds : array();
1173  }
1174 
1183  function getPathIdsUsingAdjacencyMap($a_endnode_id, $a_startnode_id = 0)
1184  {
1185  // The adjacency map algorithm is harder to implement than the nested sets algorithm.
1186  // This algorithms performs an index search for each of the path element.
1187  // This algorithms performs well for large trees which are not deeply nested.
1188 
1189  // The $takeId variable is used, to determine if a given id shall be included in the path
1190  $takeId = $a_startnode_id == 0;
1191 
1192  if (!isset($a_endnode_id))
1193  {
1194  $this->ilErr->raiseError(get_class($this)."::getPathId(): No endnode_id given! ",$this->ilErr->WARNING);
1195  }
1196 
1197  global $log, $ilDB;
1198 
1199  $types = array('integer','integer');
1200  $data = array($a_endnode_id,$this->tree_id);
1201 
1202  $query = 'SELECT t.depth, t.parent '.
1203  'FROM '.$this->table_tree.' t '.
1204  'WHERE child = %s '.
1205  'AND '.$this->tree_pk.' = %s ';
1206  $res = $ilDB->queryF($query,$types,$data);
1207 
1208  if($res->numRows() == 0)
1209  {
1210  return array();
1211  }
1212 
1213  $row = $ilDB->fetchAssoc($res);
1214  $nodeDepth = $row['depth'];
1215  $parentId = $row['parent'];
1216  //$this->writelog('getIdsUsingAdjacencyMap depth='.$nodeDepth);
1217 
1218  // Fetch the node ids. For shallow depths we can fill in the id's directly.
1219  $pathIds = array();
1220  if ($nodeDepth == 1)
1221  {
1222  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1223  if ($takeId) $pathIds[] = $a_endnode_id;
1224  }
1225  else if ($nodeDepth == 2)
1226  {
1227  $takeId = $takeId || $parentId == $a_startnode_id;
1228  if ($takeId) $pathIds[] = $parentId;
1229  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1230  if ($takeId) $pathIds[] = $a_endnode_id;
1231  }
1232  else if ($nodeDepth == 3)
1233  {
1234  $takeId = $takeId || $this->root_id == $a_startnode_id;
1235  if ($takeId) $pathIds[] = $this->root_id;
1236  $takeId = $takeId || $parentId == $a_startnode_id;
1237  if ($takeId) $pathIds[] = $parentId;
1238  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1239  if ($takeId) $pathIds[] = $a_endnode_id;
1240  }
1241  else if ($nodeDepth < 32)
1242  {
1243  // Adjacency Map Tree performs better than
1244  // Nested Sets Tree even for very deep trees.
1245  // The following code construct nested self-joins
1246  // Since we already know the root-id of the tree and
1247  // we also know the id and parent id of the current node,
1248  // we only need to perform $nodeDepth - 3 self-joins.
1249  // We can further reduce the number of self-joins by 1
1250  // by taking into account, that each row in table tree
1251  // contains the id of itself and of its parent.
1252  $qSelect = 't1.child c0';
1253  $qJoin = '';
1254  for ($i = 1; $i < $nodeDepth - 2; $i++)
1255  {
1256  $qSelect .= ', t'.$i.'.parent c'.$i;
1257  $qJoin .= ' JOIN '.$this->table_tree.' t'.$i.' ON '.
1258  't'.$i.'.child=t'.($i - 1).'.parent AND '.
1259  't'.$i.'.'.$this->tree_pk.' = '.(int) $this->tree_id;
1260  }
1261 
1262  $types = array('integer','integer');
1263  $data = array($this->tree_id,$parentId);
1264  $query = 'SELECT '.$qSelect.' '.
1265  'FROM '.$this->table_tree.' t0 '.$qJoin.' '.
1266  'WHERE t0.'.$this->tree_pk.' = %s '.
1267  'AND t0.child = %s ';
1268 
1269  $ilDB->setLimit(1);
1270  $res = $ilDB->queryF($query,$types,$data);
1271 
1272  if ($res->numRows() == 0)
1273  {
1274  return array();
1275  }
1276  $row = $ilDB->fetchAssoc($res);
1277 
1278  $takeId = $takeId || $this->root_id == $a_startnode_id;
1279  if ($takeId) $pathIds[] = $this->root_id;
1280  for ($i = $nodeDepth - 4; $i >=0; $i--)
1281  {
1282  $takeId = $takeId || $row['c'.$i] == $a_startnode_id;
1283  if ($takeId) $pathIds[] = $row['c'.$i];
1284  }
1285  $takeId = $takeId || $parentId == $a_startnode_id;
1286  if ($takeId) $pathIds[] = $parentId;
1287  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1288  if ($takeId) $pathIds[] = $a_endnode_id;
1289  }
1290  else
1291  {
1292  // Fall back to nested sets tree for extremely deep tree structures
1293  return $this->getPathIdsUsingNestedSets($a_endnode_id, $a_startnode_id);
1294  }
1295 
1296  return $pathIds;
1297  }
1298 
1307  function getPathId($a_endnode_id, $a_startnode_id = 0)
1308  {
1309  // path id cache
1310  if ($this->isCacheUsed() && isset($this->path_id_cache[$a_endnode_id][$a_startnode_id]))
1311  {
1312 //echo "<br>getPathIdhit";
1313  return $this->path_id_cache[$a_endnode_id][$a_startnode_id];
1314  }
1315 //echo "<br>miss";
1316 
1317  $pathIds =& $this->getPathIdsUsingAdjacencyMap($a_endnode_id, $a_startnode_id);
1318 
1319  if($this->__isMainTree())
1320  {
1321  $this->path_id_cache[$a_endnode_id][$a_startnode_id] = $pathIds;
1322  }
1323  return $pathIds;
1324  }
1325 
1326  // BEGIN WebDAV: getNodePathForTitlePath function added
1344  function getNodePathForTitlePath($titlePath, $a_startnode_id = null)
1345  {
1346  global $ilDB, $log;
1347  //$log->write('getNodePathForTitlePath('.implode('/',$titlePath));
1348 
1349  // handle empty title path
1350  if ($titlePath == null || count($titlePath) == 0)
1351  {
1352  if ($a_startnode_id == 0)
1353  {
1354  return null;
1355  }
1356  else
1357  {
1358  return $this->getNodePath($a_startnode_id);
1359  }
1360  }
1361 
1362  // fetch the node path up to the startnode
1363  if ($a_startnode_id != null && $a_startnode_id != 0)
1364  {
1365  // Start using the node path to the root of the relative path
1366  $nodePath = $this->getNodePath($a_startnode_id);
1367  $parent = $a_startnode_id;
1368  }
1369  else
1370  {
1371  // Start using the root of the tree
1372  $nodePath = array();
1373  $parent = 0;
1374  }
1375 
1376 
1377  // Convert title path into Unicode Normal Form C
1378  // This is needed to ensure that we can compare title path strings with
1379  // strings from the database.
1380  require_once('include/Unicode/UtfNormal.php');
1381  include_once './Services/Utilities/classes/class.ilStr.php';
1382  $inClause = 'd.title IN (';
1383  for ($i=0; $i < count($titlePath); $i++)
1384  {
1385  $titlePath[$i] = ilStr::strToLower(UtfNormal::toNFC($titlePath[$i]));
1386  if ($i > 0) $inClause .= ',';
1387  $inClause .= $ilDB->quote($titlePath[$i],'text');
1388  }
1389  $inClause .= ')';
1390 
1391  // Fetch all rows that are potential path elements
1392  if ($this->table_obj_reference)
1393  {
1394  $joinClause = 'JOIN '.$this->table_obj_reference.' r ON t.child = r.'.$this->ref_pk.' '.
1395  'JOIN '.$this->table_obj_data.' d ON r.'.$this->obj_pk.' = d.'.$this->obj_pk;
1396  }
1397  else
1398  {
1399  $joinClause = 'JOIN '.$this->table_obj_data.' d ON t.child = d.'.$this->obj_pk;
1400  }
1401  // The ORDER BY clause in the following SQL statement ensures that,
1402  // in case of a multiple objects with the same title, always the Object
1403  // with the oldest ref_id is chosen.
1404  // This ensure, that, if a new object with the same title is added,
1405  // WebDAV clients can still work with the older object.
1406  $q = 'SELECT t.depth, t.parent, t.child, d.'.$this->obj_pk.' obj_id, d.type, d.title '.
1407  'FROM '.$this->table_tree.' t '.
1408  $joinClause.' '.
1409  'WHERE '.$inClause.' '.
1410  'AND t.depth <= '.(count($titlePath)+count($nodePath)).' '.
1411  'AND t.tree = 1 '.
1412  'ORDER BY t.depth, t.child ASC';
1413  $r = $ilDB->query($q);
1414 
1415  $rows = array();
1416  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1417  {
1418  $row['title'] = UtfNormal::toNFC($row['title']);
1419  $row['ref_id'] = $row['child'];
1420  $rows[] = $row;
1421  }
1422 
1423  // Extract the path elements from the fetched rows
1424  for ($i = 0; $i < count($titlePath); $i++) {
1425  $pathElementFound = false;
1426  foreach ($rows as $row) {
1427  if ($row['parent'] == $parent &&
1428  ilStr::strToLower($row['title']) == $titlePath[$i])
1429  {
1430  // FIXME - We should test here, if the user has
1431  // 'visible' permission for the object.
1432  $nodePath[] = $row;
1433  $parent = $row['child'];
1434  $pathElementFound = true;
1435  break;
1436  }
1437  }
1438  // Abort if we haven't found a path element for the current depth
1439  if (! $pathElementFound)
1440  {
1441  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):null');
1442  return null;
1443  }
1444  }
1445  // Return the node path
1446  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):'.var_export($nodePath,true));
1447  return $nodePath;
1448  }
1449  // END WebDAV: getNodePathForTitlePath function added
1450  // END WebDAV: getNodePath function added
1467  function getNodePath($a_endnode_id, $a_startnode_id = 0)
1468  {
1469  global $ilDB;
1470 
1471  $pathIds = $this->getPathId($a_endnode_id, $a_startnode_id);
1472 
1473  // Abort if no path ids were found
1474  if (count($pathIds) == 0)
1475  {
1476  return null;
1477  }
1478 
1479 
1480  $types = array();
1481  $data = array();
1482  for ($i = 0; $i < count($pathIds); $i++)
1483  {
1484  $types[] = 'integer';
1485  $data[] = $pathIds[$i];
1486  }
1487 
1488  $query = 'SELECT t.depth,t.parent,t.child,d.obj_id,d.type,d.title '.
1489  'FROM '.$this->table_tree.' t '.
1490  'JOIN '.$this->table_obj_reference.' r ON r.ref_id = t.child '.
1491  'JOIN '.$this->table_obj_data.' d ON d.obj_id = r.obj_id '.
1492  'WHERE '.$ilDB->in('t.child',$data,false,'integer').' '.
1493  'ORDER BY t.depth ';
1494 
1495  $res = $ilDB->queryF($query,$types,$data);
1496 
1497  $titlePath = array();
1498  while ($row = $ilDB->fetchAssoc($res))
1499  {
1500  $titlePath[] = $row;
1501  }
1502  return $titlePath;
1503  }
1504  // END WebDAV: getNodePath function added
1505 
1512  function checkTree()
1513  {
1514  global $ilDB;
1515 
1516  $types = array('integer');
1517  $query = 'SELECT lft,rgt FROM '.$this->table_tree.' '.
1518  'WHERE '.$this->tree_pk.' = %s ';
1519 
1520  $res = $ilDB->queryF($query,$types,array($this->tree_id));
1521  while ($row = $ilDB->fetchObject($res))
1522  {
1523  $lft[] = $row->lft;
1524  $rgt[] = $row->rgt;
1525  }
1526 
1527  $all = array_merge($lft,$rgt);
1528  $uni = array_unique($all);
1529 
1530  if (count($all) != count($uni))
1531  {
1532  $message = sprintf('%s::checkTree(): Tree is corrupted!',
1533  get_class($this));
1534 
1535  $this->log->write($message,$this->log->FATAL);
1536  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1537  }
1538 
1539  return true;
1540  }
1541 
1545  function checkTreeChilds($a_no_zero_child = true)
1546  {
1547  global $ilDB;
1548 
1549  $query = 'SELECT * FROM '.$this->table_tree.' '.
1550  'WHERE '.$this->tree_pk.' = %s '.
1551  'ORDER BY lft';
1552  $r1 = $ilDB->queryF($query,array('integer'),array($this->tree_id));
1553 
1554  while ($row = $ilDB->fetchAssoc($r1))
1555  {
1556 //echo "tree:".$row[$this->tree_pk].":lft:".$row["lft"].":rgt:".$row["rgt"].":child:".$row["child"].":<br>";
1557  if (($row["child"] == 0) && $a_no_zero_child)
1558  {
1559  $this->ilErr->raiseError(get_class($this)."::checkTreeChilds(): Tree contains child with ID 0!",$this->ilErr->WARNING);
1560  }
1561 
1562  if ($this->table_obj_reference)
1563  {
1564  // get object reference data
1565  $query = 'SELECT * FROM '.$this->table_obj_reference.' WHERE '.$this->ref_pk.' = %s ';
1566  $r2 = $ilDB->queryF($query,array('integer'),array($row['child']));
1567 
1568 //echo "num_childs:".$r2->numRows().":<br>";
1569  if ($r2->numRows() == 0)
1570  {
1571  $this->ilErr->raiseError(get_class($this)."::checkTree(): No Object-to-Reference entry found for ID ".
1572  $row["child"]."!",$this->ilErr->WARNING);
1573  }
1574  if ($r2->numRows() > 1)
1575  {
1576  $this->ilErr->raiseError(get_class($this)."::checkTree(): More Object-to-Reference entries found for ID ".
1577  $row["child"]."!",$this->ilErr->WARNING);
1578  }
1579 
1580  // get object data
1581  $obj_ref = $ilDB->fetchAssoc($r2);
1582 
1583  $query = 'SELECT * FROM '.$this->table_obj_data.' WHERE '.$this->obj_pk.' = %s';
1584  $r3 = $ilDB->queryF($query,array('integer'),array($obj_ref[$this->obj_pk]));
1585  if ($r3->numRows() == 0)
1586  {
1587  $this->ilErr->raiseError(get_class($this)."::checkTree(): No child found for ID ".
1588  $obj_ref[$this->obj_pk]."!",$this->ilErr->WARNING);
1589  }
1590  if ($r3->numRows() > 1)
1591  {
1592  $this->ilErr->raiseError(get_class($this)."::checkTree(): More childs found for ID ".
1593  $obj_ref[$this->obj_pk]."!",$this->ilErr->WARNING);
1594  }
1595 
1596  }
1597  else
1598  {
1599  // get only object data
1600  $query = 'SELECT * FROM '.$this->table_obj_data.' WHERE '.$this->obj_pk.' = %s';
1601  $r2 = $ilDB->queryF($query,array('integer'),array($row['child']));
1602 //echo "num_childs:".$r2->numRows().":<br>";
1603  if ($r2->numRows() == 0)
1604  {
1605  $this->ilErr->raiseError(get_class($this)."::checkTree(): No child found for ID ".
1606  $row["child"]."!",$this->ilErr->WARNING);
1607  }
1608  if ($r2->numRows() > 1)
1609  {
1610  $this->ilErr->raiseError(get_class($this)."::checkTree(): More childs found for ID ".
1611  $row["child"]."!",$this->ilErr->WARNING);
1612  }
1613  }
1614  }
1615 
1616  return true;
1617  }
1618 
1624  function getMaximumDepth()
1625  {
1626  global $ilDB;
1627 
1628  $query = 'SELECT MAX(depth) depth FROM '.$this->table_tree;
1629  $res = $ilDB->query($query);
1630 
1631  $row = $ilDB->fetchAssoc($res);
1632  return $row['depth'];
1633  }
1634 
1641  function getDepth($a_node_id)
1642  {
1643  global $ilDB;
1644 
1645  if ($a_node_id)
1646  {
1647  $query = 'SELECT depth FROM '.$this->table_tree.' '.
1648  'WHERE child = %s '.
1649  'AND '.$this->tree_pk.' = %s ';
1650  $res = $ilDB->queryF($query,array('integer','integer'),array($a_node_id,$this->tree_id));
1651  $row = $ilDB->fetchObject($res);
1652 
1653  return $row->depth;
1654  }
1655  else
1656  {
1657  return 1;
1658  }
1659  }
1660 
1661 
1669  // BEGIN WebDAV: Pass tree id to this method
1670  //function getNodeData($a_node_id)
1671  function getNodeData($a_node_id, $a_tree_pk = null)
1672  // END PATCH WebDAV: Pass tree id to this method
1673  {
1674  global $ilDB;
1675 
1676  if (!isset($a_node_id))
1677  {
1678  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1679  }
1680  if($this->__isMainTree())
1681  {
1682  if($a_node_id < 1)
1683  {
1684  $message = sprintf('%s::getNodeData(): No valid parameter given! $a_node_id: %s',
1685  get_class($this),
1686  $a_node_id);
1687 
1688  $this->log->write($message,$this->log->FATAL);
1689  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1690  }
1691  }
1692 
1693  // BEGIN WebDAV: Pass tree id to this method
1694  $query = 'SELECT * FROM '.$this->table_tree.' '.
1695  $this->buildJoin().
1696  'WHERE '.$this->table_tree.'.child = %s '.
1697  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
1698  $res = $ilDB->queryF($query,array('integer','integer'),array(
1699  $a_node_id,
1700  $a_tree_pk === null ? $this->tree_id : $a_tree_pk));
1701  // END WebDAV: Pass tree id to this method
1702  $row = $ilDB->fetchAssoc($res);
1704 
1705  return $this->fetchNodeData($row);
1706  }
1707 
1715  function fetchNodeData($a_row)
1716  {
1717  global $objDefinition, $lng, $ilBench,$ilDB;
1718 
1719  //$ilBench->start("Tree", "fetchNodeData_getRow");
1720  $data = $a_row;
1721  $data["desc"] = $a_row["description"]; // for compability
1722  //$ilBench->stop("Tree", "fetchNodeData_getRow");
1723 
1724  // multilingual support systemobjects (sys) & categories (db)
1725  //$ilBench->start("Tree", "fetchNodeData_readDefinition");
1726  if (is_object($objDefinition))
1727  {
1728  $translation_type = $objDefinition->getTranslationType($data["type"]);
1729  }
1730  //$ilBench->stop("Tree", "fetchNodeData_readDefinition");
1731 
1732  if ($translation_type == "sys")
1733  {
1734  //$ilBench->start("Tree", "fetchNodeData_getLangData");
1735  if ($data["type"] == "rolf" and $data["obj_id"] != ROLE_FOLDER_ID)
1736  {
1737  $data["description"] = $lng->txt("obj_".$data["type"]."_local_desc").$data["title"].$data["desc"];
1738  $data["desc"] = $lng->txt("obj_".$data["type"]."_local_desc").$data["title"].$data["desc"];
1739  $data["title"] = $lng->txt("obj_".$data["type"]."_local");
1740  }
1741  else
1742  {
1743  $data["title"] = $lng->txt("obj_".$data["type"]);
1744  $data["description"] = $lng->txt("obj_".$data["type"]."_desc");
1745  $data["desc"] = $lng->txt("obj_".$data["type"]."_desc");
1746  }
1747  //$ilBench->stop("Tree", "fetchNodeData_getLangData");
1748  }
1749  elseif ($translation_type == "db")
1750  {
1751 
1752  // Try to retrieve object translation from cache
1753  if ($this->isCacheUsed() &&
1754  array_key_exists($data["obj_id"].'.'.$lang_code, $this->translation_cache)) {
1755 
1756  $key = $data["obj_id"].'.'.$lang_code;
1757  $data["title"] = $this->translation_cache[$key]['title'];
1758  $data["description"] = $this->translation_cache[$key]['description'];
1759  $data["desc"] = $this->translation_cache[$key]['desc'];
1760  } else {
1761  // Object translation is not in cache, read it from database
1762 
1763  //$ilBench->start("Tree", "fetchNodeData_getTranslation");
1764  $query = 'SELECT title,description FROM object_translation '.
1765  'WHERE obj_id = %s '.
1766  'AND lang_code = %s '.
1767  'AND NOT lang_default = %s';
1768 
1769  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
1770  $data['obj_id'],
1771  $this->lang_code,
1772  1));
1773  $row = $ilDB->fetchObject($res);
1774 
1775  if ($row)
1776  {
1777  $data["title"] = $row->title;
1778  $data["description"] = ilUtil::shortenText($row->description,MAXLENGTH_OBJ_DESC,true);
1779  $data["desc"] = $row->description;
1780  }
1781  //$ilBench->stop("Tree", "fetchNodeData_getTranslation");
1782 
1783  // Store up to 1000 object translations in cache
1784  if ($this->isCacheUsed() && count($this->translation_cache) < 1000)
1785  {
1786  $key = $data["obj_id"].'.'.$lang_code;
1787  $this->translation_cache[$key] = array();
1788  $this->translation_cache[$key]['title'] = $data["title"] ;
1789  $this->translation_cache[$key]['description'] = $data["description"];
1790  $this->translation_cache[$key]['desc'] = $data["desc"];
1791  }
1792  }
1793  }
1794 
1795  // TODO: Handle this switch by module.xml definitions
1796  if($data['type'] == 'crsr' or $data['type'] == 'catr')
1797  {
1798  include_once('./Services/ContainerReference/classes/class.ilContainerReference.php');
1800  }
1801 
1802  return $data ? $data : array();
1803  }
1804 
1805 
1813  function isInTree($a_node_id)
1814  {
1815  global $ilDB;
1816 
1817  if (!isset($a_node_id))
1818  {
1819  return false;
1820  #$this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1821  }
1822 
1823  // is in tree cache
1824  if ($this->isCacheUsed() && isset($this->in_tree_cache[$a_node_id]))
1825  {
1826  #$GLOBALS['ilLog']->write(__METHOD__.': Using in tree cache '.$a_node_id);
1827 //echo "<br>in_tree_hit";
1828  return $this->in_tree_cache[$a_node_id];
1829  }
1830 
1831  $query = 'SELECT * FROM '.$this->table_tree.' '.
1832  'WHERE '.$this->table_tree.'.child = %s '.
1833  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s';
1834 
1835  $res = $ilDB->queryF($query,array('integer','integer'),array(
1836  $a_node_id,
1837  $this->tree_id));
1838 
1839  if ($res->numRows() > 0)
1840  {
1841  if($this->__isMainTree())
1842  {
1843  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
1844  $this->in_tree_cache[$a_node_id] = true;
1845  }
1846  return true;
1847  }
1848  else
1849  {
1850  if($this->__isMainTree())
1851  {
1852  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = false');
1853  $this->in_tree_cache[$a_node_id] = false;
1854  }
1855  return false;
1856  }
1857  }
1858 
1865  function getParentNodeData($a_node_id)
1866  {
1867  global $ilDB;
1868  global $ilLog;
1869 
1870  if (!isset($a_node_id))
1871  {
1872  $ilLog->logStack();
1873  $this->ilErr->raiseError(get_class($this)."::getParentNodeData(): No node_id given! ",$this->ilErr->WARNING);
1874  }
1875 
1876  if ($this->table_obj_reference)
1877  {
1878  // Use inner join instead of left join to improve performance
1879  $innerjoin = "JOIN ".$this->table_obj_reference." ON v.child=".$this->table_obj_reference.".".$this->ref_pk." ".
1880  "JOIN ".$this->table_obj_data." ON ".$this->table_obj_reference.".".$this->obj_pk."=".$this->table_obj_data.".".$this->obj_pk." ";
1881  }
1882  else
1883  {
1884  // Use inner join instead of left join to improve performance
1885  $innerjoin = "JOIN ".$this->table_obj_data." ON v.child=".$this->table_obj_data.".".$this->obj_pk." ";
1886  }
1887 
1888  $query = 'SELECT * FROM '.$this->table_tree.' s, '.$this->table_tree.' v '.
1889  $innerjoin.
1890  'WHERE s.child = %s '.
1891  'AND s.parent = v.child '.
1892  'AND s.lft > v.lft '.
1893  'AND s.rgt < v.rgt '.
1894  'AND s.'.$this->tree_pk.' = %s '.
1895  'AND v.'.$this->tree_pk.' = %s';
1896  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
1897  $a_node_id,
1898  $this->tree_id,
1899  $this->tree_id));
1900  $row = $ilDB->fetchAssoc($res);
1901  return $this->fetchNodeData($row);
1902  }
1903 
1911  function isGrandChild($a_startnode_id,$a_querynode_id)
1912  {
1913  global $ilDB;
1914 
1915  if (!isset($a_startnode_id) or !isset($a_querynode_id))
1916  {
1917  return false;
1918  }
1919 
1920  $query = 'SELECT * FROM '.$this->table_tree.' s, '.$this->table_tree.' v '.
1921  'WHERE s.child = %s '.
1922  'AND v.child = %s '.
1923  'AND s.'.$this->tree_pk.' = %s '.
1924  'AND v.'.$this->tree_pk.' = %s '.
1925  'AND v.lft BETWEEN s.lft AND s.rgt '.
1926  'AND v.rgt BETWEEN s.lft AND s.rgt';
1927  $res = $ilDB->queryF(
1928  $query,
1929  array('integer','integer','integer','integer'),
1930  array(
1931  $a_startnode_id,
1932  $a_querynode_id,
1933  $this->tree_id,
1934  $this->tree_id));
1935 
1936  return $res->numRows();
1937  }
1938 
1947  function addTree($a_tree_id,$a_node_id = -1)
1948  {
1949  global $ilDB;
1950 
1951  // FOR SECURITY addTree() IS NOT ALLOWED ON MAIN TREE
1952  if($this->__isMainTree())
1953  {
1954  $message = sprintf('%s::addTree(): Operation not allowed on main tree! $a_tree_if: %s $a_node_id: %s',
1955  get_class($this),
1956  $a_tree_id,
1957  $a_node_id);
1958  $this->log->write($message,$this->log->FATAL);
1959  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1960  }
1961 
1962  if (!isset($a_tree_id))
1963  {
1964  $this->ilErr->raiseError(get_class($this)."::addTree(): No tree_id given! ",$this->ilErr->WARNING);
1965  }
1966 
1967  if ($a_node_id <= 0)
1968  {
1969  $a_node_id = $a_tree_id;
1970  }
1971 
1972  $query = 'INSERT INTO '.$this->table_tree.' ('.
1973  $this->tree_pk.', child,parent,lft,rgt,depth) '.
1974  'VALUES '.
1975  '(%s,%s,%s,%s,%s,%s)';
1976  $res = $ilDB->manipulateF($query,array('integer','integer','integer','integer','integer','integer'),array(
1977  $a_tree_id,
1978  $a_node_id,
1979  0,
1980  1,
1981  2,
1982  1));
1983 
1984  return true;
1985  }
1986 
1994  function getNodeDataByType($a_type)
1995  {
1996  global $ilDB;
1997 
1998  if (!isset($a_type) or (!is_string($a_type)))
1999  {
2000  $this->ilErr->raiseError(get_class($this)."::getNodeDataByType(): Type not given or wrong datatype!",$this->ilErr->WARNING);
2001  }
2002 
2003  $data = array(); // node_data
2004  $row = ""; // fetched row
2005  $left = ""; // tree_left
2006  $right = ""; // tree_right
2007 
2008  $query = 'SELECT * FROM '.$this->table_tree.' '.
2009  'WHERE '.$this->tree_pk.' = %s '.
2010  'AND parent = %s ';
2011  $res = $ilDB->queryF($query,array('integer','integer'),array(
2012  $this->tree_id,
2013  0));
2014 
2015  while ($row = $ilDB->fetchObject($res))
2016  {
2017  $left = $row->lft;
2018  $right = $row->rgt;
2019  }
2020 
2021  $query = 'SELECT * FROM '.$this->table_tree.' '.
2022  $this->buildJoin().
2023  'WHERE '.$this->table_obj_data.'.type = %s '.
2024  'AND '.$this->table_tree.'.lft BETWEEN %s AND %s '.
2025  'AND '.$this->table_tree.'.rgt BETWEEN %s AND %s '.
2026  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2027  $res = $ilDB->queryF($query,array('text','integer','integer','integer','integer','integer'),array(
2028  $a_type,
2029  $left,
2030  $right,
2031  $left,
2032  $right,
2033  $this->tree_id));
2034 
2035  while($row = $ilDB->fetchAssoc($res))
2036  {
2037  $data[] = $this->fetchNodeData($row);
2038  }
2039 
2040  return $data;
2041  }
2042 
2050  function removeTree($a_tree_id)
2051  {
2052  global $ilDB;
2053 
2054  // OPERATION NOT ALLOWED ON MAIN TREE
2055  if($this->__isMainTree())
2056  {
2057  $message = sprintf('%s::removeTree(): Operation not allowed on main tree! $a_tree_if: %s',
2058  get_class($this),
2059  $a_tree_id);
2060  $this->log->write($message,$this->log->FATAL);
2061  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2062  }
2063  if (!$a_tree_id)
2064  {
2065  $this->ilErr->raiseError(get_class($this)."::removeTree(): No tree_id given! Action aborted",$this->ilErr->MESSAGE);
2066  }
2067 
2068  $query = 'DELETE FROM '.$this->table_tree.
2069  ' WHERE '.$this->tree_pk.' = %s ';
2070  $res = $ilDB->manipulateF($query,array('integer'),array($a_tree_id));
2071  return true;
2072  }
2073 
2081  function saveSubTree($a_node_id, $a_set_deleted = false)
2082  {
2083  global $ilDB;
2084 
2085  if (!$a_node_id)
2086  {
2087  $message = sprintf('%s::saveSubTree(): No valid parameter given! $a_node_id: %s',
2088  get_class($this),
2089  $a_node_id);
2090  $this->log->write($message,$this->log->FATAL);
2091  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2092  }
2093 
2094  // LOCKED ###############################################
2095  if($this->__isMainTree())
2096  {
2097  $ilDB->lockTables(
2098  array(
2099  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE),
2100  1 => array('name' => 'object_reference', 'type' => ilDB::LOCK_WRITE)));
2101 
2102  #ilDB::_lockTables(array('tree' => 'WRITE',
2103  # 'object_reference' => 'WRITE'));
2104 
2105  }
2106 
2107  // GET LEFT AND RIGHT VALUE
2108  $query = 'SELECT * FROM '.$this->table_tree.' '.
2109  'WHERE '.$this->tree_pk.' = %s '.
2110  'AND child = %s ';
2111  $res = $ilDB->queryF($query,array('integer','integer'),array(
2112  $this->tree_id,
2113  $a_node_id));
2114 
2115  while($row = $ilDB->fetchObject($res))
2116  {
2117  $lft = $row->lft;
2118  $rgt = $row->rgt;
2119  }
2120 
2121  // GET ALL SUBNODES
2122  $query = 'SELECT child FROM '.$this->table_tree.' '.
2123  'WHERE '.$this->tree_pk.' = %s '.
2124  'AND lft BETWEEN %s AND %s ';
2125  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2126  $this->tree_id,
2127  $lft,
2128  $rgt));
2129 
2130  $subnodes = array();
2131  while($row = $ilDB->fetchAssoc($res))
2132  {
2133  $subnodes[] = $row['child'];
2134  }
2135 
2136  if(!count($subnodes))
2137  {
2138  // possibly already deleted
2139 
2140  // Unlock locked tables before returning
2141  if($this->__isMainTree())
2142  {
2143  $ilDB->unlockTables();
2144  }
2145 
2146  return false;
2147  }
2148 
2149  // SAVE SUBTREE
2150  foreach($subnodes as $child)
2151  {
2152  // set node as deleted
2153  if ($a_set_deleted)
2154  {
2155  // TODO: new method that expects an array of ids
2156  ilObject::_setDeletedDate($child);
2157  }
2158  }
2159 
2160  // Set the nodes deleted (negative tree id)
2161  $query = 'UPDATE '.$this->table_tree.' '.
2162  'SET tree = %s '.
2163  'WHERE '.$this->tree_pk.' = %s '.
2164  'AND lft BETWEEN %s AND %s ';
2165  $res = $ilDB->manipulateF($query,array('integer','integer','integer','integer'),array(
2166  -$a_node_id,
2167  $this->tree_id,
2168  $lft,
2169  $rgt));
2170 
2171  if($this->__isMainTree())
2172  {
2173  $ilDB->unlockTables();
2174  }
2175 
2176  // LOCKED ###############################################
2177  return true;
2178  }
2179 
2183  function isDeleted($a_node_id)
2184  {
2185  return $this->isSaved($a_node_id);
2186  }
2187 
2191  function isSaved($a_node_id)
2192  {
2193  global $ilDB;
2194 
2195  // is saved cache
2196  if ($this->isCacheUsed() && isset($this->is_saved_cache[$a_node_id]))
2197  {
2198 //echo "<br>issavedhit";
2199  return $this->is_saved_cache[$a_node_id];
2200  }
2201 
2202  $query = 'SELECT * FROM '.$this->table_tree.' '.
2203  'WHERE child = %s ';
2204  $res = $ilDB->queryF($query,array('integer'),array($a_node_id));
2205  $row = $ilDB->fetchAssoc($res);
2206 
2207  if ($row[$this->tree_pk] < 0)
2208  {
2209  if($this->__isMainTree())
2210  {
2211  $this->is_saved_cache[$a_node_id] = true;
2212  }
2213  return true;
2214  }
2215  else
2216  {
2217  if($this->__isMainTree())
2218  {
2219  $this->is_saved_cache[$a_node_id] = false;
2220  }
2221  return false;
2222  }
2223  }
2224 
2225 
2226 
2233  function getSavedNodeData($a_parent_id)
2234  {
2235  global $ilDB;
2236 
2237  if (!isset($a_parent_id))
2238  {
2239  $this->ilErr->raiseError(get_class($this)."::getSavedNodeData(): No node_id given!",$this->ilErr->WARNING);
2240  }
2241 
2242  $query = 'SELECT * FROM '.$this->table_tree.' '.
2243  $this->buildJoin().
2244  'WHERE '.$this->table_tree.'.'.$this->tree_pk.' < %s '.
2245  'AND '.$this->table_tree.'.parent = %s';
2246  $res = $ilDB->queryF($query,array('integer','integer'),array(
2247  0,
2248  $a_parent_id));
2249 
2250  while($row = $ilDB->fetchAssoc($res))
2251  {
2252  $saved[] = $this->fetchNodeData($row);
2253  }
2254 
2255  return $saved ? $saved : array();
2256  }
2257 
2264  function getParentId($a_node_id)
2265  {
2266  global $ilDB;
2267 
2268  if (!isset($a_node_id))
2269  {
2270  $this->ilErr->raiseError(get_class($this)."::getParentId(): No node_id given! ",$this->ilErr->WARNING);
2271  }
2272 
2273  $query = 'SELECT parent FROM '.$this->table_tree.' '.
2274  'WHERE child = %s '.
2275  'AND '.$this->tree_pk.' = %s ';
2276  $res = $ilDB->queryF($query,array('integer','integer'),array(
2277  $a_node_id,
2278  $this->tree_id));
2279 
2280  $row = $ilDB->fetchObject($res);
2281  return $row->parent;
2282  }
2283 
2290  function getLeftValue($a_node_id)
2291  {
2292  global $ilDB;
2293 
2294  if (!isset($a_node_id))
2295  {
2296  $this->ilErr->raiseError(get_class($this)."::getLeftValued(): No node_id given! ",$this->ilErr->WARNING);
2297  }
2298 
2299  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2300  'WHERE child = %s '.
2301  'AND '.$this->tree_pk.' = %s ';
2302  $res = $ilDB->queryF($query,array('integer','integer'),array(
2303  $a_node_id,
2304  $this->tree_id));
2305  $row = $ilDB->fetchObject($res);
2306  return $row->lft;
2307  }
2308 
2315  function getChildSequenceNumber($a_node, $type = "")
2316  {
2317  global $ilDB;
2318 
2319  if (!isset($a_node))
2320  {
2321  $this->ilErr->raiseError(get_class($this)."::getChildSequenceNumber(): No node_id given! ",$this->ilErr->WARNING);
2322  }
2323 
2324  if($type)
2325  {
2326  $query = 'SELECT count(*) cnt FROM '.$this->table_tree.' '.
2327  $this->buildJoin().
2328  'WHERE lft <= %s '.
2329  'AND type = %s '.
2330  'AND parent = %s '.
2331  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2332 
2333  $res = $ilDB->queryF($query,array('integer','text','integer','integer'),array(
2334  $a_node['lft'],
2335  $type,
2336  $a_node['parent'],
2337  $this->tree_id));
2338  }
2339  else
2340  {
2341  $query = 'SELECT count(*) cnt FROM '.$this->table_tree.' '.
2342  $this->buildJoin().
2343  'WHERE lft <= %s '.
2344  'AND parent = %s '.
2345  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2346 
2347  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2348  $a_node['lft'],
2349  $a_node['parent'],
2350  $this->tree_id));
2351 
2352  }
2353  $row = $ilDB->fetchAssoc($res);
2354  return $row["cnt"];
2355  }
2356 
2363  function readRootId()
2364  {
2365  global $ilDB;
2366 
2367  $query = 'SELECT child FROM '.$this->table_tree.' '.
2368  'WHERE parent = %s '.
2369  'AND '.$this->tree_pk.' = %s ';
2370  $res = $ilDB->queryF($query,array('integer','integer'),array(
2371  0,
2372  $this->tree_id));
2373  $row = $ilDB->fetchObject($res);
2374  $this->root_id = $row->child;
2375  return $this->root_id;
2376  }
2377 
2383  function getRootId()
2384  {
2385  return $this->root_id;
2386  }
2387  function setRootId($a_root_id)
2388  {
2389  $this->root_id = $a_root_id;
2390  }
2391 
2397  function getTreeId()
2398  {
2399  return $this->tree_id;
2400  }
2401 
2407  function setTreeId($a_tree_id)
2408  {
2409  $this->tree_id = $a_tree_id;
2410  }
2411 
2419  function fetchSuccessorNode($a_node_id, $a_type = "")
2420  {
2421  global $ilDB;
2422 
2423  if (!isset($a_node_id))
2424  {
2425  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
2426  }
2427 
2428  // get lft value for current node
2429  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2430  'WHERE '.$this->table_tree.'.child = %s '.
2431  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2432  $res = $ilDB->queryF($query,array('integer','integer'),array(
2433  $a_node_id,
2434  $this->tree_id));
2435 
2436  $curr_node = $ilDB->fetchAssoc($res);
2437 
2438  if($a_type)
2439  {
2440  $query = 'SELECT * FROM '.$this->table_tree.' '.
2441  $this->buildJoin().
2442  'WHERE lft > %s '.
2443  'AND '.$this->table_obj_data.'.type = %s '.
2444  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2445  'ORDER BY lft ';
2446  $ilDB->setLimit(1);
2447  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
2448  $curr_node['lft'],
2449  $a_type,
2450  $this->tree_id));
2451  }
2452  else
2453  {
2454  $query = 'SELECT * FROM '.$this->table_tree.' '.
2455  $this->buildJoin().
2456  'WHERE lft > %s '.
2457  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2458  'ORDER BY lft ';
2459  $ilDB->setLimit(1);
2460  $res = $ilDB->queryF($query,array('integer','integer'),array(
2461  $curr_node['lft'],
2462  $this->tree_id));
2463  }
2464 
2465  if ($res->numRows() < 1)
2466  {
2467  return false;
2468  }
2469  else
2470  {
2471  $row = $ilDB->fetchAssoc($res);
2472  return $this->fetchNodeData($row);
2473  }
2474  }
2475 
2483  function fetchPredecessorNode($a_node_id, $a_type = "")
2484  {
2485  global $ilDB;
2486 
2487  if (!isset($a_node_id))
2488  {
2489  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
2490  }
2491 
2492  // get lft value for current node
2493  $query = 'SELECT lft FROM '.$this->table_tree.' '.
2494  'WHERE '.$this->table_tree.'.child = %s '.
2495  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s ';
2496  $res = $ilDB->queryF($query,array('integer','integer'),array(
2497  $a_node_id,
2498  $this->tree_id));
2499 
2500  $curr_node = $ilDB->fetchAssoc($res);
2501 
2502  if($a_type)
2503  {
2504  $query = 'SELECT * FROM '.$this->table_tree.' '.
2505  $this->buildJoin().
2506  'WHERE lft < %s '.
2507  'AND '.$this->table_obj_data.'.type = %s '.
2508  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2509  'ORDER BY lft DESC';
2510  $ilDB->setLimit(1);
2511  $res = $ilDB->queryF($query,array('integer','text','integer'),array(
2512  $curr_node['lft'],
2513  $a_type,
2514  $this->tree_id));
2515  }
2516  else
2517  {
2518  $query = 'SELECT * FROM '.$this->table_tree.' '.
2519  $this->buildJoin().
2520  'WHERE lft < %s '.
2521  'AND '.$this->table_tree.'.'.$this->tree_pk.' = %s '.
2522  'ORDER BY lft DESC';
2523  $ilDB->setLimit(1);
2524  $res = $ilDB->queryF($query,array('integer','integer'),array(
2525  $curr_node['lft'],
2526  $this->tree_id));
2527  }
2528 
2529  if ($res->numRows() < 1)
2530  {
2531  return false;
2532  }
2533  else
2534  {
2535  $row = $ilDB->fetchAssoc($res);
2536  return $this->fetchNodeData($row);
2537  }
2538  }
2539 
2548  function renumber($node_id = 1, $i = 1)
2549  {
2550  global $ilDB;
2551 
2552  // LOCKED ###################################
2553  if($this->__isMainTree())
2554  {
2555  /*
2556  ilDB::_lockTables(array($this->table_tree => 'WRITE',
2557  $this->table_obj_data => 'WRITE',
2558  $this->table_obj_reference => 'WRITE',
2559  'object_translation' => 'WRITE',
2560  'object_data od' => 'WRITE',
2561  'container_reference cr' => 'WRITE'));
2562  */
2563  $ilDB->lockTables(
2564  array(
2565  0 => array('name' => $this->table_tree, 'type' => ilDB::LOCK_WRITE),
2566  1 => array('name' => $this->table_obj_data, 'type' => ilDB::LOCK_WRITE),
2567  2 => array('name' => $this->table_obj_reference, 'type' => ilDB::LOCK_WRITE),
2568  3 => array('name' => 'object_translation', 'type' => ilDB::LOCK_WRITE),
2569  4 => array('name' => 'object_data', 'type' => ilDB::LOCK_WRITE, 'alias' => 'od'),
2570  5 => array('name' => 'container_reference', 'type' => ilDB::LOCK_WRITE, 'alias' => 'cr')
2571  ));
2572  }
2573  $return = $this->__renumber($node_id,$i);
2574  if($this->__isMainTree())
2575  {
2576  $ilDB->unlockTables();
2577  }
2578  // LOCKED ###################################
2579  return $return;
2580  }
2581 
2582  // PRIVATE
2592  function __renumber($node_id = 1, $i = 1)
2593  {
2594  global $ilDB;
2595 
2596  $query = 'UPDATE '.$this->table_tree.' SET lft = %s WHERE child = %s';
2597  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2598  $i,
2599  $node_id));
2600 
2601  $childs = $this->getChilds($node_id);
2602 
2603  foreach ($childs as $child)
2604  {
2605  $i = $this->__renumber($child["child"],$i+1);
2606  }
2607  $i++;
2608 
2609  // Insert a gap at the end of node, if the node has children
2610  if (count($childs) > 0)
2611  {
2612  $i += $this->gap * 2;
2613  }
2614 
2615 
2616  $query = 'UPDATE '.$this->table_tree.' SET rgt = %s WHERE child = %s';
2617  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2618  $i,
2619  $node_id));
2620  return $i;
2621  }
2622 
2623 
2634  function checkForParentType($a_ref_id,$a_type)
2635  {
2636  // Try to return a cached result
2637  if ($this->isCacheUsed() &&
2638  array_key_exists($a_ref_id.'.'.$a_type, $this->parent_type_cache)) {
2639  return $this->parent_type_cache[$a_ref_id.'.'.$a_type];
2640  }
2641 
2642  if(!$this->isInTree($a_ref_id))
2643  {
2644  // Store up to 1000 results in cache
2645  if ($this->__isMainTree() && count($this->parent_type_cache) < 1000) {
2646  $this->parent_type_cache[$a_ref_id.'.'.$a_type] = false;
2647  }
2648  return false;
2649  }
2650  $path = array_reverse($this->getPathFull($a_ref_id));
2651 
2652  foreach($path as $node)
2653  {
2654  if($node["type"] == $a_type)
2655  {
2656  // Store up to 1000 results in cache
2657  if ($this->__isMainTree() && count($this->parent_type_cache) < 1000) {
2658  $this->parent_type_cache[$a_ref_id.'.'.$a_type] = $node["child"];
2659  }
2660  return $node["child"];
2661  }
2662  }
2663  // Store up to 1000 results in cache
2664  if ($this->__isMainTree() && count($this->parent_type_cache) < 1000) {
2665  $this->parent_type_cache[$a_ref_id.'.'.$a_type] = false;
2666  }
2667  return 0;
2668  }
2669 
2679  function _removeEntry($a_tree,$a_child,$a_db_table = "tree")
2680  {
2681  global $ilDB,$ilLog,$ilErr;
2682 
2683  if($a_db_table === 'tree')
2684  {
2685  if($a_tree == 1 and $a_child == ROOT_FOLDER_ID)
2686  {
2687  $message = sprintf('%s::_removeEntry(): Tried to delete root node! $a_tree: %s $a_child: %s',
2688  get_class($this),
2689  $a_tree,
2690  $a_child);
2691  $ilLog->write($message,$ilLog->FATAL);
2692  $ilErr->raiseError($message,$ilErr->WARNING);
2693  }
2694  }
2695 
2696  $query = 'DELETE FROM '.$a_db_table.' '.
2697  'WHERE tree = %s '.
2698  'AND child = %s ';
2699  $res = $ilDB->manipulateF($query,array('integer','integer'),array(
2700  $a_tree,
2701  $a_child));
2702 
2703  }
2704 
2705  // PRIVATE METHODS
2712  function __isMainTree()
2713  {
2714  return $this->table_tree === 'tree';
2715  }
2716 
2725  function __checkDelete($a_node)
2726  {
2727  global $ilDB;
2728 
2729  // get subtree by lft,rgt
2730  $query = 'SELECT * FROM '.$this->table_tree.' '.
2731  'WHERE lft >= %s '.
2732  'AND rgt <= %s '.
2733  'AND '.$this->tree_pk.' = %s ';
2734  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2735  $a_node['lft'],
2736  $a_node['rgt'],
2737  $a_node[$this->tree_pk]));
2738 
2739  $counter = (int) $lft_childs = array();
2740  while($row = $ilDB->fetchObject($res))
2741  {
2742  $lft_childs[$row->child] = $row->parent;
2743  ++$counter;
2744  }
2745 
2746  // CHECK FOR DUPLICATE CHILD IDS
2747  if($counter != count($lft_childs))
2748  {
2749  $message = sprintf('%s::__checkTree(): Duplicate entries for "child" in maintree! $a_node_id: %s',
2750  get_class($this),
2751  $a_node['child']);
2752  $this->log->write($message,$this->log->FATAL);
2753  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2754  }
2755 
2756  // GET SUBTREE BY PARENT RELATION
2757  $parent_childs = array();
2758  $this->__getSubTreeByParentRelation($a_node['child'],$parent_childs);
2759  $this->__validateSubtrees($lft_childs,$parent_childs);
2760 
2761  return true;
2762  }
2763 
2764  function __getSubTreeByParentRelation($a_node_id,&$parent_childs)
2765  {
2766  global $ilDB;
2767 
2768  // GET PARENT ID
2769  $query = 'SELECT * FROM '.$this->table_tree.' '.
2770  'WHERE child = %s '.
2771  'AND tree = %s ';
2772  $res = $ilDB->queryF($query,array('integer','integer'),array(
2773  $a_node_id,
2774  $this->tree_id));
2775 
2776  $counter = 0;
2777  while($row = $ilDB->fetchObject($res))
2778  {
2779  $parent_childs[$a_node_id] = $row->parent;
2780  ++$counter;
2781  }
2782  // MULTIPLE ENTRIES
2783  if($counter > 1)
2784  {
2785  $message = sprintf('%s::__getSubTreeByParentRelation(): Multiple entries in maintree! $a_node_id: %s',
2786  get_class($this),
2787  $a_node_id);
2788  $this->log->write($message,$this->log->FATAL);
2789  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2790  }
2791 
2792  // GET ALL CHILDS
2793  $query = 'SELECT * FROM '.$this->table_tree.' '.
2794  'WHERE parent = %s ';
2795  $res = $ilDB->queryF($query,array('integer'),array($a_node_id));
2796 
2797  while($row = $ilDB->fetchObject($res))
2798  {
2799  // RECURSION
2800  $this->__getSubTreeByParentRelation($row->child,$parent_childs);
2801  }
2802  return true;
2803  }
2804 
2805  function __validateSubtrees(&$lft_childs,$parent_childs)
2806  {
2807  // SORT BY KEY
2808  ksort($lft_childs);
2809  ksort($parent_childs);
2810 
2811  if(count($lft_childs) != count($parent_childs))
2812  {
2813  $message = sprintf('%s::__validateSubtrees(): (COUNT) Tree is corrupted! Left/Right subtree does not comply .'.
2814  'with parent relation',
2815  get_class($this));
2816  $this->log->write($message,$this->log->FATAL);
2817  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2818  }
2819 
2820  foreach($lft_childs as $key => $value)
2821  {
2822  if($parent_childs[$key] != $value)
2823  {
2824  $message = sprintf('%s::__validateSubtrees(): (COMPARE) Tree is corrupted! Left/Right subtree does not comply '.
2825  'with parent relation',
2826  get_class($this));
2827  $this->log->write($message,$this->log->FATAL);
2828  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2829  }
2830  if($key == ROOT_FOLDER_ID)
2831  {
2832  $message = sprintf('%s::__validateSubtrees(): (ROOT_FOLDER) Tree is corrupted! Tried to delete root folder',
2833  get_class($this));
2834  $this->log->write($message,$this->log->FATAL);
2835  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2836  }
2837  }
2838  return true;
2839  }
2840 
2850  public function moveTree($a_source_id,$a_target_id,$a_location = IL_LAST_NODE)
2851  {
2852  global $ilDB;
2853 
2854  if($this->__isMainTree())
2855  {
2856  #ilDB::_lockTables(array('tree' => 'WRITE'));
2857  $ilDB->lockTables(
2858  array(
2859  0 => array('name' => 'tree', 'type' => ilDB::LOCK_WRITE)));
2860 
2861  }
2862  // Receive node infos for source and target
2863  $query = 'SELECT * FROM '.$this->table_tree.' '.
2864  'WHERE ( child = %s OR child = %s ) '.
2865  'AND tree = %s ';
2866  $res = $ilDB->queryF($query,array('integer','integer','integer'),array(
2867  $a_source_id,
2868  $a_target_id,
2869  $this->tree_id));
2870 
2871  // Check in tree
2872  if($res->numRows() != 2)
2873  {
2874  if($this->__isMainTree())
2875  {
2876  $ilDB->unlockTables();
2877  }
2878  $this->log->write(__METHOD__.' Objects not found in tree!',$this->log->FATAL);
2879  $this->ilErr->raiseError('Error moving node',$this->ilErr->WARNING);
2880  }
2881  while($row = $ilDB->fetchObject($res))
2882  {
2883  if($row->child == $a_source_id)
2884  {
2885  $source_lft = $row->lft;
2886  $source_rgt = $row->rgt;
2887  $source_depth = $row->depth;
2888  $source_parent = $row->parent;
2889  }
2890  else
2891  {
2892  $target_lft = $row->lft;
2893  $target_rgt = $row->rgt;
2894  $target_depth = $row->depth;
2895  }
2896  }
2897 
2898  #var_dump("<pre>",$source_lft,$source_rgt,$source_depth,$target_lft,$target_rgt,$target_depth,"<pre>");
2899  // Check target not child of source
2900  if($target_lft >= $source_lft and $target_rgt <= $source_rgt)
2901  {
2902  if($this->__isMainTree())
2903  {
2904  $ilDB->unlockTables();
2905  }
2906  $this->log->write(__METHOD__.' Target is child of source',$this->log->FATAL);
2907  $this->ilErr->raiseError('Error moving node',$this->ilErr->WARNING);
2908  }
2909 
2910  // Now spread the tree at the target location. After this update the table should be still in a consistent state.
2911  // implementation for IL_LAST_NODE
2912  $spread_diff = $source_rgt - $source_lft + 1;
2913  #var_dump("<pre>","SPREAD_DIFF: ",$spread_diff,"<pre>");
2914 
2915  $query = 'UPDATE '.$this->table_tree.' SET '.
2916  'lft = CASE WHEN lft > %s THEN lft + %s ELSE lft END, '.
2917  'rgt = CASE WHEN rgt >= %s THEN rgt + %s ELSE rgt END '.
2918  'WHERE tree = %s ';
2919  $res = $ilDB->manipulateF($query,array('integer','integer','integer','integer','integer'),array(
2920  $target_rgt,
2921  $spread_diff,
2922  $target_rgt,
2923  $spread_diff,
2924  $this->tree_id));
2925 
2926  // Maybe the source node has been updated, too.
2927  // Check this:
2928  if($source_lft > $target_rgt)
2929  {
2930  $where_offset = $spread_diff;
2931  $move_diff = $target_rgt - $source_lft - $spread_diff;
2932  }
2933  else
2934  {
2935  $where_offset = 0;
2936  $move_diff = $target_rgt - $source_lft;
2937  }
2938  $depth_diff = $target_depth - $source_depth + 1;
2939 
2940 
2941  $query = 'UPDATE '.$this->table_tree.' SET '.
2942  'parent = CASE WHEN parent = %s THEN %s ELSE parent END, '.
2943  'rgt = rgt + %s, '.
2944  'lft = lft + %s, '.
2945  'depth = depth + %s '.
2946  'WHERE lft >= %s '.
2947  'AND rgt <= %s '.
2948  'AND tree = %s ';
2949  $res = $ilDB->manipulateF($query,
2950  array('integer','integer','integer','integer','integer','integer','integer','integer'),
2951  array(
2952  $source_parent,
2953  $a_target_id,
2954  $move_diff,
2955  $move_diff,
2956  $depth_diff,
2957  $source_lft + $where_offset,
2958  $source_rgt + $where_offset,
2959  $this->tree_id));
2960 
2961  // done: close old gap
2962  $query = 'UPDATE '.$this->table_tree.' SET '.
2963  'lft = CASE WHEN lft >= %s THEN lft - %s ELSE lft END, '.
2964  'rgt = CASE WHEN rgt >= %s THEN rgt - %s ELSE rgt END '.
2965  'WHERE tree = %s ';
2966 
2967  $res = $ilDB->manipulateF($query,
2968  array('integer','integer','integer','integer','integer'),
2969  array(
2970  $source_lft + $where_offset,
2971  $spread_diff,
2972  $source_rgt +$where_offset,
2973  $spread_diff,
2974  $this->tree_id));
2975 
2976  if($this->__isMainTree())
2977  {
2978  $ilDB->unlockTables();
2979  }
2980  return true;
2981  }
2982 
2990  public function getRbacSubtreeInfo($a_endnode_id)
2991  {
2992  global $ilDB;
2993 
2994  $query = "SELECT t2.lft lft, t2.rgt rgt, t2.child child, type ".
2995  "FROM ".$this->table_tree." t1 ".
2996  "JOIN ".$this->table_tree." t2 ON (t2.lft BETWEEN t1.lft AND t1.rgt) ".
2997  "JOIN ".$this->table_obj_reference." obr ON t2.child = obr.ref_id ".
2998  "JOIN ".$this->table_obj_data." obd ON obr.obj_id = obd.obj_id ".
2999  "WHERE t1.child = ".$ilDB->quote($a_endnode_id,'integer')." ".
3000  "AND t1.".$this->tree_pk." = ".$ilDB->quote($this->tree_id,'integer')." ".
3001  "AND t2.".$this->tree_pk." = ".$ilDB->quote($this->tree_id,'integer')." ".
3002  "ORDER BY t2.lft";
3003 
3004  $res = $ilDB->query($query);
3005  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
3006  {
3007  $nodes[$row->child]['lft'] = $row->lft;
3008  $nodes[$row->child]['rgt'] = $row->rgt;
3009  $nodes[$row->child]['child']= $row->child;
3010  $nodes[$row->child]['type'] = $row->type;
3011 
3012  }
3013  return (array) $nodes;
3014  }
3015 
3016 
3017 
3018 } // END class.tree
3019 ?>