ILIAS  Release_3_10_x_branch Revision 61812
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.ilTree.php
Go to the documentation of this file.
1 <?php
2 /*
3  +-----------------------------------------------------------------------------+
4  | ILIAS open source |
5  +-----------------------------------------------------------------------------+
6  | Copyright (c) 1998-2006 ILIAS open source, University of Cologne |
7  | |
8  | This program is free software; you can redistribute it and/or |
9  | modify it under the terms of the GNU General Public License |
10  | as published by the Free Software Foundation; either version 2 |
11  | of the License, or (at your option) any later version. |
12  | |
13  | This program is distributed in the hope that it will be useful, |
14  | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16  | GNU General Public License for more details. |
17  | |
18  | You should have received a copy of the GNU General Public License |
19  | along with this program; if not, write to the Free Software |
20  | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
21  +-----------------------------------------------------------------------------+
22 */
23 
24 define("IL_LAST_NODE", -2);
25 define("IL_FIRST_NODE", -1);
26 
41 class ilTree
42 {
48  var $ilias;
49 
50 
56  var $log;
57 
63  var $root_id;
64 
70  var $tree_id;
71 
78 
85 
92 
98  var $ref_pk;
99 
105  var $obj_pk;
106 
112  var $tree_pk;
113 
138  var $gap;
139 
146  function ilTree($a_tree_id, $a_root_id = 0)
147  {
148  global $ilDB,$ilErr,$ilias,$ilLog;
149 
150  // set db & error handler
151  (isset($ilDB)) ? $this->ilDB =& $ilDB : $this->ilDB =& $ilias->db;
152 
153  if (!isset($ilErr))
154  {
155  $ilErr = new ilErrorHandling();
156  $ilErr->setErrorHandling(PEAR_ERROR_CALLBACK,array($ilErr,'errorHandler'));
157  }
158  else
159  {
160  $this->ilErr =& $ilErr;
161  }
162 
163  $this->lang_code = "en";
164 
165  if (!isset($a_tree_id) or (func_num_args() == 0) )
166  {
167  $this->ilErr->raiseError(get_class($this)."::Constructor(): No tree_id given!",$this->ilErr->WARNING);
168  }
169 
170  if (func_num_args() > 2)
171  {
172  $this->ilErr->raiseError(get_class($this)."::Constructor(): Wrong parameter count!",$this->ilErr->WARNING);
173  }
174 
175  // CREATE LOGGER INSTANCE
176  $this->log =& $ilLog;
177 
178  //init variables
179  if (empty($a_root_id))
180  {
181  $a_root_id = ROOT_FOLDER_ID;
182  }
183 
184  $this->tree_id = $a_tree_id;
185  $this->root_id = $a_root_id;
186  $this->table_tree = 'tree';
187  $this->table_obj_data = 'object_data';
188  $this->table_obj_reference = 'object_reference';
189  $this->ref_pk = 'ref_id';
190  $this->obj_pk = 'obj_id';
191  $this->tree_pk = 'tree';
192 
193  $this->use_cache = true;
194 
195  // If cache is activated, cache object translations to improve performance
196  $this->translation_cache = array();
197  $this->parent_type_cache = array();
198 
199  // By default, we create gaps in the tree sequence numbering for 50 nodes
200  $this->gap = 50;
201  }
202 
206  public function useCache($a_use = true)
207  {
208  $this->use_cache = $a_use;
209  }
210 
215  public function isCacheUsed()
216  {
217  return $this->__isMainTree() and $this->use_cache;
218  }
219 
224  function initLangCode()
225  {
226  global $ilUser;
227 
228  // lang_code is only required in $this->fetchnodedata
229  if (!is_object($ilUser))
230  {
231  $this->lang_code = "en";
232  }
233  else
234  {
235  $this->lang_code = $ilUser->getCurrentLanguage();
236  }
237  }
238 
239 
254  function setTableNames($a_table_tree,$a_table_obj_data,$a_table_obj_reference = "")
255  {
256  if (!isset($a_table_tree) or !isset($a_table_obj_data))
257  {
258  $this->ilErr->raiseError(get_class($this)."::setTableNames(): Missing parameter! ".
259  "tree table: ".$a_table_tree." object data table: ".$a_table_obj_data,$this->ilErr->WARNING);
260  }
261 
262  $this->table_tree = $a_table_tree;
263  $this->table_obj_data = $a_table_obj_data;
264  $this->table_obj_reference = $a_table_obj_reference;
265 
266  return true;
267  }
268 
275  function setReferenceTablePK($a_column_name)
276  {
277  if (!isset($a_column_name))
278  {
279  $this->ilErr->raiseError(get_class($this)."::setReferenceTablePK(): No column name given!",$this->ilErr->WARNING);
280  }
281 
282  $this->ref_pk = $a_column_name;
283  return true;
284  }
285 
292  function setObjectTablePK($a_column_name)
293  {
294  if (!isset($a_column_name))
295  {
296  $this->ilErr->raiseError(get_class($this)."::setObjectTablePK(): No column name given!",$this->ilErr->WARNING);
297  }
298 
299  $this->obj_pk = $a_column_name;
300  return true;
301  }
302 
309  function setTreeTablePK($a_column_name)
310  {
311  if (!isset($a_column_name))
312  {
313  $this->ilErr->raiseError(get_class($this)."::setTreeTablePK(): No column name given!",$this->ilErr->WARNING);
314  }
315 
316  $this->tree_pk = $a_column_name;
317  return true;
318  }
319 
325  function buildJoin()
326  {
327  if ($this->table_obj_reference)
328  {
329  // Use inner join instead of left join to improve performance
330  return "JOIN ".$this->table_obj_reference." ON ".$this->table_tree.".child=".$this->table_obj_reference.".".$this->ref_pk." ".
331  "JOIN ".$this->table_obj_data." ON ".$this->table_obj_reference.".".$this->obj_pk."=".$this->table_obj_data.".".$this->obj_pk." ";
332  }
333  else
334  {
335  // Use inner join instead of left join to improve performance
336  return "JOIN ".$this->table_obj_data." ON ".$this->table_tree.".child=".$this->table_obj_data.".".$this->obj_pk." ";
337  }
338  }
339 
348  function getChilds($a_node_id, $a_order = "", $a_direction = "ASC")
349  {
350  global $ilBench;
351 
352  if (!isset($a_node_id))
353  {
354  $message = get_class($this)."::getChilds(): No node_id given!";
355  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
356  }
357 
358  // init childs
359  $childs = array();
360 
361  // number of childs
362  $count = 0;
363 
364  // init order_clause
365  $order_clause = "";
366 
367  // set order_clause if sort order parameter is given
368  if (!empty($a_order))
369  {
370  $order_clause = "ORDER BY ".$a_order." ".$a_direction;
371  }
372  else
373  {
374  $order_clause = "ORDER BY ".$this->table_tree.".lft";
375  }
376 
377  //666
378  $q = "SELECT * FROM ".$this->table_tree." ".
379  $this->buildJoin().
380  "WHERE parent = ".$this->ilDB->quote($a_node_id)." ".
381  "AND ".$this->table_tree.".".$this->tree_pk." = ".$this->ilDB->quote($this->tree_id)." ".
382  $order_clause;
383 
384  //$ilBench->start("Tree", "getChilds_Query");
385  $r = $this->ilDB->query($q);
386  //$ilBench->stop("Tree", "getChilds_Query");
387 
388  $count = $r->numRows();
389 
390 
391  if ($count > 0)
392  {
393  //$ilBench->start("Tree", "getChilds_fetchNodeData");
394  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
395  {
396  $childs[] = $this->fetchNodeData($row);
397 
398  // Update cache of main tree
399  if ($this->__isMainTree())
400  {
401  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child'].' = true');
402  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
403  }
404  }
405  //$ilBench->stop("Tree", "getChilds_fetchNodeData");
406 
407  // mark the last child node (important for display)
408  $childs[$count - 1]["last"] = true;
409  return $childs;
410  }
411  else
412  {
413  return $childs;
414  }
415  }
416 
426  function getFilteredChilds($a_filter,$a_node,$a_order = "",$a_direction = "ASC")
427  {
428  $childs = $this->getChilds($a_node,$a_order,$a_direction);
429 
430  foreach($childs as $child)
431  {
432  if(!in_array($child["type"],$a_filter))
433  {
434  $filtered[] = $child;
435  }
436  }
437  return $filtered ? $filtered : array();
438  }
439 
440 
448  function getChildsByType($a_node_id,$a_type)
449  {
450  if (!isset($a_node_id) or !isset($a_type))
451  {
452  $message = get_class($this)."::getChildsByType(): Missing parameter! node_id:".$a_node_id." type:".$a_type;
453  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
454  }
455 
456  // init childs
457  $childs = array();
458 
459  if ($a_type=='rolf' && $this->table_obj_reference) {
460  // Performance optimization: A node can only have exactly one
461  // role folder as its child. Therefore we don't need to sort the
462  // results, and we can let the database know about the expected limit.
463  $q = "SELECT * FROM ".$this->table_tree." ".
464  $this->buildJoin().
465  "WHERE parent = '".$a_node_id."' ".
466  "AND ".$this->table_tree.".".$this->tree_pk." = '".$this->tree_id."' ".
467  "AND ".$this->table_obj_data.".type='".$a_type."' ".
468  "LIMIT 1";
469  } else {
470  $q = "SELECT * FROM ".$this->table_tree." ".
471  $this->buildJoin().
472  "WHERE parent = '".$a_node_id."' ".
473  "AND ".$this->table_tree.".".$this->tree_pk." = '".$this->tree_id."' ".
474  "AND ".$this->table_obj_data.".type='".$a_type."' ".
475  "ORDER BY ".$this->table_tree.".lft";
476  }
477  $r = $this->ilDB->query($q);
478 
479  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
480  {
481  $childs[] = $this->fetchNodeData($row);
482  }
483 
484 
485  return $childs;
486  }
487 
488 
496  public function getChildsByTypeFilter($a_node_id,$a_types)
497  {
498  if (!isset($a_node_id) or !isset($a_types))
499  {
500  $message = get_class($this)."::getChildsByType(): Missing parameter! node_id:".$a_node_id." type:".$a_types;
501  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
502  }
503 
504  // init childs
505  $childs = array();
506 
507  $q = "SELECT * FROM ".$this->table_tree." ".
508  $this->buildJoin().
509  "WHERE parent = '".$a_node_id."' ".
510  "AND ".$this->table_tree.".".$this->tree_pk." = '".$this->tree_id."' ".
511  $this->buildTypeFilter($this->table_obj_data.".type", $a_types)." ".
512  "ORDER BY ".$this->table_tree.".lft";
513  $r = $this->ilDB->query($q);
514 
515  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
516  {
517  $childs[] = $this->fetchNodeData($row);
518  }
519 
520 
521  return $childs;
522  }
523 
531  function insertNode($a_node_id, $a_parent_id, $a_pos = IL_LAST_NODE, $a_reset_deletion_date = false)
532  {
533 //echo "+$a_node_id+$a_parent_id+";
534  // CHECK node_id and parent_id > 0 if in main tree
535  if($this->__isMainTree())
536  {
537  if($a_node_id <= 1 or $a_parent_id <= 0)
538  {
539  $message = sprintf('%s::insertNode(): Invalid parameters! $a_node_id: %s $a_parent_id: %s',
540  get_class($this),
541  $a_node_id,
542  $a_parent_id);
543  $this->log->write($message,$this->log->FATAL);
544  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
545  }
546  }
547 
548 
549  if (!isset($a_node_id) or !isset($a_parent_id))
550  {
551  $this->ilErr->raiseError(get_class($this)."::insertNode(): Missing parameter! ".
552  "node_id: ".$a_node_id." parent_id: ".$a_parent_id,$this->ilErr->WARNING);
553  }
554  if ($this->isInTree($a_node_id))
555  {
556  $this->ilErr->raiseError(get_class($this)."::insertNode(): Node ".$a_node_id." already in tree ".
557  $this->table_tree."!",$this->ilErr->WARNING);
558  }
559 
560  //
561  switch ($a_pos)
562  {
563  case IL_FIRST_NODE:
564 
565  if($this->__isMainTree())
566  {
567  ilDBx::_lockTables(array('tree' => 'WRITE'));
568  }
569 
570  // get left value of parent
571  $q = "SELECT * FROM ".$this->table_tree." ".
572  "WHERE child = '".$a_parent_id."' ".
573  "AND ".$this->tree_pk." = '".$this->tree_id."'";
574  $res = $this->ilDB->query($q);
575  $r = $res->fetchRow(DB_FETCHMODE_OBJECT);
576 
577  if ($r->parent == NULL)
578  {
579  if($this->__isMainTree())
580  {
582  }
583  $this->ilErr->raiseError(get_class($this)."::insertNode(): Parent with ID ".$a_parent_id." not found in ".
584  $this->table_tree."!",$this->ilErr->WARNING);
585  }
586 
587  $left = $r->lft;
588  $lft = $left + 1;
589  $rgt = $left + 2;
590 
591  // spread tree
592  $q = "UPDATE ".$this->table_tree." SET ".
593  "lft = CASE ".
594  "WHEN lft > ".$left." ".
595  "THEN lft + 2 ".
596  "ELSE lft ".
597  "END, ".
598  "rgt = CASE ".
599  "WHEN rgt > ".$left." ".
600  "THEN rgt + 2 ".
601  "ELSE rgt ".
602  "END ".
603  "WHERE ".$this->tree_pk." = '".$this->tree_id."'";
604  $this->ilDB->query($q);
605  break;
606 
607  case IL_LAST_NODE:
608  // Special treatment for trees with gaps
609  if ($this->gap > 0)
610  {
611  if($this->__isMainTree())
612  {
613  ilDBx::_lockTables(array('tree' => 'WRITE'));
614  }
615 
616  // get lft and rgt value of parent
617  $q = 'SELECT rgt,lft,parent FROM '.$this->table_tree.' '.
618  'WHERE child = '.$a_parent_id.' '.
619  'AND '.$this->tree_pk.' = '.$this->tree_id;
620  $res = $this->ilDB->query($q);
621  $r = $res->fetchRow(DB_FETCHMODE_ASSOC);
622 
623 
624  if ($r['parent'] == NULL)
625  {
626  if($this->__isMainTree())
627  {
629  }
630  $this->ilErr->raiseError(get_class($this)."::insertNode(): Parent with ID ".
631  $a_parent_id." not found in ".$this->table_tree."!",$this->ilErr->WARNING);
632  }
633  $parentRgt = $r['rgt'];
634  $parentLft = $r['lft'];
635 
636  // Get the available space, without taking children into account yet
637  $availableSpace = $parentRgt - $parentLft;
638  if ($availableSpace < 2)
639  {
640  // If there is not enough space between parent lft and rgt, we don't need
641  // to look any further, because we must spread the tree.
642  $lft = $parentRgt;
643  }
644  else
645  {
646  // If there is space between parent lft and rgt, we need to check
647  // whether there is space left between the rightmost child of the
648  // parent and parent rgt.
649  $q = 'SELECT MAX(rgt) AS max_rgt FROM '.$this->table_tree.' '.
650  'WHERE parent = '.$a_parent_id.' '.
651  'AND '.$this->tree_pk.' = '.$this->tree_id;
652  $res = $this->ilDB->query($q);
653  $r = $res->fetchRow(DB_FETCHMODE_ASSOC);
654  if (isset($r['max_rgt']))
655  {
656  // If the parent has children, we compute the available space
657  // between rgt of the rightmost child and parent rgt.
658  $availableSpace = $parentRgt - $r['max_rgt'];
659  $lft = $r['max_rgt'] + 1;
660  }
661  else
662  {
663  // If the parent has no children, we know now, that we can
664  // add the new node at parent lft + 1 without having to spread
665  // the tree.
666  $lft = $parentLft + 1;
667  }
668  }
669  $rgt = $lft + 1;
670 
671 
672  // spread tree if there is not enough space to insert the new node
673  if ($availableSpace < 2)
674  {
675  //$this->log->write('ilTree.insertNode('.$a_node_id.','.$a_parent_id.') creating gap at '.$a_parent_id.' '.$parentLft.'..'.$parentRgt.'+'.(2 + $this->gap * 2));
676  $q = "UPDATE ".$this->table_tree." SET ".
677  "lft = CASE ".
678  "WHEN lft > ".$parentRgt." ".
679  "THEN lft + ".(2 + $this->gap * 2).' '.
680  "ELSE lft ".
681  "END, ".
682  "rgt = CASE ".
683  "WHEN rgt >= ".$parentRgt." ".
684  "THEN rgt + ".(2 + $this->gap * 2).' '.
685  "ELSE rgt ".
686  "END ".
687  "WHERE ".$this->tree_pk." = '".$this->tree_id."'";
688  $this->ilDB->query($q);
689  }
690  else
691  {
692  //$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);
693  }
694  }
695  // Treatment for trees without gaps
696  else
697  {
698  if($this->__isMainTree())
699  {
700  ilDBx::_lockTables(array('tree' => 'WRITE'));
701  }
702 
703  // get right value of parent
704  $q = "SELECT * FROM ".$this->table_tree." ".
705  "WHERE child = '".$a_parent_id."' ".
706  "AND ".$this->tree_pk." = '".$this->tree_id."'";
707  $res = $this->ilDB->query($q);
708  $r = $res->fetchRow(DB_FETCHMODE_OBJECT);
709 
710  if ($r->parent == NULL)
711  {
712  if($this->__isMainTree())
713  {
715  }
716  $this->ilErr->raiseError(get_class($this)."::insertNode(): Parent with ID ".
717  $a_parent_id." not found in ".$this->table_tree."!",$this->ilErr->WARNING);
718  }
719 
720  $right = $r->rgt;
721  $lft = $right;
722  $rgt = $right + 1;
723 
724  // spread tree
725  $q = "UPDATE ".$this->table_tree." SET ".
726  "lft = CASE ".
727  "WHEN lft > ".$right." ".
728  "THEN lft + 2 ".
729  "ELSE lft ".
730  "END, ".
731  "rgt = CASE ".
732  "WHEN rgt >= ".$right." ".
733  "THEN rgt + 2 ".
734  "ELSE rgt ".
735  "END ".
736  "WHERE ".$this->tree_pk." = '".$this->tree_id."'";
737  $this->ilDB->query($q);
738  }
739 
740  break;
741 
742  default:
743 
744  // this code shouldn't be executed
745  if($this->__isMainTree())
746  {
747  ilDBx::_lockTables(array('tree' => 'WRITE'));
748  }
749 
750  // get right value of preceeding child
751  $q = "SELECT * FROM ".$this->table_tree." ".
752  "WHERE child = '".$a_pos."' ".
753  "AND ".$this->tree_pk." = '".$this->tree_id."'";
754  $res = $this->ilDB->query($q);
755  $r = $res->fetchRow(DB_FETCHMODE_OBJECT);
756 
757  // crosscheck parents of sibling and new node (must be identical)
758  if ($r->parent != $a_parent_id)
759  {
760  if($this->__isMainTree())
761  {
763  }
764  $this->ilErr->raiseError(get_class($this)."::insertNode(): Parents mismatch! ".
765  "new node parent: ".$a_parent_id." sibling parent: ".$r->parent,$this->ilErr->WARNING);
766  }
767 
768  $right = $r->rgt;
769  $lft = $right + 1;
770  $rgt = $right + 2;
771 
772  // update lft/rgt values
773  $q = "UPDATE ".$this->table_tree." SET ".
774  "lft = CASE ".
775  "WHEN lft > ".$right." ".
776  "THEN lft + 2 ".
777  "ELSE lft ".
778  "END, ".
779  "rgt = CASE ".
780  "WHEN rgt > ".$right." ".
781  "THEN rgt + 2 ".
782  "ELSE rgt ".
783  "END ".
784  "WHERE ".$this->tree_pk." = '".$this->tree_id."'";
785  $this->ilDB->query($q);
786  break;
787 
788  }
789 
790  // get depth
791  $depth = $this->getDepth($a_parent_id) + 1;
792 
793  // insert node
794  //$this->log->write('ilTree.insertNode('.$a_node_id.','.$a_parent_id.') inserting node:'.$a_node_id.' parent:'.$a_parent_id." ".$lft."..".$rgt." depth:".$depth);
795  $q = "INSERT INTO ".$this->table_tree." (".$this->tree_pk.",child,parent,lft,rgt,depth) ".
796  "VALUES ".
797  "('".$this->tree_id."','".$a_node_id."','".$a_parent_id."','".$lft."','".$rgt."','".$depth."')";
798 
799  $this->ilDB->query($q);
800 
801  // Finally unlock tables and update cache
802  if($this->__isMainTree())
803  {
804  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
805  $this->in_tree_cache[$a_node_id] = true;
807  }
808 
809  // reset deletion date
810  if ($a_reset_deletion_date)
811  {
812  ilObject::_resetDeletedDate($a_node_id);
813  }
814  }
815 
828  public function getFilteredSubTree($a_node_id,$a_filter = array())
829  {
830  $node = $this->getNodeData($a_node_id);
831 
832  $first = true;
833  $depth = 0;
834  foreach($this->getSubTree($node) as $subnode)
835  {
836  if($depth and $subnode['depth'] > $depth)
837  {
838  continue;
839  }
840  if(!$first and in_array($subnode['type'],$a_filter))
841  {
842  $depth = $subnode['depth'];
843  $first = false;
844  continue;
845  }
846  $depth = 0;
847  $first = false;
848  $filtered[] = $subnode;
849  }
850  return $filtered ? $filtered : array();
851  }
852 
860  public function getRbacSubtreeInfo($a_endnode_id)
861  {
862  global $ilDB;
863 
864  $query = "SELECT t2.lft lft, t2.rgt rgt, t2.child child, type ".
865  "FROM ".$this->table_tree." t1 ".
866  "JOIN ".$this->table_tree." t2 ON (t2.lft BETWEEN t1.lft AND t1.rgt) ".
867  "JOIN ".$this->table_obj_reference." obr ON t2.child = obr.ref_id ".
868  "JOIN ".$this->table_obj_data." obd ON obr.obj_id = obd.obj_id ".
869  "WHERE t1.child = ".$ilDB->quote($a_endnode_id)." ".
870  "AND t1.".$this->tree_pk." = ".$ilDB->quote($this->tree_id)." ".
871  "AND t2.".$this->tree_pk." = ".$ilDB->quote($this->tree_id)." ".
872  "ORDER BY t2.depth";
873 
874  $res = $ilDB->query($query);
875  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
876  {
877  $node['lft'] = $row->lft;
878  $node['rgt'] = $row->rgt;
879  $node['child'] = $row->child;
880  $node['type'] = $row->type;
881 
882  $nodes[] = $node;
883  }
884  return (array) $nodes;
885  }
886 
887 
896  function getSubTree($a_node,$a_with_data = true, $a_type = "")
897  {
898  if (!is_array($a_node))
899  {
900  $this->ilErr->raiseError(get_class($this)."::getSubTree(): Wrong datatype for node_data! ",$this->ilErr->WARNING);
901  }
902 
903  if($a_node['lft'] < 1 or $a_node['rgt'] < 2)
904  {
905  $message = sprintf('%s::getSubTree(): Invalid node given! $a_node["lft"]: %s $a_node["rgt"]: %s',
906  get_class($this),
907  $a_node['lft'],
908  $a_node['rgt']);
909 
910  $this->log->write($message,$this->log->FATAL);
911 
912  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
913  }
914 
915  $subtree = array();
916 
917  $type_str = "";
918  if ($a_type != "")
919  {
920  $type_str = "AND ".$this->table_obj_data.".type='".$a_type."' ";
921  }
922 
923  $q = "SELECT * FROM ".$this->table_tree." ".
924  $this->buildJoin().
925  "WHERE ".$this->table_tree.".lft BETWEEN '".$a_node["lft"]."' AND '".$a_node["rgt"]."' ".
926  "AND ".$this->table_tree.".".$this->tree_pk." = '".$this->tree_id."' ".
927  $type_str.
928  "ORDER BY ".$this->table_tree.".lft";
929 
930  $r = $this->ilDB->query($q);
931 
932  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
933  {
934  if($a_with_data)
935  {
936  $subtree[] = $this->fetchNodeData($row);
937  }
938  else
939  {
940  $subtree[] = $row['child'];
941  }
942  if($this->__isMainTree())
943  {
944  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
945  $this->in_tree_cache[$row['child']] = true;
946  }
947  }
948 
949  return $subtree ? $subtree : array();
950  }
951 
960  function getSubTreeTypes($a_node,$a_filter = 0)
961  {
962  $a_filter = $a_filter ? $a_filter : array();
963 
964  foreach($this->getSubtree($this->getNodeData($a_node)) as $node)
965  {
966  if(in_array($node["type"],$a_filter))
967  {
968  continue;
969  }
970  $types["$node[type]"] = $node["type"];
971  }
972  return $types ? $types : array();
973  }
974 
980  function deleteTree($a_node)
981  {
982  if (!is_array($a_node))
983  {
984  $this->ilErr->raiseError(get_class($this)."::deleteTree(): Wrong datatype for node_data! ",$this->ilErr->WARNING);
985  }
986  if($this->__isMainTree() and $a_node[$this->tree_pk] === 1)
987  {
988  if($a_node['lft'] <= 1 or $a_node['rgt'] <= 2)
989  {
990  $message = sprintf('%s::deleteTree(): Invalid parameters given: $a_node["lft"]: %s, $a_node["rgt"] %s',
991  get_class($this),
992  $a_node['lft'],
993  $a_node['rgt']);
994 
995  $this->log->write($message,$this->log->FATAL);
996  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
997  }
998  else if(!$this->__checkDelete($a_node))
999  {
1000  $message = sprintf('%s::deleteTree(): Check delete failed: $a_node["lft"]: %s, $a_node["rgt"] %s',
1001  get_class($this),
1002  $a_node['lft'],
1003  $a_node['rgt']);
1004  $this->log->write($message,$this->log->FATAL);
1005  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1006  }
1007 
1008  }
1009  $diff = $a_node["rgt"] - $a_node["lft"] + 1;
1010 
1011 
1012  // LOCKED ###########################################################
1013  // get lft and rgt values. Don't trust parameter lft/rgt values of $a_node
1014  if($this->__isMainTree())
1015  {
1016  ilDBx::_lockTables(array('tree' => 'WRITE'));
1017  }
1018 
1019  $query = "SELECT * FROM ".$this->table_tree." ".
1020  "WHERE child = '".$a_node['child']."' ".
1021  "AND ".$this->tree_pk." = '".$a_node[$this->tree_pk]."'";
1022 
1023  $res = $this->ilDB->query($query);
1024  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
1025  {
1026  $a_node['lft'] = $row->lft;
1027  $a_node['rgt'] = $row->rgt;
1028  $diff = $a_node["rgt"] - $a_node["lft"] + 1;
1029  }
1030 
1031  // delete subtree
1032  $q = "DELETE FROM ".$this->table_tree." ".
1033  "WHERE lft BETWEEN '".$a_node["lft"]."' AND '".$a_node["rgt"]."' ".
1034  "AND rgt BETWEEN '".$a_node["lft"]."' AND '".$a_node["rgt"]."' ".
1035  "AND ".$this->tree_pk." = '".$a_node[$this->tree_pk]."'";
1036  $this->ilDB->query($q);
1037 
1038  // Performance improvement: We only close the gap, if the node
1039  // is not in a trash tree, and if the resulting gap will be
1040  // larger than twice the gap value
1041  if ($a_node[$this->tree_pk] >= 0 &&
1042  $a_node['rgt'] - $a_node['lft'] >= $this->gap * 2)
1043  {
1044  //$this->log->write('ilTree.deleteTree('.$a_node['child'].') closing gap at '.$a_node['lft'].'...'.$a_node['rgt']);
1045  // close gaps
1046  $q = "UPDATE ".$this->table_tree." SET ".
1047  "lft = CASE ".
1048  "WHEN lft > '".$a_node["lft"]." '".
1049  "THEN lft - '".$diff." '".
1050  "ELSE lft ".
1051  "END, ".
1052  "rgt = CASE ".
1053  "WHEN rgt > '".$a_node["lft"]." '".
1054  "THEN rgt - '".$diff." '".
1055  "ELSE rgt ".
1056  "END ".
1057  "WHERE ".$this->tree_pk." = '".$a_node[$this->tree_pk]."'";
1058  $this->ilDB->query($q);
1059  }
1060  else
1061  {
1062  //$this->log->write('ilTree.deleteTree('.$a_node['child'].') leaving gap open '.$a_node['lft'].'...'.$a_node['rgt']);
1063  }
1064 
1065  if($this->__isMainTree())
1066  {
1067  #$GLOBALS['ilLog']->write(__METHOD__.': Resetting in tree cache ');
1069  $this->in_tree_cache = array();
1070  }
1071  // LOCKED ###########################################################
1072  }
1073 
1084  function getPathFull($a_endnode_id, $a_startnode_id = 0)
1085  {
1086  $pathIds =& $this->getPathId($a_endnode_id, $a_startnode_id);
1087 
1088  // We retrieve the full path in a single query to improve performance
1089  global $ilDB;
1090 
1091  // Abort if no path ids were found
1092  if (count($pathIds) == 0)
1093  {
1094  return null;
1095  }
1096 
1097  $inClause = 'child IN (';
1098  for ($i=0; $i < count($pathIds); $i++)
1099  {
1100  if ($i > 0) $inClause .= ',';
1101  $inClause .= $ilDB->quote($pathIds[$i]);
1102  }
1103  $inClause .= ')';
1104 
1105  $q = 'SELECT * '.
1106  'FROM '.$this->table_tree.' '.
1107  $this->buildJoin().' '.
1108  'WHERE '.$inClause.' '.
1109  'AND '.$this->table_tree.'.'.$this->tree_pk.' = '.$this->ilDB->quote($this->tree_id).' '.
1110  'ORDER BY depth ';
1111  $r = $ilDB->query($q);
1112 
1113  $pathFull = array();
1114  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1115  {
1116  $pathFull[] = $this->fetchNodeData($row);
1117 
1118  // Update cache
1119  if ($this->__isMainTree())
1120  {
1121  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$row['child']);
1122  $this->in_tree_cache[$row['child']] = $row['tree'] == 1;
1123  }
1124  }
1125  return $pathFull;
1126  }
1135  function getPathIdsUsingNestedSets($a_endnode_id, $a_startnode_id = 0)
1136  {
1137  // The nested sets algorithm is very easy to implement.
1138  // Unfortunately it always does a full table space scan to retrieve the path
1139  // regardless whether indices on lft and rgt are set or not.
1140  // (At least, this is what happens on MySQL 4.1).
1141  // This algorithms performs well for small trees which are deeply nested.
1142 
1143 
1144  if (!isset($a_endnode_id))
1145  {
1146  $this->ilErr->raiseError(get_class($this)."::getPathId(): No endnode_id given! ",$this->ilErr->WARNING);
1147  }
1148 
1149  $q = "SELECT T2.child ".
1150  "FROM ".$this->table_tree." AS T1, ".$this->table_tree." AS T2 ".
1151  "WHERE T1.child = '".$a_endnode_id."' ".
1152  "AND T1.lft BETWEEN T2.lft AND T2.rgt ".
1153  "AND T1.".$this->tree_pk." = '".$this->tree_id." '".
1154  "AND T2.".$this->tree_pk." = '".$this->tree_id." '".
1155  "ORDER BY T2.depth";
1156 
1157  $r = $this->ilDB->query($q);
1158  $takeId = $a_startnode_id == 0;
1159  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1160  {
1161  if ($takeId || $row['child'] == $a_startnode_id)
1162  {
1163  $takeId = true;
1164  $pathIds[] = $row['child'];
1165  }
1166  }
1167  return $pathIds;
1168 
1169  }
1178  function getPathIdsUsingAdjacencyMap($a_endnode_id, $a_startnode_id = 0)
1179  {
1180  // The adjacency map algorithm is harder to implement than the nested sets algorithm.
1181  // This algorithms performs an index search for each of the path element.
1182  // This algorithms performs well for large trees which are not deeply nested.
1183 
1184  // The $takeId variable is used, to determine if a given id shall be included in the path
1185  $takeId = $a_startnode_id == 0;
1186 
1187  if (!isset($a_endnode_id))
1188  {
1189  $this->ilErr->raiseError(get_class($this)."::getPathId(): No endnode_id given! ",$this->ilErr->WARNING);
1190  }
1191 
1192  global $log, $ilDB;
1193 
1194  // Determine the depth of the endnode, and fetch its parent field also.
1195  $q = 'SELECT t.depth,t.parent '.
1196  'FROM '.$this->table_tree.' AS t '.
1197  'WHERE child='.$this->ilDB->quote($a_endnode_id).' '.
1198  'AND '.$this->tree_pk.' = '.$this->tree_id.' '.
1199  'LIMIT 1';
1200  //$this->writelog('getIdsUsingAdjacencyMap q='.$q);
1201  $r = $this->ilDB->query($q);
1202 
1203  if ($r->numRows() == 0)
1204  {
1205  return array();
1206  }
1207  $row = $r->fetchRow(DB_FETCHMODE_ASSOC);
1208  $nodeDepth = $row['depth'];
1209  $parentId = $row['parent'];
1210  //$this->writelog('getIdsUsingAdjacencyMap depth='.$nodeDepth);
1211 
1212  // Fetch the node ids. For shallow depths we can fill in the id's directly.
1213  $pathIds = array();
1214  if ($nodeDepth == 1)
1215  {
1216  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1217  if ($takeId) $pathIds[] = $a_endnode_id;
1218  }
1219  else if ($nodeDepth == 2)
1220  {
1221  $takeId = $takeId || $parentId == $a_startnode_id;
1222  if ($takeId) $pathIds[] = $parentId;
1223  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1224  if ($takeId) $pathIds[] = $a_endnode_id;
1225  }
1226  else if ($nodeDepth == 3)
1227  {
1228  $takeId = $takeId || $this->root_id == $a_startnode_id;
1229  if ($takeId) $pathIds[] = $this->root_id;
1230  $takeId = $takeId || $parentId == $a_startnode_id;
1231  if ($takeId) $pathIds[] = $parentId;
1232  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1233  if ($takeId) $pathIds[] = $a_endnode_id;
1234  }
1235  else if ($nodeDepth < 32)
1236  {
1237  // Adjacency Map Tree performs better than
1238  // Nested Sets Tree even for very deep trees.
1239  // The following code construct nested self-joins
1240  // Since we already know the root-id of the tree and
1241  // we also know the id and parent id of the current node,
1242  // we only need to perform $nodeDepth - 3 self-joins.
1243  // We can further reduce the number of self-joins by 1
1244  // by taking into account, that each row in table tree
1245  // contains the id of itself and of its parent.
1246  $qSelect = 't1.child as c0';
1247  $qJoin = '';
1248  for ($i = 1; $i < $nodeDepth - 2; $i++)
1249  {
1250  $qSelect .= ', t'.$i.'.parent as c'.$i;
1251  $qJoin .= ' JOIN '.$this->table_tree.' AS t'.$i.' ON '.
1252  't'.$i.'.child=t'.($i - 1).'.parent AND '.
1253  't'.$i.'.'.$this->tree_pk.' = '.$this->tree_id;
1254  }
1255  $q = 'SELECT '.$qSelect.' '.
1256  'FROM '.$this->table_tree.' AS t0 '.$qJoin.' '.
1257  'WHERE t0.'.$this->tree_pk.' = '.$this->tree_id.' '.
1258  'AND t0.child='.$parentId.' '.
1259  'LIMIT 1';
1260  $r = $this->ilDB->query($q);
1261  if ($r->numRows() == 0)
1262  {
1263  return array();
1264  }
1265  $row = $r->fetchRow(DB_FETCHMODE_ASSOC);
1266 
1267  $takeId = $takeId || $this->root_id == $a_startnode_id;
1268  if ($takeId) $pathIds[] = $this->root_id;
1269  for ($i = $nodeDepth - 4; $i >=0; $i--)
1270  {
1271  $takeId = $takeId || $row['c'.$i] == $a_startnode_id;
1272  if ($takeId) $pathIds[] = $row['c'.$i];
1273  }
1274  $takeId = $takeId || $parentId == $a_startnode_id;
1275  if ($takeId) $pathIds[] = $parentId;
1276  $takeId = $takeId || $a_endnode_id == $a_startnode_id;
1277  if ($takeId) $pathIds[] = $a_endnode_id;
1278  }
1279  else
1280  {
1281  // Fall back to nested sets tree for extremely deep tree structures
1282  return $this->getPathIdsUsingNestedSets($a_endnode_id, $a_startnode_id);
1283  }
1284 
1285  return $pathIds;
1286  }
1287 
1296  function getPathId($a_endnode_id, $a_startnode_id = 0)
1297  {
1298  // path id cache
1299  if ($this->isCacheUsed() && isset($this->path_id_cache[$a_endnode_id][$a_startnode_id]))
1300  {
1301 //echo "<br>getPathIdhit";
1302  return $this->path_id_cache[$a_endnode_id][$a_startnode_id];
1303  }
1304 //echo "<br>miss";
1305 
1306  $pathIds =& $this->getPathIdsUsingAdjacencyMap($a_endnode_id, $a_startnode_id);
1307 
1308  if($this->__isMainTree())
1309  {
1310  $this->path_id_cache[$a_endnode_id][$a_startnode_id] = $pathIds;
1311  }
1312  return $pathIds;
1313  }
1314 
1315  // BEGIN WebDAV: getNodePathForTitlePath function added
1333  function getNodePathForTitlePath($titlePath, $a_startnode_id = null)
1334  {
1335  global $ilDB, $log;
1336  //$log->write('getNodePathForTitlePath('.implode('/',$titlePath));
1337 
1338  // handle empty title path
1339  if ($titlePath == null || count($titlePath) == 0)
1340  {
1341  if ($a_startnode_id == 0)
1342  {
1343  return null;
1344  }
1345  else
1346  {
1347  return $this->getNodePath($a_startnode_id);
1348  }
1349  }
1350 
1351  // fetch the node path up to the startnode
1352  if ($a_startnode_id != null && $a_startnode_id != 0)
1353  {
1354  // Start using the node path to the root of the relative path
1355  $nodePath = $this->getNodePath($a_startnode_id);
1356  $parent = $a_startnode_id;
1357  }
1358  else
1359  {
1360  // Start using the root of the tree
1361  $nodePath = array();
1362  $parent = 0;
1363  }
1364 
1365 
1366  // Convert title path into Unicode Normal Form C
1367  // This is needed to ensure that we can compare title path strings with
1368  // strings from the database.
1369  require_once('include/Unicode/UtfNormal.php');
1370  $inClause = 'd.title IN (';
1371  for ($i=0; $i < count($titlePath); $i++)
1372  {
1373  $titlePath[$i] = strtolower(UtfNormal::toNFC($titlePath[$i]));
1374  if ($i > 0) $inClause .= ',';
1375  $inClause .= $ilDB->quote($titlePath[$i]);
1376  }
1377  $inClause .= ')';
1378 
1379  // Fetch all rows that are potential path elements
1380  if ($this->table_obj_reference)
1381  {
1382  $joinClause = 'JOIN '.$this->table_obj_reference.' AS r ON t.child=r.'.$this->ref_pk.' '.
1383  'JOIN '.$this->table_obj_data.' AS d ON r.'.$this->obj_pk.'=d.'.$this->obj_pk;
1384  }
1385  else
1386  {
1387  $joinClause = 'JOIN '.$this->table_obj_data.' AS d ON t.child=d.'.$this->obj_pk;
1388  }
1389  // The ORDER BY clause in the following SQL statement ensures that,
1390  // in case of a multiple objects with the same title, always the Object
1391  // with the oldest ref_id is chosen.
1392  // This ensure, that, if a new object with the same title is added,
1393  // WebDAV clients can still work with the older object.
1394  $q = 'SELECT t.depth, t.parent, t.child, d.'.$this->obj_pk.' AS obj_id, d.type, d.title '.
1395  'FROM '.$this->table_tree.' AS t '.
1396  $joinClause.' '.
1397  'WHERE '.$inClause.' '.
1398  'AND t.depth <= '.(count($titlePath)+count($nodePath)).' '.
1399  'AND t.tree = 1 '.
1400  'ORDER BY t.depth, t.child ASC';
1401  $r = $ilDB->query($q);
1402 
1403  $rows = array();
1404  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1405  {
1406  $row['title'] = UtfNormal::toNFC($row['title']);
1407  $row['ref_id'] = $row['child'];
1408  $rows[] = $row;
1409  }
1410 
1411  // Extract the path elements from the fetched rows
1412  for ($i = 0; $i < count($titlePath); $i++) {
1413  $pathElementFound = false;
1414  foreach ($rows as $row) {
1415  if ($row['parent'] == $parent &&
1416  strtolower($row['title']) == $titlePath[$i])
1417  {
1418  // FIXME - We should test here, if the user has
1419  // 'visible' permission for the object.
1420  $nodePath[] = $row;
1421  $parent = $row['child'];
1422  $pathElementFound = true;
1423  break;
1424  }
1425  }
1426  // Abort if we haven't found a path element for the current depth
1427  if (! $pathElementFound)
1428  {
1429  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):null');
1430  return null;
1431  }
1432  }
1433  // Return the node path
1434  //$log->write('ilTree.getNodePathForTitlePath('.var_export($titlePath,true).','.$a_startnode_id.'):'.var_export($nodePath,true));
1435  return $nodePath;
1436  }
1437  // END WebDAV: getNodePathForTitlePath function added
1438  // END WebDAV: getNodePath function added
1455  function getNodePath($a_endnode_id, $a_startnode_id = 0)
1456  {
1457  global $ilDB;
1458 
1459  $pathIds =& $this->getPathId($a_endnode_id, $a_startnode_id);
1460 
1461  // Abort if no path ids were found
1462  if (count($pathIds) == 0)
1463  {
1464  return null;
1465  }
1466 
1467  $inClause = 't.child IN (';
1468  for ($i=0; $i < count($pathIds); $i++)
1469  {
1470  if ($i > 0) $inClause .= ',';
1471  $inClause .= $ilDB->quote($pathIds[$i]);
1472  }
1473  $inClause .= ')';
1474 
1475  $q = 'SELECT t.depth,t.parent,t.child,d.obj_id,d.type,d.title '.
1476  'FROM '.$this->table_tree.' AS t '.
1477  'JOIN '.$this->table_obj_reference.' AS r ON r.ref_id=t.child '.
1478  'JOIN '.$this->table_obj_data.' AS d ON d.obj_id=r.obj_id '.
1479  'WHERE '.$inClause.' '.
1480  'ORDER BY t.depth ';
1481  $r = $ilDB->query($q);
1482 
1483  $titlePath = array();
1484  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1485  {
1486  $titlePath[] = $row;
1487  }
1488  return $titlePath;
1489  }
1490  // END WebDAV: getNodePath function added
1491 
1498  function checkTree()
1499  {
1500  $q = "SELECT lft,rgt FROM ".$this->table_tree." ".
1501  "WHERE ".$this->tree_pk." = '".$this->tree_id."'";
1502 
1503  $r = $this->ilDB->query($q);
1504 
1505  while ($row = $r->fetchRow(DB_FETCHMODE_OBJECT))
1506  {
1507  $lft[] = $row->lft;
1508  $rgt[] = $row->rgt;
1509  }
1510 
1511  $all = array_merge($lft,$rgt);
1512  $uni = array_unique($all);
1513 
1514  if (count($all) != count($uni))
1515  {
1516  $message = sprintf('%s::checkTree(): Tree is corrupted!',
1517  get_class($this));
1518 
1519  $this->log->write($message,$this->log->FATAL);
1520  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1521  }
1522 
1523  return true;
1524  }
1525 
1529  function checkTreeChilds($a_no_zero_child = true)
1530  {
1531  $q = "SELECT * FROM ".$this->table_tree." ".
1532  "WHERE ".$this->tree_pk." = '".$this->tree_id."' ".
1533  "ORDER BY lft";
1534  $r1 = $this->ilDB->query($q);
1535  while ($row = $r1->fetchRow(DB_FETCHMODE_ASSOC))
1536  {
1537 //echo "tree:".$row[$this->tree_pk].":lft:".$row["lft"].":rgt:".$row["rgt"].":child:".$row["child"].":<br>";
1538  if (($row["child"] == 0) && $a_no_zero_child)
1539  {
1540  $this->ilErr->raiseError(get_class($this)."::checkTreeChilds(): Tree contains child with ID 0!",$this->ilErr->WARNING);
1541  }
1542 
1543  if ($this->table_obj_reference)
1544  {
1545  // get object reference data
1546  $q = "SELECT * FROM ".$this->table_obj_reference." WHERE ".$this->ref_pk."='".$row["child"]."'";
1547  $r2 = $this->ilDB->query($q);
1548 //echo "num_childs:".$r2->numRows().":<br>";
1549  if ($r2->numRows() == 0)
1550  {
1551  $this->ilErr->raiseError(get_class($this)."::checkTree(): No Object-to-Reference entry found for ID ".
1552  $row["child"]."!",$this->ilErr->WARNING);
1553  }
1554  if ($r2->numRows() > 1)
1555  {
1556  $this->ilErr->raiseError(get_class($this)."::checkTree(): More Object-to-Reference entries found for ID ".
1557  $row["child"]."!",$this->ilErr->WARNING);
1558  }
1559 
1560  // get object data
1561  $obj_ref = $r2->fetchRow(DB_FETCHMODE_ASSOC);
1562 
1563  $q = "SELECT * FROM ".$this->table_obj_data." WHERE ".$this->obj_pk."='".$obj_ref[$this->obj_pk]."'";
1564  $r3 = $this->ilDB->query($q);
1565  if ($r3->numRows() == 0)
1566  {
1567  $this->ilErr->raiseError(get_class($this)."::checkTree(): No child found for ID ".
1568  $obj_ref[$this->obj_pk]."!",$this->ilErr->WARNING);
1569  }
1570  if ($r3->numRows() > 1)
1571  {
1572  $this->ilErr->raiseError(get_class($this)."::checkTree(): More childs found for ID ".
1573  $obj_ref[$this->obj_pk]."!",$this->ilErr->WARNING);
1574  }
1575 
1576  }
1577  else
1578  {
1579  // get only object data
1580  $q = "SELECT * FROM ".$this->table_obj_data." WHERE ".$this->obj_pk."='".$row["child"]."'";
1581  $r2 = $this->ilDB->query($q);
1582 //echo "num_childs:".$r2->numRows().":<br>";
1583  if ($r2->numRows() == 0)
1584  {
1585  $this->ilErr->raiseError(get_class($this)."::checkTree(): No child found for ID ".
1586  $row["child"]."!",$this->ilErr->WARNING);
1587  }
1588  if ($r2->numRows() > 1)
1589  {
1590  $this->ilErr->raiseError(get_class($this)."::checkTree(): More childs found for ID ".
1591  $row["child"]."!",$this->ilErr->WARNING);
1592  }
1593  }
1594  }
1595 
1596  return true;
1597  }
1598 
1604  function getMaximumDepth()
1605  {
1606  $q = "SELECT MAX(depth) FROM ".$this->table_tree;
1607  $r = $this->ilDB->query($q);
1608 
1609  $row = $r->fetchRow();
1610 
1611  return $row[0];
1612  }
1613 
1620  function getDepth($a_node_id)
1621  {
1622  if ($a_node_id)
1623  {
1624  $q = "SELECT depth FROM ".$this->table_tree." ".
1625  "WHERE child = '".$a_node_id."' ".
1626  "AND ".$this->tree_pk." = '".$this->tree_id."'";
1627 
1628  $res = $this->ilDB->query($q);
1629  $row = $res->fetchRow(DB_FETCHMODE_OBJECT);
1630 
1631  return $row->depth;
1632  }
1633  else
1634  {
1635  return 1;
1636  }
1637  }
1638 
1639 
1647  // BEGIN WebDAV: Pass tree id to this method
1648  //function getNodeData($a_node_id)
1649  function getNodeData($a_node_id, $a_tree_pk = null)
1650  // END PATCH WebDAV: Pass tree id to this method
1651  {
1652  if (!isset($a_node_id))
1653  {
1654  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1655  }
1656  if($this->__isMainTree())
1657  {
1658  if($a_node_id < 1)
1659  {
1660  $message = sprintf('%s::getNodeData(): No valid parameter given! $a_node_id: %s',
1661  get_class($this),
1662  $a_node_id);
1663 
1664  $this->log->write($message,$this->log->FATAL);
1665  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1666  }
1667  }
1668 
1669  // BEGIN WebDAV: Pass tree id to this method
1670  $q = "SELECT * FROM ".$this->table_tree." ".
1671  $this->buildJoin().
1672  "WHERE ".$this->table_tree.".child = '".$a_node_id."' ".
1673  "AND ".$this->table_tree.".".$this->tree_pk." = '".
1674  (($a_tree_pk === null) ? $this->tree_id : $a_tree_pk).
1675  "'";
1676  // END WebDAV: Pass tree id to this method
1677  $r = $this->ilDB->query($q);
1678  $row = $r->fetchRow(DB_FETCHMODE_ASSOC);
1680 
1681  return $this->fetchNodeData($row);
1682  }
1683 
1691  function fetchNodeData($a_row)
1692  {
1693  global $objDefinition, $lng, $ilBench;
1694 
1695  //$ilBench->start("Tree", "fetchNodeData_getRow");
1696  $data = $a_row;
1697  $data["desc"] = $a_row["description"]; // for compability
1698  //$ilBench->stop("Tree", "fetchNodeData_getRow");
1699 
1700  // multilingual support systemobjects (sys) & categories (db)
1701  //$ilBench->start("Tree", "fetchNodeData_readDefinition");
1702  if (is_object($objDefinition))
1703  {
1704  $translation_type = $objDefinition->getTranslationType($data["type"]);
1705  }
1706  //$ilBench->stop("Tree", "fetchNodeData_readDefinition");
1707 
1708  if ($translation_type == "sys")
1709  {
1710  //$ilBench->start("Tree", "fetchNodeData_getLangData");
1711  if ($data["type"] == "rolf" and $data["obj_id"] != ROLE_FOLDER_ID)
1712  {
1713  $data["description"] = $lng->txt("obj_".$data["type"]."_local_desc").$data["title"].$data["desc"];
1714  $data["desc"] = $lng->txt("obj_".$data["type"]."_local_desc").$data["title"].$data["desc"];
1715  $data["title"] = $lng->txt("obj_".$data["type"]."_local");
1716  }
1717  else
1718  {
1719  $data["title"] = $lng->txt("obj_".$data["type"]);
1720  $data["description"] = $lng->txt("obj_".$data["type"]."_desc");
1721  $data["desc"] = $lng->txt("obj_".$data["type"]."_desc");
1722  }
1723  //$ilBench->stop("Tree", "fetchNodeData_getLangData");
1724  }
1725  elseif ($translation_type == "db")
1726  {
1727 
1728  // Try to retrieve object translation from cache
1729  if ($this->isCacheUsed() &&
1730  array_key_exists($data["obj_id"].'.'.$lang_code, $this->translation_cache)) {
1731 
1732  $key = $data["obj_id"].'.'.$lang_code;
1733  $data["title"] = $this->translation_cache[$key]['title'];
1734  $data["description"] = $this->translation_cache[$key]['description'];
1735  $data["desc"] = $this->translation_cache[$key]['desc'];
1736  } else {
1737  // Object translation is not in cache, read it from database
1738 
1739  //$ilBench->start("Tree", "fetchNodeData_getTranslation");
1740  $q = "SELECT title,description FROM object_translation ".
1741  "WHERE obj_id = ".$data["obj_id"]." ".
1742  "AND lang_code = '".$this->lang_code."' ".
1743  "AND NOT lang_default = 1";
1744  $r = $this->ilDB->query($q);
1745 
1746  $row = $r->fetchRow(DB_FETCHMODE_OBJECT);
1747 
1748  if ($row)
1749  {
1750  $data["title"] = $row->title;
1751  $data["description"] = ilUtil::shortenText($row->description,MAXLENGTH_OBJ_DESC,true);
1752  $data["desc"] = $row->description;
1753  }
1754  //$ilBench->stop("Tree", "fetchNodeData_getTranslation");
1755 
1756  // Store up to 1000 object translations in cache
1757  if ($this->isCacheUsed() && count($this->translation_cache) < 1000)
1758  {
1759  $key = $data["obj_id"].'.'.$lang_code;
1760  $this->translation_cache[$key] = array();
1761  $this->translation_cache[$key]['title'] = $data["title"] ;
1762  $this->translation_cache[$key]['description'] = $data["description"];
1763  $this->translation_cache[$key]['desc'] = $data["desc"];
1764  }
1765  }
1766  }
1767 
1768  // TODO: Handle this switch by module.xml definitions
1769  if($data['type'] == 'crsr' or $data['type'] == 'catr')
1770  {
1771  include_once('./Services/ContainerReference/classes/class.ilContainerReference.php');
1773  }
1774 
1775  return $data ? $data : array();
1776  }
1777 
1778 
1786  function isInTree($a_node_id)
1787  {
1788  if (!isset($a_node_id))
1789  {
1790  return false;
1791  #$this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
1792  }
1793 
1794  // is in tree cache
1795  if ($this->isCacheUsed() && isset($this->in_tree_cache[$a_node_id]))
1796  {
1797  #$GLOBALS['ilLog']->write(__METHOD__.': Using in tree cache '.$a_node_id);
1798 //echo "<br>in_tree_hit";
1799  return $this->in_tree_cache[$a_node_id];
1800  }
1801 
1802  $q = "SELECT * FROM ".$this->table_tree." ".
1803  "WHERE ".$this->table_tree.".child = ".$this->ilDB->quote($a_node_id)." ".
1804  "AND ".$this->table_tree.".".$this->tree_pk." = '".$this->tree_id."'";
1805  $r = $this->ilDB->query($q);
1806 
1807  if ($r->numRows() > 0)
1808  {
1809  if($this->__isMainTree())
1810  {
1811  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = true');
1812  $this->in_tree_cache[$a_node_id] = true;
1813  }
1814  return true;
1815  }
1816  else
1817  {
1818  if($this->__isMainTree())
1819  {
1820  #$GLOBALS['ilLog']->write(__METHOD__.': Storing in tree cache '.$a_node_id.' = false');
1821  $this->in_tree_cache[$a_node_id] = false;
1822  }
1823  return false;
1824  }
1825 
1826  }
1827 
1834  function getParentNodeData($a_node_id)
1835  {
1836  global $ilLog;
1837 
1838  if (!isset($a_node_id))
1839  {
1840  $ilLog->logStack();
1841  $this->ilErr->raiseError(get_class($this)."::getParentNodeData(): No node_id given! ",$this->ilErr->WARNING);
1842  }
1843 
1844  if ($this->table_obj_reference)
1845  {
1846  // Use inner join instead of left join to improve performance
1847  $innerjoin = "JOIN ".$this->table_obj_reference." ON v.child=".$this->table_obj_reference.".".$this->ref_pk." ".
1848  "JOIN ".$this->table_obj_data." ON ".$this->table_obj_reference.".".$this->obj_pk."=".$this->table_obj_data.".".$this->obj_pk." ";
1849  }
1850  else
1851  {
1852  // Use inner join instead of left join to improve performance
1853  $innerjoin = "JOIN ".$this->table_obj_data." ON v.child=".$this->table_obj_data.".".$this->obj_pk." ";
1854  }
1855 
1856  $q = "SELECT * FROM ".$this->table_tree." s,".$this->table_tree." v ".
1857  $innerjoin.
1858  "WHERE s.child = '".$a_node_id."' ".
1859  "AND s.parent = v.child ".
1860  "AND s.lft > v.lft ".
1861  "AND s.rgt < v.rgt ".
1862  "AND s.".$this->tree_pk." = '".$this->tree_id."' ".
1863  "AND v.".$this->tree_pk." = '".$this->tree_id."'";
1864  $r = $this->ilDB->query($q);
1865 
1866  $row = $r->fetchRow(DB_FETCHMODE_ASSOC);
1867 
1868  return $this->fetchNodeData($row);
1869  }
1870 
1878  function isGrandChild($a_startnode_id,$a_querynode_id)
1879  {
1880  if (!isset($a_startnode_id) or !isset($a_querynode_id))
1881  {
1882  return false;
1883  // No raise error, since it is a already a check function
1884  #$this->ilErr->raiseError(get_class($this)."::isGrandChild(): Missing parameter! startnode: ".$a_startnode_id." querynode: ".
1885  # $a_querynode_id,$this->ilErr->WARNING);
1886  }
1887 
1888  $q = "SELECT * FROM ".$this->table_tree." s,".$this->table_tree." v ".
1889  "WHERE s.child = '".$a_startnode_id."' ".
1890  "AND v.child = '".$a_querynode_id."' ".
1891  "AND s.".$this->tree_pk." = '".$this->tree_id."' ".
1892  "AND v.".$this->tree_pk." = '".$this->tree_id."' ".
1893  "AND v.lft BETWEEN s.lft AND s.rgt ".
1894  "AND v.rgt BETWEEN s.lft AND s.rgt";
1895  $r = $this->ilDB->query($q);
1896 
1897  return $r->numRows();
1898  }
1899 
1908  function addTree($a_tree_id,$a_node_id = -1)
1909  {
1910  // FOR SECURITY addTree() IS NOT ALLOWED ON MAIN TREE
1911  // IF SOMEONE WILL NEED FEATURES LIKE $tree->addTree(2) ON THE MAIN TREE PLEASE CONTACT ME (smeyer.ilias@gmx.de)
1912  if($this->__isMainTree())
1913  {
1914  $message = sprintf('%s::addTree(): Operation not allowed on main tree! $a_tree_if: %s $a_node_id: %s',
1915  get_class($this),
1916  $a_tree_id,
1917  $a_node_id);
1918  $this->log->write($message,$this->log->FATAL);
1919  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
1920  }
1921 
1922  if (!isset($a_tree_id))
1923  {
1924  $this->ilErr->raiseError(get_class($this)."::addTree(): No tree_id given! ",$this->ilErr->WARNING);
1925  }
1926 
1927  if ($a_node_id <= 0)
1928  {
1929  $a_node_id = $a_tree_id;
1930  }
1931 
1932  $q = "INSERT INTO ".$this->table_tree." (".$this->tree_pk.", child, parent, lft, rgt, depth) ".
1933  "VALUES ".
1934  "('".$a_tree_id."','".$a_node_id."', 0, 1, 2, 1)";
1935 
1936  $this->ilDB->query($q);
1937  return true;
1938  }
1939 
1947  function getNodeDataByType($a_type)
1948  {
1949  if (!isset($a_type) or (!is_string($a_type)))
1950  {
1951  $this->ilErr->raiseError(get_class($this)."::getNodeDataByType(): Type not given or wrong datatype!",$this->ilErr->WARNING);
1952  }
1953 
1954  $data = array(); // node_data
1955  $row = ""; // fetched row
1956  $left = ""; // tree_left
1957  $right = ""; // tree_right
1958 
1959  $q = "SELECT * FROM ".$this->table_tree." ".
1960  "WHERE ".$this->tree_pk." = '".$this->tree_id."'".
1961  "AND parent = '0'";
1962  $r = $this->ilDB->query($q);
1963 
1964  while ($row = $r->fetchRow(DB_FETCHMODE_OBJECT))
1965  {
1966  $left = $row->lft;
1967  $right = $row->rgt;
1968  }
1969 
1970  $q = "SELECT * FROM ".$this->table_tree." ".
1971  $this->buildJoin().
1972  "WHERE ".$this->table_obj_data.".type = '".$a_type."' ".
1973  "AND ".$this->table_tree.".lft BETWEEN '".$left."' AND '".$right."' ".
1974  "AND ".$this->table_tree.".rgt BETWEEN '".$left."' AND '".$right."' ".
1975  "AND ".$this->table_tree.".".$this->tree_pk." = '".$this->tree_id."'";
1976  $r = $this->ilDB->query($q);
1977 
1978  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
1979  {
1980  $data[] = $this->fetchNodeData($row);
1981  }
1982 
1983  return $data;
1984  }
1985 
1993  function removeTree($a_tree_id)
1994  {
1995  // OPERATION NOT ALLOWED ON MAIN TREE
1996  if($this->__isMainTree())
1997  {
1998  $message = sprintf('%s::removeTree(): Operation not allowed on main tree! $a_tree_if: %s',
1999  get_class($this),
2000  $a_tree_id);
2001  $this->log->write($message,$this->log->FATAL);
2002  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2003  }
2004  if (!$a_tree_id)
2005  {
2006  $this->ilErr->raiseError(get_class($this)."::removeTree(): No tree_id given! Action aborted",$this->ilErr->MESSAGE);
2007  }
2008 
2009  $q = "DELETE FROM ".$this->table_tree." WHERE ".$this->tree_pk." = '".$a_tree_id."'";
2010  $this->ilDB->query($q);
2011 
2012  return true;
2013  }
2014 
2022  function saveSubTree($a_node_id, $a_set_deleted = false)
2023  {
2024  global $ilDB;
2025 
2026  if (!$a_node_id)
2027  {
2028  $message = sprintf('%s::saveSubTree(): No valid parameter given! $a_node_id: %s',
2029  get_class($this),
2030  $a_node_id);
2031  $this->log->write($message,$this->log->FATAL);
2032  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2033  }
2034 
2035  // LOCKED ###############################################
2036  if($this->__isMainTree())
2037  {
2038  ilDBx::_lockTables(array('tree' => 'WRITE',
2039  'object_reference' => 'WRITE'));
2040  }
2041 
2042  // GET LEFT AND RIGHT VALUE
2043  $q = "SELECT * FROM ".$this->table_tree." ".
2044  "WHERE ".$this->tree_pk." = '".$this->tree_id."' ".
2045  "AND child = '".$a_node_id."' ";
2046  $r = $this->ilDB->query($q);
2047 
2048  while($row = $r->fetchRow(DB_FETCHMODE_OBJECT))
2049  {
2050  $lft = $row->lft;
2051  $rgt = $row->rgt;
2052  }
2053 
2054  // GET ALL SUBNODES
2055  $q = "SELECT child FROM ".$this->table_tree." ".
2056  "WHERE ".$this->tree_pk." = '".$this->tree_id."' ".
2057  "AND lft BETWEEN '".$lft."' AND '".$rgt."'";
2058  $r = $this->ilDB->query($q);
2059 
2060  $subnodes = array();
2061  while($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
2062  {
2063  $subnodes[] = $row['child'];
2064  }
2065 
2066  if(!count($subnodes))
2067  {
2068  // possibly already deleted
2069 
2070  // Unlock locked tables before returning
2071  if($this->__isMainTree())
2072  {
2073  ilDBX::_unlockTables();
2074  }
2075 
2076  return false;
2077  }
2078 
2079  // SAVE SUBTREE
2080  foreach($subnodes as $child)
2081  {
2082  #$q = "INSERT INTO ".$this->table_tree." ".
2083  # "VALUES ('".-$a_node_id."','".$node["child"]."','".$node["parent"]."','".
2084  # $node["lft"]."','".$node["rgt"]."','".$node["depth"]."')";
2085  #$r = $this->ilDB->query($q);
2086 
2087  // set node as deleted
2088  if ($a_set_deleted)
2089  {
2090  ilObject::_setDeletedDate($child);
2091  }
2092  }
2093 
2094  // Set the nodes deleted (negative tree id)
2095  $query = "UPDATE ".$this->table_tree." ".
2096  "SET tree = ".$ilDB->quote(-$a_node_id)." ".
2097  "WHERE ".$this->tree_pk." = ".$ilDB->quote($this->tree_id)." ".
2098  "AND lft BETWEEN ".$ilDB->quote($lft)." AND ".$ilDB->quote($rgt)." ";
2099  $res = $ilDB->query($query);
2100 
2101 
2102  if($this->__isMainTree())
2103  {
2104  ilDBX::_unlockTables();
2105  }
2106 
2107  // LOCKED ###############################################
2108  return true;
2109  }
2110 
2114  function isDeleted($a_node_id)
2115  {
2116  return $this->isSaved($a_node_id);
2117  }
2118 
2122  function isSaved($a_node_id)
2123  {
2124  // is saved cache
2125  if ($this->isCacheUsed() && isset($this->is_saved_cache[$a_node_id]))
2126  {
2127 //echo "<br>issavedhit";
2128  return $this->is_saved_cache[$a_node_id];
2129  }
2130 
2131  $q = "SELECT * FROM ".$this->table_tree." ".
2132  "WHERE child = '".$a_node_id."'";
2133  $s = $this->ilDB->query($q);
2134  $r = $s->fetchRow(DB_FETCHMODE_ASSOC);
2135 
2136  if ($r[$this->tree_pk] < 0)
2137  {
2138  if($this->__isMainTree())
2139  {
2140  $this->is_saved_cache[$a_node_id] = true;
2141  }
2142  return true;
2143  }
2144  else
2145  {
2146  if($this->__isMainTree())
2147  {
2148  $this->is_saved_cache[$a_node_id] = false;
2149  }
2150  return false;
2151  }
2152  }
2153 
2154 
2155 
2162  function getSavedNodeData($a_parent_id)
2163  {
2164  if (!isset($a_parent_id))
2165  {
2166  $this->ilErr->raiseError(get_class($this)."::getSavedNodeData(): No node_id given!",$this->ilErr->WARNING);
2167  }
2168 
2169  $q = "SELECT * FROM ".$this->table_tree." ".
2170  $this->buildJoin().
2171  "WHERE ".$this->table_tree.".".$this->tree_pk." < 0 ".
2172  "AND ".$this->table_tree.".parent = '".$a_parent_id."' ";
2173  $r = $this->ilDB->query($q);
2174 
2175  while ($row = $r->fetchRow(DB_FETCHMODE_ASSOC))
2176  {
2177  $saved[] = $this->fetchNodeData($row);
2178  }
2179 
2180  return $saved ? $saved : array();
2181  }
2182 
2189  function getParentId($a_node_id)
2190  {
2191  if (!isset($a_node_id))
2192  {
2193  $this->ilErr->raiseError(get_class($this)."::getParentId(): No node_id given! ",$this->ilErr->WARNING);
2194  }
2195 
2196  $q = "SELECT parent FROM ".$this->table_tree." ".
2197  "WHERE child='".$a_node_id."' ".
2198  "AND ".$this->tree_pk."='".$this->tree_id."'";
2199  $r = $this->ilDB->query($q);
2200 
2201  $row = $r->fetchRow(DB_FETCHMODE_OBJECT);
2202 
2203  return $row->parent;
2204  }
2205 
2212  function getLeftValue($a_node_id)
2213  {
2214  if (!isset($a_node_id))
2215  {
2216  $this->ilErr->raiseError(get_class($this)."::getLeftValued(): No node_id given! ",$this->ilErr->WARNING);
2217  }
2218 
2219  $q = "SELECT lft FROM ".$this->table_tree." ".
2220  "WHERE child='".$a_node_id."' ".
2221  "AND ".$this->tree_pk."='".$this->tree_id."'";
2222  $r = $this->ilDB->query($q);
2223 
2224  $row = $r->fetchRow(DB_FETCHMODE_OBJECT);
2225 
2226  return $row->lft;
2227  }
2228 
2235  function getChildSequenceNumber($a_node, $type = "")
2236  {
2237  if (!isset($a_node))
2238  {
2239  $this->ilErr->raiseError(get_class($this)."::getChildSequenceNumber(): No node_id given! ",$this->ilErr->WARNING);
2240  }
2241 
2242  $type_str = ($type != "")
2243  ? "AND type='$type'"
2244  : "";
2245 
2246  $q = "SELECT count(*) AS cnt FROM ".$this->table_tree." ".
2247  $this->buildJoin().
2248  "WHERE lft <=".$this->ilDB->quote($a_node["lft"])." ".
2249  $type_str.
2250  "AND parent=".$this->ilDB->quote($a_node["parent"])." ".
2251  "AND ".$this->table_tree.".".$this->tree_pk."=".$this->ilDB->quote($this->tree_id);
2252  $r = $this->ilDB->query($q);
2253 
2254  $row = $r->fetchRow(DB_FETCHMODE_ASSOC);
2255 
2256  return $row["cnt"];
2257  }
2258 
2265  function readRootId()
2266  {
2267  $query = "SELECT child FROM $this->table_tree ".
2268  "WHERE parent = '0'".
2269  "AND ".$this->tree_pk." = '".$this->tree_id."'";
2270  $row = $this->ilDB->getRow($query,DB_FETCHMODE_OBJECT);
2271 
2272  $this->root_id = $row->child;
2273  return $this->root_id;
2274  }
2275 
2281  function getRootId()
2282  {
2283  return $this->root_id;
2284  }
2285  function setRootId($a_root_id)
2286  {
2287  $this->root_id = $a_root_id;
2288  }
2289 
2295  function getTreeId()
2296  {
2297  return $this->tree_id;
2298  }
2299 
2305  function setTreeId($a_tree_id)
2306  {
2307  $this->tree_id = $a_tree_id;
2308  }
2309 
2317  function fetchSuccessorNode($a_node_id, $a_type = "")
2318  {
2319  if (!isset($a_node_id))
2320  {
2321  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
2322  }
2323 
2324  // get lft value for current node
2325  $q = "SELECT lft FROM ".$this->table_tree." ".
2326  "WHERE ".$this->table_tree.".child = '".$a_node_id."' ".
2327  "AND ".$this->table_tree.".".$this->tree_pk." = '".$this->tree_id."'";
2328  $r = $this->ilDB->query($q);
2329  $curr_node = $r->fetchRow(DB_FETCHMODE_ASSOC);
2330 
2331  // get data of successor node
2332  $type_where = ($a_type != "")
2333  ? "AND ".$this->table_obj_data.".type = '$a_type' "
2334  : "";
2335  $q = "SELECT * FROM ".$this->table_tree." ".
2336  $this->buildJoin().
2337  "WHERE lft > '".$curr_node["lft"]."' ".
2338  $type_where.
2339  "AND ".$this->table_tree.".".$this->tree_pk." = '".$this->tree_id."'".
2340  "ORDER BY lft LIMIT 1";
2341  $r = $this->ilDB->query($q);
2342 
2343  if ($r->numRows() < 1)
2344  {
2345  return false;
2346  }
2347  else
2348  {
2349  $row = $r->fetchRow(DB_FETCHMODE_ASSOC);
2350  return $this->fetchNodeData($row);
2351  }
2352  }
2353 
2361  function fetchPredecessorNode($a_node_id, $a_type = "")
2362  {
2363  if (!isset($a_node_id))
2364  {
2365  $this->ilErr->raiseError(get_class($this)."::getNodeData(): No node_id given! ",$this->ilErr->WARNING);
2366  }
2367 
2368  // get lft value for current node
2369  $q = "SELECT lft FROM ".$this->table_tree." ".
2370  "WHERE ".$this->table_tree.".child = '".$a_node_id."' ".
2371  "AND ".$this->table_tree.".".$this->tree_pk." = '".$this->tree_id."'";
2372  $r = $this->ilDB->query($q);
2373  $curr_node = $r->fetchRow(DB_FETCHMODE_ASSOC);
2374 
2375  // get data of predecessor node
2376  $type_where = ($a_type != "")
2377  ? "AND ".$this->table_obj_data.".type = '$a_type' "
2378  : "";
2379  $q = "SELECT * FROM ".$this->table_tree." ".
2380  $this->buildJoin().
2381  "WHERE lft < '".$curr_node["lft"]."' ".
2382  $type_where.
2383  "AND ".$this->table_tree.".".$this->tree_pk." = '".$this->tree_id."'".
2384  "ORDER BY lft DESC LIMIT 1";
2385  $r = $this->ilDB->query($q);
2386 
2387  if ($r->numRows() < 1)
2388  {
2389  return false;
2390  }
2391  else
2392  {
2393  $row = $r->fetchRow(DB_FETCHMODE_ASSOC);
2394  return $this->fetchNodeData($row);
2395  }
2396  }
2397 
2406  function renumber($node_id = 1, $i = 1)
2407  {
2408  // LOCKED ###################################
2409  if($this->__isMainTree())
2410  {
2411  ilDBx::_lockTables(array($this->table_tree => 'WRITE',
2412  $this->table_obj_data => 'WRITE',
2413  $this->table_obj_reference => 'WRITE',
2414  'object_translation' => 'WRITE',
2415  'object_data od' => 'WRITE',
2416  'container_reference cr' => 'WRITE'));
2417  }
2418  $return = $this->__renumber($node_id,$i);
2419  if($this->__isMainTree())
2420  {
2422  }
2423  // LOCKED ###################################
2424  return $return;
2425  }
2426 
2427  // PRIVATE
2437  function __renumber($node_id = 1, $i = 1)
2438  {
2439  $q = "UPDATE ".$this->table_tree." SET lft='".$i."' WHERE child='".$node_id."'";
2440  $this->ilDB->query($q);
2441 
2442  $childs = $this->getChilds($node_id);
2443 
2444  foreach ($childs as $child)
2445  {
2446  $i = $this->__renumber($child["child"],$i+1);
2447  }
2448 
2449  $i++;
2450 
2451  // Insert a gap at the end of node, if the node has children
2452  if (count($childs) > 0)
2453  {
2454  $i += $this->gap * 2;
2455  }
2456 
2457  $q = "UPDATE ".$this->table_tree." SET rgt='".$i."' WHERE child='".$node_id."'";
2458  $this->ilDB->query($q);
2459 
2460  return $i;
2461  }
2462 
2463 
2473  function checkForParentType($a_ref_id,$a_type)
2474  {
2475  // Try to return a cached result
2476  if ($this->isCacheUsed() &&
2477  array_key_exists($a_ref_id.'.'.$a_type, $this->parent_type_cache)) {
2478  return $this->parent_type_cache[$a_ref_id.'.'.$a_type];
2479  }
2480 
2481  if(!$this->isInTree($a_ref_id))
2482  {
2483  // Store up to 1000 results in cache
2484  if ($this->__isMainTree() && count($this->parent_type_cache) < 1000) {
2485  $this->parent_type_cache[$a_ref_id.'.'.$a_type] = false;
2486  }
2487  return false;
2488  }
2489  $path = array_reverse($this->getPathFull($a_ref_id));
2490 
2491  foreach($path as $node)
2492  {
2493  if($node["type"] == $a_type)
2494  {
2495  // Store up to 1000 results in cache
2496  if ($this->__isMainTree() && count($this->parent_type_cache) < 1000) {
2497  $this->parent_type_cache[$a_ref_id.'.'.$a_type] = $node["child"];
2498  }
2499  return $node["child"];
2500  }
2501  }
2502  // Store up to 1000 results in cache
2503  if ($this->__isMainTree() && count($this->parent_type_cache) < 1000) {
2504  $this->parent_type_cache[$a_ref_id.'.'.$a_type] = false;
2505  }
2506  return 0;
2507  }
2508 
2518  function _removeEntry($a_tree,$a_child,$a_db_table = "tree")
2519  {
2520  global $ilDB,$ilLog,$ilErr;
2521 
2522  if($a_db_table === 'tree')
2523  {
2524  if($a_tree == 1 and $a_child == ROOT_FOLDER_ID)
2525  {
2526  $message = sprintf('%s::_removeEntry(): Tried to delete root node! $a_tree: %s $a_child: %s',
2527  get_class($this),
2528  $a_tree,
2529  $a_child);
2530  $ilLog->write($message,$ilLog->FATAL);
2531  $ilErr->raiseError($message,$ilErr->WARNING);
2532  }
2533  }
2534 
2535  $q = "DELETE from ".$a_db_table." WHERE tree='".$a_tree."' AND child='".$a_child."'";
2536  $ilDB->query($q);
2537  }
2538 
2545  function moveSubTreeAlex($a_source_id, $a_target_id)
2546  {
2547  // check if both IDs are > 0
2548  if($a_source_id <= 0 or $a_target_id <= 0)
2549  {
2550  $message = sprintf('%s::insertNode(): Invalid parameters! $a_source_id: %s $a_target_id: %s',
2551  get_class($this),
2552  $a_source_id,
2553  $a_target_id);
2554  $this->log->write($message,$this->log->FATAL);
2555  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2556  }
2557  }
2558 
2559 
2560  // PRIVATE METHODS
2567  function __isMainTree()
2568  {
2569  return $this->table_tree === 'tree';
2570  }
2571 
2580  function __checkDelete($a_node)
2581  {
2582  // get subtree by lft,rgt
2583  $query = "SELECT * FROM ".$this->table_tree." ".
2584  "WHERE lft >= ".$a_node['lft']." ".
2585  "AND rgt <= ".$a_node['rgt']." ".
2586  "AND ".$this->tree_pk." = '".$a_node[$this->tree_pk]."'";
2587 
2588 
2589  $res = $this->ilDB->query($query);
2590 
2591  $counter = (int) $lft_childs = array();
2592  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
2593  {
2594  $lft_childs[$row->child] = $row->parent;
2595  ++$counter;
2596  }
2597 
2598  // CHECK FOR DUPLICATE CHILD IDS
2599  if($counter != count($lft_childs))
2600  {
2601  $message = sprintf('%s::__checkTree(): Duplicate entries for "child" in maintree! $a_node_id: %s',
2602  get_class($this),
2603  $a_node['child']);
2604  $this->log->write($message,$this->log->FATAL);
2605  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2606  }
2607 
2608  // GET SUBTREE BY PARENT RELATION
2609  $parent_childs = array();
2610  $this->__getSubTreeByParentRelation($a_node['child'],$parent_childs);
2611  $this->__validateSubtrees($lft_childs,$parent_childs);
2612 
2613  return true;
2614  }
2615 
2616  function __getSubTreeByParentRelation($a_node_id,&$parent_childs)
2617  {
2618  // GET PARENT ID
2619  $query = "SELECT * FROM ".$this->table_tree." ".
2620  "WHERE child = '".$a_node_id."' ".
2621  "AND tree = '".$this->tree_id."'";
2622 
2623  $res = $this->ilDB->query($query);
2624  $counter = 0;
2625  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
2626  {
2627  $parent_childs[$a_node_id] = $row->parent;
2628  ++$counter;
2629  }
2630  // MULTIPLE ENTRIES
2631  if($counter > 1)
2632  {
2633  $message = sprintf('%s::__getSubTreeByParentRelation(): Multiple entries in maintree! $a_node_id: %s',
2634  get_class($this),
2635  $a_node_id);
2636  $this->log->write($message,$this->log->FATAL);
2637  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2638  }
2639 
2640  // GET ALL CHILDS
2641  $query = "SELECT * FROM ".$this->table_tree." ".
2642  "WHERE parent = '".$a_node_id."'";
2643 
2644  $res = $this->ilDB->query($query);
2645  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
2646  {
2647  // RECURSION
2648  $this->__getSubTreeByParentRelation($row->child,$parent_childs);
2649  }
2650  return true;
2651  }
2652 
2653  function __validateSubtrees(&$lft_childs,$parent_childs)
2654  {
2655  // SORT BY KEY
2656  ksort($lft_childs);
2657  ksort($parent_childs);
2658 
2659  if(count($lft_childs) != count($parent_childs))
2660  {
2661  $message = sprintf('%s::__validateSubtrees(): (COUNT) Tree is corrupted! Left/Right subtree does not comply .'.
2662  'with parent relation',
2663  get_class($this));
2664  $this->log->write($message,$this->log->FATAL);
2665  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2666  }
2667 
2668  foreach($lft_childs as $key => $value)
2669  {
2670  if($parent_childs[$key] != $value)
2671  {
2672  $message = sprintf('%s::__validateSubtrees(): (COMPARE) Tree is corrupted! Left/Right subtree does not comply '.
2673  'with parent relation',
2674  get_class($this));
2675  $this->log->write($message,$this->log->FATAL);
2676  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2677  }
2678  if($key == ROOT_FOLDER_ID)
2679  {
2680  $message = sprintf('%s::__validateSubtrees(): (ROOT_FOLDER) Tree is corrupted! Tried to delete root folder',
2681  get_class($this));
2682  $this->log->write($message,$this->log->FATAL);
2683  $this->ilErr->raiseError($message,$this->ilErr->WARNING);
2684  }
2685  }
2686  return true;
2687  }
2688 
2699  public function moveTree($a_source_id,$a_target_id,$a_location = IL_LAST_NODE)
2700  {
2701  if($this->__isMainTree())
2702  {
2703  ilDBx::_lockTables(array('tree' => 'WRITE'));
2704  }
2705  // Receive node infos for source and target
2706  $query = "SELECT * FROM ".$this->table_tree." ".
2707  "WHERE (child = ".$this->ilDB->quote($a_source_id)." ".
2708  "OR child = ".$this->ilDB->quote($a_target_id).") ".
2709  "AND tree = ".$this->ilDB->quote($this->tree_id);
2710  $res = $this->ilDB->query($query);
2711  #var_dump("<pre>",$query,"<pre>");
2712 
2713  // Check in tree
2714  if($res->numRows() != 2)
2715  {
2716  if($this->__isMainTree())
2717  {
2719  }
2720  $this->log->write(__METHOD__.' Objects not found in tree!',$this->log->FATAL);
2721  $this->ilErr->raiseError('Error moving node',$this->ilErr->WARNING);
2722  }
2723  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
2724  {
2725  if($row->child == $a_source_id)
2726  {
2727  $source_lft = $row->lft;
2728  $source_rgt = $row->rgt;
2729  $source_depth = $row->depth;
2730  $source_parent = $row->parent;
2731  }
2732  else
2733  {
2734  $target_lft = $row->lft;
2735  $target_rgt = $row->rgt;
2736  $target_depth = $row->depth;
2737  }
2738  }
2739 
2740  #var_dump("<pre>",$source_lft,$source_rgt,$source_depth,$target_lft,$target_rgt,$target_depth,"<pre>");
2741  // Check target not child of source
2742  if($target_lft >= $source_lft and $target_rgt <= $source_rgt)
2743  {
2744  if($this->__isMainTree())
2745  {
2747  }
2748  $this->log->write(__METHOD__.' Target is child of source',$this->log->FATAL);
2749  $this->ilErr->raiseError('Error moving node',$this->ilErr->WARNING);
2750  }
2751 
2752  // Now spread the tree at the target location. After this update the table should be still in a consistent state.
2753  // implementation for IL_LAST_NODE
2754  $spread_diff = $source_rgt - $source_lft + 1;
2755  #var_dump("<pre>","SPREAD_DIFF: ",$spread_diff,"<pre>");
2756 
2757  $query = "UPDATE ".$this->table_tree ." SET ".
2758  "lft = CASE ".
2759  "WHEN lft > ".$this->ilDB->quote($target_rgt)." ".
2760  "THEN lft + ".$this->ilDB->quote($spread_diff)." ".
2761  "ELSE lft ".
2762  "END, ".
2763  "rgt = CASE ".
2764  "WHEN rgt >= ".$this->ilDB->quote($target_rgt)." ".
2765  "THEN rgt + ".$this->ilDB->quote($spread_diff)." ".
2766  "ELSE rgt ".
2767  "END ".
2768  "WHERE tree = ".$this->ilDB->quote($this->tree_id);
2769  #var_dump("<pre>",$query,"<pre>");
2770  $res = $this->ilDB->query($query);
2771 
2772  // Ok, maybe the source node has been updated, too.
2773  // Check this:
2774  if($source_lft > $target_rgt)
2775  {
2776  $where_offset = $spread_diff;
2777  $move_diff = $target_rgt - $source_lft - $spread_diff;
2778  }
2779  else
2780  {
2781  $where_offset = 0;
2782  $move_diff = $target_rgt - $source_lft;
2783  }
2784  $depth_diff = $target_depth - $source_depth + 1;
2785 
2786  // Update source subtree:
2787  $query = "UPDATE ".$this->table_tree ." SET ".
2788  "parent = CASE ".
2789  "WHEN parent = ".$this->ilDB->quote($source_parent)." ".
2790  "THEN ".$this->ilDB->quote($a_target_id)." ".
2791  "ELSE parent ".
2792  "END, ".
2793  "rgt = rgt + ".$this->ilDB->quote($move_diff).", ".
2794  "lft = lft + ".$this->ilDB->quote($move_diff).", ".
2795  "depth = depth + ".$this->ilDB->quote($depth_diff)." ".
2796  "WHERE lft >= ".$this->ilDB->quote(($source_lft + $where_offset))." ".
2797  "AND rgt <= ".$this->ilDB->quote(($source_rgt + $where_offset))." ".
2798  "AND tree = ".$this->ilDB->quote($this->tree_id);
2799  #var_dump("<pre>",$query,"<pre>");
2800  $res = $this->ilDB->query($query);
2801 
2802  // done: close old gap
2803  $query = "UPDATE ".$this->table_tree ." SET ".
2804  "lft = CASE ".
2805  "WHEN lft >= ".$this->ilDB->quote(($source_lft + $where_offset))." ".
2806  "THEN lft - ".$this->ilDB->quote($spread_diff)." ".
2807  "ELSE lft ".
2808  "END, ".
2809  "rgt = CASE ".
2810  "WHEN rgt >= ".$this->ilDB->quote(($source_rgt + $where_offset))." ".
2811  "THEN rgt - ".$this->ilDB->quote($spread_diff)." ".
2812  "ELSE rgt ".
2813  "END ".
2814  "WHERE tree = ".$this->tree_id;
2815  #var_dump("<pre>",$query,"</pre>");
2816  $res = $this->ilDB->query($query);
2817 
2818  if($this->__isMainTree())
2819  {
2821  }
2822  return true;
2823  }
2824 
2831  private function buildTypeFilter ($fieldname, $types) {
2832  global $ilDB;
2833  $type_str = "";
2834  if (is_array($types) && count ($types) > 0)
2835  {
2836  $type_str = " AND ";
2837  if (is_array($types)) {
2838  $type_filter = array();
2839  foreach ($types as $type) {
2840  $type_filter []= $fieldname."='".$type."'";
2841  }
2842  $type_str .= " (" . join (" OR ", $type_filter) .") ";
2843  }
2844  } elseif (is_string($types) && strlen($types) > 0) {
2845  $type_str .= " AND ".$fieldname."= ".$ilDB->quote($types)." ";
2846  }
2847  return $type_str;
2848  }
2849 
2850 } // END class.tree
2851 ?>