ILIAS  trunk Revision v11.0_alpha-1731-gff9cd7e2bd3
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
class.ilTestEvaluationGUI.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
21 use ILIAS\Test\Results\Presentation\TitlesBuilder as ResultsTitlesBuilder;
23 use ILIAS\UI\Component\ViewControl\Mode as ViewControlMode;
27 
47 {
48  private const DEFAULT_CMD = 'outUserListOfAnswerPasses';
51 
52  public function __construct(ilObjTest $object)
53  {
54  parent::__construct($object);
55  $this->participant_access_filter = new ilTestParticipantAccessFilterFactory($this->access);
56 
57  $this->processLockerFactory = new ilTestProcessLockerFactory(
58  new ilSetting('assessment'),
59  $this->db
60  );
61  }
62 
63  public function getTestAccess(): ilTestAccess
64  {
65  return $this->testAccess;
66  }
67 
68  public function setTestAccess($testAccess): void
69  {
70  $this->testAccess = $testAccess;
71  }
72 
73  public function executeCommand()
74  {
75  $cmd = $this->ctrl->getCmd(self::DEFAULT_CMD);
76  $next_class = $this->ctrl->getNextClass($this);
77  $this->ctrl->saveParameter($this, 'sequence');
78  $this->ctrl->saveParameter($this, 'active_id');
79 
80  switch ($next_class) {
81  case 'iltestpassdetailsoverviewtablegui':
82  $tableGUI = new ilTestPassDetailsOverviewTableGUI($this->ctrl, $this, 'outUserPassDetails');
83  $this->ctrl->forwardCommand($tableGUI);
84  break;
85 
86  default:
87  if (in_array($cmd, ['excel_scored_test_run', 'excel_all_test_runs'])) {
88  $ret = $this->exportEvaluation($cmd);
89  } elseif (in_array($cmd, ['excel_all_test_runs_a'])) {
90  $ret = $this->exportAggregatedResults($cmd);
91  } else {
92  $ret = $this->$cmd();
93  }
94  break;
95  }
96  return $ret;
97  }
98 
105  public function getEvaluationQuestionId($question_id, $original_id = '')
106  {
107  if ($original_id > 0) {
108  return $original_id;
109  } else {
110  return $question_id;
111  }
112  }
113 
114  protected function setCss(): void
115  {
116  $this->tpl->addCss(ilUtil::getStyleSheetLocation('output', 'test_print.css'), 'print');
117  if ($this->object->getShowSolutionAnswersOnly()) {
118  $this->tpl->addCss(ilUtil::getStyleSheetLocation('output', 'test_print_hide_content.css'), 'print');
119  }
120  $this->tpl->addCss(ilObjStyleSheet::getContentStylePath(0));
121  }
122 
123  public function printResults(): void
124  {
125  $this->ctrl->saveParameterByClass(self::class, 'active_ids');
126  $this->global_screen->tool()->context()->current()->addAdditionalData(
127  PrintLayoutProvider::TEST_CONTEXT_PRINT,
128  true
129  );
130 
131  $selected_active_ids = explode(',', $this->testrequest->strVal('active_ids'));
132  $results_panel = $this->ui_factory->panel()->report(
133  $this->lng->txt('tst_results'),
134  array_map(
135  function (string $v): SubPanel {
136  $value = (int) $v;
137  $attempt_id = ilObjTest::_getResultPass($value);
138  $components = $this->buildAttemptComponents($value, $attempt_id, false, true);
139  return $this->ui_factory->panel()->sub(
140  $this->buildResultsTitle(
141  ilObjUser::_lookupFullname($this->object->_getUserIdFromActiveId($value)),
142  $attempt_id
143  ),
145  );
146  },
147  $selected_active_ids
148  )
149  );
150 
151  $this->tpl->setVariable(
152  'ADM_CONTENT',
153  $this->ui_renderer->render([
154  $results_panel,
155  $this->ui_factory->legacy()->content('')->withAdditionalOnLoadCode(
156  fn(string $id): string => 'setTimeout(() => {window.print();}, 50)'
157  )
158  ])
159  );
160  }
161 
162  public function showResults(): void
163  {
164  $this->ctrl->saveParameterByClass(self::class, 'active_ids');
165  $selected_active_ids = explode(',', $this->testrequest->strVal('active_ids'));
166 
169 
170  $current_active_id = (int) $selected_active_ids[0];
171  if (count($selected_active_ids) > 1
172  && ($selected_active_id = $this->testrequest->getActiveId()) > 0
173  && array_search($selected_active_id, $selected_active_ids) !== false) {
174  $current_active_id = $selected_active_id;
175  }
176 
177  if ($this->testrequest->isset('attempt')) {
178  $attempt_id = $this->testrequest->int('attempt');
179  } else {
180  $attempt_id = ilObjTest::_getResultPass($current_active_id);
181  }
182 
183  $results_panel = $this->ui_factory->panel()->report(
184  $this->buildResultsTitle(
185  ilObjUser::_lookupFullname($this->object->_getUserIdFromActiveId($current_active_id)),
186  $attempt_id
187  ),
188  $this->buildAttemptComponents($current_active_id, $attempt_id, true, false)
189  );
190 
191  $attempts_ids_array = $this->results_data_factory->getAttemptIdsArrayFor(
192  $this->object,
193  $current_active_id
194  );
195 
196  if (count($attempts_ids_array) > 1) {
197  $results_panel = $results_panel->withViewControls([
199  $attempts_ids_array,
200  ++$attempt_id
201  )
202  ]);
203  }
204 
205  if (count($selected_active_ids) > 1) {
206  $this->addParticipantSelectorToToolbar($selected_active_ids, $current_active_id);
207  }
208 
209  $this->tpl->setVariable(
210  'ADM_CONTENT',
211  $this->ui_renderer->render($results_panel)
212  );
213 
214  $this->tabs->setBackTarget(
215  $this->lng->txt('back'),
216  $this->ctrl->getLinkTargetByClass(['ilTestParticipantsGUI'])
217  );
218  }
219 
220  public function outUserPassDetails(): void
221  {
222  $this->tabs->clearSubTabs();
223  $this->tabs->setBackTarget($this->lng->txt('tst_results_back_overview'), $this->ctrl->getLinkTarget($this));
224 
225  $test_session = $this->test_session_factory->getSession();
226 
227  if (!$this->object->getShowPassDetails()) {
228  $this->ctrl->redirectByClass([ilRepositoryGUI::class, ilObjTestGUI::class, ilInfoScreenGUI::class]);
229  }
230 
231  $active_id = $test_session->getActiveId();
232  $user_id = $test_session->getUserId();
233 
234  $this->ctrl->saveParameter($this, 'pass');
235  $pass = $this->testrequest->int('pass');
236 
237  $test_result_header_label_builder = new ResultsTitlesBuilder($this->lng, $this->obj_cache);
238 
239  $objectives_list = null;
240 
241  $consider_hidden_questions = true;
242  $consider_optional_questions = true;
243 
244  if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
245  $consider_hidden_questions = false;
246  $consider_optional_questions = true;
247 
248  $test_sequence = $this->test_sequence_factory->getSequenceByActiveIdAndPass($active_id, $pass);
249  $test_sequence->loadFromDb();
250  $test_sequence->loadQuestions();
251 
252  if ($this->object->isRandomTest() && !$test_sequence->isAnsweringOptionalQuestionsConfirmed()) {
253  $consider_optional_questions = false;
254  }
255 
256  $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($test_session);
257 
258  $objectives_list = $this->buildQuestionRelatedObjectivesList($objectivesAdapter, $test_sequence);
259  $objectives_list->loadObjectivesTitles();
260 
261  $test_result_header_label_builder->setObjectiveOrientedContainerId($test_session->getObjectiveOrientedContainerId());
262  $test_result_header_label_builder->setUserId($test_session->getUserId());
263  $test_result_header_label_builder->setTestObjId($this->object->getId());
264  $test_result_header_label_builder->setTestRefId($this->object->getRefId());
265  $test_result_header_label_builder->initObjectiveOrientedMode();
266  }
267 
268  $tpl = new ilTemplate('tpl.il_as_tst_pass_details_overview_participants.html', true, true, 'components/ILIAS/Test');
269 
270  $this->addPrintButtonToToolbar();
271 
272  if ((new ilCertificateDownloadValidator())->isCertificateDownloadable($user_id, $this->object->getId())) {
274  }
275 
276  $tpl->setCurrentBlock('signature');
277  $tpl->setVariable('SIGNATURE', $this->getResultsSignature());
279 
280  if ($this->object->isShowExamIdInTestResultsEnabled()) {
281  if ($this->object->isShowExamIdInTestResultsEnabled()) {
283  $test_session->getActiveId(),
284  $pass
285  ));
286  $tpl->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id'));
287  }
288  }
289 
290  if (!$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired() &&
291  $this->isGradingMessageRequired() && $this->object->getNrOfTries() == 1) {
292  $grading_message_builder = $this->getGradingMessageBuilder($active_id);
293  $grading_message_builder->buildMessage();
294  $grading_message_builder->sendMessage();
295  }
296 
297  $data = $this->object->getCompleteEvaluationData();
298  $reached = $data->getParticipant($active_id)->getPass($pass)->getReachedPoints();
299  $max = $data->getParticipant($active_id)->getPass($pass)->getMaxPoints();
300  $percent = $max ? $reached / $max * 100.0 : 0;
301  $result = $data->getParticipant($active_id)->getPass($pass)->getReachedPoints() . ' ' . strtolower($this->lng->txt('of')) . ' ' . $data->getParticipant($active_id)->getPass($pass)->getMaxPoints() . ' (' . sprintf('%2.2f', $percent) . ' %' . ')';
302  $tpl->setCurrentBlock('total_score');
303  $tpl->setVariable('TOTAL_RESULT_TEXT', $this->lng->txt('tst_stat_result_resultspoints'));
304  $tpl->setVariable('TOTAL_RESULT', $result);
306 
307  $tpl->setVariable('TEXT_RESULTS', $test_result_header_label_builder->getPassDetailsHeaderLabel($pass + 1));
308  $tpl->setVariable('FORMACTION', $this->ctrl->getFormAction($this));
309 
310  $this->populateExamId($tpl, $active_id, (int) $pass);
311  $this->populatePassFinishDate($tpl, ilObjTest::lookupLastTestPassAccess($active_id, $pass));
312 
313  $this->setCss();
314 
315  $settings = $this->results_presentation_factory->getAttemptResultsSettings(
316  $this->object,
317  true
318  );
319  $table = $this->results_presentation_factory->getAttemptResultsPresentationTable(
320  $this->results_data_factory->getAttemptResultsFor(
321  $settings,
322  $this->object,
323  $active_id,
324  $pass,
325  true
326  ),
327  $settings,
328  $this->buildResultsTitle($this->user->getFullname(), $pass),
329  false
330  );
331 
332  $tpl->setVariable('LIST_OF_ANSWERS', $table->render());
333 
334  $this->tpl->addCss(ilObjStyleSheet::getContentStylePath(0));
335 
336  $this->tpl->setContent(
337  $tpl->get()
338  );
339  }
340 
341  public function outUserResultsOverview()
342  {
343  $test_session = $this->test_session_factory->getSession();
344  $active_id = $test_session->getActiveId();
345  $user_id = $this->user->getId();
346  $uname = $this->object->userLookupFullName($user_id, true);
347 
348  if (!$this->object->canShowTestResults($test_session)) {
349  $this->ctrl->redirectByClass([ilRepositoryGUI::class, ilObjTestGUI::class, ilInfoScreenGUI::class]);
350  }
351 
352  $templatehead = new ilTemplate('tpl.il_as_tst_results_participants.html', true, true, 'components/ILIAS/Test');
353  $template = new ilTemplate('tpl.il_as_tst_results_participant.html', true, true, 'components/ILIAS/Test');
354 
355  $this->addPrintButtonToToolbar();
356 
357  if ((new ilCertificateDownloadValidator())->isCertificateDownloadable($user_id, $this->object->getId())) {
359  }
360 
361  $test_result_header_label_builder = new ResultsTitlesBuilder($this->lng, $this->obj_cache);
362  if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
363  $test_result_header_label_builder->setObjectiveOrientedContainerId($test_session->getObjectiveOrientedContainerId());
364  $test_result_header_label_builder->setUserId($test_session->getUserId());
365  $test_result_header_label_builder->setTestObjId($this->object->getId());
366  $test_result_header_label_builder->setTestRefId($this->object->getRefId());
367  $test_result_header_label_builder->initObjectiveOrientedMode();
368  }
369 
370  $template->setCurrentBlock('pass_overview');
371 
372  $test_passes_selector = new ilTestPassesSelector($this->db, $this->object);
373  $test_passes_selector->setActiveId($test_session->getActiveId());
374  $test_passes_selector->setLastFinishedPass($test_session->getLastFinishedPass());
375 
376  $pass_overview_table_gui = $this->buildPassOverviewTableGUI($this);
377  $pass_overview_table_gui->setActiveId($test_session->getActiveId());
378  $pass_overview_table_gui->setResultPresentationEnabled(true);
379  if ($this->object->getShowPassDetails()) {
380  $pass_overview_table_gui->setPassDetailsCommand('outUserPassDetails');
381  }
382  if ($this->object->isPassDeletionAllowed()) {
383  $pass_overview_table_gui->setPassDeletionCommand('confirmDeletePass');
384  }
385  $pass_overview_table_gui->init();
386  $pass_overview_table_gui->setData($this->getPassOverviewTableData($test_session, $test_passes_selector->getReportablePasses(), true));
387  $pass_overview_table_gui->setTitle($test_result_header_label_builder->getPassOverviewHeaderLabel());
388  $overview = $pass_overview_table_gui->getHTML();
389  if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
390  $lo_status = new ilTestLearningObjectivesStatusGUI(
391  $this->lng,
392  $this->ctrl,
393  $this->ui_factory,
394  $this->ui_renderer,
395  $this->testrequest
396  );
397  $lo_status->setCrsObjId($this->getObjectiveOrientedContainer()->getObjId());
398  $lo_status->setUsrId($test_session->getUserId());
399  $overview .= '<br />' . $lo_status->getHTML();
400  }
401  $template->setVariable('PASS_OVERVIEW', $overview);
402  $template->parseCurrentBlock();
403 
404  if ($this->isGradingMessageRequired()) {
405  $grading_message_builder = $this->getGradingMessageBuilder($active_id);
406  $grading_message_builder->buildMessage();
407  $grading_message_builder->sendMessage();
408  }
409 
410  $user_data = $this->getAdditionalUsrDataHtmlAndPopulateWindowTitle($test_session, $active_id, true);
411 
412  if (!$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
413  if ($this->object->getAnonymity()) {
414  $template->setVariable('TEXT_HEADING', $this->lng->txt('tst_result'));
415  } else {
416  $template->setVariable('TEXT_HEADING', sprintf($this->lng->txt('tst_result_user_name'), $uname));
417  $template->setVariable('USER_DATA', $user_data);
418  }
419  }
420 
421  $this->setCss();
422  $templatehead->setVariable('RESULTS_PARTICIPANT', $template->get());
423  $this->tpl->setContent($templatehead->get());
424  }
425 
426  public function outUserListOfAnswerPasses()
427  {
428  if (!$this->object->getShowSolutionPrintview()) {
429  $this->tpl->setOnScreenMessage('info', $this->lng->txt('no_permission'), true);
430  $this->ctrl->redirectByClass([ilRepositoryGUI::class, ilObjTestGUI::class, ilInfoScreenGUI::class]);
431  }
432 
433  $template = new ilTemplate('tpl.il_as_tst_info_list_of_answers.html', true, true, 'components/ILIAS/Test');
434 
435  $user_id = $this->user->getId();
436 
437  $test_session = $this->test_session_factory->getSession();
438  $active_id = $test_session->getActiveId();
439 
440  if ($this->testrequest->isset('attempt')) {
441  $attempt = $this->testrequest->int('attempt');
442  } else {
443  $attempt = \ilObjTest::_getResultPass($test_session->getActiveId());
444  }
445 
446  $test_passes_selector = new ilTestPassesSelector($this->db, $this->object);
447  $test_passes_selector->setActiveId($test_session->getActiveId());
448  $test_passes_selector->setLastFinishedPass($test_session->getLastFinishedPass());
449 
450  if (count($test_passes_selector->getClosedPasses()) > 1) {
451  $this->addAttemptSwitchingViewControlToToolbar($test_passes_selector->getClosedPasses(), $attempt);
452  }
453 
454  $this->addPrintButtonToToolbar();
455 
456  $test_result_header_label_builder = new ResultsTitlesBuilder($this->lng, $this->obj_cache);
457  $test_result_header_label_builder->setAttemptLastAccessDate(
458  (new \DateTimeImmutable(
459  '@' . ilObjTest::lookupLastTestPassAccess($test_session->getActiveId(), $attempt)
460  ))->setTimezone(new \DateTimeZone($this->user->getTimeZone()))
461  ->format($this->user->getDateTimeFormat()->toString())
462  );
463 
464  $objectives_list = null;
465  if ($this->getObjectiveOrientedContainer()?->isObjectiveOrientedPresentationRequired()) {
466  $test_sequence = $this->test_sequence_factory->getSequenceByActiveIdAndPass($active_id, $attempt);
467  $test_sequence->loadFromDb();
468  $test_sequence->loadQuestions();
469 
470  $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($test_session);
471 
472  $objectives_list = $this->buildQuestionRelatedObjectivesList($objectivesAdapter, $test_sequence);
473  $objectives_list->loadObjectivesTitles();
474 
475  $test_result_header_label_builder->setObjectiveOrientedContainerId($test_session->getObjectiveOrientedContainerId());
476  $test_result_header_label_builder->setUserId($test_session->getUserId());
477  $test_result_header_label_builder->setTestObjId($this->object->getId());
478  $test_result_header_label_builder->setTestRefId($this->object->getRefId());
479  $test_result_header_label_builder->initObjectiveOrientedMode();
480  }
481 
482  $result_array = $this->object->getTestResult(
483  $active_id,
484  $attempt,
485  false,
486  !$this->getObjectiveOrientedContainer()?->isObjectiveOrientedPresentationRequired()
487  );
488 
489  $signature = $this->getResultsSignature();
490  $show_all_answers = true;
491  if ($this->object->isExecutable($test_session, $user_id)) {
492  $show_all_answers = false;
493  }
494  $this->setContextResultPresentation(false);
495  $answers = $this->getPassListOfAnswers(
496  $result_array,
497  $active_id,
498  $attempt,
499  false,
500  $show_all_answers,
501  false,
502  false,
503  false,
504  $objectives_list,
505  $test_result_header_label_builder
506  );
507  $template->setVariable('PASS_DETAILS', $answers);
508 
509  $user_data = $this->getAdditionalUsrDataHtmlAndPopulateWindowTitle($test_session, $active_id, true);
510  $template->setVariable('USER_DATA', $user_data);
511  if (strlen($signature)) {
512  $template->setVariable('SIGNATURE', $signature);
513  }
514  if (!is_null($attempt) && $this->object->isShowExamIdInTestResultsEnabled()) {
515  $template->setCurrentBlock('exam_id_footer');
516  $template->setVariable('EXAM_ID_VAL', ilObjTest::lookupExamId(
517  $test_session->getActiveId(),
518  $attempt
519  ));
520  $template->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id'));
521  $template->parseCurrentBlock();
522  }
523 
524  $this->setCss();
525  $this->tpl->setVariable('ADM_CONTENT', $template->get());
526  }
527 
528  public function outCertificate()
529  {
530  $ilUserCertificateRepository = new ilUserCertificateRepository($this->db, $this->logger->getComponentLogger());
531  $pdfGenerator = new ilPdfGenerator($ilUserCertificateRepository);
532 
533  $pdfAction = new ilCertificatePdfAction(
534  $pdfGenerator,
536  $this->lng->txt('error_creating_certificate_pdf')
537  );
538 
539  $pdfAction->downloadPdf($this->user->getId(), $this->object->getId());
540  }
541 
542  public function confirmDeletePass()
543  {
544  if ($this->testrequest->isset('context') && strlen($this->testrequest->raw('context'))) {
545  $context = $this->testrequest->raw('context');
546  } else {
548  }
549 
550  if (!$this->object->isPassDeletionAllowed()) {
552  }
553 
554  $confirm = new ilTestPassDeletionConfirmationGUI($this->ctrl, $this->lng, $this);
555  $confirm->build($this->testrequest->getActiveId('active_id'), $this->testrequest->int('pass'), $context);
556 
557  $this->tpl->setContent($this->ctrl->getHTML($confirm));
558  }
559 
560  public function cancelDeletePass(): void
561  {
562  $this->redirectToPassDeletionContext($this->testrequest->strVal('context'));
563  }
564 
565  private function redirectToPassDeletionContext(string $context): void
566  {
567  switch ($context) {
569 
570  $this->ctrl->redirect($this, 'outUserResultsOverview');
571 
572  // no break
574  $this->ctrl->redirectByClass([ilRepositoryGUI::class, ilObjTestGUI::class, ilInfoScreenGUI::class]);
575  }
576  }
577 
578  public function performDeletePass(): void
579  {
580  $context = $this->testrequest->strVal('context') ?? ilTestPassDeletionConfirmationGUI::CONTEXT_PASS_OVERVIEW;
581  $active_fi = $this->testrequest->int('active_id');
582  $pass = $this->testrequest->int('pass');
583 
584  if (!$this->object->isPassDeletionAllowed()) {
586  }
587 
588  $ilDB = $this->db;
589 
590  if ($active_fi === 0 || !$this->testrequest->isset('pass')) {
591  $this->ctrl->redirect($this, 'outUserResultsOverview');
592  }
593 
594  if ($pass === ilObjTest::_getResultPass($active_fi)) {
595  $this->ctrl->redirect($this, 'outUserResultsOverview');
596  }
597 
598  // Get information
599  $result = $ilDB->query("
600  SELECT tst_active.tries, tst_active.last_finished_pass, tst_sequence.pass
601  FROM tst_active
602  LEFT JOIN tst_sequence
603  ON tst_sequence.active_fi = tst_active.active_id
604  AND tst_sequence.pass = tst_active.tries
605  WHERE tst_active.active_id = {$ilDB->quote($active_fi, 'integer')}
606  ");
607 
608  $row = $ilDB->fetchAssoc($result);
609 
610  $tries = $row['tries'];
611  $lastFinishedPass = is_numeric($row['last_finished_pass']) ? $row['last_finished_pass'] : -1;
612 
613  if ($pass < $lastFinishedPass) {
614  $isActivePass = false;
615  $must_renumber = true;
616  } elseif ($pass == $lastFinishedPass) {
617  $isActivePass = false;
618 
619  if ($tries == $row['pass']) {
620  $must_renumber = true;
621  } else {
622  $must_renumber = false;
623  }
624  } elseif ($pass == $row['pass']) {
625  $isActivePass = true;
626  $must_renumber = false;
627  } else {
628  throw new ilTestException('This should not happen, please contact Bjoern Heyser to clean up this pass salad!');
629  }
630 
631  if ($isActivePass) {
632  $this->ctrl->redirect($this, 'outUserResultsOverview');
633  }
634 
635  if ($pass == 0 && (
636  ($lastFinishedPass == 0 && $tries == 1 && $tries != $row['pass'])
637  || ($isActivePass == true) // should be equal to || ($lastFinishedPass == -1 && $tries == 0)
638  )) {
639  $last_pass = true;
640  } else {
641  $last_pass = false;
642  }
643 
644  // Work on tables:
645  // tst_active
646  if ($last_pass) {
647  $ilDB->manipulate(
648  'DELETE
649  FROM tst_active
650  WHERE active_id = ' . $ilDB->quote($active_fi, 'integer')
651  );
652  } elseif (!$isActivePass) {
653  $ilDB->manipulate(
654  'UPDATE tst_active
655  SET tries = ' . $ilDB->quote($tries - 1, 'integer') . ',
656  last_finished_pass = ' . $ilDB->quote($lastFinishedPass - 1, 'integer') . '
657  WHERE active_id = ' . $ilDB->quote($active_fi, 'integer')
658  );
659  }
660  // tst_manual_fb
661  $ilDB->manipulate(
662  'DELETE
663  FROM tst_manual_fb
664  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
665  AND pass = ' . $ilDB->quote($pass, 'integer')
666  );
667 
668  if ($must_renumber) {
669  $ilDB->manipulate(
670  'UPDATE tst_manual_fb
671  SET pass = pass - 1
672  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
673  AND pass > ' . $ilDB->quote($pass, 'integer')
674  );
675  }
676 
677  // tst_mark -> nothing to do
678  //
679  // tst_pass_result
680  $ilDB->manipulate(
681  'DELETE
682  FROM tst_pass_result
683  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
684  AND pass = ' . $ilDB->quote($pass, 'integer')
685  );
686 
687  if ($must_renumber) {
688  $ilDB->manipulate(
689  'UPDATE tst_pass_result
690  SET pass = pass - 1
691  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
692  AND pass > ' . $ilDB->quote($pass, 'integer')
693  );
694  }
695 
696  $ilDB->manipulate(
697  'DELETE
698  FROM tst_sequence
699  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
700  AND pass = ' . $ilDB->quote($pass, 'integer')
701  );
702 
703  if ($must_renumber) {
704  $ilDB->manipulate(
705  'UPDATE tst_sequence
706  SET pass = pass - 1
707  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
708  AND pass > ' . $ilDB->quote($pass, 'integer')
709  );
710  }
711 
712  // tst_solutions
713  $ilDB->manipulate(
714  'DELETE
715  FROM tst_solutions
716  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
717  AND pass = ' . $ilDB->quote($pass, 'integer')
718  );
719 
720  if ($must_renumber) {
721  $ilDB->manipulate(
722  'UPDATE tst_solutions
723  SET pass = pass - 1
724  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
725  AND pass > ' . $ilDB->quote($pass, 'integer')
726  );
727  }
728 
729  // tst_test_result
730  $ilDB->manipulate(
731  'DELETE
732  FROM tst_test_result
733  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
734  AND pass = ' . $ilDB->quote($pass, 'integer')
735  );
736 
737  if ($must_renumber) {
738  $ilDB->manipulate(
739  'UPDATE tst_test_result
740  SET pass = pass - 1
741  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
742  AND pass > ' . $ilDB->quote($pass, 'integer')
743  );
744  }
745 
746  // qpl_hint_tracking
747  $ilDB->manipulate(
748  'DELETE
749  FROM qpl_hint_tracking
750  WHERE qhtr_active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
751  AND qhtr_pass = ' . $ilDB->quote($pass, 'integer')
752  );
753 
754  if ($must_renumber) {
755  $ilDB->manipulate(
756  'UPDATE qpl_hint_tracking
757  SET qhtr_pass = qhtr_pass - 1
758  WHERE qhtr_active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
759  AND qhtr_pass > ' . $ilDB->quote($pass, 'integer')
760  );
761  }
762 
763  // tst_test_rnd_qst -> nothing to do
764 
765  // tst_times
766  $ilDB->manipulate(
767  'DELETE
768  FROM tst_times
769  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
770  AND pass = ' . $ilDB->quote($pass, 'integer')
771  );
772 
773  if ($must_renumber) {
774  $ilDB->manipulate(
775  'UPDATE tst_times
776  SET pass = pass - 1
777  WHERE active_fi = ' . $ilDB->quote($active_fi, 'integer') . '
778  AND pass > ' . $ilDB->quote($pass, 'integer')
779  );
780  }
781 
782  $this->object->updateTestResultCache((int) $active_fi);
783 
785  }
786 
787  protected function redirectBackToParticipantsScreen()
788  {
789  $this->ctrl->redirectByClass(ilTestParticipantsGUI::class);
790  }
791 
792  protected function sendPage(string $page)
793  {
794  $this->http->saveResponse($this->http->response()->withBody(
795  Streams::ofString($page)
796  ));
797  $this->http->sendResponse();
798  $this->http->close();
799  }
800 
801  protected function buildResultsTitle(string $fullname, int $pass): string
802  {
803  if ($this->object->getAnonymity()) {
804  return sprintf(
805  $this->lng->txt('tst_eval_results_by_pass_lo'),
806  $pass + 1
807  );
808  }
809  return sprintf(
810  $this->lng->txt('tst_result_user_name_pass'),
811  $pass + 1,
812  $fullname
813  );
814  }
815 
816  private function buildAttemptComponents(
817  int $active_id,
818  int $attempt_id,
819  bool $with_test_results_overview,
820  bool $for_print
821  ): array {
822  $settings = $this->results_presentation_factory->getAttemptResultsSettings(
823  $this->object,
824  false
825  );
826  $attempt_overview = $this->ui_factory->panel()->sub(
827  $this->lng->txt('question_summary'),
828  $this->results_data_factory->getAttemptOverviewFor(
829  $settings,
830  $this->object,
831  $active_id,
832  $attempt_id
833  )->getAsDescriptiveListing(
834  $this->lng,
835  $this->ui_factory,
836  [
837  'timezone' => new DateTimeZone($this->user->getTimeZone()),
838  'datetimeformat' => $this->user->getDateTimeFormat()->toString()
839  ]
840  )
841  );
842 
843  if ($with_test_results_overview) {
844  $attempt_overview = $attempt_overview->withFurtherInformation(
845  $this->ui_factory->card()->standard($this->lng->txt('overview'))->withSections([
846  $this->results_data_factory->getOverviewDataForTest($this->object)
847  ->getAsDescriptiveListing(
848  $this->lng,
849  $this->ui_factory
850  )
851  ])
852  );
853  }
854 
855  $results_presentation_table = $this->results_presentation_factory->getAttemptResultsPresentationTable(
856  $this->results_data_factory->getAttemptResultsFor(
857  $settings,
858  $this->object,
859  $active_id,
860  $attempt_id,
861  false
862  ),
863  $settings,
864  '',
865  $for_print
866  )->getTableComponent();
867 
868  if ($for_print) {
869  $signal = $results_presentation_table->getExpandAllSignal();
870  $results_presentation_table = [
871  $results_presentation_table,
872  $this->ui_factory->legacy('')->withAdditionalOnLoadCode(
873  fn(string $id): string => "$(document).trigger('{$signal->getId()}',"
874  . '{"options" : ' . json_encode($signal->getOptions()) . '}); '
875  )
876  ];
877  }
878 
879 
880 
881  $attempt_details = $this->ui_factory->panel()->sub(
882  $this->lng->txt('details'),
883  $results_presentation_table
884  );
885 
886  return [$attempt_overview, $attempt_details];
887  }
888 
889  private function addPrintResultsButtonToToolbar(): void
890  {
891  $link = $this->ctrl->getLinkTargetByClass(self::class, 'printResults');
892  $this->toolbar->addComponent(
893  $this->ui_factory->button()->standard(
894  $this->lng->txt('print'),
895  ''
896  )->withOnLoadCode(
897  fn($id): string => "document.getElementById('{$id}').addEventListener('click', "
898  . "(e) => {window.open('{$link}');}"
899  . ');'
900  )
901  );
902  }
903 
904  private function addPrintButtonToToolbar(): void
905  {
906  $this->toolbar->addComponent(
907  $this->ui_factory->button()->standard(
908  $this->lng->txt('print'),
909  ''
910  )->withOnLoadCode(
911  fn($id): string => "document.getElementById('{$id}').addEventListener('click', "
912  . "()=>{window.print();}"
913  . ');'
914  )
915  );
916  }
917 
918  private function addCertificateDownloadButtonToToolbar(): void
919  {
920  $this->toolbar->addComponent(
921  $this->ui_factory->button()->standard(
922  $this->lng->txt('certificate'),
923  $this->ctrl->getLinkTargetByClass(self::class, 'outCertificate')
924  )
925  );
926  }
927 
928  private function addToggleBestSolutionButtonToToolbar(): void
929  {
930  if ($this->testrequest->isset('show_best_solutions')) {
932  'tst_results_show_best_solutions',
933  $this->testrequest->int('show_best_solutions') === 1
934  );
935  }
936 
937  if (ilSession::get('tst_results_show_best_solutions')) {
938  $this->ctrl->setParameter($this, 'show_best_solutions', '0');
939  $label = $this->lng->txt('tst_btn_hide_best_solutions');
940  } else {
941  $this->ctrl->setParameter($this, 'show_best_solutions', '1');
942  $label = $this->lng->txt('tst_btn_show_best_solutions');
943  }
944 
945  $this->toolbar->addSeparator();
946  $this->toolbar->addComponent(
947  $this->ui_factory->button()->standard(
948  $label,
949  $this->ctrl->getLinkTargetByClass(self::class, $this->ctrl->getCmd(self::DEFAULT_CMD))
950  )
951  );
952  $this->ctrl->clearParameters($this, 'show_best_solutions');
953  }
954 
956  array $selected_active_ids,
957  int $current_active_id
958  ): void {
959  $this->toolbar->addSeparator();
960  $this->toolbar->addComponent(
961  $this->ui_factory->dropdown()
962  ->standard(
963  $this->buildParticipantSelectorArray($selected_active_ids, $current_active_id)
964  )->withLabel($this->lng->txt('tst_res_jump_to_participant_hint_opt'))
965  );
966  }
967 
969  array $selected_active_ids,
970  int $current_active_id
971  ): array {
972  $this->ctrl->setParameterByClass(self::class, 'active_ids', implode(',', $selected_active_ids));
973  unset($selected_active_ids[array_search($current_active_id, $selected_active_ids)]);
974  $available_user_links = array_map(
975  function (int $v): StandardLink {
976  $this->ctrl->setParameterByClass(self::class, 'active_id', $v);
977  return $this->ui_factory->link()->standard(
978  ilObjUser::_lookupFullname($this->object->_getUserIdFromActiveId($v)),
979  $this->ctrl->getLinkTargetByClass(self::class, 'showResults')
980  );
981  },
982  $selected_active_ids
983  );
984  $this->ctrl->clearParameterByClass(self::class, 'active_id');
985  $this->ctrl->clearParameterByClass(self::class, 'active_ids');
986  return $available_user_links;
987  }
988 
990  array $available_attempts,
991  int $selected_attempt
992  ): void {
993  $selected_attempt++;
994  $this->toolbar->addComponent(
996  $available_attempts,
997  $selected_attempt
998  )
999  );
1000  $this->ctrl->clearParameterByClass(self::class, 'attempt');
1001  }
1002 
1004  array $available_attempts,
1005  int $selected_attempt
1006  ): ViewControlMode {
1007  return $this->ui_factory->viewControl()->mode(
1008  array_reduce(
1009  $available_attempts,
1010  function (array $c, int $v): array {
1011  $this->ctrl->setParameterByClass(self::class, 'attempt', $v);
1012  $attempt = $v + 1;
1013  $c["{$this->lng->txt('tst_attempt')} {$attempt}"] = $this
1014  ->ctrl->getLinkTargetByClass(self::class, $this->ctrl->getCmd(self::DEFAULT_CMD));
1015  return $c;
1016  },
1017  []
1018  ),
1019  $this->lng->txt('select_attempt')
1020  )->withActive("{$this->lng->txt('tst_attempt')} {$selected_attempt}");
1021  }
1022 }
static get(string $a_var)
static getStyleSheetLocation(string $mode="output", string $a_css_name="")
get full style sheet file name (path inclusive) of current user
parseCurrentBlock(string $block_name=self::DEFAULT_BLOCK)
Parses the given block.
populateExamId(ilTemplate $tpl, int $active_id, int $pass)
ilTestProcessLockerFactory $processLockerFactory
getAdditionalUsrDataHtmlAndPopulateWindowTitle($testSession, $active_id, $overwrite_anonymity=false)
Returns the user data for a test results output.
$context
Definition: webdav.php:31
buildParticipantSelectorArray(array $selected_active_ids, int $current_active_id)
buildAttemptSwitchingViewControl(array $available_attempts, int $selected_attempt)
setCurrentBlock(string $part=self::DEFAULT_BLOCK)
Sets the template to the given block.
addParticipantSelectorToToolbar(array $selected_active_ids, int $current_active_id)
static _lookupFullname(int $a_user_id)
redirectToPassDeletionContext(string $context)
ilGlobalTemplateInterface ilTemplate $tpl
sk 2023-08-01: We need this union type, even if it is wrong! To change this
setContextResultPresentation(bool $contextResultPresentation)
getResultsSignature()
Returns HTML code for a signature field.
buildAttemptComponents(int $active_id, int $attempt_id, bool $with_test_results_overview, bool $for_print)
setVariable(string $variable, $value='')
Sets the given variable to the given value.
$c
Definition: deliver.php:25
buildResultsTitle(string $fullname, int $pass)
__construct(ilObjTest $object)
buildPassOverviewTableGUI(ilTestEvaluationGUI $target_gui)
Base Exception for all Exceptions relating to Modules/Test.
$components
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
getEvaluationQuestionId($question_id, $original_id='')
Returns the ID of a question for evaluation purposes.
static getInstance(ilTestSession $a_test_session)
static http()
Fetches the global http state from ILIAS.
getGradingMessageBuilder(int $active_id)
readonly ilDBInterface $db
static _getResultPass($active_id)
Retrieves the pass number that should be counted for a given user.
Validates if an active certificate is stored in the database and can be downloaded by the user...
getPassOverviewTableData(ilTestSession $test_session, array $passes, bool $with_results)
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, ?ResultsTitlesBuilder $testResultHeaderLabelBuilder=null)
Returns the list of answers of a users test pass.
Output class for assessment test evaluation.
get(string $part=self::DEFAULT_BLOCK)
Renders the given block and returns the html string.
static getContentStylePath(int $a_style_id, bool $add_random=true, bool $add_token=true)
get content style path static (to avoid full reading)
addAttemptSwitchingViewControlToToolbar(array $available_attempts, int $selected_attempt)
buildQuestionRelatedObjectivesList(ilLOTestQuestionAdapter $objectives_adapter, ilTestQuestionSequence $test_sequence)
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
__construct(Container $dic, ilPlugin $plugin)
readonly ilSetting $settings
Service GUI class for tests.
populatePassFinishDate(ilTemplate $tpl, ?int $pass_finish_date)
static set(string $a_var, $a_val)
Set a value.
static lookupExamId($active_id, $pass)
downloadPdf(int $userId, int $objectId)