ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilTestArchiver.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21use ILIAS\Test\Results\Presentation\TitlesBuilder as ResultsTitleBuilder;
24use ILIAS\UI\Factory as UIFactory;
25use ILIAS\UI\Renderer as UIRenderer;
30use ILIAS\Test\Results\Data\Repository as TestResultRepository;
32use Psr\Http\Message\ServerRequestInterface;
33
38{
39 public const DIR_SEP = DIRECTORY_SEPARATOR;
40
41 public const EXPORT_DIRECTORY = 'archive_exports';
42
43 private const PASS_MATERIALS_PATH_COMPONENT = 'materials';
44 private const QUESTION_PATH_COMPONENT_PREFIX = 'q_';
45
46 private const TEST_BEST_SOLUTION_PATH_COMPONENT = 'best_solution';
47 private const HTML_BEST_SOLUTION_FILENAME = 'best_solution.html';
48 private const TEST_MATERIALS_PATH_COMPONENT = 'materials';
49
50 private const TEST_RESULT_FILENAME = 'test_result.html';
51
52 private const LOG_DTSGROUP_FORMAT = 'D M j G:i:s T Y';
53 private const LOG_ADDITION_STRING = ' Adding ';
54
55 private const TEST_LOG_FILENAME = 'test_log.xlsx';
56 private const DATA_INDEX_FILENAME = 'data_index.csv';
57 private const ARCHIVE_LOG = 'archive.log';
58
60 private string $client_id = CLIENT_ID;
62
64
66
67 protected TestResultRepository $test_result_repository;
68
69 public function __construct(
70 private readonly ilLanguage $lng,
71 private readonly ilDBInterface $db,
72 private readonly ilObjUser $user,
73 private readonly UIFactory $ui_factory,
74 private readonly UIRenderer $ui_renderer,
75 private readonly IRSS $irss,
76 private readonly ServerRequestInterface $request,
77 private readonly ilObjectDataCache $obj_cache,
78 private readonly ilTestParticipantAccessFilterFactory $participant_access_filter_factory,
79 private readonly TestLogViewer $log_viewer,
80 private readonly int $test_obj_id,
81 private ?int $test_ref_id = null
82 ) {
84 global $DIC;
85 $ilias = $DIC['ilias'];
86 $local_dic = TestDIC::dic();
87
88 $this->html_generator = new ilTestHTMLGenerator();
89 $this->external_directory_path = $ilias->ini_ilias->readVariable('clients', 'datadir');
90 $this->archive_data_index = $this->readArchiveDataIndex();
91 $this->test_result_repository = $local_dic['results.data.repository'];
92 }
93
95 {
97 }
98
100 {
101 $this->participant_data = $participant_data;
102 }
103
105 int $active_fi,
106 int $pass,
107 int $question_fi,
108 string $original_filename,
109 string $file_path
110 ): void {
112 $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass);
113
114 $pass_question_directory = $this->getPassDataDirectory($active_fi, $pass)
115 . DIRECTORY_SEPARATOR . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi;
116 if (!is_dir($pass_question_directory)) {
117 mkdir($pass_question_directory, 0777, true);
118 }
119
120 copy($file_path, $pass_question_directory . DIRECTORY_SEPARATOR . $original_filename);
121
122 $this->logArchivingProcess(
123 date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
124 . $pass_question_directory . DIRECTORY_SEPARATOR . $original_filename
125 );
126 }
127
128 public function handInParticipantMisc(
129 int $active_fi,
130 int $pass,
131 string $original_filename,
132 string $file_path
133 ): void {
134 $this->ensureTestArchiveIsAvailable();
135 $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass);
136 $new_path = $this->getPassDataDirectory($active_fi, $pass) . DIRECTORY_SEPARATOR . $original_filename;
137 copy($file_path, $new_path);
138 $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_path);
139 }
140
141 public function handInTestBestSolution(string $best_solution): void
142 {
143 $this->ensureTestArchiveIsAvailable();
144
145 $best_solution_path = $this->getTestArchive() . DIRECTORY_SEPARATOR . self::TEST_BEST_SOLUTION_PATH_COMPONENT;
146 if (!is_dir($best_solution_path)) {
147 mkdir($best_solution_path, 0777, true);
148 }
149
150 $this->html_generator->generateHTML(
151 $best_solution,
152 $best_solution_path . DIRECTORY_SEPARATOR . self::HTML_BEST_SOLUTION_FILENAME
153 );
154
155 $this->logArchivingProcess(
156 date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
157 . $best_solution_path . DIRECTORY_SEPARATOR . self::HTML_BEST_SOLUTION_FILENAME
158 );
159
160 $this->logArchivingProcess(
161 date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $best_solution_path
162 );
163 }
164
166 int $active_fi,
167 int $pass,
168 ilObjTest $tst_obj
169 ): void {
170 $questions = $tst_obj->getQuestionsOfPass($active_fi, $pass);
171 foreach ($questions as $question) {
172 $question = $tst_obj->getQuestionDataset($question['question_fi']);
173 if ($question->type_tag === 'assFileUpload') {
174 $this->ensureTestArchiveIsAvailable();
175 $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass);
176 $this->ensurePassMaterialsDirectoryIsAvailable($active_fi, $pass);
177 $pass_material_directory = $this->getPassMaterialsDirectory($active_fi, $pass);
178 $archive_folder = $pass_material_directory . DIRECTORY_SEPARATOR . $question->question_id . DIRECTORY_SEPARATOR;
179 if (!file_exists($archive_folder)) {
180 mkdir($archive_folder, 0777, true);
181 }
182 $resource_id = $tst_obj->getTextAnswer($active_fi, $question->question_id, $pass);
183 if ($resource_id === '') {
184 continue;
185 }
186 $irss_unique_id = $this->irss->manage()->find($resource_id);
187 if ($irss_unique_id != null) {
188 $resource = $this->irss->manage()->getResource($irss_unique_id);
189 $information = $resource->getCurrentRevision()->getInformation();
190 $stream = $this->irss->consume()->stream($irss_unique_id);
191 // this feels unnecessary..
192 $file_stream = fopen($stream->getStream()->getMetadata('uri'), 'r');
193 $file_content = stream_get_contents($file_stream);
194 fclose($file_stream);
195 $target_destination = $archive_folder . $information->getTitle();
196 file_put_contents($target_destination, $file_content);
197 }
198 }
199 }
200 }
201
203 int $question_fi,
204 string $orginial_filename,
205 string $file_path
206 ): void {
207 $this->ensureTestArchiveIsAvailable();
208
209 $best_solution_path = $this->getTestArchive() . DIRECTORY_SEPARATOR . self::TEST_BEST_SOLUTION_PATH_COMPONENT;
210 if (!is_dir($best_solution_path)) {
211 mkdir($best_solution_path, 0777, true);
212 }
213
214 $materials_path = $best_solution_path . DIRECTORY_SEPARATOR . self::TEST_MATERIALS_PATH_COMPONENT;
215 if (!is_dir($materials_path)) {
216 mkdir($materials_path, 0777, true);
217 }
218
219 $question_materials_path = $materials_path . DIRECTORY_SEPARATOR . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi;
220 if (!is_dir($question_materials_path)) {
221 mkdir($question_materials_path, 0777, true);
222 }
223
224 copy($file_path, $question_materials_path . DIRECTORY_SEPARATOR . $orginial_filename);
225
226 $this->logArchivingProcess(
227 date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
228 . $question_materials_path . DIRECTORY_SEPARATOR . $orginial_filename
229 );
230 }
231
232 public function handInTestResult(int $active_fi, int $pass, string $pdf_path): void
233 {
234 $this->ensureTestArchiveIsAvailable();
235 $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass);
236 $new_path = $this->getPassDataDirectory($active_fi, $pass) . DIRECTORY_SEPARATOR . self::TEST_RESULT_FILENAME;
237 copy($pdf_path, $new_path);
238 $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_path);
239 }
240
241 protected function hasTestArchive(): bool
242 {
243 return is_dir($this->getTestArchive());
244 }
245
246 protected function createArchiveForTest(): void
247 {
248 ilFileUtils::makeDirParents($this->getTestArchive());
249 }
250
251 protected function getTestArchive(): string
252 {
253 $test_archive_directory = $this->external_directory_path . DIRECTORY_SEPARATOR . $this->client_id . DIRECTORY_SEPARATOR . 'tst_data'
254 . DIRECTORY_SEPARATOR . 'archive' . DIRECTORY_SEPARATOR . 'tst_' . $this->test_obj_id;
255 return $test_archive_directory;
256 }
257
258 protected function ensureTestArchiveIsAvailable(): void
259 {
260 if (!$this->hasTestArchive()) {
261 $this->createArchiveForTest();
262 }
263 return;
264 }
265
266 public function updateTestArchive(): void
267 {
268 $this->log_viewer->getLogExportForRefjId(
269 $this->test_ref_id
270 )->writeToFile(
271 $this->getTestArchive() . DIRECTORY_SEPARATOR . self::TEST_LOG_FILENAME
272 );
273
274 // Generate test pass overview
275 $test = new ilObjTest($this->test_obj_id, false);
276 if ($this->test_ref_id !== null) {
277 $test->setRefId($this->test_ref_id);
278 }
279
280 $array_of_actives = [];
281 $participants = $test->getParticipants();
282
283 foreach (array_keys($participants) as $key) {
284 $array_of_actives[] = $key;
285 }
286
287 $filename = realpath($this->getTestArchive()) . DIRECTORY_SEPARATOR . 'participant_attempt_overview.html';
288 $this->html_generator->generateHTML(
289 $this->createUserResultsForArchive(
290 $test,
291 $array_of_actives
292 ),
294 );
295 }
296
297 public function ensureZipExportDirectoryExists(): void
298 {
299 if (!$this->hasZipExportDirectory()) {
300 $this->createZipExportDirectory();
301 }
302 }
303
304 public function hasZipExportDirectory(): bool
305 {
306 return is_dir($this->getZipExportDirectory());
307 }
308
309 protected function createZipExportDirectory(): void
310 {
311 mkdir($this->getZipExportDirectory(), 0777, true);
312 }
313
314 public function getZipExportDirectory(): string
315 {
316 return $this->external_directory_path . DIRECTORY_SEPARATOR . $this->client_id . DIRECTORY_SEPARATOR . 'tst_data'
317 . DIRECTORY_SEPARATOR . self::EXPORT_DIRECTORY . DIRECTORY_SEPARATOR . 'tst_' . $this->test_obj_id;
318 }
319
320 public function compressTestArchive(): void
321 {
322 $this->updateTestArchive();
323 $this->ensureZipExportDirectoryExists();
324
325 $zip_output_path = $this->getZipExportDirectory();
326 $zip_output_filename = 'test_archive_obj_' . $this->test_obj_id . '_' . time() . '.zip';
327
328 ilFileUtils::zip($this->getTestArchive(), $zip_output_path . DIRECTORY_SEPARATOR . $zip_output_filename, true);
329 return;
330 }
331
332 protected function hasPassDataDirectory(int $active_fi, int $pass): bool
333 {
334 return is_dir($this->getPassDataDirectory($active_fi, $pass));
335 }
336
337 protected function createPassDataDirectory(int $active_fi, int $pass): void
338 {
339 mkdir($this->getPassDataDirectory($active_fi, $pass), 0777, true);
340 return;
341 }
342
343 private function buildPassDataDirectory($active_fi, $pass): ?string
344 {
345 foreach ($this->archive_data_index as $data_index_entry) {
346 if ($data_index_entry != null && $data_index_entry['identifier'] == $active_fi . '|' . $pass) {
347 array_shift($data_index_entry);
348 return $this->getTestArchive() . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $data_index_entry);
349 }
350 }
351
352 return null;
353 }
354
355 protected function getPassDataDirectory(int $active_fi, int $pass): ?string
356 {
357 $pass_data_dir = $this->buildPassDataDirectory($active_fi, $pass);
358
359 if ($pass_data_dir !== null) {
360 return $pass_data_dir;
361 }
362
363 $test_obj = new ilObjTest($this->test_obj_id, false);
364 if ($test_obj->getAnonymity()) {
365 $firstname = $this->lng->txt('anonymous');
366 $lastname = '';
367 $matriculation = '';
368 } elseif ($this->getParticipantData()) {
369 $usr_data = $this->getParticipantData()->getUserDataByActiveId($active_fi);
370 $firstname = $usr_data['firstname'] ?? $this->lng->txt('deleted_user');
371 $lastname = $usr_data['lastname'] ?? '';
372 $matriculation = $usr_data['matriculation'] ?? '';
373 } else {
374 $firstname = $this->user->getFirstname();
375 $lastname = $this->user->getLastname();
376 $matriculation = $this->user->getMatriculation();
377 }
378
379 $this->appendToArchiveDataIndex(
380 date(DATE_ISO8601),
381 $active_fi,
382 $pass,
383 $firstname,
384 $lastname,
385 $matriculation
386 );
387
388 return $this->buildPassDataDirectory($active_fi, $pass);
389 }
390
391 protected function ensurePassDataDirectoryIsAvailable(int $active_fi, int $pass): void
392 {
393 if (!$this->hasPassDataDirectory($active_fi, $pass)) {
394 $this->createPassDataDirectory($active_fi, $pass);
395 }
396 return;
397 }
398
399 protected function hasPassMaterialsDirectory(int $active_fi, int $pass): bool
400 {
401 if (is_dir($this->getPassMaterialsDirectory($active_fi, $pass))) {
402 return true;
403 }
404 return false;
405 }
406
407 protected function createPassMaterialsDirectory(int $active_fi, int $pass): string
408 {
414 $user = $this->user;
415
416 if ($this->getParticipantData()) {
417 $usrData = $this->getParticipantData()->getUserDataByActiveId($active_fi);
418 $user = new ilObjUser();
419 $user->setFirstname($usrData['firstname'] ?? $this->lng->txt('deleted_user'));
420 $user->setLastname($usrData['lastname'] ?? '');
421 $user->setMatriculation($usrData['matriculation'] ?? '');
422 }
423
424 $this->appendToArchiveDataIndex(
425 date('c'),
426 $active_fi,
427 $pass,
428 $user->getFirstname(),
429 $user->getLastname(),
430 $user->getMatriculation()
431 );
432 $material_directory = $this->getPassMaterialsDirectory($active_fi, $pass);
433 mkdir($material_directory, 0777, true);
434 return $material_directory;
435 }
436
437 protected function getPassMaterialsDirectory(int $active_fi, int $pass): string
438 {
439 $pass_data_directory = $this->getPassDataDirectory($active_fi, $pass);
440 return $pass_data_directory . DIRECTORY_SEPARATOR . self::PASS_MATERIALS_PATH_COMPONENT;
441 }
442
443 protected function ensurePassMaterialsDirectoryIsAvailable(int $active_fi, int $pass): void
444 {
445 if (!$this->hasPassMaterialsDirectory($active_fi, $pass)) {
446 $this->createPassMaterialsDirectory($active_fi, $pass);
447 }
448 }
449
450 protected function readArchiveDataIndex(): array
451 {
456 $data_index_file = $this->getTestArchive() . DIRECTORY_SEPARATOR . self::DATA_INDEX_FILENAME;
457
458 $contents = [];
459
461 if (@file_exists($data_index_file)) {
462 $lines = explode("\n", file_get_contents($data_index_file));
463 foreach ($lines as $line) {
464 if (strlen($line) === 0) {
465 continue;
466 }
467 $line_items = explode('|', $line);
468 $line_data = [];
469 $line_data['identifier'] = $line_items[0] . '|' . $line_items[1];
470 $line_data['yyyy'] = $line_items[2];
471 $line_data['mm'] = $line_items[3];
472 $line_data['dd'] = $line_items[4];
473 $line_data['directory'] = $line_items[5];
474 $contents[] = $line_data;
475 }
476 }
477 return $contents;
478 }
479
480 protected function appendToArchiveDataIndex(
481 string $date,
482 int $active_fi,
483 int $pass,
484 string $user_firstname,
485 string $user_lastname,
486 string $matriculation
487 ): void {
488 $line = $this->determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation);
489
490 $this->archive_data_index[] = $line;
491 $output_contents = '';
492
493 foreach ($this->archive_data_index as $line_data) {
494 if ($line_data['identifier'] == "|") {
495 continue;
496 }
497 $output_contents .= implode('|', $line_data) . "\n";
498 }
499
500 file_put_contents($this->getTestArchive() . DIRECTORY_SEPARATOR . self::DATA_INDEX_FILENAME, $output_contents);
501 $this->readArchiveDataIndex();
502 return;
503 }
504
505 private function determinePassDataPath(
506 string $date,
507 int $active_fi,
508 int $pass,
509 string $user_firstname,
510 string $user_lastname,
511 string $matriculation
512 ): array {
513 $parsed_date = date_create_from_format('Y-m-d\TH:i:sP', $date);
514 if (!$parsed_date) {
515 throw new Exception('Invalid date format. Expected ISO 8601 format.');
516 }
517
518 $line = [
519 'identifier' => $active_fi . '|' . $pass,
520 'yyyy' => date_format($parsed_date, 'Y'),
521 'mm' => date_format($parsed_date, 'm'),
522 'dd' => date_format($parsed_date, 'd'),
523 'directory' => $active_fi . '_' . $pass . '_' . $user_firstname . '_' . $user_lastname . '_' . $matriculation
524 ];
525 return $line;
526 }
527
529 \ilObjTest $test_obj,
530 array $active_ids,
531 ): string {
532 $template = new ilTemplate('tpl.il_as_tst_participants_result_output.html', true, true, 'components/ILIAS/Test');
533
534 $participant_data = new ilTestParticipantData($this->db, $this->lng);
535 $participant_data->setParticipantAccessFilter(
536 $this->participant_access_filter_factory->getAccessResultsUserFilter($test_obj->getRefId())
537 );
538 $participant_data->setActiveIdsFilter($active_ids);
539 $participant_data->load($test_obj->getTestId());
540
541 $test_session_factory = new ilTestSessionFactory($test_obj, $this->db, $this->user);
542
543 $count = 0;
544 foreach ($active_ids as $active_id) {
545 if (!in_array($active_id, $participant_data->getActiveIds())) {
546 continue;
547 }
548
549 $count++;
550 $results = '';
551 if ($active_id > 0) {
552 $results = $this->getResultsOfUserOutput(
553 $test_obj,
554 $test_session_factory->getSession($active_id),
555 $participant_data->getUserDataByActiveId($active_id),
556 (int) $active_id,
557 ilObjTest::_getResultPass($active_id)
558 );
559 }
560 if ($count < count($active_ids)) {
561 $template->touchBlock('break');
562 }
563 $template->setCurrentBlock('user_result');
564 $template->setVariable('USER_RESULT', $results);
565 $template->parseCurrentBlock();
566 }
567
568 return $template->get();
569 }
570
571 public function getResultsOfUserOutput(
572 \ilObjTest $test_obj,
573 ilTestSession $test_session,
574 array $participant_data,
575 int $active_id,
576 int $attempt
577 ): string {
578 $template = new ilTemplate('tpl.il_as_tst_results_participant.html', true, true, 'components/ILIAS/Test');
579
580 $uname = "{$participant_data['firstname']} {$participant_data['lastname']}";
581 if ($test_obj->getAnonymity()) {
582 $uname = $this->lng->txt('anonymous');
583 }
584
585 $test_result_title_builder = new ResultsTitleBuilder($this->lng, $this->obj_cache);
586
587 $result_array = $test_obj->getTestResult(
588 $active_id,
589 $attempt,
590 false,
591 true
592 );
593
594 $table = $this->ui_factory->table()->data(
595 $this->getDataRetrievalForAttemptOverviewTable($result_array),
596 $test_result_title_builder->getPassDetailsHeaderLabel($attempt + 1),
597 $this->getColumnsForAttemptOverviewTable(),
598 )->withRequest($this->request);
599 $template->setVariable(
600 'PASS_DETAILS',
601 $this->ui_renderer->render($table)
602 );
603
604 if ($test_obj->isShowExamIdInTestResultsEnabled()) {
605 $template->setCurrentBlock('exam_id_footer');
606 $template->setVariable('EXAM_ID_VAL', ilObjTest::lookupExamId(
607 $test_session->getActiveId(),
608 $attempt
609 ));
610 $template->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id'));
611 $template->parseCurrentBlock();
612 }
613
614 $template->setCurrentBlock('participant_block_id');
615 $template->setVariable('PARTICIPANT_BLOCK_ID', "participant_active_{$active_id}");
616 $template->parseCurrentBlock();
617
618 $template->setVariable('TEXT_HEADING', sprintf($this->lng->txt('tst_result_user_name'), $uname));
619
620 if ($participant_data['matriculation'] !== '') {
621 $template->setVariable('USER_DATA', "{$this->lng->txt('matriculation')}: {$participant_data['matriculation']}");
622 }
623
624 $results = $this->test_result_repository->getTestResult($active_id);
625
626 $status = $this->lng->txt($results->isPassed() ? 'passed_official' : 'failed_official');
627 $template->setVariable(
628 'GRADING_MESSAGE',
629 "{$this->lng->txt('passed_status')}: {$status}<br>"
630 . "{$this->lng->txt('tst_mark')}: {$results->getMarkOfficial()}"
631 );
632
633 $template->setVariable('PASS_FINISH_DATE_LABEL', $this->lng->txt('tst_pass_finished_on'));
634 $template->setVariable(
635 'PASS_FINISH_DATE_VALUE',
636 (new \DateTimeImmutable('@' . ilObjTest::lookupLastTestPassAccess($active_id, $attempt)))
637 ->setTimezone(new DateTimeZone($this->user->getTimeZone()))
638 ->format($this->user->getDateTimeFormat()->toString())
639 );
640
641 return $template->get();
642 }
643
644 private function getColumnsForAttemptOverviewTable(): array
645 {
646 $cf = $this->ui_factory->table()->column();
647 $columns = [
648 'order' => $cf->number($this->lng->txt('order')),
649 'question_id' => $cf->number($this->lng->txt('question_id')),
650 'title' => $cf->text($this->lng->txt('tst_question_title')),
651 'reachable_points' => $cf->number($this->lng->txt('tst_maximum_points')),
652 'reached_points' => $cf->number($this->lng->txt('tst_reached_points'))
653 ];
654 $columns['solved'] = $cf->text($this->lng->txt('tst_percent_solved'));
655 return $columns;
656 }
657
658 private function getDataRetrievalForAttemptOverviewTable(array $result_data): DataRetrieval
659 {
660 return new class ($result_data) implements DataRetrieval {
661 public function __construct(
662 private readonly array $result_data
663 ) {
664 }
665
666 public function getRows(
667 DataRowBuilder $row_builder,
668 array $visible_column_ids,
670 Order $order,
671 ?array $filter_data,
672 ?array $additional_parameters
673 ): \Generator {
674 $i = 1;
675 foreach ($this->result_data as $result) {
676 if (!isset($result['qid'])) {
677 continue;
678 }
679 yield $row_builder->buildDataRow(
680 (string) $result['qid'],
681 [
682 'order' => $i++,
683 'question_id' => $result['qid'],
684 'title' => $result['title'],
685 'reachable_points' => $result['max'],
686 'reached_points' => $result['reached'],
687 'solved' => $result['percent']
688 ]
689 );
690 }
691 }
692
693 public function getTotalRowCount(
694 ?array $filter_data,
695 ?array $additional_parameters
696 ): ?int {
697 return count($this->result_data);
698 }
699 };
700 }
701
702 private function logArchivingProcess(string $message): void
703 {
704 $archive = $this->getTestArchive() . DIRECTORY_SEPARATOR . self::ARCHIVE_LOG;
705 if (file_exists($archive)) {
706 $content = file_get_contents($archive) . "\n" . $message;
707 } else {
708 $content = $message;
709 }
710
711 file_put_contents($archive, $content);
712 }
713}
$filename
Definition: buildRTE.php:78
Builds a Color from either hex- or rgb values.
Definition: Factory.php:31
Both the subject and the direction need to be specified when expressing an order.
Definition: Order.php:29
A simple class to express a naive range of whole positive numbers.
Definition: Range.php:29
return true
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
static zip(string $a_dir, string $a_file, bool $compress_content=false)
language handling
static _getResultPass($active_id)
Retrieves the pass number that should be counted for a given user.
getTestId()
Gets the database id of the additional test data.
static lookupExamId($active_id, $pass)
getTestResult(int $active_id, ?int $attempt=null, bool $ordered_sequence=false, bool $consider_hidden_questions=true, bool $consider_optional_questions=true)
Calculates the results of a test for a given user and returns an array with all test results.
getQuestionDataset($question_id)
Returns the dataset for a given question id.
isShowExamIdInTestResultsEnabled()
getTextAnswer($active_id, $question_id, $pass=null)
Returns the text answer of a given user for a given question.
User class.
class ilObjectDataCache
special template class to simplify handling of ITX/PEAR
ilTestParticipantData $participant_data
createPassDataDirectory(int $active_fi, int $pass)
getDataRetrievalForAttemptOverviewTable(array $result_data)
getPassDataDirectory(int $active_fi, int $pass)
appendToArchiveDataIndex(string $date, int $active_fi, int $pass, string $user_firstname, string $user_lastname, string $matriculation)
handInParticipantQuestionMaterial(int $active_fi, int $pass, int $question_fi, string $original_filename, string $file_path)
handInBestSolutionQuestionMaterial(int $question_fi, string $orginial_filename, string $file_path)
setParticipantData(ilTestParticipantData $participant_data)
createUserResultsForArchive(\ilObjTest $test_obj, array $active_ids,)
logArchivingProcess(string $message)
ilTestHTMLGenerator $html_generator
hasPassMaterialsDirectory(int $active_fi, int $pass)
hasPassDataDirectory(int $active_fi, int $pass)
createPassMaterialsDirectory(int $active_fi, int $pass)
ensurePassMaterialsDirectoryIsAvailable(int $active_fi, int $pass)
const TEST_BEST_SOLUTION_PATH_COMPONENT
ensurePassDataDirectoryIsAvailable(int $active_fi, int $pass)
buildPassDataDirectory($active_fi, $pass)
handInParticipantUploadedResults(int $active_fi, int $pass, ilObjTest $tst_obj)
determinePassDataPath(string $date, int $active_fi, int $pass, string $user_firstname, string $user_lastname, string $matriculation)
handInTestBestSolution(string $best_solution)
TestResultRepository $test_result_repository
getPassMaterialsDirectory(int $active_fi, int $pass)
handInParticipantMisc(int $active_fi, int $pass, string $original_filename, string $file_path)
const QUESTION_PATH_COMPONENT_PREFIX
getResultsOfUserOutput(\ilObjTest $test_obj, ilTestSession $test_session, array $participant_data, int $active_id, int $attempt)
handInTestResult(int $active_fi, int $pass, string $pdf_path)
Class that handles PDF generation for test and assessment.
setParticipantAccessFilter(Closure $participantAccessFilter)
setActiveIdsFilter(array $active_ids_filter)
Test session handler.
const CLIENT_ID
Definition: constants.php:41
buildDataRow(string $id, array $record)
An entity that renders components to a string output.
Definition: Renderer.php:31
Interface ilDBInterface.
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
global $lng
Definition: privfeed.php:31
$results
global $DIC
Definition: shib_login.php:26
$message
Definition: xapiexit.php:31