19 declare(strict_types=1);
39 public const DIR_SEP = DIRECTORY_SEPARATOR;
75 private readonly
IRSS $irss,
80 private readonly
int $test_obj_id,
81 private ?
int $test_ref_id =
null 85 $ilias = $DIC[
'ilias'];
86 $local_dic = TestDIC::dic();
89 $this->external_directory_path = $ilias->ini_ilias->readVariable(
'clients',
'datadir');
91 $this->test_result_repository = $local_dic[
'results.data.repository'];
108 string $original_filename,
115 . DIRECTORY_SEPARATOR . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi;
116 if (!is_dir($pass_question_directory)) {
117 mkdir($pass_question_directory, 0777,
true);
120 copy($file_path, $pass_question_directory . DIRECTORY_SEPARATOR . $original_filename);
123 date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
124 . $pass_question_directory . DIRECTORY_SEPARATOR . $original_filename
131 string $original_filename,
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);
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);
150 $this->html_generator->generateHTML(
152 $best_solution_path . DIRECTORY_SEPARATOR . self::HTML_BEST_SOLUTION_FILENAME
156 date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
157 . $best_solution_path . DIRECTORY_SEPARATOR . self::HTML_BEST_SOLUTION_FILENAME
161 date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $best_solution_path
171 foreach ($questions as $question) {
173 if ($question->type_tag ===
'assFileUpload') {
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);
182 $resource_id = $tst_obj->
getTextAnswer($active_fi, $question->question_id, $pass);
183 if ($resource_id ===
'') {
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);
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);
204 string $orginial_filename,
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);
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);
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);
224 copy($file_path, $question_materials_path . DIRECTORY_SEPARATOR . $orginial_filename);
227 date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
228 . $question_materials_path . DIRECTORY_SEPARATOR . $orginial_filename
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);
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;
268 $this->log_viewer->getLogExportForRefjId(
271 $this->
getTestArchive() . DIRECTORY_SEPARATOR . self::TEST_LOG_FILENAME
275 $test =
new ilObjTest($this->test_obj_id,
false);
276 if ($this->test_ref_id !==
null) {
277 $test->setRefId($this->test_ref_id);
280 $array_of_actives = [];
281 $participants = $test->getParticipants();
283 foreach (array_keys($participants) as $key) {
284 $array_of_actives[] = $key;
288 $this->html_generator->generateHTML(
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;
326 $zip_output_filename =
'test_archive_obj_' . $this->test_obj_id .
'_' . time() .
'.zip';
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);
359 if ($pass_data_dir !==
null) {
360 return $pass_data_dir;
363 $test_obj =
new ilObjTest($this->test_obj_id,
false);
364 if ($test_obj->getAnonymity()) {
365 $firstname = $this->
lng->txt(
'anonymous');
370 $firstname = $usr_data[
'firstname'] ?? $this->
lng->txt(
'deleted_user');
371 $lastname = $usr_data[
'lastname'] ??
'';
372 $matriculation = $usr_data[
'matriculation'] ??
'';
374 $firstname = $this->
user->getFirstname();
375 $lastname = $this->
user->getLastname();
376 $matriculation = $this->
user->getMatriculation();
419 $user->setFirstname($usrData[
'firstname'] ?? $this->
lng->txt(
'deleted_user'));
420 $user->setLastname($usrData[
'lastname'] ??
'');
421 $user->setMatriculation($usrData[
'matriculation'] ??
'');
428 $user->getFirstname(),
429 $user->getLastname(),
430 $user->getMatriculation()
433 mkdir($material_directory, 0777,
true);
434 return $material_directory;
440 return $pass_data_directory . DIRECTORY_SEPARATOR . self::PASS_MATERIALS_PATH_COMPONENT;
456 $data_index_file = $this->
getTestArchive() . DIRECTORY_SEPARATOR . self::DATA_INDEX_FILENAME;
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) {
467 $line_items = explode(
'|', $line);
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;
484 string $user_firstname,
485 string $user_lastname,
486 string $matriculation
488 $line = $this->
determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation);
490 $this->archive_data_index[] = $line;
491 $output_contents =
'';
493 foreach ($this->archive_data_index as $line_data) {
494 if ($line_data[
'identifier'] ==
"|") {
497 $output_contents .= implode(
'|', $line_data) .
"\n";
500 file_put_contents($this->
getTestArchive() . DIRECTORY_SEPARATOR . self::DATA_INDEX_FILENAME, $output_contents);
509 string $user_firstname,
510 string $user_lastname,
511 string $matriculation
513 $parsed_date = date_create_from_format(
'Y-m-d\TH:i:sP', $date);
515 throw new Exception(
'Invalid date format. Expected ISO 8601 format.');
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
532 $template =
new ilTemplate(
'tpl.il_as_tst_participants_result_output.html',
true,
true,
'components/ILIAS/Test');
536 $this->participant_access_filter_factory->getAccessResultsUserFilter($test_obj->
getRefId())
544 foreach ($active_ids as $active_id) {
545 if (!in_array($active_id, $participant_data->
getActiveIds())) {
551 if ($active_id > 0) {
554 $test_session_factory->getSession($active_id),
560 if ($count < count($active_ids)) {
561 $template->touchBlock(
'break');
563 $template->setCurrentBlock(
'user_result');
564 $template->setVariable(
'USER_RESULT',
$results);
565 $template->parseCurrentBlock();
568 return $template->get();
574 array $participant_data,
578 $template =
new ilTemplate(
'tpl.il_as_tst_results_participant.html',
true,
true,
'components/ILIAS/Test');
580 $uname =
"{$participant_data['firstname']} {$participant_data['lastname']}";
582 $uname = $this->
lng->txt(
'anonymous');
585 $test_result_title_builder =
new ResultsTitleBuilder($this->
lng, $this->obj_cache);
594 $table = $this->ui_factory->table()->data(
596 $test_result_title_builder->getPassDetailsHeaderLabel($attempt + 1),
598 )->withRequest($this->request);
599 $template->setVariable(
601 $this->ui_renderer->render($table)
605 $template->setCurrentBlock(
'exam_id_footer');
610 $template->setVariable(
'EXAM_ID_TXT', $this->
lng->txt(
'exam_id'));
611 $template->parseCurrentBlock();
614 $template->setCurrentBlock(
'participant_block_id');
615 $template->setVariable(
'PARTICIPANT_BLOCK_ID',
"participant_active_{$active_id}");
616 $template->parseCurrentBlock();
618 $template->setVariable(
'TEXT_HEADING', sprintf($this->
lng->txt(
'tst_result_user_name'), $uname));
620 if ($participant_data[
'matriculation'] !==
'') {
621 $template->setVariable(
'USER_DATA',
"{$this->lng->txt('matriculation')}: {$participant_data['matriculation']}");
624 $results = $this->test_result_repository->getTestResult($active_id);
626 $status = $this->
lng->txt(
$results->isPassed() ?
'passed_official' :
'failed_official');
627 $template->setVariable(
629 "{$this->lng->txt('passed_status')}: {$status}<br>" 630 .
"{$this->lng->txt('tst_mark')}: {$results->getMarkOfficial()}" 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)))
638 ->format($this->
user->getDateTimeFormat()->toString())
641 return $template->get();
646 $cf = $this->ui_factory->table()->column();
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'))
654 $columns[
'solved'] = $cf->text($this->
lng->txt(
'tst_percent_solved'));
662 private readonly array $result_data
666 public function getRows(
668 array $visible_column_ids,
672 ?array $additional_parameters
675 foreach ($this->result_data as $result) {
676 if (!isset($result[
'qid'])) {
680 (
string) $result[
'qid'],
683 'question_id' => $result[
'qid'],
684 'title' => $result[
'title'],
685 'reachable_points' => $result[
'max'],
686 'reached_points' => $result[
'reached'],
687 'solved' => $result[
'percent']
693 public function getTotalRowCount(
695 ?array $additional_parameters
697 return count($this->result_data);
704 $archive = $this->
getTestArchive() . DIRECTORY_SEPARATOR . self::ARCHIVE_LOG;
705 if (file_exists($archive)) {
706 $content = file_get_contents($archive) .
"\n" .
$message;
711 file_put_contents($archive, $content);
ilTestParticipantData $participant_data
createUserResultsForArchive(\ilObjTest $test_obj, array $active_ids,)
TestResultRepository $test_result_repository
const LOG_DTSGROUP_FORMAT
handInParticipantQuestionMaterial(int $active_fi, int $pass, int $question_fi, string $original_filename, string $file_path)
getPassDataDirectory(int $active_fi, int $pass)
const HTML_BEST_SOLUTION_FILENAME
createZipExportDirectory()
const TEST_RESULT_FILENAME
ensurePassDataDirectoryIsAvailable(int $active_fi, int $pass)
ensureZipExportDirectoryExists()
getTestId()
Gets the database id of the additional test data.
createPassMaterialsDirectory(int $active_fi, int $pass)
string $external_directory_path
const DATA_INDEX_FILENAME
isShowExamIdInTestResultsEnabled()
getQuestionsOfPass(int $active_id, int $pass)
hasPassMaterialsDirectory(int $active_fi, int $pass)
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
const PASS_MATERIALS_PATH_COMPONENT
Both the subject and the direction need to be specified when expressing an order. ...
buildDataRow(string $id, array $record)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
setParticipantAccessFilter(Closure $participantAccessFilter)
const QUESTION_PATH_COMPONENT_PREFIX
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setActiveIdsFilter(array $active_ids_filter)
createPassDataDirectory(int $active_fi, int $pass)
getUserDataByActiveId(int $active_Id)
static _getResultPass($active_id)
Retrieves the pass number that should be counted for a given user.
buildPassDataDirectory($active_fi, $pass)
getPassMaterialsDirectory(int $active_fi, int $pass)
handInTestBestSolution(string $best_solution)
handInParticipantUploadedResults(int $active_fi, int $pass, ilObjTest $tst_obj)
const LOG_ADDITION_STRING
determinePassDataPath(string $date, int $active_fi, int $pass, string $user_firstname, string $user_lastname, string $matriculation)
getResultsOfUserOutput(\ilObjTest $test_obj, ilTestSession $test_session, array $participant_data, int $active_id, int $attempt)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Class that handles PDF generation for test and assessment.
getColumnsForAttemptOverviewTable()
getQuestionDataset($question_id)
Returns the dataset for a given question id.
getTextAnswer($active_id, $question_id, $pass=null)
Returns the text answer of a given user for a given question.
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...
handInParticipantMisc(int $active_fi, int $pass, string $original_filename, string $file_path)
__construct(Container $dic, ilPlugin $plugin)
static zip(string $a_dir, string $a_file, bool $compress_content=false)
appendToArchiveDataIndex(string $date, int $active_fi, int $pass, string $user_firstname, string $user_lastname, string $matriculation)
const TEST_BEST_SOLUTION_PATH_COMPONENT
handInTestResult(int $active_fi, int $pass, string $pdf_path)
ensureTestArchiveIsAvailable()
setParticipantData(ilTestParticipantData $participant_data)
logArchivingProcess(string $message)
handInBestSolutionQuestionMaterial(int $question_fi, string $orginial_filename, string $file_path)
A simple class to express a naive range of whole positive numbers.
ensurePassMaterialsDirectoryIsAvailable(int $active_fi, int $pass)
const TEST_MATERIALS_PATH_COMPONENT
ilTestHTMLGenerator $html_generator
getDataRetrievalForAttemptOverviewTable(array $result_data)
static lookupExamId($active_id, $pass)
hasPassDataDirectory(int $active_fi, int $pass)