ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilValidator.php
Go to the documentation of this file.
1 <?php
2 
25 {
27  protected ilDBInterface $db;
28  protected ilLanguage $lng;
29  protected ilLogger $log;
31  protected ilTree $tree;
32  protected ilObjUser $user;
33  protected ?array $media_pool_ids = null;
34  public array $rbac_object_types;
35  public array $workspace_object_ids = [];
36  public array $invalid_rbac_entries;
37 
51  public array $object_types_exclude = [
52  "adm", "root", "mail", "usrf", "objf", "lngf",
53  "trac", "taxf", "auth", "rolf", "assf", "svyf", "extt", "adve", "fold"
54  ];
55 
56  public array $mode = [
57  "scan" => true, // gather information about corrupted entries
58  "dump_tree" => false, // dump tree
59  "clean" => false, // remove all unusable entries & renumber tree
60  "restore" => false, // restore objects with invalid parent to RecoveryFolder
61  "purge" => false, // delete all objects with invalid parent from system
62  "restore_trash" => false, // restore all objects in trash to RecoveryFolder
63  "purge_trash" => false // delete all objects in trash from system
64  ];
65 
66  public array $invalid_references = [];
67  public array $invalid_childs = [];
68  public array $missing_objects = [];
69  public array $unbound_objects = [];
70  public array $deleted_objects = []; // in trash
71 
77  public array $invalid_rolefolders = [];
78 
82  public array $invalid_objects = [];
83  public bool $logging = false; // true enables scan log
84  public ?ilLog $scan_log = null;
85  public string $scan_log_file = "scanlog.log";
86  public string $scan_log_separator = "<!-- scan log start -->";
87 
88  public function __construct(
89  bool $a_log = false
90  ) {
91  global $DIC;
92 
93  $this->obj_definition = $DIC["objDefinition"];
94  $this->db = $DIC->database();
95  $this->lng = $DIC->language();
96  $this->log = $DIC["ilLog"];
97  $this->rbacadmin = $DIC->rbac()->admin();
98  $this->tree = $DIC->repositoryTree();
99  $this->user = $DIC->user();
100  $objDefinition = $DIC["objDefinition"];
101  $ilDB = $DIC->database();
102 
103  $this->db = &$ilDB;
104  $this->rbac_object_types = $objDefinition->getAllRBACObjects();
105 
106  if ($a_log === true) {
107  $this->logging = true;
108 
109  // should be available thru inc.header.php
110  // TODO: move log functionality to new class ilScanLog
111  // Delete old scan log
112  $this->deleteScanLog();
113 
114  // create scan log
115  $this->scan_log = new ilLog(CLIENT_DATA_DIR, "scanlog.log");
116  $this->scan_log->setLogFormat("");
117  $this->writeScanLogLine($this->scan_log_separator);
118  $this->writeScanLogLine("\n[Systemscan from " . date("y-m-d H:i]"));
119  }
120  }
121 
125  public function getPossibleModes(): array
126  {
127  return array_keys($this->mode);
128  }
129 
139  public function setMode(string $a_mode, bool $a_value): bool
140  {
141  if ((!array_key_exists($a_mode, $this->mode) && $a_mode !== "all") || !is_bool($a_value)) {
142  $this->throwError(INVALID_PARAM, FATAL, DEBUG);
143  return false;
144  }
145 
146  if ($a_mode === "all") {
147  foreach ($this->mode as $mode => $value) {
148  $this->mode[$mode] = $a_value;
149  }
150  } else {
151  $this->mode[$a_mode] = $a_value;
152  }
153 
154  // consider mode dependencies
155  $this->setModeDependencies();
156 
157  return true;
158  }
159 
160  public function isModeEnabled(
161  string $a_mode
162  ): bool {
163  if (!array_key_exists($a_mode, $this->mode)) {
164  $this->throwError(VALIDATER_UNKNOWN_MODE, WARNING, DEBUG);
165  return false;
166  }
167 
168  return $this->mode[$a_mode];
169  }
170 
171  public function isLogEnabled(): bool
172  {
173  return $this->logging;
174  }
175 
181  public function setModeDependencies(): void
182  {
183  // DO NOT change the order
184 
185  if ($this->mode["restore"] === true) {
186  $this->mode["scan"] = true;
187  $this->mode["purge"] = false;
188  }
189 
190  if ($this->mode["purge"] === true) {
191  $this->mode["scan"] = true;
192  $this->mode["restore"] = false;
193  }
194 
195  if ($this->mode["restore_trash"] === true) {
196  $this->mode["scan"] = true;
197  $this->mode["purge_trash"] = false;
198  }
199 
200  if ($this->mode["purge_trash"] === true) {
201  $this->mode["scan"] = true;
202  $this->mode["restore_trash"] = false;
203  }
204 
205  if ($this->mode["clean"] === true) {
206  $this->mode["scan"] = true;
207  }
208  }
209 
214  public function validate(): string
215  {
216  $lng = $this->lng;
217 
218  // The validation summary.
219  $summary = "";
220 
221 
222  // STEP 1: Scan
223  // -------------------
224  $summary .= $lng->txt("scanning_system");
225  if (!$this->isModeEnabled("scan")) {
226  $summary .= $lng->txt("disabled");
227  } else {
228  $summary .= "<br/>" . $lng->txt("searching_invalid_refs");
229  if ($this->findInvalidReferences()) {
230  $summary .= count($this->getInvalidReferences()) . " " . $lng->txt("found");
231  } else {
232  $summary .= $lng->txt("found_none");
233  }
234 
235  $summary .= "<br/>" . $lng->txt("searching_invalid_childs");
236  if ($this->findInvalidChilds()) {
237  $summary .= count($this->getInvalidChilds()) . " " . $lng->txt("found");
238  } else {
239  $summary .= $lng->txt("found_none");
240  }
241 
242  $summary .= "<br/>" . $lng->txt("searching_missing_objs");
243  if ($this->findMissingObjects()) {
244  $summary .= count($this->getMissingObjects()) . " " . $lng->txt("found");
245  } else {
246  $summary .= $lng->txt("found_none");
247  }
248 
249  $summary .= "<br/>" . $lng->txt("searching_unbound_objs");
250  if ($this->findUnboundObjects()) {
251  $summary .= count($this->getUnboundObjects()) . " " . $lng->txt("found");
252  } else {
253  $summary .= $lng->txt("found_none");
254  }
255 
256  $summary .= "<br/>" . $lng->txt("searching_deleted_objs");
257  if ($this->findDeletedObjects()) {
258  $summary .= count($this->getDeletedObjects()) . " " . $lng->txt("found");
259  } else {
260  $summary .= $lng->txt("found_none");
261  }
262 
263  $summary .= "<br/>" . $lng->txt("searching_invalid_rolfs");
264  if ($this->findInvalidRolefolders()) {
265  $summary .= count($this->getInvalidRolefolders()) . " " . $lng->txt("found");
266  } else {
267  $summary .= $lng->txt("found_none");
268  }
269 
270  $summary .= "<br/><br/>" . $lng->txt("analyzing_tree_structure");
271  if ($this->checkTreeStructure()) {
272  $summary .= $lng->txt("tree_corrupt");
273  } else {
274  $summary .= $lng->txt("done");
275  }
276  }
277 
278  // STEP 2: Dump tree
279  // -------------------
280  $summary .= "<br /><br />" . $lng->txt("dumping_tree");
281  if (!$this->isModeEnabled("dump_tree")) {
282  $summary .= $lng->txt("disabled");
283  } else {
284  $error_count = $this->dumpTree();
285  if ($error_count > 0) {
286  $summary .= $lng->txt("tree_corrupt");
287  } else {
288  $summary .= $lng->txt("done");
289  }
290  }
291 
292  // STEP 3: Clean Up
293  // -------------------
294  $summary .= "<br /><br />" . $lng->txt("cleaning");
295  if (!$this->isModeEnabled("clean")) {
296  $summary .= $lng->txt("disabled");
297  } else {
298  $summary .= "<br />" . $lng->txt("removing_invalid_refs");
299  if ($this->removeInvalidReferences()) {
300  $summary .= strtolower($lng->txt("done"));
301  } else {
302  $summary .= $lng->txt("nothing_to_remove") . $lng->txt("skipped");
303  }
304 
305  $summary .= "<br />" . $lng->txt("removing_invalid_childs");
306  if ($this->removeInvalidChilds()) {
307  $summary .= strtolower($lng->txt("done"));
308  } else {
309  $summary .= $lng->txt("nothing_to_remove") . $lng->txt("skipped");
310  }
311 
312  $summary .= "<br />" . $lng->txt("removing_invalid_rolfs");
313  if ($this->removeInvalidRolefolders()) {
314  $summary .= strtolower($lng->txt("done"));
315  } else {
316  $summary .= $lng->txt("nothing_to_remove") . $lng->txt("skipped");
317  }
318 
319  // find unbound objects again AFTER cleaning process!
320  // This updates the array 'unboundobjects' required for the further steps
321  // There might be other objects unbounded now due to removal of object_data/reference entries.
322  $this->findUnboundObjects();
323  }
324 
325  // STEP 4: Restore objects
326  $summary .= "<br /><br />" . $lng->txt("restoring");
327 
328  if (!$this->isModeEnabled("restore")) {
329  $summary .= $lng->txt("disabled");
330  } else {
331  $summary .= "<br />" . $lng->txt("restoring_missing_objs");
332  if ($this->restoreMissingObjects()) {
333  $summary .= strtolower($lng->txt("done"));
334  } else {
335  $summary .= $lng->txt("nothing_to_restore") . $lng->txt("skipped");
336  }
337 
338  $summary .= "<br />" . $lng->txt("restoring_unbound_objs");
339  if ($this->restoreUnboundObjects()) {
340  $summary .= strtolower($lng->txt("done"));
341  } else {
342  $summary .= $lng->txt("nothing_to_restore") . $lng->txt("skipped");
343  }
344  }
345 
346  // STEP 5: Restoring Trash
347  $summary .= "<br /><br />" . $lng->txt("restoring_trash");
348 
349  if (!$this->isModeEnabled("restore_trash")) {
350  $summary .= $lng->txt("disabled");
351  } elseif ($this->restoreTrash()) {
352  $summary .= strtolower($lng->txt("done"));
353  } else {
354  $summary .= $lng->txt("nothing_to_restore") . $lng->txt("skipped");
355  }
356 
357  // STEP 6: Purging...
358  $summary .= "<br /><br />" . $lng->txt("purging");
359 
360  if (!$this->isModeEnabled("purge")) {
361  $summary .= $lng->txt("disabled");
362  } else {
363  $summary .= "<br />" . $lng->txt("purging_missing_objs");
364  if ($this->purgeMissingObjects()) {
365  $summary .= strtolower($lng->txt("done"));
366  } else {
367  $summary .= $lng->txt("nothing_to_purge") . $lng->txt("skipped");
368  }
369 
370  $summary .= "<br />" . $lng->txt("purging_unbound_objs");
371  if ($this->purgeUnboundObjects()) {
372  $summary .= strtolower($lng->txt("done"));
373  } else {
374  $summary .= $lng->txt("nothing_to_purge") . $lng->txt("skipped");
375  }
376  }
377 
378  // STEP 7: Purging trash...
379  $summary .= "<br /><br />" . $lng->txt("purging_trash");
380 
381  if (!$this->isModeEnabled("purge_trash")) {
382  $summary .= $lng->txt("disabled");
383  } elseif ($this->purgeTrash()) {
384  $summary .= strtolower($lng->txt("done"));
385  } else {
386  $summary .= $lng->txt("nothing_to_purge") . $lng->txt("skipped");
387  }
388 
389  // STEP 8: Initialize gaps in tree
390  if ($this->isModeEnabled("clean")) {
391  $summary .= "<br /><br />" . $lng->txt("cleaning_final");
392  if ($this->initGapsInTree()) {
393  $summary .= "<br />" . $lng->txt("initializing_gaps") . " " . strtolower($lng->txt("done"));
394  }
395  }
396 
397  // check RBAC starts here
398  // ...
399 
400  // le fin
401  foreach ($this->mode as $mode => $value) {
402  $arr[] = $mode . "[" . (int) $value . "]";
403  }
404 
405  return $summary;
406  }
407 
408 
413  public function findMissingObjects(): bool
414  {
415  $ilDB = $this->db;
416 
417  // check mode: analyze
418  if ($this->mode["scan"] !== true) {
419  return false;
420  }
421 
422  // init
423  $this->missing_objects = [];
424 
425  $this->writeScanLogLine("\nfindMissingObjects:");
426 
427  // Repair missing objects.
428  // We only repair file objects which have an entry in table object_reference.
429  // XXX - We should check all references to file objects which don't
430  // have an object_reference. If we can't find any reference to such
431  // a file object, we should repair it too!
432  $q = "SELECT object_data.*, ref_id FROM object_data " .
433  "LEFT JOIN object_reference ON object_data.obj_id = object_reference.obj_id " .
434  "LEFT JOIN tree ON object_reference.ref_id = tree.child " .
435  "WHERE tree.child IS NULL " .
436  "AND (object_reference.obj_id IS NOT NULL " .
437  " OR object_data.type <> 'file' AND " .
438  $ilDB->in('object_data.type', $this->rbac_object_types, false, 'text') .
439  ")";
440  $r = $this->db->query($q);
441 
442  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
443  #if (!in_array($row->type,$this->object_types_exclude))
444  if (!$this->isExcludedFromRecovery($row->type, $row->obj_id)) {
445  $this->missing_objects[] = [
446  "obj_id" => $row->obj_id,
447  "type" => $row->type,
448  "ref_id" => $row->ref_id,
449  "child" => $row->child,
450  "title" => $row->title,
451  "desc" => $row->description,
452  "owner" => $row->owner,
453  "create_date" => $row->create_date,
454  "last_update" => $row->last_update
455  ];
456  }
457  }
458 
459  $this->filterWorkspaceObjects($this->missing_objects);
460  if (count($this->missing_objects) > 0) {
461  $this->writeScanLogLine("obj_id\ttype\tref_id\tchild\ttitle\tdesc\towner\tcreate_date\tlast_update");
462  $this->writeScanLogArray($this->missing_objects);
463  return true;
464  }
465 
466  $this->writeScanLogLine("none");
467  return false;
468  }
469 
477  public function findInvalidRolefolders(): bool
478  {
479  $ilDB = $this->db;
480 
481  // check mode: analyze
482  if ($this->mode["scan"] !== true) {
483  return false;
484  }
485 
486  // init
487  $this->invalid_rolefolders = [];
488 
489  $this->writeScanLogLine("\nfindInvalidRolefolders:");
490 
491  // find rolfs without reference/tree entry
492  $q = "SELECT object_data.*, ref_id FROM object_data " .
493  "LEFT JOIN object_reference ON object_data.obj_id = object_reference.obj_id " .
494  "LEFT JOIN tree ON object_reference.ref_id = tree.child " .
495  "WHERE (object_reference.obj_id IS NULL OR tree.child IS NULL) " .
496  "AND object_data.type='rolf'";
497  $r = $this->db->query($q);
498 
499  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
500  $this->invalid_rolefolders[] = [
501  "obj_id" => $row->obj_id,
502  "type" => $row->type,
503  "ref_id" => $row->ref_id,
504  "child" => $row->child,
505  "title" => $row->title,
506  "desc" => $row->description,
507  "owner" => $row->owner,
508  "create_date" => $row->create_date,
509  "last_update" => $row->last_update
510  ];
511  }
512 
513  // find rolfs within RECOVERY FOLDER
514  $q = "SELECT object_data.*, ref_id FROM object_data " .
515  "LEFT JOIN object_reference ON object_data.obj_id = object_reference.obj_id " .
516  "LEFT JOIN tree ON object_reference.ref_id = tree.child " .
517  "WHERE object_reference.ref_id = " . $ilDB->quote(RECOVERY_FOLDER_ID, 'integer') . " " .
518  "AND object_data.type='rolf'";
519  $r = $this->db->query($q);
520 
521  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
522  $this->invalid_rolefolders[] = [
523  "obj_id" => $row->obj_id,
524  "type" => $row->type,
525  "ref_id" => $row->ref_id,
526  "child" => $row->child,
527  "title" => $row->title,
528  "desc" => $row->description,
529  "owner" => $row->owner,
530  "create_date" => $row->create_date,
531  "last_update" => $row->last_update
532  ];
533  }
534 
535  $this->filterWorkspaceObjects($this->invalid_rolefolders);
536  if (count($this->invalid_rolefolders) > 0) {
537  $this->writeScanLogLine("obj_id\ttype\tref_id\tchild\ttitle\tdesc\towner\tcreate_date\tlast_update");
538  $this->writeScanLogArray($this->invalid_rolefolders);
539  return true;
540  }
541 
542  $this->writeScanLogLine("none");
543  return false;
544  }
545 
551  public function findInvalidRBACEntries(): bool
552  {
553  $ilDB = $this->db;
554 
555  // check mode: analyze
556  if ($this->mode["scan"] !== true) {
557  return false;
558  }
559 
560  // init
561  $this->invalid_rbac_entries = [];
562 
563  $this->writeScanLogLine("\nfindInvalidRBACEntries:");
564 
565  $q = "SELECT object_data.*, ref_id FROM object_data " .
566  "LEFT JOIN object_reference ON object_data.obj_id = object_reference.obj_id " .
567  "LEFT JOIN tree ON object_reference.ref_id = tree.child " .
568  "WHERE (object_reference.obj_id IS NULL OR tree.child IS NULL) " .
569  "AND object_data.type='rolf'";
570  $r = $this->db->query($q);
571 
572  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
573  $this->invalid_rolefolders[] = [
574  "obj_id" => $row->obj_id,
575  "type" => $row->type,
576  "ref_id" => $row->ref_id,
577  "child" => $row->child,
578  "title" => $row->title,
579  "desc" => $row->description,
580  "owner" => $row->owner,
581  "create_date" => $row->create_date,
582  "last_update" => $row->last_update
583  ];
584  }
585 
586  // find rolfs within RECOVERY FOLDER
587  $q = "SELECT object_data.*, ref_id FROM object_data " .
588  "LEFT JOIN object_reference ON object_data.obj_id = object_reference.obj_id " .
589  "LEFT JOIN tree ON object_reference.ref_id = tree.child " .
590  "WHERE object_reference.ref_id =" . $ilDB->quote(RECOVERY_FOLDER_ID, "integer") . " " .
591  "AND object_data.type='rolf'";
592  $r = $this->db->query($q);
593 
594  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
595  $this->invalid_rolefolders[] = [
596  "obj_id" => $row->obj_id,
597  "type" => $row->type,
598  "ref_id" => $row->ref_id,
599  "child" => $row->child,
600  "title" => $row->title,
601  "desc" => $row->description,
602  "owner" => $row->owner,
603  "create_date" => $row->create_date,
604  "last_update" => $row->last_update
605  ];
606  }
607 
608  $this->filterWorkspaceObjects($this->invalid_rolefolders);
609  if (count($this->invalid_rolefolders) > 0) {
610  $this->writeScanLogLine("obj_id\ttype\tref_id\tchild\ttitle\tdesc\towner\tcreate_date\tlast_update");
611  $this->writeScanLogArray($this->invalid_rolefolders);
612  return true;
613  }
614 
615  $this->writeScanLogLine("none");
616  return false;
617  }
618 
627  public function getMissingObjects(): array
628  {
629  return $this->missing_objects;
630  }
631 
637  public function findInvalidReferences(): bool
638  {
639  $ilDB = $this->db;
640 
641  // check mode: analyze
642  if ($this->mode["scan"] !== true) {
643  return false;
644  }
645 
646  // init
647  $this->invalid_references = [];
648 
649  $this->writeScanLogLine("\nfindInvalidReferences:");
650  $q = "SELECT object_reference.* FROM object_reference " .
651  "LEFT JOIN object_data ON object_data.obj_id = object_reference.obj_id " .
652  "WHERE object_data.obj_id IS NULL " .
653  "OR " . $ilDB->in('object_data.type', $this->rbac_object_types, true, 'text');
654  $r = $this->db->query($q);
655 
656  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
657  $this->invalid_references[] = [
658  "ref_id" => $row->ref_id,
659  "obj_id" => $row->obj_id,
660  "msg" => "Object does not exist."
661  ];
662  }
663 
664  $this->filterWorkspaceObjects($this->invalid_references);
665  if (count($this->invalid_references) > 0) {
666  $this->writeScanLogLine("ref_id\t\tobj_id");
667  $this->writeScanLogArray($this->invalid_references);
668  return true;
669  }
670 
671  $this->writeScanLogLine("none");
672  return false;
673  }
674 
678  public function getInvalidReferences(): array
679  {
681  }
682 
688  public function findInvalidChilds(): bool
689  {
690  // check mode: analyze
691  if ($this->mode["scan"] !== true) {
692  return false;
693  }
694 
695  // init
696  $this->invalid_childs = [];
697 
698  $this->writeScanLogLine("\nfindInvalidChilds:");
699 
700  $q = "SELECT tree.*,object_reference.ref_id FROM tree " .
701  "LEFT JOIN object_reference ON tree.child = object_reference.ref_id " .
702  "LEFT JOIN object_data ON object_reference.obj_id = object_data.obj_id " .
703  "WHERE object_reference.ref_id IS NULL or object_data.obj_id IS NULL";
704  $r = $this->db->query($q);
705  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
706  $this->invalid_childs[] = [
707  "child" => $row->child,
708  "ref_id" => $row->ref_id,
709  "msg" => "No object found"
710  ];
711  }
712 
713  if (count($this->invalid_childs) > 0) {
714  $this->writeScanLogLine("child\t\tref_id");
715  $this->writeScanLogArray($this->invalid_childs);
716  return true;
717  }
718 
719  $this->writeScanLogLine("none");
720  return false;
721  }
722 
726  public function getInvalidChilds(): array
727  {
728  return $this->invalid_childs;
729  }
730 
738  public function findUnboundObjects(): bool
739  {
740  // check mode: analyze
741  if ($this->mode["scan"] !== true) {
742  return false;
743  }
744 
745  // init
746  $this->unbound_objects = [];
747 
748  $this->writeScanLogLine("\nfindUnboundObjects:");
749 
750  $q = "SELECT T1.tree,T1.child,T1.parent," .
751  "T2.tree deleted,T2.parent grandparent " .
752  "FROM tree T1 " .
753  "LEFT JOIN tree T2 ON T2.child=T1.parent " .
754  "WHERE (T2.tree!=1 OR T2.tree IS NULL) AND T1.parent!=0";
755  $r = $this->db->query($q);
756 
757  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
758  // exclude deleted nodes
759  if ($row->deleted === null) {
760  $this->unbound_objects[] = [
761  "child" => $row->child,
762  "parent" => $row->parent,
763  "tree" => $row->tree,
764  "msg" => "No valid parent node found"
765  ];
766  }
767  }
768 
769  if (count($this->unbound_objects) > 0) {
770  $this->writeScanLogLine("child\t\tparent\ttree");
771  $this->writeScanLogArray($this->unbound_objects);
772  return true;
773  }
774 
775  $this->writeScanLogLine("none");
776  return false;
777  }
778 
786  public function findDeletedObjects(): bool
787  {
788  // check mode: analyze
789  if ($this->mode["scan"] !== true) {
790  return false;
791  }
792 
793  // init
794  $this->deleted_objects = [];
795 
796  $this->writeScanLogLine("\nfindDeletedObjects:");
797 
798  // Delete objects, start with the oldest objects first
799  $query = "SELECT object_data.*,tree.tree,tree.child,tree.parent,deleted " .
800  "FROM object_data " .
801  "LEFT JOIN object_reference ON object_data.obj_id=object_reference.obj_id " .
802  "LEFT JOIN tree ON tree.child=object_reference.ref_id " .
803  " WHERE tree != 1 " .
804  " ORDER BY deleted";
805  $r = $this->db->query($query);
806 
807  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
808  $tmp_date = new ilDateTime($row->deleted, IL_CAL_DATETIME);
809 
810  $this->deleted_objects[] = [
811  "child" => $row->child,
812  "parent" => $row->parent,
813  "tree" => $row->tree,
814  "type" => $row->type,
815  "title" => $row->title,
816  "desc" => $row->description,
817  "owner" => $row->owner,
818  "deleted" => $row->deleted,
819  "deleted_timestamp" => $tmp_date->get(IL_CAL_UNIX),
820  "create_date" => $row->create_date,
821  "last_update" => $row->last_update
822  ];
823  }
824 
825  if (count($this->deleted_objects) > 0) {
826  $this->writeScanLogArray([array_keys($this->deleted_objects[0])]);
827  $this->writeScanLogArray($this->deleted_objects);
828  return true;
829  }
830  $this->writeScanLogLine("none");
831  return false;
832  }
833 
834 
843  public function getUnboundObjects(): array
844  {
845  return $this->unbound_objects;
846  }
847 
851  public function getDeletedObjects(): array
852  {
853  return $this->deleted_objects;
854  }
855 
859  public function getInvalidRolefolders(): array
860  {
862  }
863 
869  public function removeInvalidReferences(
870  array $a_invalid_refs = null
871  ): bool {
872  $ilLog = $this->log;
873  $ilDB = $this->db;
874 
875  // check mode: clean
876  if ($this->mode["clean"] !== true) {
877  return false;
878  }
879 
880  $this->writeScanLogLine("\nremoveInvalidReferences:");
881 
882  if ($a_invalid_refs === null && isset($this->invalid_references)) {
883  $a_invalid_refs = &$this->invalid_references;
884  }
885 
886  // handle wrong input
887  if (!is_array($a_invalid_refs)) {
888  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
889  return false;
890  }
891  // no unbound references found. do nothing
892  if (count($a_invalid_refs) === 0) {
893  $this->writeScanLogLine("none");
894  return false;
895  }
896 
897  /*******************
898  removal starts here
899  ********************/
900 
901  $message = sprintf(
902  '%s::removeInvalidReferences(): Started...',
903  get_class($this)
904  );
905  $ilLog->write($message, $ilLog->WARNING);
906 
907  // to make sure
908  $this->filterWorkspaceObjects($a_invalid_refs);
909 
910  foreach ($a_invalid_refs as $entry) {
911  $query = "DELETE FROM object_reference WHERE ref_id= " . $this->db->quote($entry["ref_id"], 'integer') .
912  " AND obj_id = " . $this->db->quote($entry["obj_id"], 'integer') . " ";
913  $res = $ilDB->manipulate($query);
914 
915  $message = sprintf(
916  '%s::removeInvalidReferences(): Reference %s removed',
917  get_class($this),
918  $entry["ref_id"]
919  );
920  $ilLog->write($message, $ilLog->WARNING);
921 
922  $this->writeScanLogLine("Entry " . $entry["ref_id"] . " removed");
923  }
924 
925  return true;
926  }
927 
933  public function removeInvalidChilds(
934  array $a_invalid_childs = null
935  ): bool {
936  $ilLog = $this->log;
937 
938  // check mode: clean
939  if ($this->mode["clean"] !== true) {
940  return false;
941  }
942 
943  $this->writeScanLogLine("\nremoveInvalidChilds:");
944 
945  if ($a_invalid_childs === null && isset($this->invalid_childs)) {
946  $a_invalid_childs = &$this->invalid_childs;
947  }
948 
949  // handle wrong input
950  if (!is_array($a_invalid_childs)) {
951  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
952  return false;
953  }
954 
955  // no unbound childs found. do nothing
956  if (count($a_invalid_childs) === 0) {
957  $this->writeScanLogLine("none");
958  return false;
959  }
960 
961  /*******************
962  removal starts here
963  ********************/
964 
965  $message = sprintf(
966  '%s::removeInvalidChilds(): Started...',
967  get_class($this)
968  );
969  $ilLog->write($message, $ilLog->WARNING);
970 
971  foreach ($a_invalid_childs as $entry) {
972  $q = "DELETE FROM tree WHERE child='" . $entry["child"] . "'";
973  $this->db->query($q);
974 
975  $message = sprintf(
976  '%s::removeInvalidChilds(): Entry child=%s removed',
977  get_class($this),
978  $entry["child"]
979  );
980  $ilLog->write($message, $ilLog->WARNING);
981 
982  $this->writeScanLogLine("Entry " . $entry["child"] . " removed");
983  }
984 
985  return true;
986  }
987 
994  public function removeInvalidRolefolders(
995  array $a_invalid_rolefolders = null
996  ): bool {
997  $ilLog = $this->log;
998 
999  // check mode: clean
1000  if ($this->mode["clean"] !== true) {
1001  return false;
1002  }
1003 
1004  $this->writeScanLogLine("\nremoveInvalidRolefolders:");
1005 
1006  if ($a_invalid_rolefolders === null && isset($this->invalid_rolefolders)) {
1007  $a_invalid_rolefolders = $this->invalid_rolefolders;
1008  }
1009 
1010  // handle wrong input
1011  if (!is_array($a_invalid_rolefolders)) {
1012  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1013  return false;
1014  }
1015 
1016  // no invalid rolefolders found. do nothing
1017  if (count($a_invalid_rolefolders) === 0) {
1018  $this->writeScanLogLine("none");
1019  return false;
1020  }
1021 
1022  /*******************
1023  removal starts here
1024  ********************/
1025 
1026  $removed = false;
1027 
1028  $message = sprintf(
1029  '%s::removeInvalidRolefolders(): Started...',
1030  get_class($this)
1031  );
1032  $ilLog->write($message, $ilLog->WARNING);
1033 
1034  // to make sure
1035  $this->filterWorkspaceObjects($a_invalid_rolefolders);
1036 
1037  foreach ($a_invalid_rolefolders as $rolf) {
1038  // restore ref_id in case of missing
1039  if ($rolf["ref_id"] === null) {
1040  $rolf["ref_id"] = $this->restoreReference($rolf["obj_id"]);
1041 
1042  $this->writeScanLogLine("Created missing reference '" . $rolf["ref_id"] . "' for rolefolder object '" . $rolf["obj_id"] . "'");
1043  }
1044 
1045  // now delete rolefolder
1046  $obj_data = ilObjectFactory::getInstanceByRefId($rolf["ref_id"]);
1047  $obj_data->delete();
1048  unset($obj_data);
1049  $removed = true;
1050  $this->writeScanLogLine("Removed invalid rolefolder '" . $rolf["title"] . "' (id=" . $rolf["obj_id"] . ",ref=" . $rolf["ref_id"] . ") from system");
1051  }
1052 
1053  return $removed;
1054  }
1055 
1064  public function restoreMissingObjects(
1065  array $a_missing_objects = null
1066  ): bool {
1067  $rbacadmin = $this->rbacadmin;
1068  $ilLog = $this->log;
1069 
1070  // check mode: restore
1071  if ($this->mode["restore"] !== true) {
1072  return false;
1073  }
1074 
1075  $this->writeScanLogLine("\nrestoreMissingObjects:");
1076 
1077  if ($a_missing_objects === null && isset($this->missing_objects)) {
1078  $a_missing_objects = $this->missing_objects;
1079  }
1080 
1081  // handle wrong input
1082  if (!is_array($a_missing_objects)) {
1083  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1084  return false;
1085  }
1086 
1087  // no missing objects found. do nothing
1088  if (count($a_missing_objects) === 0) {
1089  $this->writeScanLogLine("none");
1090  return false;
1091  }
1092 
1093  /*******************
1094  restore starts here
1095  ********************/
1096 
1097  $restored = false;
1098 
1099  $message = sprintf(
1100  '%s::restoreMissingObjects(): Started...',
1101  get_class($this)
1102  );
1103  $ilLog->write($message, $ilLog->WARNING);
1104 
1105  // to make sure
1106  $this->filterWorkspaceObjects($a_missing_objects);
1107 
1108  foreach ($a_missing_objects as $missing_obj) {
1109  // restore ref_id in case of missing
1110  if ($missing_obj["ref_id"] === null) {
1111  $missing_obj["ref_id"] = $this->restoreReference($missing_obj["obj_id"]);
1112 
1113  $this->writeScanLogLine("Created missing reference '" . $missing_obj["ref_id"] . "' for object '" . $missing_obj["obj_id"] . "'");
1114  }
1115 
1116  // put in tree under RecoveryFolder if not on exclude list
1117  #if (!in_array($missing_obj["type"],$this->object_types_exclude))
1118  if (!$this->isExcludedFromRecovery($missing_obj['type'], $missing_obj['obj_id'])) {
1119  $rbacadmin->revokePermission((int) $missing_obj["ref_id"]);
1120  $obj_data = ilObjectFactory::getInstanceByRefId($missing_obj["ref_id"]);
1121  $obj_data->putInTree(RECOVERY_FOLDER_ID);
1122  $obj_data->setPermissions(RECOVERY_FOLDER_ID);
1123  unset($obj_data);
1124  //$tree->insertNode($missing_obj["ref_id"],RECOVERY_FOLDER_ID);
1125  $restored = true;
1126  $this->writeScanLogLine("Restored object '" . $missing_obj["title"] . "' (id=" . $missing_obj["obj_id"] . ",ref=" . $missing_obj["ref_id"] . ") in 'Restored objects folder'");
1127  }
1128 
1129  // TODO: process rolefolders
1130  }
1131 
1132  return $restored;
1133  }
1134 
1140  public function restoreReference(int $a_obj_id)
1141  {
1142  $ilLog = $this->log;
1143  $ilDB = $this->db;
1144 
1145  if (empty($a_obj_id)) {
1146  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1147  return false;
1148  }
1149 
1150  $next_id = $ilDB->nextId('object_reference');
1151  $query = "INSERT INTO object_reference (ref_id, obj_id) " .
1152  "VALUES (" . $this->db->quote($next_id, 'integer') . ", " . $this->db->quote($a_obj_id, 'integer') . ")";
1153  $res = $ilDB->manipulate($query);
1154 
1155  $message = sprintf(
1156  '%s::restoreReference(): new reference %s for obj_id %s created',
1157  get_class($this),
1158  $next_id,
1159  $a_obj_id
1160  );
1161  $ilLog->write($message, $ilLog->WARNING);
1162 
1163  return $next_id;
1164  }
1165 
1171  public function restoreUnboundObjects(
1172  array $a_unbound_objects = null
1173  ): bool {
1174  $ilLog = $this->log;
1175 
1176  // check mode: restore
1177  if ($this->mode["restore"] !== true) {
1178  return false;
1179  }
1180 
1181  $this->writeScanLogLine("\nrestoreUnboundObjects:");
1182 
1183  if ($a_unbound_objects === null && isset($this->unbound_objects)) {
1184  $a_unbound_objects = $this->unbound_objects;
1185  }
1186 
1187  // handle wrong input
1188  if (!is_array($a_unbound_objects)) {
1189  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1190  return false;
1191  }
1192 
1193  $message = sprintf(
1194  '%s::restoreUnboundObjects(): Started...',
1195  get_class($this)
1196  );
1197  $ilLog->write($message, $ilLog->WARNING);
1198 
1199  // start restore process
1200  return $this->restoreSubTrees($a_unbound_objects);
1201  }
1202 
1209  public function restoreTrash(
1210  array $a_deleted_objects = null
1211  ): bool {
1212  $ilLog = $this->log;
1213 
1214  // check mode: restore
1215  if ($this->mode["restore_trash"] !== true) {
1216  return false;
1217  }
1218 
1219  $this->writeScanLogLine("\nrestoreTrash:");
1220 
1221  if ($a_deleted_objects === null && isset($this->deleted_objects)) {
1222  $a_deleted_objects = $this->deleted_objects;
1223  }
1224 
1225  // handle wrong input
1226  if (!is_array($a_deleted_objects)) {
1227  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1228  return false;
1229  }
1230 
1231  $message = sprintf(
1232  '%s::restoreTrash(): Started...',
1233  get_class($this)
1234  );
1235  $ilLog->write($message, $ilLog->WARNING);
1236 
1237  // start restore process
1238  $restored = $this->restoreDeletedObjects($a_deleted_objects);
1239 
1240  if ($restored) {
1241  $q = "DELETE FROM tree WHERE tree!=1";
1242  $this->db->query($q);
1243 
1244  $message = sprintf(
1245  '%s::restoreTrash(): Removed all trees with tree id <> 1',
1246  get_class($this)
1247  );
1248  $ilLog->write($message, $ilLog->WARNING);
1249 
1250  $this->writeScanLogLine("Old tree entries removed");
1251  }
1252 
1253  return $restored;
1254  }
1255 
1264  public function restoreDeletedObjects(
1265  array $a_nodes
1266  ): bool {
1267  $tree = $this->tree;
1268  $rbacadmin = $this->rbacadmin;
1269  $ilLog = $this->log;
1270  // handle wrong input
1271  if (!is_array($a_nodes)) {
1272  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1273  return false;
1274  }
1275 
1276  // no invalid parents found. do nothing
1277  if (count($a_nodes) === 0) {
1278  $this->writeScanLogLine("none");
1279  return false;
1280  }
1281 
1282  $message = sprintf(
1283  '%s::restoreDeletedObjects()): Started...',
1284  get_class($this)
1285  );
1286  $ilLog->write($message, $ilLog->WARNING);
1287 
1288  // first delete all rolefolders
1289  // don't save rolefolders, remove them
1290  // TODO process ROLE_FOLDER_ID
1291  foreach ($a_nodes as $key => $node) {
1292  if ($node["type"] === "rolf") {
1293  // delete old tree entries
1294  $tree->deleteTree($node);
1295 
1296  $obj_data = ilObjectFactory::getInstanceByRefId($node["child"]);
1297  $obj_data->delete();
1298  unset($a_nodes[$key]);
1299  }
1300  }
1301 
1302  // process move
1303  foreach ($a_nodes as $node) {
1304  // delete old tree entries
1305  $tree->deleteTree($node);
1306 
1307  $rbacadmin->revokePermission((int) $node["child"]);
1308  $obj_data = ilObjectFactory::getInstanceByRefId($node["child"]);
1309  $obj_data->putInTree(RECOVERY_FOLDER_ID);
1310  $obj_data->setPermissions(RECOVERY_FOLDER_ID);
1311  }
1312 
1313  return true;
1314  }
1315 
1321  public function restoreSubTrees(
1322  array $a_nodes
1323  ): bool {
1324  $tree = $this->tree;
1325  $rbacadmin = $this->rbacadmin;
1326  $ilLog = $this->log;
1327 
1328  // handle wrong input
1329  if (!is_array($a_nodes)) {
1330  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1331  return false;
1332  }
1333 
1334  // no invalid parents found. do nothing
1335  if (count($a_nodes) === 0) {
1336  $this->writeScanLogLine("none");
1337  return false;
1338  }
1339 
1340  /*******************
1341  restore starts here
1342  ********************/
1343 
1344  $subnodes = [];
1345  $topnode = [];
1346 
1347  $message = sprintf(
1348  '%s::restoreSubTrees(): Started...',
1349  get_class($this)
1350  );
1351  $ilLog->write($message, $ilLog->WARNING);
1352 
1353  // process move subtree
1354  foreach ($a_nodes as $node) {
1355  // get node data
1356  $topnode = $tree->getNodeData($node["child"], $node['tree']);
1357 
1358  // don't save rolefolders, remove them
1359  // TODO process ROLE_FOLDER_ID
1360  if ($topnode["type"] === "rolf") {
1361  $rolfObj = ilObjectFactory::getInstanceByRefId($topnode["child"]);
1362  $rolfObj->delete();
1363  unset($top_node, $rolfObj);
1364  continue;
1365  }
1366 
1367  // get subnodes of top nodes
1368  $subnodes[$node["child"]] = $tree->getSubTree($topnode);
1369 
1370  // delete old tree entries
1371  $tree->deleteTree($topnode);
1372  }
1373 
1374  // now move all subtrees to new location
1375  // TODO: this whole put in place again stuff needs revision. Permission settings get lost.
1376  foreach ($subnodes as $key => $subnode) {
1377  // first paste top_node ...
1378  $rbacadmin->revokePermission((int) $key);
1379  $obj_data = ilObjectFactory::getInstanceByRefId($key);
1380  $obj_data->putInTree(RECOVERY_FOLDER_ID);
1381  $obj_data->setPermissions(RECOVERY_FOLDER_ID);
1382 
1383  $this->writeScanLogLine("Object '" . $obj_data->getId() . "' restored.");
1384 
1385  // ... remove top_node from list ...
1386  array_shift($subnode);
1387 
1388  // ... insert subtree of top_node if any subnodes exist
1389  if (count($subnode) > 0) {
1390  foreach ($subnode as $node) {
1391  $rbacadmin->revokePermission((int) $node["child"]);
1392  $obj_data = ilObjectFactory::getInstanceByRefId($node["child"]);
1393  $obj_data->putInTree($node["parent"]);
1394  $obj_data->setPermissions($node["parent"]);
1395 
1396  $this->writeScanLogLine("Object '" . $obj_data->getId() . "' restored.");
1397  }
1398  }
1399  }
1400 
1401  // final clean up
1402  $this->findInvalidChilds();
1403  $this->removeInvalidChilds();
1404 
1405  return true;
1406  }
1407 
1413  public function purgeTrash(
1414  array $a_nodes = null
1415  ): bool {
1416  $ilLog = $this->log;
1417 
1418  // check mode: purge_trash
1419  if ($this->mode["purge_trash"] !== true) {
1420  return false;
1421  }
1422 
1423  $this->writeScanLogLine("\npurgeTrash:");
1424 
1425  if ($a_nodes === null && isset($this->deleted_objects)) {
1426  $a_nodes = $this->deleted_objects;
1427  }
1428  $message = sprintf(
1429  '%s::purgeTrash(): Started...',
1430  get_class($this)
1431  );
1432  $ilLog->write($message, $ilLog->WARNING);
1433 
1434  // start purge process
1435  return $this->purgeObjects($a_nodes);
1436  }
1437 
1443  public function purgeUnboundObjects(
1444  array $a_nodes = null
1445  ): bool {
1446  $ilLog = $this->log;
1447 
1448  // check mode: purge
1449  if ($this->mode["purge"] !== true) {
1450  return false;
1451  }
1452 
1453  $this->writeScanLogLine("\npurgeUnboundObjects:");
1454 
1455  if ($a_nodes === null && isset($this->unbound_objects)) {
1456  $a_nodes = $this->unbound_objects;
1457  }
1458 
1459  $message = sprintf(
1460  '%s::purgeUnboundObjects(): Started...',
1461  get_class($this)
1462  );
1463  $ilLog->write($message, $ilLog->WARNING);
1464 
1465  // start purge process
1466  return $this->purgeObjects($a_nodes);
1467  }
1468 
1474  public function purgeMissingObjects(
1475  array $a_nodes = null
1476  ): bool {
1477  $ilLog = $this->log;
1478 
1479  // check mode: purge
1480  if ($this->mode["purge"] !== true) {
1481  return false;
1482  }
1483 
1484  $this->writeScanLogLine("\npurgeMissingObjects:");
1485 
1486  if ($a_nodes === null && isset($this->missing_objects)) {
1487  $a_nodes = $this->missing_objects;
1488  }
1489 
1490  $message = sprintf(
1491  '%s::purgeMissingObjects(): Started...',
1492  get_class($this)
1493  );
1494  $ilLog->write($message, $ilLog->WARNING);
1495 
1496  // start purge process
1497  return $this->purgeObjects($a_nodes);
1498  }
1499 
1505  public function purgeObjects(
1506  array $a_nodes
1507  ): bool {
1508  $ilLog = $this->log;
1509  $ilUser = $this->user;
1510 
1511  // Get purge limits
1512  $count_limit = $ilUser->getPref("systemcheck_count_limit");
1513  if (!is_numeric($count_limit) || $count_limit < 0) {
1514  $count_limit = count($a_nodes);
1515  }
1516  $timestamp_limit = time();
1517  $age_limit = $ilUser->getPref("systemcheck_age_limit");
1518  if (is_numeric($age_limit) && $age_limit > 0) {
1519  $timestamp_limit -= $age_limit * 60 * 60 * 24;
1520  }
1521  $type_limit = $ilUser->getPref("systemcheck_type_limit");
1522  if ($type_limit) {
1523  $type_limit = trim($type_limit);
1524  if ($type_limit === '') {
1525  $type_limit = null;
1526  }
1527  }
1528 
1529  // handle wrong input
1530  if (!is_array($a_nodes)) {
1531  $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1532  return false;
1533  }
1534 
1535  // start delete process
1536  $this->writeScanLogLine("action\tref_id\tobj_id\ttype\telapsed\ttitle");
1537  $count = 0;
1538  foreach ($a_nodes as $node) {
1539  if ($type_limit && $node['type'] != $type_limit) {
1540  $this->writeScanLogLine(
1541  "skip\t" .
1542  $node['child'] . "\t\t" . $node['type'] . "\t\t" . $node['title']
1543  );
1544  continue;
1545  }
1546 
1547 
1548  $count++;
1549  if ($count > $count_limit) {
1550  $this->writeScanLogLine("Stopped purging after " . ($count - 1) . " objects, because count limit was reached: " . $count_limit);
1551  break;
1552  }
1553  if ($node["deleted_timestamp"] > $timestamp_limit) {
1554  $this->writeScanLogLine("Stopped purging after " . ($count - 1) . " objects, because timestamp limit was reached: " . date("c", $timestamp_limit));
1555  continue;
1556  }
1557 
1558  $ref_id = ($node["child"]) ?: $node["ref_id"];
1559  $node_obj = ilObjectFactory::getInstanceByRefId($ref_id, false);
1560 
1561  if ($node_obj === false) {
1562  $this->invalid_objects[] = $node;
1563  continue;
1564  }
1565 
1566  $message = sprintf(
1567  '%s::purgeObjects(): Removing object (id:%s ref:%s)',
1568  get_class($this),
1569  $ref_id,
1570  $node_obj->getId()
1571  );
1572  $ilLog->write($message, $ilLog->WARNING);
1573 
1574  $startTime = microtime(true);
1575  $node_obj->delete();
1576  ilTree::_removeEntry($node["tree"], $ref_id);
1577  $endTime = microtime(true);
1578 
1579  $this->writeScanLogLine("purged\t" . $ref_id . "\t" . $node_obj->getId() .
1580  "\t" . $node['type'] . "\t" . round($endTime - $startTime, 1) . "\t" . $node['title']);
1581  }
1582 
1583  $this->findInvalidChilds();
1584  $this->removeInvalidChilds();
1585 
1586  return true;
1587  }
1588 
1599  public function initGapsInTree(): bool
1600  {
1601  $tree = $this->tree;
1602  $ilLog = $this->log;
1603 
1604  $message = sprintf(
1605  '%s::initGapsInTree(): Started...',
1606  get_class($this)
1607  );
1608  $ilLog->write($message, $ilLog->WARNING);
1609 
1610  // check mode: clean
1611  if ($this->mode["clean"] !== true) {
1612  return false;
1613  }
1614  $this->writeScanLogLine("\nrenumberTree:");
1615 
1616  $tree->renumber(ROOT_FOLDER_ID);
1617 
1618  $this->writeScanLogLine("done");
1619 
1620  return true;
1621  }
1622 
1629  public function handleErr(
1630  object $error
1631  ): void {
1632  $call_loc = $error->backtrace[count($error->backtrace) - 1];
1633  $num_args = count($call_loc["args"]);
1634  $arg_list = [];
1635  $arg_str = "";
1636  if ($num_args > 0) {
1637  foreach ($call_loc["args"] as $arg) {
1638  $type = gettype($arg);
1639 
1640  switch ($type) {
1641  case "string":
1642  $value = strlen($arg);
1643  break;
1644 
1645  case "array":
1646  $value = count($arg);
1647  break;
1648 
1649  case "object":
1650  $value = get_class($arg);
1651  break;
1652 
1653  case "boolean":
1654  $value = ($arg) ? "true" : "false";
1655  break;
1656 
1657  default:
1658  $value = $arg;
1659  break;
1660  }
1661 
1662  $arg_list[] = [
1663  "type" => $type,
1664  "value" => "(" . $value . ")"
1665  ];
1666  }
1667 
1668  foreach ($arg_list as $arg) {
1669  $arg_str .= implode("", $arg) . " ";
1670  }
1671  }
1672 
1673  $err_msg = "<br/><b>" . $error->getCode() . ":</b> " . $error->getMessage() . " in " . $call_loc["class"] . $call_loc["type"] . $call_loc["function"] . "()" .
1674  "<br/>Called from: " . basename($call_loc["file"]) . " , line " . $call_loc["line"] .
1675  "<br/>Passed parameters: [" . $num_args . "] " . $arg_str . "<br/>";
1676  printf($err_msg);
1677 
1678  /*
1679  if ($error->getUserInfo()) {
1680  printf("<br/>Parameter details:");
1681  echo "<pre>";
1682  var_dump($call_loc["args"]);// TODO PHP8-REVIEW This should be removed and the ilLogger should be used instead
1683  echo "</pre>";
1684  }*/
1685 
1686  if ($error->getCode() == FATAL) {
1687  exit();
1688  }
1689  }
1690 
1691  public function writeScanLogArray(array $a_arr): void
1692  {
1693  if (!$this->isLogEnabled()) {
1694  return;
1695  }
1696 
1697  foreach ($a_arr as $entry) {
1698  $this->scan_log->write(implode("\t", $entry));
1699  }
1700  }
1701 
1702  public function writeScanLogLine(string $a_msg): void
1703  {
1704  if (!$this->isLogEnabled()) {
1705  return;
1706  }
1707 
1708  $this->scan_log->write($a_msg);
1709  }
1710 
1714  public function hasScanLog(): bool
1715  {
1716  // file check
1717  return is_file(CLIENT_DATA_DIR . "/" . $this->scan_log_file);
1718  }
1719 
1723  public function deleteScanLog(): void
1724  {
1725  unlink(CLIENT_DATA_DIR . "/" . $this->scan_log_file);
1726  }
1727 
1728  public function readScanLog(): ?array
1729  {
1730  // file check
1731  if (!$this->hasScanLog()) {
1732  return null;
1733  }
1734 
1735  $scanfile = file(CLIENT_DATA_DIR . "/" . $this->scan_log_file);
1736  if (!$scan_log = $this->get_last_scan($scanfile)) {
1737  return null;
1738  }
1739  // Ensure that memory is freed
1740  unset($scanfile);
1741 
1742  return $scan_log;
1743  }
1744 
1745  public function get_last_scan(array $a_scan_log): ?array
1746  {
1747  $logs = array_keys($a_scan_log, $this->scan_log_separator . "\n");
1748 
1749  if (count($logs) > 0) {
1750  return array_slice($a_scan_log, array_pop($logs) + 2);
1751  }
1752 
1753  return null;
1754  }
1755 
1756  public function checkTreeStructure(): bool
1757  {
1758  $this->writeScanLogLine("\nchecking tree structure is disabled");
1759 
1760  return false;
1761  }
1762 
1767  public function dumpTree(): int
1768  {
1769  $this->writeScanLogLine("BEGIN dumpTree:");
1770 
1771  // collect nodes with duplicate child Id's
1772  // (We use this, to mark these nodes later in the output as being
1773  // erroneous.).
1774  $q = 'SELECT child FROM tree GROUP BY child HAVING COUNT(*) > 1';
1775  $r = $this->db->query($q);
1776  $duplicateNodes = [];
1777  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
1778  $duplicateNodes[] = $row->child;
1779  }
1780 
1781  // dump tree
1782  $q = "SELECT tree.*,ref.ref_id,dat.obj_id objobj_id,ref.obj_id refobj_id,ref.deleted,dat.* "
1783  . "FROM tree "
1784  . "RIGHT JOIN object_reference ref ON tree.child = ref.ref_id "
1785  . "RIGHT JOIN object_data dat ON ref.obj_id = dat.obj_id "
1786 // ."LEFT JOIN usr_data usr ON usr.usr_id = dat.owner "
1787  . "ORDER BY tree, lft, type, dat.title";
1788  $r = $this->db->query($q);
1789 
1790  $this->writeScanLogLine(
1791  '<table><tr>'
1792  . '<td>tree, child, parent, lft, rgt, depth</td>'
1793  . '<td>ref_id, ref.obj_id, deleted</td>'
1794  . '<td>obj_id, type, owner, title</td>'
1795  . '</tr>'
1796  );
1797 
1798  // We use a stack to represent the path to the current node.
1799  // This allows us to do analyze the tree structure without having
1800  // to implement a recursive algorithm.
1801  $stack = [];
1802  $error_count = 0;
1803  $repository_tree_count = 0;
1804  $trash_trees_count = 0;
1805  $other_trees_count = 0;
1806  $not_in_tree_count = 0;
1807 
1808  // The previous number is used for gap checking
1809  $previousNumber = 0;
1810 
1811  $this->initWorkspaceObjects();
1812  $isRefRefOkay = true;
1813  $isRefObjOkay = true;
1814  $isChildOkay = true;
1815  $indent = "";
1816  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
1817  // workspace objects are not to be processed
1818  if ($this->workspace_object_ids &&
1819  in_array($row->objobj_id, $this->workspace_object_ids)) {
1820  continue;
1821  }
1822 
1823  // If there is no entry in table tree for the object, we display it here
1824  if (is_null($row->child)) {
1825  switch ($row->type) {
1826  case 'crsg':
1827  case 'usr':
1828  case 'typ':
1829  case 'lng':
1830  case 'rolt':
1831  case 'role':
1832  case 'mob':
1833  case 'sty':
1834  case 'tax': // #13798
1835  // We are not interested in dumping these object types.
1836  continue 2;
1837  //break; NOT REACHED
1838  case 'file':
1839  if (is_null($row->ref_id)) {
1840  // File objects can be part of a learning module.
1841  // In this case, they do not have a row in table object_reference.
1842  // We are not interested in dumping these file objects.
1843  continue 2;
1844  }
1845 
1846  // File objects which have a row in table object_reference, but
1847  // none in table tree are an error.
1848  $error_count++;
1849  $isRowOkay = false;
1850  $isParentOkay = false;
1851  $isLftOkay = false;
1852  $isRgtOkay = false;
1853  $isDepthOkay = false;
1854  break;
1855 
1856  default:
1857  // ignore folders on media pools
1858  if ($row->type === "fold" && $this->isMediaFolder($row->obj_id)) {
1859  continue 2;
1860  }
1861  $error_count++;
1862  $isRowOkay = false;
1863  $isParentOkay = false;
1864  $isLftOkay = false;
1865  $isRgtOkay = false;
1866  $isDepthOkay = false;
1867  break;
1868  }
1869 
1870  // moved here (below continues in switch)
1871  $not_in_tree_count++;
1872 
1873  $this->writeScanLogLine(
1874  '<tr>'
1875  . '<td>'
1876  . (($isRowOkay) ? '' : '<font color=#ff0000>')
1877  . $row->tree . ', '
1878  . $row->child . ', '
1879  . (($isParentOkay) ? '' : 'parent:<b>')
1880  . $row->parent
1881  . (($isParentOkay) ? '' : '</b>')
1882  . ', '
1883  . (($isLftOkay) ? '' : 'lft:<b>')
1884  . $row->lft
1885  . (($isLftOkay) ? '' : '</b>')
1886  . ', '
1887  . (($isRgtOkay) ? '' : 'rgt:<b>')
1888  . $row->rgt
1889  . (($isRgtOkay) ? '' : '</b>')
1890  . ', '
1891  . (($isDepthOkay) ? '' : 'depth:<b>')
1892  . $row->depth
1893  . (($isDepthOkay) ? '' : '</b>')
1894  . (($isRowOkay) ? '' : '</font>')
1895  . '</td><td>'
1896  . (($isRowOkay) ? '' : '<font color=#ff0000>')
1897  . (($isRefRefOkay && $isChildOkay) ? '' : 'ref.ref_id:<b>')
1898  . $row->ref_id
1899  . (($isRefRefOkay && $isChildOkay) ? '' : '</b>')
1900  . ', '
1901  . (($isRefObjOkay) ? '' : 'ref.obj_id:<b>')
1902  . $row->refobj_id
1903  . (($isRefObjOkay) ? '' : '</b>')
1904  . (($isRowOkay) ? '' : '<font color=#ff0000>')
1905  . (($row->deleted != null) ? ', ' . $row->deleted : '')
1906  . '</td><td>'
1907  . (($isRowOkay) ? '' : '<font color=#ff0000>')
1908  . $indent
1909  . $row->obj_id . ', '
1910  . $row->type . ', '
1911  . ($row->login ?? '') . ', '
1912  . $row->title
1913  . (($isRowOkay) ? '' : ' <b>*ERROR*</b><font color=#ff0000>')
1914  . '</td>'
1915  . '</tr>'
1916  );
1917  continue;
1918  }
1919 
1920  // Update stack
1921  // -------------------
1922  $indent = "";
1923  for ($i = 1; $i < $row->depth; $i++) {
1924  $indent .= ". ";
1925  }
1926 
1927  // Initialize the stack and the previous number if we are in a new tree
1928  if (count($stack) === 0 || $stack[0]->tree != $row->tree) {
1929  $stack = [];
1930  $previousNumber = $row->lft - 1;
1931  $this->writeScanLogLine('<tr><td>&nbsp;</td></tr>');
1932  }
1933  // Pop old stack entries
1934 
1935 
1936  while (count($stack) > 0 && $stack[count($stack) - 1]->rgt < $row->lft) {
1937  $popped = array_pop($stack);
1938 
1939  // check for gap
1940  $gap = $popped->rgt - $previousNumber - 1;
1941  if ($gap > 0) {
1942  $poppedIndent = "";
1943  for ($i = 1; $i < $popped->depth; $i++) {
1944  $poppedIndent .= ". ";
1945  }
1946  $this->writeScanLogLine(
1947  '<tr>'
1948  . '<td colspan=2><div align="right">'
1949  . '<font color=#00cc00>*gap* for ' . ($gap / 2) . ' nodes at end of&nbsp;</font>'
1950  . '</div></td>'
1951  . '<td>'
1952  . '<font color=#00cc00>'
1953  . $poppedIndent
1954  . $popped->obj_id . ', '
1955  . $popped->type . ', '
1956  . ($popped->login ?? '') . ', '
1957  . $popped->title
1958  . '</font>'
1959  . '</td>'
1960  . '</tr>'
1961  );
1962  }
1963  $previousNumber = $popped->rgt;
1964  unset($popped);
1965  }
1966 
1967  // Check row integrity
1968  // -------------------
1969  $isRowOkay = true;
1970 
1971  // Check tree structure
1972  $isChildOkay = true;
1973  $isParentOkay = true;
1974  $isLftOkay = true;
1975  $isRgtOkay = true;
1976  $isDepthOkay = true;
1977  $isGap = false;
1978 
1979  if (count($stack) > 0) {
1980  $parent = $stack[count($stack) - 1];
1981  if ($parent->depth + 1 != $row->depth) {
1982  $isDepthOkay = false;
1983  $isRowOkay = false;
1984  }
1985  if ($parent->child != $row->parent) {
1986  $isParentOkay = false;
1987  $isRowOkay = false;
1988  }
1989  if ($GLOBALS['ilSetting']->get('main_tree_impl', 'ns') === 'ns') {
1990  if ($parent->lft >= $row->lft) {
1991  $isLftOkay = false;
1992  $isRowOkay = false;
1993  }
1994  if ($parent->rgt <= $row->rgt) {
1995  $isRgtOkay = false;
1996  $isRowOkay = false;
1997  }
1998  }
1999  }
2000 
2001  // Check lft rgt
2002  if ($row->lft >= $row->rgt && $GLOBALS['ilSetting']->get('main_tree_impl', 'ns') === 'ns') {
2003  $isLftOkay = false;
2004  $isRgtOkay = false;
2005  $isRowOkay = false;
2006  }
2007  if (in_array($row->child, $duplicateNodes)) {
2008  $isChildOkay = false;
2009  $isRowOkay = false;
2010  }
2011 
2012  // Check object reference
2013  $isRefRefOkay = true;
2014  $isRefObjOkay = true;
2015  if ($row->ref_id == null) {
2016  $isRefRefOkay = false;
2017  $isRowOkay = false;
2018  }
2019  if ($row->obj_id == null) {
2020  $isRefObjOkay = false;
2021  $isRowOkay = false;
2022  }
2023 
2024  if (!$isRowOkay) {
2025  $error_count++;
2026  }
2027 
2028  // Check for gap between siblings,
2029  // and eventually write a log line
2030  if ($GLOBALS['ilSetting']->get('main_tree_impl', 'ns') === 'ns') {
2031  $gap = $row->lft - $previousNumber - 1;
2032  $previousNumber = $row->lft;
2033  if ($gap > 0) {
2034  $this->writeScanLogLine(
2035  '<tr>'
2036  . '<td colspan=2><div align="right">'
2037  . '<font color=#00cc00>*gap* for ' . ($gap / 2) . ' nodes between&nbsp;</font>'
2038  . '</div></td>'
2039  . '<td>'
2040  . '<font color=#00cc00>siblings</font>'
2041  . '</td>'
2042  . '</tr>'
2043  );
2044  }
2045  }
2046 
2047  // Write log line
2048  // -------------------
2049  $this->writeScanLogLine(
2050  '<tr>'
2051  . '<td>'
2052  . (($isRowOkay) ? '' : '<font color=#ff0000>')
2053  . $row->tree . ', '
2054  . $row->child . ', '
2055  . (($isParentOkay) ? '' : 'parent:<b>')
2056  . $row->parent
2057  . (($isParentOkay) ? '' : '</b>')
2058  . ', '
2059  . (($isLftOkay) ? '' : 'lft:<b>')
2060  . $row->lft
2061  . (($isLftOkay) ? '' : '</b>')
2062  . ', '
2063  . (($isRgtOkay) ? '' : 'rgt:<b>')
2064  . $row->rgt
2065  . (($isRgtOkay) ? '' : '</b>')
2066  . ', '
2067  . (($isDepthOkay) ? '' : 'depth:<b>')
2068  . $row->depth
2069  . (($isDepthOkay) ? '' : '</b>')
2070  . (($isRowOkay) ? '' : '</font>')
2071  . '</td><td>'
2072  . (($isRowOkay) ? '' : '<font color=#ff0000>')
2073  . (($isRefRefOkay && $isChildOkay) ? '' : 'ref.ref_id:<b>')
2074  . $row->ref_id
2075  . (($isRefRefOkay && $isChildOkay) ? '' : '</b>')
2076  . ', '
2077  . (($isRefObjOkay) ? '' : 'ref.obj_id:<b>')
2078  . $row->refobj_id
2079  . (($isRefObjOkay) ? '' : '</b>')
2080  . (($isRowOkay) ? '' : '<font color=#ff0000>')
2081  . (($row->tree < 0) ? ', ' . $row->deleted : '')
2082  . '</td><td>'
2083  . (($isRowOkay) ? '' : '<font color=#ff0000>')
2084  . $indent
2085  . $row->obj_id . ', '
2086  . $row->type . ', '
2087  . ($row->login ?? '') . ', ' . ', '
2088  . $row->title
2089  . (($isRowOkay) ? '' : ' <b>*ERROR*</b><font color=#ff0000>')
2090  . '</td>'
2091  . '</tr>'
2092  );
2093 
2094  // Update stack
2095  // -------------------
2096  // Push node on stack
2097  $stack[] = $row;
2098 
2099  // Count nodes
2100  // -----------------
2101  if ($row->tree == 1) {
2102  $repository_tree_count++;
2103  } elseif ($row->tree < 0) {
2104  $trash_trees_count++;
2105  } else {
2106  $other_trees_count++;
2107  }
2108  }
2109  //
2110  // Pop remaining stack entries
2111 
2112  while (count($stack) > 0) {
2113  $popped = array_pop($stack);
2114 
2115  // check for gap
2116  $gap = $popped->rgt - $previousNumber - 1;
2117  if ($gap > 0) {
2118  $poppedIndent = "";
2119  for ($i = 1; $i < $popped->depth; $i++) {
2120  $poppedIndent .= ". ";
2121  }
2122  $this->writeScanLogLine(
2123  '<tr>'
2124  . '<td colspan=2><div align="right">'
2125  . '<font color=#00cc00>*gap* for ' . ($gap / 2) . ' nodes at end of&nbsp;</font>'
2126  . '</div></td>'
2127  . '<td>'
2128  . '<font color=#00cc00>'
2129  . $poppedIndent
2130  . $popped->obj_id . ', '
2131  . $popped->type . ', '
2132  . ($popped->login ?? '') . ', '
2133  . $popped->title
2134  . '</font>'
2135  . '</td>'
2136  . '</tr>'
2137  );
2138  }
2139  $previousNumber = $popped->rgt;
2140  unset($popped);
2141  }
2142 
2143  //
2144  $this->writeScanLogLine("</table>");
2145 
2146  if ($error_count > 0) {
2147  $this->writeScanLogLine('<font color=#ff0000>' . $error_count . ' errors found while dumping tree.</font>');
2148  } else {
2149  $this->writeScanLogLine('No errors found while dumping tree.');
2150  }
2151  $this->writeScanLogLine("$repository_tree_count nodes in repository tree");
2152  $this->writeScanLogLine("$trash_trees_count nodes in trash trees");
2153  $this->writeScanLogLine("$other_trees_count nodes in other trees");
2154  $this->writeScanLogLine("$not_in_tree_count nodes are not in a tree");
2155  $this->writeScanLogLine("END dumpTree");
2156 
2157  return $error_count;
2158  }
2159 
2160  protected function isMediaFolder(int $a_obj_id): bool
2161  {
2162  $ilDB = $this->db;
2163 
2164  if (!is_array($this->media_pool_ids)) {
2165  $this->media_pool_ids = [];
2166  $query = "SELECT child FROM mep_tree ";
2167  $res = $ilDB->query($query);
2168  while ($row = $ilDB->fetchObject($res)) {
2169  $this->media_pool_ids[] = $row->child;
2170  }
2171  }
2172 
2173  return in_array($a_obj_id, $this->media_pool_ids);
2174  }
2175 
2176  // Check if type is excluded from recovery
2177  protected function isExcludedFromRecovery(
2178  string $a_type,
2179  int $a_obj_id
2180  ): bool {
2181  switch ($a_type) {
2182  case 'fold':
2183  if (!$this->isMediaFolder($a_obj_id)) {
2184  return false;
2185  }
2186  }
2187  return in_array($a_type, $this->object_types_exclude);
2188  }
2189 
2190  protected function initWorkspaceObjects(): void
2191  {
2192  $ilDB = $this->db;
2193 
2194  if ($this->workspace_object_ids === null) {
2195  $this->workspace_object_ids = [];
2196 
2197  // workspace objects
2198  $set = $ilDB->query("SELECT DISTINCT(obj_id) FROM object_reference_ws");
2199  while ($row = $ilDB->fetchAssoc($set)) {
2200  $this->workspace_object_ids[] = $row["obj_id"];
2201  }
2202 
2203  // portfolios
2204  $set = $ilDB->query("SELECT id FROM usr_portfolio");
2205  while ($row = $ilDB->fetchAssoc($set)) {
2206  $this->workspace_object_ids[] = $row["id"];
2207  }
2208  }
2209  }
2210 
2211  protected function filterWorkspaceObjects(
2212  array &$a_data,
2213  string $a_index = "obj_id"
2214  ): void {
2215  if (count($a_data)) {
2216  $this->initWorkspaceObjects();
2217 
2218  // remove workspace objects from result objects
2219  if (is_array($this->workspace_object_ids)) {
2220  foreach ($a_data as $idx => $item) {
2221  if (in_array($item[$a_index], $this->workspace_object_ids)) {
2222  unset($a_data[$idx]);
2223  }
2224  }
2225  }
2226  }
2227  }
2228 }
$res
Definition: ltiservices.php:69
array $rbac_object_types
ilLanguage $lng
findDeletedObjects()
Search database for all tree entries having no valid parent (=> no valid path to root node) and store...
array $invalid_objects
contains correct registrated objects but data are corrupted (experimental)
ilDBInterface $db
exit
Definition: login.php:29
getDeletedObjects()
Gets all object in trash.
getNodeData(int $a_node_id, ?int $a_tree_pk=null)
get all information of a node.
removeInvalidChilds(array $a_invalid_childs=null)
Removes all tree entries without any link to a valid object.
const IL_CAL_DATETIME
findMissingObjects()
Search database for all object entries with missing reference and/or tree entry and stores result in ...
array $object_types_exclude
list of object types to exclude from recovering
txt(string $a_topic, string $a_default_lang_fallback_mod="")
gets the text for a given topic if the topic is not in the list, the topic itself with "-" will be re...
isModeEnabled(string $a_mode)
getInvalidChilds()
Gets all tree entries without any link to a valid object.
const ROOT_FOLDER_ID
Definition: constants.php:32
purgeUnboundObjects(array $a_nodes=null)
Removes all invalid objects from system.
writeScanLogLine(string $a_msg)
array $invalid_rbac_entries
purgeObjects(array $a_nodes)
removes objects from system
__construct(bool $a_log=false)
string $scan_log_separator
ilObjectDefinition $obj_definition
deleteScanLog()
Delete scan log.
deleteTree(array $a_node)
delete node and the whole subtree under this node
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: class.ilLog.php:30
revokePermission(int $a_ref_id, int $a_rol_id=0, bool $a_keep_protected=true)
Revokes permissions of an object of one role.
dumpTree()
Dumps the Tree structure into the scan log.
isExcludedFromRecovery(string $a_type, int $a_obj_id)
handleErr(object $error)
Callback function handles PEAR_error and outputs detailed infos about error TODO: implement that in g...
const IL_CAL_UNIX
writeScanLogArray(array $a_arr)
findInvalidChilds()
Search database for all tree entries without any link to a valid object and stores result in $this->i...
getUnboundObjects()
Gets all tree entries having no valid parent (=> no valid path to root node) Returns an array with ch...
purgeMissingObjects(array $a_nodes=null)
Removes all missing objects from system.
restoreMissingObjects(array $a_missing_objects=null)
Restores missing reference and/or tree entry for all objects found by this::getMissingObjects() Resto...
hasScanLog()
Quickly determine if there is a scan log.
global $DIC
Definition: feed.php:28
parses the objects.xml it handles the xml-description of all ilias objects
const CLIENT_DATA_DIR
Definition: constants.php:46
restoreUnboundObjects(array $a_unbound_objects=null)
Restore objects (and their subobjects) to RecoveryFolder that are valid but not linked correctly in t...
get_last_scan(array $a_scan_log)
$ref_id
Definition: ltiauth.php:67
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.
$GLOBALS["DIC"]
Definition: wac.php:31
restoreSubTrees(array $a_nodes)
Restore objects (and their subobjects) to RecoveryFolder.
getPref(string $a_keyword)
restoreReference(int $a_obj_id)
restore a reference for an object Creates a new reference entry in DB table object_reference for $a_o...
static getInstanceByRefId(int $ref_id, bool $stop_on_error=true)
get an instance of an Ilias object by reference id
renumber(int $node_id=1, int $i=1)
Wrapper for renumber.
string $key
Consumer key/client ID value.
Definition: System.php:193
restoreDeletedObjects(array $a_nodes)
Restore deleted objects (and their subobjects) to RecoveryFolder.
array $invalid_rolefolders
contains missing objects that are rolefolders.
findInvalidReferences()
Search database for all reference entries that are not linked with a valid object id and stores resul...
removeInvalidReferences(array $a_invalid_refs=null)
Removes all reference entries that are linked with invalid object IDs.
findInvalidRolefolders()
Search database for all rolefolder object entries with missing reference entry.
getInvalidRolefolders()
Gets invalid rolefolders (same as missing objects)
array $workspace_object_ids
getPossibleModes()
get possible ilValidator modes
filterWorkspaceObjects(array &$a_data, string $a_index="obj_id")
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...
restoreTrash(array $a_deleted_objects=null)
Restore all objects in trash to RecoveryFolder NOTE: All objects will be restored to top of RecoveryF...
array $invalid_references
findInvalidRBACEntries()
Search database for all role entries that are linked to invalid ref_ids, stores results in $this->inv...
const RECOVERY_FOLDER_ID
Definition: constants.php:37
$q
Definition: shib_logout.php:21
ilRbacAdmin $rbacadmin
setMode(string $a_mode, bool $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
isMediaFolder(int $a_obj_id)
$message
Definition: xapiexit.php:32
Class ilRbacAdmin Core functions for role based access control.
removeInvalidRolefolders(array $a_invalid_rolefolders=null)
Removes invalid rolefolders.
getSubTree(array $a_node, bool $a_with_data=true, array $a_type=[])
get all nodes in the subtree under specified node
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static _removeEntry(int $a_tree, int $a_child, string $a_db_table="tree")
STATIC METHOD Removes a single entry from a tree.
initGapsInTree()
Initializes gaps in lft/rgt values of a tree.
purgeTrash(array $a_nodes=null)
Removes all objects in trash from system.
$r