ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
class.ilValidator.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
12 {
16  protected $obj_definition;
17 
21  protected $db;
22 
26  protected $lng;
27 
31  protected $log;
32 
36  protected $rbacadmin;
37 
41  protected $tree;
42 
46  protected $user;
47 
48  protected $media_pool_ids = null;
49 
54  public $rbac_object_types = null;
55 
60  // i added folder due to bug #1860 (even if this will not completely fix it)
61  // and the fact, that media pool folders may find their way into
62  // the recovery folder (what results in broken pools, if the are deleted)
63  // Alex, 2006-07-21
64  // I removed file objects from this exclusion list, because file objects
65  // can be in the repository tree, and thus can suffer from data
66  // inconsistencies as well.
67  // Werner, 2007-04-16
68  public $object_types_exclude = array("adm","root","mail","usrf","objf","lngf",
69  "trac","taxf","auth","rolf","assf","svyf","extt","adve","fold");
70 
75  public $mode = array(
76  "scan" => true, // gather information about corrupted entries
77  "dump_tree" => false, // dump tree
78  "clean" => false, // remove all unusable entries & renumber tree
79  "restore" => false, // restore objects with invalid parent to RecoveryFolder
80  "purge" => false, // delete all objects with invalid parent from system
81  "restore_trash" => false, // restore all objects in trash to RecoveryFolder
82  "purge_trash" => false // delete all objects in trash from system
83  );
84 
89  public $invalid_references = array();
90 
95  public $invalid_childs = array();
96 
101  public $missing_objects = array();
102 
107  public $unbound_objects = array();
108 
113  public $deleted_objects = array();
114 
121  public $invalid_rolefolders = array();
122 
127  public $invalid_objects = array();
128 
133  public $logging = false;
134 
139  public $scan_log;
140 
141  public $scan_log_file = "scanlog.log";
142 
143  public $scan_log_separator = "<!-- scan log start -->";
144 
151  public function __construct($a_log = false)
152  {
153  global $DIC;
154 
155  $this->obj_definition = $DIC["objDefinition"];
156  $this->db = $DIC->database();
157  $this->lng = $DIC->language();
158  $this->log = $DIC["ilLog"];
159  $this->rbacadmin = $DIC->rbac()->admin();
160  $this->tree = $DIC->repositoryTree();
161  $this->user = $DIC->user();
162  $objDefinition = $DIC["objDefinition"];
163  $ilDB = $DIC->database();
164 
165  $this->db = &$ilDB;
166  $this->rbac_object_types = "'" . implode("','", $objDefinition->getAllRBACObjects()) . "'";
167  $this->rbac_object_types = $objDefinition->getAllRBACObjects();
168 
169  if ($a_log === true) {
170  $this->logging = true;
171 
172  // should be available thru inc.header.php
173  // TODO: move log functionality to new class ilScanLog
174  include_once "./Services/Logging/classes/class.ilLog.php";
175 
176  // Delete old scan log
177  $this->deleteScanLog();
178 
179  // create scan log
180  include_once './Services/Logging/classes/class.ilLog.php';
181  $this->scan_log = new ilLog(CLIENT_DATA_DIR, "scanlog.log");
182  $this->scan_log->setLogFormat("");
183  $this->writeScanLogLine($this->scan_log_separator);
184  $this->writeScanLogLine("\n[Systemscan from " . date("y-m-d H:i]"));
185  }
186  }
187 
193  public function getPossibleModes()
194  {
195  return array_keys($this->mode);
196  }
197 
209  public function setMode($a_mode, $a_value)
210  {
211  if ((!in_array($a_mode, array_keys($this->mode)) and $a_mode != "all") or !is_bool($a_value)) {
212  $this->throwError(INVALID_PARAM, FATAL, DEBUG);
213  return false;
214  }
215 
216  if ($a_mode == "all") {
217  foreach ($this->mode as $mode => $value) {
218  $this->mode[$mode] = $a_value;
219  }
220  } else {
221  $this->mode[$a_mode] = $a_value;
222  }
223 
224  // consider mode dependencies
225  $this->setModeDependencies();
226 
227  return true;
228  }
229 
238  public function isModeEnabled($a_mode)
239  {
240  if (!in_array($a_mode, array_keys($this->mode))) {
241  $this->throwError(VALIDATER_UNKNOWN_MODE, WARNING, DEBUG);
242  return false;
243  }
244 
245  return $this->mode[$a_mode];
246  }
247 
248  public function isLogEnabled()
249  {
250  return $this->logging;
251  }
252 
261  public function setModeDependencies()
262  {
263  // DO NOT change the order
264 
265  if ($this->mode["restore"] === true) {
266  $this->mode["scan"] = true;
267  $this->mode["purge"] = false;
268  }
269 
270  if ($this->mode["purge"] === true) {
271  $this->mode["scan"] = true;
272  $this->mode["restore"] = false;
273  }
274 
275  if ($this->mode["restore_trash"] === true) {
276  $this->mode["scan"] = true;
277  $this->mode["purge_trash"] = false;
278  }
279 
280  if ($this->mode["purge_trash"] === true) {
281  $this->mode["scan"] = true;
282  $this->mode["restore_trash"] = false;
283  }
284 
285  if ($this->mode["clean"] === true) {
286  $this->mode["scan"] = true;
287  }
288  }
289 
298  public function validate()
299  {
300  $lng = $this->lng;
301 
302  // The validation summary.
303  $summary = "";
304 
305 
306  // STEP 1: Scan
307  // -------------------
308  $summary .= $lng->txt("scanning_system");
309  if (!$this->isModeEnabled("scan")) {
310  $summary .= $lng->txt("disabled");
311  } else {
312  $summary .= "<br/>" . $lng->txt("searching_invalid_refs");
313  if ($this->findInvalidReferences()) {
314  $summary .= count($this->getInvalidReferences()) . " " . $lng->txt("found");
315  } else {
316  $summary .= $lng->txt("found_none");
317  }
318 
319  $summary .= "<br/>" . $lng->txt("searching_invalid_childs");
320  if ($this->findInvalidChilds()) {
321  $summary .= count($this->getInvalidChilds()) . " " . $lng->txt("found");
322  } else {
323  $summary .= $lng->txt("found_none");
324  }
325 
326  $summary .= "<br/>" . $lng->txt("searching_missing_objs");
327  if ($this->findMissingObjects()) {
328  $summary .= count($this->getMissingObjects()) . " " . $lng->txt("found");
329  } else {
330  $summary .= $lng->txt("found_none");
331  }
332 
333  $summary .= "<br/>" . $lng->txt("searching_unbound_objs");
334  if ($this->findUnboundObjects()) {
335  $summary .= count($this->getUnboundObjects()) . " " . $lng->txt("found");
336  } else {
337  $summary .= $lng->txt("found_none");
338  }
339 
340  $summary .= "<br/>" . $lng->txt("searching_deleted_objs");
341  if ($this->findDeletedObjects()) {
342  $summary .= count($this->getDeletedObjects()) . " " . $lng->txt("found");
343  } else {
344  $summary .= $lng->txt("found_none");
345  }
346 
347  $summary .= "<br/>" . $lng->txt("searching_invalid_rolfs");
348  if ($this->findInvalidRolefolders()) {
349  $summary .= count($this->getInvalidRolefolders()) . " " . $lng->txt("found");
350  } else {
351  $summary .= $lng->txt("found_none");
352  }
353 
354  $summary .= "<br/><br/>" . $lng->txt("analyzing_tree_structure");
355  if ($this->checkTreeStructure()) {
356  $summary .= $lng->txt("tree_corrupt");
357  } else {
358  $summary .= $lng->txt("done");
359  }
360  }
361 
362  // STEP 2: Dump tree
363  // -------------------
364  $summary .= "<br /><br />" . $lng->txt("dumping_tree");
365  if (!$this->isModeEnabled("dump_tree")) {
366  $summary .= $lng->txt("disabled");
367  } else {
368  $error_count = $this->dumpTree();
369  if ($error_count > 0) {
370  $summary .= $lng->txt("tree_corrupt");
371  } else {
372  $summary .= $lng->txt("done");
373  }
374  }
375 
376  // STEP 3: Clean Up
377  // -------------------
378  $summary .= "<br /><br />" . $lng->txt("cleaning");
379  if (!$this->isModeEnabled("clean")) {
380  $summary .= $lng->txt("disabled");
381  } else {
382  $summary .= "<br />" . $lng->txt("removing_invalid_refs");
383  if ($this->removeInvalidReferences()) {
384  $summary .= strtolower($lng->txt("done"));
385  } else {
386  $summary .= $lng->txt("nothing_to_remove") . $lng->txt("skipped");
387  }
388 
389  $summary .= "<br />" . $lng->txt("removing_invalid_childs");
390  if ($this->removeInvalidChilds()) {
391  $summary .= strtolower($lng->txt("done"));
392  } else {
393  $summary .= $lng->txt("nothing_to_remove") . $lng->txt("skipped");
394  }
395 
396  $summary .= "<br />" . $lng->txt("removing_invalid_rolfs");
397  if ($this->removeInvalidRolefolders()) {
398  $summary .= strtolower($lng->txt("done"));
399  } else {
400  $summary .= $lng->txt("nothing_to_remove") . $lng->txt("skipped");
401  }
402 
403  // find unbound objects again AFTER cleaning process!
404  // This updates the array 'unboundobjects' required for the further steps
405  // There might be other objects unbounded now due to removal of object_data/reference entries.
406  $this->findUnboundObjects();
407  }
408 
409  // STEP 4: Restore objects
410  $summary .= "<br /><br />" . $lng->txt("restoring");
411 
412  if (!$this->isModeEnabled("restore")) {
413  $summary .= $lng->txt("disabled");
414  } else {
415  $summary .= "<br />" . $lng->txt("restoring_missing_objs");
416  if ($this->restoreMissingObjects()) {
417  $summary .= strtolower($lng->txt("done"));
418  } else {
419  $summary .= $lng->txt("nothing_to_restore") . $lng->txt("skipped");
420  }
421 
422  $summary .= "<br />" . $lng->txt("restoring_unbound_objs");
423  if ($this->restoreUnboundObjects()) {
424  $summary .= strtolower($lng->txt("done"));
425  } else {
426  $summary .= $lng->txt("nothing_to_restore") . $lng->txt("skipped");
427  }
428  }
429 
430  // STEP 5: Restoring Trash
431  $summary .= "<br /><br />" . $lng->txt("restoring_trash");
432 
433  if (!$this->isModeEnabled("restore_trash")) {
434  $summary .= $lng->txt("disabled");
435  } else {
436  if ($this->restoreTrash()) {
437  $summary .= strtolower($lng->txt("done"));
438  } else {
439  $summary .= $lng->txt("nothing_to_restore") . $lng->txt("skipped");
440  }
441  }
442 
443  // STEP 6: Purging...
444  $summary .= "<br /><br />" . $lng->txt("purging");
445 
446  if (!$this->isModeEnabled("purge")) {
447  $summary .= $lng->txt("disabled");
448  } else {
449  $summary .= "<br />" . $lng->txt("purging_missing_objs");
450  if ($this->purgeMissingObjects()) {
451  $summary .= strtolower($lng->txt("done"));
452  } else {
453  $summary .= $lng->txt("nothing_to_purge") . $lng->txt("skipped");
454  }
455 
456  $summary .= "<br />" . $lng->txt("purging_unbound_objs");
457  if ($this->purgeUnboundObjects()) {
458  $summary .= strtolower($lng->txt("done"));
459  } else {
460  $summary .= $lng->txt("nothing_to_purge") . $lng->txt("skipped");
461  }
462  }
463 
464  // STEP 7: Purging trash...
465  $summary .= "<br /><br />" . $lng->txt("purging_trash");
466 
467  if (!$this->isModeEnabled("purge_trash")) {
468  $summary .= $lng->txt("disabled");
469  } else {
470  if ($this->purgeTrash()) {
471  $summary .= strtolower($lng->txt("done"));
472  } else {
473  $summary .= $lng->txt("nothing_to_purge") . $lng->txt("skipped");
474  }
475  }
476 
477  // STEP 8: Initialize gaps in tree
478  if ($this->isModeEnabled("clean")) {
479  $summary .= "<br /><br />" . $lng->txt("cleaning_final");
480  if ($this->initGapsInTree()) {
481  $summary .= "<br />" . $lng->txt("initializing_gaps") . " " . strtolower($lng->txt("done"));
482  }
483  }
484 
485  // check RBAC starts here
486  // ...
487 
488  // le fin
489  foreach ($this->mode as $mode => $value) {
490  $arr[] = $mode . "[" . (int) $value . "]";
491  }
492 
493  return $summary;
494  }
495 
496 
506  public function findMissingObjects()
507  {
508  $ilDB = $this->db;
509 
510  // check mode: analyze
511  if ($this->mode["scan"] !== true) {
512  return false;
513  }
514 
515  // init
516  $this->missing_objects = array();
517 
518  $this->writeScanLogLine("\nfindMissingObjects:");
519 
520  // Repair missing objects.
521  // We only repair file objects which have an entry in table object_reference.
522  // XXX - We should check all references to file objects which don't
523  // have an object_reference. If we can't find any reference to such
524  // a file object, we should repair it too!
525  $q = "SELECT object_data.*, ref_id FROM object_data " .
526  "LEFT JOIN object_reference ON object_data.obj_id = object_reference.obj_id " .
527  "LEFT JOIN tree ON object_reference.ref_id = tree.child " .
528  "WHERE tree.child IS NULL " .
529  "AND (object_reference.obj_id IS NOT NULL " .
530  " OR object_data.type <> 'file' AND " .
531  $ilDB->in('object_data.type', $this->rbac_object_types, false, 'text') .
532  ")";
533  $r = $this->db->query($q);
534 
535  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
536  #if (!in_array($row->type,$this->object_types_exclude))
537  if (!$this->isExcludedFromRecovery($row->type, $row->obj_id)) {
538  $this->missing_objects[] = array(
539  "obj_id" => $row->obj_id,
540  "type" => $row->type,
541  "ref_id" => $row->ref_id,
542  "child" => $row->child,
543  "title" => $row->title,
544  "desc" => $row->description,
545  "owner" => $row->owner,
546  "create_date" => $row->create_date,
547  "last_update" => $row->last_update
548  );
549  }
550  }
551 
552  $this->filterWorkspaceObjects($this->missing_objects);
553  if (count($this->missing_objects) > 0) {
554  $this->writeScanLogLine("obj_id\ttype\tref_id\tchild\ttitle\tdesc\towner\tcreate_date\tlast_update");
555  $this->writeScanLogArray($this->missing_objects);
556  return true;
557  }
558 
559  $this->writeScanLogLine("none");
560  return false;
561  }
562 
574  public function findInvalidRolefolders()
575  {
576  $ilDB = $this->db;
577 
578  // check mode: analyze
579  if ($this->mode["scan"] !== true) {
580  return false;
581  }
582 
583  // init
584  $this->invalid_rolefolders = array();
585 
586  $this->writeScanLogLine("\nfindInvalidRolefolders:");
587 
588  // find rolfs without reference/tree entry
589  $q = "SELECT object_data.*, ref_id FROM object_data " .
590  "LEFT JOIN object_reference ON object_data.obj_id = object_reference.obj_id " .
591  "LEFT JOIN tree ON object_reference.ref_id = tree.child " .
592  "WHERE (object_reference.obj_id IS NULL OR tree.child IS NULL) " .
593  "AND object_data.type='rolf'";
594  $r = $this->db->query($q);
595 
596  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
597  $this->invalid_rolefolders[] = array(
598  "obj_id" => $row->obj_id,
599  "type" => $row->type,
600  "ref_id" => $row->ref_id,
601  "child" => $row->child,
602  "title" => $row->title,
603  "desc" => $row->description,
604  "owner" => $row->owner,
605  "create_date" => $row->create_date,
606  "last_update" => $row->last_update
607  );
608  }
609 
610  // find rolfs within RECOVERY FOLDER
611  $q = "SELECT object_data.*, ref_id FROM object_data " .
612  "LEFT JOIN object_reference ON object_data.obj_id = object_reference.obj_id " .
613  "LEFT JOIN tree ON object_reference.ref_id = tree.child " .
614  "WHERE object_reference.ref_id = " . $ilDB->quote(RECOVERY_FOLDER_ID, 'integer') . " " .
615  "AND object_data.type='rolf'";
616  $r = $this->db->query($q);
617 
618  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
619  $this->invalid_rolefolders[] = array(
620  "obj_id" => $row->obj_id,
621  "type" => $row->type,
622  "ref_id" => $row->ref_id,
623  "child" => $row->child,
624  "title" => $row->title,
625  "desc" => $row->description,
626  "owner" => $row->owner,
627  "create_date" => $row->create_date,
628  "last_update" => $row->last_update
629  );
630  }
631 
632  $this->filterWorkspaceObjects($this->invalid_rolefolders);
633  if (count($this->invalid_rolefolders) > 0) {
634  $this->writeScanLogLine("obj_id\ttype\tref_id\tchild\ttitle\tdesc\towner\tcreate_date\tlast_update");
635  $this->writeScanLogArray($this->invalid_rolefolders);
636  return true;
637  }
638 
639  $this->writeScanLogLine("none");
640  return false;
641  }
642 
652  public function findInvalidRBACEntries()
653  {
654  $ilDB = $this->db;
655 
656  // check mode: analyze
657  if ($this->mode["scan"] !== true) {
658  return false;
659  }
660 
661  // init
662  $this->invalid_rbac_entries = array();
663 
664  $this->writeScanLogLine("\nfindInvalidRBACEntries:");
665 
666  $q = "SELECT object_data.*, ref_id FROM object_data " .
667  "LEFT JOIN object_reference ON object_data.obj_id = object_reference.obj_id " .
668  "LEFT JOIN tree ON object_reference.ref_id = tree.child " .
669  "WHERE (object_reference.obj_id IS NULL OR tree.child IS NULL) " .
670  "AND object_data.type='rolf'";
671  $r = $this->db->query($q);
672 
673  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
674  $this->invalid_rolefolders[] = array(
675  "obj_id" => $row->obj_id,
676  "type" => $row->type,
677  "ref_id" => $row->ref_id,
678  "child" => $row->child,
679  "title" => $row->title,
680  "desc" => $row->description,
681  "owner" => $row->owner,
682  "create_date" => $row->create_date,
683  "last_update" => $row->last_update
684  );
685  }
686 
687  // find rolfs within RECOVERY FOLDER
688  $q = "SELECT object_data.*, ref_id FROM object_data " .
689  "LEFT JOIN object_reference ON object_data.obj_id = object_reference.obj_id " .
690  "LEFT JOIN tree ON object_reference.ref_id = tree.child " .
691  "WHERE object_reference.ref_id =" . $ilDB->quote(RECOVERY_FOLDER_ID) . " " .
692  "AND object_data.type='rolf'";
693  $r = $this->db->query($q);
694 
695  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
696  $this->invalid_rolefolders[] = array(
697  "obj_id" => $row->obj_id,
698  "type" => $row->type,
699  "ref_id" => $row->ref_id,
700  "child" => $row->child,
701  "title" => $row->title,
702  "desc" => $row->description,
703  "owner" => $row->owner,
704  "create_date" => $row->create_date,
705  "last_update" => $row->last_update
706  );
707  }
708 
709  $this->filterWorkspaceObjects($this->invalid_rolefolders);
710  if (count($this->invalid_rolefolders) > 0) {
711  $this->writeScanLogLine("obj_id\ttype\tref_id\tchild\ttitle\tdesc\towner\tcreate_date\tlast_update");
712  $this->writeScanLogArray($this->invalid_rolefolders);
713  return true;
714  }
715 
716  $this->writeScanLogLine("none");
717  return false;
718  }
719 
733  public function getMissingObjects()
734  {
735  return $this->missing_objects;
736  }
737 
747  public function findInvalidReferences()
748  {
749  $ilDB = $this->db;
750 
751  // check mode: analyze
752  if ($this->mode["scan"] !== true) {
753  return false;
754  }
755 
756  // init
757  $this->invalid_references = array();
758 
759  $this->writeScanLogLine("\nfindInvalidReferences:");
760  $q = "SELECT object_reference.* FROM object_reference " .
761  "LEFT JOIN object_data ON object_data.obj_id = object_reference.obj_id " .
762  "WHERE object_data.obj_id IS NULL " .
763  "OR " . $ilDB->in('object_data.type', $this->rbac_object_types, true, 'text');
764  $r = $this->db->query($q);
765 
766  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
767  $this->invalid_references[] = array(
768  "ref_id" => $row->ref_id,
769  "obj_id" => $row->obj_id,
770  "msg" => "Object does not exist."
771  );
772  }
773 
774  $this->filterWorkspaceObjects($this->invalid_references);
775  if (count($this->invalid_references) > 0) {
776  $this->writeScanLogLine("ref_id\t\tobj_id");
777  $this->writeScanLogArray($this->invalid_references);
778  return true;
779  }
780 
781  $this->writeScanLogLine("none");
782  return false;
783  }
784 
793  public function getInvalidReferences()
794  {
796  }
797 
807  public function findInvalidChilds()
808  {
809  $ilDB = $this->db;
810 
811  // check mode: analyze
812  if ($this->mode["scan"] !== true) {
813  return false;
814  }
815 
816  // init
817  $this->invalid_childs = array();
818 
819  $this->writeScanLogLine("\nfindInvalidChilds:");
820 
821  $q = "SELECT tree.*,object_reference.ref_id FROM tree " .
822  "LEFT JOIN object_reference ON tree.child = object_reference.ref_id " .
823  "LEFT JOIN object_data ON object_reference.obj_id = object_data.obj_id " .
824  "WHERE object_reference.ref_id IS NULL or object_data.obj_id IS NULL";
825  $r = $this->db->query($q);
826  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
827  $this->invalid_childs[] = array(
828  "child" => $row->child,
829  "ref_id" => $row->ref_id,
830  "msg" => "No object found"
831  );
832  }
833 
834  if (count($this->invalid_childs) > 0) {
835  $this->writeScanLogLine("child\t\tref_id");
836  $this->writeScanLogArray($this->invalid_childs);
837  return true;
838  }
839 
840  $this->writeScanLogLine("none");
841  return false;
842  }
843 
852  public function getInvalidChilds()
853  {
854  return $this->invalid_childs;
855  }
856 
868  public function findUnboundObjects()
869  {
870  // check mode: analyze
871  if ($this->mode["scan"] !== true) {
872  return false;
873  }
874 
875  // init
876  $this->unbound_objects = array();
877 
878  $this->writeScanLogLine("\nfindUnboundObjects:");
879 
880  $q = "SELECT T1.tree,T1.child,T1.parent," .
881  "T2.tree deleted,T2.parent grandparent " .
882  "FROM tree T1 " .
883  "LEFT JOIN tree T2 ON T2.child=T1.parent " .
884  "WHERE (T2.tree!=1 OR T2.tree IS NULL) AND T1.parent!=0";
885  $r = $this->db->query($q);
886 
887  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
888  // exclude deleted nodes
889  if ($row->deleted === null) {
890  $this->unbound_objects[] = array(
891  "child" => $row->child,
892  "parent" => $row->parent,
893  "tree" => $row->tree,
894  "msg" => "No valid parent node found"
895  );
896  }
897  }
898 
899  if (count($this->unbound_objects) > 0) {
900  $this->writeScanLogLine("child\t\tparent\ttree");
901  $this->writeScanLogArray($this->unbound_objects);
902  return true;
903  }
904 
905  $this->writeScanLogLine("none");
906  return false;
907  }
908 
920  public function findDeletedObjects()
921  {
922  // check mode: analyze
923  if ($this->mode["scan"] !== true) {
924  return false;
925  }
926 
927  // init
928  $this->deleted_objects = array();
929 
930  $this->writeScanLogLine("\nfindDeletedObjects:");
931 
932  // Delete objects, start with the oldest objects first
933  $query = "SELECT object_data.*,tree.tree,tree.child,tree.parent,deleted " .
934  "FROM object_data " .
935  "LEFT JOIN object_reference ON object_data.obj_id=object_reference.obj_id " .
936  "LEFT JOIN tree ON tree.child=object_reference.ref_id " .
937  " WHERE tree != 1 " .
938  " ORDER BY deleted";
939  $r = $this->db->query($query);
940 
941  include_once './Services/Calendar/classes/class.ilDateTime.php';
942  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
943  $tmp_date = new ilDateTime($row->deleted, IL_CAL_DATETIME);
944 
945  $this->deleted_objects[] = array(
946  "child" => $row->child,
947  "parent" => $row->parent,
948  "tree" => $row->tree,
949  "type" => $row->type,
950  "title" => $row->title,
951  "desc" => $row->description,
952  "owner" => $row->owner,
953  "deleted" => $row->deleted,
954  "deleted_timestamp" => $tmp_date->get(IL_CAL_UNIX),
955  "create_date" => $row->create_date,
956  "last_update" => $row->last_update
957  );
958  }
959 
960  if (count($this->deleted_objects) > 0) {
961  $this->writeScanLogArray(array(array_keys($this->deleted_objects[0])));
962  $this->writeScanLogArray($this->deleted_objects);
963  return true;
964  }
965  $this->writeScanLogLine("none");
966  return false;
967  }
968 
969 
983  public function getUnboundObjects()
984  {
985  return $this->unbound_objects;
986  }
987 
994  public function getDeletedObjects()
995  {
996  return $this->deleted_objects;
997  }
998 
1007  public function getInvalidRolefolders()
1008  {
1010  }
1011 
1021  public function removeInvalidReferences($a_invalid_refs = null)
1022  {
1023  $ilLog = $this->log;
1024  $ilDB = $this->db;
1025 
1026  // check mode: clean
1027  if ($this->mode["clean"] !== true) {
1028  return false;
1029  }
1030 
1031  $this->writeScanLogLine("\nremoveInvalidReferences:");
1032 
1033  if ($a_invalid_refs === null and isset($this->invalid_references)) {
1034  $a_invalid_refs = &$this->invalid_references;
1035  }
1036 
1037  // handle wrong input
1038  if (!is_array($a_invalid_refs)) {
1039  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1040  return false;
1041  }
1042  // no unbound references found. do nothing
1043  if (count($a_invalid_refs) == 0) {
1044  $this->writeScanLogLine("none");
1045  return false;
1046  }
1047 
1048  /*******************
1049  removal starts here
1050  ********************/
1051 
1052  $message = sprintf(
1053  '%s::removeInvalidReferences(): Started...',
1054  get_class($this)
1055  );
1056  $ilLog->write($message, $ilLog->WARNING);
1057 
1058  // to make sure
1059  $this->filterWorkspaceObjects($a_invalid_refs);
1060 
1061  foreach ($a_invalid_refs as $entry) {
1062  $query = "DELETE FROM object_reference WHERE ref_id= " . $this->db->quote($entry["ref_id"], 'integer') .
1063  " AND obj_id = " . $this->db->quote($entry["obj_id"], 'integer') . " ";
1064  $res = $ilDB->manipulate($query);
1065 
1066  $message = sprintf(
1067  '%s::removeInvalidReferences(): Reference %s removed',
1068  get_class($this),
1069  $entry["ref_id"]
1070  );
1071  $ilLog->write($message, $ilLog->WARNING);
1072 
1073  $this->writeScanLogLine("Entry " . $entry["ref_id"] . " removed");
1074  }
1075 
1076  return true;
1077  }
1078 
1088  public function removeInvalidChilds($a_invalid_childs = null)
1089  {
1090  $ilLog = $this->log;
1091 
1092  // check mode: clean
1093  if ($this->mode["clean"] !== true) {
1094  return false;
1095  }
1096 
1097  $this->writeScanLogLine("\nremoveInvalidChilds:");
1098 
1099  if ($a_invalid_childs === null and isset($this->invalid_childs)) {
1100  $a_invalid_childs = &$this->invalid_childs;
1101  }
1102 
1103  // handle wrong input
1104  if (!is_array($a_invalid_childs)) {
1105  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1106  return false;
1107  }
1108 
1109  // no unbound childs found. do nothing
1110  if (count($a_invalid_childs) == 0) {
1111  $this->writeScanLogLine("none");
1112  return false;
1113  }
1114 
1115  /*******************
1116  removal starts here
1117  ********************/
1118 
1119  $message = sprintf(
1120  '%s::removeInvalidChilds(): Started...',
1121  get_class($this)
1122  );
1123  $ilLog->write($message, $ilLog->WARNING);
1124 
1125  foreach ($a_invalid_childs as $entry) {
1126  $q = "DELETE FROM tree WHERE child='" . $entry["child"] . "'";
1127  $this->db->query($q);
1128 
1129  $message = sprintf(
1130  '%s::removeInvalidChilds(): Entry child=%s removed',
1131  get_class($this),
1132  $entry["child"]
1133  );
1134  $ilLog->write($message, $ilLog->WARNING);
1135 
1136  $this->writeScanLogLine("Entry " . $entry["child"] . " removed");
1137  }
1138 
1139  return true;
1140  }
1141 
1152  public function removeInvalidRolefolders($a_invalid_rolefolders = null)
1153  {
1154  $ilLog = $this->log;
1155 
1156  // check mode: clean
1157  if ($this->mode["clean"] !== true) {
1158  return false;
1159  }
1160 
1161  $this->writeScanLogLine("\nremoveInvalidRolefolders:");
1162 
1163  if ($a_invalid_rolefolders === null and isset($this->invalid_rolefolders)) {
1164  $a_invalid_rolefolders = $this->invalid_rolefolders;
1165  }
1166 
1167  // handle wrong input
1168  if (!is_array($a_invalid_rolefolders)) {
1169  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1170  return false;
1171  }
1172 
1173  // no invalid rolefolders found. do nothing
1174  if (count($a_invalid_rolefolders) == 0) {
1175  $this->writeScanLogLine("none");
1176  return false;
1177  }
1178 
1179  /*******************
1180  removal starts here
1181  ********************/
1182 
1183  $removed = false;
1184 
1185  $message = sprintf(
1186  '%s::removeInvalidRolefolders(): Started...',
1187  get_class($this)
1188  );
1189  $ilLog->write($message, $ilLog->WARNING);
1190 
1191  // to make sure
1192  $this->filterWorkspaceObjects($a_invalid_rolefolders);
1193 
1194  foreach ($a_invalid_rolefolders as $rolf) {
1195  // restore ref_id in case of missing
1196  if ($rolf["ref_id"] === null) {
1197  $rolf["ref_id"] = $this->restoreReference($rolf["obj_id"]);
1198 
1199  $this->writeScanLogLine("Created missing reference '" . $rolf["ref_id"] . "' for rolefolder object '" . $rolf["obj_id"] . "'");
1200  }
1201 
1202  // now delete rolefolder
1203  $obj_data = ilObjectFactory::getInstanceByRefId($rolf["ref_id"]);
1204  $obj_data->delete();
1205  unset($obj_data);
1206  $removed = true;
1207  $this->writeScanLogLine("Removed invalid rolefolder '" . $rolf["title"] . "' (id=" . $rolf["obj_id"] . ",ref=" . $rolf["ref_id"] . ") from system");
1208  }
1209 
1210  return $removed;
1211  }
1212 
1223  public function restoreMissingObjects($a_missing_objects = null)
1224  {
1226  $ilLog = $this->log;
1227 
1228  // check mode: restore
1229  if ($this->mode["restore"] !== true) {
1230  return false;
1231  }
1232 
1233  $this->writeScanLogLine("\nrestoreMissingObjects:");
1234 
1235  if ($a_missing_objects === null and isset($this->missing_objects)) {
1236  $a_missing_objects = $this->missing_objects;
1237  }
1238 
1239  // handle wrong input
1240  if (!is_array($a_missing_objects)) {
1241  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1242  return false;
1243  }
1244 
1245  // no missing objects found. do nothing
1246  if (count($a_missing_objects) == 0) {
1247  $this->writeScanLogLine("none");
1248  return false;
1249  }
1250 
1251  /*******************
1252  restore starts here
1253  ********************/
1254 
1255  $restored = false;
1256 
1257  $message = sprintf(
1258  '%s::restoreMissingObjects(): Started...',
1259  get_class($this)
1260  );
1261  $ilLog->write($message, $ilLog->WARNING);
1262 
1263  // to make sure
1264  $this->filterWorkspaceObjects($a_missing_objects);
1265 
1266  foreach ($a_missing_objects as $missing_obj) {
1267  // restore ref_id in case of missing
1268  if ($missing_obj["ref_id"] === null) {
1269  $missing_obj["ref_id"] = $this->restoreReference($missing_obj["obj_id"]);
1270 
1271  $this->writeScanLogLine("Created missing reference '" . $missing_obj["ref_id"] . "' for object '" . $missing_obj["obj_id"] . "'");
1272  }
1273 
1274  // put in tree under RecoveryFolder if not on exclude list
1275  #if (!in_array($missing_obj["type"],$this->object_types_exclude))
1276  if (!$this->isExcludedFromRecovery($missing_obj['type'], $missing_obj['obj_id'])) {
1277  $rbacadmin->revokePermission($missing_obj["ref_id"]);
1278  $obj_data = ilObjectFactory::getInstanceByRefId($missing_obj["ref_id"]);
1279  $obj_data->putInTree(RECOVERY_FOLDER_ID);
1280  $obj_data->setPermissions(RECOVERY_FOLDER_ID);
1281  unset($obj_data);
1282  //$tree->insertNode($missing_obj["ref_id"],RECOVERY_FOLDER_ID);
1283  $restored = true;
1284  $this->writeScanLogLine("Restored object '" . $missing_obj["title"] . "' (id=" . $missing_obj["obj_id"] . ",ref=" . $missing_obj["ref_id"] . ") in 'Restored objects folder'");
1285  }
1286 
1287  // TODO: process rolefolders
1288  }
1289 
1290  return $restored;
1291  }
1292 
1302  public function restoreReference($a_obj_id)
1303  {
1304  $ilLog = $this->log;
1305  $ilDB = $this->db;
1306 
1307  if (empty($a_obj_id)) {
1308  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1309  return false;
1310  }
1311 
1312  $query = "INSERT INTO object_reference (ref_id,obj_id) " .
1313  "VALUES (" . $next_id = $ilDB->nextId('object_reference') . "," . $this->db->quote($a_obj_id, 'integer') . " )";
1314  $res = $ilDB->manipulate($query);
1315 
1316  $message = sprintf(
1317  '%s::restoreReference(): new reference %s for obj_id %s created',
1318  get_class($this),
1319  $next_id,
1320  $a_obj_id
1321  );
1322  $ilLog->write($message, $ilLog->WARNING);
1323 
1324  return $next_id;
1325  }
1326 
1337  public function restoreUnboundObjects($a_unbound_objects = null)
1338  {
1339  $ilLog = $this->log;
1340 
1341  // check mode: restore
1342  if ($this->mode["restore"] !== true) {
1343  return false;
1344  }
1345 
1346  $this->writeScanLogLine("\nrestoreUnboundObjects:");
1347 
1348  if ($a_unbound_objects === null and isset($this->unbound_objects)) {
1349  $a_unbound_objects = $this->unbound_objects;
1350  }
1351 
1352  // handle wrong input
1353  if (!is_array($a_unbound_objects)) {
1354  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1355  return false;
1356  }
1357 
1358  $message = sprintf(
1359  '%s::restoreUnboundObjects(): Started...',
1360  get_class($this)
1361  );
1362  $ilLog->write($message, $ilLog->WARNING);
1363 
1364  // start restore process
1365  return $this->restoreSubTrees($a_unbound_objects);
1366  }
1367 
1377  public function restoreTrash($a_deleted_objects = null)
1378  {
1379  $ilLog = $this->log;
1380 
1381  // check mode: restore
1382  if ($this->mode["restore_trash"] !== true) {
1383  return false;
1384  }
1385 
1386  $this->writeScanLogLine("\nrestoreTrash:");
1387 
1388  if ($a_deleted_objects === null and isset($this->deleted_objects)) {
1389  $a_deleted_objects = $this->deleted_objects;
1390  }
1391 
1392  // handle wrong input
1393  if (!is_array($a_deleted_objects)) {
1394  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1395  return false;
1396  }
1397 
1398  $message = sprintf(
1399  '%s::restoreTrash(): Started...',
1400  get_class($this)
1401  );
1402  $ilLog->write($message, $ilLog->WARNING);
1403 
1404  // start restore process
1405  $restored = $this->restoreDeletedObjects($a_deleted_objects);
1406 
1407  if ($restored) {
1408  $q = "DELETE FROM tree WHERE tree!=1";
1409  $this->db->query($q);
1410 
1411  $message = sprintf(
1412  '%s::restoreTrash(): Removed all trees with tree id <> 1',
1413  get_class($this)
1414  );
1415  $ilLog->write($message, $ilLog->WARNING);
1416 
1417  $this->writeScanLogLine("Old tree entries removed");
1418  }
1419 
1420  return $restored;
1421  }
1422 
1431  public function restoreDeletedObjects($a_nodes)
1432  {
1433  $tree = $this->tree;
1435  $ilLog = $this->log;
1436  //vd($a_nodes);exit;
1437  // handle wrong input
1438  if (!is_array($a_nodes)) {
1439  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1440  return false;
1441  }
1442 
1443  // no invalid parents found. do nothing
1444  if (count($a_nodes) == 0) {
1445  $this->writeScanLogLine("none");
1446  return false;
1447  }
1448 
1449  $message = sprintf(
1450  '%s::restoreDeletedObjects()): Started...',
1451  get_class($this)
1452  );
1453  $ilLog->write($message, $ilLog->WARNING);
1454 
1455  // first delete all rolefolders
1456  // don't save rolefolders, remove them
1457  // TODO process ROLE_FOLDER_ID
1458  foreach ($a_nodes as $key => $node) {
1459  if ($node["type"] == "rolf") {
1460  // delete old tree entries
1461  $tree->deleteTree($node);
1462 
1463  $obj_data = ilObjectFactory::getInstanceByRefId($node["child"]);
1464  $obj_data->delete();
1465  unset($a_nodes[$key]);
1466  }
1467  }
1468 
1469  // process move
1470  foreach ($a_nodes as $node) {
1471  // delete old tree entries
1472  $tree->deleteTree($node);
1473 
1474  $rbacadmin->revokePermission($node["child"]);
1475  $obj_data = ilObjectFactory::getInstanceByRefId($node["child"]);
1476  $obj_data->putInTree(RECOVERY_FOLDER_ID);
1477  $obj_data->setPermissions(RECOVERY_FOLDER_ID);
1478  }
1479 
1480  return true;
1481  }
1482 
1491  public function restoreSubTrees($a_nodes)
1492  {
1493  $tree = $this->tree;
1495  $ilLog = $this->log;
1496 
1497  // handle wrong input
1498  if (!is_array($a_nodes)) {
1499  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1500  return false;
1501  }
1502 
1503  // no invalid parents found. do nothing
1504  if (count($a_nodes) == 0) {
1505  $this->writeScanLogLine("none");
1506  return false;
1507  }
1508 
1509  /*******************
1510  restore starts here
1511  ********************/
1512 
1513  $subnodes = array();
1514  $topnode = array();
1515 
1516  $message = sprintf(
1517  '%s::restoreSubTrees(): Started...',
1518  get_class($this)
1519  );
1520  $ilLog->write($message, $ilLog->WARNING);
1521 
1522  // process move subtree
1523  foreach ($a_nodes as $node) {
1524  // get node data
1525  $topnode = $tree->getNodeData($node["child"], $node['tree']);
1526 
1527  // don't save rolefolders, remove them
1528  // TODO process ROLE_FOLDER_ID
1529  if ($topnode["type"] == "rolf") {
1530  $rolfObj = ilObjectFactory::getInstanceByRefId($topnode["child"]);
1531  $rolfObj->delete();
1532  unset($top_node);
1533  unset($rolfObj);
1534  continue;
1535  }
1536 
1537  // get subnodes of top nodes
1538  $subnodes[$node["child"]] = $tree->getSubtree($topnode);
1539 
1540  // delete old tree entries
1541  $tree->deleteTree($topnode);
1542  }
1543 
1544  // now move all subtrees to new location
1545  // TODO: this whole put in place again stuff needs revision. Permission settings get lost.
1546  foreach ($subnodes as $key => $subnode) {
1547 
1548  // first paste top_node ...
1549  $rbacadmin->revokePermission($key);
1551  $obj_data->putInTree(RECOVERY_FOLDER_ID);
1552  $obj_data->setPermissions(RECOVERY_FOLDER_ID);
1553 
1554  $this->writeScanLogLine("Object '" . $obj_data->getId() . "' restored.");
1555 
1556  // ... remove top_node from list ...
1557  array_shift($subnode);
1558 
1559  // ... insert subtree of top_node if any subnodes exist
1560  if (count($subnode) > 0) {
1561  foreach ($subnode as $node) {
1562  $rbacadmin->revokePermission($node["child"]);
1563  $obj_data = ilObjectFactory::getInstanceByRefId($node["child"]);
1564  $obj_data->putInTree($node["parent"]);
1565  $obj_data->setPermissions($node["parent"]);
1566 
1567  $this->writeScanLogLine("Object '" . $obj_data->getId() . "' restored.");
1568  }
1569  }
1570  }
1571 
1572  // final clean up
1573  $this->findInvalidChilds();
1574  $this->removeInvalidChilds();
1575 
1576  return true;
1577  }
1578 
1588  public function purgeTrash($a_nodes = null)
1589  {
1590  $ilLog = $this->log;
1591 
1592  // check mode: purge_trash
1593  if ($this->mode["purge_trash"] !== true) {
1594  return false;
1595  }
1596 
1597  $this->writeScanLogLine("\npurgeTrash:");
1598 
1599  if ($a_nodes === null and isset($this->deleted_objects)) {
1600  $a_nodes = $this->deleted_objects;
1601  }
1602  $message = sprintf(
1603  '%s::purgeTrash(): Started...',
1604  get_class($this)
1605  );
1606  $ilLog->write($message, $ilLog->WARNING);
1607 
1608  // start purge process
1609  return $this->purgeObjects($a_nodes);
1610  }
1611 
1621  public function purgeUnboundObjects($a_nodes = null)
1622  {
1623  $ilLog = $this->log;
1624 
1625  // check mode: purge
1626  if ($this->mode["purge"] !== true) {
1627  return false;
1628  }
1629 
1630  $this->writeScanLogLine("\npurgeUnboundObjects:");
1631 
1632  if ($a_nodes === null and isset($this->unbound_objects)) {
1633  $a_nodes = $this->unbound_objects;
1634  }
1635 
1636  $message = sprintf(
1637  '%s::purgeUnboundObjects(): Started...',
1638  get_class($this)
1639  );
1640  $ilLog->write($message, $ilLog->WARNING);
1641 
1642  // start purge process
1643  return $this->purgeObjects($a_nodes);
1644  }
1645 
1655  public function purgeMissingObjects($a_nodes = null)
1656  {
1657  $ilLog = $this->log;
1658 
1659  // check mode: purge
1660  if ($this->mode["purge"] !== true) {
1661  return false;
1662  }
1663 
1664  $this->writeScanLogLine("\npurgeMissingObjects:");
1665 
1666  if ($a_nodes === null and isset($this->missing_objects)) {
1667  $a_nodes = $this->missing_objects;
1668  }
1669 
1670  $message = sprintf(
1671  '%s::purgeMissingObjects(): Started...',
1672  get_class($this)
1673  );
1674  $ilLog->write($message, $ilLog->WARNING);
1675 
1676  // start purge process
1677  return $this->purgeObjects($a_nodes);
1678  }
1679 
1687  public function purgeObjects($a_nodes)
1688  {
1689  $ilLog = $this->log;
1690  $ilUser = $this->user;
1691 
1692  // Get purge limits
1693  $count_limit = $ilUser->getPref("systemcheck_count_limit");
1694  if (!is_numeric($count_limit) || $count_limit < 0) {
1695  $count_limit = count($a_nodes);
1696  }
1697  $timestamp_limit = time();
1698  $age_limit = $ilUser->getPref("systemcheck_age_limit");
1699  if (is_numeric($age_limit) && $age_limit > 0) {
1700  $timestamp_limit -= $age_limit * 60 * 60 * 24;
1701  }
1702  $type_limit = $ilUser->getPref("systemcheck_type_limit");
1703  if ($type_limit) {
1704  $type_limit = trim($type_limit);
1705  if (strlen($type_limit) == 0) {
1706  $type_limit = null;
1707  }
1708  }
1709 
1710  // handle wrong input
1711  if (!is_array($a_nodes)) {
1712  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1713  return false;
1714  }
1715 
1716  // start delete process
1717  $this->writeScanLogLine("action\tref_id\tobj_id\ttype\telapsed\ttitle");
1718  $count = 0;
1719  foreach ($a_nodes as $node) {
1720  if ($type_limit && $node['type'] != $type_limit) {
1721  $this->writeScanLogLine(
1722  "skip\t" .
1723  $node['child'] . "\t\t" . $node['type'] . "\t\t" . $node['title']
1724  );
1725  continue;
1726  }
1727 
1728 
1729  $count++;
1730  if ($count > $count_limit) {
1731  $this->writeScanLogLine("Stopped purging after " . ($count - 1) . " objects, because count limit was reached: " . $count_limit);
1732  break;
1733  }
1734  if ($node["deleted_timestamp"] > $timestamp_limit) {
1735  $this->writeScanLogLine("Stopped purging after " . ($count - 1) . " objects, because timestamp limit was reached: " . date("c", $timestamp_limit));
1736  continue;
1737  }
1738 
1739  $ref_id = ($node["child"]) ? $node["child"] : $node["ref_id"];
1740  $node_obj = ilObjectFactory::getInstanceByRefId($ref_id, false);
1741 
1742  if ($node_obj === false) {
1743  $this->invalid_objects[] = $node;
1744  continue;
1745  }
1746 
1747  $message = sprintf(
1748  '%s::purgeObjects(): Removing object (id:%s ref:%s)',
1749  get_class($this),
1750  $ref_id,
1751  $node_obj->getId()
1752  );
1753  $ilLog->write($message, $ilLog->WARNING);
1754 
1755  $startTime = microtime(true);
1756  $node_obj->delete();
1757  ilTree::_removeEntry($node["tree"], $ref_id);
1758  $endTime = microtime(true);
1759 
1760  $this->writeScanLogLine("purged\t" . $ref_id . "\t" . $node_obj->getId() .
1761  "\t" . $node['type'] . "\t" . round($endTime - $startTime, 1) . "\t" . $node['title']);
1762  }
1763 
1764  $this->findInvalidChilds();
1765  $this->removeInvalidChilds();
1766 
1767  return true;
1768  }
1769 
1783  public function initGapsInTree()
1784  {
1785  $tree = $this->tree;
1786  $ilLog = $this->log;
1787 
1788  $message = sprintf(
1789  '%s::initGapsInTree(): Started...',
1790  get_class($this)
1791  );
1792  $ilLog->write($message, $ilLog->WARNING);
1793 
1794  // check mode: clean
1795  if ($this->mode["clean"] !== true) {
1796  return false;
1797  }
1798  $this->writeScanLogLine("\nrenumberTree:");
1799 
1800  $tree->renumber(ROOT_FOLDER_ID);
1801 
1802  $this->writeScanLogLine("done");
1803 
1804  return true;
1805  }
1806 
1816  public function handleErr($error)
1817  {
1818  $call_loc = $error->backtrace[count($error->backtrace) - 1];
1819  $num_args = count($call_loc["args"]);
1820 
1821  if ($num_args > 0) {
1822  foreach ($call_loc["args"] as $arg) {
1823  $type = gettype($arg);
1824 
1825  switch ($type) {
1826  case "string":
1827  $value = strlen($arg);
1828  break;
1829 
1830  case "array":
1831  $value = count($arg);
1832  break;
1833 
1834  case "object":
1835  $value = get_class($arg);
1836  break;
1837 
1838  case "boolean":
1839  $value = ($arg) ? "true" : "false";
1840  break;
1841 
1842  default:
1843  $value = $arg;
1844  break;
1845  }
1846 
1847  $arg_list[] = array(
1848  "type" => $type,
1849  "value" => "(" . $value . ")"
1850  );
1851  }
1852 
1853  foreach ($arg_list as $arg) {
1854  $arg_str .= implode("", $arg) . " ";
1855  }
1856  }
1857 
1858  $err_msg = "<br/><b>" . $error->getCode() . ":</b> " . $error->getMessage() . " in " . $call_loc["class"] . $call_loc["type"] . $call_loc["function"] . "()" .
1859  "<br/>Called from: " . basename($call_loc["file"]) . " , line " . $call_loc["line"] .
1860  "<br/>Passed parameters: [" . $num_args . "] " . $arg_str . "<br/>";
1861  printf($err_msg);
1862 
1863  if ($error->getUserInfo()) {
1864  printf("<br/>Parameter details:");
1865  echo "<pre>";
1866  var_dump($call_loc["args"]);
1867  echo "</pre>";
1868  }
1869 
1870  if ($error->getCode() == FATAL) {
1871  exit();
1872  }
1873  }
1874 
1875  public function writeScanLogArray($a_arr)
1876  {
1877  if (!$this->isLogEnabled()) {
1878  return false;
1879  }
1880 
1881  foreach ($a_arr as $entry) {
1882  $this->scan_log->write(implode("\t", $entry));
1883  }
1884  }
1885 
1886  public function writeScanLogLine($a_msg)
1887  {
1888  if (!$this->isLogEnabled()) {
1889  return false;
1890  }
1891 
1892  $this->scan_log->write($a_msg);
1893  }
1894 
1898  public function hasScanLog()
1899  {
1900  // file check
1901  return is_file(CLIENT_DATA_DIR . "/" . $this->scan_log_file);
1902  }
1903 
1907  public function deleteScanLog()
1908  {
1909  @unlink(CLIENT_DATA_DIR . "/" . $this->scan_log_file);
1910  }
1911 
1912  public function readScanLog()
1913  {
1914  // file check
1915  if (!$this->hasScanLog()) {
1916  return false;
1917  }
1918 
1919  $scanfile = &file(CLIENT_DATA_DIR . "/" . $this->scan_log_file);
1920  if (!$scan_log = &$this->get_last_scan($scanfile)) {
1921  return false;
1922  }
1923  // Ensure that memory is freed
1924  unset($scanfile);
1925 
1926  return $scan_log;
1927  }
1928 
1929  public function get_last_scan($a_scan_log)
1930  {
1931  $logs = array_keys($a_scan_log, $this->scan_log_separator . "\n");
1932 
1933  if (count($logs) > 0) {
1934  return array_slice($a_scan_log, array_pop($logs) + 2);
1935  }
1936 
1937  return false;
1938  }
1939 
1940  public function checkTreeStructure($a_startnode = null)
1941  {
1942  $tree = $this->tree;
1943 
1944  $this->writeScanLogLine("\nchecking tree structure is disabled");
1945 
1946  return false;
1947  }
1948 
1955  public function dumpTree()
1956  {
1957  $ilDB = $this->db;
1958 
1959  $this->writeScanLogLine("BEGIN dumpTree:");
1960 
1961  // collect nodes with duplicate child Id's
1962  // (We use this, to mark these nodes later in the output as being
1963  // erroneous.).
1964  $q = 'SELECT child FROM tree GROUP BY child HAVING COUNT(*) > 1';
1965  $r = $this->db->query($q);
1966  $duplicateNodes = array();
1967  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
1968  $duplicateNodes[] = $row->child;
1969  }
1970 
1971  // dump tree
1972  $q = "SELECT tree.*,ref.ref_id,dat.obj_id objobj_id,ref.obj_id refobj_id,ref.deleted,dat.* "
1973  . "FROM tree "
1974  . "RIGHT JOIN object_reference ref ON tree.child = ref.ref_id "
1975  . "RIGHT JOIN object_data dat ON ref.obj_id = dat.obj_id "
1976 // ."LEFT JOIN usr_data usr ON usr.usr_id = dat.owner "
1977  . "ORDER BY tree, lft, type, dat.title";
1978  $r = $this->db->query($q);
1979 
1980  $this->writeScanLogLine(
1981  '<table><tr>'
1982  . '<td>tree, child, parent, lft, rgt, depth</td>'
1983  . '<td>ref_id, ref.obj_id, deleted</td>'
1984  . '<td>obj_id, type, owner, title</td>'
1985  . '</tr>'
1986  );
1987 
1988  // We use a stack to represent the path to the current node.
1989  // This allows us to do analyze the tree structure without having
1990  // to implement a recursive algorithm.
1991  $stack = array();
1992  $error_count = 0;
1993  $repository_tree_count = 0;
1994  $trash_trees_count = 0;
1995  $other_trees_count = 0;
1996  $not_in_tree_count = 0;
1997 
1998  // The previous number is used for gap checking
1999  $previousNumber = 0;
2000 
2001  $this->initWorkspaceObjects();
2002 
2003  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
2004  // workspace objects are not to be processed
2005  if ($this->workspace_object_ids &&
2006  in_array($row->objobj_id, $this->workspace_object_ids)) {
2007  continue;
2008  }
2009 
2010  // If there is no entry in table tree for the object, we display it here
2011  if (is_null($row->child)) {
2012  switch ($row->type) {
2013  case 'crsg':
2014  case 'usr':
2015  case 'typ':
2016  case 'lng':
2017  case 'rolt':
2018  case 'role':
2019  case 'mob':
2020  case 'sty':
2021  case 'tax': // #13798
2022  // We are not interested in dumping these object types.
2023  continue 2;
2024  //break; NOT REACHED
2025  case 'file':
2026  if (is_null($row->ref_id)) {
2027  // File objects can be part of a learning module.
2028  // In this case, they do not have a row in table object_reference.
2029  // We are not interested in dumping these file objects.
2030  continue 2;
2031  } else {
2032  // File objects which have a row in table object_reference, but
2033  // none in table tree are an error.
2034  $error_count++;
2035  $isRowOkay = false;
2036  $isParentOkay = false;
2037  $isLftOkay = false;
2038  $isRgtOkay = false;
2039  $isDepthOkay = false;
2040  }
2041  break;
2042 
2043 
2044  case 'fold':
2045  // ignore folders on media pools
2046  if ($this->isMediaFolder($row->obj_id)) {
2047  continue 2;
2048  }
2049  // no break
2050  default:
2051  $error_count++;
2052  $isRowOkay = false;
2053  $isParentOkay = false;
2054  $isLftOkay = false;
2055  $isRgtOkay = false;
2056  $isDepthOkay = false;
2057  break;
2058  }
2059 
2060  // moved here (below continues in switch)
2061  $not_in_tree_count++;
2062 
2063  $this->writeScanLogLine(
2064  '<tr>'
2065  . '<td>'
2066  . (($isRowOkay) ? '' : '<font color=#ff0000>')
2067  . $row->tree . ', '
2068  . $row->child . ', '
2069  . (($isParentOkay) ? '' : 'parent:<b>')
2070  . $row->parent
2071  . (($isParentOkay) ? '' : '</b>')
2072  . ', '
2073  . (($isLftOkay) ? '' : 'lft:<b>')
2074  . $row->lft
2075  . (($isLftOkay) ? '' : '</b>')
2076  . ', '
2077  . (($isRgtOkay) ? '' : 'rgt:<b>')
2078  . $row->rgt
2079  . (($isRgtOkay) ? '' : '</b>')
2080  . ', '
2081  . (($isDepthOkay) ? '' : 'depth:<b>')
2082  . $row->depth
2083  . (($isDepthOkay) ? '' : '</b>')
2084  . (($isRowOkay) ? '' : '</font>')
2085  . '</td><td>'
2086  . (($isRowOkay) ? '' : '<font color=#ff0000>')
2087  . (($isRefRefOkay && $isChildOkay) ? '' : 'ref.ref_id:<b>')
2088  . $row->ref_id
2089  . (($isRefRefOkay && $isChildOkay) ? '' : '</b>')
2090  . ', '
2091  . (($isRefObjOkay) ? '' : 'ref.obj_id:<b>')
2092  . $row->refobj_id
2093  . (($isRefObjOkay) ? '' : '</b>')
2094  . (($isRowOkay) ? '' : '<font color=#ff0000>')
2095  . (($row->deleted != null) ? ', ' . $row->deleted : '')
2096  . '</td><td>'
2097  . (($isRowOkay) ? '' : '<font color=#ff0000>')
2098  . $indent
2099  . $row->obj_id . ', '
2100  . $row->type . ', '
2101  . $row->login . ', '
2102  . $row->title
2103  . (($isRowOkay) ? '' : ' <b>*ERROR*</b><font color=#ff0000>')
2104  . '</td>'
2105  . '</tr>'
2106  );
2107  continue;
2108  }
2109 
2110  // Update stack
2111  // -------------------
2112  $indent = "";
2113  for ($i = 1; $i < $row->depth; $i++) {
2114  $indent .= ". ";
2115  }
2116 
2117  // Initialize the stack and the previous number if we are in a new tree
2118  if (count($stack) == 0 || $stack[0]->tree != $row->tree) {
2119  $stack = array();
2120  $previousNumber = $row->lft - 1;
2121  $this->writeScanLogLine('<tr><td>&nbsp;</td></tr>');
2122  }
2123  // Pop old stack entries
2124 
2125 
2126  while (count($stack) > 0 && $stack[count($stack) - 1]->rgt < $row->lft) {
2127  $popped = array_pop($stack);
2128 
2129  // check for gap
2130  $gap = $popped->rgt - $previousNumber - 1;
2131  if ($gap > 0) {
2132  $poppedIndent = "";
2133  for ($i = 1; $i < $popped->depth; $i++) {
2134  $poppedIndent .= ". ";
2135  }
2136  $this->writeScanLogLine(
2137  '<tr>'
2138  . '<td colspan=2><div align="right">'
2139  . '<font color=#00cc00>*gap* for ' . ($gap / 2) . ' nodes at end of&nbsp;</font>'
2140  . '</div></td>'
2141  . '<td>'
2142  . '<font color=#00cc00>'
2143  . $poppedIndent
2144  . $popped->obj_id . ', '
2145  . $popped->type . ', '
2146  . $popped->login . ', '
2147  . $popped->title
2148  . '</font>'
2149  . '</td>'
2150  . '</tr>'
2151  );
2152  }
2153  $previousNumber = $popped->rgt;
2154  unset($popped);
2155  }
2156 
2157  // Check row integrity
2158  // -------------------
2159  $isRowOkay = true;
2160 
2161  // Check tree structure
2162  $isChildOkay = true;
2163  $isParentOkay = true;
2164  $isLftOkay = true;
2165  $isRgtOkay = true;
2166  $isDepthOkay = true;
2167  $isGap = false;
2168 
2169  if (count($stack) > 0) {
2170  $parent = $stack[count($stack) - 1];
2171  if ($parent->depth + 1 != $row->depth) {
2172  $isDepthOkay = false;
2173  $isRowOkay = false;
2174  }
2175  if ($parent->child != $row->parent) {
2176  $isParentOkay = false;
2177  $isRowOkay = false;
2178  }
2179  if ($GLOBALS['ilSetting']->get('main_tree_impl', 'ns') == 'ns') {
2180  if ($parent->lft >= $row->lft) {
2181  $isLftOkay = false;
2182  $isRowOkay = false;
2183  }
2184  if ($parent->rgt <= $row->rgt) {
2185  $isRgtOkay = false;
2186  $isRowOkay = false;
2187  }
2188  }
2189  }
2190 
2191  // Check lft rgt
2192  if ($GLOBALS['ilSetting']->get('main_tree_impl', 'ns') == 'ns') {
2193  if ($row->lft >= $row->rgt) {
2194  $isLftOkay = false;
2195  $isRgtOkay = false;
2196  $isRowOkay = false;
2197  }
2198  }
2199  if (in_array($row->child, $duplicateNodes)) {
2200  $isChildOkay = false;
2201  $isRowOkay = false;
2202  }
2203 
2204  // Check object reference
2205  $isRefRefOkay = true;
2206  $isRefObjOkay = true;
2207  if ($row->ref_id == null) {
2208  $isRefRefOkay = false;
2209  $isRowOkay = false;
2210  }
2211  if ($row->obj_id == null) {
2212  $isRefObjOkay = false;
2213  $isRowOkay = false;
2214  }
2215 
2216  if (!$isRowOkay) {
2217  $error_count++;
2218  }
2219 
2220  // Check for gap between siblings,
2221  // and eventually write a log line
2222  if ($GLOBALS['ilSetting']->get('main_tree_impl', 'ns') == 'ns') {
2223  $gap = $row->lft - $previousNumber - 1;
2224  $previousNumber = $row->lft;
2225  if ($gap > 0) {
2226  $this->writeScanLogLine(
2227  '<tr>'
2228  . '<td colspan=2><div align="right">'
2229  . '<font color=#00cc00>*gap* for ' . ($gap / 2) . ' nodes between&nbsp;</font>'
2230  . '</div></td>'
2231  . '<td>'
2232  . '<font color=#00cc00>siblings</font>'
2233  . '</td>'
2234  . '</tr>'
2235  );
2236  }
2237  }
2238 
2239  // Write log line
2240  // -------------------
2241  $this->writeScanLogLine(
2242  '<tr>'
2243  . '<td>'
2244  . (($isRowOkay) ? '' : '<font color=#ff0000>')
2245  . $row->tree . ', '
2246  . $row->child . ', '
2247  . (($isParentOkay) ? '' : 'parent:<b>')
2248  . $row->parent
2249  . (($isParentOkay) ? '' : '</b>')
2250  . ', '
2251  . (($isLftOkay) ? '' : 'lft:<b>')
2252  . $row->lft
2253  . (($isLftOkay) ? '' : '</b>')
2254  . ', '
2255  . (($isRgtOkay) ? '' : 'rgt:<b>')
2256  . $row->rgt
2257  . (($isRgtOkay) ? '' : '</b>')
2258  . ', '
2259  . (($isDepthOkay) ? '' : 'depth:<b>')
2260  . $row->depth
2261  . (($isDepthOkay) ? '' : '</b>')
2262  . (($isRowOkay) ? '' : '</font>')
2263  . '</td><td>'
2264  . (($isRowOkay) ? '' : '<font color=#ff0000>')
2265  . (($isRefRefOkay && $isChildOkay) ? '' : 'ref.ref_id:<b>')
2266  . $row->ref_id
2267  . (($isRefRefOkay && $isChildOkay) ? '' : '</b>')
2268  . ', '
2269  . (($isRefObjOkay) ? '' : 'ref.obj_id:<b>')
2270  . $row->refobj_id
2271  . (($isRefObjOkay) ? '' : '</b>')
2272  . (($isRowOkay) ? '' : '<font color=#ff0000>')
2273  . (($row->tree < 0) ? ', ' . $row->deleted : '')
2274  . '</td><td>'
2275  . (($isRowOkay) ? '' : '<font color=#ff0000>')
2276  . $indent
2277  . $row->obj_id . ', '
2278  . $row->type . ', '
2279  . $row->login . ', '
2280  . $row->title
2281  . (($isRowOkay) ? '' : ' <b>*ERROR*</b><font color=#ff0000>')
2282  . '</td>'
2283  . '</tr>'
2284  );
2285 
2286  // Update stack
2287  // -------------------
2288  // Push node on stack
2289  $stack[] = $row;
2290 
2291  // Count nodes
2292  // -----------------
2293  if ($row->tree == 1) {
2294  $repository_tree_count++;
2295  } elseif ($row->tree < 0) {
2296  $trash_trees_count++;
2297  } else {
2298  $other_trees_count++;
2299  }
2300  }
2301  //
2302  // Pop remaining stack entries
2303 
2304  while (count($stack) > 0) {
2305  $popped = array_pop($stack);
2306 
2307  // check for gap
2308  $gap = $popped->rgt - $previousNumber - 1;
2309  if ($gap > 0) {
2310  $poppedIndent = "";
2311  for ($i = 1; $i < $popped->depth; $i++) {
2312  $poppedIndent .= ". ";
2313  }
2314  $this->writeScanLogLine(
2315  '<tr>'
2316  . '<td colspan=2><div align="right">'
2317  . '<font color=#00cc00>*gap* for ' . ($gap / 2) . ' nodes at end of&nbsp;</font>'
2318  . '</div></td>'
2319  . '<td>'
2320  . '<font color=#00cc00>'
2321  . $poppedIndent
2322  . $popped->obj_id . ', '
2323  . $popped->type . ', '
2324  . $popped->login . ', '
2325  . $popped->title
2326  . '</font>'
2327  . '</td>'
2328  . '</tr>'
2329  );
2330  }
2331  $previousNumber = $popped->rgt;
2332  unset($popped);
2333  }
2334 
2335  //
2336  $this->writeScanLogLine("</table>");
2337 
2338  if ($error_count > 0) {
2339  $this->writeScanLogLine('<font color=#ff0000>' . $error_count . ' errors found while dumping tree.</font>');
2340  } else {
2341  $this->writeScanLogLine('No errors found while dumping tree.');
2342  }
2343  $this->writeScanLogLine("$repository_tree_count nodes in repository tree");
2344  $this->writeScanLogLine("$trash_trees_count nodes in trash trees");
2345  $this->writeScanLogLine("$other_trees_count nodes in other trees");
2346  $this->writeScanLogLine("$not_in_tree_count nodes are not in a tree");
2347  $this->writeScanLogLine("END dumpTree");
2348 
2349  return $error_count;
2350  }
2351 
2352  protected function isMediaFolder($a_obj_id)
2353  {
2354  $ilDB = $this->db;
2355 
2356  if (!is_array($this->media_pool_ids)) {
2357  $this->media_pool_ids = array();
2358  $query = "SELECT child FROM mep_tree ";
2359  $res = $ilDB->query($query);
2360  while ($row = $ilDB->fetchObject($res)) {
2361  $this->media_pool_ids[] = $row->child;
2362  }
2363  }
2364 
2365  return in_array($a_obj_id, $this->media_pool_ids) ? true : false;
2366  }
2367 
2374  protected function isExcludedFromRecovery($a_type, $a_obj_id)
2375  {
2376  switch ($a_type) {
2377  case 'fold':
2378  if (!$this->isMediaFolder($a_obj_id)) {
2379  return false;
2380  }
2381  }
2382  return in_array($a_type, $this->object_types_exclude);
2383  }
2384 
2385  protected function initWorkspaceObjects()
2386  {
2387  $ilDB = $this->db;
2388 
2389  if ($this->workspace_object_ids === null) {
2390  $this->workspace_object_ids = array();
2391 
2392  // workspace objects
2393  $set = $ilDB->query("SELECT DISTINCT(obj_id) FROM object_reference_ws");
2394  while ($row = $ilDB->fetchAssoc($set)) {
2395  $this->workspace_object_ids[] = $row["obj_id"];
2396  }
2397 
2398  // portfolios
2399  $set = $ilDB->query("SELECT id FROM usr_portfolio");
2400  while ($row = $ilDB->fetchAssoc($set)) {
2401  $this->workspace_object_ids[] = $row["id"];
2402  }
2403  }
2404  }
2405 
2406  protected function filterWorkspaceObjects(array &$a_data, $a_index = "obj_id")
2407  {
2408  if (sizeof($a_data)) {
2409  $this->initWorkspaceObjects();
2410 
2411  // remove workspace objects from result objects
2412  if (is_array($this->workspace_object_ids)) {
2413  foreach ($a_data as $idx => $item) {
2414  if (in_array($item[$a_index], $this->workspace_object_ids)) {
2415  unset($a_data[$idx]);
2416  }
2417  }
2418  }
2419  }
2420  }
2421 } // END class.ilValidator
checkTreeStructure($a_startnode=null)
findDeletedObjects()
Search database for all tree entries having no valid parent (=> no valid path to root node) and store...
__construct($a_log=false)
Constructor.
getDeletedObjects()
Gets all object in trash.
setMode($a_mode, $a_value)
set mode of ilValidator Usage: setMode("restore",true) => enable object restorey setMode("all",true) => enable all features For all possible modes see variables declaration
const IL_CAL_DATETIME
findMissingObjects()
Search database for all object entries with missing reference and/or tree entry and stores result in ...
removeInvalidChilds($a_invalid_childs=null)
Removes all tree entries without any link to a valid object.
$type
restoreTrash($a_deleted_objects=null)
Restore all objects in trash to RecoveryFolder NOTE: All objects will be restored to top of RecoveryF...
purgeTrash($a_nodes=null)
Removes all objects in trash from system.
getInvalidChilds()
Gets all tree entries without any link to a valid object.
global $DIC
Definition: saml.php:7
static _removeEntry($a_tree, $a_child, $a_db_table="tree")
STATIC METHOD Removes a single entry from a tree.
deleteScanLog()
Delete scan log.
purgeUnboundObjects($a_nodes=null)
Removes all invalid objects from system.
logging
Definition: class.ilLog.php:18
dumpTree()
Dumps the Tree structure into the scan log.
restoreSubTrees($a_nodes)
Restore objects (and their subobjects) to RecoveryFolder.
purgeMissingObjects($a_nodes=null)
Removes all missing objects from system.
const IL_CAL_UNIX
$summary
Definition: cron.php:24
findInvalidChilds()
Search database for all tree entries without any link to a valid object and stores result in $this->i...
user()
Definition: user.php:4
getUnboundObjects()
Gets all tree entries having no valid parent (=> no valid path to root node) Returns an array with ch...
isExcludedFromRecovery($a_type, $a_obj_id)
Check if type is excluded from recovery.
hasScanLog()
Quickly determine if there is a scan log.
const DEBUG
$a_type
Definition: workflow.php:92
removeInvalidReferences($a_invalid_refs=null)
Removes all reference entries that are linked with invalid object IDs.
$r
Definition: example_031.php:79
catch(Exception $e) $message
getInvalidReferences()
Gets all reference entries that are not linked with a valid object id.
validate()
Performs the validation for each enabled mode.
getMissingObjects()
Gets all object entries with missing reference and/or tree entry.
foreach($_POST as $key=> $value) $res
removeInvalidRolefolders($a_invalid_rolefolders=null)
Removes invalid rolefolders.
purgeObjects($a_nodes)
removes objects from system
Date and time handling
$ilUser
Definition: imgupload.php:18
findInvalidReferences()
Search database for all reference entries that are not linked with a valid object id and stores resul...
$query
restoreDeletedObjects($a_nodes)
Restore deleted objects (and their subobjects) to RecoveryFolder.
findInvalidRolefolders()
Search database for all rolefolder object entries with missing reference entry.
getInvalidRolefolders()
Gets invalid rolefolders (same as missing objects)
filterWorkspaceObjects(array &$a_data, $a_index="obj_id")
$row
get_last_scan($a_scan_log)
restoreUnboundObjects($a_unbound_objects=null)
Restore objects (and their subobjects) to RecoveryFolder that are valid but not linked correctly in t...
exit
Definition: backend.php:16
getPossibleModes()
get possible ilValidator modes public
findUnboundObjects()
Search database for all tree entries having no valid parent (=> no valid path to root node) and store...
setModeDependencies()
Sets modes by considering mode dependencies; some modes require other modes to be activated...
findInvalidRBACEntries()
Search database for all role entries that are linked to invalid ref_ids.
isMediaFolder($a_obj_id)
global $ilDB
$i
Definition: disco.tpl.php:19
static getInstanceByRefId($a_ref_id, $stop_on_error=true)
get an instance of an Ilias object by reference id
handleErr($error)
Callback function handles PEAR_error and outputs detailed infos about error TODO: implement that in g...
isModeEnabled($a_mode)
Is a particular mode enabled?
ILIAS Data Validator & Recovery Tool.
restoreMissingObjects($a_missing_objects=null)
Restores missing reference and/or tree entry for all objects found by this::getMissingObjects() Resto...
$key
Definition: croninfo.php:18
restoreReference($a_obj_id)
restore a reference for an object Creates a new reference entry in DB table object_reference for $a_o...
writeScanLogArray($a_arr)
initGapsInTree()
Initializes gaps in lft/rgt values of a tree.
$GLOBALS['JPEG_Segment_Names']
Global Variable: XMP_tag_captions.
writeScanLogLine($a_msg)