19 declare(strict_types=1);
    37     public const DIR_SEP = DIRECTORY_SEPARATOR;
    71         private readonly 
IRSS $irss,
    76         private readonly 
int $test_obj_id,
    77         private ?
int $test_ref_id = null
    81         $ilias = $DIC[
'ilias'];
    84         $this->external_directory_path = $ilias->ini_ilias->readVariable(
'clients', 
'datadir');
   102         string $original_filename,
   109             . DIRECTORY_SEPARATOR . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi;
   110         if (!is_dir($pass_question_directory)) {
   111             mkdir($pass_question_directory, 0777, 
true);
   114         copy($file_path, $pass_question_directory . DIRECTORY_SEPARATOR . $original_filename);
   117             date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
   118             . $pass_question_directory . DIRECTORY_SEPARATOR . $original_filename
   125         string $original_filename,
   130         $new_path = $this->
getPassDataDirectory($active_fi, $pass) . DIRECTORY_SEPARATOR . $original_filename;
   131         copy($file_path, $new_path);
   132         $this->
logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_path);
   139         $best_solution_path = $this->
getTestArchive() . DIRECTORY_SEPARATOR . self::TEST_BEST_SOLUTION_PATH_COMPONENT;
   140         if (!is_dir($best_solution_path)) {
   141             mkdir($best_solution_path, 0777, 
true);
   144         $this->html_generator->generateHTML(
   146             $best_solution_path . DIRECTORY_SEPARATOR . self::HTML_BEST_SOLUTION_FILENAME
   150             date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
   151             . $best_solution_path . DIRECTORY_SEPARATOR . self::HTML_BEST_SOLUTION_FILENAME
   155             date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $best_solution_path
   165         foreach ($questions as $question) {
   167             if ($question->type_tag === 
'assFileUpload') {
   172                 $archive_folder = $pass_material_directory . DIRECTORY_SEPARATOR . $question->question_id . DIRECTORY_SEPARATOR;
   173                 if (!file_exists($archive_folder)) {
   174                     mkdir($archive_folder, 0777, 
true);
   176                 $resource_id = $tst_obj->
getTextAnswer($active_fi, $question->question_id, $pass);
   177                 if ($resource_id === 
'') {
   180                 $irss_unique_id = $this->irss->manage()->find($resource_id);
   181                 if ($irss_unique_id != null) {
   182                     $resource = $this->irss->manage()->getResource($irss_unique_id);
   183                     $information = $resource->getCurrentRevision()->getInformation();
   184                     $stream = $this->irss->consume()->stream($irss_unique_id);
   186                     $file_stream = fopen($stream->getStream()->getMetadata(
'uri'), 
'r');
   187                     $file_content = stream_get_contents($file_stream);
   188                     fclose($file_stream);
   189                     $target_destination = $archive_folder . $information->getTitle();
   190                     file_put_contents($target_destination, $file_content);
   198         string $orginial_filename,
   203         $best_solution_path = $this->
getTestArchive() . DIRECTORY_SEPARATOR . self::TEST_BEST_SOLUTION_PATH_COMPONENT;
   204         if (!is_dir($best_solution_path)) {
   205             mkdir($best_solution_path, 0777, 
true);
   208         $materials_path = $best_solution_path . DIRECTORY_SEPARATOR . self::TEST_MATERIALS_PATH_COMPONENT;
   209         if (!is_dir($materials_path)) {
   210             mkdir($materials_path, 0777, 
true);
   213         $question_materials_path = $materials_path . DIRECTORY_SEPARATOR . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi;
   214         if (!is_dir($question_materials_path)) {
   215             mkdir($question_materials_path, 0777, 
true);
   218         copy($file_path, $question_materials_path . DIRECTORY_SEPARATOR . $orginial_filename);
   221             date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
   222             . $question_materials_path . DIRECTORY_SEPARATOR . $orginial_filename
   230         $new_path = $this->
getPassDataDirectory($active_fi, $pass) . DIRECTORY_SEPARATOR . self::TEST_RESULT_FILENAME;
   231         copy($pdf_path, $new_path);
   232         $this->
logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_path);
   247         $test_archive_directory = $this->external_directory_path . DIRECTORY_SEPARATOR . $this->client_id . DIRECTORY_SEPARATOR . 
'tst_data'   248             . DIRECTORY_SEPARATOR . 
'archive' . DIRECTORY_SEPARATOR . 
'tst_' . $this->test_obj_id;
   249         return $test_archive_directory;
   262         $this->log_viewer->getLogExportForRefjId(
   265             $this->
getTestArchive() . DIRECTORY_SEPARATOR . self::TEST_LOG_FILENAME
   269         $test = 
new ilObjTest($this->test_obj_id, 
false);
   270         if ($this->test_ref_id !== null) {
   271             $test->setRefId($this->test_ref_id);
   274         $array_of_actives = [];
   275         $participants = $test->getParticipants();
   277         foreach (array_keys($participants) as $key) {
   278             $array_of_actives[] = $key;
   282         $this->html_generator->generateHTML(
   310         return $this->external_directory_path . DIRECTORY_SEPARATOR . $this->client_id . DIRECTORY_SEPARATOR . 
'tst_data'   311             . DIRECTORY_SEPARATOR . self::EXPORT_DIRECTORY . DIRECTORY_SEPARATOR . 
'tst_' . $this->test_obj_id;
   320         $zip_output_filename = 
'test_archive_obj_' . $this->test_obj_id . 
'_' . time() . 
'.zip';
   339         foreach ($this->archive_data_index as $data_index_entry) {
   340             if ($data_index_entry != null && $data_index_entry[
'identifier'] == $active_fi . 
'|' . $pass) {
   341                 array_shift($data_index_entry);
   342                 return $this->
getTestArchive() . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $data_index_entry);
   353         if ($pass_data_dir !== null) {
   354             return $pass_data_dir;
   357         $test_obj = 
new ilObjTest($this->test_obj_id, 
false);
   358         if ($test_obj->getAnonymity()) {
   359             $firstname = $this->
lng->txt(
'anonymous');
   364             $firstname = $usr_data[
'firstname'] ?? $this->
lng->txt(
'deleted_user');
   365             $lastname = $usr_data[
'lastname'] ?? 
'';
   366             $matriculation = $usr_data[
'matriculation'] ?? 
'';
   368             $firstname = $this->
user->getFirstname();
   369             $lastname = $this->
user->getLastname();
   370             $matriculation = $this->
user->getMatriculation();
   413             $user->setFirstname($usrData[
'firstname'] ?? $this->
lng->txt(
'deleted_user'));
   414             $user->setLastname($usrData[
'lastname'] ?? 
'');
   415             $user->setMatriculation($usrData[
'matriculation'] ?? 
'');
   422             $user->getFirstname(),
   423             $user->getLastname(),
   424             $user->getMatriculation()
   427         mkdir($material_directory, 0777, 
true);
   428         return $material_directory;
   434         return $pass_data_directory . DIRECTORY_SEPARATOR . self::PASS_MATERIALS_PATH_COMPONENT;
   450         $data_index_file = $this->
getTestArchive() . DIRECTORY_SEPARATOR . self::DATA_INDEX_FILENAME;
   455         if (@file_exists($data_index_file)) {
   456             $lines = explode(
"\n", file_get_contents($data_index_file));
   457             foreach ($lines as $line) {
   458                 if (strlen($line) === 0) {
   461                 $line_items = explode(
'|', $line);
   463                 $line_data[
'identifier'] = $line_items[0] . 
'|' . $line_items[1];
   464                 $line_data[
'yyyy'] = $line_items[2];
   465                 $line_data[
'mm'] = $line_items[3];
   466                 $line_data[
'dd'] = $line_items[4];
   467                 $line_data[
'directory'] = $line_items[5];
   468                 $contents[] = $line_data;
   478         string $user_firstname,
   479         string $user_lastname,
   480         string $matriculation
   482         $line = $this->
determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation);
   484         $this->archive_data_index[] = $line;
   485         $output_contents = 
'';
   487         foreach ($this->archive_data_index as $line_data) {
   488             if ($line_data[
'identifier'] == 
"|") {
   491             $output_contents .= implode(
'|', $line_data) . 
"\n";
   494         file_put_contents($this->
getTestArchive() . DIRECTORY_SEPARATOR . self::DATA_INDEX_FILENAME, $output_contents);
   503         string $user_firstname,
   504         string $user_lastname,
   505         string $matriculation
   507         $parsed_date = date_create_from_format(
'Y-m-d\TH:i:sP', $date);
   509             throw new Exception(
'Invalid date format. Expected ISO 8601 format.');
   513             'identifier' => $active_fi . 
'|' . $pass,
   514             'yyyy' => date_format($parsed_date, 
'Y'),
   515             'mm' => date_format($parsed_date, 
'm'),
   516             'dd' => date_format($parsed_date, 
'd'),
   517             'directory' => $active_fi . 
'_' . $pass . 
'_' . $user_firstname . 
'_' . $user_lastname . 
'_' . $matriculation
   526         $template = 
new ilTemplate(
'tpl.il_as_tst_participants_result_output.html', 
true, 
true, 
'components/ILIAS/Test');
   530             $this->participant_access_filter_factory->getAccessResultsUserFilter($test_obj->
getRefId())
   538         foreach ($active_ids as $active_id) {
   539             if (!in_array($active_id, $participant_data->
getActiveIds())) {
   545             if ($active_id > 0) {
   548                     $test_session_factory->getSession($active_id),
   554             if ($count < count($active_ids)) {
   555                 $template->touchBlock(
'break');
   557             $template->setCurrentBlock(
'user_result');
   558             $template->setVariable(
'USER_RESULT', 
$results);
   559             $template->parseCurrentBlock();
   562         return $template->get();
   568         array $participant_data,
   572         $template = 
new ilTemplate(
'tpl.il_as_tst_results_participant.html', 
true, 
true, 
'components/ILIAS/Test');
   574         $uname = 
"{$participant_data['firstname']} {$participant_data['lastname']}";
   576             $uname = $this->
lng->txt(
'anonymous');
   579         $test_result_title_builder = 
new ResultsTitleBuilder($this->
lng, $this->obj_cache);
   588         $table = $this->ui_factory->table()->data(
   589             $test_result_title_builder->getPassDetailsHeaderLabel($attempt + 1),
   592         )->withRequest($this->request);
   593         $template->setVariable(
   595             $this->ui_renderer->render($table)
   599             $template->setCurrentBlock(
'exam_id_footer');
   604             $template->setVariable(
'EXAM_ID_TXT', $this->
lng->txt(
'exam_id'));
   605             $template->parseCurrentBlock();
   608         $template->setCurrentBlock(
'participant_block_id');
   609         $template->setVariable(
'PARTICIPANT_BLOCK_ID', 
"participant_active_{$active_id}");
   610         $template->parseCurrentBlock();
   612         $template->setVariable(
'TEXT_HEADING', sprintf($this->
lng->txt(
'tst_result_user_name'), $uname));
   614         if ($participant_data[
'matriculation'] !== 
'') {
   615             $template->setVariable(
'USER_DATA', 
"{$this->lng->txt('matriculation')}: {$participant_data['matriculation']}");
   619         $status = $this->
lng->txt(
$results[
'passed'] ? 
'passed_official' : 
'failed_official');
   620         $template->setVariable(
   622             "{$this->lng->txt('passed_status')}: {$status}<br>"   623             . 
"{$this->lng->txt('tst_mark')}: {$results['mark_official']}"   626         $template->setVariable(
'PASS_FINISH_DATE_LABEL', $this->
lng->txt(
'tst_pass_finished_on'));
   627         $template->setVariable(
   628             'PASS_FINISH_DATE_VALUE',
   629             (
new \
DateTimeImmutable(
'@' . ilObjTest::lookupLastTestPassAccess($active_id, $attempt)))
   631                 ->format($this->
user->getDateTimeFormat()->toString())
   634         return $template->get();
   638         bool $show_requested_hints_info
   640         $cf = $this->ui_factory->table()->column();
   642             'order' => $cf->number($this->
lng->txt(
'order')),
   643             'question_id' => $cf->number($this->lng->txt(
'question_id')),
   644             'title' => $cf->text($this->lng->txt(
'tst_question_title')),
   645             'reachable_points' => $cf->number($this->lng->txt(
'tst_maximum_points')),
   646             'reached_points' => $cf->number($this->lng->txt(
'tst_reached_points'))
   648         if ($show_requested_hints_info) {
   649             $columns[
'hints'] = $cf->number($this->
lng->txt(
'tst_question_hints_requested_hint_count_header'));
   651         $columns[
'solved'] = $cf->text($this->
lng->txt(
'tst_percent_solved'));
   659                 private readonly array $result_data
   663             public function getRows(
   665                 array $visible_column_ids,
   669                 ?array $additional_parameters
   672                 foreach ($this->result_data as $result) {
   673                     if (!isset($result[
'qid'])) {
   677                         (
string) $result[
'qid'],
   680                             'question_id' => $result[
'qid'],
   681                             'title' => $result[
'title'],
   682                             'reachable_points' => $result[
'max'],
   683                             'reached_points' => $result[
'reached'],
   684                             'hints' => $result[
'requested_hints'] ?? 0,
   685                             'solved' => $result[
'percent']
   691             public function getTotalRowCount(
   693                 ?array $additional_parameters
   695                 return count($this->result_data);
   702         $archive = $this->
getTestArchive() . DIRECTORY_SEPARATOR . self::ARCHIVE_LOG;
   703         if (file_exists($archive)) {
   704             $content = file_get_contents($archive) . 
"\n" . $message;
   709         file_put_contents($archive, $content);
 ilTestParticipantData $participant_data
 
createUserResultsForArchive(\ilObjTest $test_obj, array $active_ids,)
 
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
 
getTestResult(int $active_id, ?int $pass=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...
 
Both the subject and the direction need to be specified when expressing an order. ...
 
buildDataRow(string $id, array $record)
 
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)
 
getResultsForActiveId(int $active_id)
 
handInParticipantUploadedResults(int $active_fi, int $pass, ilObjTest $tst_obj)
 
isOfferingQuestionHintsEnabled()
 
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)
 
Class that handles PDF generation for test and assessment. 
 
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. 
 
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)
 
getColumnsForAttemptOverviewTable(bool $show_requested_hints_info)
 
hasPassDataDirectory(int $active_fi, int $pass)