ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilTestPlayerAbstractGUI.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
23 
24 require_once './Modules/Test/classes/inc.AssessmentConstants.php';
25 
41 {
42  public const PRESENTATION_MODE_VIEW = 'view';
43  public const PRESENTATION_MODE_EDIT = 'edit';
44 
46 
48  public bool $endingTimeReached;
49  public int $ref_id;
50 
53  protected ?ilTestSession $test_session = null;
54  protected ?ilSetting $assSettings = null;
55  protected ?ilTestSequence $testSequence = null;
56 
58 
59  public function __construct(ilObjTest $object)
60  {
61  parent::__construct($object);
62  $this->ref_id = $this->testrequest->getRefId();
63  $this->passwordChecker = new ilTestPasswordChecker($this->rbac_system, $this->user, $this->object, $this->lng);
64  }
65 
66  protected function checkReadAccess(): bool|string
67  {
68  if (!$this->rbac_system->checkAccess('read', $this->object->getRefId())) {
69  return $this->lng->txt('cannot_execute_test');
70  }
71 
72  if (ilObjTestAccess::_lookupOnlineTestAccess($this->getObject()->getId(), $this->user->getId()) !== true) {
73  return $this->lng->txt('user_wrong_clientip');
74  }
75 
76  return true;
77  }
78 
79  protected function checkTestExecutable()
80  {
81  $executable = $this->object->isExecutable($this->test_session, $this->test_session->getUserId());
82 
83  if (!$executable['executable']) {
84  $this->tpl->setOnScreenMessage('info', $executable['errormessage'], true);
85  $this->ctrl->redirectByClass(ilTestScreenGUI::class, ilTestScreenGUI::DEFAULT_CMD);
86  }
87  }
88 
89  protected function checkTestSessionUser(ilTestSession $test_session): void
90  {
91  if ($test_session->getUserId() != $this->user->getId()) {
92  throw new ilTestException('active id given does not relate to current user!');
93  }
94  }
95 
96  protected function ensureExistingTestSession(ilTestSession $test_session): void
97  {
98  if ($test_session->getActiveId()) {
99  return;
100  }
101 
102  $test_session->setUserId($this->user->getId());
103 
104  if ($test_session->isAnonymousUser()) {
105  if (!$test_session->doesAccessCodeInSessionExists()) {
106  return;
107  }
108 
109  $test_session->setAnonymousId($test_session->getAccessCodeFromSession());
110  }
111 
112  $test_session->saveToDb();
113  }
114 
115  protected function initProcessLocker($activeId)
116  {
117  $ilDB = $this->db;
118  $processLockerFactory = new ilTestProcessLockerFactory($this->assSettings, $ilDB);
119  $this->processLocker = $processLockerFactory->withContextId((int) $activeId)->getLocker();
120  }
121 
128  public function saveTagsCmd()
129  {
130  $tagging_gui = new ilTaggingGUI();
131  $tagging_gui->setObject($this->object->getId(), $this->object->getType());
132  $tagging_gui->saveInput();
133  $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen");
134  }
135 
139  public function updateWorkingTime()
140  {
141  if (ilSession::get("active_time_id") != null) {
142  $this->object->updateWorkingTime(ilSession::get("active_time_id"));
143  }
144 
146  "active_time_id",
147  $this->object->startWorkingTime(
148  $this->test_session->getActiveId(),
149  $this->test_session->getPass()
150  )
151  );
152  }
153 
154  public function removeIntermediateSolution(): void
155  {
156  $questionId = $this->getCurrentQuestionId();
157 
158  $this->getQuestionInstance($questionId)->removeIntermediateSolution(
159  $this->test_session->getActiveId(),
160  $this->test_session->getPass()
161  );
162  }
163 
167  abstract public function saveQuestionSolution($authorized = true, $force = false);
168 
169  abstract protected function canSaveResult();
170 
171  public function suspendTestCmd()
172  {
173  $this->ctrl->redirectByClass(ilTestScreenGUI::class, ilTestScreenGUI::DEFAULT_CMD);
174  }
175 
176  public function isMaxProcessingTimeReached(): bool
177  {
178  $active_id = $this->test_session->getActiveId();
179  $starting_time = $this->object->getStartingTimeOfUser($active_id);
180  if ($starting_time === false) {
181  return false;
182  } else {
183  return $this->object->isMaxProcessingTimeReached($starting_time, $active_id);
184  }
185  }
186 
187  protected function determineInlineScoreDisplay(): bool
188  {
189  $show_question_inline_score = false;
190  if ($this->object->getAnswerFeedbackPoints()) {
191  $show_question_inline_score = true;
192  return $show_question_inline_score;
193  }
194  return $show_question_inline_score;
195  }
196 
197  protected function populateTestNavigationToolbar(ilTestNavigationToolbarGUI $toolbar_gui): void
198  {
199  $this->tpl->setCurrentBlock('test_nav_toolbar');
200  $this->tpl->setVariable('TEST_NAV_TOOLBAR', $toolbar_gui->getHTML());
201  $this->tpl->parseCurrentBlock();
202 
203  if ($this->finish_test_modal === null) {
204  return;
205  }
206 
207  $this->tpl->setCurrentBlock('finish_test_modal');
208  $this->tpl->setVariable(
209  'FINISH_TEST_MODAL',
210  $this->ui_renderer->render(
211  $this->finish_test_modal->withOnLoad($this->finish_test_modal->getShowSignal())
212  )
213  );
214  $this->tpl->parseCurrentBlock();
215  }
216 
217  protected function populateQuestionNavigation($sequence_element, $primary_next): void
218  {
219  if (!$this->isFirstQuestionInSequence($sequence_element)) {
220  $this->populatePreviousButtons();
221  }
222 
223  if (!$this->isLastQuestionInSequence($sequence_element)) {
224  $this->populateNextButtons($primary_next);
225  }
226  }
227 
228  protected function populatePreviousButtons(): void
229  {
232  }
233 
234  protected function populateNextButtons($primary_next): void
235  {
236  $this->populateUpperNextButtonBlock($primary_next);
237  $this->populateLowerNextButtonBlock($primary_next);
238  }
239 
240  protected function populateLowerNextButtonBlock($primary_next): void
241  {
242  $button = $this->buildNextButtonInstance($primary_next);
243 
244  $this->tpl->setCurrentBlock("next_bottom");
245  $this->tpl->setVariable("BTN_NEXT_BOTTOM", $this->ui_renderer->render($button));
246  $this->tpl->parseCurrentBlock();
247  }
248 
249  protected function populateUpperNextButtonBlock($primaryNext)
250  {
251  $button = $this->buildNextButtonInstance($primaryNext);
252 
253  $this->tpl->setCurrentBlock("next");
254  $this->tpl->setVariable("BTN_NEXT", $this->ui_renderer->render($button));
255  $this->tpl->parseCurrentBlock();
256  }
257 
258  protected function populateLowerPreviousButtonBlock()
259  {
260  $button = $this->buildPreviousButtonInstance();
261 
262  $this->tpl->setCurrentBlock("prev_bottom");
263  $this->tpl->setVariable("BTN_PREV_BOTTOM", $this->ui_renderer->render($button));
264  $this->tpl->parseCurrentBlock();
265  }
266 
267  protected function populateUpperPreviousButtonBlock()
268  {
269  $button = $this->buildPreviousButtonInstance();
270 
271  $this->tpl->setCurrentBlock("prev");
272  $this->tpl->setVariable("BTN_PREV", $this->ui_renderer->render($button));
273  $this->tpl->parseCurrentBlock();
274  }
275 
280  private function buildNextButtonInstance($primaryNext)
281  {
282  $target = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::NEXT_QUESTION);
283  if ($primaryNext) {
284  $button = $this->ui_factory->button()->primary(
285  $this->lng->txt('next_question') . '<span class="glyphicon glyphicon-arrow-right"></span> ',
286  ''
287  )->withUnavailableAction(true)
289  } else {
290  $button = $this->ui_factory->button()->standard(
291  $this->lng->txt('next_question') . '<span class="glyphicon glyphicon-arrow-right"></span> ',
292  ''
293  )->withUnavailableAction(true)
295  }
296  return $button;
297  }
298 
303  private function buildPreviousButtonInstance()
304  {
305  $target = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::PREVIOUS_QUESTION);
306  $button = $this->ui_factory->button()->standard(
307  '<span class="glyphicon glyphicon-arrow-left"></span> ' . $this->lng->txt('previous_question'),
308  ''
309  )->withUnavailableAction(true)
311  return $button;
312  }
313 
314  private function getOnLoadCodeForNavigationButtons(string $target, string $cmd): Closure
315  {
316  return static function (string $id) use ($target, $cmd): string {
317  return "document.getElementById('{$id}').addEventListener('click', "
318  . "(e) => {il.TestPlayerQuestionEditControl.checkNavigation('{$target}', '{$cmd}', e);}"
319  . "); "
320  . "document.getElementById('{$id}').removeAttribute('disabled');";
321  };
322  }
323 
327  protected function populateSpecificFeedbackBlock(assQuestionGUI $question_gui): bool
328  {
329  $solutionValues = $question_gui->object->getSolutionValues(
330  $this->test_session->getActiveId(),
331  null
332  );
333 
334  $feedback = $question_gui->getSpecificFeedbackOutput(
335  $question_gui->object->fetchIndexedValuesFromValuePairs($solutionValues)
336  );
337 
338  if (!empty($feedback)) {
339  $this->tpl->setCurrentBlock("specific_feedback");
340  $this->tpl->setVariable("SPECIFIC_FEEDBACK", $feedback);
341  $this->tpl->parseCurrentBlock();
342  return true;
343  }
344  return false;
345  }
346 
350  protected function populateGenericFeedbackBlock(assQuestionGUI $question_gui, $solutionCorrect): bool
351  {
352  // fix #031263: add pass
353  $feedback = $question_gui->getGenericFeedbackOutput($this->test_session->getActiveId(), $this->test_session->getPass());
354 
355  if (strlen($feedback)) {
356  $cssClass = (
357  $solutionCorrect ?
359  );
360 
361  $this->tpl->setCurrentBlock("answer_feedback");
362  $this->tpl->setVariable("ANSWER_FEEDBACK", $feedback);
363  $this->tpl->setVariable("ILC_FB_CSS_CLASS", $cssClass);
364  $this->tpl->parseCurrentBlock();
365  return true;
366  }
367  return false;
368  }
369 
370  protected function populateScoreBlock($reachedPoints, $maxPoints)
371  {
372  $scoreInformation = sprintf(
373  $this->lng->txt("you_received_a_of_b_points"),
374  $reachedPoints,
375  $maxPoints
376  );
377 
378  $this->tpl->setCurrentBlock("received_points_information");
379  $this->tpl->setVariable("RECEIVED_POINTS_INFORMATION", $scoreInformation);
380  $this->tpl->parseCurrentBlock();
381  }
382 
383  protected function populateSolutionBlock($solutionoutput)
384  {
385  if (strlen($solutionoutput)) {
386  $this->tpl->setCurrentBlock("solution_output");
387  $this->tpl->setVariable("CORRECT_SOLUTION", $this->lng->txt("tst_best_solution_is"));
388  $this->tpl->setVariable("QUESTION_FEEDBACK", $solutionoutput);
389  $this->tpl->parseCurrentBlock();
390  }
391  }
392 
393  protected function populateSyntaxStyleBlock()
394  {
395  $this->tpl->setCurrentBlock("SyntaxStyle");
396  $this->tpl->setVariable(
397  "LOCATION_SYNTAX_STYLESHEET",
399  );
400  $this->tpl->parseCurrentBlock();
401  }
402 
403  protected function populateContentStyleBlock()
404  {
405  $this->tpl->setCurrentBlock("ContentStyle");
406  $this->tpl->setVariable(
407  "LOCATION_CONTENT_STYLESHEET",
409  );
410  $this->tpl->parseCurrentBlock();
411  }
412 
418  public function setAnonymousIdCmd()
419  {
420  if ($this->test_session->isAnonymousUser()) {
421  $this->test_session->setAccessCodeToSession($_POST['anonymous_id']);
422  }
423 
424  $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen");
425  }
426 
433  protected function startPlayerCmd()
434  {
435  $testStartLock = $this->getLockParameter();
436  $isFirstTestStartRequest = false;
437 
438  $this->processLocker->executeTestStartLockOperation(function () use ($testStartLock, &$isFirstTestStartRequest) {
439  if ($this->test_session->lookupTestStartLock() !== $testStartLock) {
440  $this->test_session->persistTestStartLock($testStartLock);
441  $isFirstTestStartRequest = true;
442  }
443  });
444 
445  if ($isFirstTestStartRequest) {
446  $this->handleUserSettings();
447  $this->ctrl->redirect($this, ilTestPlayerCommands::INIT_TEST);
448  }
449 
450  $this->ctrl->setParameterByClass('ilObjTestGUI', 'lock', $testStartLock);
451  $this->ctrl->redirectByClass("ilobjtestgui", "redirectToInfoScreen");
452  }
453 
454  public function getLockParameter()
455  {
456  if ($this->testrequest->isset('lock') && strlen($this->testrequest->raw('lock'))) {
457  return $this->testrequest->raw('lock');
458  }
459 
460  return null;
461  }
462 
466  abstract protected function resumePlayerCmd();
467 
471  protected function initTestCmd()
472  {
473  if ($this->test_session->isAnonymousUser()
474  && !$this->test_session->doesAccessCodeInSessionExists()) {
475  $accessCode = $this->test_session->createNewAccessCode();
476 
477  $this->test_session->setAccessCodeToSession($accessCode);
478  $this->test_session->setAnonymousId($accessCode);
479  $this->test_session->saveToDb();
480 
481  $this->ctrl->redirect($this, ilTestPlayerCommands::DISPLAY_ACCESS_CODE);
482  }
483 
484  if (!$this->test_session->isAnonymousUser()) {
485  $this->test_session->unsetAccessCodeInSession();
486  }
487  $this->ctrl->redirect($this, ilTestPlayerCommands::START_TEST);
488  }
489 
490  public function displayAccessCodeCmd()
491  {
492  $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_anonymous_code_presentation.html", "Modules/Test");
493  $this->tpl->setCurrentBlock("adm_content");
494  $this->tpl->setVariable("TEXT_ANONYMOUS_CODE_CREATED", $this->lng->txt("tst_access_code_created"));
495  $this->tpl->setVariable("TEXT_ANONYMOUS_CODE", $this->test_session->getAccessCodeFromSession());
496  $this->tpl->setVariable("FORMACTION", $this->ctrl->getFormAction($this));
497  $this->tpl->setVariable("CMD_CONFIRM", ilTestPlayerCommands::ACCESS_CODE_CONFIRMED);
498  $this->tpl->setVariable("TXT_CONFIRM", $this->lng->txt("continue_work"));
499  $this->tpl->parseCurrentBlock();
500  }
501 
502  public function accessCodeConfirmedCmd()
503  {
504  $this->ctrl->redirect($this, ilTestPlayerCommands::START_TEST);
505  }
506 
510  public function handleUserSettings()
511  {
512  if ($this->object->getNrOfTries() != 1
513  && $this->object->getUsePreviousAnswers() == 1
514  ) {
515  $chb_use_previous_answers = 0;
516  if ($this->post_wrapper->has('chb_use_previous_answers')) {
517  $chb_use_previous_answers = $this->post_wrapper->retrieve(
518  'chb_use_previous_answers',
519  $this->refinery->kindlyTo()->int()
520  );
521  }
522  $this->user->writePref("tst_use_previous_answers", (string) $chb_use_previous_answers);
523  }
524  }
525 
530  public function redirectAfterAutosaveCmd(): void
531  {
532  $this->redirectAfterFinish();
533  }
534 
535  public function redirectAfterQuestionListCmd(): void
536  {
537  $this->redirectAfterFinish();
538  }
539 
540  protected function redirectAfterFinish(): void
541  {
543 
544  $url = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::AFTER_TEST_PASS_FINISHED, '', false, false);
545 
546  $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_redirect_autosave.html", "Modules/Test");
547  $this->tpl->setVariable("TEXT_REDIRECT", $this->lng->txt("redirectAfterSave"));
548  $this->tpl->setVariable("URL", $url);
549  }
550 
551  abstract protected function getCurrentQuestionId();
552 
557  public function autosaveCmd(): void
558  {
559  if (!$this->access->checkAccess('read', '', $this->ref_id)) {
560  echo $this->lng->txt('autosave_failed') . ': ' . $this->lng->txt('msg_no_perm_read_item');
561  exit;
562  }
563  $test_can_run = $this->object->isExecutable($this->test_session, $this->test_session->getUserId());
564  if (!$test_can_run['executable']) {
565  echo $test_can_run['errormessage'];
566  exit;
567  }
568  if (!is_countable($_POST) || count($_POST) === 0) {
569  echo '';
570  exit;
571  }
572 
573  if (!$this->canSaveResult() || $this->isParticipantsAnswerFixed($this->getCurrentQuestionId())) {
574  echo '-IGNORE-';
575  exit;
576  }
577 
578  $authorize = !$this->getAnswerChangedParameter();
579  $res = $this->saveQuestionSolution($authorize, true);
580 
581  if ($res) {
582  echo $this->lng->txt("autosave_success");
583  exit;
584  }
585 
586  echo $this->lng->txt("autosave_failed");
587  exit;
588  }
589 
594  public function autosaveOnTimeLimitCmd()
595  {
596  if (!$this->isParticipantsAnswerFixed($this->getCurrentQuestionId())) {
597  $this->saveQuestionSolution(false, true);
598  }
599  $this->ctrl->redirect($this, ilTestPlayerCommands::REDIRECT_ON_TIME_LIMIT);
600  }
601 
602 
603  // fau: testNav - new function detectChangesCmd()
609  protected function detectChangesCmd()
610  {
611  $questionId = $this->getCurrentQuestionId();
612  $state = $this->getQuestionInstance($questionId)->lookupForExistingSolutions(
613  $this->test_session->getActiveId(),
614  $this->test_session->getPass()
615  );
616  $result = [];
617  $result['isAnswered'] = $state['authorized'];
618  $result['isAnswerChanged'] = $state['intermediate'];
619 
620  echo json_encode($result);
621  exit;
622  }
623  // fau.
624 
625  protected function submitIntermediateSolutionCmd()
626  {
627  $this->saveQuestionSolution(false, true);
628  // fau: testNav - set the 'answer changed' parameter when an intermediate solution is submitted
629  $this->setAnswerChangedParameter(true);
630  // fau.
631  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
632  }
633 
634  protected function markQuestionAndSaveIntermediateCmd(): void
635  {
636  $this->handleIntermediateSubmit();
637  $this->markQuestionCmd();
638  }
639 
643  protected function markQuestionCmd(): void
644  {
645  $questionId = $this->testSequence->getQuestionForSequence(
647  );
648 
649  $this->object->setQuestionSetSolved(1, $questionId, $this->test_session->getUserId());
650 
651  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
652  }
653 
655  {
656  // fau: testNav - handle intermediate submit when unmarking the question
657  $this->handleIntermediateSubmit();
658  // fau.
659  $this->unmarkQuestionCmd();
660  }
661 
665  protected function unmarkQuestionCmd()
666  {
667  $questionId = $this->testSequence->getQuestionForSequence(
669  );
670 
671  $this->object->setQuestionSetSolved(0, $questionId, $this->test_session->getUserId());
672 
673  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
674  }
675 
676  public function finishTestCmd()
677  {
678  $this->handleCheckTestPassValid();
679  ilSession::clear("tst_next");
680 
681  $all_obligations_answered = $this->object->allObligationsAnswered();
682 
683  /*
684  * The following "endgames" are possible prior to actually finishing the test:
685  * - Obligations (Ability to finish the test.)
686  * If not all obligatory questions are answered, the user is presented a list
687  * showing the deficits.
688  * - Examview (Will to finish the test.)
689  * With the examview, the participant can review all answers given in ILIAS or a PDF prior to
690  * commencing to the finished test.
691  * - Last pass allowed (Reassuring the will to finish the test.)
692  * If passes are limited, on the last pass, an additional confirmation is to be displayed.
693  */
694 
695  if ($this->object->areObligationsEnabled() && !$all_obligations_answered) {
696  if ($this->object->getListOfQuestions()) {
698  return;
699  }
700 
702  return;
703  }
704 
705  if ($this->testrequest->strVal('finalization_confirmed') !== 'confirmed') {
706  $this->finish_test_modal = $this->buildFinishTestModal();
707  $this->showQuestionCmd();
708  return;
709  }
710 
711  // Non-last try finish
712  if (ilSession::get('tst_pass_finish') === null) {
713  ilSession::set('tst_pass_finish', 1);
714  }
715 
717 
718  $this->ctrl->redirect($this, ilTestPlayerCommands::AFTER_TEST_PASS_FINISHED);
719  }
720 
721  protected function performTestPassFinishedTasks(): void
722  {
723  (new ilTestPassFinishTasks($this->test_session, $this->object))->performFinishTasks($this->processLocker);
724 
726  $this->test_session->getActiveId(),
727  $this->test_session->getPass()
728  );
729  }
730 
731  protected function sendNewPassFinishedNotificationEmailIfActivated(int $active_id, int $pass)
732  {
733  $notification_type = $this->object->getMainSettings()->getFinishingSettings()->getMailNotificationContentType();
734 
735  if ($notification_type === 0
736  || !$this->object->getMainSettings()->getFinishingSettings()->getAlwaysSendMailNotification()
737  && $pass !== $this->object->getNrOfTries() - 1) {
738  return;
739  }
740 
741  switch ($this->object->getMainSettings()->getFinishingSettings()->getMailNotificationContentType()) {
742  case 1:
743  $this->object->sendSimpleNotification($active_id);
744  break;
745  case 2:
746  $this->object->sendAdvancedNotification($active_id);
747  break;
748  }
749  }
750 
751  protected function afterTestPassFinishedCmd()
752  {
753  // show final statement
754  if (!$this->testrequest->isset('skipfinalstatement')) {
755  if ($this->object->getMainSettings()->getFinishingSettings()->getConcludingRemarksEnabled()) {
756  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_FINAL_STATMENT);
757  }
758  }
759 
760  // redirect after test
761  $redirection_url = $this->object->getMainSettings()->getFinishingSettings()->getRedirectionUrl();
762  if (!empty($redirection_url)
763  && !$this->object->canShowTestResults($this->test_session)
764  && $this->object->getMainSettings()->getFinishingSettings()->getRedirectionMode() !== ilObjTest::REDIRECT_NONE) {
765  if ($this->object->isRedirectModeKiosk()) {
766  if ($this->object->getKioskMode()) {
767  ilUtil::redirect($redirection_url);
768  }
769  } else {
770  ilUtil::redirect($redirection_url);
771  }
772  }
773 
774  // default redirect (pass overview when enabled, otherwise testscreen)
775  $this->redirectBackCmd();
776  }
777 
779  {
780  $class = get_class($this);
781  $this->ctrl->setParameterByClass($class, 'finalization_confirmed', 'confirmed');
782  $next_url = $this->ctrl->getLinkTargetByClass($class, ilTestPlayerCommands::FINISH_TEST);
783  $this->ctrl->clearParameterByClass($class, 'finalization_confirmed');
784 
785  $message = $this->lng->txt('tst_finish_confirmation_question');
786  if (($this->object->getNrOfTries() - 1) === $this->test_session->getPass()) {
787  $message = $this->lng->txt('tst_finish_confirmation_question_no_attempts_left');
788  }
789 
790  return $this->ui_factory->modal()->interruptive(
791  $this->lng->txt('finish_test'),
792  $message,
793  $next_url
794  )->withActionButtonLabel($this->lng->txt('tst_finish_confirm_button'));
795  }
796 
797  public function redirectBackCmd(): void
798  {
799  $testPassesSelector = new ilTestPassesSelector($this->db, $this->object);
800  $testPassesSelector->setActiveId($this->test_session->getActiveId());
801  $testPassesSelector->setLastFinishedPass($this->test_session->getLastFinishedPass());
802 
803  if (count($testPassesSelector->getReportablePasses())) {
804  if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
805  $this->ctrl->redirectByClass(['ilTestResultsGUI', 'ilTestEvalObjectiveOrientedGUI']);
806  }
807 
808  $this->ctrl->redirectByClass(['ilTestResultsGUI', 'ilMyTestResultsGUI', 'ilTestEvaluationGUI']);
809  }
810 
811  $this->ctrl->redirectByClass(ilTestScreenGUI::class, ilTestScreenGUI::DEFAULT_CMD);
812  }
813 
814  /*
815  * Presents the final statement of a test
816  */
817  public function showFinalStatementCmd()
818  {
819  $this->global_screen->tool()->context()->current()->getAdditionalData()->replace(
821  $this->object->getTitle() . ' - ' . $this->lng->txt('final_statement')
822  );
823  $this->content_style->gui()->addCss($this->tpl, $this->ref_id);
824  $this->ctrl->setParameterByClass(ilTestPageGUI::class, 'page_type', 'concludingremarkspage');
825  $template = new ilTemplate("tpl.il_as_tst_final_statement.html", true, true, "Modules/Test");
826  $this->ctrl->setParameter($this, "skipfinalstatement", 1);
827  $template->setVariable("FORMACTION", $this->ctrl->getFormAction($this, ilTestPlayerCommands::AFTER_TEST_PASS_FINISHED));
828  $template->setVariable("FINALSTATEMENT", $this->object->getFinalStatement());
829  $template->setVariable("BUTTON_CONTINUE", $this->lng->txt("btn_next"));
830  $this->tpl->setVariable($this->getContentBlockName(), $template->get());
831  }
832 
833  protected function prepareTestPage($sequenceElement, $questionId)
834  {
835  $this->navigation_history->addItem(
836  $this->test_session->getRefId(),
837  $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::RESUME_PLAYER),
838  'tst'
839  );
840 
841  $this->initTestPageTemplate();
842  $this->populateContentStyleBlock();
843  $this->populateSyntaxStyleBlock();
844 
845  if ($this->isMaxProcessingTimeReached()) {
846  $this->maxProcessingTimeReached();
847  return;
848  }
849 
850  if ($this->object->endingTimeReached()) {
851  $this->endingTimeReached();
852  return;
853  }
854 
855  if ($this->isOptionalQuestionAnsweringConfirmationRequired($sequenceElement)) {
856  $this->ctrl->setParameter($this, "sequence", $sequenceElement);
858  return;
859  }
860 
861  $this->tpl->setVariable("TEST_ID", (string) $this->object->getTestId());
862  $this->tpl->setVariable("LOGIN", $this->user->getLogin());
863 
864  $this->tpl->setVariable("SEQ_ID", $sequenceElement);
865  $this->tpl->setVariable("QUEST_ID", $questionId);
866 
867  if ($this->object->getEnableProcessingTime()) {
868  $this->outProcessingTime($this->test_session->getActiveId(), false);
869  }
870 
871  $this->tpl->setVariable("PAGETITLE", "- " . $this->object->getTitle());
872 
873  if ($this->object->isShowExamIdInTestPassEnabled() && !$this->object->getKioskMode()) {
874  $this->tpl->setCurrentBlock('exam_id_footer');
875  $this->tpl->setVariable('EXAM_ID_VAL', ilObjTest::lookupExamId(
876  $this->test_session->getActiveId(),
877  $this->test_session->getPass(),
878  $this->object->getId()
879  ));
880  $this->tpl->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id'));
881  $this->tpl->parseCurrentBlock();
882  }
883 
884  if ($this->object->getListOfQuestions()) {
885  $this->showSideList($sequenceElement);
886  }
887  }
888 
889  abstract protected function isOptionalQuestionAnsweringConfirmationRequired($sequenceElement);
890 
891  abstract protected function isShowingPostponeStatusReguired($questionId);
892 
893  protected function showQuestionViewable(assQuestionGUI $questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse)
894  {
895  $questionNavigationGUI = $this->buildReadOnlyStateQuestionNavigationGUI($questionGui->object->getId());
896  $questionNavigationGUI->setQuestionWorkedThrough($isQuestionWorkedThrough);
897  $questionGui->setNavigationGUI($questionNavigationGUI);
898 
899  // fau: testNav - set answere status in question header
900  $questionGui->getQuestionHeaderBlockBuilder()->setQuestionAnswered($isQuestionWorkedThrough);
901  // fau.
902 
903  $answerFeedbackEnabled = (
904  $instantResponse && $this->object->getSpecificAnswerFeedback()
905  );
906 
907  $solutionoutput = $questionGui->getSolutionOutput(
908  $this->test_session->getActiveId(), #active_id
909  $this->test_session->getPass(), #pass
910  false, #graphical_output
911  false, #result_output
912  true, #show_question_only
913  $answerFeedbackEnabled, #show_feedback
914  false, #show_correct_solution
915  false, #show_manual_scoring
916  true #show_question_text
917  );
918 
919  $pageoutput = $questionGui->outQuestionPage(
920  "",
921  $this->isShowingPostponeStatusReguired($questionGui->object->getId()),
922  $this->test_session->getActiveId(),
923  $solutionoutput
924  );
925 
926  $this->tpl->setVariable(
927  'LOCKSTATE_INFOBOX',
928  $this->ui_renderer->render(
929  $this->ui_factory->messageBox()->info($this->lng->txt("tst_player_answer_saved_and_locked"))
930  )
931  );
932  $this->tpl->parseCurrentBlock();
933 
934  $this->tpl->setVariable('QUESTION_OUTPUT', $pageoutput);
935 
936  $this->tpl->setVariable("FORMACTION", $formAction);
937  $this->tpl->setVariable("ENCTYPE", 'enctype="' . $questionGui->getFormEncodingType() . '"');
938  $this->tpl->setVariable("FORM_TIMESTAMP", time());
939  $this->populateQuestionEditControl($questionGui);
940  }
941 
942  protected function showQuestionEditable(assQuestionGUI $questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse)
943  {
944  $questionNavigationGUI = $this->buildEditableStateQuestionNavigationGUI($questionGui->object->getId());
945  if ($isQuestionWorkedThrough) {
946  $questionNavigationGUI->setDiscardSolutionButtonEnabled(true);
947  // fau: testNav - set answere status in question header
948  $questionGui->getQuestionHeaderBlockBuilder()->setQuestionAnswered(true);
949  // fau.
950  } elseif ($this->object->isPostponingEnabled()) {
951  $questionNavigationGUI->setSkipQuestionLinkTarget(
952  $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::SKIP_QUESTION)
953  );
954  }
955  $questionGui->setNavigationGUI($questionNavigationGUI);
956 
957  $isPostponed = $this->isShowingPostponeStatusReguired($questionGui->object->getId());
958 
959  $answerFeedbackEnabled = (
960  $instantResponse && $this->object->getSpecificAnswerFeedback()
961  );
962 
963  if ($this->testrequest->isset('save_error') && $this->testrequest->raw('save_error') == 1 && ilSession::get('previouspost') != null) {
964  $userPostSolution = ilSession::get('previouspost');
965  ilSession::clear('previouspost');
966  } else {
967  $userPostSolution = false;
968  }
969 
970  // fau: testNav - add special checkbox for mc question
971  // moved to another patch block
972  // fau.
973 
974  // hey: prevPassSolutions - determine solution pass index and configure gui accordingly
975  $qstConfig = $questionGui->object->getTestPresentationConfig();
976 
977  if ($questionGui instanceof assMultipleChoiceGUI) {
978  $qstConfig->setWorkedThrough($isQuestionWorkedThrough);
979  }
980 
981  if ($qstConfig->isPreviousPassSolutionReuseAllowed()) {
982  $passIndex = $this->determineSolutionPassIndex($questionGui); // last pass having solution stored
983  if ($passIndex < $this->test_session->getPass()) { // it's the previous pass if current pass is higher
984  $qstConfig->setSolutionInitiallyPrefilled(true);
985  }
986  } else {
987  $passIndex = $this->test_session->getPass();
988  }
989  // hey.
990 
991  // Answer specific feedback is rendered into the display of the test question with in the concrete question types outQuestionForTest-method.
992  // Notation of the params prior to getting rid of this crap in favor of a class
993  $questionGui->outQuestionForTest(
994  $formAction, #form_action
995  $this->test_session->getActiveId(), #active_id
996  // hey: prevPassSolutions - prepared pass index having no, current or previous solution
997  $passIndex, #pass
998  // hey.
999  $isPostponed, #is_postponed
1000  $userPostSolution, #user_post_solution
1001  $answerFeedbackEnabled #answer_feedback == inline_specific_feedback
1002  );
1003  // The display of specific inline feedback and specific feedback in an own block is to honor questions, which
1004  // have the possibility to embed the specific feedback into their output while maintaining compatibility to
1005  // questions, which do not have such facilities. E.g. there can be no "specific inline feedback" for essay
1006  // questions, while the multiple-choice questions do well.
1007 
1008 
1009  $this->populateModals();
1010 
1011  // fau: testNav - pouplate the new question edit control instead of the deprecated intermediate solution saver
1012  $this->populateQuestionEditControl($questionGui);
1013  // fau.
1014  }
1015 
1016  // hey: prevPassSolutions - determine solution pass index
1017  protected function determineSolutionPassIndex(assQuestionGUI $questionGui): int
1018  {
1019  if ($this->object->isPreviousSolutionReuseEnabled($this->test_session->getActiveId())) {
1020  $currentSolutionAvailable = $questionGui->object->authorizedOrIntermediateSolutionExists(
1021  $this->test_session->getActiveId(),
1022  $this->test_session->getPass()
1023  );
1024 
1025  if (!$currentSolutionAvailable) {
1026  $previousPass = $questionGui->object->getSolutionMaxPass(
1027  $this->test_session->getActiveId()
1028  );
1029 
1030  $previousSolutionAvailable = $questionGui->object->authorizedSolutionExists(
1031  $this->test_session->getActiveId(),
1032  $previousPass
1033  );
1034 
1035  if ($previousSolutionAvailable) {
1036  return $previousPass;
1037  }
1038 
1039  }
1040  }
1041 
1042  return $this->test_session->getPass();
1043  }
1044  // hey.
1045 
1046  abstract protected function showQuestionCmd();
1047 
1048  abstract protected function editSolutionCmd();
1049 
1050  abstract protected function submitSolutionCmd();
1051 
1052  // fau: testNav - new function to revert probably auto-saved changes and show the last submitted question state
1053  protected function revertChangesCmd()
1054  {
1055  $this->removeIntermediateSolution();
1056  $this->setAnswerChangedParameter(false);
1057  $this->ctrl->saveParameter($this, 'sequence');
1058  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
1059  }
1060  // fau.
1061 
1062  abstract protected function discardSolutionCmd();
1063 
1064  abstract protected function skipQuestionCmd();
1065 
1066  abstract protected function startTestCmd();
1067 
1068  public function checkOnlineTestAccess()
1069  {
1070  $user_filtered_for_invitation = $this->object->getInvitedUsers($this->user->getId());
1071  if ($user_filtered_for_invitation === []) {
1072  $this->tpl->setOnScreenMessage('info', $this->lng->txt('user_not_invited'), true);
1073  $this->ctrl->redirectByClass('ilobjtestgui', 'backToRepository');
1074  }
1075 
1076  $client_ip = $user_filtered_for_invitation['client_ip'];
1077  if ($client_ip !== ''
1078  && $client_ip !== $_SERVER['REMOTE_ADDR']) {
1079  $this->tpl->setOnScreenMessage('info', $this->lng->txt('user_wrong_clientip'), true);
1080  $this->ctrl->redirectByClass('ilobjtestgui', 'backToRepository');
1081  }
1082  }
1083 
1084 
1088  public function isTestAccessible(): bool
1089  {
1090  return !$this->isNrOfTriesReached()
1091  and !$this->isMaxProcessingTimeReached()
1092  and $this->object->startingTimeReached()
1093  and !$this->object->endingTimeReached();
1094  }
1095 
1099  public function isNrOfTriesReached(): bool
1100  {
1101  return $this->object->hasNrOfTriesRestriction() && $this->object->isNrOfTriesReached($this->test_session->getPass());
1102  }
1103 
1109  public function endingTimeReached()
1110  {
1111  $this->tpl->setOnScreenMessage('info', sprintf($this->lng->txt("detail_ending_time_reached"), ilDatePresentation::formatDate(new ilDateTime($this->object->getEndingTime(), IL_CAL_UNIX))));
1112  $this->test_session->increasePass();
1113  $this->test_session->setLastSequence(0);
1114  $this->test_session->saveToDb();
1115 
1116  $this->redirectBackCmd();
1117  }
1118 
1126  public function maxProcessingTimeReached()
1127  {
1128  $this->suspendTestCmd();
1129  }
1130 
1136  public function confirmSubmitAnswers()
1137  {
1138  $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_submit_answers_confirm.html", "Modules/Test");
1139  $this->tpl->setCurrentBlock("adm_content");
1140  if ($this->object->isTestFinished($this->test_session->getActiveId())) {
1141  $this->tpl->setCurrentBlock("not_submit_allowed");
1142  $this->tpl->setVariable("TEXT_ALREADY_SUBMITTED", $this->lng->txt("tst_already_submitted"));
1143  $this->tpl->setVariable("BTN_OK", $this->lng->txt("tst_show_answer_sheet"));
1144  } else {
1145  $this->tpl->setCurrentBlock("submit_allowed");
1146  $this->tpl->setVariable("TEXT_CONFIRM_SUBMIT_RESULTS", $this->lng->txt("tst_confirm_submit_answers"));
1147  $this->tpl->setVariable("BTN_OK", $this->lng->txt("tst_submit_results"));
1148  }
1149  $this->tpl->setVariable("BTN_BACK", $this->lng->txt("back"));
1150  $this->tpl->setVariable("FORMACTION", $this->ctrl->getFormAction($this, "finalSubmission"));
1151  $this->tpl->parseCurrentBlock();
1152  }
1153 
1154  private function outProcessingTime(int $active_id, bool $verbose): void
1155  {
1156  $starting_time = $this->object->getStartingTimeOfUser($active_id);
1157  $working_time = new WorkingTime(
1158  $this->lng,
1159  $this->ui_factory,
1160  $this->ui_renderer,
1161  $starting_time,
1162  $this->object->getProcessingTimeInSeconds($active_id)
1163  );
1164 
1165  $this->tpl->setCurrentBlock('enableprocessingtime');
1166  $this->tpl->setVariable('USER_WORKING_TIME_MESSAGE_BOX', $working_time->getMessageBox($verbose));
1167  $this->tpl->parseCurrentBlock();
1168 
1169  $working_time_js_template = $working_time->prepareWorkingTimeJsTemplate(
1170  $this->getObject(),
1171  getdate($starting_time),
1172  $this->ctrl->getLinkTarget($this, 'checkWorkingTime', '', true),
1173  $this->ctrl->getFormAction($this, ilTestPlayerCommands::REDIRECT_AFTER_QUESTION_LIST)
1174  );
1175 
1176  $this->tpl->addOnLoadCode($working_time_js_template->get());
1177  }
1178 
1186  public function checkWorkingTimeCmd(): void
1187  {
1188  $active_id = $this->test_session->getActiveId();
1189  echo (string) $this->object->getProcessingTimeInSeconds($active_id);
1190  exit;
1191  }
1192 
1193  protected function showSideList($current_sequence_element): void
1194  {
1195  $question_summary_data = $this->service->getQuestionSummaryData($this->testSequence, false);
1196  $questions = [];
1197  $active = 0;
1198 
1199  foreach ($question_summary_data as $idx => $row) {
1200  $title = htmlspecialchars($row['title'], ENT_QUOTES, null, false);
1201  $description = '';
1202  if ($row['description'] !== '') {
1203  $description = htmlspecialchars($row['description'], ENT_QUOTES, null, false);
1204  }
1205 
1206  if (!$row['disabled']) {
1207  $this->ctrl->setParameter($this, 'pmode', '');
1208  $this->ctrl->setParameter($this, 'sequence', $row['sequence']);
1209  $action = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::SHOW_QUESTION);
1210  $this->ctrl->setParameter($this, 'pmode', ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW);
1211  $this->ctrl->setParameter($this, 'sequence', $this->getCurrentSequenceElement($current_sequence_element));
1212  }
1213 
1215 
1216  if (
1217  ($row['worked_through'] || $row['isAnswered'])
1218  && $row['has_authorized_answer']
1219  ) {
1221  }
1222 
1223  $questions[] = $this->ui_factory->listing()->workflow()
1224  ->step($title, $description, $action)
1225  ->withStatus($status);
1226  $active = $row['sequence'] == $current_sequence_element ? $idx : $active;
1227  }
1228 
1229  $question_listing = $this->ui_factory->listing()->workflow()->linear(
1230  $this->lng->txt('mainbar_button_label_questionlist'),
1231  $questions
1232  )->withActive($active);
1233 
1234 
1235  $this->global_screen->tool()->context()->current()->addAdditionalData(
1237  $question_listing
1238  );
1239  }
1240 
1241  abstract protected function isQuestionSummaryFinishTestButtonRequired();
1242 
1246  public function outQuestionSummaryCmd(
1247  bool $obligations_info = false,
1248  bool $obligations_filter = false
1249  ) {
1250  $this->help->setScreenIdComponent("tst");
1251  $this->help->setScreenId("assessment");
1252  $this->help->setSubScreenId("question_summary");
1253 
1254  $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_question_summary.html", "Modules/Test");
1255 
1256  $this->global_screen->tool()->context()->current()->getAdditionalData()->replace(
1258  $this->getObject()->getTitle() . ' - ' . $this->lng->txt('question_summary')
1259  );
1260 
1261  if ($obligations_info
1262  && $this->object->areObligationsEnabled()
1263  && !$this->object->allObligationsAnswered()) {
1264  $this->tpl->setOnScreenMessage('failure', $this->lng->txt('not_all_obligations_answered'));
1265  }
1266 
1267  $active_id = $this->test_session->getActiveId();
1268  $question_summary_data = $this->service->getQuestionSummaryData($this->testSequence, $obligations_filter);
1269 
1270  $this->ctrl->setParameter($this, "sequence", $this->testrequest->raw("sequence"));
1271 
1272  $table_gui = new ilListOfQuestionsTableGUI(
1273  $this,
1274  'showQuestion',
1275  $this->ui_factory,
1276  $this->ui_renderer
1277  );
1278  if (($this->object->getNrOfTries() - 1) === $this->test_session->getPass()) {
1279  $table_gui->setUserHasAttemptsLeft(false);
1280  }
1281  $table_gui->setShowPointsEnabled(!$this->object->getTitleOutput());
1282  $table_gui->setShowMarkerEnabled($this->object->getShowMarker());
1283  $table_gui->setObligationsNotAnswered(!$this->object->allObligationsAnswered());
1284  $table_gui->setShowObligationsEnabled($this->object->areObligationsEnabled());
1285  $table_gui->setObligationsFilterEnabled($obligations_filter);
1286  $table_gui->setFinishTestButtonEnabled($this->isQuestionSummaryFinishTestButtonRequired());
1287 
1288  $table_gui->init();
1289 
1290  $table_gui->setData($question_summary_data);
1291 
1292  $this->tpl->setVariable('TABLE_LIST_OF_QUESTIONS', $table_gui->getHTML());
1293 
1294  if ($this->object->getEnableProcessingTime()) {
1295  $this->outProcessingTime($active_id, true);
1296  }
1297 
1298  if ($this->object->isShowExamIdInTestPassEnabled()) {
1299  $this->tpl->setCurrentBlock('exam_id_footer');
1300  $this->tpl->setVariable('EXAM_ID_VAL', ilObjTest::lookupExamId(
1301  $this->test_session->getActiveId(),
1302  $this->test_session->getPass(),
1303  $this->object->getId()
1304  ));
1305  $this->tpl->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id'));
1306  $this->tpl->parseCurrentBlock();
1307  }
1308  }
1309 
1311  {
1312  $this->outQuestionSummaryCmd(true, false);
1313  }
1314 
1316  {
1317  $this->outQuestionSummaryCmd(true, true);
1318  }
1319 
1320  public function backFromFinishingCmd()
1321  {
1322  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
1323  }
1324 
1328  public function outCorrectSolution(): void
1329  {
1330  $this->tpl->addBlockFile("ADM_CONTENT", "adm_content", "tpl.il_as_tst_correct_solution.html", "Modules/Test");
1331 
1332  $this->tpl->setCurrentBlock("ContentStyle");
1333  $this->tpl->setVariable("LOCATION_CONTENT_STYLESHEET", ilObjStyleSheet::getContentStylePath(0));
1334  $this->tpl->parseCurrentBlock();
1335 
1336  $this->tpl->setCurrentBlock("SyntaxStyle");
1337  $this->tpl->setVariable("LOCATION_SYNTAX_STYLESHEET", ilObjStyleSheet::getSyntaxStylePath());
1338  $this->tpl->parseCurrentBlock();
1339 
1340  $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print.css", "Modules/Test"), "print");
1341  if ($this->object->getShowSolutionAnswersOnly()) {
1342  $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print_hide_content.css", "Modules/Test"), "print");
1343  }
1344 
1345  $this->tpl->setCurrentBlock("adm_content");
1346  $solution = $this->getCorrectSolutionOutput($this->testrequest->raw("evaluation"), $this->testrequest->raw("active_id"), $this->testrequest->raw("pass"));
1347  $this->tpl->setVariable("OUTPUT_SOLUTION", $solution);
1348  $this->tpl->setVariable("TEXT_BACK", $this->lng->txt("back"));
1349  $this->ctrl->saveParameter($this, "pass");
1350  $this->ctrl->saveParameter($this, "active_id");
1351  $this->tpl->setVariable("URL_BACK", $this->ctrl->getLinkTarget($this, "outUserResultsOverview"));
1352  $this->tpl->parseCurrentBlock();
1353  }
1354 
1364  public function showListOfAnswers($active_id, $pass = null, $top_data = "", $bottom_data = "")
1365  {
1366  $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_finish_list_of_answers.html", "Modules/Test");
1367 
1368  $result_array = &$this->object->getTestResult(
1369  $active_id,
1370  $pass,
1371  false,
1372  !$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()
1373  );
1374 
1375  $counter = 1;
1376  // output of questions with solutions
1377  foreach ($result_array as $question_data) {
1378  $question = $question_data["qid"];
1379  if (is_numeric($question)) {
1380  $this->tpl->setCurrentBlock("printview_question");
1381  $question_gui = $this->object->createQuestionGUI("", $question);
1382  $template = new ilTemplate("tpl.il_as_qpl_question_printview.html", true, true, "Modules/TestQuestionPool");
1383  $template->setVariable("COUNTER_QUESTION", $counter . ". ");
1384  $template->setVariable("QUESTION_TITLE", $question_gui->object->getTitleForHTMLOutput());
1385 
1386  $show_question_only = ($this->object->getShowSolutionAnswersOnly()) ? true : false;
1387  $result_output = $question_gui->getSolutionOutput(
1388  $active_id,
1389  $pass,
1390  false,
1391  false,
1392  $show_question_only,
1393  $this->object->getShowSolutionFeedback()
1394  );
1395  $template->setVariable("SOLUTION_OUTPUT", $result_output);
1396  $this->tpl->setVariable("QUESTION_OUTPUT", $template->get());
1397  $this->tpl->parseCurrentBlock();
1398  $counter++;
1399  }
1400  }
1401 
1402  $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print.css", "Modules/Test"), "print");
1403  if ($this->object->getShowSolutionAnswersOnly()) {
1404  $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print_hide_content.css", "Modules/Test"), "print");
1405  }
1406  if (strlen($top_data)) {
1407  $this->tpl->setCurrentBlock("top_data");
1408  $this->tpl->setVariable("TOP_DATA", $top_data);
1409  $this->tpl->parseCurrentBlock();
1410  }
1411 
1412  if (strlen($bottom_data)) {
1413  $this->tpl->setCurrentBlock("bottom_data");
1414  $this->tpl->setVariable("FORMACTION", $this->ctrl->getFormAction($this));
1415  $this->tpl->setVariable("BOTTOM_DATA", $bottom_data);
1416  $this->tpl->parseCurrentBlock();
1417  }
1418 
1419  $this->tpl->setCurrentBlock("adm_content");
1420  $this->tpl->setVariable("TXT_ANSWER_SHEET", $this->lng->txt("tst_list_of_answers"));
1421  $user_data = $this->getAdditionalUsrDataHtmlAndPopulateWindowTitle($this->test_session, $active_id, true);
1422  $signature = $this->getResultsSignature();
1423  $this->tpl->setVariable("USER_DETAILS", $user_data);
1424  $this->tpl->setVariable("SIGNATURE", $signature);
1425  $this->tpl->setVariable("TITLE", $this->object->getTitle());
1426  $this->tpl->setVariable("TXT_TEST_PROLOG", $this->lng->txt("tst_your_answers"));
1427  $invited_user = &$this->object->getInvitedUsers($this->user->getId());
1428  $pagetitle = $this->object->getTitle() . " - " . $this->lng->txt("clientip") .
1429  ": " . $invited_user[$this->user->getId()]["clientip"] . " - " .
1430  $this->lng->txt("matriculation") . ": " .
1431  $invited_user[$this->user->getId()]["matriculation"];
1432  $this->tpl->setVariable("PAGETITLE", $pagetitle);
1433  $this->tpl->parseCurrentBlock();
1434  }
1435 
1441  public function getContentBlockName(): string
1442  {
1443  return "ADM_CONTENT";
1444 
1445  if ($this->object->getKioskMode()) {
1446  $this->tpl->setBodyClass("kiosk");
1447  $this->tpl->hideFooter();
1448  return "CONTENT";
1449  } else {
1450  return "ADM_CONTENT";
1451  }
1452  }
1453 
1454  public function outUserResultsOverviewCmd()
1455  {
1456  $this->ctrl->redirectByClass(
1457  ['ilRepositoryGUI', 'ilObjTestGUI', 'ilTestEvaluationGUI'],
1458  "outUserResultsOverview"
1459  );
1460  }
1461 
1465  protected function showRequestedHintListCmd()
1466  {
1467  // fau: testNav - handle intermediate submit for viewing requested hints
1468  $this->handleIntermediateSubmit();
1469  // fau.
1470 
1471  $this->ctrl->setParameter($this, 'pmode', self::PRESENTATION_MODE_EDIT);
1472 
1473  $this->ctrl->redirectByClass('ilAssQuestionHintRequestGUI', ilAssQuestionHintRequestGUI::CMD_SHOW_LIST);
1474  }
1475 
1479  protected function confirmHintRequestCmd()
1480  {
1481  // fau: testNav - handle intermediate submit for confirming hint requests
1482  $this->handleIntermediateSubmit();
1483  // fau.
1484 
1485  $this->ctrl->setParameter($this, 'pmode', self::PRESENTATION_MODE_EDIT);
1486 
1487  $this->ctrl->redirectByClass('ilAssQuestionHintRequestGUI', ilAssQuestionHintRequestGUI::CMD_CONFIRM_REQUEST);
1488  }
1489 
1490  abstract protected function isFirstQuestionInSequence($sequenceElement);
1491 
1492  abstract protected function isLastQuestionInSequence($sequenceElement);
1493 
1494 
1495  abstract protected function handleQuestionActionCmd();
1496 
1497  abstract protected function showInstantResponseCmd();
1498 
1499  abstract protected function nextQuestionCmd();
1500 
1501  abstract protected function previousQuestionCmd();
1502 
1503  protected function prepareSummaryPage()
1504  {
1505  $this->tpl->addBlockFile(
1506  $this->getContentBlockName(),
1507  'adm_content',
1508  'tpl.il_as_tst_question_summary.html',
1509  'Modules/Test'
1510  );
1511  }
1512 
1513  protected function initTestPageTemplate()
1514  {
1515  $onload_js = <<<JS
1516  let key_event = (event) => {
1517  if( event.key === 13 && event.target.tagName.toLowerCase() === "a" ) {
1518  return;
1519  }
1520  if (event.key === 13 &&
1521  event.target.tagName.toLowerCase() !== "textarea" &&
1522  (event.target.tagName.toLowerCase() !== "input" || event.target.type.toLowerCase() !== "submit")) {
1523  event.preventDefault();
1524  }
1525  };
1526 
1527  let form = document.getElementById('taForm');
1528  form.onkeyup = key_event;
1529  form.onkeydown = key_event;
1530  form.onkeypress = key_event;
1531 JS;
1532  $this->tpl->addOnLoadCode($onload_js);
1533  $this->tpl->addBlockFile(
1534  $this->getContentBlockName(),
1535  'adm_content',
1536  'tpl.il_as_tst_output.html',
1537  'Modules/Test'
1538  );
1539  }
1540 
1542  {
1549  if ($this->test_session->isPasswordChecked() === true) {
1550  return;
1551  }
1552 
1553  if ($this->ctrl->getNextClass() === 'iltestpasswordprotectiongui') {
1554  return;
1555  }
1556 
1557  if (!$this->passwordChecker->isPasswordProtectionPageRedirectRequired()) {
1558  $this->test_session->setPasswordChecked(true);
1559  return;
1560  }
1561 
1562  $this->ctrl->setParameterByClass(self::class, 'lock', $this->getLockParameter());
1563 
1564  $next_command = $this->ctrl->getCmdClass() . '::' . ilTestPlayerCommands::START_TEST;
1565  $this->ctrl->setParameterByClass(ilTestPasswordProtectionGUI::class, 'nextCommand', $next_command);
1566  $this->ctrl->redirectByClass(ilTestPasswordProtectionGUI::class, 'showPasswordForm');
1567  }
1568 
1569  protected function isParticipantsAnswerFixed($questionId): bool
1570  {
1571  if ($this->object->isInstantFeedbackAnswerFixationEnabled() && $this->testSequence->isQuestionChecked($questionId)) {
1572  return true;
1573  }
1574 
1575  if ($this->object->isFollowupQuestionAnswerFixationEnabled() && $this->testSequence->isNextQuestionPresented($questionId)) {
1576  return true;
1577  }
1578 
1579  return false;
1580  }
1581 
1585  protected function getIntroductionPageButtonLabel(): string
1586  {
1587  return $this->lng->txt("save_introduction");
1588  }
1589 
1590  protected function initAssessmentSettings()
1591  {
1592  $this->assSettings = new ilSetting('assessment');
1593  }
1594 
1598  protected function handleSkillTriggering(ilTestSession $test_session)
1599  {
1600  $questionList = $this->buildTestPassQuestionList();
1601  $questionList->load();
1602 
1603  $testResults = $this->object->getTestResult($test_session->getActiveId(), $test_session->getPass(), true);
1604 
1605  $skillEvaluation = new ilTestSkillEvaluation(
1606  $this->db,
1607  $this->logging_services,
1608  $this->object->getTestId(),
1609  $this->object->getRefId(),
1610  $this->skills_service->profile(),
1611  $this->skills_service->personal()
1612  );
1613 
1614  $skillEvaluation->setUserId($test_session->getUserId());
1615  $skillEvaluation->setActiveId($test_session->getActiveId());
1616  $skillEvaluation->setPass($test_session->getPass());
1617 
1618  $skillEvaluation->setNumRequiredBookingsForSkillTriggering((int) $this->assSettings->get(
1619  'ass_skl_trig_num_answ_barrier',
1621  ));
1622 
1623 
1624  $skillEvaluation->init($questionList);
1625  $skillEvaluation->evaluate($testResults);
1626 
1627  $skillEvaluation->handleSkillTriggering();
1628  }
1629 
1630  abstract protected function buildTestPassQuestionList();
1631 
1633  {
1634  $confirmation = new ilTestAnswerOptionalQuestionsConfirmationGUI($this->lng);
1635 
1636  $confirmation->setFormAction($this->ctrl->getFormAction($this));
1637  $confirmation->setCancelCmd('cancelAnswerOptionalQuestions');
1638  $confirmation->setConfirmCmd('confirmAnswerOptionalQuestions');
1639 
1640  $confirmation->build($this->object->isFixedTest());
1641 
1642  $this->populateHelperGuiContent($confirmation);
1643  }
1644 
1646  {
1647  $this->testSequence->setAnsweringOptionalQuestionsConfirmed(true);
1648  $this->testSequence->saveToDb();
1649 
1650  $this->ctrl->setParameter($this, 'activecommand', 'gotoquestion');
1651  $this->ctrl->redirect($this, 'redirectQuestion');
1652  }
1653 
1655  {
1656  if ($this->object->getListOfQuestions()) {
1657  $this->ctrl->setParameter($this, 'activecommand', 'summary');
1658  } else {
1659  $this->ctrl->setParameter($this, 'activecommand', 'previous');
1660  }
1661 
1662  $this->ctrl->redirect($this, 'redirectQuestion');
1663  }
1664 
1668  protected function populateHelperGuiContent($helperGui)
1669  {
1670  if ($this->object->getKioskMode()) {
1671  //$this->tpl->setBodyClass("kiosk");
1672  $this->tpl->hideFooter();
1673  $this->tpl->addBlockfile('CONTENT', 'adm_content', "tpl.il_as_tst_kiosk_mode_content.html", "Modules/Test");
1674  $this->tpl->setContent($this->ctrl->getHTML($helperGui));
1675  } else {
1676  $this->tpl->setVariable($this->getContentBlockName(), $this->ctrl->getHTML($helperGui));
1677  }
1678  }
1679 
1681  {
1682  $navigation_toolbar = new ilTestNavigationToolbarGUI($this->ctrl, $this);
1683  $navigation_toolbar->setSuspendTestButtonEnabled($this->object->getShowCancel());
1684  $navigation_toolbar->setUserPassOverviewEnabled($this->object->getUsrPassOverviewEnabled());
1685  $navigation_toolbar->setFinishTestCommand($this->getFinishTestCommand());
1686  return $navigation_toolbar;
1687  }
1688 
1690  {
1691  $navigationGUI = new ilTestQuestionNavigationGUI(
1692  $this->lng,
1693  $this->ui_factory,
1694  $this->ui_renderer
1695  );
1696 
1697  if (!$this->isParticipantsAnswerFixed($questionId)) {
1698  $navigationGUI->setEditSolutionCommand(ilTestPlayerCommands::EDIT_SOLUTION);
1699  }
1700 
1701  if ($this->object->getShowMarker()) {
1702  $solved_array = ilObjTest::_getSolvedQuestions($this->test_session->getActiveId(), $questionId);
1703  $solved = 0;
1704 
1705  if (count($solved_array) > 0) {
1706  $solved = array_pop($solved_array);
1707  $solved = $solved["solved"];
1708  }
1709  // fau: testNav - change question mark command to link target
1710  if ($solved == 1) {
1711  $navigationGUI->setQuestionMarkLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::UNMARK_QUESTION));
1712  $navigationGUI->setQuestionMarked(true);
1713  } else {
1714  $navigationGUI->setQuestionMarkLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::MARK_QUESTION));
1715  $navigationGUI->setQuestionMarked(false);
1716  }
1717  }
1718  // fau.
1719 
1720  return $navigationGUI;
1721  }
1722 
1724  {
1725  $navigationGUI = new ilTestQuestionNavigationGUI(
1726  $this->lng,
1727  $this->ui_factory,
1728  $this->ui_renderer
1729  );
1730 
1731  if ($this->object->isForceInstantFeedbackEnabled()) {
1732  $navigationGUI->setSubmitSolutionCommand(ilTestPlayerCommands::SUBMIT_SOLUTION);
1733  } else {
1734  // fau: testNav - use simple "submitSolution" button instead of "submitSolutionAndNext"
1735  $navigationGUI->setSubmitSolutionCommand(ilTestPlayerCommands::SUBMIT_SOLUTION);
1736  // fau.
1737  }
1738 
1739  // fau: testNav - add a 'revert changes' link for editable question
1740  $navigationGUI->setRevertChangesLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::REVERT_CHANGES));
1741  // fau.
1742 
1743 
1744  // feedback
1745  switch (1) {
1746  case $this->object->getSpecificAnswerFeedback():
1747  case $this->object->getGenericAnswerFeedback():
1748  case $this->object->getAnswerFeedbackPoints():
1749  case $this->object->getInstantFeedbackSolution():
1750 
1751  $navigationGUI->setAnswerFreezingEnabled($this->object->isInstantFeedbackAnswerFixationEnabled());
1752 
1753  if ($this->object->isForceInstantFeedbackEnabled()) {
1754  $navigationGUI->setForceInstantResponseEnabled(true);
1755  $navigationGUI->setInstantFeedbackCommand(ilTestPlayerCommands::SUBMIT_SOLUTION);
1756  } else {
1757  $navigationGUI->setInstantFeedbackCommand(ilTestPlayerCommands::SHOW_INSTANT_RESPONSE);
1758  }
1759  }
1760 
1761  // hints
1762  if ($this->object->isOfferingQuestionHintsEnabled()) {
1763  $activeId = $this->test_session->getActiveId();
1764  $pass = $this->test_session->getPass();
1765 
1766  $questionHintTracking = new ilAssQuestionHintTracking($questionId, $activeId, $pass);
1767 
1768  if ($questionHintTracking->requestsPossible()) {
1769  $navigationGUI->setRequestHintCommand(ilTestPlayerCommands::CONFIRM_HINT_REQUEST);
1770  }
1771 
1772  if ($questionHintTracking->requestsExist()) {
1773  $navigationGUI->setShowHintsCommand(ilTestPlayerCommands::SHOW_REQUESTED_HINTS_LIST);
1774  }
1775  }
1776 
1777  if ($this->object->getShowMarker()) {
1778  $solved_array = ilObjTest::_getSolvedQuestions($this->test_session->getActiveId(), $questionId);
1779  $solved = 0;
1780 
1781  if (count($solved_array) > 0) {
1782  $solved = array_pop($solved_array);
1783  $solved = $solved["solved"];
1784  }
1785 
1786  // fau: testNav - change question mark command to link target
1787  if ($solved == 1) {
1788  $navigationGUI->setQuestionMarkLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::UNMARK_QUESTION_SAVE));
1789  $navigationGUI->setQuestionMarked(true);
1790  } else {
1791  $navigationGUI->setQuestionMarkLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::MARK_QUESTION_SAVE));
1792  $navigationGUI->setQuestionMarked(false);
1793  }
1794  }
1795  // fau.
1796  return $navigationGUI;
1797  }
1798 
1802  protected function getFinishTestCommand(): string
1803  {
1804  if (!$this->object->getListOfQuestionsEnd()) {
1806  }
1807 
1808  if ($this->object->areObligationsEnabled()
1809  && !$this->object->allObligationsAnswered()) {
1811  }
1812 
1814  }
1815 
1816  protected function populateInstantResponseModal(assQuestionGUI $questionGui, $navUrl)
1817  {
1818  $questionGui->setNavigationGUI(null);
1819  $questionGui->getQuestionHeaderBlockBuilder()->setQuestionAnswered(true);
1820 
1821  $answerFeedbackEnabled = $this->object->getSpecificAnswerFeedback();
1822 
1823  $solutionoutput = $questionGui->getSolutionOutput(
1824  $this->test_session->getActiveId(), #active_id
1825  $this->test_session->getPass(), #pass
1826  false, #graphical_output
1827  false, #result_output
1828  true, #show_question_only
1829  $answerFeedbackEnabled, #show_feedback
1830  false, #show_correct_solution
1831  false, #show_manual_scoring
1832  true #show_question_text
1833  );
1834 
1835  $pageoutput = $questionGui->outQuestionPage(
1836  "",
1837  $this->isShowingPostponeStatusReguired($questionGui->object->getId()),
1838  $this->test_session->getActiveId(),
1839  $solutionoutput
1840  );
1841 
1842  $tpl = new ilTemplate('tpl.tst_player_response_modal.html', true, true, 'Modules/Test');
1843 
1844  // populate the instant response blocks in the
1845  $saved_tpl = $this->tpl;
1846  $this->tpl = $tpl;
1847  $this->populateInstantResponseBlocks($questionGui, true);
1848  $this->tpl = $saved_tpl;
1849 
1850  $tpl->setVariable('QUESTION_OUTPUT', $pageoutput);
1851 
1852  $button = $this->ui_factory->button()->primary($this->lng->txt('proceed'), $navUrl);
1853  $tpl->setVariable('BUTTON', $this->ui_renderer->render($button));
1854 
1855  $modal = ilModalGUI::getInstance();
1856  $modal->setType(ilModalGUI::TYPE_LARGE);
1857  $modal->setId('tst_question_feedback_modal');
1858  $modal->setHeading($this->lng->txt('tst_instant_feedback'));
1859  $modal->setBody($tpl->get());
1860 
1861  $this->tpl->addOnLoadCode("$('#tst_question_feedback_modal').modal('show');");
1862  $this->tpl->setVariable('INSTANT_RESPONSE_MODAL', $modal->getHTML());
1863  }
1864  // fau;
1865 
1869  protected function populateInstantResponseBlocks(assQuestionGUI $questionGui, $authorizedSolution)
1870  {
1871  $response_available = false;
1872  $jump_to_response = false;
1873 
1874  // This controls if the solution should be shown.
1875  // It gets the parameter "Scoring and Results" -> "Instant Feedback" -> "Show Solutions"
1876  if ($this->object->getInstantFeedbackSolution()) {
1877  $show_question_inline_score = $this->determineInlineScoreDisplay();
1878 
1879  // Notation of the params prior to getting rid of this crap in favor of a class
1880  $solutionoutput = $questionGui->getSolutionOutput(
1881  $this->test_session->getActiveId(), #active_id
1882  $this->test_session->getPass(), #pass
1883  false, #graphical_output
1884  $show_question_inline_score, #result_output
1885  true, #show_question_only
1886  false, #show_feedback
1887  true, #show_correct_solution
1888  false, #show_manual_scoring
1889  false #show_question_text
1890  );
1891  $solutionoutput = str_replace('<h1 class="ilc_page_title_PageTitle"></h1>', '', $solutionoutput);
1892  $this->populateSolutionBlock($solutionoutput);
1893  $response_available = true;
1894  $jump_to_response = true;
1895  }
1896 
1897  $reachedPoints = $questionGui->object->getAdjustedReachedPoints(
1898  $this->test_session->getActiveId(),
1899  ilObjTest::_getPass($this->test_session->getActiveId()),
1900  $authorizedSolution
1901  );
1902 
1903  $maxPoints = $questionGui->object->getMaximumPoints();
1904 
1905  $solutionCorrect = ($reachedPoints == $maxPoints);
1906 
1907  // This controls if the score should be shown.
1908  // It gets the parameter "Scoring and Results" -> "Instant Feedback" -> "Show Results (Only Points)"
1909  if ($this->object->getAnswerFeedbackPoints()) {
1910  $this->populateScoreBlock($reachedPoints, $maxPoints);
1911  $response_available = true;
1912  $jump_to_response = true;
1913  }
1914 
1915  // This controls if the generic feedback should be shown.
1916  // It gets the parameter "Scoring and Results" -> "Instant Feedback" -> "Show Solutions"
1917  if ($this->object->getGenericAnswerFeedback()) {
1918  if ($this->populateGenericFeedbackBlock($questionGui, $solutionCorrect)) {
1919  $response_available = true;
1920  $jump_to_response = true;
1921  }
1922  }
1923 
1924  // This controls if the specific feedback should be shown.
1925  // It gets the parameter "Scoring and Results" -> "Instant Feedback" -> "Show Answer-Specific Feedback"
1926  if ($this->object->getSpecificAnswerFeedback()) {
1927  if ($questionGui->hasInlineFeedback()) {
1928  // Don't jump to the feedback below the question if some feedback is shown within the question
1929  $jump_to_response = false;
1930  } elseif ($this->populateSpecificFeedbackBlock($questionGui)) {
1931  $response_available = true;
1932  $jump_to_response = true;
1933  }
1934  }
1935 
1936  $this->populateFeedbackBlockHeader($jump_to_response);
1937  if (!$response_available) {
1938  if ($questionGui->hasInlineFeedback()) {
1939  $this->populateFeedbackBlockMessage($this->lng->txt('tst_feedback_is_given_inline'));
1940  } else {
1941  $this->populateFeedbackBlockMessage($this->lng->txt('tst_feedback_not_available_for_answer'));
1942  }
1943  }
1944  }
1945 
1946  protected function populateFeedbackBlockHeader($withFocusAnchor)
1947  {
1948  if ($withFocusAnchor) {
1949  $this->tpl->setCurrentBlock('inst_resp_id');
1950  $this->tpl->setVariable('INSTANT_RESPONSE_FOCUS_ID', 'focus');
1951  $this->tpl->parseCurrentBlock();
1952  }
1953 
1954  $this->tpl->setCurrentBlock('instant_response_header');
1955  $this->tpl->setVariable('INSTANT_RESPONSE_HEADER', $this->lng->txt('tst_feedback'));
1956  $this->tpl->parseCurrentBlock();
1957  }
1958 
1959  protected function populateFeedbackBlockMessage(string $a_message)
1960  {
1961  $this->tpl->setCurrentBlock('instant_response_message');
1962  $this->tpl->setVariable('INSTANT_RESPONSE_MESSAGE', $a_message);
1963  $this->tpl->parseCurrentBlock();
1964  }
1965 
1966 
1967  protected function getCurrentSequenceElement(): int
1968  {
1969  if ($this->getSequenceElementParameter()) {
1970  return $this->getSequenceElementParameter();
1971  }
1972 
1973  return $this->test_session->getLastSequence();
1974  }
1975 
1976  protected function getSequenceElementParameter(): ?int
1977  {
1978  if ($this->testrequest->isset('sequence')) {
1979  return $this->testrequest->int('sequence');
1980  }
1981 
1982  return null;
1983  }
1984 
1985  protected function getPresentationModeParameter()
1986  {
1987  if ($this->testrequest->isset('pmode')) {
1988  return $this->testrequest->raw('pmode');
1989  }
1990 
1991  return null;
1992  }
1993 
1994  protected function getInstantResponseParameter()
1995  {
1996  if ($this->testrequest->isset('instresp')) {
1997  return $this->testrequest->raw('instresp');
1998  }
1999 
2000  return null;
2001  }
2002 
2003  protected function getNextCommandParameter()
2004  {
2005  $nextcmd = '';
2006  if ($this->testrequest->isset('nextcmd')) {
2007  $nextcmd = $this->testrequest->strVal('nextcmd');
2008  }
2009 
2010  return $nextcmd !== '' ? $nextcmd : null;
2011  }
2012 
2013  protected function getNextSequenceParameter(): ?int
2014  {
2015  if (isset($_POST['nextseq']) && is_numeric($_POST['nextseq'])) {
2016  return (int) $_POST['nextseq'];
2017  }
2018 
2019  return null;
2020  }
2021 
2022  // fau: testNav - get the navigation url set by a submit from ilTestPlayerNavigationControl.js
2023  protected function getNavigationUrlParameter(): ?string
2024  {
2025  if (isset($_POST['test_player_navigation_url'])) {
2026  $navigation_url = $_POST['test_player_navigation_url'];
2027 
2028  $navigation_url_parts = parse_url($navigation_url);
2029  $ilias_url_parts = parse_url(ilUtil::_getHttpPath());
2030 
2031  if (!isset($navigation_url_parts['host']) || ($ilias_url_parts['host'] === $navigation_url_parts['host'])) {
2032  return $navigation_url;
2033  }
2034  }
2035 
2036  return null;
2037  }
2038  // fau.
2039 
2040  // fau: testNav - get set and check the 'answer_changed' url parameter
2046  protected function getAnswerChangedParameter(): bool
2047  {
2048  return !empty($this->testrequest->raw('test_answer_changed'));
2049  }
2050 
2055  protected function setAnswerChangedParameter($changed = true)
2056  {
2057  $this->ctrl->setParameter($this, 'test_answer_changed', $changed ? '1' : '0');
2058  }
2059 
2060 
2066  protected function handleIntermediateSubmit()
2067  {
2068  if ($this->getAnswerChangedParameter()) {
2069  $this->saveQuestionSolution(false);
2070  } else {
2071  $this->removeIntermediateSolution();
2072  }
2074  }
2075  // fau.
2076 
2077  // fau: testNav - save the switch to prevent the navigation confirmation
2082  {
2083  if (!empty($_POST['save_on_navigation_prevent_confirmation'])) {
2084  ilSession::set('save_on_navigation_prevent_confirmation', true);
2085  }
2086 
2087  if (!empty($_POST[self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM])) {
2088  ilSession::set(self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM, true);
2089  }
2090  }
2091  // fau.
2092 
2096  private $cachedQuestionGuis = [];
2097 
2103  protected function getQuestionGuiInstance($question_id, $fromCache = true): object
2104  {
2105  $tpl = $this->tpl;
2106 
2107  if (!$fromCache || !isset($this->cachedQuestionGuis[$question_id])) {
2108  $question_gui = $this->object->createQuestionGUI("", $question_id);
2109  $question_gui->setTargetGui($this);
2110  $question_gui->setPresentationContext(assQuestionGUI::PRESENTATION_CONTEXT_TEST);
2111  $question_gui->object->setObligationsToBeConsidered($this->object->areObligationsEnabled());
2112  $question_gui->populateJavascriptFilesRequiredForWorkForm($tpl);
2113  $question_gui->object->setShuffler($this->shuffler->getAnswerShuffleFor(
2114  $question_id,
2115  $this->test_session->getActiveId(),
2116  $this->test_session->getPass()
2117  ));
2118 
2119  // hey: prevPassSolutions - determine solution pass index and configure gui accordingly
2120  $this->initTestQuestionConfig($question_gui->object);
2121  // hey.
2122 
2123  $this->cachedQuestionGuis[$question_id] = $question_gui;
2124  }
2125 
2126  return $this->cachedQuestionGuis[$question_id];
2127  }
2128 
2130 
2131  protected function getQuestionInstance(int $question_id, bool $from_cache = true): assQuestion
2132  {
2133  if ($from_cache && isset($this->cachedQuestionObjects[$question_id])) {
2134  return $this->cachedQuestionObjects[$question_id];
2135  }
2136  $question = assQuestion::instantiateQuestion($question_id);
2137  $ass_settings = new ilSetting('assessment');
2138 
2139  $process_locker_factory = new ilAssQuestionProcessLockerFactory($ass_settings, $this->db);
2140  $process_locker_factory->setQuestionId($question->getId());
2141  $process_locker_factory->setUserId($this->user->getId());
2142  $process_locker_factory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging());
2143  $question->setProcessLocker($process_locker_factory->getLocker());
2144 
2145  $question->setObligationsToBeConsidered($this->object->areObligationsEnabled());
2146 
2147  $this->initTestQuestionConfig($question);
2148 
2149  $this->cachedQuestionObjects[$question_id] = $question;
2150  return $question;
2151  }
2152 
2153  // hey: prevPassSolutions - determine solution pass index and configure gui accordingly
2154  protected function initTestQuestionConfig(assQuestion $questionOBJ)
2155  {
2156  $questionOBJ->getTestPresentationConfig()->setPreviousPassSolutionReuseAllowed(
2157  $this->object->isPreviousSolutionReuseEnabled($this->test_session->getActiveId())
2158  );
2159  }
2160 
2161  protected function handleTearsAndAngerQuestionIsNull(int $question_id, $sequence_element): void
2162  {
2163  $this->logging_services->root()->write(
2164  "INV SEQ:"
2165  . "active={$this->test_session->getActiveId()} "
2166  . "qId=$question_id seq=$sequence_element "
2167  . serialize($this->testSequence)
2168  );
2169 
2170  $this->logging_services->root()->logStack(ilLogLevel::ERROR);
2171 
2172  $this->ctrl->setParameter($this, 'sequence', $this->testSequence->getFirstSequence());
2173  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
2174  }
2175 
2179  protected function populateMessageContent($contentHTML)
2180  {
2181  if ($this->object->getKioskMode()) {
2182  $this->tpl->addBlockfile($this->getContentBlockName(), 'content', "tpl.il_as_tst_kiosk_mode_content.html", "Modules/Test");
2183  $this->tpl->setContent($contentHTML);
2184  } else {
2185  $this->tpl->setVariable($this->getContentBlockName(), $contentHTML);
2186  }
2187  }
2188 
2189  protected function populateModals()
2190  {
2192  // fau: testNav - populateNavWhenChangedModal instead of populateNavWhileEditModal
2193  $this->populateNavWhenChangedModal();
2194  // fau.
2195 
2196  if ($this->object->isFollowupQuestionAnswerFixationEnabled()) {
2198 
2200  }
2201  }
2202 
2203  protected function populateDiscardSolutionModal()
2204  {
2205  $tpl = new ilTemplate('tpl.tst_player_confirmation_modal.html', true, true, 'Modules/Test');
2206 
2207  $tpl->setVariable('CONFIRMATION_TEXT', $this->lng->txt('discard_answer_confirmation'));
2208 
2209  $button = $this->ui_factory->button()->standard($this->lng->txt('discard_answer'), '#')
2211  fn($id) => "document.getElementById('$id').addEventListener(
2212  'click',
2213  (event)=>{
2214  event.target.name = 'cmd[discardSolution]';
2215  event.target.form.requestSubmit(event.target);
2216  }
2217  )"
2218  );
2219 
2220  $tpl->setCurrentBlock('buttons');
2221  $tpl->setVariable('BUTTON', $this->ui_renderer->render($button));
2223 
2224  $button = $this->ui_factory->button()->primary($this->lng->txt('cancel'), '#')
2226  fn($id) => "document.getElementById('$id').addEventListener(
2227  'click',
2228  ()=>$('#tst_discard_solution_modal').modal('hide')
2229  );"
2230  );
2231  $tpl->setCurrentBlock('buttons');
2232  $tpl->setVariable('BUTTON', $this->ui_renderer->render($button));
2234 
2235  $modal = ilModalGUI::getInstance();
2236  $modal->setId('tst_discard_solution_modal');
2237  $modal->setHeading($this->lng->txt('discard_answer'));
2238  $modal->setBody($tpl->get());
2239 
2240  $this->tpl->setCurrentBlock('discard_solution_modal');
2241  $this->tpl->setVariable('DISCARD_SOLUTION_MODAL', $modal->getHTML());
2242  $this->tpl->parseCurrentBlock();
2243  }
2244 
2245  protected function populateNavWhenChangedModal()
2246  {
2247  return; // usibility fix: get rid of popup
2248 
2249  if (ilSession::get('save_on_navigation_prevent_confirmation') == null) {
2250  return;
2251  }
2252 
2253  $tpl = new ilTemplate('tpl.tst_player_confirmation_modal.html', true, true, 'Modules/Test');
2254 
2255  if ($this->object->isInstantFeedbackAnswerFixationEnabled() && $this->object->isForceInstantFeedbackEnabled()) {
2256  $text = $this->lng->txt('save_on_navigation_locked_confirmation');
2257  } else {
2258  $text = $this->lng->txt('save_on_navigation_confirmation');
2259  }
2260  if ($this->object->isForceInstantFeedbackEnabled()) {
2261  $text .= " " . $this->lng->txt('save_on_navigation_forced_feedback_hint');
2262  }
2263  $tpl->setVariable('CONFIRMATION_TEXT', $text);
2264 
2265  /*
2266  $button = ilLinkButton::getInstance();
2267  $button->setId('tst_save_on_navigation_button');
2268  $button->setUrl('#');
2269  $button->setCaption('tst_save_and_proceed');
2270  $button->setPrimary(true);
2271  */
2272  $button = $this->ui_factory->button()->primary($this->lng->txt('tst_save_and_proceed'), '#');
2273 
2274  $tpl->setCurrentBlock('buttons');
2275  $tpl->setVariable('BUTTON', $this->ui_renderer->render($button));
2277 
2278  $button = ilLinkButton::getInstance();
2279  $button->setId('tst_cancel_on_navigation_button');
2280  $button->setUrl('#');
2281  $button->setCaption('cancel');
2282  $button->setPrimary(false);
2283  $tpl->setCurrentBlock('buttons');
2284  $tpl->setVariable('BUTTON', $button->render());
2286 
2287  $tpl->setCurrentBlock('checkbox');
2288  $tpl->setVariable('CONFIRMATION_CHECKBOX_NAME', 'save_on_navigation_prevent_confirmation');
2289  $tpl->setVariable('CONFIRMATION_CHECKBOX_LABEL', $this->lng->txt('tst_dont_show_msg_again_in_current_session'));
2291 
2292  $modal = ilModalGUI::getInstance();
2293  $modal->setId('tst_save_on_navigation_modal');
2294  $modal->setHeading($this->lng->txt('save_on_navigation'));
2295  $modal->setBody($tpl->get());
2296 
2297  $this->tpl->setCurrentBlock('nav_while_edit_modal');
2298  $this->tpl->setVariable('NAV_WHILE_EDIT_MODAL', $modal->getHTML());
2299  $this->tpl->parseCurrentBlock();
2300  }
2301  // fau.
2302 
2303  protected function populateNextLocksUnchangedModal()
2304  {
2305  $modal = new ilTestPlayerConfirmationModal($this->ui_renderer);
2306  $modal->setModalId('tst_next_locks_unchanged_modal');
2307 
2308  $modal->setHeaderText($this->lng->txt('tst_nav_next_locks_empty_answer_header'));
2309  $modal->setConfirmationText($this->lng->txt('tst_nav_next_locks_empty_answer_confirm'));
2310 
2311  $button = $this->ui_factory->button()->standard($this->lng->txt('tst_proceed'), '#');
2312  $modal->addButton($button);
2313 
2314  $button = $this->ui_factory->button()->primary($this->lng->txt('cancel'), '#');
2315  $modal->addButton($button);
2316 
2317  $this->tpl->setCurrentBlock('next_locks_unchanged_modal');
2318  $this->tpl->setVariable('NEXT_LOCKS_UNCHANGED_MODAL', $modal->getHTML());
2319  $this->tpl->parseCurrentBlock();
2320  }
2321 
2322  protected function populateNextLocksChangedModal()
2323  {
2325  return;
2326  }
2327 
2328  $modal = new ilTestPlayerConfirmationModal($this->ui_renderer);
2329  $modal->setModalId('tst_next_locks_changed_modal');
2330 
2331  $modal->setHeaderText($this->lng->txt('tst_nav_next_locks_current_answer_header'));
2332  $modal->setConfirmationText($this->lng->txt('tst_nav_next_locks_current_answer_confirm'));
2333 
2334  $modal->setConfirmationCheckboxName(self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM);
2335  $modal->setConfirmationCheckboxLabel($this->lng->txt('tst_dont_show_msg_again_in_current_session'));
2336 
2337  $button = $this->ui_factory->button()->primary($this->lng->txt('tst_save_and_proceed'), '#');
2338  $modal->addButton($button);
2339 
2340  $button = $this->ui_factory->button()->standard($this->lng->txt('cancel'), '#');
2341  $modal->addButton($button);
2342 
2343  $this->tpl->setCurrentBlock('next_locks_changed_modal');
2344  $this->tpl->setVariable('NEXT_LOCKS_CHANGED_MODAL', $modal->getHTML());
2345  $this->tpl->parseCurrentBlock();
2346  }
2347 
2348  public const FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM = 'followup_qst_locks_prevent_confirmation';
2349 
2351  {
2352  ilSession::set(self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM, true);
2353  }
2354 
2356  {
2357  if (ilSession::get(self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM) == null) {
2358  return false;
2359  }
2360 
2361  return ilSession::get(self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM);
2362  }
2363 
2364  protected function populateQuestionEditControl(assQuestionGUI $question_gui): void
2365  {
2366  // configuration for ilTestPlayerQuestionEditControl.js
2367  $config = [];
2368  $state = $question_gui->object->lookupForExistingSolutions($this->test_session->getActiveId(), $this->test_session->getPass());
2369  $config['isAnswered'] = $state['authorized'];
2370  $config['isAnswerChanged'] = $state['intermediate'] || $this->getAnswerChangedParameter();
2371  $config['isAnswerFixed'] = $this->isParticipantsAnswerFixed($question_gui->object->getId());
2372  $config['saveOnTimeReachedUrl'] = str_replace('&amp;', '&', $this->ctrl->getFormAction($this, ilTestPlayerCommands::AUTO_SAVE_ON_TIME_LIMIT));
2373 
2374  $config['autosaveUrl'] = '';
2375  $config['autosaveInterval'] = 0;
2376  if ($question_gui->object instanceof ilAssQuestionAutosaveable && $this->object->getAutosave()) {
2377  $config['autosaveUrl'] = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::AUTO_SAVE, '', true);
2378  $config['autosaveInterval'] = $this->object->getMainSettings()->getQuestionBehaviourSettings()->getAutosaveInterval();
2379  }
2380 
2382  // hey: prevPassSolutions - refactored method identifiers
2383  $question_config = $question_gui->object->getTestPresentationConfig();
2384  // hey.
2385 
2386  // Normal questions: changes are done in form fields an can be detected there
2387  $config['withFormChangeDetection'] = $question_config->isFormChangeDetectionEnabled();
2388 
2389  // Flash and Java questions: changes are directly sent to ilias and have to be polled from there
2390  $config['withBackgroundChangeDetection'] = $question_config->isBackgroundChangeDetectionEnabled();
2391  $config['backgroundDetectorUrl'] = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::DETECT_CHANGES, '', true);
2392 
2393  // Forced feedback will change the navigation saving command
2394  $config['forcedInstantFeedback'] = $this->object->isForceInstantFeedbackEnabled();
2395  $config['questionLocked'] = $this->isParticipantsAnswerFixed($question_gui->object->getId());
2396  $config['nextQuestionLocks'] = $this->object->isFollowupQuestionAnswerFixationEnabled();
2397  $config['autosaveFailureMessage'] = $this->lng->txt('autosave_failed');
2398 
2399  $this->tpl->addJavascript('./Modules/Test/js/ilTestPlayerQuestionEditControl.js');
2400  $this->tpl->addOnLoadCode('il.TestPlayerQuestionEditControl.init(' . json_encode($config) . ')');
2401  }
2402  // fau.
2403 
2404  protected function getQuestionsDefaultPresentationMode($isQuestionWorkedThrough): string
2405  {
2406  // fau: testNav - always set default presentation mode to "edit"
2407  return self::PRESENTATION_MODE_EDIT;
2408  // fau.
2409  }
2410 
2411  protected function registerForcedFeedbackNavUrl($forcedFeedbackNavUrl)
2412  {
2413  if (ilSession::get('forced_feedback_navigation_url') == null) {
2414  ilSession::set('forced_feedback_navigation_url', []);
2415  }
2416  $forced_feeback_navigation_url = ilSession::get('forced_feedback_navigation_url');
2417  $forced_feeback_navigation_url[$this->test_session->getActiveId()] = $forcedFeedbackNavUrl;
2418  ilSession::set('forced_feedback_navigation_url', $forced_feeback_navigation_url);
2419  }
2420 
2422  {
2423  if (ilSession::get('forced_feedback_navigation_url') == null) {
2424  return null;
2425  }
2426  $forced_feedback_navigation_url = ilSession::get('forced_feedback_navigation_url');
2427  if (!isset($forced_feedback_navigation_url[$this->test_session->getActiveId()])) {
2428  return null;
2429  }
2430 
2431  return $forced_feedback_navigation_url[$this->test_session->getActiveId()];
2432  }
2433 
2434  protected function isForcedFeedbackNavUrlRegistered(): bool
2435  {
2436  return !empty($this->getRegisteredForcedFeedbackNavUrl());
2437  }
2438 
2439  protected function unregisterForcedFeedbackNavUrl()
2440  {
2441  $forced_feedback_navigation_url = ilSession::get('forced_feedback_navigation_url');
2442  if (isset($forced_feedback_navigation_url[$this->test_session->getActiveId()])) {
2443  unset($forced_feedback_navigation_url[$this->test_session->getActiveId()]);
2444  ilSession::set('forced_feedback_navigation_url', $forced_feedback_navigation_url);
2445  }
2446  }
2447 
2448  protected function handleFileUploadCmd()
2449  {
2450  $this->updateWorkingTime();
2451  $this->saveQuestionSolution(false);
2452  $this->ctrl->redirect($this, ilTestPlayerCommands::SUBMIT_SOLUTION);
2453  }
2454 }
showSideList($current_sequence_element)
getQuestionInstance(int $question_id, bool $from_cache=true)
static get(string $a_var)
redirectAfterAutosaveCmd()
Redirect the user after an automatic save when the time limit is reached.
saveQuestionSolution($authorized=true, $force=false)
saves the user input of a question
$res
Definition: ltiservices.php:69
startPlayerCmd()
Start a test for the first time.
prepareWorkingTimeJsTemplate(ilObjTest $object, array $date, string $check_url, string $redirect_url)
getOnLoadCodeForNavigationButtons(string $target, string $cmd)
getQuestionGuiInstance($question_id, $fromCache=true)
exit
Definition: login.php:29
Class ilTestPassFinishTasks.
sendNewPassFinishedNotificationEmailIfActivated(int $active_id, int $pass)
getAdditionalUsrDataHtmlAndPopulateWindowTitle($testSession, $active_id, $overwrite_anonymity=false)
Returns the user data for a test results output.
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setAnswerChangedParameter($changed=true)
Set the &#39;answer changed&#39; url parameter for generated links.
isFirstQuestionInSequence($sequenceElement)
populateSpecificFeedbackBlock(assQuestionGUI $question_gui)
outQuestionSummaryCmd(bool $obligations_info=false, bool $obligations_filter=false)
Output of a summary of all test questions for test participants.
setAnonymousIdCmd()
Sets a session variable with the test access code for an anonymous test user.
determineSolutionPassIndex(assQuestionGUI $questionGui)
showQuestionEditable(assQuestionGUI $questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse)
autosaveCmd()
Automatically save a user answer while working on the test (called repeatedly by asynchronous posts i...
Abstract basic class which is to be extended by the concrete assessment question type classes...
isLastQuestionInSequence($sequenceElement)
populateTestNavigationToolbar(ilTestNavigationToolbarGUI $toolbar_gui)
Class ilTaggingGUI.
confirmSubmitAnswers()
confirm submit results if confirm then results are submitted and the screen will be redirected to the...
updateWorkingTime()
updates working time and stores state saveresult to see if question has to be stored or not ...
saveTagsCmd()
Save tags for tagging gui.
ilGlobalTemplateInterface ilTemplate $tpl
sk 2023-08-01: We need this union type, even if it is wrong! To change this
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.
getResultsSignature()
Returns HTML code for a signature field.
setVariable(string $variable, $value='')
Sets the given variable to the given value.
handleUserSettings()
Handles some form parameters on starting and resuming a test.
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
getContentBlockName()
Returns the name of the current content block (depends on the kiosk mode setting) ...
confirmHintRequestCmd()
Go to hint request confirmation.
checkWorkingTimeCmd()
This is asynchronously called by tpl.workingtime.js to check for changes in the user&#39;s processing tim...
Base Exception for all Exceptions relating to Modules/Test.
maxProcessingTimeReached()
Outputs a message when the maximum processing time is reached.
Test sequence handler.
showRequestedHintListCmd()
Go to requested hint list.
populateInstantResponseBlocks(assQuestionGUI $questionGui, $authorizedSolution)
checkTestSessionUser(ilTestSession $test_session)
static _lookupOnlineTestAccess($a_test_id, $a_user_id)
Checks if a user is allowd to run an online exam.
showListOfAnswers($active_id, $pass=null, $top_data="", $bottom_data="")
Creates an output of the list of answers for a test participant during the test (only the actual pass...
static instantiateQuestion(int $question_id)
populateQuestionNavigation($sequence_element, $primary_next)
initTestQuestionConfig(assQuestion $questionOBJ)
markQuestionCmd()
Set a question solved.
handleIntermediateSubmit()
Check the &#39;answer changed&#39; parameter when a question form is intermediately submitted.
__construct(VocabulariesInterface $vocabularies)
populateGenericFeedbackBlock(assQuestionGUI $question_gui, $solutionCorrect)
$_SERVER['HTTP_HOST']
Definition: raiseError.php:10
outCorrectSolution()
Creates an output of the solution of an answer compared to the correct solution.
handleSkillTriggering(ilTestSession $test_session)
resumePlayerCmd()
Resume a test at the last position.
saveNavigationPreventConfirmation()
Save the save the switch to prevent the navigation confirmation.
endingTimeReached()
handle endingTimeReached
const REDIRECT_NONE
static _getSolvedQuestions($active_id, $question_fi=null)
get solved questions
setUserId(int $user_id)
ensureExistingTestSession(ilTestSession $test_session)
outProcessingTime(int $active_id, bool $verbose)
$url
Definition: ltiregstart.php:35
getCorrectSolutionOutput($question_id, $active_id, $pass, ilTestQuestionRelatedObjectivesList $objectives_list=null)
Returns an output of the solution to an answer compared to the correct solution.
getAnswerChangedParameter()
Get the &#39;answer changed&#39; status from the current request It may be set by ilTestPlayerNavigationContr...
Basic GUI class for assessment questions.
detectChangesCmd()
Detect changes sent in the background to the server This is called by ajax from ilTestPlayerQuestionE...
static getContentStylePath(int $a_style_id, bool $add_random=true, bool $add_token=true)
get content style path static (to avoid full reading)
isShowingPostponeStatusReguired($questionId)
static redirect(string $a_script)
unmarkQuestionCmd()
Set a question unsolved.
setCurrentBlock(string $part=self::DEFAULT_BLOCK)
Sets the template to the given block.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
registerForcedFeedbackNavUrl($forcedFeedbackNavUrl)
static getInstance()
form( $class_path, string $cmd, string $submit_caption="")
setAnonymousId(string $anonymous_id)
isOptionalQuestionAnsweringConfirmationRequired($sequenceElement)
getSpecificFeedbackOutput(array $userSolution)
Returns the answer specific feedback for the question.
showQuestionViewable(assQuestionGUI $questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse)
isTestAccessible()
test accessible returns true if the user can perform the test
static _getHttpPath()
outQuestionPage($a_temp_var, $a_postponed=false, $active_id="", $html="", $inlineFeedbackEnabled=false)
getSolutionOutput( $active_id, $pass=null, $graphicalOutput=false, $result_output=false, $show_question_only=true, $show_feedback=false, $show_correct_solution=false, $show_manual_scoring=false, $show_question_text=true)
populateInstantResponseModal(assQuestionGUI $questionGui, $navUrl)
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$message
Definition: xapiexit.php:32
getQuestionsDefaultPresentationMode($isQuestionWorkedThrough)
handleTearsAndAngerQuestionIsNull(int $question_id, $sequence_element)
Service GUI class for tests.
initTestCmd()
Start a test for the first time after a redirect.
parseCurrentBlock(string $block_name=self::DEFAULT_BLOCK)
Parses the given block.
static clear(string $a_var)
static set(string $a_var, $a_val)
Set a value.
outQuestionForTest(string $formaction, int $active_id, ?int $pass, bool $is_question_postponed=false, $user_post_solutions=false, bool $show_specific_inline_feedback=false)
populateScoreBlock($reachedPoints, $maxPoints)
setNavigationGUI(?ilTestQuestionNavigationGUI $navigationGUI)
Output class for assessment test execution.
prepareTestPage($sequenceElement, $questionId)
autosaveOnTimeLimitCmd()
Automatically save a user answer when the limited duration of a test run is reached (called by synchr...
static lookupExamId($active_id, $pass)
getGenericFeedbackOutput(int $active_id, ?int $pass)