ILIAS  release_8 Revision v8.24
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 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 {
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
1378 // first paste top_node ...
1379 $rbacadmin->revokePermission((int) $key);
1381 $obj_data->putInTree(RECOVERY_FOLDER_ID);
1382 $obj_data->setPermissions(RECOVERY_FOLDER_ID);
1383
1384 $this->writeScanLogLine("Object '" . $obj_data->getId() . "' restored.");
1385
1386 // ... remove top_node from list ...
1387 array_shift($subnode);
1388
1389 // ... insert subtree of top_node if any subnodes exist
1390 if (count($subnode) > 0) {
1391 foreach ($subnode as $node) {
1392 $rbacadmin->revokePermission((int) $node["child"]);
1393 $obj_data = ilObjectFactory::getInstanceByRefId($node["child"]);
1394 $obj_data->putInTree($node["parent"]);
1395 $obj_data->setPermissions($node["parent"]);
1396
1397 $this->writeScanLogLine("Object '" . $obj_data->getId() . "' restored.");
1398 }
1399 }
1400 }
1401
1402 // final clean up
1403 $this->findInvalidChilds();
1404 $this->removeInvalidChilds();
1405
1406 return true;
1407 }
1408
1414 public function purgeTrash(
1415 array $a_nodes = null
1416 ): bool {
1417 $ilLog = $this->log;
1418
1419 // check mode: purge_trash
1420 if ($this->mode["purge_trash"] !== true) {
1421 return false;
1422 }
1423
1424 $this->writeScanLogLine("\npurgeTrash:");
1425
1426 if ($a_nodes === null && isset($this->deleted_objects)) {
1427 $a_nodes = $this->deleted_objects;
1428 }
1429 $message = sprintf(
1430 '%s::purgeTrash(): Started...',
1431 get_class($this)
1432 );
1433 $ilLog->write($message, $ilLog->WARNING);
1434
1435 // start purge process
1436 return $this->purgeObjects($a_nodes);
1437 }
1438
1444 public function purgeUnboundObjects(
1445 array $a_nodes = null
1446 ): bool {
1447 $ilLog = $this->log;
1448
1449 // check mode: purge
1450 if ($this->mode["purge"] !== true) {
1451 return false;
1452 }
1453
1454 $this->writeScanLogLine("\npurgeUnboundObjects:");
1455
1456 if ($a_nodes === null && isset($this->unbound_objects)) {
1457 $a_nodes = $this->unbound_objects;
1458 }
1459
1460 $message = sprintf(
1461 '%s::purgeUnboundObjects(): Started...',
1462 get_class($this)
1463 );
1464 $ilLog->write($message, $ilLog->WARNING);
1465
1466 // start purge process
1467 return $this->purgeObjects($a_nodes);
1468 }
1469
1475 public function purgeMissingObjects(
1476 array $a_nodes = null
1477 ): bool {
1478 $ilLog = $this->log;
1479
1480 // check mode: purge
1481 if ($this->mode["purge"] !== true) {
1482 return false;
1483 }
1484
1485 $this->writeScanLogLine("\npurgeMissingObjects:");
1486
1487 if ($a_nodes === null && isset($this->missing_objects)) {
1488 $a_nodes = $this->missing_objects;
1489 }
1490
1491 $message = sprintf(
1492 '%s::purgeMissingObjects(): Started...',
1493 get_class($this)
1494 );
1495 $ilLog->write($message, $ilLog->WARNING);
1496
1497 // start purge process
1498 return $this->purgeObjects($a_nodes);
1499 }
1500
1506 public function purgeObjects(
1507 array $a_nodes
1508 ): bool {
1509 $ilLog = $this->log;
1510 $ilUser = $this->user;
1511
1512 // Get purge limits
1513 $count_limit = $ilUser->getPref("systemcheck_count_limit");
1514 if (!is_numeric($count_limit) || $count_limit < 0) {
1515 $count_limit = count($a_nodes);
1516 }
1517 $timestamp_limit = time();
1518 $age_limit = $ilUser->getPref("systemcheck_age_limit");
1519 if (is_numeric($age_limit) && $age_limit > 0) {
1520 $timestamp_limit -= $age_limit * 60 * 60 * 24;
1521 }
1522 $type_limit = $ilUser->getPref("systemcheck_type_limit");
1523 if ($type_limit) {
1524 $type_limit = trim($type_limit);
1525 if ($type_limit === '') {
1526 $type_limit = null;
1527 }
1528 }
1529
1530 // handle wrong input
1531 if (!is_array($a_nodes)) {
1532 $this->throwError(INVALID_PARAM, WARNING, DEBUG);
1533 return false;
1534 }
1535
1536 // start delete process
1537 $this->writeScanLogLine("action\tref_id\tobj_id\ttype\telapsed\ttitle");
1538 $count = 0;
1539 foreach ($a_nodes as $node) {
1540 if ($type_limit && $node['type'] != $type_limit) {
1541 $this->writeScanLogLine(
1542 "skip\t" .
1543 $node['child'] . "\t\t" . $node['type'] . "\t\t" . $node['title']
1544 );
1545 continue;
1546 }
1547
1548
1549 $count++;
1550 if ($count > $count_limit) {
1551 $this->writeScanLogLine("Stopped purging after " . ($count - 1) . " objects, because count limit was reached: " . $count_limit);
1552 break;
1553 }
1554 if ($node["deleted_timestamp"] > $timestamp_limit) {
1555 $this->writeScanLogLine("Stopped purging after " . ($count - 1) . " objects, because timestamp limit was reached: " . date("c", $timestamp_limit));
1556 continue;
1557 }
1558
1559 $ref_id = ($node["child"]) ?: $node["ref_id"];
1560 $node_obj = ilObjectFactory::getInstanceByRefId($ref_id, false);
1561
1562 if ($node_obj === false) {
1563 $this->invalid_objects[] = $node;
1564 continue;
1565 }
1566
1567 $message = sprintf(
1568 '%s::purgeObjects(): Removing object (id:%s ref:%s)',
1569 get_class($this),
1570 $ref_id,
1571 $node_obj->getId()
1572 );
1573 $ilLog->write($message, $ilLog->WARNING);
1574
1575 $startTime = microtime(true);
1576 $node_obj->delete();
1577 ilTree::_removeEntry($node["tree"], $ref_id);
1578 $endTime = microtime(true);
1579
1580 $this->writeScanLogLine("purged\t" . $ref_id . "\t" . $node_obj->getId() .
1581 "\t" . $node['type'] . "\t" . round($endTime - $startTime, 1) . "\t" . $node['title']);
1582 }
1583
1584 $this->findInvalidChilds();
1585 $this->removeInvalidChilds();
1586
1587 return true;
1588 }
1589
1600 public function initGapsInTree(): bool
1601 {
1602 $tree = $this->tree;
1603 $ilLog = $this->log;
1604
1605 $message = sprintf(
1606 '%s::initGapsInTree(): Started...',
1607 get_class($this)
1608 );
1609 $ilLog->write($message, $ilLog->WARNING);
1610
1611 // check mode: clean
1612 if ($this->mode["clean"] !== true) {
1613 return false;
1614 }
1615 $this->writeScanLogLine("\nrenumberTree:");
1616
1617 $tree->renumber(ROOT_FOLDER_ID);
1618
1619 $this->writeScanLogLine("done");
1620
1621 return true;
1622 }
1623
1630 public function handleErr(
1631 object $error
1632 ): void {
1633 $call_loc = $error->backtrace[count($error->backtrace) - 1];
1634 $num_args = count($call_loc["args"]);
1635 $arg_list = [];
1636 $arg_str = "";
1637 if ($num_args > 0) {
1638 foreach ($call_loc["args"] as $arg) {
1639 $type = gettype($arg);
1640
1641 switch ($type) {
1642 case "string":
1643 $value = strlen($arg);
1644 break;
1645
1646 case "array":
1647 $value = count($arg);
1648 break;
1649
1650 case "object":
1651 $value = get_class($arg);
1652 break;
1653
1654 case "boolean":
1655 $value = ($arg) ? "true" : "false";
1656 break;
1657
1658 default:
1659 $value = $arg;
1660 break;
1661 }
1662
1663 $arg_list[] = [
1664 "type" => $type,
1665 "value" => "(" . $value . ")"
1666 ];
1667 }
1668
1669 foreach ($arg_list as $arg) {
1670 $arg_str .= implode("", $arg) . " ";
1671 }
1672 }
1673
1674 $err_msg = "<br/><b>" . $error->getCode() . ":</b> " . $error->getMessage() . " in " . $call_loc["class"] . $call_loc["type"] . $call_loc["function"] . "()" .
1675 "<br/>Called from: " . basename($call_loc["file"]) . " , line " . $call_loc["line"] .
1676 "<br/>Passed parameters: [" . $num_args . "] " . $arg_str . "<br/>";
1677 printf($err_msg);
1678
1679 /*
1680 if ($error->getUserInfo()) {
1681 printf("<br/>Parameter details:");
1682 echo "<pre>";
1683 var_dump($call_loc["args"]);// TODO PHP8-REVIEW This should be removed and the ilLogger should be used instead
1684 echo "</pre>";
1685 }*/
1686
1687 if ($error->getCode() == FATAL) {
1688 exit();
1689 }
1690 }
1691
1692 public function writeScanLogArray(array $a_arr): void
1693 {
1694 if (!$this->isLogEnabled()) {
1695 return;
1696 }
1697
1698 foreach ($a_arr as $entry) {
1699 $this->scan_log->write(implode("\t", $entry));
1700 }
1701 }
1702
1703 public function writeScanLogLine(string $a_msg): void
1704 {
1705 if (!$this->isLogEnabled()) {
1706 return;
1707 }
1708
1709 $this->scan_log->write($a_msg);
1710 }
1711
1715 public function hasScanLog(): bool
1716 {
1717 // file check
1718 return is_file(CLIENT_DATA_DIR . "/" . $this->scan_log_file);
1719 }
1720
1724 public function deleteScanLog(): void
1725 {
1726 unlink(CLIENT_DATA_DIR . "/" . $this->scan_log_file);
1727 }
1728
1729 public function readScanLog(): ?array
1730 {
1731 // file check
1732 if (!$this->hasScanLog()) {
1733 return null;
1734 }
1735
1736 $scanfile = file(CLIENT_DATA_DIR . "/" . $this->scan_log_file);
1737 if (!$scan_log = $this->get_last_scan($scanfile)) {
1738 return null;
1739 }
1740 // Ensure that memory is freed
1741 unset($scanfile);
1742
1743 return $scan_log;
1744 }
1745
1746 public function get_last_scan(array $a_scan_log): ?array
1747 {
1748 $logs = array_keys($a_scan_log, $this->scan_log_separator . "\n");
1749
1750 if (count($logs) > 0) {
1751 return array_slice($a_scan_log, array_pop($logs) + 2);
1752 }
1753
1754 return null;
1755 }
1756
1757 public function checkTreeStructure(): bool
1758 {
1759 $this->writeScanLogLine("\nchecking tree structure is disabled");
1760
1761 return false;
1762 }
1763
1768 public function dumpTree(): int
1769 {
1770 $this->writeScanLogLine("BEGIN dumpTree:");
1771
1772 // collect nodes with duplicate child Id's
1773 // (We use this, to mark these nodes later in the output as being
1774 // erroneous.).
1775 $q = 'SELECT child FROM tree GROUP BY child HAVING COUNT(*) > 1';
1776 $r = $this->db->query($q);
1777 $duplicateNodes = [];
1778 while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
1779 $duplicateNodes[] = $row->child;
1780 }
1781
1782 // dump tree
1783 $q = "SELECT tree.*,ref.ref_id,dat.obj_id objobj_id,ref.obj_id refobj_id,ref.deleted,dat.* "
1784 . "FROM tree "
1785 . "RIGHT JOIN object_reference ref ON tree.child = ref.ref_id "
1786 . "RIGHT JOIN object_data dat ON ref.obj_id = dat.obj_id "
1787// ."LEFT JOIN usr_data usr ON usr.usr_id = dat.owner "
1788 . "ORDER BY tree, lft, type, dat.title";
1789 $r = $this->db->query($q);
1790
1791 $this->writeScanLogLine(
1792 '<table><tr>'
1793 . '<td>tree, child, parent, lft, rgt, depth</td>'
1794 . '<td>ref_id, ref.obj_id, deleted</td>'
1795 . '<td>obj_id, type, owner, title</td>'
1796 . '</tr>'
1797 );
1798
1799 // We use a stack to represent the path to the current node.
1800 // This allows us to do analyze the tree structure without having
1801 // to implement a recursive algorithm.
1802 $stack = [];
1803 $error_count = 0;
1804 $repository_tree_count = 0;
1805 $trash_trees_count = 0;
1806 $other_trees_count = 0;
1807 $not_in_tree_count = 0;
1808
1809 // The previous number is used for gap checking
1810 $previousNumber = 0;
1811
1812 $this->initWorkspaceObjects();
1813
1814 while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
1815 // workspace objects are not to be processed
1816 if ($this->workspace_object_ids &&
1817 in_array($row->objobj_id, $this->workspace_object_ids)) {
1818 continue;
1819 }
1820
1821 // If there is no entry in table tree for the object, we display it here
1822 if (is_null($row->child)) {
1823 switch ($row->type) {
1824 case 'crsg':
1825 case 'usr':
1826 case 'typ':
1827 case 'lng':
1828 case 'rolt':
1829 case 'role':
1830 case 'mob':
1831 case 'sty':
1832 case 'tax': // #13798
1833 // We are not interested in dumping these object types.
1834 continue 2;
1835 //break; NOT REACHED
1836 case 'file':
1837 if (is_null($row->ref_id)) {
1838 // File objects can be part of a learning module.
1839 // In this case, they do not have a row in table object_reference.
1840 // We are not interested in dumping these file objects.
1841 continue 2;
1842 }
1843
1844 // File objects which have a row in table object_reference, but
1845 // none in table tree are an error.
1846 $error_count++;
1847 $isRowOkay = false;
1848 $isParentOkay = false;
1849 $isLftOkay = false;
1850 $isRgtOkay = false;
1851 $isDepthOkay = false;
1852 break;
1853
1854 default:
1855 // ignore folders on media pools
1856 if ($row->type === "fold" && $this->isMediaFolder($row->obj_id)) {
1857 continue 2;
1858 }
1859 $error_count++;
1860 $isRowOkay = false;
1861 $isParentOkay = false;
1862 $isLftOkay = false;
1863 $isRgtOkay = false;
1864 $isDepthOkay = false;
1865 break;
1866 }
1867
1868 // moved here (below continues in switch)
1869 $not_in_tree_count++;
1870
1871 $this->writeScanLogLine(
1872 '<tr>'
1873 . '<td>'
1874 . (($isRowOkay) ? '' : '<font color=#ff0000>')
1875 . $row->tree . ', '
1876 . $row->child . ', '
1877 . (($isParentOkay) ? '' : 'parent:<b>')
1878 . $row->parent
1879 . (($isParentOkay) ? '' : '</b>')
1880 . ', '
1881 . (($isLftOkay) ? '' : 'lft:<b>')
1882 . $row->lft
1883 . (($isLftOkay) ? '' : '</b>')
1884 . ', '
1885 . (($isRgtOkay) ? '' : 'rgt:<b>')
1886 . $row->rgt
1887 . (($isRgtOkay) ? '' : '</b>')
1888 . ', '
1889 . (($isDepthOkay) ? '' : 'depth:<b>')
1890 . $row->depth
1891 . (($isDepthOkay) ? '' : '</b>')
1892 . (($isRowOkay) ? '' : '</font>')
1893 . '</td><td>'
1894 . (($isRowOkay) ? '' : '<font color=#ff0000>')
1895 . (($isRefRefOkay && $isChildOkay) ? '' : 'ref.ref_id:<b>')
1896 . $row->ref_id
1897 . (($isRefRefOkay && $isChildOkay) ? '' : '</b>')
1898 . ', '
1899 . (($isRefObjOkay) ? '' : 'ref.obj_id:<b>')
1900 . $row->refobj_id
1901 . (($isRefObjOkay) ? '' : '</b>')
1902 . (($isRowOkay) ? '' : '<font color=#ff0000>')
1903 . (($row->deleted != null) ? ', ' . $row->deleted : '')
1904 . '</td><td>'
1905 . (($isRowOkay) ? '' : '<font color=#ff0000>')
1906 . $indent
1907 . $row->obj_id . ', '
1908 . $row->type . ', '
1909 . $row->login . ', '
1910 . $row->title
1911 . (($isRowOkay) ? '' : ' <b>*ERROR*</b><font color=#ff0000>')
1912 . '</td>'
1913 . '</tr>'
1914 );
1915 continue;
1916 }
1917
1918 // Update stack
1919 // -------------------
1920 $indent = "";
1921 for ($i = 1; $i < $row->depth; $i++) {
1922 $indent .= ". ";
1923 }
1924
1925 // Initialize the stack and the previous number if we are in a new tree
1926 if (count($stack) === 0 || $stack[0]->tree != $row->tree) {
1927 $stack = [];
1928 $previousNumber = $row->lft - 1;
1929 $this->writeScanLogLine('<tr><td>&nbsp;</td></tr>');
1930 }
1931 // Pop old stack entries
1932
1933
1934 while (count($stack) > 0 && $stack[count($stack) - 1]->rgt < $row->lft) {
1935 $popped = array_pop($stack);
1936
1937 // check for gap
1938 $gap = $popped->rgt - $previousNumber - 1;
1939 if ($gap > 0) {
1940 $poppedIndent = "";
1941 for ($i = 1; $i < $popped->depth; $i++) {
1942 $poppedIndent .= ". ";
1943 }
1944 $this->writeScanLogLine(
1945 '<tr>'
1946 . '<td colspan=2><div align="right">'
1947 . '<font color=#00cc00>*gap* for ' . ($gap / 2) . ' nodes at end of&nbsp;</font>'
1948 . '</div></td>'
1949 . '<td>'
1950 . '<font color=#00cc00>'
1951 . $poppedIndent
1952 . $popped->obj_id . ', '
1953 . $popped->type . ', '
1954 . $popped->login . ', '
1955 . $popped->title
1956 . '</font>'
1957 . '</td>'
1958 . '</tr>'
1959 );
1960 }
1961 $previousNumber = $popped->rgt;
1962 unset($popped);
1963 }
1964
1965 // Check row integrity
1966 // -------------------
1967 $isRowOkay = true;
1968
1969 // Check tree structure
1970 $isChildOkay = true;
1971 $isParentOkay = true;
1972 $isLftOkay = true;
1973 $isRgtOkay = true;
1974 $isDepthOkay = true;
1975 $isGap = false;
1976
1977 if (count($stack) > 0) {
1978 $parent = $stack[count($stack) - 1];
1979 if ($parent->depth + 1 != $row->depth) {
1980 $isDepthOkay = false;
1981 $isRowOkay = false;
1982 }
1983 if ($parent->child != $row->parent) {
1984 $isParentOkay = false;
1985 $isRowOkay = false;
1986 }
1987 if ($GLOBALS['ilSetting']->get('main_tree_impl', 'ns') === 'ns') {
1988 if ($parent->lft >= $row->lft) {
1989 $isLftOkay = false;
1990 $isRowOkay = false;
1991 }
1992 if ($parent->rgt <= $row->rgt) {
1993 $isRgtOkay = false;
1994 $isRowOkay = false;
1995 }
1996 }
1997 }
1998
1999 // Check lft rgt
2000 if ($row->lft >= $row->rgt && $GLOBALS['ilSetting']->get('main_tree_impl', 'ns') === 'ns') {
2001 $isLftOkay = false;
2002 $isRgtOkay = false;
2003 $isRowOkay = false;
2004 }
2005 if (in_array($row->child, $duplicateNodes)) {
2006 $isChildOkay = false;
2007 $isRowOkay = false;
2008 }
2009
2010 // Check object reference
2011 $isRefRefOkay = true;
2012 $isRefObjOkay = true;
2013 if ($row->ref_id == null) {
2014 $isRefRefOkay = false;
2015 $isRowOkay = false;
2016 }
2017 if ($row->obj_id == null) {
2018 $isRefObjOkay = false;
2019 $isRowOkay = false;
2020 }
2021
2022 if (!$isRowOkay) {
2023 $error_count++;
2024 }
2025
2026 // Check for gap between siblings,
2027 // and eventually write a log line
2028 if ($GLOBALS['ilSetting']->get('main_tree_impl', 'ns') === 'ns') {
2029 $gap = $row->lft - $previousNumber - 1;
2030 $previousNumber = $row->lft;
2031 if ($gap > 0) {
2032 $this->writeScanLogLine(
2033 '<tr>'
2034 . '<td colspan=2><div align="right">'
2035 . '<font color=#00cc00>*gap* for ' . ($gap / 2) . ' nodes between&nbsp;</font>'
2036 . '</div></td>'
2037 . '<td>'
2038 . '<font color=#00cc00>siblings</font>'
2039 . '</td>'
2040 . '</tr>'
2041 );
2042 }
2043 }
2044
2045 // Write log line
2046 // -------------------
2047 $this->writeScanLogLine(
2048 '<tr>'
2049 . '<td>'
2050 . (($isRowOkay) ? '' : '<font color=#ff0000>')
2051 . $row->tree . ', '
2052 . $row->child . ', '
2053 . (($isParentOkay) ? '' : 'parent:<b>')
2054 . $row->parent
2055 . (($isParentOkay) ? '' : '</b>')
2056 . ', '
2057 . (($isLftOkay) ? '' : 'lft:<b>')
2058 . $row->lft
2059 . (($isLftOkay) ? '' : '</b>')
2060 . ', '
2061 . (($isRgtOkay) ? '' : 'rgt:<b>')
2062 . $row->rgt
2063 . (($isRgtOkay) ? '' : '</b>')
2064 . ', '
2065 . (($isDepthOkay) ? '' : 'depth:<b>')
2066 . $row->depth
2067 . (($isDepthOkay) ? '' : '</b>')
2068 . (($isRowOkay) ? '' : '</font>')
2069 . '</td><td>'
2070 . (($isRowOkay) ? '' : '<font color=#ff0000>')
2071 . (($isRefRefOkay && $isChildOkay) ? '' : 'ref.ref_id:<b>')
2072 . $row->ref_id
2073 . (($isRefRefOkay && $isChildOkay) ? '' : '</b>')
2074 . ', '
2075 . (($isRefObjOkay) ? '' : 'ref.obj_id:<b>')
2076 . $row->refobj_id
2077 . (($isRefObjOkay) ? '' : '</b>')
2078 . (($isRowOkay) ? '' : '<font color=#ff0000>')
2079 . (($row->tree < 0) ? ', ' . $row->deleted : '')
2080 . '</td><td>'
2081 . (($isRowOkay) ? '' : '<font color=#ff0000>')
2082 . $indent
2083 . $row->obj_id . ', '
2084 . $row->type . ', '
2085 . $row->login . ', '
2086 . $row->title
2087 . (($isRowOkay) ? '' : ' <b>*ERROR*</b><font color=#ff0000>')
2088 . '</td>'
2089 . '</tr>'
2090 );
2091
2092 // Update stack
2093 // -------------------
2094 // Push node on stack
2095 $stack[] = $row;
2096
2097 // Count nodes
2098 // -----------------
2099 if ($row->tree == 1) {
2100 $repository_tree_count++;
2101 } elseif ($row->tree < 0) {
2102 $trash_trees_count++;
2103 } else {
2104 $other_trees_count++;
2105 }
2106 }
2107 //
2108 // Pop remaining stack entries
2109
2110 while (count($stack) > 0) {
2111 $popped = array_pop($stack);
2112
2113 // check for gap
2114 $gap = $popped->rgt - $previousNumber - 1;
2115 if ($gap > 0) {
2116 $poppedIndent = "";
2117 for ($i = 1; $i < $popped->depth; $i++) {
2118 $poppedIndent .= ". ";
2119 }
2120 $this->writeScanLogLine(
2121 '<tr>'
2122 . '<td colspan=2><div align="right">'
2123 . '<font color=#00cc00>*gap* for ' . ($gap / 2) . ' nodes at end of&nbsp;</font>'
2124 . '</div></td>'
2125 . '<td>'
2126 . '<font color=#00cc00>'
2127 . $poppedIndent
2128 . $popped->obj_id . ', '
2129 . $popped->type . ', '
2130 . $popped->login . ', '
2131 . $popped->title
2132 . '</font>'
2133 . '</td>'
2134 . '</tr>'
2135 );
2136 }
2137 $previousNumber = $popped->rgt;
2138 unset($popped);
2139 }
2140
2141 //
2142 $this->writeScanLogLine("</table>");
2143
2144 if ($error_count > 0) {
2145 $this->writeScanLogLine('<font color=#ff0000>' . $error_count . ' errors found while dumping tree.</font>');
2146 } else {
2147 $this->writeScanLogLine('No errors found while dumping tree.');
2148 }
2149 $this->writeScanLogLine("$repository_tree_count nodes in repository tree");
2150 $this->writeScanLogLine("$trash_trees_count nodes in trash trees");
2151 $this->writeScanLogLine("$other_trees_count nodes in other trees");
2152 $this->writeScanLogLine("$not_in_tree_count nodes are not in a tree");
2153 $this->writeScanLogLine("END dumpTree");
2154
2155 return $error_count;
2156 }
2157
2158 protected function isMediaFolder(int $a_obj_id): bool
2159 {
2160 $ilDB = $this->db;
2161
2162 if (!is_array($this->media_pool_ids)) {
2163 $this->media_pool_ids = [];
2164 $query = "SELECT child FROM mep_tree ";
2165 $res = $ilDB->query($query);
2166 while ($row = $ilDB->fetchObject($res)) {
2167 $this->media_pool_ids[] = $row->child;
2168 }
2169 }
2170
2171 return in_array($a_obj_id, $this->media_pool_ids);
2172 }
2173
2174 // Check if type is excluded from recovery
2175 protected function isExcludedFromRecovery(
2176 string $a_type,
2177 int $a_obj_id
2178 ): bool {
2179 switch ($a_type) {
2180 case 'fold':
2181 if (!$this->isMediaFolder($a_obj_id)) {
2182 return false;
2183 }
2184 }
2185 return in_array($a_type, $this->object_types_exclude);
2186 }
2187
2188 protected function initWorkspaceObjects(): void
2189 {
2190 $ilDB = $this->db;
2191
2192 if ($this->workspace_object_ids === null) {
2193 $this->workspace_object_ids = [];
2194
2195 // workspace objects
2196 $set = $ilDB->query("SELECT DISTINCT(obj_id) FROM object_reference_ws");
2197 while ($row = $ilDB->fetchAssoc($set)) {
2198 $this->workspace_object_ids[] = $row["obj_id"];
2199 }
2200
2201 // portfolios
2202 $set = $ilDB->query("SELECT id FROM usr_portfolio");
2203 while ($row = $ilDB->fetchAssoc($set)) {
2204 $this->workspace_object_ids[] = $row["id"];
2205 }
2206 }
2207 }
2208
2209 protected function filterWorkspaceObjects(
2210 array &$a_data,
2211 string $a_index = "obj_id"
2212 ): void {
2213 if (count($a_data)) {
2214 $this->initWorkspaceObjects();
2215
2216 // remove workspace objects from result objects
2217 if (is_array($this->workspace_object_ids)) {
2218 foreach ($a_data as $idx => $item) {
2219 if (in_array($item[$a_index], $this->workspace_object_ids)) {
2220 unset($a_data[$idx]);
2221 }
2222 }
2223 }
2224 }
2225 }
2226}
if(!defined('PATH_SEPARATOR')) $GLOBALS['_PEAR_default_error_mode']
Definition: PEAR.php:64
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.
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.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
writeScanLogArray(array $a_arr)
deleteScanLog()
Delete scan log.
findUnboundObjects()
Search database for all tree entries having no valid parent (=> no valid path to root node) and store...
purgeTrash(array $a_nodes=null)
Removes all objects in trash from system.
string $scan_log_separator
purgeUnboundObjects(array $a_nodes=null)
Removes all invalid objects from system.
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.
restoreMissingObjects(array $a_missing_objects=null)
Restores missing reference and/or tree entry for all objects found by this::getMissingObjects() Resto...
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.
removeInvalidRolefolders(array $a_invalid_rolefolders=null)
Removes invalid rolefolders.
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.
restoreUnboundObjects(array $a_unbound_objects=null)
Restore objects (and their subobjects) to RecoveryFolder that are valid but not linked correctly in t...
removeInvalidReferences(array $a_invalid_refs=null)
Removes all reference entries that are linked with invalid object IDs.
restoreTrash(array $a_deleted_objects=null)
Restore all objects in trash to RecoveryFolder NOTE: All objects will be restored to top of RecoveryF...
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...
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...
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
ilLanguage $lng
array $workspace_object_ids
ilRbacAdmin $rbacadmin
getMissingObjects()
Gets all object entries with missing reference and/or tree entry.
removeInvalidChilds(array $a_invalid_childs=null)
Removes all tree entries without any link to a valid object.
if(!file_exists(getcwd() . '/ilias.ini.php'))
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: confirmReg.php:20
const RECOVERY_FOLDER_ID
Definition: constants.php:37
const CLIENT_DATA_DIR
Definition: constants.php:46
const ROOT_FOLDER_ID
Definition: constants.php:32
global $DIC
Definition: feed.php:28
$ilUser
Definition: imgupload.php:34
Interface ilDBInterface.
exit
Definition: login.php:28
$ref_id
Definition: ltiauth.php:67
$res
Definition: ltiservices.php:69
$i
Definition: metadata.php:41
string $key
Consumer key/client ID value.
Definition: System.php:193
$query
$type
$log
Definition: result.php:33
$lng
$message
Definition: xapiexit.php:32