ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilTestEvaluationGUI.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
43 {
46 
55  public function __construct(ilObjTest $object)
56  {
57  parent::__construct($object);
58  $this->participant_access_filter = new ilTestParticipantAccessFilterFactory($this->access);
59 
60  $this->processLockerFactory = new ilTestProcessLockerFactory(
61  new ilSetting('assessment'),
62  $this->db
63  );
64  }
65 
66  public function getTestAccess(): ilTestAccess
67  {
68  return $this->testAccess;
69  }
70 
71  public function setTestAccess($testAccess): void
72  {
73  $this->testAccess = $testAccess;
74  }
75 
76  public function executeCommand()
77  {
78  $cmd = $this->ctrl->getCmd();
79  $next_class = $this->ctrl->getNextClass($this);
80  $this->ctrl->saveParameter($this, "sequence");
81  $this->ctrl->saveParameter($this, "active_id");
82 
83  switch ($next_class) {
84  case 'iltestpassdetailsoverviewtablegui':
85  $tableGUI = new ilTestPassDetailsOverviewTableGUI($this->ctrl, $this, 'outUserPassDetails');
86  $this->ctrl->forwardCommand($tableGUI);
87  break;
88 
89  default:
90  if (in_array($cmd, ['excel_scored_test_run', 'excel_all_test_runs', 'csv'])) {
91  $ret = $this->exportEvaluation($cmd);
92  } elseif (in_array($cmd, ['excel_all_test_runs_a', 'csv_a'])) {
93  $ret = $this->exportAggregatedResults($cmd);
94  } else {
95  $ret = $this->$cmd();
96  }
97  break;
98  }
99  return $ret;
100  }
101 
105  public function filterEvaluation()
106  {
107  if (!$this->getTestAccess()->checkStatisticsAccess()) {
108  ilObjTestGUI::accessViolationRedirect();
109  }
110 
111  $table_gui = new ilEvaluationAllTableGUI($this, 'outEvaluation', $this->settings);
112  $table_gui->writeFilterToSession();
113  $this->ctrl->redirect($this, "outEvaluation");
114  }
115 
119  public function resetfilterEvaluation()
120  {
121  if (!$this->getTestAccess()->checkStatisticsAccess()) {
122  ilObjTestGUI::accessViolationRedirect();
123  }
124 
125  $table_gui = new ilEvaluationAllTableGUI($this, 'outEvaluation', $this->settings);
126  $table_gui->resetFilter();
127  $this->ctrl->redirect($this, "outEvaluation");
128  }
129 
133  public function outEvaluation(?array $prior_components = null): void
134  {
135  $ilToolbar = $this->toolbar;
136 
137  if (!$this->getTestAccess()->checkStatisticsAccess()) {
138  ilObjTestGUI::accessViolationRedirect();
139  }
140 
141  $this->tabs->activateTab(ilTestTabsManager::TAB_ID_STATISTICS);
142 
143  $table_gui = new ilEvaluationAllTableGUI(
144  $this,
145  'outEvaluation',
146  $this->settings,
147  $this->object->getAnonymity(),
148  $this->object->isOfferingQuestionHintsEnabled()
149  );
150 
151  $data = [];
152  $filter_array = [];
153 
154  foreach ($table_gui->getFilterItems() as $item) {
155  if (!in_array($item->getValue(), [false, ''])) {
156  switch ($item->getPostVar()) {
157  case 'group':
158  case 'name':
159  case 'course':
160  $filter_array[$item->getPostVar()] = $item->getValue();
161  break;
162  case 'passed_only':
163  $passedonly = $item->getChecked();
164  break;
165  }
166  }
167  }
168 
169  $factory = new ilTestEvaluationFactory(
170  $this->db,
171  $this->object
172  );
173  $eval = $factory->getEvaluationData();
174 
175  $eval->setFilterArray($filter_array);
176  $found_participants = $eval->getParticipants();
177 
178  $participantData = new ilTestParticipantData($this->db, $this->lng);
179  $participantData->setActiveIdsFilter($eval->getParticipantIds());
180 
182  $this->participant_access_filter->getAccessStatisticsUserFilter($this->ref_id)
183  );
184 
185  $participantData->load($this->object->getTestId());
186 
187  $counter = 1;
188  if (count($participantData->getActiveIds()) > 0) {
189  foreach ($participantData->getActiveIds() as $active_id) {
190  if (!isset($found_participants[$active_id]) || !($found_participants[$active_id] instanceof ilTestEvaluationUserData)) {
191  continue;
192  }
193 
195  $userdata = $found_participants[$active_id];
196 
197  $remove = false;
198  if ($passedonly) {
199  $mark_obj = $this->object->getMarkSchema()->getMatchingMark($userdata->getReachedPointsInPercent());
200 
201  if ($mark_obj->getPassed() == false || !$userdata->areObligationsAnswered()) {
202  $remove = true;
203  }
204  }
205  if (!$remove) {
206  // build the evaluation row
207  $evaluationrow = [];
208  if ($this->object->getAnonymity()) {
209  $evaluationrow['name'] = $counter;
210  $evaluationrow['login'] = '';
211  } else {
212  $evaluationrow['name'] = $userdata->getName();
213  if (strlen($userdata->getLogin())) {
214  $evaluationrow['login'] = "[" . $userdata->getLogin() . "]";
215  } else {
216  $evaluationrow['login'] = '';
217  }
218  }
219 
220  $evaluationrow['reached'] = $userdata->getReached();
221  $evaluationrow['max'] = $userdata->getMaxpoints();
222  $evaluationrow['hint_count'] = $userdata->getRequestedHintsCountFromScoredPass();
223  $evaluationrow['exam_id'] = $userdata->getExamIdFromScoredPass();
224  $percentage = $userdata->getReachedPointsInPercent();
225  $mark = $this->object->getMarkSchema()->getMatchingMark($percentage);
226  if (is_object($mark)) {
227  $evaluationrow['mark'] = $mark->getShortName();
228  }
229  $evaluationrow['answered'] = $userdata->getQuestionsWorkedThroughInPercent();
230  $evaluationrow['questions_worked_through'] = $userdata->getQuestionsWorkedThrough();
231  $evaluationrow['number_of_questions'] = $userdata->getNumberOfQuestions();
232  $time_seconds = $userdata->getTimeOfWork();
233  $time_hours = floor($time_seconds / 3600);
234  $time_seconds -= $time_hours * 3600;
235  $time_minutes = floor($time_seconds / 60);
236  $time_seconds -= $time_minutes * 60;
237  $evaluationrow['working_time'] = sprintf("%02d:%02d:%02d", $time_hours, $time_minutes, $time_seconds);
238  $this->ctrl->setParameter($this, "active_id", $active_id);
239  $href = $this->ctrl->getLinkTarget($this, "detailedEvaluation");
240  $detailed_evaluation = $this->lng->txt("detailed_evaluation_show");
241  $evaluationrow['details'] = "<a class=\"il_ContainerItemCommand\" href=\"$href\">$detailed_evaluation</a>";
242  $userfields = [];
243  if ($userdata->getUserID() !== null) {
244  $userfields = ilObjUser::_lookupFields($userdata->getUserID());
245  }
246  $evaluationrow['email'] = $userfields['email'] ?? '';
247  $evaluationrow['institution'] = $userfields['institution'] ?? '';
248  $evaluationrow['street'] = $userfields['street'] ?? '';
249  $evaluationrow['city'] = $userfields['city'] ?? '';
250  $evaluationrow['zipcode'] = $userfields['zipcode'] ?? '';
251  $evaluationrow['country'] = $userfields['country'] ?? '';
252  $evaluationrow['sel_country'] = $userfields['sel_country'] ?? '';
253  $evaluationrow['department'] = $userfields['department'] ?? '';
254  $evaluationrow['matriculation'] = $userfields['matriculation'] ?? '';
255  $counter++;
256  $data[] = $evaluationrow;
257  }
258  }
259  }
260 
261  $table_gui->setData($data);
262  if (count($participantData->getActiveIds()) > 0) {
263  $ilToolbar->setFormName('form_output_eval');
264  $ilToolbar->setFormAction($this->ctrl->getFormAction($this, 'exportEvaluation'));
265  $export_type = new ilSelectInputGUI($this->lng->txt('exp_eval_data'), 'export_type');
266  if ($this->getObject() && $this->getObject()->getQuestionSetType() !== ilObjTest::QUESTION_SET_TYPE_RANDOM) {
267  $options = array(
268  $this->ui_factory->button()->shy($this->lng->txt('exp_grammar_as') . ' ' . $this->lng->txt('exp_type_excel') . ' (' . $this->lng->txt('exp_scored_test_run') . ')', $this->ctrl->getLinkTarget($this, 'excel_scored_test_run')),
269  $this->ui_factory->button()->shy($this->lng->txt('exp_grammar_as') . ' ' . $this->lng->txt('exp_type_excel') . ' (' . $this->lng->txt('exp_all_test_runs') . ')', $this->ctrl->getLinkTarget($this, 'excel_all_test_runs')),
270  $this->ui_factory->button()->shy($this->lng->txt('exp_grammar_as') . ' ' . $this->lng->txt('exp_type_spss'), $this->ctrl->getLinkTarget($this, 'csv'))
271  );
272  } else {
273  $options = array(
274  $this->ui_factory->button()->shy($this->lng->txt('exp_grammar_as') . ' ' . $this->lng->txt('exp_type_excel') . ' (' . $this->lng->txt('exp_all_test_runs') . ')', $this->ctrl->getLinkTarget($this, 'excel_all_test_runs')),
275  $this->ui_factory->button()->shy($this->lng->txt('exp_grammar_as') . ' ' . $this->lng->txt('exp_type_spss'), $this->ctrl->getLinkTarget($this, 'csv'))
276  );
277  }
278 
279  if (!$this->object->getAnonymity()) {
280  try {
281  $globalCertificatePrerequisites = new ilCertificateActiveValidator();
282  if ($globalCertificatePrerequisites->validate()) {
283  $options[] = $this->ui_factory->button()->shy($this->lng->txt('exp_grammar_as') . ' ' . $this->lng->txt('exp_type_certificate'), $this->ctrl->getLinkTarget($this, 'exportCertificateArchive'));
284  }
285  } catch (ilException $e) {
286  }
287  }
288 
289  $select = $this->ui_factory->dropdown()->standard($options)->withLabel($this->lng->txt('exp_eval_data'));
290  $ilToolbar->addComponent($select);
291  }
292 
293  $this->setCss();
294 
295  $this->tpl->setContent($this->ui_renderer->render(
296  array_filter(array_merge(
297  $prior_components ?? [],
298  [$this->ui_factory->legacy($table_gui->getHTML())]
299  ))
300  ));
301  }
302 
303  public function detailedEvaluation()
304  {
305  if (!$this->getTestAccess()->checkStatisticsAccess()) {
306  ilObjTestGUI::accessViolationRedirect();
307  }
308 
309  $this->tabs->activateTab(ilTestTabsManager::TAB_ID_STATISTICS);
310 
311  $active_id = $this->testrequest->int('active_id');
312 
313  if (!$this->getTestAccess()->checkResultsAccessForActiveId($active_id)) {
314  ilObjTestGUI::accessViolationRedirect();
315  }
316 
317  if ($active_id === null) {
318  $this->tpl->setOnScreenMessage('info', $this->lng->txt('detailed_evaluation_missing_active_id'), true);
319  $this->ctrl->redirect($this, 'outEvaluation');
320  }
321 
322  $this->tpl->addCss(ilUtil::getStyleSheetLocation('output', 'test_print.css', 'Modules/Test'), 'print');
323 
324  $backBtn = $this->ui_factory->button()->standard($this->lng->txt('back'), $this->ctrl->getLinkTarget($this, 'outEvaluation'));
325  $this->toolbar->addComponent($backBtn);
326 
327  $this->object->setAccessFilteredParticipantList(
328  $this->object->buildStatisticsAccessFilteredParticipantList()
329  );
330 
331  $data = $this->object->getCompleteEvaluationData();
332 
333  $form = new ilPropertyFormGUI();
334  $form->setTitle(sprintf(
335  $this->lng->txt('detailed_evaluation_for'),
336  $data->getParticipant($active_id)->getName()
337  ));
338 
339  $resultPoints = new ilNonEditableValueGUI($this->lng->txt('tst_stat_result_resultspoints'));
340  $resultPoints->setValue($data->getParticipant($active_id)->getReached() . " " . strtolower($this->lng->txt("of")) . " " . $data->getParticipant($active_id)->getMaxpoints() . " (" . sprintf("%2.2f", $data->getParticipant($active_id)->getReachedPointsInPercent()) . " %" . ")");
341  $form->addItem($resultPoints);
342 
343  if (strlen($data->getParticipant($active_id)->getMark())) {
344  $resultMarks = new ilNonEditableValueGUI($this->lng->txt('tst_stat_result_resultsmarks'));
345  $resultMarks->setValue($data->getParticipant($active_id)->getMark());
346  $form->addItem($resultMarks);
347  }
348 
349  if ($this->object->isOfferingQuestionHintsEnabled()) {
350  $requestHints = new ilNonEditableValueGUI($this->lng->txt('tst_question_hints_requested_hint_count_header'));
351  $requestHints->setValue($data->getParticipant($active_id)->getRequestedHintsCountFromScoredPass());
352  $form->addItem($requestHints);
353  }
354 
355  $time_seconds = $data->getParticipant($active_id)->getTimeOfWork();
356  $atime_seconds = $data->getParticipant($active_id)->getNumberOfQuestions() ? $time_seconds / $data->getParticipant($active_id)->getNumberOfQuestions() : 0;
357  $time_hours = floor($time_seconds / 3600);
358  $time_seconds -= $time_hours * 3600;
359  $time_minutes = floor($time_seconds / 60);
360  $time_seconds -= $time_minutes * 60;
361  $timeOfWork = new ilNonEditableValueGUI($this->lng->txt('tst_stat_result_timeofwork'));
362  $timeOfWork->setValue(sprintf("%02d:%02d:%02d", $time_hours, $time_minutes, $time_seconds));
363  $form->addItem($timeOfWork);
364 
365  $this->tpl->setVariable("TXT_ATIMEOFWORK", $this->lng->txt(""));
366  $time_hours = floor($atime_seconds / 3600);
367  $atime_seconds -= $time_hours * 3600;
368  $time_minutes = floor($atime_seconds / 60);
369  $atime_seconds -= $time_minutes * 60;
370  $avgTimeOfWork = new ilNonEditableValueGUI($this->lng->txt('tst_stat_result_atimeofwork'));
371  $avgTimeOfWork->setValue(sprintf("%02d:%02d:%02d", $time_hours, $time_minutes, $atime_seconds));
372  $form->addItem($avgTimeOfWork);
373 
374  $firstVisit = new ilNonEditableValueGUI($this->lng->txt('tst_stat_result_firstvisit'));
375  $firstVisit->setValue(ilDatePresentation::formatDate(new ilDateTime($data->getParticipant($active_id)->getFirstVisit(), IL_CAL_UNIX)));
376  $form->addItem($firstVisit);
377 
378  $lastVisit = new ilNonEditableValueGUI($this->lng->txt('tst_stat_result_lastvisit'));
379  $lastVisit->setValue(ilDatePresentation::formatDate(new ilDateTime($data->getParticipant($active_id)->getLastVisit(), IL_CAL_UNIX)));
380  $form->addItem($lastVisit);
381 
382  $nrPasses = new ilNonEditableValueGUI($this->lng->txt('tst_nr_of_passes'));
383  $nrPasses->setValue($data->getParticipant($active_id)->getLastPass() + 1);
384  $form->addItem($nrPasses);
385 
386  $scoredPass = new ilNonEditableValueGUI($this->lng->txt('scored_pass'));
387  if ($this->object->getPassScoring() == SCORE_BEST_PASS) {
388  $scoredPass->setValue($data->getParticipant($active_id)->getBestPass() + 1);
389  } else {
390  $scoredPass->setValue($data->getParticipant($active_id)->getLastPass() + 1);
391  }
392  $form->addItem($scoredPass);
393 
394  $median = $data->getStatistics()->getStatistics()->median();
395  $pct = $data->getParticipant($active_id)->getMaxpoints() ? ($median / $data->getParticipant($active_id)->getMaxpoints()) * 100.0 : 0;
396  $mark = $this->object->getMarkSchema()->getMatchingMark($pct);
397  if (is_object($mark)) {
398  $markMedian = new ilNonEditableValueGUI($this->lng->txt('tst_stat_result_mark_median'));
399  $markMedian->setValue($mark->getShortName());
400  $form->addItem($markMedian);
401  }
402 
403  $rankParticipant = new ilNonEditableValueGUI($this->lng->txt('tst_stat_result_rank_participant'));
404  $rankParticipant->setValue($data->getStatistics()->getStatistics()->rank($data->getParticipant($active_id)->getReached()));
405  $form->addItem($rankParticipant);
406 
407  $rankMedian = new ilNonEditableValueGUI($this->lng->txt('tst_stat_result_rank_median'));
408  $rankMedian->setValue($data->getStatistics()->getStatistics()->rank_median());
409  $form->addItem($rankMedian);
410 
411  $totalParticipants = new ilNonEditableValueGUI($this->lng->txt('tst_stat_result_total_participants'));
412  $totalParticipants->setValue($data->getStatistics()->getStatistics()->count());
413  $form->addItem($totalParticipants);
414 
415  $medianField = new ilNonEditableValueGUI($this->lng->txt('tst_stat_result_median'));
416  $medianField->setValue($median);
417  $form->addItem($medianField);
418 
419  $this->tpl->setContent($form->getHTML());
420 
421  $tables = [];
422 
423  for ($pass = 0; $pass <= $data->getParticipant($active_id)->getLastPass(); $pass++) {
424  $finishdate = ilObjTest::lookupPassResultsUpdateTimestamp($active_id, $pass);
425  if ($finishdate > 0) {
426  if (($this->testAccess->getAccess()->checkAccess('write', '', $this->testrequest->getRefId()))) {
427  $this->ctrl->setParameter($this, 'statistics', '1');
428  $this->ctrl->setParameter($this, 'active_id', $active_id);
429  $this->ctrl->setParameter($this, 'pass', $pass);
430  } else {
431  $this->ctrl->setParameter($this, 'statistics', '');
432  $this->ctrl->setParameter($this, 'active_id', '');
433  $this->ctrl->setParameter($this, 'pass', '');
434  }
435 
436  $table = new ilTestDetailedEvaluationStatisticsTableGUI($this, 'detailedEvaluation', ($pass + 1) . '_' . $this->object->getId());
437  $table->setTitle(sprintf($this->lng->txt("tst_eval_question_points"), $pass + 1));
438  if (($this->testAccess->getAccess()->checkAccess('write', '', $this->testrequest->getRefId()))) {
439  $button_show_answer = $this->ui_renderer->render(
440  $this->ui_factory->button()->standard(
441  $this->lng->txt('tst_show_answer_sheet'),
442  $this->ctrl->getLinkTarget($this, 'outParticipantsPassDetails'),
443  ),
444  );
445  }
446 
447  $questions = $data->getParticipant($active_id)->getQuestions($pass);
448  if (!is_array($questions)) {
449  $questions = $data->getParticipant($active_id)->getQuestions(0);
450  }
451 
452  $tableData = [];
453 
454  $counter = 0;
455  foreach ((array) $questions as $question) {
456  $userDataData = array(
457  'counter' => ++$counter,
458  'id' => $question['id'],
459  'id_txt' => $this->lng->txt('question_id_short'),
460  'title' => htmlspecialchars($data->getQuestionTitle($question['id']))
461  );
462 
463  $answeredquestion = $data->getParticipant($active_id)->getPass($pass)->getAnsweredQuestionByQuestionId($question["id"]);
464  if (is_array($answeredquestion)) {
465  $percent = $answeredquestion['points'] ? $answeredquestion['reached'] / $answeredquestion['points'] * 100.0 : 0;
466  $userDataData['points'] = $answeredquestion['reached'] . ' ' . strtolower($this->lng->txt('of')) . " " . $answeredquestion['points'] . ' (' . sprintf("%.2f", $percent) . ' %)';
467  } else {
468  $userDataData['points'] = '0 ' . strtolower($this->lng->txt('of')) . ' ' . $question['points'] . ' (' . sprintf("%.2f", 0) . ' %) - ' . $this->lng->txt('question_not_answered');
469  }
470 
471  $tableData[] = $userDataData;
472  }
473  $table->setData($tableData);
474 
475  $tables[] = $table->getHTML() . ($button_show_answer ?? '');
476  }
477  }
478 
479  $this->tpl->setContent($form->getHTML() . implode('', $tables));
480  }
481 
487  {
488  $question_id = $this->testrequest->int('qid');
489  $question_content = $this->getQuestionResultForTestUsers($question_id, $this->object->getTestId());
490  $question_title = assQuestion::instantiateQuestion($question_id)->getTitleForHTMLOutput();
491  $page = $this->prepareContentForPrint($question_title, $question_content);
492  $this->sendPage($page);
493  }
494 
500  {
501  $question_object = assQuestion::instantiateQuestion((int) $this->testrequest->raw("qid"));
502  if ($question_object instanceof ilObjFileHandlingQuestionType) {
503  $question_object->deliverFileUploadZIPFile(
504  $this->ref_id,
505  $this->object->getTestId(),
506  $this->object->getTitle()
507  );
508  } else {
509  $this->ctrl->redirect($this, "singleResults");
510  }
511  }
512 
516  public function eval_a()
517  {
518  if (!$this->getTestAccess()->checkStatisticsAccess()) {
519  ilObjTestGUI::accessViolationRedirect();
520  }
521 
522  $this->tabs->activateTab(ilTestTabsManager::TAB_ID_STATISTICS);
523 
524  $this->tpl->addBlockFile("ADM_CONTENT", "adm_content", "tpl.il_as_tst_eval_anonymous_aggregation.html", "Modules/Test");
525 
526  $this->object->setAccessFilteredParticipantList(
527  $this->object->buildStatisticsAccessFilteredParticipantList()
528  );
529 
530  $eval = $this->object->getCompleteEvaluationData();
531  $data = [];
532  $found_participants = $eval->getParticipants();
533  if (count($found_participants)) {
534  $options = [
535  $this->ui_factory->button()->shy($this->lng->txt('exp_type_excel'), $this->ctrl->getLinkTarget($this, 'excel_all_test_runs_a')),
536  $this->ui_factory->button()->shy($this->lng->txt('exp_type_spss'), $this->ctrl->getLinkTarget($this, 'csv_a'))
537  ];
538 
539  $select = $this->ui_factory->dropdown()->standard($options)->withLabel($this->lng->txt('exp_eval_data'));
540  $this->toolbar->addComponent($select);
541 
542  $data[] = array(
543  'result' => $this->lng->txt("tst_eval_total_persons"),
544  'value' => count($found_participants)
545  );
546  $total_finished = $eval->getTotalFinishedParticipants();
547  $data[] = array(
548  'result' => $this->lng->txt("tst_eval_total_finished"),
549  'value' => $total_finished
550  );
551  $average_time = $this->object->evalTotalStartedAverageTime(
552  $eval->getParticipantIds()
553  );
554  $diff_seconds = $average_time;
555  $diff_hours = floor($diff_seconds / 3600);
556  $diff_seconds -= $diff_hours * 3600;
557  $diff_minutes = floor($diff_seconds / 60);
558  $diff_seconds -= $diff_minutes * 60;
559  array_push($data, array(
560  'result' => $this->lng->txt("tst_eval_total_finished_average_time"),
561  'value' => sprintf("%02d:%02d:%02d", $diff_hours, $diff_minutes, $diff_seconds)
562  ));
563  $total_passed = 0;
564  $total_passed_reached = 0;
565  $total_passed_max = 0;
566  $total_passed_time = 0;
567  foreach ($found_participants as $userdata) {
568  if ($userdata->getPassed()) {
569  $total_passed++;
570  $total_passed_reached += $userdata->getReached();
571  $total_passed_max += $userdata->getMaxpoints();
572  $total_passed_time += $userdata->getTimeOfWork();
573  }
574  }
575  $average_passed_reached = $total_passed ? $total_passed_reached / $total_passed : 0;
576  $average_passed_max = $total_passed ? $total_passed_max / $total_passed : 0;
577  $average_passed_time = $total_passed ? $total_passed_time / $total_passed : 0;
578  array_push($data, array(
579  'result' => $this->lng->txt("tst_eval_total_passed"),
580  'value' => $total_passed
581  ));
582  array_push($data, array(
583  'result' => $this->lng->txt("tst_eval_total_passed_average_points"),
584  'value' => sprintf("%2.2f", $average_passed_reached) . " " . strtolower($this->lng->txt("of")) . " " . sprintf("%2.2f", $average_passed_max)
585  ));
586  $average_time = $average_passed_time;
587  $diff_seconds = $average_time;
588  $diff_hours = floor($diff_seconds / 3600);
589  $diff_seconds -= $diff_hours * 3600;
590  $diff_minutes = floor($diff_seconds / 60);
591  $diff_seconds -= $diff_minutes * 60;
592  array_push($data, array(
593  'result' => $this->lng->txt("tst_eval_total_passed_average_time"),
594  'value' => sprintf("%02d:%02d:%02d", $diff_hours, $diff_minutes, $diff_seconds)
595  ));
596  }
597 
598  $table_gui = new ilTestAggregatedResultsTableGUI($this, 'eval_a');
599  $table_gui->setData($data);
600  $this->tpl->setVariable('AGGREGATED_RESULTS', $table_gui->getHTML());
601 
602  $rows = [];
603  $counter = 0;
604  foreach ($eval->getQuestionTitles() as $question_id => $question_title) {
605  $answered = 0;
606  $reached = 0;
607  $max = 0;
608  foreach ($found_participants as $userdata) {
609  for ($i = 0; $i <= $userdata->getLastPass(); $i++) {
610  if (is_object($userdata->getPass($i))) {
611  $question = $userdata->getPass($i)->getAnsweredQuestionByQuestionId($question_id);
612  if (is_array($question)) {
613  $answered++;
614  $reached += $question["reached"];
615  $max += $question["points"];
616  }
617  }
618  }
619  }
620  $percent = $max ? $reached / $max * 100.0 : 0;
621  $counter++;
622  $this->ctrl->setParameter($this, "qid", $question_id);
623 
624  $points_reached = ($answered ? $reached / $answered : 0);
625  $points_max = ($answered ? $max / $answered : 0);
626  array_push(
627  $rows,
628  [
629  'qid' => $question_id,
630  'title' => htmlspecialchars($question_title),
631  'points' => $points_reached,
632  'points_reached' => $points_reached,
633  'points_max' => $points_max,
634  'percentage' => (float) $percent,
635  'answers' => $answered
636  ]
637  );
638  }
639  $table_gui = new ilTestAverageReachedPointsTableGUI($this, 'eval_a');
640  $table_gui->setData($rows);
641  $this->tpl->setVariable('TBL_AVG_REACHED', $table_gui->getHTML());
642  }
643 
644  public function exportEvaluation($cmd = "")
645  {
647  if ($this->testrequest->isset("g_filterby")) {
648  $filterby = $this->testrequest->raw("g_filterby");
649  }
650 
651  $filtertext = "";
652  if ($this->testrequest->isset("g_userfilter")) {
653  $filtertext = $this->testrequest->raw("g_userfilter");
654  }
655 
656  $passedonly = false;
657  if ($this->testrequest->isset("g_passedonly")) {
658  if ($this->testrequest->raw("g_passedonly") == 1) {
659  $passedonly = true;
660  }
661  }
662 
663  if ($cmd == '') {
664  $cmd = $this->testrequest->raw("export_type");
665  }
666  switch ($cmd) {
667  case "excel_scored_test_run":
668  (new ilExcelTestExport($this->object, $filterby, $filtertext, $passedonly, true))
669  ->withResultsPage()
670  ->withUserPages()
671  ->deliver($this->object->getTitle() . '_results');
672  break;
673 
674  case "csv":
675  (new ilCSVTestExport($this->object, $filterby, $filtertext, $passedonly))
676  ->withAllResults()
677  ->deliver($this->object->getTitle() . '_results');
678  break;
679 
680  case "excel_all_test_runs":
681  (new ilExcelTestExport($this->object, $filterby, $filtertext, $passedonly, false))
682  ->withResultsPage()
683  ->withUserPages()
684  ->deliver($this->object->getTitle() . '_results');
685  break;
686  case "certificate":
687  if ($passedonly) {
688  $this->ctrl->setParameterByClass("iltestcertificategui", "g_passedonly", "1");
689  }
690  if (strlen($filtertext)) {
691  $this->ctrl->setParameterByClass("iltestcertificategui", "g_userfilter", $filtertext);
692  }
693  $this->ctrl->redirect($this, "exportCertificateArchive");
694  break;
695  }
696  }
697 
698  public function exportAggregatedResults($cmd = '')
699  {
700  switch ($cmd) {
701  case "excel_all_test_runs_a":
702  (new ilExcelTestExport($this->object, ilTestEvaluationData::FILTER_BY_NONE, '', false, true))
703  ->withAggregatedResultsPage()
704  ->deliver($this->object->getTitle() . '_aggregated');
705  break;
706  case "csv_a":
707  (new ilCSVTestExport($this->object, ilTestEvaluationData::FILTER_BY_NONE, '', false))
708  ->withAggregatedResults()
709  ->deliver($this->object->getTitle() . '_aggregated');
710  break;
711  }
712  }
713 
714  public function exportCertificateArchive(): void
715  {
716  $globalCertificatePrerequisites = new ilCertificateActiveValidator();
717  if (!$globalCertificatePrerequisites->validate()) {
718  $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->MESSAGE);
719  }
720 
721  $database = $this->db;
722  $logger = $this->logging_services->root();
723 
724  $pathFactory = new ilCertificatePathFactory();
725  $objectId = $this->object->getId();
726  $zipAction = new ilUserCertificateZip(
727  $objectId,
728  $pathFactory->create($this->object)
729  );
730 
731  $archive_dir = $zipAction->createArchiveDirectory();
732 
733  $this->object->setAccessFilteredParticipantList(
734  $this->object->buildStatisticsAccessFilteredParticipantList()
735  );
736 
737  $ilUserCertificateRepository = new ilUserCertificateRepository($database, $logger);
738  $pdfGenerator = new ilPdfGenerator($ilUserCertificateRepository);
739 
740  $total_users = $this->object->evalTotalPersonsArray();
741  if (count($total_users) === 0) {
742  $this->outEvaluation([
743  $this->ui_factory->messageBox()->info(
744  $this->lng->txt('export_cert_no_users')
745  )
746  ]);
747  return;
748  }
749 
750  $certValidator = new ilCertificateDownloadValidator();
751 
752  $num_pdfs = 0;
753  $ignored_usr_ids = [];
754  $failed_pdf_generation_usr_ids = [];
755  foreach ($total_users as $active_id => $name) {
756  $user_id = ilObjTest::_getUserIdFromActiveId($active_id);
757 
758  if (!$certValidator->isCertificateDownloadable($user_id, $objectId)) {
759  $this->logging_services->root()->debug(
760  sprintf(
761  'No certificate available for user %s in test %s ' .
762  '(Check if: ilServer is enabled / Certificates are enabled globally / ' .
763  'A Certificate is issued for the user)',
764  $user_id,
765  $objectId
766  )
767  );
768  $ignored_usr_ids[] = $user_id;
769  continue;
770  }
771 
772  $pdfAction = new ilCertificatePdfAction(
773  $pdfGenerator,
775  $this->lng->txt('error_creating_certificate_pdf')
776  );
777 
778  $pdf = $pdfAction->createPDF($user_id, $objectId);
779  if ($pdf !== '') {
780  $zipAction->addPDFtoArchiveDirectory($pdf, $archive_dir, $user_id . "_" . str_replace(
781  " ",
782  "_",
784  ) . ".pdf");
785  ++$num_pdfs;
786  } else {
787  $this->logging_services->root()->error(
788  sprintf(
789  'The certificate service could not create a PDF for user %s and test %s',
790  $user_id,
791  $objectId
792  )
793  );
794  $failed_pdf_generation_usr_ids[] = $user_id;
795  }
796  }
797 
798  $components = [];
799 
800  if ($num_pdfs > 0) {
801  try {
802  $zipAction->zipCertificatesInArchiveDirectory($archive_dir, true);
803  } catch (\ILIAS\Filesystem\Exception\IOException $e) {
804  $this->logging_services->root()->error($e->getMessage());
805  $this->logging_services->root()->error($e->getTraceAsString());
806  $components[] = $this->ui_factory->messageBox()->failure(
807  $this->lng->txt('error_creating_certificate_zip_empty')
808  );
809  }
810  }
811 
812  if ($ignored_usr_ids !== []) {
813  $user_logins = array_map(
814  static fn($usr_id): string => ilObjUser::_lookupLogin((int) $usr_id),
815  $ignored_usr_ids
816  );
817  if (count($ignored_usr_ids) === 1) {
818  $components[] = $this->ui_factory->messageBox()->info(sprintf(
819  $this->lng->txt('export_cert_ignored_for_users_s'),
820  implode(', ', $user_logins)
821  ));
822  } else {
823  $components[] = $this->ui_factory->messageBox()->info(sprintf(
824  $this->lng->txt('export_cert_ignored_for_users_p'),
825  count($ignored_usr_ids),
826  implode(', ', $user_logins)
827  ));
828  }
829  }
830 
831  if ($failed_pdf_generation_usr_ids !== []) {
832  $user_logins = array_map(
833  static fn($usr_id): string => ilObjUser::_lookupLogin((int) $usr_id),
834  $failed_pdf_generation_usr_ids
835  );
836  if (count($failed_pdf_generation_usr_ids) === 1) {
837  $components[] = $this->ui_factory->messageBox()->info(sprintf(
838  $this->lng->txt('export_cert_failed_for_users_s'),
839  implode(', ', $user_logins)
840  ));
841  } else {
842  $components[] = $this->ui_factory->messageBox()->info(sprintf(
843  $this->lng->txt('export_cert_failed_for_users_p'),
844  count($ignored_usr_ids),
845  implode(', ', $user_logins)
846  ));
847  }
848  }
849 
850  $this->outEvaluation($components);
851  }
852 
859  public function getEvaluationQuestionId($question_id, $original_id = "")
860  {
861  if ($original_id > 0) {
862  return $original_id;
863  } else {
864  return $question_id;
865  }
866  }
867 
871  public function outParticipantsPassDetails()
872  {
873  $ilTabs = $this->tabs;
874 
875  $active_id = (int) $this->testrequest->raw("active_id");
876 
877  if (!$this->getTestAccess()->checkResultsAccessForActiveId($active_id)) {
878  ilObjTestGUI::accessViolationRedirect();
879  }
880 
881  $this->ctrl->saveParameter($this, "active_id");
882  $testSession = $this->testSessionFactory->getSession($active_id);
883 
884  // protect actives from other tests
885  if ($testSession->getTestId() != $this->object->getTestId()) {
886  ilObjTestGUI::accessViolationRedirect();
887  }
888 
889  $this->ctrl->saveParameter($this, "pass");
890  $pass = (int) $this->testrequest->raw("pass");
891 
892  if ($this->testrequest->isset('statistics') && $this->testrequest->raw('statistics') == 1) {
893  $this->ctrl->setParameterByClass("ilTestEvaluationGUI", "active_id", $active_id);
894  $this->ctrl->saveParameter($this, 'statistics');
895 
896  $ilTabs->setBackTarget(
897  $this->lng->txt('back'),
898  $this->ctrl->getLinkTargetByClass('ilTestEvaluationGUI', 'detailedEvaluation')
899  );
900  } elseif ($this->object->getNrOfTries() == 1) {
901  $ilTabs->setBackTarget(
902  $this->lng->txt('back'),
903  $this->ctrl->getLinkTargetByClass('ilParticipantsTestResultsGUI')
904  );
905  } else {
906  $ilTabs->setBackTarget(
907  $this->lng->txt('tst_results_back_overview'),
908  $this->ctrl->getLinkTarget($this, 'outParticipantsResultsOverview')
909  );
910  }
911 
912  if ($this->testrequest->isset('show_best_solutions')) {
913  ilSession::set('tst_results_show_best_solutions', true);
914  } elseif ($this->testrequest->isset('hide_best_solutions')) {
915  ilSession::set('tst_results_show_best_solutions', false);
916  } elseif (ilSession::get('tst_results_show_best_solutions') !== null) {
917  ilSession::clear('tst_results_show_best_solutions');
918  }
919 
920  $template = new ilTemplate("tpl.il_as_tst_pass_details_overview_participants.html", true, true, "Modules/Test");
921  $this->populateExamId($template, $active_id, (int) $pass);
922  $this->populatePassFinishDate($template, ilObjTest::lookupLastTestPassAccess($active_id, $pass));
923 
925  if (ilSession::get('tst_results_show_best_solutions')) {
926  $this->ctrl->setParameter($this, 'hide_best_solutions', '1');
927  $toolbar->setHideBestSolutionsLinkTarget($this->ctrl->getLinkTarget($this, 'outParticipantsPassDetails'));
928  $this->ctrl->setParameter($this, 'hide_best_solutions', '');
929  } else {
930  $this->ctrl->setParameter($this, 'show_best_solutions', '1');
931  $toolbar->setShowBestSolutionsLinkTarget($this->ctrl->getLinkTarget($this, 'outParticipantsPassDetails'));
932  $this->ctrl->setParameter($this, 'show_best_solutions', '');
933  }
934 
935  $toolbar->build();
936  $template->setVariable('RESULTS_TOOLBAR', $this->ctrl->getHTML($toolbar));
937 
938  if ($this->isGradingMessageRequired() && $this->object->getNrOfTries() == 1) {
939  $gradingMessageBuilder = $this->getGradingMessageBuilder($active_id);
940  $gradingMessageBuilder->buildList();
941 
942  $template->setCurrentBlock('grading_message');
943  $template->setVariable('GRADING_MESSAGE', $gradingMessageBuilder->getList());
944  $template->parseCurrentBlock();
945  }
946 
947  $pass_results = $this->results_factory->getPassResultsFor(
948  $this->object,
949  $active_id,
950  $pass,
951  false
952  );
953 
954  $table = $this->results_presentation_factory->getPassResultsPresentationTable(
955  $pass_results,
956  $this->buildResultsTitle($active_id, $pass)
957  );
958 
959  $this->setCss();
960  $this->tpl->setVariable(
961  "ADM_CONTENT",
962  $template->get() .
963  $table->render()
964  );
965  }
966 
967  protected function setCss(): void
968  {
969  $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print.css", "Modules/Test"), "print");
970  if ($this->object->getShowSolutionAnswersOnly()) {
971  $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print_hide_content.css", "Modules/Test"), "print");
972  }
973  $this->tpl->addCss(ilObjStyleSheet::getContentStylePath(0));
974  }
975 
977  {
978  $show_user_results = ilSession::get("show_user_results");
979 
981  if ($this->testrequest->isset('show_best_solutions')) {
982  ilSession::set('tst_results_show_best_solutions', true);
983  } elseif ($this->testrequest->isset('hide_best_solutions')) {
984  ilSession::set('tst_results_show_best_solutions', false);
985  } elseif (ilSession::get('tst_results_show_best_solutions') !== null) {
986  ilSession::clear('tst_results_show_best_solutions');
987  }
988  if (ilSession::get('tst_results_show_best_solutions')) {
989  $this->ctrl->setParameter($this, 'hide_best_solutions', '1');
990  $toolbar->setHideBestSolutionsLinkTarget($this->ctrl->getLinkTarget($this, 'multiParticipantsPassDetails'));
991  $this->ctrl->setParameter($this, 'hide_best_solutions', '');
992  } else {
993  $this->ctrl->setParameter($this, 'show_best_solutions', '1');
994  $toolbar->setShowBestSolutionsLinkTarget($this->ctrl->getLinkTarget($this, 'multiParticipantsPassDetails'));
995  $this->ctrl->setParameter($this, 'show_best_solutions', '');
996  }
997 
998  $content = [];
999  $anchors = [];
1000 
1001  foreach ($show_user_results as $selected_user) {
1002  $active_id = (int) $selected_user;
1003  $pass = ilObjTest::_getResultPass($active_id);
1004 
1005  $template = new ilTemplate("tpl.il_as_tst_pass_details_overview_participants.html", true, true, "Modules/Test");
1006  $this->populateExamId($template, $active_id, (int) $pass);
1007  $this->populatePassFinishDate($template, ilObjTest::lookupLastTestPassAccess($active_id, $pass));
1008 
1009  $pass_results = $this->results_factory->getPassResultsFor(
1010  $this->object,
1011  $active_id,
1012  $pass,
1013  false
1014  );
1015 
1016  $table = $this->results_presentation_factory->getPassResultsPresentationTable(
1017  $pass_results,
1018  $this->buildResultsTitle($active_id, $pass)
1019  );
1020 
1021  $anchor = '<a name="participant_active_' . $active_id . '"></a>';
1022  $anchors[$active_id] = ilObjUser::_lookupFullname($this->object->_getUserIdFromActiveId($active_id));
1023  $content[] = $anchor . $template->get() . $table->render();
1024  }
1025 
1026  $toolbar->setParticipantSelectorOptions($anchors);
1027  $toolbar->build();
1028  $template = new ilTemplate("tpl.il_as_tst_pass_details_overview_participants.html", true, true, "Modules/Test");
1029  $template->setVariable('RESULTS_TOOLBAR', $toolbar->getHTML());
1030  array_unshift($content, $template->get());
1031 
1032  $this->tpl->setVariable(
1033  "ADM_CONTENT",
1034  implode('', $content)
1035  );
1036 
1037  $this->tabs->setBackTarget(
1038  $this->lng->txt('back'),
1039  $this->ctrl->getLinkTargetByClass(['ilObjTestGUI', 'ilTestResultsGUI', 'ilParticipantsTestResultsGUI'])
1040  );
1041 
1042  }
1043 
1044 
1046  {
1047  $ilTabs = $this->tabs;
1048  $ilObjDataCache = $this->obj_cache;
1049 
1050  $active_id = (int) $this->testrequest->raw("active_id");
1051 
1052  if (!$this->getTestAccess()->checkResultsAccessForActiveId($active_id)) {
1053  ilObjTestGUI::accessViolationRedirect();
1054  }
1055 
1056  $testSession = $this->testSessionFactory->getSession($active_id);
1057 
1058  // protect actives from other tests
1059  if ($testSession->getTestId() != $this->object->getTestId()) {
1060  ilObjTestGUI::accessViolationRedirect();
1061  }
1062 
1063  if ($this->object->getNrOfTries() == 1) {
1064  $this->ctrl->setParameter($this, "active_id", $active_id);
1065  $this->ctrl->setParameter($this, "pass", ilObjTest::_getResultPass($active_id));
1066  $this->ctrl->redirect($this, "outParticipantsPassDetails");
1067  }
1068 
1069  $ilTabs->setBackTarget(
1070  $this->lng->txt('back'),
1071  $this->ctrl->getLinkTargetByClass(['ilObjTestGUI', 'ilTestResultsGUI', 'ilParticipantsTestResultsGUI'])
1072  );
1073 
1074  $template = new ilTemplate("tpl.il_as_tst_pass_overview_participants.html", true, true, "Modules/Test");
1075 
1077  $toolbar->build();
1078  $template->setVariable('RESULTS_TOOLBAR', $this->ctrl->getHTML($toolbar));
1079 
1080  $testResultHeaderLabelBuilder = new ilTestResultHeaderLabelBuilder($this->lng, $ilObjDataCache);
1081  if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
1082  $testResultHeaderLabelBuilder->setObjectiveOrientedContainerId($testSession->getObjectiveOrientedContainerId());
1083  $testResultHeaderLabelBuilder->setUserId($testSession->getUserId());
1084  $testResultHeaderLabelBuilder->setTestObjId($this->object->getId());
1085  $testResultHeaderLabelBuilder->setTestRefId($this->object->getRefId());
1086  $testResultHeaderLabelBuilder->initObjectiveOrientedMode();
1087  }
1088 
1089  $testPassesSelector = new ilTestPassesSelector($this->db, $this->object);
1090  $testPassesSelector->setActiveId($testSession->getActiveId());
1091  $testPassesSelector->setLastFinishedPass($testSession->getLastFinishedPass());
1092 
1093  $passOverViewTableGUI = $this->buildPassOverviewTableGUI($this);
1094  $passOverViewTableGUI->setActiveId($testSession->getActiveId());
1095  $passOverViewTableGUI->setResultPresentationEnabled(true);
1096  $passOverViewTableGUI->setPassDetailsCommand('outParticipantsPassDetails');
1097  $passOverViewTableGUI->init();
1098  $passOverViewTableGUI->setData($this->getPassOverviewTableData($testSession, $testPassesSelector->getExistingPasses(), true));
1099  $passOverViewTableGUI->setTitle($testResultHeaderLabelBuilder->getPassOverviewHeaderLabel());
1100  $template->setVariable("PASS_OVERVIEW", $passOverViewTableGUI->getHTML());
1101 
1102  if ($this->isGradingMessageRequired()) {
1103  $gradingMessageBuilder = $this->getGradingMessageBuilder($active_id);
1104  $gradingMessageBuilder->buildList();
1105 
1106  $template->setCurrentBlock('grading_message');
1107  $template->setVariable('GRADING_MESSAGE', $gradingMessageBuilder->getList());
1108  $template->parseCurrentBlock();
1109  }
1110 
1111  $user_data = $this->getAdditionalUsrDataHtmlAndPopulateWindowTitle($testSession, $active_id, true);
1112  $user_id = $this->object->_getUserIdFromActiveId($active_id);
1113 
1114  if (!$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
1115  if ($this->object->getAnonymity()) {
1116  $template->setVariable("TEXT_HEADING", $this->lng->txt("tst_result"));
1117  } else {
1118  $uname = $this->object->userLookupFullName($user_id, true);
1119  $template->setVariable("TEXT_HEADING", sprintf($this->lng->txt("tst_result_user_name"), $uname));
1120  $template->setVariable("USER_DATA", $user_data);
1121  }
1122  }
1123 
1124  $template->parseCurrentBlock();
1125 
1126  $this->setCss();
1127  $this->tpl->setVariable("ADM_CONTENT", $template->get());
1128  }
1129 
1130  public function outUserPassDetails(): void
1131  {
1132  $this->tabs->clearSubTabs();
1133  $this->tabs->setBackTarget($this->lng->txt('tst_results_back_overview'), $this->ctrl->getLinkTarget($this));
1134 
1135  $testSession = $this->testSessionFactory->getSession();
1136 
1137  if (!$this->object->getShowPassDetails()) {
1138  $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen");
1139  }
1140 
1141  $active_id = $testSession->getActiveId();
1142  $user_id = $testSession->getUserId();
1143 
1144  $this->ctrl->saveParameter($this, "pass");
1145  $pass = $this->testrequest->int("pass");
1146 
1147  $testResultHeaderLabelBuilder = new ilTestResultHeaderLabelBuilder($this->lng, $this->obj_cache);
1148 
1149  $objectivesList = null;
1150 
1151  $considerHiddenQuestions = true;
1152  $considerOptionalQuestions = true;
1153 
1154  if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
1155  $considerHiddenQuestions = false;
1156 
1157  $testSequence = $this->testSequenceFactory->getSequenceByActiveIdAndPass($active_id, $pass);
1158  $testSequence->loadFromDb();
1159  $testSequence->loadQuestions();
1160 
1161  if ($this->object->isRandomTest() && !$testSequence->isAnsweringOptionalQuestionsConfirmed()) {
1162  $considerOptionalQuestions = false;
1163  }
1164 
1165  $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($testSession);
1166 
1167  $objectivesList = $this->buildQuestionRelatedObjectivesList($objectivesAdapter, $testSequence);
1168  $objectivesList->loadObjectivesTitles();
1169 
1170  $testResultHeaderLabelBuilder->setObjectiveOrientedContainerId($testSession->getObjectiveOrientedContainerId());
1171  $testResultHeaderLabelBuilder->setUserId($testSession->getUserId());
1172  $testResultHeaderLabelBuilder->setTestObjId($this->object->getId());
1173  $testResultHeaderLabelBuilder->setTestRefId($this->object->getRefId());
1174  $testResultHeaderLabelBuilder->initObjectiveOrientedMode();
1175  }
1176 
1177  $tpl = new ilTemplate('tpl.il_as_tst_pass_details_overview_participants.html', true, true, "Modules/Test");
1178 
1180 
1181  $validator = new ilCertificateDownloadValidator();
1182  if ($validator->isCertificateDownloadable($user_id, $this->object->getId())) {
1183  $toolbar->setCertificateLinkTarget($this->ctrl->getLinkTarget($this, 'outCertificate'));
1184  }
1185 
1186  $toolbar->build();
1187 
1188  $tpl->setVariable('RESULTS_TOOLBAR', $this->ctrl->getHTML($toolbar));
1189 
1190  $tpl->setCurrentBlock('signature');
1191  $tpl->setVariable("SIGNATURE", $this->getResultsSignature());
1193 
1194  if ($this->object->isShowExamIdInTestResultsEnabled()) {
1195  if ($this->object->isShowExamIdInTestResultsEnabled()) {
1197  $testSession->getActiveId(),
1198  $pass
1199  ));
1200  $tpl->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id'));
1201  }
1202  }
1203 
1204  if (!$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired() &&
1205  $this->isGradingMessageRequired() && $this->object->getNrOfTries() == 1) {
1206  $gradingMessageBuilder = $this->getGradingMessageBuilder($active_id);
1207  $gradingMessageBuilder->buildMessage();
1208  $gradingMessageBuilder->sendMessage();
1209  }
1210 
1211  $data = $this->object->getCompleteEvaluationData();
1212  $reached = $data->getParticipant($active_id)->getPass($pass)->getReachedPoints();
1213  $max = $data->getParticipant($active_id)->getPass($pass)->getMaxPoints();
1214  $percent = $max ? $reached / $max * 100.0 : 0;
1215  $result = $data->getParticipant($active_id)->getPass($pass)->getReachedPoints() . " " . strtolower($this->lng->txt("of")) . " " . $data->getParticipant($active_id)->getPass($pass)->getMaxPoints() . " (" . sprintf("%2.2f", $percent) . " %" . ")";
1216  $tpl->setCurrentBlock('total_score');
1217  $tpl->setVariable("TOTAL_RESULT_TEXT", $this->lng->txt('tst_stat_result_resultspoints'));
1218  $tpl->setVariable("TOTAL_RESULT", $result);
1220 
1221  $tpl->setVariable("TEXT_RESULTS", $testResultHeaderLabelBuilder->getPassDetailsHeaderLabel($pass + 1));
1222  $tpl->setVariable("FORMACTION", $this->ctrl->getFormAction($this));
1223 
1224  $this->populateExamId($tpl, $active_id, (int) $pass);
1225  $this->populatePassFinishDate($tpl, ilObjTest::lookupLastTestPassAccess($active_id, $pass));
1226 
1227  $this->setCss();
1228 
1229  $pass_results = $this->results_factory->getPassResultsFor(
1230  $this->object,
1231  $active_id,
1232  $pass,
1233  true
1234  );
1235 
1236  $table = $this->results_presentation_factory->getPassResultsPresentationTable(
1237  $pass_results,
1238  $this->buildResultsTitle($active_id, $pass)
1239  );
1240 
1241  $tpl->setVariable("LIST_OF_ANSWERS", $table->render());
1242 
1243  $this->tpl->addCss(ilObjStyleSheet::getContentStylePath(0));
1244 
1245  $this->tpl->setContent(
1246  $tpl->get()
1247  );
1248  }
1249 
1250  public function outUserResultsOverview()
1251  {
1252  $testSession = $this->testSessionFactory->getSession();
1253  $active_id = $testSession->getActiveId();
1254  $user_id = $this->user->getId();
1255  $uname = $this->object->userLookupFullName($user_id, true);
1256 
1257  if (!$this->object->canShowTestResults($testSession)) {
1258  $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen");
1259  }
1260 
1261  $templatehead = new ilTemplate("tpl.il_as_tst_results_participants.html", true, true, "Modules/Test");
1262  $template = new ilTemplate("tpl.il_as_tst_results_participant.html", true, true, "Modules/Test");
1263 
1265 
1266  $validator = new ilCertificateDownloadValidator();
1267  if ($validator->isCertificateDownloadable($user_id, $this->object->getId())) {
1268  $toolbar->setCertificateLinkTarget($this->ctrl->getLinkTarget($this, 'outCertificate'));
1269  }
1270 
1271  $toolbar->build();
1272 
1273  $templatehead->setVariable('RESULTS_TOOLBAR', $this->ctrl->getHTML($toolbar));
1274 
1275  $passDetailsEnabled = $this->object->getShowPassDetails();
1276 
1277  $testResultHeaderLabelBuilder = new ilTestResultHeaderLabelBuilder($this->lng, $this->obj_cache);
1278  if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
1279  $testResultHeaderLabelBuilder->setObjectiveOrientedContainerId($testSession->getObjectiveOrientedContainerId());
1280  $testResultHeaderLabelBuilder->setUserId($testSession->getUserId());
1281  $testResultHeaderLabelBuilder->setTestObjId($this->object->getId());
1282  $testResultHeaderLabelBuilder->setTestRefId($this->object->getRefId());
1283  $testResultHeaderLabelBuilder->initObjectiveOrientedMode();
1284  }
1285 
1286  $template->setCurrentBlock("pass_overview");
1287 
1288  $testPassesSelector = new ilTestPassesSelector($this->db, $this->object);
1289  $testPassesSelector->setActiveId($testSession->getActiveId());
1290  $testPassesSelector->setLastFinishedPass($testSession->getLastFinishedPass());
1291 
1292  $passOverViewTableGUI = $this->buildPassOverviewTableGUI($this);
1293  $passOverViewTableGUI->setActiveId($testSession->getActiveId());
1294  $passOverViewTableGUI->setResultPresentationEnabled(true);
1295  if ($passDetailsEnabled) {
1296  $passOverViewTableGUI->setPassDetailsCommand('outUserPassDetails');
1297  }
1298  if ($this->object->isPassDeletionAllowed()) {
1299  $passOverViewTableGUI->setPassDeletionCommand('confirmDeletePass');
1300  }
1301  $passOverViewTableGUI->init();
1302  $passOverViewTableGUI->setData($this->getPassOverviewTableData($testSession, $testPassesSelector->getReportablePasses(), true));
1303  $passOverViewTableGUI->setTitle($testResultHeaderLabelBuilder->getPassOverviewHeaderLabel());
1304  $overview = $passOverViewTableGUI->getHTML();
1305  if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
1306  $lo_status = new ilTestLearningObjectivesStatusGUI(
1307  $this->lng,
1308  $this->ctrl,
1309  $this->ui_factory,
1310  $this->ui_renderer,
1311  $this->testrequest
1312  );
1313  $lo_status->setCrsObjId($this->getObjectiveOrientedContainer()->getObjId());
1314  $lo_status->setUsrId($testSession->getUserId());
1315  $overview .= "<br />" . $lo_status->getHTML();
1316  }
1317  $template->setVariable("PASS_OVERVIEW", $overview);
1318  $template->parseCurrentBlock();
1319 
1320  if ($this->isGradingMessageRequired()) {
1321  $gradingMessageBuilder = $this->getGradingMessageBuilder($active_id);
1322  $gradingMessageBuilder->buildMessage();
1323  $gradingMessageBuilder->sendMessage();
1324  }
1325 
1326  $user_data = $this->getAdditionalUsrDataHtmlAndPopulateWindowTitle($testSession, $active_id, true);
1327 
1328  if (!$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
1329  if ($this->object->getAnonymity()) {
1330  $template->setVariable("TEXT_HEADING", $this->lng->txt("tst_result"));
1331  } else {
1332  $template->setVariable("TEXT_HEADING", sprintf($this->lng->txt("tst_result_user_name"), $uname));
1333  $template->setVariable("USER_DATA", $user_data);
1334  }
1335  }
1336 
1337  $this->setCss();
1338  $templatehead->setVariable("RESULTS_PARTICIPANT", $template->get());
1339  $this->tpl->setContent($templatehead->get());
1340  }
1341 
1342  public function outUserListOfAnswerPasses()
1343  {
1344  if (!$this->object->getShowSolutionPrintview()) {
1345  $this->tpl->setOnScreenMessage('info', $this->lng->txt("no_permission"), true);
1346  $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen");
1347  }
1348 
1349  $template = new ilTemplate("tpl.il_as_tst_info_list_of_answers.html", true, true, "Modules/Test");
1350 
1351  $pass = null;
1352  if ($this->testrequest->isset('pass')) {
1353  $pass = $this->testrequest->int('pass');
1354  }
1355  $user_id = $this->user->getId();
1356 
1357  $testSession = $this->testSessionFactory->getSession();
1358  $active_id = $testSession->getActiveId();
1359 
1360  $template->setVariable("TEXT_RESULTS", $this->lng->txt("tst_passes"));
1361 
1362  $testPassesSelector = new ilTestPassesSelector($this->db, $this->object);
1363  $testPassesSelector->setActiveId($testSession->getActiveId());
1364  $testPassesSelector->setLastFinishedPass($testSession->getLastFinishedPass());
1365 
1366  $passOverViewTableGUI = $this->buildPassOverviewTableGUI($this);
1367  $passOverViewTableGUI->setActiveId($testSession->getActiveId());
1368  $passOverViewTableGUI->setResultPresentationEnabled(false);
1369  $passOverViewTableGUI->setPassDetailsCommand('outUserListOfAnswerPasses');
1370  $passOverViewTableGUI->init();
1371  $passOverViewTableGUI->setData($this->getPassOverviewTableData($testSession, $testPassesSelector->getClosedPasses(), false));
1372  $template->setVariable("PASS_OVERVIEW", $passOverViewTableGUI->getHTML());
1373 
1374  $signature = '';
1375  if ($pass !== null) {
1376  $testResultHeaderLabelBuilder = new ilTestResultHeaderLabelBuilder($this->lng, $this->obj_cache);
1377 
1378  $objectivesList = null;
1379 
1380  if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
1381  $testSequence = $this->testSequenceFactory->getSequenceByActiveIdAndPass($active_id, $pass);
1382  $testSequence->loadFromDb();
1383  $testSequence->loadQuestions();
1384 
1385  $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($testSession);
1386 
1387  $objectivesList = $this->buildQuestionRelatedObjectivesList($objectivesAdapter, $testSequence);
1388  $objectivesList->loadObjectivesTitles();
1389 
1390  $testResultHeaderLabelBuilder->setObjectiveOrientedContainerId($testSession->getObjectiveOrientedContainerId());
1391  $testResultHeaderLabelBuilder->setUserId($testSession->getUserId());
1392  $testResultHeaderLabelBuilder->setTestObjId($this->object->getId());
1393  $testResultHeaderLabelBuilder->setTestRefId($this->object->getRefId());
1394  $testResultHeaderLabelBuilder->initObjectiveOrientedMode();
1395  }
1396 
1397  $result_array = $this->object->getTestResult(
1398  $active_id,
1399  $pass,
1400  false,
1401  !$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()
1402  );
1403 
1404  $signature = $this->getResultsSignature();
1405  $user_id = $this->object->_getUserIdFromActiveId($active_id);
1406  $showAllAnswers = true;
1407  if ($this->object->isExecutable($testSession, $user_id)) {
1408  $showAllAnswers = false;
1409  }
1410  $this->setContextResultPresentation(false);
1411  $answers = $this->getPassListOfAnswers($result_array, $active_id, $pass, false, $showAllAnswers, false, false, false, $objectivesList, $testResultHeaderLabelBuilder);
1412  $template->setVariable("PASS_DETAILS", $answers);
1413  }
1414  $template->setVariable("FORMACTION", $this->ctrl->getFormAction($this));
1415  $template->setVariable("PRINT_TEXT", $this->lng->txt("print"));
1416  $template->setVariable("PRINT_URL", "javascript:window.print();");
1417 
1418  $user_data = $this->getAdditionalUsrDataHtmlAndPopulateWindowTitle($testSession, $active_id, true);
1419  $template->setVariable("USER_DATA", $user_data);
1420  $template->setVariable("TEXT_LIST_OF_ANSWERS", $this->lng->txt("tst_list_of_answers"));
1421  if (strlen($signature)) {
1422  $template->setVariable("SIGNATURE", $signature);
1423  }
1424  if (!is_null($pass) && $this->object->isShowExamIdInTestResultsEnabled()) {
1425  $template->setCurrentBlock('exam_id_footer');
1426  $template->setVariable('EXAM_ID_VAL', ilObjTest::lookupExamId(
1427  $testSession->getActiveId(),
1428  $pass
1429  ));
1430  $template->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id'));
1431  $template->parseCurrentBlock();
1432  }
1433 
1434  $this->setCss();
1435  $this->tpl->setVariable("ADM_CONTENT", $template->get());
1436  }
1437 
1438  public function passDetails()
1439  {
1440  // @PHP8-CR: With this probably never working and no detectable usages, it would be a candidate for removal...
1441  // Second opinion here, please, if it can go away.
1442  if ($this->testrequest->isset("pass") && (strlen($this->testrequest->raw("pass")) > 0)) {
1443  $this->ctrl->saveParameter($this, "pass");
1444  $this->ctrl->saveParameter($this, "active_id");
1445  $this->outTestResults(false, $this->testrequest->raw("pass"));
1446  } else {
1447  $this->outTestResults(false);
1448  }
1449  }
1450 
1455  public function singleResults()
1456  {
1457  if (!$this->getTestAccess()->checkStatisticsAccess()) {
1458  ilObjTestGUI::accessViolationRedirect();
1459  }
1460 
1461  $this->object->setAccessFilteredParticipantList(
1462  $this->object->buildStatisticsAccessFilteredParticipantList()
1463  );
1464 
1465  $this->tabs->activateTab(ilTestTabsManager::TAB_ID_STATISTICS);
1466 
1467  $data = $this->object->getCompleteEvaluationData();
1468  $color_class = array("tblrow1", "tblrow2");
1469  $counter = 0;
1470  $this->tpl->addBlockFile("ADM_CONTENT", "adm_content", "tpl.il_as_tst_eval_single_answers.html", "Modules/Test");
1471  $found_participants = $data->getParticipants();
1472  if ($found_participants === []) {
1473  $this->tpl->setOnScreenMessage('info', $this->lng->txt("tst_no_evaluation_data"));
1474  return;
1475  }
1476 
1477  $rows = [];
1478  foreach ($data->getQuestionTitles() as $question_id => $question_title) {
1479  $answered = 0;
1480  $reached = 0;
1481  $max = 0;
1482  foreach ($found_participants as $userdata) {
1483  $pass = $userdata->getScoredPass();
1484  if (is_object($userdata->getPass($pass))) {
1485  $question = $userdata->getPass($pass)->getAnsweredQuestionByQuestionId($question_id);
1486  if (is_array($question)) {
1487  $answered++;
1488  }
1489  }
1490  }
1491  $counter++;
1492  $this->ctrl->setParameter($this, "qid", $question_id);
1493  $question_object = assQuestion::instantiateQuestion($question_id);
1494  $download = '';
1495  if ($question_object instanceof ilObjFileHandlingQuestionType
1496  && $question_object->hasFileUploads($this->object->getTestId())) {
1497  $download = '<a href="' . $this->ctrl->getLinkTarget($this, "exportFileUploadsForAllParticipants") . '">'
1498  . $this->lng->txt('download') . '</a>';
1499  }
1500  $rows[] = [
1501  'qid' => $question_id,
1502  'question_title' => htmlspecialchars($question_title),
1503  'number_of_answers' => $answered,
1504  'output' => "<a target='_blank' href=\"" . $this->ctrl->getLinkTarget($this, "exportQuestionForAllParticipants") . "\">" . $this->lng->txt("print") . "</a>",
1505  'file_uploads' => $download
1506  ];
1507  }
1508 
1509  $table_gui = new ilResultsByQuestionTableGUI($this, 'singleResults');
1510  $table_gui->setTitle($this->lng->txt('tst_answered_questions_test'));
1511  $table_gui->setData($rows);
1512 
1513  $this->tpl->setVariable('TBL_SINGLE_ANSWERS', $table_gui->getHTML());
1514  }
1515 
1516  public function outCertificate()
1517  {
1518  $ilUserCertificateRepository = new ilUserCertificateRepository($this->db, $this->logging_services->root());
1519  $pdfGenerator = new ilPdfGenerator($ilUserCertificateRepository);
1520 
1521  $pdfAction = new ilCertificatePdfAction(
1522  $pdfGenerator,
1524  $this->lng->txt('error_creating_certificate_pdf')
1525  );
1526 
1527  $pdfAction->downloadPdf($this->user->getId(), $this->object->getId());
1528  }
1529 
1530  public function confirmDeletePass()
1531  {
1532  if ($this->testrequest->isset('context') && strlen($this->testrequest->raw('context'))) {
1533  $context = $this->testrequest->raw('context');
1534  } else {
1536  }
1537 
1538  if (!$this->object->isPassDeletionAllowed()) {
1540  }
1541 
1542  $confirm = new ilTestPassDeletionConfirmationGUI($this->ctrl, $this->lng, $this);
1543  $confirm->build((int) $this->testrequest->raw("active_id"), (int) $this->testrequest->raw("pass"), $context);
1544 
1545  $this->tpl->setContent($this->ctrl->getHTML($confirm));
1546  }
1547 
1548  public function cancelDeletePass()
1549  {
1550  $this->redirectToPassDeletionContext($_POST['context']);
1551  }
1552 
1554  {
1555  switch ($context) {
1557 
1558  $this->ctrl->redirect($this, 'outUserResultsOverview');
1559 
1560  // no break
1562 
1563  $this->ctrl->redirectByClass('ilObjTestGUI', 'infoScreen');
1564  }
1565  }
1566 
1567  public function performDeletePass()
1568  {
1569  if (isset($_POST['context']) && strlen($_POST['context'])) {
1570  $context = $_POST['context'];
1571  } else {
1573  }
1574 
1575  if (!$this->object->isPassDeletionAllowed()) {
1577  }
1578 
1579  $ilDB = $this->db;
1580 
1581  $active_fi = null;
1582  $pass = null;
1583 
1584  if (isset($_POST['active_id']) && (int) $_POST['active_id']) {
1585  $active_fi = $_POST['active_id'];
1586  }
1587 
1588  if (isset($_POST['pass']) && is_numeric($_POST['pass'])) {
1589  $pass = $_POST['pass'];
1590  }
1591 
1592  if (is_null($active_fi) || is_null($pass)) {
1593  $this->ctrl->redirect($this, 'outUserResultsOverview');
1594  }
1595 
1596  if ($pass == $this->object->_getResultPass($active_fi)) {
1597  $this->ctrl->redirect($this, 'outUserResultsOverview');
1598  }
1599 
1600  // Get information
1601  $result = $ilDB->query("
1602  SELECT tst_active.tries, tst_active.last_finished_pass, tst_sequence.pass
1603  FROM tst_active
1604  LEFT JOIN tst_sequence
1605  ON tst_sequence.active_fi = tst_active.active_id
1606  AND tst_sequence.pass = tst_active.tries
1607  WHERE tst_active.active_id = {$ilDB->quote($active_fi, 'integer')}
1608  ");
1609 
1610  $row = $ilDB->fetchAssoc($result);
1611 
1612  $tries = $row['tries'];
1613  $lastFinishedPass = is_numeric($row['last_finished_pass']) ? $row['last_finished_pass'] : -1;
1614 
1615  if ($pass < $lastFinishedPass) {
1616  $isActivePass = false;
1617  $must_renumber = true;
1618  } elseif ($pass == $lastFinishedPass) {
1619  $isActivePass = false;
1620 
1621  if ($tries == $row['pass']) {
1622  $must_renumber = true;
1623  } else {
1624  $must_renumber = false;
1625  }
1626  } elseif ($pass == $row['pass']) {
1627  $isActivePass = true;
1628  $must_renumber = false;
1629  } else {
1630  throw new ilTestException('This should not happen, please contact Bjoern Heyser to clean up this pass salad!');
1631  }
1632 
1633  if ($isActivePass) {
1634  $this->ctrl->redirect($this, 'outUserResultsOverview');
1635  }
1636 
1637  if ($pass == 0 && (
1638  ($lastFinishedPass == 0 && $tries == 1 && $tries != $row['pass'])
1639  || ($isActivePass == true) // should be equal to || ($lastFinishedPass == -1 && $tries == 0)
1640  )) {
1641  $last_pass = true;
1642  } else {
1643  $last_pass = false;
1644  }
1645 
1646  // Work on tables:
1647  // tst_active
1648  if ($last_pass) {
1649  $ilDB->manipulate(
1650  'DELETE
1651  FROM tst_active
1652  WHERE active_id = ' . $ilDB->quote($active_fi, 'integer')
1653  );
1654  } elseif (!$isActivePass) {
1655  $ilDB->manipulate(
1656  'UPDATE tst_active
1657  SET tries = ' . $ilDB->quote($tries - 1, 'integer') . ',
1658  last_finished_pass = ' . $ilDB->quote($lastFinishedPass - 1, 'integer') . '
1659  WHERE active_id = ' . $ilDB->quote($active_fi, 'integer')
1660  );
1661  }
1662  // tst_manual_fb
1663  $ilDB->manipulate(
1664  'DELETE
1665  FROM tst_manual_fb
1666  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1667  AND pass = ' . $ilDB->quote($pass, 'integer')
1668  );
1669 
1670  if ($must_renumber) {
1671  $ilDB->manipulate(
1672  'UPDATE tst_manual_fb
1673  SET pass = pass - 1
1674  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1675  AND pass > ' . $ilDB->quote($pass, 'integer')
1676  );
1677  }
1678 
1679  // tst_mark -> nothing to do
1680  //
1681  // tst_pass_result
1682  $ilDB->manipulate(
1683  'DELETE
1684  FROM tst_pass_result
1685  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1686  AND pass = ' . $ilDB->quote($pass, 'integer')
1687  );
1688 
1689  if ($must_renumber) {
1690  $ilDB->manipulate(
1691  'UPDATE tst_pass_result
1692  SET pass = pass - 1
1693  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1694  AND pass > ' . $ilDB->quote($pass, 'integer')
1695  );
1696  }
1697 
1698  $ilDB->manipulate(
1699  'DELETE
1700  FROM tst_sequence
1701  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1702  AND pass = ' . $ilDB->quote($pass, 'integer')
1703  );
1704 
1705  if ($must_renumber) {
1706  $ilDB->manipulate(
1707  'UPDATE tst_sequence
1708  SET pass = pass - 1
1709  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1710  AND pass > ' . $ilDB->quote($pass, 'integer')
1711  );
1712  }
1713 
1714  // tst_solutions
1715  $ilDB->manipulate(
1716  'DELETE
1717  FROM tst_solutions
1718  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1719  AND pass = ' . $ilDB->quote($pass, 'integer')
1720  );
1721 
1722  if ($must_renumber) {
1723  $ilDB->manipulate(
1724  'UPDATE tst_solutions
1725  SET pass = pass - 1
1726  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1727  AND pass > ' . $ilDB->quote($pass, 'integer')
1728  );
1729  }
1730 
1731  // tst_test_result
1732  $ilDB->manipulate(
1733  'DELETE
1734  FROM tst_test_result
1735  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1736  AND pass = ' . $ilDB->quote($pass, 'integer')
1737  );
1738 
1739  if ($must_renumber) {
1740  $ilDB->manipulate(
1741  'UPDATE tst_test_result
1742  SET pass = pass - 1
1743  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1744  AND pass > ' . $ilDB->quote($pass, 'integer')
1745  );
1746  }
1747 
1748  // qpl_hint_tracking
1749  $ilDB->manipulate(
1750  'DELETE
1751  FROM qpl_hint_tracking
1752  WHERE qhtr_active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1753  AND qhtr_pass = ' . $ilDB->quote($pass, 'integer')
1754  );
1755 
1756  if ($must_renumber) {
1757  $ilDB->manipulate(
1758  'UPDATE qpl_hint_tracking
1759  SET qhtr_pass = qhtr_pass - 1
1760  WHERE qhtr_active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1761  AND qhtr_pass > ' . $ilDB->quote($pass, 'integer')
1762  );
1763  }
1764 
1765  // tst_test_rnd_qst -> nothing to do
1766 
1767  // tst_times
1768  $ilDB->manipulate(
1769  'DELETE
1770  FROM tst_times
1771  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1772  AND pass = ' . $ilDB->quote($pass, 'integer')
1773  );
1774 
1775  if ($must_renumber) {
1776  $ilDB->manipulate(
1777  'UPDATE tst_times
1778  SET pass = pass - 1
1779  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
1780  AND pass > ' . $ilDB->quote($pass, 'integer')
1781  );
1782  }
1783 
1785  $this->object->logAction($this->lng->txtlng("assessment", "log_deleted_pass", ilObjAssessmentFolder::_getLogLanguage()));
1786  }
1787 
1788  $this->object->updateTestResultCache((int) $active_fi);
1789 
1791  }
1792 
1793  protected function getFilteredTestResult(int $active_id, int $pass, bool $considerHiddenQuestions, bool $considerOptionalQuestions): array
1794  {
1796  $ilDB = $this->db;
1797 
1798  $resultData = $this->object->getTestResult($active_id, $pass, false, $considerHiddenQuestions);
1799  $questionIds = [];
1800  foreach ($resultData as $resultItemKey => $resultItemValue) {
1801  if ($resultItemKey === 'test' || $resultItemKey === 'pass') {
1802  continue;
1803  }
1804 
1805  $questionIds[] = $resultItemValue['qid'];
1806  }
1807 
1808  $table_gui = $this->buildPassDetailsOverviewTableGUI($this, 'outUserPassDetails');
1809 
1810  $questionList = new ilAssQuestionList($ilDB, $this->lng, $this->refinery, $component_repository);
1811  $questionList->setParentObjId($this->object->getId());
1812  $questionList->setParentObjectType($this->object->getType());
1813  $questionList->setIncludeQuestionIdsFilter($questionIds);
1814 
1815  foreach ($table_gui->getFilterItems() as $item) {
1816  if (substr($item->getPostVar(), 0, strlen('tax_')) == 'tax_') {
1817  $v = $item->getValue();
1818 
1819  if (is_array($v) && count($v) && !(int) $v[0]) {
1820  continue;
1821  }
1822 
1823  $taxId = substr($item->getPostVar(), strlen('tax_'));
1824  $questionList->addTaxonomyFilter($taxId, $item->getValue(), $this->object->getId(), 'tst');
1825  } elseif ($item->getValue() !== false) {
1826  $questionList->addFieldFilter($item->getPostVar(), $item->getValue());
1827  }
1828  }
1829 
1830  $questionList->load();
1831 
1832  $filteredTestResult = [];
1833 
1834  foreach ($resultData as $resultItemKey => $resultItemValue) {
1835  if ($resultItemKey === 'test' || $resultItemKey === 'pass') {
1836  continue;
1837  }
1838 
1839  if (!$questionList->isInList($resultItemValue['qid'])) {
1840  continue;
1841  }
1842 
1843  $filteredTestResult[] = $resultItemValue;
1844  }
1845 
1846  return $filteredTestResult;
1847  }
1848 
1850  {
1851  $active_id = (int) $this->testrequest->raw("active_id");
1852  $access_filter = $this->participant_access_filter->getManageParticipantsUserFilter($this->ref_id);
1853 
1854  $participant_data = new ilTestParticipantData($this->db, $this->lng);
1855  $participant_data->setActiveIdsFilter([$active_id]);
1856  $participant_data->setParticipantAccessFilter($access_filter);
1857  $participant_data->load($this->object->getTestId());
1858 
1859  if (!in_array($active_id, $participant_data->getActiveIds())) {
1861  }
1862 
1863  $test_session = new ilTestSession($this->db, $this->user);
1864  $test_session->loadFromDb($active_id);
1865 
1866  if ($test_session->getLastStartedPass() === $test_session->getLastFinishedPass()) {
1867  $this->tpl->setOnScreenMessage('failure', $this->lng->txt('tst_already_submitted'), true);
1869  }
1870 
1871  if (($this->object->isEndingTimeEnabled() || $this->object->getEnableProcessingTime())
1872  && !$this->object->endingTimeReached()
1873  && !$this->object->isMaxProcessingTimeReached(
1874  $this->object->getStartingTimeOfUser($active_id),
1875  $active_id
1876  )) {
1877  $this->tpl->setOnScreenMessage('info', $this->lng->txt('finish_pass_for_user_in_processing_time'));
1878  }
1879 
1880  $cgui = new ilConfirmationGUI();
1881 
1882  $cgui->setHeaderText(sprintf(
1883  $this->lng->txt("finish_pass_for_user_confirmation"),
1884  $participant_data->getFormatedFullnameByActiveId($active_id)
1885  ));
1886 
1887  $this->ctrl->setParameter($this, 'active_id', $active_id);
1888  $cgui->setFormAction($this->ctrl->getFormAction($this, "participants"));
1889 
1890  $cgui->setCancel($this->lng->txt("cancel"), "redirectBackToParticipantsScreen");
1891  $cgui->setConfirm($this->lng->txt("proceed"), "confirmFinishTestPassForUser");
1892 
1893  $this->tpl->setContent($cgui->getHTML());
1894  }
1895 
1897  {
1898  $active_id = (int) $this->testrequest->raw("active_id");
1899  $access_filter = $this->participant_access_filter->getManageParticipantsUserFilter($this->ref_id);
1900 
1901  $participant_data = new ilTestParticipantData($this->db, $this->lng);
1902  $participant_data->setActiveIdsFilter(array($active_id));
1903  $participant_data->setParticipantAccessFilter($access_filter);
1904  $participant_data->load($this->object->getTestId());
1905 
1906  if (!in_array($active_id, $participant_data->getActiveIds())) {
1908  }
1909 
1910  $test_session = new ilTestSession($this->db, $this->user);
1911  $test_session->loadFromDb($active_id);
1912 
1913  if ($test_session->getLastStartedPass() === $test_session->getLastFinishedPass()) {
1914  $this->tpl->setOnScreenMessage('failure', $this->lng->txt('tst_already_submitted'), true);
1916  }
1917 
1918  $this->object->updateTestPassResults(
1919  $active_id,
1920  $test_session->getPass(),
1921  $this->object->areObligationsEnabled(),
1922  null,
1923  $this->object->getId()
1924  );
1925 
1926  $this->finishTestPass($active_id);
1927 
1929  }
1930 
1931  public function finishAllUserPasses()
1932  {
1933  if ($this->hasUsersWithWorkingTimeAvailable()) {
1934  $this->tpl->setOnScreenMessage(
1935  'failure',
1936  $this->lng->txt('finish_pass_for_all_users_in_processing_time'),
1937  true
1938  );
1940  }
1941 
1942  $cgui = new ilConfirmationGUI();
1943  $cgui->setFormAction($this->ctrl->getFormAction($this));
1944  $cgui->setHeaderText($this->lng->txt("finish_pass_for_all_users"));
1945  $cgui->setCancel($this->lng->txt("cancel"), "redirectBackToParticipantsScreen");
1946  $cgui->setConfirm($this->lng->txt("proceed"), "confirmFinishTestPassForAllUser");
1947  $this->tpl->setContent($cgui->getHTML());
1948  }
1949 
1950  private function hasUsersWithWorkingTimeAvailable(): bool
1951  {
1952  if (!$this->object->isEndingTimeEnabled() && !$this->object->getEnableProcessingTime()
1953  || $this->object->endingTimeReached()) {
1954  return false;
1955  }
1956 
1957  $access_filter = $this->participant_access_filter->getManageParticipantsUserFilter($this->ref_id);
1958  $participant_list = new ilTestParticipantList($this->object, $this->user, $this->lng, $this->db);
1959  $participant_list->initializeFromDbRows($this->object->getTestParticipants());
1960 
1961  foreach ($participant_list->getAccessFilteredList($access_filter) as $participant) {
1962  if ($participant->hasUnfinishedPasses()
1963  && !$this->object->isMaxProcessingTimeReached(
1964  $this->object->getStartingTimeOfUser($participant->getActiveId()),
1965  $participant->getActiveId()
1966  )) {
1967  return true;
1968  }
1969  }
1970 
1971  return false;
1972  }
1973 
1975  {
1976  $access_filter = $this->participant_access_filter->getManageParticipantsUserFilter($this->ref_id);
1977 
1978  $participant_list = new ilTestParticipantList($this->object, $this->user, $this->lng, $this->db);
1979  $participant_list->initializeFromDbRows($this->object->getTestParticipants());
1980  $filtered_participant_list = $participant_list->getAccessFilteredList($access_filter);
1981 
1982  foreach ($filtered_participant_list as $participant) {
1983  if (!$participant->hasUnfinishedPasses()) {
1984  continue;
1985  }
1986 
1987  $test_session = new ilTestSession($this->db, $this->user);
1988  $test_session->loadFromDb($participant->getActiveId());
1989 
1990  $this->object->updateTestPassResults(
1991  $participant->getActiveId(),
1992  $test_session->getPass(),
1993  $this->object->areObligationsEnabled(),
1994  null,
1995  $this->object->getId()
1996  );
1997 
1998  $this->finishTestPass($participant->getActiveId());
1999  }
2000 
2001 
2003  }
2004 
2005  protected function finishTestPass(int $active_id)
2006  {
2007  $process_locker = $this->processLockerFactory->withContextId($active_id)->getLocker();
2008 
2009  $test_pass_finisher = new ilTestPassFinishTasks(
2010  $this->testSessionFactory->getSession($active_id),
2011  $this->object,
2012  );
2013  $test_pass_finisher->performFinishTasks($process_locker);
2014  }
2015 
2017  {
2018  $this->ctrl->redirectByClass("ilTestParticipantsGUI");
2019  }
2020 
2021  public function getObject(): ?ilObjTest
2022  {
2023  return $this->object;
2024  }
2025 
2026  protected function prepareContentForPrint(string $question_title, string $question_content): string
2027  {
2028  $tpl = new ilGlobalTemplate(
2029  "tpl.question_statistics_print_view.html",
2030  true,
2031  true,
2032  "Modules/Test"
2033  );
2034 
2035  $tpl->addCss(\ilUtil::getStyleSheetLocation("filesystem"));
2038  $tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print.css", "Modules/Test"), "print");
2039 
2040  ilMathJax::getInstance()->includeMathJax($tpl);
2041 
2042  foreach ($this->global_screen->layout()->meta()->getJs()->getItemsInOrderOfDelivery() as $js) {
2043  $path = explode("?", $js->getContent());
2044  $file = $path[0];
2045  $tpl->addJavaScript($file, $js->addVersionNumber());
2046  }
2047  foreach ($this->global_screen->layout()->meta()->getOnLoadCode()->getItemsInOrderOfDelivery() as $code) {
2048  $tpl->addOnLoadCode($code->getContent());
2049  }
2050 
2051  $tpl->addOnLoadCode("il.Util.print();");
2052 
2053  $tpl->setVariable("QUESTION_TITLE", $question_title);
2054  $tpl->setVariable("QUESTION_CONTENT", $question_content);
2055  return $tpl->printToString();
2056  }
2057 
2058  protected function sendPage(string $page)
2059  {
2060  $this->http->saveResponse($this->http->response()->withBody(
2061  Streams::ofString($page)
2062  ));
2063  $this->http->sendResponse();
2064  $this->http->close();
2065  }
2066 
2067  protected function buildResultsTitle(int $active_id, int $pass): string
2068  {
2069  if ($this->object->getAnonymity()) {
2070  return sprintf(
2071  $this->lng->txt("tst_eval_results_by_pass_lo"),
2072  $pass + 1
2073  );
2074  } else {
2075  return sprintf(
2076  $this->lng->txt("tst_result_user_name_pass"),
2077  $pass + 1,
2078  ilObjUser::_lookupFullname($this->object->_getUserIdFromActiveId($active_id))
2079  );
2080  }
2081  }
2082 }
setData(array $a_data)
static get(string $a_var)
populateExamId(ilTemplate $tpl, int $active_id, int $pass)
ilTestProcessLockerFactory $processLockerFactory
ilComponentRepository $component_repository
getQuestionResultForTestUsers(int $question_id, int $test_id)
Class ilTestPassFinishTasks.
singleResults()
Creates user results for single questions.
getAdditionalUsrDataHtmlAndPopulateWindowTitle($testSession, $active_id, $overwrite_anonymity=false)
Returns the user data for a test results output.
$context
Definition: webdav.php:31
This class represents a selection list property in a property form.
special template class to simplify handling of ITX/PEAR
const QUESTION_SET_TYPE_RANDOM
static _lookupFullname(int $a_user_id)
Class ChatMainBarProvider .
static lookupPassResultsUpdateTimestamp($active_id, $pass)
ilGlobalTemplateInterface ilTemplate $tpl
sk 2023-08-01: We need this union type, even if it is wrong! To change this
TableGUI class for results by question.
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false)
get(string $part=self::DEFAULT_BLOCK)
Renders the given block and returns the html string.
setContextResultPresentation(bool $contextResultPresentation)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
printToString()
Use this method to get the finally rendered page as string.
getResultsSignature()
Returns HTML code for a signature field.
TableGUI class for evaluation of all users.
setVariable(string $variable, $value='')
Sets the given variable to the given value.
const IL_CAL_UNIX
static getStyleSheetLocation(string $mode="output", string $a_css_name="", string $a_css_location="")
get full style sheet file name (path inclusive) of current user
__construct(ilObjTest $object)
ilTestEvaluationGUI constructor
buildPassOverviewTableGUI(ilTestEvaluationGUI $target_gui)
Base Exception for all Exceptions relating to Modules/Test.
$path
Definition: ltiservices.php:32
static getASCIIFilename(string $a_filename)
getPassListOfAnswers(&$result_array, $active_id, $pass, $show_solutions=false, $only_answered_questions=false, $show_question_only=false, $show_reached_points=false, $anchorNav=false, ilTestQuestionRelatedObjectivesList $objectives_list=null, ilTestResultHeaderLabelBuilder $testResultHeaderLabelBuilder=null)
Returns the list of answers of a users test pass.
static _getUserIdFromActiveId(int $active_id)
getEvaluationQuestionId($question_id, $original_id="")
Returns the ID of a question for evaluation purposes.
static instantiateQuestion(int $question_id)
static getInstance(ilTestSession $a_test_session)
setParticipantAccessFilter(Closure $participantAccessFilter)
static http()
Fetches the global http state from ILIAS.
__construct(VocabulariesInterface $vocabularies)
outParticipantsPassDetails()
Output of the pass details of an existing test pass for the test statistics.
getFilteredTestResult(int $active_id, int $pass, bool $considerHiddenQuestions, bool $considerOptionalQuestions)
setActiveIdsFilter(array $active_ids_filter)
getGradingMessageBuilder(int $active_id)
static _getResultPass($active_id)
Retrieves the pass number that should be counted for a given user.
prepareContentForPrint(string $question_title, string $question_content)
Validates if an active certificate is stored in the database and can be downloaded by the user...
Output class for assessment test evaluation.
buildPassDetailsOverviewTableGUI(ilTestServiceGUI|ilParticipantsTestResultsGUI $target_gui, string $target_cmd)
addJavaScript(string $a_js_file, bool $a_add_version_parameter=true, int $a_batch=2)
Add a javascript file that should be included in the header.
static _lookupFields(int $a_user_id)
lookup fields (deprecated; use more specific methods instead)
ilObjectDataCache $obj_cache
addOnLoadCode(string $a_code, int $a_batch=2)
Add on load code.
static getContentStylePath(int $a_style_id, bool $add_random=true, bool $add_token=true)
get content style path static (to avoid full reading)
buildResultsTitle(int $active_id, int $pass)
setTitle(string $a_title, string $a_icon="", string $a_icon_alt="")
exportFileUploadsForAllParticipants()
Creates a ZIP file containing all file uploads for a given question in a test.
setCurrentBlock(string $part=self::DEFAULT_BLOCK)
Sets the template to the given block.
Just a wrapper class to create Unit Test for other classes.
createPDF(int $userId, int $objectId)
performFinishTasks(ilTestProcessLocker $process_locker)
ilTestParticipantData $participantData
buildQuestionRelatedObjectivesList(ilLOTestQuestionAdapter $objectives_adapter, ilTestQuestionSequence $test_sequence)
static getInstance()
Singleton: get instance for use in ILIAS requests with a config loaded from the settings.
exportQuestionForAllParticipants()
Creates a PDF representation of the answers for a given question in a test.
Service GUI class for tests.
parseCurrentBlock(string $block_name=self::DEFAULT_BLOCK)
Parses the given block.
populatePassFinishDate(ilTemplate $tpl, ?int $pass_finish_date)
static clear(string $a_var)
static set(string $a_var, $a_val)
Set a value.
eval_a()
Output of anonymous aggregated results for the test.
const SCORE_BEST_PASS
getPassOverviewTableData(ilTestSession $testSession, $passes, $withResults)
This method uses the data of a given test pass to create an evaluation for displaying into a table us...
addCss(string $a_css_file, string $media="screen")
Add a css file that should be included in the header.
static lookupExamId($active_id, $pass)
downloadPdf(int $userId, int $objectId)
static _lookupLogin(int $a_user_id)