ILIAS  release_8 Revision v8.24
class.ilTestPlayerAbstractGUI.php
Go to the documentation of this file.
1<?php
2
19require_once './Modules/Test/classes/inc.AssessmentConstants.php';
20
36{
37 public const PRESENTATION_MODE_VIEW = 'view';
38 public const PRESENTATION_MODE_EDIT = 'edit';
39
41
42 public $ref_id;
44 public $sequence;
45 public $cmdCtrl;
48
53
57 protected $processLocker;
58
62 protected $testSession;
63
67 protected $assSettings;
68
72 protected $testSequence = null;
73
79 public function __construct($a_object)
80 {
81 parent::__construct($a_object);
82 $this->ref_id = $this->testrequest->getRefId();
83
84 global $DIC;
85 $rbacsystem = $DIC['rbacsystem'];
86 $ilUser = $DIC['ilUser'];
87 $lng = $DIC['lng'];
88 $this->passwordChecker = new ilTestPasswordChecker($rbacsystem, $ilUser, $this->object, $lng);
89
90 $this->processLocker = null;
91 $this->testSession = null;
92 $this->assSettings = null;
93 }
94
95 protected function checkReadAccess()
96 {
97 global $DIC;
98 $rbacsystem = $DIC['rbacsystem'];
99
100 if (!$rbacsystem->checkAccess("read", $this->object->getRefId())) {
101 // only with read access it is possible to run the test
102 $this->ilias->raiseError($this->lng->txt("cannot_execute_test"), $this->ilias->error_obj->MESSAGE);
103 }
104 }
105
106 protected function checkTestExecutable()
107 {
108 $executable = $this->object->isExecutable($this->testSession, $this->testSession->getUserId());
109
110 if (!$executable['executable']) {
111 $this->tpl->setOnScreenMessage('info', $executable['errormessage'], true);
112 $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen");
113 }
114 }
115
117 {
118 global $DIC; /* @var ILIAS\DI\Container $DIC */
119
120 if ($testSession->getUserId() != $DIC->user()->getId()) {
121 throw new ilTestException('active id given does not relate to current user!');
122 }
123 }
124
126 {
127 if ($testSession->getActiveId()) {
128 return;
129 }
130
131 global $DIC;
132 $ilUser = $DIC['ilUser'];
133
134 $testSession->setUserId($ilUser->getId());
135
136 if ($testSession->isAnonymousUser()) {
137 if (!$testSession->doesAccessCodeInSessionExists()) {
138 return;
139 }
140
141 $testSession->setAnonymousId($testSession->getAccessCodeFromSession());
142 }
143
144 $testSession->saveToDb();
145 }
146
147 protected function initProcessLocker($activeId)
148 {
150 $processLockerFactory = new ilTestProcessLockerFactory($this->assSettings, $ilDB);
151 $this->processLocker = $processLockerFactory->withContextId((int) $activeId)->getLocker();
152 }
153
160 public function saveTagsCmd()
161 {
162 $tagging_gui = new ilTaggingGUI();
163 $tagging_gui->setObject($this->object->getId(), $this->object->getType());
164 $tagging_gui->saveInput();
165 $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen");
166 }
167
171 public function updateWorkingTime()
172 {
173 if (ilSession::get("active_time_id") != null) {
174 $this->object->updateWorkingTime(ilSession::get("active_time_id"));
175 }
176
178 "active_time_id",
179 $this->object->startWorkingTime(
180 $this->testSession->getActiveId(),
181 $this->testSession->getPass()
182 )
183 );
184 }
185
186 // fau: testNav - new function removeIntermediateSolution()
191 {
192 $questionId = $this->getCurrentQuestionId();
193
194 $this->getQuestionInstance($questionId)->removeIntermediateSolution(
195 $this->testSession->getActiveId(),
196 $this->testSession->getPass()
197 );
198 }
199 // fau.
200
204 abstract public function saveQuestionSolution($authorized = true, $force = false);
205
206 abstract protected function canSaveResult();
207
208 public function suspendTestCmd()
209 {
210 $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen");
211 }
212
220 public function isMaxProcessingTimeReached(): bool
221 {
222 global $DIC;
223 $ilUser = $DIC['ilUser'];
224 $active_id = $this->testSession->getActiveId();
225 $starting_time = $this->object->getStartingTimeOfUser($active_id);
226 if ($starting_time === false) {
227 return false;
228 } else {
229 return $this->object->isMaxProcessingTimeReached($starting_time, $active_id);
230 }
231 }
232
233 protected function determineInlineScoreDisplay(): bool
234 {
235 $show_question_inline_score = false;
236 if ($this->object->getAnswerFeedbackPoints()) {
237 $show_question_inline_score = true;
238 return $show_question_inline_score;
239 }
240 return $show_question_inline_score;
241 }
242
244 {
245 $this->tpl->setCurrentBlock('test_nav_toolbar');
246 $this->tpl->setVariable('TEST_NAV_TOOLBAR', $toolbarGUI->getHTML());
247 $this->tpl->parseCurrentBlock();
248 }
249
250 protected function populateQuestionNavigation($sequenceElement, $disabled, $primaryNext)
251 {
252 if (!$this->isFirstQuestionInSequence($sequenceElement)) {
253 $this->populatePreviousButtons($disabled);
254 }
255
256 if (!$this->isLastQuestionInSequence($sequenceElement)) {
257 $this->populateNextButtons($disabled, $primaryNext);
258 }
259 }
260
261 protected function populatePreviousButtons($disabled)
262 {
263 $this->populateUpperPreviousButtonBlock($disabled);
264 $this->populateLowerPreviousButtonBlock($disabled);
265 }
266
267 protected function populateNextButtons($disabled, $primaryNext)
268 {
269 $this->populateUpperNextButtonBlock($disabled, $primaryNext);
270 $this->populateLowerNextButtonBlock($disabled, $primaryNext);
271 }
272
273 protected function populateLowerNextButtonBlock($disabled, $primaryNext)
274 {
275 $button = $this->buildNextButtonInstance($disabled, $primaryNext);
276 $button->setId('bottomnextbutton');
277
278 $this->tpl->setCurrentBlock("next_bottom");
279 $this->tpl->setVariable("BTN_NEXT_BOTTOM", $button->render());
280 $this->tpl->parseCurrentBlock();
281 }
282
283 protected function populateUpperNextButtonBlock($disabled, $primaryNext)
284 {
285 $button = $this->buildNextButtonInstance($disabled, $primaryNext);
286 $button->setId('nextbutton');
287
288 $this->tpl->setCurrentBlock("next");
289 $this->tpl->setVariable("BTN_NEXT", $button->render());
290 $this->tpl->parseCurrentBlock();
291 }
292
293 protected function populateLowerPreviousButtonBlock($disabled)
294 {
295 $button = $this->buildPreviousButtonInstance($disabled);
296 $button->setId('bottomprevbutton');
297
298 $this->tpl->setCurrentBlock("prev_bottom");
299 $this->tpl->setVariable("BTN_PREV_BOTTOM", $button->render());
300 $this->tpl->parseCurrentBlock();
301 }
302
303 protected function populateUpperPreviousButtonBlock($disabled)
304 {
305 $button = $this->buildPreviousButtonInstance($disabled);
306 $button->setId('prevbutton');
307
308 $this->tpl->setCurrentBlock("prev");
309 $this->tpl->setVariable("BTN_PREV", $button->render());
310 $this->tpl->parseCurrentBlock();
311 }
312
318 private function buildNextButtonInstance($disabled, $primaryNext)
319 {
321 // fau: testNav - set glyphicon and primary
322 $button->setPrimary($primaryNext);
323 $button->setRightGlyph('glyphicon glyphicon-arrow-right');
324 // fau.
325 $button->setNextCommand(ilTestPlayerCommands::NEXT_QUESTION);
326 $button->setUrl($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::NEXT_QUESTION));
327 $button->setCaption('next_question');
328 $button->addCSSClass('ilTstNavElem');
329 //$button->setDisabled($disabled);
330 return $button;
331 }
332
338 {
340 // fau: testNav - set glyphicon and primary
341 $button->setLeftGlyph('glyphicon glyphicon-arrow-left');
342 // fau.
343 $button->setNextCommand(ilTestPlayerCommands::PREVIOUS_QUESTION);
344 $button->setUrl($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::PREVIOUS_QUESTION));
345 $button->setCaption('previous_question');
346 $button->addCSSClass('ilTstNavElem');
347 //$button->setDisabled($disabled);
348 return $button;
349 }
350
354 protected function populateSpecificFeedbackBlock(assQuestionGUI $question_gui): bool
355 {
356 $solutionValues = $question_gui->object->getSolutionValues(
357 $this->testSession->getActiveId(),
358 null
359 );
360
361 $feedback = $question_gui->getSpecificFeedbackOutput(
362 $question_gui->object->fetchIndexedValuesFromValuePairs($solutionValues)
363 );
364
365 if (!empty($feedback)) {
366 $this->tpl->setCurrentBlock("specific_feedback");
367 $this->tpl->setVariable("SPECIFIC_FEEDBACK", $feedback);
368 $this->tpl->parseCurrentBlock();
369 return true;
370 }
371 return false;
372 }
373
377 protected function populateGenericFeedbackBlock(assQuestionGUI $question_gui, $solutionCorrect): bool
378 {
379 // fix #031263: add pass
380 $feedback = $question_gui->getGenericFeedbackOutput($this->testSession->getActiveId(), $this->testSession->getPass());
381
382 if (strlen($feedback)) {
383 $cssClass = (
384 $solutionCorrect ?
386 );
387
388 $this->tpl->setCurrentBlock("answer_feedback");
389 $this->tpl->setVariable("ANSWER_FEEDBACK", $feedback);
390 $this->tpl->setVariable("ILC_FB_CSS_CLASS", $cssClass);
391 $this->tpl->parseCurrentBlock();
392 return true;
393 }
394 return false;
395 }
396
397 protected function populateScoreBlock($reachedPoints, $maxPoints)
398 {
399 $scoreInformation = sprintf(
400 $this->lng->txt("you_received_a_of_b_points"),
401 $reachedPoints,
402 $maxPoints
403 );
404
405 $this->tpl->setCurrentBlock("received_points_information");
406 $this->tpl->setVariable("RECEIVED_POINTS_INFORMATION", $scoreInformation);
407 $this->tpl->parseCurrentBlock();
408 }
409
410 protected function populateSolutionBlock($solutionoutput)
411 {
412 if (strlen($solutionoutput)) {
413 $this->tpl->setCurrentBlock("solution_output");
414 $this->tpl->setVariable("CORRECT_SOLUTION", $this->lng->txt("tst_best_solution_is"));
415 $this->tpl->setVariable("QUESTION_FEEDBACK", $solutionoutput);
416 $this->tpl->parseCurrentBlock();
417 }
418 }
419
420 protected function populateSyntaxStyleBlock()
421 {
422 $this->tpl->setCurrentBlock("SyntaxStyle");
423 $this->tpl->setVariable(
424 "LOCATION_SYNTAX_STYLESHEET",
426 );
427 $this->tpl->parseCurrentBlock();
428 }
429
430 protected function populateContentStyleBlock()
431 {
432 $this->tpl->setCurrentBlock("ContentStyle");
433 $this->tpl->setVariable(
434 "LOCATION_CONTENT_STYLESHEET",
436 );
437 $this->tpl->parseCurrentBlock();
438 }
439
445 public function setAnonymousIdCmd()
446 {
447 if ($this->testSession->isAnonymousUser()) {
448 $this->testSession->setAccessCodeToSession($_POST['anonymous_id']);
449 }
450
451 $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen");
452 }
453
460 protected function startPlayerCmd()
461 {
462 $testStartLock = $this->getLockParameter();
463 $isFirstTestStartRequest = false;
464
465 $this->processLocker->executeTestStartLockOperation(function () use ($testStartLock, &$isFirstTestStartRequest) {
466 if ($this->testSession->lookupTestStartLock() != $testStartLock) {
467 $this->testSession->persistTestStartLock($testStartLock);
468 $isFirstTestStartRequest = true;
469 }
470 });
471
472 if ($isFirstTestStartRequest) {
473 $this->handleUserSettings();
474 $this->ctrl->redirect($this, ilTestPlayerCommands::INIT_TEST);
475 }
476
477 $this->ctrl->setParameterByClass('ilObjTestGUI', 'lock', $testStartLock);
478 $this->ctrl->redirectByClass("ilobjtestgui", "redirectToInfoScreen");
479 }
480
481 public function getLockParameter()
482 {
483 if ($this->testrequest->isset('lock') && strlen($this->testrequest->raw('lock'))) {
484 return $this->testrequest->raw('lock');
485 }
486
487 return null;
488 }
489
493 abstract protected function resumePlayerCmd();
494
498 protected function initTestCmd()
499 {
500 if ($this->object->checkMaximumAllowedUsers() == false) {
502 return;
503 }
504
505 if ($this->testSession->isAnonymousUser()
506 && !$this->testSession->doesAccessCodeInSessionExists()) {
507 $accessCode = $this->testSession->createNewAccessCode();
508
509 $this->testSession->setAccessCodeToSession($accessCode);
510 $this->testSession->setAnonymousId($accessCode);
511 $this->testSession->saveToDb();
512
513 $this->ctrl->redirect($this, ilTestPlayerCommands::DISPLAY_ACCESS_CODE);
514 }
515
516 if (!$this->testSession->isAnonymousUser()) {
517 $this->testSession->unsetAccessCodeInSession();
518 }
519 $this->ctrl->redirect($this, ilTestPlayerCommands::START_TEST);
520 }
521
522 public function displayAccessCodeCmd()
523 {
524 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_anonymous_code_presentation.html", "Modules/Test");
525 $this->tpl->setCurrentBlock("adm_content");
526 $this->tpl->setVariable("TEXT_ANONYMOUS_CODE_CREATED", $this->lng->txt("tst_access_code_created"));
527 $this->tpl->setVariable("TEXT_ANONYMOUS_CODE", $this->testSession->getAccessCodeFromSession());
528 $this->tpl->setVariable("FORMACTION", $this->ctrl->getFormAction($this));
529 $this->tpl->setVariable("CMD_CONFIRM", ilTestPlayerCommands::ACCESS_CODE_CONFIRMED);
530 $this->tpl->setVariable("TXT_CONFIRM", $this->lng->txt("continue_work"));
531 $this->tpl->parseCurrentBlock();
532 }
533
534 public function accessCodeConfirmedCmd()
535 {
536 $this->ctrl->redirect($this, ilTestPlayerCommands::START_TEST);
537 }
538
542 public function handleUserSettings()
543 {
544 global $DIC;
545 $ilUser = $DIC['ilUser'];
546 $post_array = $_POST;
547 if (!is_array($post_array)) {
548 $request = $DIC->http()->request();
549 $post_array = $request->getParsedBody();
550 }
551
552 $tst_javascript = array_key_exists("chb_javascript", $post_array) ? 1 : 0;
553 $ilUser->writePref("tst_javascript", $tst_javascript);
554
555 if ($this->object->getNrOfTries() != 1
556 && $this->object->getUsePreviousAnswers() == 1
557 ) {
558 $chb_use_previous_answers = array_key_exists("chb_use_previous_answers", $post_array) ? 1 : 0;
559 $ilUser->writePref("tst_use_previous_answers", $chb_use_previous_answers);
560 }
561 }
562
567 public function redirectAfterAutosaveCmd()
568 {
569 $active_id = $this->testSession->getActiveId();
570 $actualpass = ilObjTest::_getPass($active_id);
571
572 $this->performTestPassFinishedTasks($actualpass);
573
574 $url = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::AFTER_TEST_PASS_FINISHED, '', false, false);
575
576 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_redirect_autosave.html", "Modules/Test");
577 $this->tpl->setVariable("TEXT_REDIRECT", $this->lng->txt("redirectAfterSave"));
578 $this->tpl->setVariable("URL", $url);
579 }
580
582 {
583 $active_id = $this->testSession->getActiveId();
584 $actualpass = ilObjTest::_getPass($active_id);
585
586 $this->performTestPassFinishedTasks($actualpass);
587
588 $this->testSession->setLastFinishedPass($this->testSession->getPass());
589 $this->testSession->increaseTestPass();
590
591 $url = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::AFTER_TEST_PASS_FINISHED, '', false, false);
592
593 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_redirect_autosave.html", "Modules/Test");
594 $this->tpl->setVariable("TEXT_REDIRECT", $this->lng->txt("redirectAfterSave"));
595 $this->tpl->setVariable("URL", $url);
596 }
597
598 abstract protected function getCurrentQuestionId();
599
604 public function autosaveCmd()
605 {
606 $result = "";
607 if (is_countable($_POST) && count($_POST) > 0) {
608 if (!$this->canSaveResult() || $this->isParticipantsAnswerFixed($this->getCurrentQuestionId())) {
609 $result = '-IGNORE-';
610 } else {
611 $authorize = !$this->getAnswerChangedParameter();
612 $res = $this->saveQuestionSolution($authorize, true);
613
614 if ($res) {
615 $result = $this->lng->txt("autosave_success");
616 } else {
617 $result = $this->lng->txt("autosave_failed");
618 }
619 }
620 }
621 echo $result;
622 exit;
623 }
624
629 public function autosaveOnTimeLimitCmd()
630 {
631 if (!$this->isParticipantsAnswerFixed($this->getCurrentQuestionId())) {
632 $this->saveQuestionSolution(false, true);
633 }
634 $this->ctrl->redirect($this, ilTestPlayerCommands::REDIRECT_ON_TIME_LIMIT);
635 }
636
637
638 // fau: testNav - new function detectChangesCmd()
644 protected function detectChangesCmd()
645 {
646 $questionId = $this->getCurrentQuestionId();
647 $state = $this->getQuestionInstance($questionId)->lookupForExistingSolutions(
648 $this->testSession->getActiveId(),
649 $this->testSession->getPass()
650 );
651 $result = array();
652 $result['isAnswered'] = $state['authorized'];
653 $result['isAnswerChanged'] = $state['intermediate'];
654
655 echo json_encode($result);
656 exit;
657 }
658 // fau.
659
660 protected function submitIntermediateSolutionCmd()
661 {
662 $this->saveQuestionSolution(false, true);
663 // fau: testNav - set the 'answer changed' parameter when an intermediate solution is submitted
664 $this->setAnswerChangedParameter(true);
665 // fau.
666 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
667 }
668
672 public function toggleSideListCmd()
673 {
674 global $DIC;
675 $ilUser = $DIC['ilUser'];
676
677 $show_side_list = $ilUser->getPref('side_list_of_questions');
678 $ilUser->writePref('side_list_of_questions', !$show_side_list);
679 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
680 }
681
683 {
684 // fau: testNav - handle intermediate submit when marking the question
686 // fau.
687 $this->markQuestionCmd();
688 }
689
693 protected function markQuestionCmd()
694 {
695 $questionId = $this->testSequence->getQuestionForSequence(
697 );
698
699 $this->object->setQuestionSetSolved(1, $questionId, $this->testSession->getUserId());
700
701 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
702 }
703
705 {
706 // fau: testNav - handle intermediate submit when unmarking the question
708 // fau.
709 $this->unmarkQuestionCmd();
710 }
711
715 protected function unmarkQuestionCmd()
716 {
717 $questionId = $this->testSequence->getQuestionForSequence(
719 );
720
721 $this->object->setQuestionSetSolved(0, $questionId, $this->testSession->getUserId());
722
723 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
724 }
725
729 protected function confirmFinishCmd()
730 {
731 $this->finishTestCmd(false);
732 }
733
737 protected function confirmFinishTestCmd()
738 {
742 global $DIC;
743 $ilUser = $DIC['ilUser'];
744
745 $confirmation = new ilConfirmationGUI();
746 $confirmation->setFormAction($this->ctrl->getFormAction($this, 'confirmFinish'));
747 $confirmation->setHeaderText($this->lng->txt("tst_finish_confirmation_question"));
748 $confirmation->setConfirm($this->lng->txt("tst_finish_confirm_button"), 'confirmFinish');
749 $confirmation->setCancel($this->lng->txt("tst_finish_confirm_cancel_button"), ilTestPlayerCommands::BACK_FROM_FINISHING);
750
751 $this->populateHelperGuiContent($confirmation);
752 }
753
754 public function finishTestCmd($requires_confirmation = true)
755 {
756 $this->handleCheckTestPassValid();
757 ilSession::clear("tst_next");
758
759 $active_id = $this->testSession->getActiveId();
760 $actualpass = ilObjTest::_getPass($active_id);
761
762 $allObligationsAnswered = ilObjTest::allObligationsAnswered($this->testSession->getTestId(), $active_id, $actualpass);
763
764 /*
765 * The following "endgames" are possible prior to actually finishing the test:
766 * - Obligations (Ability to finish the test.)
767 * If not all obligatory questions are answered, the user is presented a list
768 * showing the deficits.
769 * - Examview (Will to finish the test.)
770 * With the examview, the participant can review all answers given in ILIAS or a PDF prior to
771 * commencing to the finished test.
772 * - Last pass allowed (Reassuring the will to finish the test.)
773 * If passes are limited, on the last pass, an additional confirmation is to be displayed.
774 */
775
776
777 if ($this->object->areObligationsEnabled() && !$allObligationsAnswered) {
778 if ($this->object->getListOfQuestions()) {
780 } else {
782 }
783
784 return;
785 }
786
787 // Examview enabled & !reviewed & requires_confirmation? test_submission_overview (review gui)
788 if ($this->object->getEnableExamview() && !$this->testrequest->isset('reviewed') && $requires_confirmation) {
789 $this->ctrl->redirectByClass('ilTestSubmissionReviewGUI', "show");
790 return;
791 }
792
793 // Last try in limited tries & !confirmed
794 if (($requires_confirmation) && ($actualpass == $this->object->getNrOfTries() - 1)) {
795 // show confirmation page
796 $this->confirmFinishTestCmd();
797 return;
798 }
799
800 // Last try in limited tries & confirmed?
801 if (($actualpass == $this->object->getNrOfTries() - 1) && (!$requires_confirmation)) {
802 // @todo: php7 ask mister test
803 #$ilAuth->setIdle(ilSession::getIdleValue(), false);
804 #$ilAuth->setExpire(0);
805 switch ($this->object->getMailNotification()) {
806 case 1:
807 $this->object->sendSimpleNotification($active_id);
808 break;
809 case 2:
810 $this->object->sendAdvancedNotification($active_id);
811 break;
812 }
813 }
814
815 // Non-last try finish
816 if (ilSession::get('tst_pass_finish') == null) {
817 ilSession::set('tst_pass_finish', 1);
818 if ($this->object->getMailNotificationType() == 1) {
819 switch ($this->object->getMailNotification()) {
820 case 1:
821 $this->object->sendSimpleNotification($active_id);
822 break;
823 case 2:
824 $this->object->sendAdvancedNotification($active_id);
825 break;
826 }
827 }
828 }
829
830 // no redirect request loops after test pass finished tasks has been performed
831
832 $this->performTestPassFinishedTasks($actualpass);
833
835 }
836
837 protected function performTestPassFinishedTasks($finishedPass)
838 {
839 $finishTasks = new ilTestPassFinishTasks($this->testSession->getActiveId(), $this->object->getId());
840 $finishTasks->performFinishTasks($this->processLocker);
841 }
842
843 protected function afterTestPassFinishedCmd()
844 {
845 $activeId = $this->testSession->getActiveId();
846 $lastFinishedPass = $this->testSession->getLastFinishedPass();
847
848 // handle test signature
849 if ($this->isTestSignRedirectRequired($activeId, $lastFinishedPass)) {
850 $this->ctrl->redirectByClass('ilTestSignatureGUI', 'invokeSignaturePlugin');
851 }
852
853 // show final statement
854 if (!$this->testrequest->isset('skipfinalstatement')) {
855 if ($this->object->getShowFinalStatement()) {
856 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_FINAL_STATMENT);
857 }
858 }
859
860 // redirect after test
861 $redirection_mode = $this->object->getRedirectionMode();
862 $redirection_url = $this->object->getRedirectionUrl();
863 if ($redirection_url && $redirection_mode) {
864 if ($redirection_mode == REDIRECT_KIOSK) {
865 if ($this->object->getKioskMode()) {
866 ilUtil::redirect($redirection_url);
867 }
868 } else {
869 ilUtil::redirect($redirection_url);
870 }
871 }
872
873 // default redirect (pass overview when enabled, otherwise infoscreen)
874 $this->redirectBackCmd();
875 }
876
877 protected function isTestSignRedirectRequired($activeId, $lastFinishedPass): bool
878 {
879 if (!$this->object->getSignSubmission()) {
880 return false;
881 }
882
883 if (!is_null(ilSession::get("signed_{$activeId}_{$lastFinishedPass}"))) {
884 return false;
885 }
886
887 global $DIC;
889 $component_repository = $DIC["component.repository"];
890 return $component_repository->getPluginSlotById("tsig")->hasActivePlugins();
891 }
892
898 protected function archiveParticipantSubmission($active, $pass)
899 {
900 global $DIC;
901 $ilObjDataCache = $DIC['ilObjDataCache'];
902
903 $testResultHeaderLabelBuilder = new ilTestResultHeaderLabelBuilder($this->lng, $ilObjDataCache);
904
905 $objectivesList = null;
906
907 if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
908 $testSequence = $this->testSequenceFactory->getSequenceByActiveIdAndPass($this->testSession->getActiveId(), $this->testSession->getPass());
909 $testSequence->loadFromDb();
910 $testSequence->loadQuestions();
911
912 $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($this->testSession);
913
914 $objectivesList = $this->buildQuestionRelatedObjectivesList($objectivesAdapter, $testSequence);
915 $objectivesList->loadObjectivesTitles();
916
917 $testResultHeaderLabelBuilder->setObjectiveOrientedContainerId($this->testSession->getObjectiveOrientedContainerId());
918 $testResultHeaderLabelBuilder->setUserId($this->testSession->getUserId());
919 $testResultHeaderLabelBuilder->setTestObjId($this->object->getId());
920 $testResultHeaderLabelBuilder->setTestRefId($this->object->getRefId());
921 $testResultHeaderLabelBuilder->initObjectiveOrientedMode();
922 }
923
924 $results = $this->object->getTestResult(
925 $active,
926 $pass,
927 false,
928 !$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()
929 );
930
931 $testevaluationgui = new ilTestEvaluationGUI($this->object);
932 $results_output = $testevaluationgui->getPassListOfAnswers(
933 $results,
934 $active,
935 $pass,
936 false,
937 false,
938 false,
939 false,
940 false,
941 $objectivesList,
942 $testResultHeaderLabelBuilder
943 );
944
945 global $DIC;
946 $ilSetting = $DIC['ilSetting'];
947 $inst_id = $ilSetting->get('inst_id', null);
948 $archiver = new ilTestArchiver($this->object->getId());
949
950 $path = ilFileUtils::getWebspaceDir() . '/assessment/' . $this->object->getId() . '/exam_pdf';
951 if (!is_dir($path)) {
953 }
954 $filename = realpath($path) . '/exam_N' . $inst_id . '-' . $this->object->getId()
955 . '-' . $active . '-' . $pass . '.pdf';
956
958 //$template->setVariable("PDF_FILE_LOCATION", $filename);
959 // Participant submission
960 $archiver->handInParticipantSubmission($active, $pass, $filename, $results_output);
961 //$archiver->handInParticipantMisc( $active, $pass, 'signature_gedoens.sig', $filename );
962 //$archiver->handInParticipantQuestionMaterial( $active, $pass, 123, 'file_upload.pdf', $filename );
963
964 global $DIC;
965 $ilias = $DIC['ilias'];
966 $questions = $this->object->getQuestions();
967 foreach ($questions as $question_id) {
968 $question_object = $this->object->getQuestionDataset($question_id);
969 if ($question_object->type_tag == 'assFileUpload') {
970 // Pfad: /data/default/assessment/tst_2/14/21/files/file_14_4_1370417414.png
971 // /data/ - klar
972 // /assessment/ - Konvention
973 // /tst_2/ = /tst_<test_id> (ilObjTest)
974 // /14/ = /<active_fi>/
975 // /21/ = /<question_id>/ (question_object)
976 // /files/ - Konvention
977 // file_14_4_1370417414.png = file_<active_fi>_<pass>_<some timestamp>.<ext>
978
979 $candidate_path =
980 $ilias->ini_ilias->readVariable('server', 'absolute_path') . ilTestArchiver::DIR_SEP
981 . $ilias->ini_ilias->readVariable('clients', 'path') . ilTestArchiver::DIR_SEP
982 . $ilias->client_id . ilTestArchiver::DIR_SEP
983 . 'assessment' . ilTestArchiver::DIR_SEP
984 . 'tst_' . $this->object->test_id . ilTestArchiver::DIR_SEP
985 . $active . ilTestArchiver::DIR_SEP
986 . $question_id . ilTestArchiver::DIR_SEP
987 . 'files' . ilTestArchiver::DIR_SEP;
988 $handle = opendir($candidate_path);
989 while ($handle != false && ($file = readdir($handle)) !== false) {
990 if ($file != null) {
991 $filename_start = 'file_' . $active . '_' . $pass . '_';
992
993 if (strpos($file, $filename_start) === 0) {
994 $archiver->handInParticipantQuestionMaterial($active, $pass, $question_id, $file, $file);
995 }
996 }
997 }
998 }
999 }
1000 $passdata = $this->object->getTestResult(
1001 $active,
1002 $pass,
1003 false,
1004 !$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()
1005 );
1006 $overview = $testevaluationgui->getPassListOfAnswers(
1007 $passdata,
1008 $active,
1009 $pass,
1010 true,
1011 false,
1012 false,
1013 true,
1014 false,
1015 $objectivesList,
1016 $testResultHeaderLabelBuilder
1017 );
1018 $filename = realpath(ilFileUtils::getWebspaceDir()) . '/assessment/scores-' . $this->object->getId() . '-' . $active . '-' . $pass . '.pdf';
1020 $archiver->handInTestResult($active, $pass, $filename);
1021 unlink($filename);
1022
1023 return;
1024 }
1025 public function redirectBackCmd()
1026 {
1027 $testPassesSelector = new ilTestPassesSelector($this->db, $this->object);
1028 $testPassesSelector->setActiveId($this->testSession->getActiveId());
1029 $testPassesSelector->setLastFinishedPass($this->testSession->getLastFinishedPass());
1030
1031 if (count($testPassesSelector->getReportablePasses())) {
1032 if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
1033 $this->ctrl->redirectByClass(array('ilTestResultsGUI', 'ilTestEvalObjectiveOrientedGUI'));
1034 }
1035
1036 $this->ctrl->redirectByClass(array('ilTestResultsGUI', 'ilMyTestResultsGUI', 'ilTestEvaluationGUI'));
1037 }
1038
1039 $this->backToInfoScreenCmd();
1040 }
1041
1042 protected function backToInfoScreenCmd()
1043 {
1044 $this->ctrl->redirectByClass('ilObjTestGUI', 'redirectToInfoScreen');
1045 }
1046
1047 /*
1048 * Presents the final statement of a test
1049 */
1050 public function showFinalStatementCmd()
1051 {
1053 global $DIC;
1054 $DIC->globalScreen()->tool()->context()->current()->getAdditionalData()->replace(
1056 $this->object->getTitle() . ' - ' . $this->lng->txt('final_statement')
1057 );
1058
1059 $template = new ilTemplate("tpl.il_as_tst_final_statement.html", true, true, "Modules/Test");
1060 $this->ctrl->setParameter($this, "skipfinalstatement", 1);
1061 $template->setVariable("FORMACTION", $this->ctrl->getFormAction($this, ilTestPlayerCommands::AFTER_TEST_PASS_FINISHED));
1062 $template->setVariable("FINALSTATEMENT", $this->object->prepareTextareaOutput($this->object->getFinalStatement(), true));
1063 $template->setVariable("BUTTON_CONTINUE", $this->lng->txt("btn_next"));
1064 $this->tpl->setVariable($this->getContentBlockName(), $template->get());
1065 }
1066
1067 public function getKioskHead(): string
1068 {
1069 global $DIC;
1070 $ilUser = $DIC['ilUser'];
1071
1072 //this is an abomination for release_8!
1073 //proper "kiosk-handling" is _very_ much encouraged for 9.
1074 $this->tpl->addCSS('Modules/Test/templates/default/test_kiosk_header.css');
1075 //end of hack
1076
1077 $template = new ilTemplate('tpl.il_as_tst_kiosk_head.html', true, true, 'Modules/Test');
1078 if ($this->object->getShowKioskModeTitle()) {
1079 $template->setCurrentBlock("kiosk_show_title");
1080 $template->setVariable("TEST_TITLE", $this->object->getTitle());
1081 $template->parseCurrentBlock();
1082 }
1083 if ($this->object->getShowKioskModeParticipant()) {
1084 $template->setCurrentBlock("kiosk_show_participant");
1085 $template->setVariable("PARTICIPANT_NAME_TXT", $this->lng->txt("login_as"));
1086 $template->setVariable("PARTICIPANT_NAME", $ilUser->getFullname());
1087 $template->setVariable("PARTICIPANT_LOGIN", $ilUser->getLogin());
1088 $template->setVariable("PARTICIPANT_MATRICULATION", $ilUser->getMatriculation());
1089 $template->setVariable("PARTICIPANT_EMAIL", $ilUser->getEmail());
1090 $template->parseCurrentBlock();
1091 }
1092 if ($this->object->isShowExamIdInTestPassEnabled()) {
1093 $exam_id = ilObjTest::buildExamId(
1094 $this->testSession->getActiveId(),
1095 $this->testSession->getPass(),
1096 $this->object->getId()
1097 );
1098
1099 $template->setCurrentBlock("kiosk_show_exam_id");
1100 $template->setVariable("EXAM_ID_TXT", $this->lng->txt("exam_id"));
1101 $template->setVariable("EXAM_ID", $exam_id);
1102 $template->parseCurrentBlock();
1103 }
1104 return $template->get();
1105 }
1106
1107 protected function prepareTestPage($presentationMode, $sequenceElement, $questionId)
1108 {
1109 global $DIC;
1110 $ilUser = $DIC['ilUser'];
1111 $ilNavigationHistory = $DIC['ilNavigationHistory'];
1112
1113 $ilNavigationHistory->addItem(
1114 $this->testSession->getRefId(),
1115 $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::RESUME_PLAYER),
1116 'tst'
1117 );
1118
1119 $this->initTestPageTemplate();
1121 $this->populateSyntaxStyleBlock();
1122
1123 if ($this->isMaxProcessingTimeReached()) {
1124 $this->maxProcessingTimeReached();
1125 return;
1126 }
1127
1128 if ($this->object->endingTimeReached()) {
1129 $this->endingTimeReached();
1130 return;
1131 }
1132
1133 if ($this->isOptionalQuestionAnsweringConfirmationRequired($sequenceElement)) {
1134 $this->ctrl->setParameter($this, "sequence", $sequenceElement);
1136 return;
1137 }
1138
1139 if ($this->object->getKioskMode()) {
1140 $this->populateKioskHead();
1141 }
1142
1143 $this->tpl->setVariable("TEST_ID", (string) $this->object->getTestId());
1144 $this->tpl->setVariable("LOGIN", $ilUser->getLogin());
1145 $this->tpl->setVariable("SEQ_ID", $sequenceElement);
1146 $this->tpl->setVariable("QUEST_ID", $questionId);
1147
1148 if ($this->object->getEnableProcessingTime()) {
1149 $this->outProcessingTime($this->testSession->getActiveId());
1150 }
1151
1152 $this->tpl->setVariable("PAGETITLE", "- " . $this->object->getTitle());
1153
1154 if ($this->object->isShowExamIdInTestPassEnabled() && !$this->object->getKioskMode()) {
1155 $this->tpl->setCurrentBlock('exam_id_footer');
1156 $this->tpl->setVariable('EXAM_ID_VAL', ilObjTest::lookupExamId(
1157 $this->testSession->getActiveId(),
1158 $this->testSession->getPass(),
1159 $this->object->getId()
1160 ));
1161 $this->tpl->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id'));
1162 $this->tpl->parseCurrentBlock();
1163 }
1164
1165 if ($this->object->getListOfQuestions()) {
1166 $this->showSideList($presentationMode, $sequenceElement);
1167 }
1168 }
1169
1170 abstract protected function isOptionalQuestionAnsweringConfirmationRequired($sequenceElement);
1171
1172 abstract protected function isShowingPostponeStatusReguired($questionId);
1173
1174 protected function showQuestionViewable(assQuestionGUI $questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse)
1175 {
1176 $questionNavigationGUI = $this->buildReadOnlyStateQuestionNavigationGUI($questionGui->object->getId());
1177 $questionNavigationGUI->setQuestionWorkedThrough($isQuestionWorkedThrough);
1178 $questionGui->setNavigationGUI($questionNavigationGUI);
1179
1180 // fau: testNav - set answere status in question header
1181 $questionGui->getQuestionHeaderBlockBuilder()->setQuestionAnswered($isQuestionWorkedThrough);
1182 // fau.
1183
1184 $answerFeedbackEnabled = (
1185 $instantResponse && $this->object->getSpecificAnswerFeedback()
1186 );
1187
1188 $solutionoutput = $questionGui->getSolutionOutput(
1189 $this->testSession->getActiveId(), #active_id
1190 $this->testSession->getPass(), #pass
1191 false, #graphical_output
1192 false, #result_output
1193 true, #show_question_only
1194 $answerFeedbackEnabled, #show_feedback
1195 false, #show_correct_solution
1196 false, #show_manual_scoring
1197 true #show_question_text
1198 );
1199
1200 $pageoutput = $questionGui->outQuestionPage(
1201 "",
1202 $this->isShowingPostponeStatusReguired($questionGui->object->getId()),
1203 $this->testSession->getActiveId(),
1204 $solutionoutput
1205 );
1206
1207 //$this->tpl->setCurrentBlock('readonly_css_class');
1208 //$this->tpl->touchBlock('readonly_css_class');
1209 global $DIC;
1210 $f = $DIC->ui()->factory();
1211 $renderer = $DIC->ui()->renderer();
1212
1213 $this->tpl->setVariable(
1214 'LOCKSTATE_INFOBOX',
1215 $renderer->render($f->messageBox()->info($this->lng->txt("tst_player_answer_saved_and_locked")))
1216 );
1217 $this->tpl->parseCurrentBlock();
1218
1219 $this->tpl->setVariable('QUESTION_OUTPUT', $pageoutput);
1220
1221 $this->tpl->setVariable("FORMACTION", $formAction);
1222 $this->tpl->setVariable("ENCTYPE", 'enctype="' . $questionGui->getFormEncodingType() . '"');
1223 $this->tpl->setVariable("FORM_TIMESTAMP", time());
1224 }
1225
1226 protected function showQuestionEditable(assQuestionGUI $questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse)
1227 {
1228 $questionNavigationGUI = $this->buildEditableStateQuestionNavigationGUI(
1229 $questionGui->object->getId(),
1230 $this->populateCharSelectorIfRequired()
1231 );
1232 if ($isQuestionWorkedThrough) {
1233 $questionNavigationGUI->setDiscardSolutionButtonEnabled(true);
1234 // fau: testNav - set answere status in question header
1235 $questionGui->getQuestionHeaderBlockBuilder()->setQuestionAnswered(true);
1236 // fau.
1237 } elseif ($this->object->isPostponingEnabled()) {
1238 $questionNavigationGUI->setSkipQuestionLinkTarget(
1239 $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::SKIP_QUESTION)
1240 );
1241 }
1242 $questionGui->setNavigationGUI($questionNavigationGUI);
1243
1244 $isPostponed = $this->isShowingPostponeStatusReguired($questionGui->object->getId());
1245
1246 $answerFeedbackEnabled = (
1247 $instantResponse && $this->object->getSpecificAnswerFeedback()
1248 );
1249
1250 if ($this->testrequest->isset('save_error') && $this->testrequest->raw('save_error') == 1 && ilSession::get('previouspost') != null) {
1251 $userPostSolution = ilSession::get('previouspost');
1252 ilSession::clear('previouspost');
1253 } else {
1254 $userPostSolution = false;
1255 }
1256
1257 // fau: testNav - add special checkbox for mc question
1258 // moved to another patch block
1259 // fau.
1260
1261 // hey: prevPassSolutions - determine solution pass index and configure gui accordingly
1262 $qstConfig = $questionGui->object->getTestPresentationConfig();
1263
1264 if ($questionGui instanceof assMultipleChoiceGUI) {
1265 $qstConfig->setWorkedThrough($isQuestionWorkedThrough);
1266 }
1267
1268 if ($qstConfig->isPreviousPassSolutionReuseAllowed()) {
1269 $passIndex = $this->determineSolutionPassIndex($questionGui); // last pass having solution stored
1270 if ($passIndex < $this->testSession->getPass()) { // it's the previous pass if current pass is higher
1271 $qstConfig->setSolutionInitiallyPrefilled(true);
1272 }
1273 } else {
1274 $passIndex = $this->testSession->getPass();
1275 }
1276 // hey.
1277
1278 // Answer specific feedback is rendered into the display of the test question with in the concrete question types outQuestionForTest-method.
1279 // Notation of the params prior to getting rid of this crap in favor of a class
1280 $questionGui->outQuestionForTest(
1281 $formAction, #form_action
1282 $this->testSession->getActiveId(), #active_id
1283 // hey: prevPassSolutions - prepared pass index having no, current or previous solution
1284 $passIndex, #pass
1285 // hey.
1286 $isPostponed, #is_postponed
1287 $userPostSolution, #user_post_solution
1288 $answerFeedbackEnabled #answer_feedback == inline_specific_feedback
1289 );
1290 // The display of specific inline feedback and specific feedback in an own block is to honor questions, which
1291 // have the possibility to embed the specific feedback into their output while maintaining compatibility to
1292 // questions, which do not have such facilities. E.g. there can be no "specific inline feedback" for essay
1293 // questions, while the multiple-choice questions do well.
1294
1295
1296 $this->populateModals();
1297
1298 // fau: testNav - pouplate the new question edit control instead of the deprecated intermediate solution saver
1299 $this->populateQuestionEditControl($questionGui);
1300 // fau.
1301 }
1302
1303 // hey: prevPassSolutions - determine solution pass index
1304 protected function determineSolutionPassIndex(assQuestionGUI $questionGui): int
1305 {
1306 if ($this->object->isPreviousSolutionReuseEnabled($this->testSession->getActiveId())) {
1307 $currentSolutionAvailable = $questionGui->object->authorizedOrIntermediateSolutionExists(
1308 $this->testSession->getActiveId(),
1309 $this->testSession->getPass()
1310 );
1311
1312 if (!$currentSolutionAvailable) {
1313 $previousPass = $questionGui->object->getSolutionMaxPass(
1314 $this->testSession->getActiveId()
1315 );
1316
1317 $previousSolutionAvailable = $questionGui->object->authorizedSolutionExists(
1318 $this->testSession->getActiveId(),
1319 $previousPass
1320 );
1321
1322 if ($previousSolutionAvailable) {
1323 return $previousPass;
1324 }
1325
1326 }
1327 }
1328
1329 return $this->testSession->getPass();
1330 }
1331 // hey.
1332
1333 abstract protected function showQuestionCmd();
1334
1335 abstract protected function editSolutionCmd();
1336
1337 abstract protected function submitSolutionCmd();
1338
1339 // fau: testNav - new function to revert probably auto-saved changes and show the last submitted question state
1340 protected function revertChangesCmd()
1341 {
1343 $this->setAnswerChangedParameter(false);
1344 $this->ctrl->saveParameter($this, 'sequence');
1345 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
1346 }
1347 // fau.
1348
1349 abstract protected function discardSolutionCmd();
1350
1351 abstract protected function skipQuestionCmd();
1352
1353 abstract protected function startTestCmd();
1361 public function checkOnlineTestAccess()
1362 {
1363 global $DIC;
1364 $ilUser = $DIC['ilUser'];
1365
1366 // check if user is invited to participate
1367 $user = $this->object->getInvitedUsers($ilUser->getId());
1368 if (!is_array($user) || count($user) != 1) {
1369 $this->tpl->setOnScreenMessage('info', $this->lng->txt("user_not_invited"), true);
1370 $this->ctrl->redirectByClass("ilobjtestgui", "backToRepository");
1371 }
1372
1373 $user = array_pop($user);
1374 // check if client ip is set and if current remote addr is equal to stored client-ip
1375 if (strcmp($user["clientip"], "") != 0 && strcmp($user["clientip"], $_SERVER["REMOTE_ADDR"]) != 0) {
1376 $this->tpl->setOnScreenMessage('info', $this->lng->txt("user_wrong_clientip"), true);
1377 $this->ctrl->redirectByClass("ilobjtestgui", "backToRepository");
1378 }
1379 }
1380
1381
1385 public function isTestAccessible(): bool
1386 {
1387 return !$this->isNrOfTriesReached()
1388 and !$this->isMaxProcessingTimeReached()
1389 and $this->object->startingTimeReached()
1390 and !$this->object->endingTimeReached();
1391 }
1392
1396 public function isNrOfTriesReached(): bool
1397 {
1398 return $this->object->hasNrOfTriesRestriction() && $this->object->isNrOfTriesReached($this->testSession->getPass());
1399 }
1400
1406 public function endingTimeReached()
1407 {
1408 $this->tpl->setOnScreenMessage('info', sprintf($this->lng->txt("detail_ending_time_reached"), ilDatePresentation::formatDate(new ilDateTime($this->object->getEndingTime(), IL_CAL_UNIX))));
1409 $this->testSession->increasePass();
1410 $this->testSession->setLastSequence(0);
1411 $this->testSession->saveToDb();
1412
1413 $this->redirectBackCmd();
1414 }
1415
1424 {
1425 $this->suspendTestCmd();
1426 }
1427
1433 public function confirmSubmitAnswers()
1434 {
1435 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_submit_answers_confirm.html", "Modules/Test");
1436 $this->tpl->setCurrentBlock("adm_content");
1437 if ($this->object->isTestFinished($this->testSession->getActiveId())) {
1438 $this->tpl->setCurrentBlock("not_submit_allowed");
1439 $this->tpl->setVariable("TEXT_ALREADY_SUBMITTED", $this->lng->txt("tst_already_submitted"));
1440 $this->tpl->setVariable("BTN_OK", $this->lng->txt("tst_show_answer_sheet"));
1441 } else {
1442 $this->tpl->setCurrentBlock("submit_allowed");
1443 $this->tpl->setVariable("TEXT_CONFIRM_SUBMIT_RESULTS", $this->lng->txt("tst_confirm_submit_answers"));
1444 $this->tpl->setVariable("BTN_OK", $this->lng->txt("tst_submit_results"));
1445 }
1446 $this->tpl->setVariable("BTN_BACK", $this->lng->txt("back"));
1447 $this->tpl->setVariable("FORMACTION", $this->ctrl->getFormAction($this, "finalSubmission"));
1448 $this->tpl->parseCurrentBlock();
1449 }
1450
1451 public function outProcessingTime($active_id)
1452 {
1453 global $DIC;
1454 $ilUser = $DIC['ilUser'];
1455
1456 $starting_time = $this->object->getStartingTimeOfUser($active_id);
1457 $processing_time = $this->object->getProcessingTimeInSeconds($active_id);
1458 $processing_time_minutes = floor($processing_time / 60);
1459 $processing_time_seconds = $processing_time - $processing_time_minutes * 60;
1460 $str_processing_time = "";
1461 if ($processing_time_minutes > 0) {
1462 $str_processing_time = $processing_time_minutes . " " . ($processing_time_minutes == 1 ? $this->lng->txt("minute") : $this->lng->txt("minutes"));
1463 }
1464 if ($processing_time_seconds > 0) {
1465 if (strlen($str_processing_time) > 0) {
1466 $str_processing_time .= " " . $this->lng->txt("and") . " ";
1467 }
1468 $str_processing_time .= $processing_time_seconds . " " . ($processing_time_seconds == 1 ? $this->lng->txt("second") : $this->lng->txt("seconds"));
1469 }
1470 $time_left = $starting_time + $processing_time - time();
1471 $time_left_minutes = floor($time_left / 60);
1472 $time_left_seconds = $time_left - $time_left_minutes * 60;
1473 $str_time_left = "";
1474 if ($time_left_minutes > 0) {
1475 $str_time_left = $time_left_minutes . " " . ($time_left_minutes == 1 ? $this->lng->txt("minute") : $this->lng->txt("minutes"));
1476 }
1477 if ($time_left < 300) {
1478 if ($time_left_seconds > 0) {
1479 if (strlen($str_time_left) > 0) {
1480 $str_time_left .= " " . $this->lng->txt("and") . " ";
1481 }
1482 $str_time_left .= $time_left_seconds . " " . ($time_left_seconds == 1 ? $this->lng->txt("second") : $this->lng->txt("seconds"));
1483 }
1484 }
1485 $date = getdate($starting_time);
1486 $formattedStartingTime = ilDatePresentation::formatDate(new ilDateTime($date, IL_CAL_FKT_GETDATE));
1487 $datenow = getdate();
1488 $this->tpl->setCurrentBlock("enableprocessingtime");
1489 $this->tpl->setVariable(
1490 "USER_WORKING_TIME",
1491 sprintf(
1492 $this->lng->txt("tst_time_already_spent"),
1493 $formattedStartingTime,
1494 $str_processing_time
1495 )
1496 );
1497 $this->tpl->setVariable("USER_REMAINING_TIME", sprintf($this->lng->txt("tst_time_already_spent_left"), $str_time_left));
1498 $this->tpl->parseCurrentBlock();
1499
1500 // jQuery is required by tpl.workingtime.js
1502 $template = new ilTemplate("tpl.workingtime.js", true, true, 'Modules/Test');
1503 $template->setVariable("STRING_MINUTE", $this->lng->txt("minute"));
1504 $template->setVariable("STRING_MINUTES", $this->lng->txt("minutes"));
1505 $template->setVariable("STRING_SECOND", $this->lng->txt("second"));
1506 $template->setVariable("STRING_SECONDS", $this->lng->txt("seconds"));
1507 $template->setVariable("STRING_TIMELEFT", $this->lng->txt("tst_time_already_spent_left"));
1508 $template->setVariable("AND", strtolower($this->lng->txt("and")));
1509 $template->setVariable("YEAR", $date["year"]);
1510 $template->setVariable("MONTH", $date["mon"] - 1);
1511 $template->setVariable("DAY", $date["mday"]);
1512 $template->setVariable("HOUR", $date["hours"]);
1513 $template->setVariable("MINUTE", $date["minutes"]);
1514 $template->setVariable("SECOND", $date["seconds"]);
1515 if ($this->object->isEndingTimeEnabled()) {
1516 $date_time = new ilDateTime($this->object->getEndingTime(), IL_CAL_UNIX);
1517 preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $date_time->get(IL_CAL_TIMESTAMP), $matches);
1518 if (!empty($matches)) {
1519 $template->setVariable("ENDYEAR", $matches[1]);
1520 $template->setVariable("ENDMONTH", $matches[2] - 1);
1521 $template->setVariable("ENDDAY", $matches[3]);
1522 $template->setVariable("ENDHOUR", $matches[4]);
1523 $template->setVariable("ENDMINUTE", $matches[5]);
1524 $template->setVariable("ENDSECOND", $matches[6]);
1525 }
1526 }
1527 $template->setVariable("YEARNOW", $datenow["year"]);
1528 $template->setVariable("MONTHNOW", $datenow["mon"] - 1);
1529 $template->setVariable("DAYNOW", $datenow["mday"]);
1530 $template->setVariable("HOURNOW", $datenow["hours"]);
1531 $template->setVariable("MINUTENOW", $datenow["minutes"]);
1532 $template->setVariable("SECONDNOW", $datenow["seconds"]);
1533 $template->setVariable("PTIME_M", $processing_time_minutes);
1534 $template->setVariable("PTIME_S", $processing_time_seconds);
1535 if ($this->ctrl->getCmd() == 'outQuestionSummary') {
1536 $template->setVariable("REDIRECT_URL", $this->ctrl->getFormAction($this, 'redirectAfterDashboardCmd'));
1537 } else {
1538 $template->setVariable("REDIRECT_URL", "");
1539 }
1540 $template->setVariable("CHECK_URL", $this->ctrl->getLinkTarget($this, 'checkWorkingTime', '', true));
1541 $this->tpl->addOnLoadCode($template->get());
1542 }
1543
1551 public function checkWorkingTimeCmd(): void
1552 {
1553 $active_id = $this->testSession->getActiveId();
1554 echo (string) $this->object->getProcessingTimeInSeconds($active_id);
1555 exit;
1556 }
1557
1558 protected function showSideList($presentationMode, $currentSequenceElement)
1559 {
1560 global $DIC;
1561 $ilUser = $DIC['ilUser'];
1562
1563 $sideListActive = $ilUser->getPref('side_list_of_questions');
1564
1565 if ($sideListActive) {
1566 $questionSummaryData = $this->service->getQuestionSummaryData($this->testSequence, false);
1567
1568 $questionSideListGUI = new ilTestQuestionSideListGUI($this->ctrl, $this->lng);
1569 $questionSideListGUI->setTargetGUI($this);
1570 $questionSideListGUI->setQuestionSummaryData($questionSummaryData);
1571 $questionSideListGUI->setCurrentSequenceElement($currentSequenceElement);
1572 // fau: testNav - set side list presentation mode to "view" to allow navigation when question is in edit mode
1573 $questionSideListGUI->setCurrentPresentationMode(ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW);
1574 $questionSideListGUI->setDisabled(false);
1575 // $questionSideListGUI->setCurrentPresentationMode($presentationMode);
1576 // $questionSideListGUI->setDisabled($presentationMode == self::PRESENTATION_MODE_EDIT);
1577 // fau.
1578 $this->tpl->setVariable('LIST_OF_QUESTIONS', $questionSideListGUI->getHTML());
1579 }
1580 }
1581
1582 abstract protected function isQuestionSummaryFinishTestButtonRequired();
1583
1587 public function outQuestionSummaryCmd($fullpage = true, $contextFinishTest = false, $obligationsInfo = false, $obligationsFilter = false)
1588 {
1589 global $DIC;
1590 $help = $DIC->help();
1591
1592 $help->setScreenIdComponent("tst");
1593 $help->setScreenId("assessment");
1594 $help->setSubScreenId("question_summary");
1595
1596 $DIC->globalScreen()->tool()->context()->current()->getAdditionalData()->replace(
1598 $this->getObject()->getTitle() . ' - ' . $this->lng->txt('question_summary')
1599 );
1600
1601 if ($fullpage) {
1602 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_question_summary.html", "Modules/Test");
1603 }
1604
1605 $obligationsFulfilled = \ilObjTest::allObligationsAnswered(
1606 $this->object->getId(),
1607 $this->testSession->getActiveId(),
1608 $this->testSession->getPass()
1609 );
1610
1611 if ($obligationsInfo && $this->object->areObligationsEnabled() && !$obligationsFulfilled) {
1612 $this->tpl->setOnScreenMessage('failure', $this->lng->txt('not_all_obligations_answered'));
1613 }
1614
1615 if ($this->object->getKioskMode() && $fullpage) {
1616 $head = $this->getKioskHead();
1617 if (strlen($head)) {
1618 $this->tpl->setCurrentBlock("kiosk_options");
1619 $this->tpl->setVariable("KIOSK_HEAD", $head);
1620 $this->tpl->parseCurrentBlock();
1621 }
1622 }
1623
1624
1625 $active_id = $this->testSession->getActiveId();
1626 $questionSummaryData = $this->service->getQuestionSummaryData($this->testSequence, $obligationsFilter);
1627
1628 $this->ctrl->setParameter($this, "sequence", $this->testrequest->raw("sequence"));
1629
1630 if ($fullpage) {
1631 $table_gui = new ilListOfQuestionsTableGUI($this, 'showQuestion');
1632
1633 $table_gui->setShowPointsEnabled(!$this->object->getTitleOutput());
1634 $table_gui->setShowMarkerEnabled($this->object->getShowMarker());
1635 $table_gui->setObligationsNotAnswered(!$obligationsFulfilled);
1636 $table_gui->setShowObligationsEnabled($this->object->areObligationsEnabled());
1637 $table_gui->setObligationsFilterEnabled($obligationsFilter);
1638 $table_gui->setFinishTestButtonEnabled($this->isQuestionSummaryFinishTestButtonRequired());
1639
1640 $table_gui->init();
1641
1642 $table_gui->setData($questionSummaryData);
1643
1644 $this->tpl->setVariable('TABLE_LIST_OF_QUESTIONS', $table_gui->getHTML());
1645
1646 if ($this->object->getEnableProcessingTime()) {
1647 $this->outProcessingTime($active_id);
1648 }
1649
1650 if ($this->object->isShowExamIdInTestPassEnabled()) {
1651 $this->tpl->setCurrentBlock('exam_id_footer');
1652 $this->tpl->setVariable('EXAM_ID_VAL', ilObjTest::lookupExamId(
1653 $this->testSession->getActiveId(),
1654 $this->testSession->getPass(),
1655 $this->object->getId()
1656 ));
1657 $this->tpl->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id'));
1658 $this->tpl->parseCurrentBlock();
1659 }
1660 }
1661 }
1662
1664 {
1665 $this->outQuestionSummaryCmd(true, true, true, false);
1666 }
1667
1669 {
1670 $this->outQuestionSummaryCmd(true, true, true, true);
1671 }
1672
1674 {
1675 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_max_allowed_users_reached.html", "Modules/Test");
1676 $this->tpl->setCurrentBlock("adm_content");
1677 $this->tpl->setVariable("MAX_ALLOWED_USERS_MESSAGE", sprintf($this->lng->txt("tst_max_allowed_users_message"), $this->object->getAllowedUsersTimeGap()));
1678 $this->tpl->setVariable("MAX_ALLOWED_USERS_HEADING", sprintf($this->lng->txt("tst_max_allowed_users_heading"), $this->object->getAllowedUsersTimeGap()));
1679 $this->tpl->setVariable("CMD_BACK_TO_INFOSCREEN", ilTestPlayerCommands::BACK_TO_INFO_SCREEN);
1680 $this->tpl->setVariable("TXT_BACK_TO_INFOSCREEN", $this->lng->txt("tst_results_back_introduction"));
1681 $this->tpl->setVariable("FORMACTION", $this->ctrl->getFormAction($this));
1682 $this->tpl->parseCurrentBlock();
1683 }
1684
1685 public function backFromFinishingCmd()
1686 {
1687 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
1688 }
1689
1695 public function outCorrectSolution()
1696 {
1697 $this->tpl->addBlockFile("ADM_CONTENT", "adm_content", "tpl.il_as_tst_correct_solution.html", "Modules/Test");
1698
1699 $this->tpl->setCurrentBlock("ContentStyle");
1700 $this->tpl->setVariable("LOCATION_CONTENT_STYLESHEET", ilObjStyleSheet::getContentStylePath(0));
1701 $this->tpl->parseCurrentBlock();
1702
1703 $this->tpl->setCurrentBlock("SyntaxStyle");
1704 $this->tpl->setVariable("LOCATION_SYNTAX_STYLESHEET", ilObjStyleSheet::getSyntaxStylePath());
1705 $this->tpl->parseCurrentBlock();
1706
1707 $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print.css", "Modules/Test"), "print");
1708 if ($this->object->getShowSolutionAnswersOnly()) {
1709 $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print_hide_content.css", "Modules/Test"), "print");
1710 }
1711
1712 $this->tpl->setCurrentBlock("adm_content");
1713 $solution = $this->getCorrectSolutionOutput($this->testrequest->raw("evaluation"), $this->testrequest->raw("active_id"), $this->testrequest->raw("pass"));
1714 $this->tpl->setVariable("OUTPUT_SOLUTION", $solution);
1715 $this->tpl->setVariable("TEXT_BACK", $this->lng->txt("back"));
1716 $this->ctrl->saveParameter($this, "pass");
1717 $this->ctrl->saveParameter($this, "active_id");
1718 $this->tpl->setVariable("URL_BACK", $this->ctrl->getLinkTarget($this, "outUserResultsOverview"));
1719 $this->tpl->parseCurrentBlock();
1720 }
1721
1731 public function showListOfAnswers($active_id, $pass = null, $top_data = "", $bottom_data = "")
1732 {
1733 global $DIC;
1734 $ilUser = $DIC['ilUser'];
1735
1736 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_finish_list_of_answers.html", "Modules/Test");
1737
1738 $result_array = &$this->object->getTestResult(
1739 $active_id,
1740 $pass,
1741 false,
1742 !$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()
1743 );
1744
1745 $counter = 1;
1746 // output of questions with solutions
1747 foreach ($result_array as $question_data) {
1748 $question = $question_data["qid"];
1749 if (is_numeric($question)) {
1750 $this->tpl->setCurrentBlock("printview_question");
1751 $question_gui = $this->object->createQuestionGUI("", $question);
1752 $template = new ilTemplate("tpl.il_as_qpl_question_printview.html", true, true, "Modules/TestQuestionPool");
1753 $template->setVariable("COUNTER_QUESTION", $counter . ". ");
1754 $template->setVariable("QUESTION_TITLE", $question_gui->object->getTitleForHTMLOutput());
1755
1756 $show_question_only = ($this->object->getShowSolutionAnswersOnly()) ? true : false;
1757 $result_output = $question_gui->getSolutionOutput($active_id, $pass, false, false, $show_question_only, $this->object->getShowSolutionFeedback());
1758 $template->setVariable("SOLUTION_OUTPUT", $result_output);
1759 $this->tpl->setVariable("QUESTION_OUTPUT", $template->get());
1760 $this->tpl->parseCurrentBlock();
1761 $counter++;
1762 }
1763 }
1764
1765 $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print.css", "Modules/Test"), "print");
1766 if ($this->object->getShowSolutionAnswersOnly()) {
1767 $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print_hide_content.css", "Modules/Test"), "print");
1768 }
1769 if (strlen($top_data)) {
1770 $this->tpl->setCurrentBlock("top_data");
1771 $this->tpl->setVariable("TOP_DATA", $top_data);
1772 $this->tpl->parseCurrentBlock();
1773 }
1774
1775 if (strlen($bottom_data)) {
1776 $this->tpl->setCurrentBlock("bottom_data");
1777 $this->tpl->setVariable("FORMACTION", $this->ctrl->getFormAction($this));
1778 $this->tpl->setVariable("BOTTOM_DATA", $bottom_data);
1779 $this->tpl->parseCurrentBlock();
1780 }
1781
1782 $this->tpl->setCurrentBlock("adm_content");
1783 $this->tpl->setVariable("TXT_ANSWER_SHEET", $this->lng->txt("tst_list_of_answers"));
1784 $user_data = $this->getAdditionalUsrDataHtmlAndPopulateWindowTitle($this->testSession, $active_id, true);
1785 $signature = $this->getResultsSignature();
1786 $this->tpl->setVariable("USER_DETAILS", $user_data);
1787 $this->tpl->setVariable("SIGNATURE", $signature);
1788 $this->tpl->setVariable("TITLE", $this->object->getTitle());
1789 $this->tpl->setVariable("TXT_TEST_PROLOG", $this->lng->txt("tst_your_answers"));
1790 $invited_user = &$this->object->getInvitedUsers($ilUser->getId());
1791 $pagetitle = $this->object->getTitle() . " - " . $this->lng->txt("clientip") .
1792 ": " . $invited_user[$ilUser->getId()]["clientip"] . " - " .
1793 $this->lng->txt("matriculation") . ": " .
1794 $invited_user[$ilUser->getId()]["matriculation"];
1795 $this->tpl->setVariable("PAGETITLE", $pagetitle);
1796 $this->tpl->parseCurrentBlock();
1797 }
1798
1804 public function getContentBlockName(): string
1805 {
1806 return "ADM_CONTENT";
1807
1808 if ($this->object->getKioskMode()) {
1809 $this->tpl->setBodyClass("kiosk");
1810 $this->tpl->hideFooter();
1811 return "CONTENT";
1812 } else {
1813 return "ADM_CONTENT";
1814 }
1815 }
1816
1818 {
1819 $this->ctrl->redirectByClass(
1820 array('ilRepositoryGUI', 'ilObjTestGUI', 'ilTestEvaluationGUI'),
1821 "outUserResultsOverview"
1822 );
1823 }
1824
1828 protected function showRequestedHintListCmd()
1829 {
1830 // fau: testNav - handle intermediate submit for viewing requested hints
1831 $this->handleIntermediateSubmit();
1832 // fau.
1833
1834 $this->ctrl->setParameter($this, 'pmode', self::PRESENTATION_MODE_EDIT);
1835
1836 $this->ctrl->redirectByClass('ilAssQuestionHintRequestGUI', ilAssQuestionHintRequestGUI::CMD_SHOW_LIST);
1837 }
1838
1842 protected function confirmHintRequestCmd()
1843 {
1844 // fau: testNav - handle intermediate submit for confirming hint requests
1845 $this->handleIntermediateSubmit();
1846 // fau.
1847
1848 $this->ctrl->setParameter($this, 'pmode', self::PRESENTATION_MODE_EDIT);
1849
1850 $this->ctrl->redirectByClass('ilAssQuestionHintRequestGUI', ilAssQuestionHintRequestGUI::CMD_CONFIRM_REQUEST);
1851 }
1852
1853 abstract protected function isFirstQuestionInSequence($sequenceElement);
1854
1855 abstract protected function isLastQuestionInSequence($sequenceElement);
1856
1857
1858 abstract protected function handleQuestionActionCmd();
1859
1860 abstract protected function showInstantResponseCmd();
1861
1862 abstract protected function nextQuestionCmd();
1863
1864 abstract protected function previousQuestionCmd();
1865
1866 protected function prepareSummaryPage()
1867 {
1868 $this->tpl->addBlockFile(
1869 $this->getContentBlockName(),
1870 'adm_content',
1871 'tpl.il_as_tst_question_summary.html',
1872 'Modules/Test'
1873 );
1874
1875 if ($this->object->getKioskMode()) {
1876 $this->populateKioskHead();
1877 }
1878 }
1879
1880 protected function initTestPageTemplate()
1881 {
1882 $onload_js = <<<JS
1883 let key_event = (event) => {
1884 if( event.key === 13 && event.target.tagName.toLowerCase() === "a" ) {
1885 return;
1886 }
1887 if (event.key === 13 &&
1888 event.target.tagName.toLowerCase() !== "textarea" &&
1889 (event.target.tagName.toLowerCase() !== "input" || event.target.type.toLowerCase() !== "submit")) {
1890 event.preventDefault();
1891 }
1892 };
1893
1894 let form = document.getElementById('taForm');
1895 form.onkeyup = key_event;
1896 form.onkeydown = key_event;
1897 form.onkeypress = key_event;
1898JS;
1899 $this->tpl->addOnLoadCode($onload_js);
1900 $this->tpl->addBlockFile(
1901 $this->getContentBlockName(),
1902 'adm_content',
1903 'tpl.il_as_tst_output.html',
1904 'Modules/Test'
1905 );
1906 }
1907
1908 protected function populateKioskHead()
1909 {
1910 $head = $this->getKioskHead();
1911
1912 if (strlen($head)) {
1913 $this->tpl->setCurrentBlock("kiosk_options");
1914 $this->tpl->setVariable("KIOSK_HEAD", $head);
1915 $this->tpl->parseCurrentBlock();
1916 }
1917 }
1918
1920 {
1927 if ($this->testSession->isPasswordChecked() === true) {
1928 return;
1929 }
1930
1931 if ($this->ctrl->getNextClass() == 'iltestpasswordprotectiongui') {
1932 return;
1933 }
1934
1935 if (!$this->passwordChecker->isPasswordProtectionPageRedirectRequired()) {
1936 $this->testSession->setPasswordChecked(true);
1937 return;
1938 }
1939
1940 $this->ctrl->setParameter($this, 'lock', $this->getLockParameter());
1941
1942 $nextCommand = $this->ctrl->getCmdClass() . '::' . $this->ctrl->getCmd();
1943 $this->ctrl->setParameterByClass('ilTestPasswordProtectionGUI', 'nextCommand', $nextCommand);
1944 $this->ctrl->redirectByClass('ilTestPasswordProtectionGUI', 'showPasswordForm');
1945 }
1946
1947 protected function isParticipantsAnswerFixed($questionId): bool
1948 {
1949 if ($this->object->isInstantFeedbackAnswerFixationEnabled() && $this->testSequence->isQuestionChecked($questionId)) {
1950 return true;
1951 }
1952
1953 if ($this->object->isFollowupQuestionAnswerFixationEnabled() && $this->testSequence->isNextQuestionPresented($questionId)) {
1954 return true;
1955 }
1956
1957 return false;
1958 }
1959
1963 protected function getIntroductionPageButtonLabel(): string
1964 {
1965 return $this->lng->txt("save_introduction");
1966 }
1967
1968 protected function initAssessmentSettings()
1969 {
1970 $this->assSettings = new ilSetting('assessment');
1971 }
1972
1977 {
1978 $questionList = $this->buildTestPassQuestionList();
1979 $questionList->load();
1980
1981 $testResults = $this->object->getTestResult($testSession->getActiveId(), $testSession->getPass(), true);
1982
1983 $skillEvaluation = new ilTestSkillEvaluation($this->db, $this->object->getTestId(), $this->object->getRefId());
1984
1985 $skillEvaluation->setUserId($testSession->getUserId());
1986 $skillEvaluation->setActiveId($testSession->getActiveId());
1987 $skillEvaluation->setPass($testSession->getPass());
1988
1989 $skillEvaluation->setNumRequiredBookingsForSkillTriggering($this->assSettings->get(
1990 'ass_skl_trig_num_answ_barrier',
1992 ));
1993
1994
1995 $skillEvaluation->init($questionList);
1996 $skillEvaluation->evaluate($testResults);
1997
1998 $skillEvaluation->handleSkillTriggering();
1999 }
2000
2001 abstract protected function buildTestPassQuestionList();
2002
2004 {
2005 $confirmation = new ilTestAnswerOptionalQuestionsConfirmationGUI($this->lng);
2006
2007 $confirmation->setFormAction($this->ctrl->getFormAction($this));
2008 $confirmation->setCancelCmd('cancelAnswerOptionalQuestions');
2009 $confirmation->setConfirmCmd('confirmAnswerOptionalQuestions');
2010
2011 $confirmation->build($this->object->isFixedTest());
2012
2013 $this->populateHelperGuiContent($confirmation);
2014 }
2015
2017 {
2018 $this->testSequence->setAnsweringOptionalQuestionsConfirmed(true);
2019 $this->testSequence->saveToDb();
2020
2021 $this->ctrl->setParameter($this, 'activecommand', 'gotoquestion');
2022 $this->ctrl->redirect($this, 'redirectQuestion');
2023 }
2024
2026 {
2027 if ($this->object->getListOfQuestions()) {
2028 $this->ctrl->setParameter($this, 'activecommand', 'summary');
2029 } else {
2030 $this->ctrl->setParameter($this, 'activecommand', 'previous');
2031 }
2032
2033 $this->ctrl->redirect($this, 'redirectQuestion');
2034 }
2035
2039 protected function populateHelperGuiContent($helperGui)
2040 {
2041 if ($this->object->getKioskMode()) {
2042 //$this->tpl->setBodyClass("kiosk");
2043 $this->tpl->hideFooter();
2044 $this->tpl->addBlockfile('CONTENT', 'adm_content', "tpl.il_as_tst_kiosk_mode_content.html", "Modules/Test");
2045 $this->tpl->setContent($this->ctrl->getHTML($helperGui));
2046 } else {
2047 $this->tpl->setVariable($this->getContentBlockName(), $this->ctrl->getHTML($helperGui));
2048 }
2049 }
2050
2054 protected function populateCharSelectorIfRequired(): bool
2055 {
2056 global $DIC;
2057 $ilSetting = $DIC['ilSetting'];
2058
2059 if ($ilSetting->get('char_selector_availability') > 0) {
2060 $char_selector = ilCharSelectorGUI::_getCurrentGUI($this->object);
2061 if ($char_selector->getConfig()->getAvailability() == ilCharSelectorConfig::ENABLED) {
2062 $char_selector->addToPage();
2063 $this->tpl->setCurrentBlock('char_selector');
2064 $this->tpl->setVariable("CHAR_SELECTOR_TEMPLATE", $char_selector->getSelectorHtml());
2065 $this->tpl->parseCurrentBlock();
2066
2067 return true;
2068 }
2069 }
2070
2071 return false;
2072 }
2073
2075 {
2076 global $DIC;
2077 $ilUser = $DIC['ilUser'];
2078
2079 $navigationToolbarGUI = new ilTestNavigationToolbarGUI($this->ctrl, $this->lng, $this);
2080
2081 $navigationToolbarGUI->setSuspendTestButtonEnabled($this->object->getShowCancel());
2082 $navigationToolbarGUI->setQuestionTreeButtonEnabled($this->object->getListOfQuestions());
2083 $navigationToolbarGUI->setQuestionTreeVisible((bool) $ilUser->getPref('side_list_of_questions'));
2084 $navigationToolbarGUI->setQuestionListButtonEnabled($this->object->getListOfQuestions());
2085 $navigationToolbarGUI->setFinishTestCommand($this->getFinishTestCommand());
2086
2087 return $navigationToolbarGUI;
2088 }
2089
2091 {
2092 $navigationGUI = new ilTestQuestionNavigationGUI($this->lng);
2093
2094 if (!$this->isParticipantsAnswerFixed($questionId)) {
2095 $navigationGUI->setEditSolutionCommand(ilTestPlayerCommands::EDIT_SOLUTION);
2096 }
2097
2098 if ($this->object->getShowMarker()) {
2099 $solved_array = ilObjTest::_getSolvedQuestions($this->testSession->getActiveId(), $questionId);
2100 $solved = 0;
2101
2102 if (count($solved_array) > 0) {
2103 $solved = array_pop($solved_array);
2104 $solved = $solved["solved"];
2105 }
2106 // fau: testNav - change question mark command to link target
2107 if ($solved == 1) {
2108 $navigationGUI->setQuestionMarkLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::UNMARK_QUESTION));
2109 $navigationGUI->setQuestionMarked(true);
2110 } else {
2111 $navigationGUI->setQuestionMarkLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::MARK_QUESTION));
2112 $navigationGUI->setQuestionMarked(false);
2113 }
2114 }
2115 // fau.
2116
2117 return $navigationGUI;
2118 }
2119
2120 protected function buildEditableStateQuestionNavigationGUI($questionId, $charSelectorAvailable): ilTestQuestionNavigationGUI
2121 {
2122 $navigationGUI = new ilTestQuestionNavigationGUI($this->lng);
2123
2124 if ($this->object->isForceInstantFeedbackEnabled()) {
2125 $navigationGUI->setSubmitSolutionCommand(ilTestPlayerCommands::SUBMIT_SOLUTION);
2126 } else {
2127 // fau: testNav - use simple "submitSolution" button instead of "submitSolutionAndNext"
2128 $navigationGUI->setSubmitSolutionCommand(ilTestPlayerCommands::SUBMIT_SOLUTION);
2129 // fau.
2130 }
2131
2132 // fau: testNav - add a 'revert changes' link for editable question
2133 $navigationGUI->setRevertChangesLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::REVERT_CHANGES));
2134 // fau.
2135
2136
2137 // feedback
2138 switch (1) {
2139 case $this->object->getSpecificAnswerFeedback():
2140 case $this->object->getGenericAnswerFeedback():
2141 case $this->object->getAnswerFeedbackPoints():
2142 case $this->object->getInstantFeedbackSolution():
2143
2144 $navigationGUI->setAnswerFreezingEnabled($this->object->isInstantFeedbackAnswerFixationEnabled());
2145
2146 if ($this->object->isForceInstantFeedbackEnabled()) {
2147 $navigationGUI->setForceInstantResponseEnabled(true);
2148 $navigationGUI->setInstantFeedbackCommand(ilTestPlayerCommands::SUBMIT_SOLUTION);
2149 } else {
2150 $navigationGUI->setInstantFeedbackCommand(ilTestPlayerCommands::SHOW_INSTANT_RESPONSE);
2151 }
2152 }
2153
2154 // hints
2155 if ($this->object->isOfferingQuestionHintsEnabled()) {
2156 $activeId = $this->testSession->getActiveId();
2157 $pass = $this->testSession->getPass();
2158
2159 $questionHintTracking = new ilAssQuestionHintTracking($questionId, $activeId, $pass);
2160
2161 if ($questionHintTracking->requestsPossible()) {
2162 $navigationGUI->setRequestHintCommand(ilTestPlayerCommands::CONFIRM_HINT_REQUEST);
2163 }
2164
2165 if ($questionHintTracking->requestsExist()) {
2166 $navigationGUI->setShowHintsCommand(ilTestPlayerCommands::SHOW_REQUESTED_HINTS_LIST);
2167 }
2168 }
2169
2170 $navigationGUI->setCharSelectorEnabled($charSelectorAvailable);
2171
2172 if ($this->object->getShowMarker()) {
2173 $solved_array = ilObjTest::_getSolvedQuestions($this->testSession->getActiveId(), $questionId);
2174 $solved = 0;
2175
2176 if (count($solved_array) > 0) {
2177 $solved = array_pop($solved_array);
2178 $solved = $solved["solved"];
2179 }
2180
2181 // fau: testNav - change question mark command to link target
2182 if ($solved == 1) {
2183 $navigationGUI->setQuestionMarkLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::UNMARK_QUESTION_SAVE));
2184 $navigationGUI->setQuestionMarked(true);
2185 } else {
2186 $navigationGUI->setQuestionMarkLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::MARK_QUESTION_SAVE));
2187 $navigationGUI->setQuestionMarked(false);
2188 }
2189 }
2190 // fau.
2191 return $navigationGUI;
2192 }
2193
2197 protected function getFinishTestCommand(): string
2198 {
2199 if (!$this->object->getListOfQuestionsEnd()) {
2200 return 'finishTest';
2201 }
2202
2203 if ($this->object->areObligationsEnabled()) {
2204 $allObligationsAnswered = ilObjTest::allObligationsAnswered(
2205 $this->testSession->getTestId(),
2206 $this->testSession->getActiveId(),
2207 $this->testSession->getPass()
2208 );
2209
2210 if (!$allObligationsAnswered) {
2211 return 'outQuestionSummaryWithObligationsInfo';
2212 }
2213 }
2214
2215 return 'outQuestionSummary';
2216 }
2217
2218 protected function populateInstantResponseModal(assQuestionGUI $questionGui, $navUrl)
2219 {
2220 $questionGui->setNavigationGUI(null);
2221 $questionGui->getQuestionHeaderBlockBuilder()->setQuestionAnswered(true);
2222
2223 $answerFeedbackEnabled = $this->object->getSpecificAnswerFeedback();
2224
2225 $solutionoutput = $questionGui->getSolutionOutput(
2226 $this->testSession->getActiveId(), #active_id
2227 $this->testSession->getPass(), #pass
2228 false, #graphical_output
2229 false, #result_output
2230 true, #show_question_only
2231 $answerFeedbackEnabled, #show_feedback
2232 false, #show_correct_solution
2233 false, #show_manual_scoring
2234 true #show_question_text
2235 );
2236
2237 $pageoutput = $questionGui->outQuestionPage(
2238 "",
2239 $this->isShowingPostponeStatusReguired($questionGui->object->getId()),
2240 $this->testSession->getActiveId(),
2241 $solutionoutput
2242 );
2243
2244 $tpl = new ilTemplate('tpl.tst_player_response_modal.html', true, true, 'Modules/Test');
2245
2246 // populate the instant response blocks in the
2247 $saved_tpl = $this->tpl;
2248 $this->tpl = $tpl;
2249 $this->populateInstantResponseBlocks($questionGui, true);
2250 $this->tpl = $saved_tpl;
2251
2252 $tpl->setVariable('QUESTION_OUTPUT', $pageoutput);
2253
2254 $button = ilLinkButton::getInstance();
2255 $button->setId('tst_confirm_feedback');
2256 $button->setUrl($navUrl);
2257 $button->setCaption('proceed');
2258 $button->setPrimary(true);
2259 $tpl->setVariable('BUTTON', $button->render());
2260
2261 $modal = ilModalGUI::getInstance();
2262 $modal->setType(ilModalGUI::TYPE_LARGE);
2263 $modal->setId('tst_question_feedback_modal');
2264 $modal->setHeading($this->lng->txt('tst_instant_feedback'));
2265 $modal->setBody($tpl->get());
2266
2267 $this->tpl->addOnLoadCode("$('#tst_question_feedback_modal').modal('show');");
2268 $this->tpl->setVariable('INSTANT_RESPONSE_MODAL', $modal->getHTML());
2269 }
2270 // fau;
2271
2275 protected function populateInstantResponseBlocks(assQuestionGUI $questionGui, $authorizedSolution)
2276 {
2277 $response_available = false;
2278 $jump_to_response = false;
2279
2280 // This controls if the solution should be shown.
2281 // It gets the parameter "Scoring and Results" -> "Instant Feedback" -> "Show Solutions"
2282 if ($this->object->getInstantFeedbackSolution()) {
2283 $show_question_inline_score = $this->determineInlineScoreDisplay();
2284
2285 // Notation of the params prior to getting rid of this crap in favor of a class
2286 $solutionoutput = $questionGui->getSolutionOutput(
2287 $this->testSession->getActiveId(), #active_id
2288 null, #pass
2289 false, #graphical_output
2290 $show_question_inline_score, #result_output
2291 true, #show_question_only
2292 false, #show_feedback
2293 true, #show_correct_solution
2294 false, #show_manual_scoring
2295 false #show_question_text
2296 );
2297 $solutionoutput = str_replace('<h1 class="ilc_page_title_PageTitle"></h1>', '', $solutionoutput);
2298 $this->populateSolutionBlock($solutionoutput);
2299 $response_available = true;
2300 $jump_to_response = true;
2301 }
2302
2303 $reachedPoints = $questionGui->object->getAdjustedReachedPoints(
2304 $this->testSession->getActiveId(),
2305 ilObjTest::_getPass($this->testSession->getActiveId()),
2306 $authorizedSolution
2307 );
2308
2309 $maxPoints = $questionGui->object->getMaximumPoints();
2310
2311 $solutionCorrect = ($reachedPoints == $maxPoints);
2312
2313 // This controls if the score should be shown.
2314 // It gets the parameter "Scoring and Results" -> "Instant Feedback" -> "Show Results (Only Points)"
2315 if ($this->object->getAnswerFeedbackPoints()) {
2316 $this->populateScoreBlock($reachedPoints, $maxPoints);
2317 $response_available = true;
2318 $jump_to_response = true;
2319 }
2320
2321 // This controls if the generic feedback should be shown.
2322 // It gets the parameter "Scoring and Results" -> "Instant Feedback" -> "Show Solutions"
2323 if ($this->object->getGenericAnswerFeedback()) {
2324 if ($this->populateGenericFeedbackBlock($questionGui, $solutionCorrect)) {
2325 $response_available = true;
2326 $jump_to_response = true;
2327 }
2328 }
2329
2330 // This controls if the specific feedback should be shown.
2331 // It gets the parameter "Scoring and Results" -> "Instant Feedback" -> "Show Answer-Specific Feedback"
2332 if ($this->object->getSpecificAnswerFeedback()) {
2333 if ($questionGui->hasInlineFeedback()) {
2334 // Don't jump to the feedback below the question if some feedback is shown within the question
2335 $jump_to_response = false;
2336 } elseif ($this->populateSpecificFeedbackBlock($questionGui)) {
2337 $response_available = true;
2338 $jump_to_response = true;
2339 }
2340 }
2341
2342 $this->populateFeedbackBlockHeader($jump_to_response);
2343 if (!$response_available) {
2344 if ($questionGui->hasInlineFeedback()) {
2345 $this->populateFeedbackBlockMessage($this->lng->txt('tst_feedback_is_given_inline'));
2346 } else {
2347 $this->populateFeedbackBlockMessage($this->lng->txt('tst_feedback_not_available_for_answer'));
2348 }
2349 }
2350 }
2351
2352 protected function populateFeedbackBlockHeader($withFocusAnchor)
2353 {
2354 if ($withFocusAnchor) {
2355 $this->tpl->setCurrentBlock('inst_resp_id');
2356 $this->tpl->setVariable('INSTANT_RESPONSE_FOCUS_ID', 'focus');
2357 $this->tpl->parseCurrentBlock();
2358 }
2359
2360 $this->tpl->setCurrentBlock('instant_response_header');
2361 $this->tpl->setVariable('INSTANT_RESPONSE_HEADER', $this->lng->txt('tst_feedback'));
2362 $this->tpl->parseCurrentBlock();
2363 }
2364
2365 protected function populateFeedbackBlockMessage(string $a_message)
2366 {
2367 $this->tpl->setCurrentBlock('instant_response_message');
2368 $this->tpl->setVariable('INSTANT_RESPONSE_MESSAGE', $a_message);
2369 $this->tpl->parseCurrentBlock();
2370 }
2371
2372
2373 protected function getCurrentSequenceElement()
2374 {
2375 if ($this->getSequenceElementParameter()) {
2376 return $this->getSequenceElementParameter();
2377 }
2378
2379 return $this->testSession->getLastSequence();
2380 }
2381
2382 protected function getSequenceElementParameter()
2383 {
2384 if ($this->testrequest->isset('sequence')) {
2385 return $this->testrequest->raw('sequence');
2386 }
2387
2388 return null;
2389 }
2390
2391 protected function getPresentationModeParameter()
2392 {
2393 if ($this->testrequest->isset('pmode')) {
2394 return $this->testrequest->raw('pmode');
2395 }
2396
2397 return null;
2398 }
2399
2400 protected function getInstantResponseParameter()
2401 {
2402 if ($this->testrequest->isset('instresp')) {
2403 return $this->testrequest->raw('instresp');
2404 }
2405
2406 return null;
2407 }
2408
2409 protected function getNextCommandParameter()
2410 {
2411 if (isset($_POST['nextcmd']) && strlen($_POST['nextcmd'])) {
2412 return $_POST['nextcmd'];
2413 }
2414
2415 return null;
2416 }
2417
2418 protected function getNextSequenceParameter(): ?int
2419 {
2420 if (isset($_POST['nextseq']) && is_numeric($_POST['nextseq'])) {
2421 return (int) $_POST['nextseq'];
2422 }
2423
2424 return null;
2425 }
2426
2427 // fau: testNav - get the navigation url set by a submit from ilTestPlayerNavigationControl.js
2428 protected function getNavigationUrlParameter(): ?string
2429 {
2430 if (isset($_POST['test_player_navigation_url'])) {
2431 $navigation_url = $_POST['test_player_navigation_url'];
2432
2433 $navigation_url_parts = parse_url($navigation_url);
2434 $ilias_url_parts = parse_url(ilUtil::_getHttpPath());
2435
2436 if (!isset($navigation_url_parts['host']) || ($ilias_url_parts['host'] === $navigation_url_parts['host'])) {
2437 return $navigation_url;
2438 }
2439 }
2440
2441 return null;
2442 }
2443 // fau.
2444
2445 // fau: testNav - get set and check the 'answer_changed' url parameter
2451 protected function getAnswerChangedParameter(): bool
2452 {
2453 return !empty($this->testrequest->raw('test_answer_changed'));
2454 }
2455
2460 protected function setAnswerChangedParameter($changed = true)
2461 {
2462 $this->ctrl->setParameter($this, 'test_answer_changed', $changed ? '1' : '0');
2463 }
2464
2465
2471 protected function handleIntermediateSubmit()
2472 {
2473 if ($this->getAnswerChangedParameter()) {
2474 $this->saveQuestionSolution(false);
2475 } else {
2477 }
2479 }
2480 // fau.
2481
2482 // fau: testNav - save the switch to prevent the navigation confirmation
2487 {
2488 if (!empty($_POST['save_on_navigation_prevent_confirmation'])) {
2489 ilSession::set('save_on_navigation_prevent_confirmation', true);
2490 }
2491
2492 if (!empty($_POST[self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM])) {
2493 ilSession::set(self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM, true);
2494 }
2495 }
2496 // fau.
2497
2501 private $cachedQuestionGuis = array();
2502
2508 protected function getQuestionGuiInstance($question_id, $fromCache = true): object
2509 {
2510 $tpl = $this->tpl;
2511
2512 if (!$fromCache || !isset($this->cachedQuestionGuis[$question_id])) {
2513 $question_gui = $this->object->createQuestionGUI("", $question_id);
2514 $question_gui->setTargetGui($this);
2515 $question_gui->setPresentationContext(assQuestionGUI::PRESENTATION_CONTEXT_TEST);
2516 $question_gui->object->setObligationsToBeConsidered($this->object->areObligationsEnabled());
2517 $question_gui->populateJavascriptFilesRequiredForWorkForm($tpl);
2518 $question_gui->object->setOutputType(OUTPUT_JAVASCRIPT);
2519 $question_gui->object->setShuffler($this->buildQuestionAnswerShuffler(
2520 (string) $question_id,
2521 (string) $this->testSession->getActiveId(),
2522 (string) $this->testSession->getPass()
2523 ));
2524
2525 // hey: prevPassSolutions - determine solution pass index and configure gui accordingly
2526 $this->initTestQuestionConfig($question_gui->object);
2527 // hey.
2528
2529 $this->cachedQuestionGuis[$question_id] = $question_gui;
2530 }
2531
2532 return $this->cachedQuestionGuis[$question_id];
2533 }
2534
2538 private $cachedQuestionObjects = array();
2539
2544 protected function getQuestionInstance($questionId, $fromCache = true): assQuestion
2545 {
2546 global $DIC;
2547 $ilDB = $DIC['ilDB'];
2548 $ilUser = $DIC['ilUser'];
2549
2550 if (!$fromCache || !isset($this->cachedQuestionObjects[$questionId])) {
2551 $questionOBJ = assQuestion::_instantiateQuestion($questionId);
2552
2553 $assSettings = new ilSetting('assessment');
2554 $processLockerFactory = new ilAssQuestionProcessLockerFactory($assSettings, $ilDB);
2555 $processLockerFactory->setQuestionId($questionOBJ->getId());
2556 $processLockerFactory->setUserId($ilUser->getId());
2557 $processLockerFactory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging());
2558 $questionOBJ->setProcessLocker($processLockerFactory->getLocker());
2559
2560 $questionOBJ->setObligationsToBeConsidered($this->object->areObligationsEnabled());
2561 $questionOBJ->setOutputType(OUTPUT_JAVASCRIPT);
2562
2563 // hey: prevPassSolutions - determine solution pass index and configure gui accordingly
2564 $this->initTestQuestionConfig($questionOBJ);
2565 // hey.
2566
2567 $this->cachedQuestionObjects[$questionId] = $questionOBJ;
2568 }
2569
2570 return $this->cachedQuestionObjects[$questionId];
2571 }
2572
2573 // hey: prevPassSolutions - determine solution pass index and configure gui accordingly
2574 protected function initTestQuestionConfig(assQuestion $questionOBJ)
2575 {
2576 $questionOBJ->getTestPresentationConfig()->setPreviousPassSolutionReuseAllowed(
2577 $this->object->isPreviousSolutionReuseEnabled($this->testSession->getActiveId())
2578 );
2579 }
2580
2586 protected function handleTearsAndAngerQuestionIsNull($questionId, $sequenceElement)
2587 {
2588 global $DIC;
2589 $ilLog = $DIC['ilLog'];
2590
2591 $ilLog->write(
2592 "INV SEQ:"
2593 . "active={$this->testSession->getActiveId()} "
2594 . "qId=$questionId seq=$sequenceElement "
2595 . serialize($this->testSequence)
2596 );
2597
2598 $ilLog->logStack('INV SEQ');
2599
2600 $this->ctrl->setParameter($this, 'sequence', $this->testSequence->getFirstSequence());
2601 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
2602 }
2603
2607 protected function populateMessageContent($contentHTML)
2608 {
2609 if ($this->object->getKioskMode()) {
2610 $this->tpl->addBlockfile($this->getContentBlockName(), 'content', "tpl.il_as_tst_kiosk_mode_content.html", "Modules/Test");
2611 $this->tpl->setContent($contentHTML);
2612 } else {
2613 $this->tpl->setVariable($this->getContentBlockName(), $contentHTML);
2614 }
2615 }
2616
2617 protected function populateModals()
2618 {
2620 // fau: testNav - populateNavWhenChangedModal instead of populateNavWhileEditModal
2622 // fau.
2623
2624 if ($this->object->isFollowupQuestionAnswerFixationEnabled()) {
2626
2628 }
2629 }
2630
2631 protected function populateDiscardSolutionModal()
2632 {
2633 $tpl = new ilTemplate('tpl.tst_player_confirmation_modal.html', true, true, 'Modules/Test');
2634
2635 $tpl->setVariable('CONFIRMATION_TEXT', $this->lng->txt('discard_answer_confirmation'));
2636
2637 $button = ilSubmitButton::getInstance();
2638 $button->setCommand(ilTestPlayerCommands::DISCARD_SOLUTION);
2639 $button->setCaption('discard_answer');
2640 $tpl->setCurrentBlock('buttons');
2641 $tpl->setVariable('BUTTON', $button->render());
2642 $tpl->parseCurrentBlock();
2643
2644 $button = ilLinkButton::getInstance();
2645 $button->setId('tst_cancel_discard_button');
2646 $button->setCaption('cancel');
2647 $button->setPrimary(true);
2648 $tpl->setCurrentBlock('buttons');
2649 $tpl->setVariable('BUTTON', $button->render());
2650 $tpl->parseCurrentBlock();
2651
2652 $modal = ilModalGUI::getInstance();
2653 $modal->setId('tst_discard_solution_modal');
2654 $modal->setHeading($this->lng->txt('discard_answer'));
2655 $modal->setBody($tpl->get());
2656
2657 $this->tpl->setCurrentBlock('discard_solution_modal');
2658 $this->tpl->setVariable('DISCARD_SOLUTION_MODAL', $modal->getHTML());
2659 $this->tpl->parseCurrentBlock();
2660 }
2661
2662 protected function populateNavWhenChangedModal()
2663 {
2664 return; // usibility fix: get rid of popup
2665
2666 if (ilSession::get('save_on_navigation_prevent_confirmation') == null) {
2667 return;
2668 }
2669
2670 $tpl = new ilTemplate('tpl.tst_player_confirmation_modal.html', true, true, 'Modules/Test');
2671
2672 if ($this->object->isInstantFeedbackAnswerFixationEnabled() && $this->object->isForceInstantFeedbackEnabled()) {
2673 $text = $this->lng->txt('save_on_navigation_locked_confirmation');
2674 } else {
2675 $text = $this->lng->txt('save_on_navigation_confirmation');
2676 }
2677 if ($this->object->isForceInstantFeedbackEnabled()) {
2678 $text .= " " . $this->lng->txt('save_on_navigation_forced_feedback_hint');
2679 }
2680 $tpl->setVariable('CONFIRMATION_TEXT', $text);
2681
2682
2683 $button = ilLinkButton::getInstance();
2684 $button->setId('tst_save_on_navigation_button');
2685 $button->setUrl('#');
2686 $button->setCaption('tst_save_and_proceed');
2687 $button->setPrimary(true);
2688 $tpl->setCurrentBlock('buttons');
2689 $tpl->setVariable('BUTTON', $button->render());
2690 $tpl->parseCurrentBlock();
2691
2692 $button = ilLinkButton::getInstance();
2693 $button->setId('tst_cancel_on_navigation_button');
2694 $button->setUrl('#');
2695 $button->setCaption('cancel');
2696 $button->setPrimary(false);
2697 $tpl->setCurrentBlock('buttons');
2698 $tpl->setVariable('BUTTON', $button->render());
2699 $tpl->parseCurrentBlock();
2700
2701 $tpl->setCurrentBlock('checkbox');
2702 $tpl->setVariable('CONFIRMATION_CHECKBOX_NAME', 'save_on_navigation_prevent_confirmation');
2703 $tpl->setVariable('CONFIRMATION_CHECKBOX_LABEL', $this->lng->txt('tst_dont_show_msg_again_in_current_session'));
2704 $tpl->parseCurrentBlock();
2705
2706 $modal = ilModalGUI::getInstance();
2707 $modal->setId('tst_save_on_navigation_modal');
2708 $modal->setHeading($this->lng->txt('save_on_navigation'));
2709 $modal->setBody($tpl->get());
2710
2711 $this->tpl->setCurrentBlock('nav_while_edit_modal');
2712 $this->tpl->setVariable('NAV_WHILE_EDIT_MODAL', $modal->getHTML());
2713 $this->tpl->parseCurrentBlock();
2714 }
2715 // fau.
2716
2718 {
2719 $modal = new ilTestPlayerConfirmationModal();
2720 $modal->setModalId('tst_next_locks_unchanged_modal');
2721
2722 $modal->setHeaderText($this->lng->txt('tst_nav_next_locks_empty_answer_header'));
2723 $modal->setConfirmationText($this->lng->txt('tst_nav_next_locks_empty_answer_confirm'));
2724
2725 $button = $modal->buildModalButtonInstance('tst_nav_next_empty_answer_button');
2726 $button->setCaption('tst_proceed');
2727 $button->setPrimary(false);
2728 $modal->addButton($button);
2729
2730 $button = $modal->buildModalButtonInstance('tst_cancel_next_empty_answer_button');
2731 $button->setCaption('cancel');
2732 $button->setPrimary(true);
2733 $modal->addButton($button);
2734
2735 $this->tpl->setCurrentBlock('next_locks_unchanged_modal');
2736 $this->tpl->setVariable('NEXT_LOCKS_UNCHANGED_MODAL', $modal->getHTML());
2737 $this->tpl->parseCurrentBlock();
2738 }
2739
2741 {
2743 return;
2744 }
2745
2746 $modal = new ilTestPlayerConfirmationModal();
2747 $modal->setModalId('tst_next_locks_changed_modal');
2748
2749 $modal->setHeaderText($this->lng->txt('tst_nav_next_locks_current_answer_header'));
2750 $modal->setConfirmationText($this->lng->txt('tst_nav_next_locks_current_answer_confirm'));
2751
2752 $modal->setConfirmationCheckboxName(self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM);
2753 $modal->setConfirmationCheckboxLabel($this->lng->txt('tst_dont_show_msg_again_in_current_session'));
2754
2755 $button = $modal->buildModalButtonInstance('tst_nav_next_changed_answer_button');
2756 $button->setCaption('tst_save_and_proceed');
2757 $button->setPrimary(true);
2758 $modal->addButton($button);
2759
2760 $button = $modal->buildModalButtonInstance('tst_cancel_next_changed_answer_button');
2761 $button->setCaption('cancel');
2762 $button->setPrimary(false);
2763 $modal->addButton($button);
2764
2765 $this->tpl->setCurrentBlock('next_locks_changed_modal');
2766 $this->tpl->setVariable('NEXT_LOCKS_CHANGED_MODAL', $modal->getHTML());
2767 $this->tpl->parseCurrentBlock();
2768 }
2769
2770 public const FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM = 'followup_qst_locks_prevent_confirmation';
2771
2773 {
2774 ilSession::set(self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM, true);
2775 }
2776
2778 {
2779 if (ilSession::get(self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM) == null) {
2780 return false;
2781 }
2782
2783 return ilSession::get(self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM);
2784 }
2785
2786 // fau: testNav - new function populateQuestionEditControl
2792 protected function populateQuestionEditControl($questionGUI)
2793 {
2794 // configuration for ilTestPlayerQuestionEditControl.js
2795 $config = array();
2796
2797 // set the initial state of the question
2798 $state = $questionGUI->object->lookupForExistingSolutions($this->testSession->getActiveId(), $this->testSession->getPass());
2799 $config['isAnswered'] = $state['authorized'];
2800 $config['isAnswerChanged'] = $state['intermediate'] || $this->getAnswerChangedParameter();
2801
2802 // set url to which the for should be submitted when the working time is over
2803 // don't use asynch url because the form is submitted directly
2804 // but use simple '&' because url is copied by javascript into the form action
2805 $config['saveOnTimeReachedUrl'] = str_replace('&amp;', '&', $this->ctrl->getFormAction($this, ilTestPlayerCommands::AUTO_SAVE_ON_TIME_LIMIT));
2806
2807 // enable the auto saving function
2808 // the autosave url is asynch because it will be used by an ajax request
2809 if ($questionGUI->isAutosaveable() && $this->object->getAutosave()) {
2810 $config['autosaveUrl'] = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::AUTO_SAVE, '', true);
2811 $config['autosaveInterval'] = $this->object->getAutosaveIval();
2812 } else {
2813 $config['autosaveUrl'] = '';
2814 $config['autosaveInterval'] = 0;
2815 }
2816
2818 // hey: prevPassSolutions - refactored method identifiers
2819 $questionConfig = $questionGUI->object->getTestPresentationConfig();
2820 // hey.
2821
2822 // Normal questions: changes are done in form fields an can be detected there
2823 $config['withFormChangeDetection'] = $questionConfig->isFormChangeDetectionEnabled();
2824
2825 // Flash and Java questions: changes are directly sent to ilias and have to be polled from there
2826 $config['withBackgroundChangeDetection'] = $questionConfig->isBackgroundChangeDetectionEnabled();
2827 $config['backgroundDetectorUrl'] = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::DETECT_CHANGES, '', true);
2828
2829 // Forced feedback will change the navigation saving command
2830 $config['forcedInstantFeedback'] = $this->object->isForceInstantFeedbackEnabled();
2831 $config['nextQuestionLocks'] = $this->object->isFollowupQuestionAnswerFixationEnabled();
2832 $config['autosaveFailureMessage'] = $this->lng->txt('autosave_failed');
2833
2834 $this->tpl->addJavascript('./Modules/Test/js/ilTestPlayerQuestionEditControl.js');
2835 $this->tpl->addOnLoadCode('il.TestPlayerQuestionEditControl.init(' . json_encode($config) . ')');
2836 }
2837 // fau.
2838
2839 protected function getQuestionsDefaultPresentationMode($isQuestionWorkedThrough): string
2840 {
2841 // fau: testNav - always set default presentation mode to "edit"
2843 // fau.
2844 }
2845
2846 protected function registerForcedFeedbackNavUrl($forcedFeedbackNavUrl)
2847 {
2848 if (ilSession::get('forced_feedback_navigation_url') == null) {
2849 ilSession::set('forced_feedback_navigation_url', array());
2850 }
2851 $forced_feeback_navigation_url = ilSession::get('forced_feedback_navigation_url');
2852 $forced_feeback_navigation_url[$this->testSession->getActiveId()] = $forcedFeedbackNavUrl;
2853 ilSession::set('forced_feedback_navigation_url', $forced_feeback_navigation_url);
2854 }
2855
2857 {
2858 if (ilSession::get('forced_feedback_navigation_url') == null) {
2859 return null;
2860 }
2861 $forced_feedback_navigation_url = ilSession::get('forced_feedback_navigation_url');
2862 if (!isset($forced_feedback_navigation_url[$this->testSession->getActiveId()])) {
2863 return null;
2864 }
2865
2866 return $forced_feedback_navigation_url[$this->testSession->getActiveId()];
2867 }
2868
2869 protected function isForcedFeedbackNavUrlRegistered(): bool
2870 {
2871 return !empty($this->getRegisteredForcedFeedbackNavUrl());
2872 }
2873
2875 {
2876 $forced_feedback_navigation_url = ilSession::get('forced_feedback_navigation_url');
2877 if (isset($forced_feedback_navigation_url[$this->testSession->getActiveId()])) {
2878 unset($forced_feedback_navigation_url[$this->testSession->getActiveId()]);
2879 ilSession::set('forced_feedback_navigation_url', $forced_feedback_navigation_url);
2880 }
2881 }
2882
2883 protected function handleFileUploadCmd()
2884 {
2885 $this->updateWorkingTime();
2886 $this->saveQuestionSolution(false);
2887 $this->ctrl->redirect($this, ilTestPlayerCommands::SUBMIT_SOLUTION);
2888 }
2889}
$filename
Definition: buildRTE.php:78
const IL_CAL_FKT_GETDATE
const IL_CAL_TIMESTAMP
const IL_CAL_UNIX
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
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)
outQuestionPage($a_temp_var, $a_postponed=false, $active_id="", $html="", $inlineFeedbackEnabled=false)
getGenericFeedbackOutput(int $active_id, ?int $pass)
getSpecificFeedbackOutput(array $userSolution)
Returns the answer specific feedback for the question.
outQuestionForTest(string $formaction, int $active_id, ?int $pass, bool $is_question_postponed=false, $user_post_solutions=false, bool $show_specific_inline_feedback=false)
setNavigationGUI(?ilTestQuestionNavigationGUI $navigationGUI)
Abstract basic class which is to be extended by the concrete assessment question type classes.
static _instantiateQuestion(int $question_id)
static _getCurrentGUI(ilObjTest $a_test_obj=null)
Get the GUI that is used for the currently available selector (other GUI instances may exist for conf...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false)
@classDescription Date and time handling
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
static getWebspaceDir(string $mode="filesystem")
get webspace directory
static getInstance(ilTestSession $a_test_session)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static getInstance()
static getContentStylePath(int $a_style_id, bool $add_random=true, bool $add_token=true)
get content style path static (to avoid full reading)
static lookupExamId($active_id, $pass)
static allObligationsAnswered($test_id, $active_id, $pass)
checks wether all questions marked as obligatory were answered within the test pass with given testId...
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
static buildExamId($active_id, $pass, $test_obj_id=null)
static _getSolvedQuestions($active_id, $question_fi=null)
get solved questions
static get(string $a_var)
static clear(string $a_var)
static set(string $a_var, $a_val)
Set a value.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Class ilTaggingGUI.
special template class to simplify handling of ITX/PEAR
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static generatePDF($pdf_output, $output_mode, $filename=null, $purpose=null)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
handleSkillTriggering(ilTestSession $testSession)
checkTestSessionUser(ilTestSession $testSession)
populateNextButtons($disabled, $primaryNext)
redirectAfterAutosaveCmd()
Redirect the user after an automatic save when the time limit is reached.
isShowingPostponeStatusReguired($questionId)
saveTagsCmd()
Save tags for tagging gui.
initTestQuestionConfig(assQuestion $questionOBJ)
checkWorkingTimeCmd()
This is asynchronously called by tpl.workingtime.js to check for changes in the user's processing tim...
confirmHintRequestCmd()
Go to hint request confirmation.
__construct($a_object)
ilTestOutputGUI constructor
removeIntermediateSolution()
remove an auto-saved solution of the current question
autosaveOnTimeLimitCmd()
Automatically save a user answer when the limited duration of a test run is reached (called by synchr...
setAnswerChangedParameter($changed=true)
Set the 'answer changed' url parameter for generated links.
confirmFinishCmd()
The final submission of a test was confirmed.
buildEditableStateQuestionNavigationGUI($questionId, $charSelectorAvailable)
startPlayerCmd()
Start a test for the first time.
prepareTestPage($presentationMode, $sequenceElement, $questionId)
isOptionalQuestionAnsweringConfirmationRequired($sequenceElement)
handleTearsAndAngerQuestionIsNull($questionId, $sequenceElement)
getQuestionInstance($questionId, $fromCache=true)
saveQuestionSolution($authorized=true, $force=false)
saves the user input of a question
isFirstQuestionInSequence($sequenceElement)
populateGenericFeedbackBlock(assQuestionGUI $question_gui, $solutionCorrect)
getQuestionGuiInstance($question_id, $fromCache=true)
unmarkQuestionCmd()
Set a question unsolved.
showSideList($presentationMode, $currentSequenceElement)
populateUpperNextButtonBlock($disabled, $primaryNext)
initTestCmd()
Start a test for the first time after a redirect.
populateInstantResponseModal(assQuestionGUI $questionGui, $navUrl)
endingTimeReached()
handle endingTimeReached
outQuestionSummaryCmd($fullpage=true, $contextFinishTest=false, $obligationsInfo=false, $obligationsFilter=false)
Output of a summary of all test questions for test participants.
maxProcessingTimeReached()
Outputs a message when the maximum processing time is reached.
showQuestionEditable(assQuestionGUI $questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse)
resumePlayerCmd()
Resume a test at the last position.
handleIntermediateSubmit()
Check the 'answer changed' parameter when a question form is intermediately submitted.
confirmSubmitAnswers()
confirm submit results if confirm then results are submitted and the screen will be redirected to the...
autosaveCmd()
Automatically save a user answer while working on the test (called repeatedly by asynchronous posts i...
ensureExistingTestSession(ilTestSession $testSession)
populateScoreBlock($reachedPoints, $maxPoints)
updateWorkingTime()
updates working time and stores state saveresult to see if question has to be stored or not
handleUserSettings()
Handles some form parameters on starting and resuming a test.
populateInstantResponseBlocks(assQuestionGUI $questionGui, $authorizedSolution)
getQuestionsDefaultPresentationMode($isQuestionWorkedThrough)
outCorrectSolution()
Creates an output of the solution of an answer compared to the correct solution.
detectChangesCmd()
Detect changes sent in the background to the server This is called by ajax from ilTestPlayerQuestionE...
populateLowerNextButtonBlock($disabled, $primaryNext)
finishTestCmd($requires_confirmation=true)
markQuestionCmd()
Set a question solved.
populateTestNavigationToolbar(ilTestNavigationToolbarGUI $toolbarGUI)
getAnswerChangedParameter()
Get the 'answer changed' status from the current request It may be set by ilTestPlayerNavigationContr...
determineSolutionPassIndex(assQuestionGUI $questionGui)
populateSpecificFeedbackBlock(assQuestionGUI $question_gui)
showRequestedHintListCmd()
Go to requested hint list.
isMaxProcessingTimeReached()
Checks wheather the maximum processing time is reached or not.
setAnonymousIdCmd()
Sets a session variable with the test access code for an anonymous test user.
saveNavigationPreventConfirmation()
Save the save the switch to prevent the navigation confirmation.
registerForcedFeedbackNavUrl($forcedFeedbackNavUrl)
isLastQuestionInSequence($sequenceElement)
checkOnlineTestAccess()
check access restrictions like client ip, partipating user etc.
populateQuestionNavigation($sequenceElement, $disabled, $primaryNext)
buildNextButtonInstance($disabled, $primaryNext)
isTestAccessible()
test accessible returns true if the user can perform the test
showQuestionViewable(assQuestionGUI $questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse)
getContentBlockName()
Returns the name of the current content block (depends on the kiosk mode setting)
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...
Service GUI class for tests.
getCorrectSolutionOutput($question_id, $active_id, $pass, ilTestQuestionRelatedObjectivesList $objectivesList=null)
Returns an output of the solution to an answer compared to the correct solution.
getResultsSignature()
Returns HTML code for a signature field.
buildQuestionAnswerShuffler(int $question_id, int $active_id, int $pass_id)
buildQuestionRelatedObjectivesList(ilLOTestQuestionAdapter $objectivesAdapter, ilTestQuestionSequence $testSequence)
getAdditionalUsrDataHtmlAndPopulateWindowTitle($testSession, $active_id, $overwrite_anonymity=false)
Returns the user data for a test results output.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static getStyleSheetLocation(string $mode="output", string $a_css_name="", string $a_css_location="")
get full style sheet file name (path inclusive) of current user
static _getHttpPath()
static redirect(string $a_script)
static initjQuery(ilGlobalTemplateInterface $a_tpl=null)
inits and adds the jQuery JS-File to the global or a passed template
global $DIC
Definition: feed.php:28
$ilUser
Definition: imgupload.php:34
const OUTPUT_JAVASCRIPT
const REDIRECT_KIOSK
exit
Definition: login.php:28
$path
Definition: ltiservices.php:32
$res
Definition: ltiservices.php:69
if(!array_key_exists('PATH_INFO', $_SERVER)) $config
Definition: metadata.php:85
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
form( $class_path, string $cmd)
header include for all ilias files.
global $ilSetting
Definition: privfeed.php:17
$url
$_SERVER['HTTP_HOST']
Definition: raiseError.php:10
$results
$post_array
Definition: webdav.php:31