ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
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 = [];
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 ilInitialisation::initILIAS();
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 {
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 {
680 return $this->invalid_references;
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 {
861 return $this->invalid_rolefolders;
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
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}
const IL_CAL_UNIX
const IL_CAL_DATETIME
@classDescription Date and time handling
language handling
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: class.ilLog.php:31
Component logger with individual log levels by component id.
User class.
parses the objects.xml it handles the xml-description of all ilias objects
static getInstanceByRefId(int $ref_id, bool $stop_on_error=true)
get an instance of an Ilias object by reference id
Class ilRbacAdmin Core functions for role based access control.
Tree class data representation in hierachical trees using the Nested Set Model with Gaps by Joe Celco...
static _removeEntry(int $a_tree, int $a_child, string $a_db_table="tree")
STATIC METHOD Removes a single entry from a tree.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
restoreUnboundObjects(?array $a_unbound_objects=null)
Restore objects (and their subobjects) to RecoveryFolder that are valid but not linked correctly in t...
writeScanLogArray(array $a_arr)
purgeUnboundObjects(?array $a_nodes=null)
Removes all invalid objects from system.
deleteScanLog()
Delete scan log.
findUnboundObjects()
Search database for all tree entries having no valid parent (=> no valid path to root node) and store...
string $scan_log_separator
getInvalidChilds()
Gets all tree entries without any link to a valid object.
getDeletedObjects()
Gets all object in trash.
getInvalidReferences()
Gets all reference entries that are not linked with a valid object id.
get_last_scan(array $a_scan_log)
setMode(string $a_mode, bool $a_value)
set mode of ilValidator Usage: setMode("restore",true) => enable object restorey setMode("all",...
restoreSubTrees(array $a_nodes)
Restore objects (and their subobjects) to RecoveryFolder.
restoreDeletedObjects(array $a_nodes)
Restore deleted objects (and their subobjects) to RecoveryFolder.
array $invalid_rolefolders
contains missing objects that are rolefolders.
array $rbac_object_types
array $invalid_rbac_entries
isMediaFolder(int $a_obj_id)
array $invalid_objects
contains correct registrated objects but data are corrupted (experimental)
array $object_types_exclude
list of object types to exclude from recovering
array $invalid_references
getInvalidRolefolders()
Gets invalid rolefolders (same as missing objects)
filterWorkspaceObjects(array &$a_data, string $a_index="obj_id")
ilObjectDefinition $obj_definition
handleErr(object $error)
Callback function handles PEAR_error and outputs detailed infos about error TODO: implement that in g...
initGapsInTree()
Initializes gaps in lft/rgt values of a tree.
setModeDependencies()
Sets modes by considering mode dependencies; some modes require other modes to be activated.
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.
restoreReference(int $a_obj_id)
restore a reference for an object Creates a new reference entry in DB table object_reference for $a_o...
writeScanLogLine(string $a_msg)
getPossibleModes()
get possible ilValidator modes
findDeletedObjects()
Search database for all tree entries having no valid parent (=> no valid path to root node) and store...
isModeEnabled(string $a_mode)
hasScanLog()
Quickly determine if there is a scan log.
dumpTree()
Dumps the Tree structure into the scan log.
findInvalidChilds()
Search database for all tree entries without any link to a valid object and stores result in $this->i...
findMissingObjects()
Search database for all object entries with missing reference and/or tree entry and stores result in ...
findInvalidRBACEntries()
Search database for all role entries that are linked to invalid ref_ids, stores results in $this->inv...
restoreTrash(?array $a_deleted_objects=null)
Restore all objects in trash to RecoveryFolder NOTE: All objects will be restored to top of RecoveryF...
removeInvalidReferences(?array $a_invalid_refs=null)
Removes all reference entries that are linked with invalid object IDs.
isExcludedFromRecovery(string $a_type, int $a_obj_id)
ilDBInterface $db
__construct(bool $a_log=false)
findInvalidReferences()
Search database for all reference entries that are not linked with a valid object id and stores resul...
restoreMissingObjects(?array $a_missing_objects=null)
Restores missing reference and/or tree entry for all objects found by this::getMissingObjects() Resto...
findInvalidRolefolders()
Search database for all rolefolder object entries with missing reference entry.
validate()
Performs the validation for each enabled mode.
purgeObjects(array $a_nodes)
removes objects from system
removeInvalidRolefolders(?array $a_invalid_rolefolders=null)
Removes invalid rolefolders.
removeInvalidChilds(?array $a_invalid_childs=null)
Removes all tree entries without any link to a valid object.
ilLanguage $lng
array $workspace_object_ids
purgeTrash(?array $a_nodes=null)
Removes all objects in trash from system.
ilRbacAdmin $rbacadmin
getMissingObjects()
Gets all object entries with missing reference and/or tree entry.
const RECOVERY_FOLDER_ID
Definition: constants.php:37
const CLIENT_DATA_DIR
Definition: constants.php:46
const ROOT_FOLDER_ID
Definition: constants.php:32
exit
Interface ilDBInterface.
$ref_id
Definition: ltiauth.php:66
$log
Definition: ltiresult.php:34
$res
Definition: ltiservices.php:69
global $lng
Definition: privfeed.php:31
if(!file_exists('../ilias.ini.php'))
global $DIC
Definition: shib_login.php:26
$q
Definition: shib_logout.php:23
$GLOBALS["DIC"]
Definition: wac.php:54
$message
Definition: xapiexit.php:31