ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilTestOutputGUI.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
36 {
38  protected bool $save_result;
39 
43  public function executeCommand()
44  {
45  $this->tabs->clearTargets();
46 
47  $cmd = $this->ctrl->getCmd();
48  $next_class = $this->ctrl->getNextClass($this);
49 
50  if (($read_access = $this->checkReadAccess()) !== true) {
51  if ($cmd === 'autosave') {
52  echo $this->lng->txt('autosave_failed') . ': ' . $read_access;
53  exit;
54  }
55  $this->tpl->setOnScreenMessage('failure', $read_access, true);
56  $this->ctrl->redirectByClass([ilRepositoryGUI::class, ilObjTestGUI::class, TestScreenGUI::class]);
57  }
58 
59  $this->ctrl->saveParameter($this, "sequence");
60  $this->ctrl->saveParameter($this, "pmode");
61  $this->ctrl->saveParameter($this, "active_id");
62 
63  $this->initAssessmentSettings();
64 
65  $testSessionFactory = new ilTestSessionFactory($this->object, $this->db, $this->user);
66  $this->test_session = $testSessionFactory->getSession($this->testrequest->int('active_id'));
67 
68  $this->ensureExistingTestSession($this->test_session);
69  $this->checkTestSessionUser($this->test_session);
70 
71  $this->initProcessLocker($this->test_session->getActiveId());
72 
73  $test_sequence_factory = new ilTestSequenceFactory($this->object, $this->db, $this->questioninfo);
74  $this->testSequence = $test_sequence_factory->getSequenceByTestSession($this->test_session);
75  $this->testSequence->loadFromDb();
76  $this->testSequence->loadQuestions();
77 
78  $this->question_related_objectives_list = new ilTestQuestionRelatedObjectivesList();
79 
82 
84 
85  $instance_name = $this->settings->get('short_inst_name') ?? '';
86  if (trim($instance_name) === '') {
87  $instance_name = 'ILIAS';
88  }
89  $this->global_screen->tool()->context()->current()->addAdditionalData(
91  $instance_name
92  );
93  $this->global_screen->tool()->context()->current()->addAdditionalData(
95  $this->object->getKioskMode()
96  );
97 
98  $title = $this->object->getTitle();
99  if (($sequence_index = $this->getSequenceElementParameter()) !== null) {
100  $title .= ' - ' . $this->lng->txt('question') . $sequence_index;
101  }
102  $this->global_screen->tool()->context()->current()->addAdditionalData(
104  $title
105  );
106  $this->global_screen->tool()->context()->current()->addAdditionalData(
108  $this->getTestPlayerTitle()
109  );
110 
111 
112  $cmd = $this->getCommand($cmd);
113 
114  switch ($next_class) {
115  case 'ilassquestionpagegui':
116  $this->checkTestExecutable();
117 
118  $questionId = $this->testSequence->getQuestionForSequence($this->getCurrentSequenceElement());
119 
120  $page_gui = new ilAssQuestionPageGUI($questionId);
121  $ret = $this->ctrl->forwardCommand($page_gui);
122  break;
123 
124  case 'iltestsubmissionreviewgui':
125  $this->checkTestExecutable();
126  $this->handleCheckTestPassValid();
127 
128  $gui = new ilTestSubmissionReviewGUI($this, $this->object, $this->test_session);
129  $gui->setObjectiveOrientedContainer($this->getObjectiveOrientedContainer());
130  $ret = $this->ctrl->forwardCommand($gui);
131  break;
132 
133  case 'ilassquestionhintrequestgui':
134  $this->checkTestExecutable();
135 
136  $questionGUI = $this->object->createQuestionGUI(
137  "",
138  $this->testSequence->getQuestionForSequence($this->getCurrentSequenceElement())
139  );
140 
141  $questionHintTracking = new ilAssQuestionHintTracking(
142  $questionGUI->object->getId(),
143  $this->test_session->getActiveId(),
144  $this->test_session->getPass()
145  );
146 
147  $gui = new ilAssQuestionHintRequestGUI(
148  $this,
150  $questionGUI,
151  $questionHintTracking,
152  $this->ctrl,
153  $this->lng,
154  $this->tpl,
155  $this->tabs,
156  $this->global_screen
157  );
158 
159  // fau: testNav - save the 'answer changed' status for viewing hint requests
161  // fau.
162  $ret = $this->ctrl->forwardCommand($gui);
163 
164  break;
165  case 'ilassspecfeedbackpagegui':
166  case 'ilassgenfeedbackpagegui':
167  $id = $this->testrequest->int('pg_id');
168  if ($this->ctrl->getCmd() !== 'displayMediaFullscreen'
169  || $id === 0) {
170  break;
171  }
172 
173  (new ilPageObjectGUI(
174  $next_class === 'ilassgenfeedbackpagegui' ? 'qfbg' : 'qfbs',
175  $id
176  ))->displayMediaFullscreen();
177  break;
178 
179  case 'iltestpasswordprotectiongui':
180  $this->checkTestExecutable();
181 
182  $gui = new ilTestPasswordProtectionGUI(
183  $this->ctrl,
184  $this->tpl,
185  $this->lng,
186  $this,
187  $this->passwordChecker,
188  $this->testrequest,
189  $this->global_screen
190  );
191  $ret = $this->ctrl->forwardCommand($gui);
192  break;
193 
194  default:
195  if ($cmd !== 'autosave' && ilTestPlayerCommands::isTestExecutionCommand($cmd)) {
196  $this->checkTestExecutable();
197  }
198 
199  if (strtolower($cmd) === 'showquestion') {
200  $testPassesSelector = new ilTestPassesSelector($this->db, $this->object);
201  $testPassesSelector->setActiveId($this->test_session->getActiveId());
202  $testPassesSelector->setLastFinishedPass($this->test_session->getLastFinishedPass());
203 
204  if (!$testPassesSelector->openPassExists()) {
205  $this->tpl->setOnScreenMessage('info', $this->lng->txt('tst_pass_finished'), true);
206  $this->ctrl->redirectByClass([ilRepositoryGUI::class, ilObjTestGUI::class, ilTestScreenGUI::class]);
207  }
208  }
209 
210  if ($cmd === 'outQuestionSummary'
211  || $cmd === 'submitSolution') {
212  $this->handleCheckTestPassValid();
213  }
214 
215  $cmd .= 'Cmd';
216  $ret = $this->$cmd();
217  break;
218  }
219  return $ret;
220  }
221 
222  protected function startTestCmd()
223  {
224  ilSession::set('tst_pass_finish', 0);
225 
226  // ensure existing test session
227  $this->test_session->setUserId($this->user->getId());
228  $access_code = ilSession::get('tst_access_code');
229  if ($access_code != null && isset($access_code[$this->object->getTestId()])) {
230  $this->test_session->setAnonymousId($access_code[$this->object->getTestId()]);
231  }
232  if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) {
233  $this->test_session->setObjectiveOrientedContainerId($this->getObjectiveOrientedContainer()->getObjId());
234  }
235  $this->test_session->saveToDb();
236 
237  $active_id = $this->test_session->getActiveId();
238  $this->ctrl->setParameter($this, "active_id", $active_id);
239 
240  $shuffle = $this->object->getShuffleQuestions();
241  if ($this->object->isRandomTest()) {
243 
244  $this->object->loadQuestions();
245  $shuffle = false; // shuffle is already done during the creation of the random questions
246  }
247 
248  $this->object->updateTestPassResults(
249  $active_id,
250  $this->test_session->getPass(),
251  $this->object->areObligationsEnabled(),
252  null,
253  $this->object->getId()
254  );
255 
256  // ensure existing test sequence
257  if (!$this->testSequence->hasSequence()) {
258  $this->testSequence->createNewSequence($this->object->getQuestionCount(), $shuffle);
259  $this->testSequence->saveToDb();
260  }
261 
262  $this->testSequence->loadFromDb();
263  $this->testSequence->loadQuestions();
264 
265  if ($this->test_session->isObjectiveOriented()) {
266  $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($this->test_session);
267 
268  $objectivesAdapter->notifyTestStart($this->test_session, $this->object->getId());
269  $objectivesAdapter->prepareTestPass($this->test_session, $this->testSequence);
270 
271  $objectivesAdapter->buildQuestionRelatedObjectiveList(
272  $this->testSequence,
273  $this->question_related_objectives_list
274  );
275 
276  if ($this->testSequence->hasOptionalQuestions()) {
278 
279  $this->testSequence->reorderOptionalQuestionsToSequenceEnd();
280  $this->testSequence->saveToDb();
281  }
282  }
283 
284  $active_time_id = $this->object->startWorkingTime(
285  $this->test_session->getActiveId(),
286  $this->test_session->getPass()
287  );
288  ilSession::set("active_time_id", $active_time_id);
289 
291 
292  $sequence_element = $this->testSequence->getFirstSequence();
293 
294  $this->ctrl->setParameter($this, 'sequence', $sequence_element);
295  $this->ctrl->setParameter($this, 'pmode', '');
296 
297  if ($this->object->getListOfQuestionsStart()) {
298  $this->ctrl->redirect($this, ilTestPlayerCommands::QUESTION_SUMMARY);
299  }
300 
301  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
302  }
303 
305  {
306  ilLPStatusWrapper::_updateStatus($this->object->getId(), $this->user->getId());
307  }
308 
309  private function isValidSequenceElement($sequence_element): bool
310  {
311  if ($sequence_element === false) {
312  return false;
313  }
314 
315  if ($sequence_element < 1) {
316  return false;
317  }
318 
319  if (!$this->testSequence->getPositionOfSequence($sequence_element)) {
320  return false;
321  }
322 
323  return true;
324  }
325 
326  protected function showQuestionCmd(): void
327  {
328  ilSession::set('tst_pass_finish', 0);
329 
331  "active_time_id",
332  $this->object->startWorkingTime(
333  $this->test_session->getActiveId(),
334  $this->test_session->getPass()
335  )
336  );
337 
338  $this->help->setScreenIdComponent("tst");
339  $this->help->setScreenId("assessment");
340  $this->help->setSubScreenId("question");
341 
342  $sequence_element = $this->getCurrentSequenceElement();
343 
344  if (!$this->isValidSequenceElement($sequence_element)) {
345  $sequence_element = $this->testSequence->getFirstSequence();
346  }
347 
348  $this->test_session->setLastSequence($sequence_element ?? 0);
349  $this->test_session->saveToDb();
350 
351  $questionId = $this->testSequence->getQuestionForSequence($sequence_element ?? 0);
352 
353  if (!(int) $questionId && $this->test_session->isObjectiveOriented()) {
355  }
356 
357  if ($questionId !== null && !$this->testSequence->isQuestionPresented($questionId)) {
358  $this->testSequence->setQuestionPresented($questionId);
359  $this->testSequence->saveToDb();
360  }
361 
362  $isQuestionWorkedThrough = $this->questioninfo->lookupResultRecordExist(
363  $this->test_session->getActiveId(),
364  $questionId,
365  $this->test_session->getPass()
366  );
367 
368  // fau: testNav - always use edit mode, except for fixed answer
369  $instant_response = false;
370  if ($this->isParticipantsAnswerFixed($questionId)) {
372  $s = $this->object->getMainSettings()->getQuestionBehaviourSettings();
373  if ($s->getInstantFeedbackGenericEnabled()
374  || $s->getInstantFeedbackPointsEnabled()
375  || $s->getInstantFeedbackSolutionEnabled()
376  || $s->getInstantFeedbackSpecificEnabled()) {
377  $instant_response = true;
378  }
379  } else {
381  // #37025 don't show instant response if a request for it should fix the answer and answer is not yet fixed
382  if (!$this->object->isInstantFeedbackAnswerFixationEnabled()) {
383  $instant_response = $this->getInstantResponseParameter();
384  }
385  }
386  // fau.
387 
388  $questionGui = $this->getQuestionGuiInstance($questionId);
389 
390  if (!($questionGui instanceof assQuestionGUI)) {
391  $this->handleTearsAndAngerQuestionIsNull($questionId, $sequence_element);
392  }
393 
394  $questionGui->setSequenceNumber($this->testSequence->getPositionOfSequence($sequence_element));
395  $questionGui->setQuestionCount($this->testSequence->getUserQuestionCount());
396 
397  $headerBlockBuilder = new ilTestQuestionHeaderBlockBuilder($this->lng);
398  $headerBlockBuilder->setHeaderMode($this->object->getTitleOutput());
399  $headerBlockBuilder->setQuestionTitle($questionGui->object->getTitleForHTMLOutput());
400  $headerBlockBuilder->setQuestionPoints($questionGui->object->getPoints());
401  $headerBlockBuilder->setQuestionPosition($this->testSequence->getPositionOfSequence($sequence_element));
402  $headerBlockBuilder->setQuestionCount($this->testSequence->getUserQuestionCount());
403  $headerBlockBuilder->setQuestionPostponed($this->testSequence->isPostponedQuestion($questionId));
404  $headerBlockBuilder->setQuestionObligatory(
405  $this->object->areObligationsEnabled() && ilObjTest::isQuestionObligatory($questionGui->object->getId())
406  );
407  if ($this->test_session->isObjectiveOriented()) {
408  $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($this->test_session);
409  $objectivesAdapter->buildQuestionRelatedObjectiveList($this->testSequence, $this->question_related_objectives_list);
410  $this->question_related_objectives_list->loadObjectivesTitles();
411 
412  $objectivesString = $this->question_related_objectives_list->getQuestionRelatedObjectiveTitles($questionId);
413  $headerBlockBuilder->setQuestionRelatedObjectives($objectivesString);
414  }
415  $questionGui->setQuestionHeaderBlockBuilder($headerBlockBuilder);
416 
417  $this->prepareTestPage($sequence_element, $questionId);
418 
419  $navigationToolbarGUI = $this->getTestNavigationToolbarGUI();
420  $navigationToolbarGUI->setFinishTestButtonEnabled(true);
421 
422  $isNextPrimary = $this->handlePrimaryButton($navigationToolbarGUI, $questionId);
423 
424  $this->ctrl->setParameter($this, 'sequence', $sequence_element);
425  $this->ctrl->setParameter($this, 'pmode', $presentationMode);
426  $formAction = $this->ctrl->getFormAction($this, ilTestPlayerCommands::SUBMIT_INTERMEDIATE_SOLUTION);
427 
428  switch ($presentationMode) {
430 
431  // fau: testNav - enable navigation toolbar in edit mode
432  $navigationToolbarGUI->setDisabledStateEnabled(false);
433  // fau.
434  $this->showQuestionEditable($questionGui, $formAction, $isQuestionWorkedThrough, $instant_response);
435 
436  break;
437 
439 
440  if ($this->testSequence->isQuestionOptional($questionGui->object->getId())) {
442  }
443 
444  $this->showQuestionViewable($questionGui, $formAction, $isQuestionWorkedThrough, $instant_response);
445 
446  break;
447 
448  default:
449  throw new ilTestException('no presentation mode given');
450  }
451 
452  $navigationToolbarGUI->build();
453  $this->populateTestNavigationToolbar($navigationToolbarGUI);
454 
455  // fau: testNav - enable the question navigation in edit mode
456  $this->populateQuestionNavigation($sequence_element, $isNextPrimary);
457  // fau.
458 
459  if ($instant_response) {
460  // fau: testNav - always use authorized solution for instant feedback
462  $questionGui,
463  true
464  );
465  // fau.
466  }
467 
468  // fau: testNav - add feedback modal
469  if ($this->isForcedFeedbackNavUrlRegistered()) {
472  }
473  // fau.
474  }
475 
476  protected function editSolutionCmd()
477  {
478  $this->ctrl->setParameter($this, 'pmode', ilTestPlayerAbstractGUI::PRESENTATION_MODE_EDIT);
479  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
480  }
481 
482  protected function submitSolutionAndNextCmd()
483  {
484  if ($this->object->isForceInstantFeedbackEnabled()) {
485  $this->submitSolutionCmd();
486  return;
487  }
488 
489  if ($this->saveQuestionSolution(true, false)) {
490  $questionId = $this->testSequence->getQuestionForSequence(
492  );
493 
495 
496  $nextSequenceElement = $this->testSequence->getNextSequence($this->getCurrentSequenceElement());
497 
498  if (!$this->isValidSequenceElement($nextSequenceElement)) {
499  $nextSequenceElement = $this->testSequence->getFirstSequence();
500  }
501 
502  $this->test_session->setLastSequence($nextSequenceElement ?? 0);
503  $this->test_session->saveToDb();
504 
505  $this->ctrl->setParameter($this, 'sequence', $nextSequenceElement);
506  $this->ctrl->setParameter($this, 'pmode', '');
507  }
508 
509  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
510  }
511 
512  protected function submitSolutionCmd()
513  {
514  if ($this->saveQuestionSolution(true, false)) {
515  $questionId = $this->testSequence->getQuestionForSequence(
517  );
518 
520 
521  if ($this->object->isForceInstantFeedbackEnabled()) {
522  $this->ctrl->setParameter($this, 'instresp', 1);
523 
524  $this->testSequence->setQuestionChecked($questionId);
525  $this->testSequence->saveToDb();
526  }
527 
528  if ($this->getNextCommandParameter()) {
529  if ($this->getNextSequenceParameter()) {
530  $this->ctrl->setParameter($this, 'sequence', $this->getNextSequenceParameter());
531  $this->ctrl->setParameter($this, 'pmode', '');
532  }
533 
534  $this->ctrl->redirect($this, $this->getNextCommandParameter());
535  }
536 
537  $this->ctrl->setParameter($this, 'pmode', ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW);
538  } else {
539  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
540  }
541 
542  // fau: testNav - remember to prevent the navigation confirmation
544  // fau.
545 
546  // fau: testNav - handle navigation after saving
547  if ($this->getNavigationUrlParameter()) {
549  } else {
550  $this->ctrl->saveParameter($this, 'sequence');
551  }
552  // fau.
553  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
554  }
555 
556  protected function discardSolutionCmd()
557  {
558  $currentSequenceElement = $this->getCurrentSequenceElement();
559 
560  $currentQuestionOBJ = $this->getQuestionInstance(
561  $this->testSequence->getQuestionForSequence($currentSequenceElement)
562  );
563 
564  $currentQuestionOBJ->setTestId($this->getObject()->getId());
565 
566  $currentQuestionOBJ->resetUsersAnswer(
567  $this->test_session->getActiveId(),
568  $this->test_session->getPass()
569  );
570 
571  $this->ctrl->saveParameter($this, 'sequence');
572 
573  $this->ctrl->setParameter($this, 'pmode', ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW);
574 
575  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
576  }
577 
578  protected function skipQuestionCmd()
579  {
580  $curSequenceElement = $this->getCurrentSequenceElement();
581  $nextSequenceElement = $this->testSequence->getNextSequence($curSequenceElement);
582 
583  if (!$this->isValidSequenceElement($nextSequenceElement)) {
584  $nextSequenceElement = $this->testSequence->getFirstSequence();
585  }
586 
587  if ($this->object->isPostponingEnabled()) {
588  $this->testSequence->postponeSequence($curSequenceElement);
589  $this->testSequence->saveToDb();
590  }
591 
592  $this->ctrl->setParameter($this, 'sequence', $nextSequenceElement);
593  $this->ctrl->setParameter($this, 'pmode', '');
594 
595  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
596  }
597 
598  protected function handleQuestionPostponing($sequence_element)
599  {
600  $questionId = $this->testSequence->getQuestionForSequence($sequence_element);
601 
602  $isQuestionWorkedThrough = $this->questioninfo->lookupResultRecordExist(
603  $this->test_session->getActiveId(),
604  $questionId,
605  $this->test_session->getPass()
606  );
607 
608  if (!$isQuestionWorkedThrough) {
609  $this->testSequence->postponeQuestion($questionId);
610  $this->testSequence->saveToDb();
611  }
612  }
613 
614  protected function handleCheckTestPassValid(): void
615  {
616  $testObj = new ilObjTest($this->ref_id, true);
617 
618  $participants = $testObj->getActiveParticipantList();
619  $participant = $participants->getParticipantByActiveId($this->testrequest->getActiveId());
620  if (!$participant || !$participant->hasUnfinishedPasses()) {
621  $this->tpl->setOnScreenMessage('failure', $this->lng->txt("tst_current_run_no_longer_valid"), true);
622  $this->ctrl->redirectByClass([ilObjTestGUI::class, ilTestScreenGUI::class], ilTestScreenGUI::DEFAULT_CMD);
623  }
624  }
625 
626  protected function nextQuestionCmd()
627  {
628  $this->handleCheckTestPassValid();
629  $lastSequenceElement = $this->getCurrentSequenceElement();
630  $nextSequenceElement = $this->testSequence->getNextSequence($lastSequenceElement);
631 
632  if ($this->object->isPostponingEnabled()) {
633  $this->handleQuestionPostponing($lastSequenceElement);
634  }
635 
636  if (!$this->isValidSequenceElement($nextSequenceElement)) {
637  $nextSequenceElement = $this->testSequence->getFirstSequence();
638  }
639 
640  $this->ctrl->setParameter($this, 'sequence', $nextSequenceElement);
641  $this->ctrl->setParameter($this, 'pmode', '');
642 
643  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
644  }
645 
646  protected function previousQuestionCmd()
647  {
648  $this->handleCheckTestPassValid();
649 
650  $sequence_element = $this->testSequence->getPreviousSequence(
652  );
653 
654  if (!$this->isValidSequenceElement($sequence_element)) {
655  $sequence_element = $this->testSequence->getLastSequence();
656  }
657 
658  $this->ctrl->setParameter($this, 'sequence', $sequence_element);
659  $this->ctrl->setParameter($this, 'pmode', '');
660 
661  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
662  }
663 
664  protected function isFirstQuestionInSequence($sequence_element): bool
665  {
666  return $sequence_element == $this->testSequence->getFirstSequence();
667  }
668 
669  protected function isLastQuestionInSequence($sequence_element): bool
670  {
671  return $sequence_element == $this->testSequence->getLastSequence();
672  }
673 
679  protected function canSaveResult(): bool
680  {
681  return !$this->object->endingTimeReached() && !$this->isMaxProcessingTimeReached() && !$this->isNrOfTriesReached();
682  }
683 
687  protected function getCurrentQuestionId(): int
688  {
689  return $this->testSequence->getQuestionForSequence($this->testrequest->int('sequence'));
690  }
691 
695  public function saveQuestionSolution($authorized = true, $force = false): bool
696  {
697  $this->updateWorkingTime();
698  $this->save_result = false;
699  if (!$force) {
700  $formtimestamp = $_POST["formtimestamp"] ?? '';
701  if (strlen($formtimestamp) == 0) {
702  $formtimestamp = $this->testrequest->raw("formtimestamp");
703  }
704  if (ilSession::get('formtimestamp') == null || $formtimestamp != ilSession::get("formtimestamp")) {
705  ilSession::set("formtimestamp", $formtimestamp);
706  } else {
707  return false;
708  }
709  }
710 
711  /*
712  #21097 - exceed maximum passes
713  this is a battle of conditions; e.g. ilTestPlayerAbstractGUI::autosaveOnTimeLimitCmd forces saving of results.
714  However, if an admin has finished the pass in the meantime, a new pass should not be created.
715  */
716  if ($force && $this->isNrOfTriesReached()) {
717  $force = false;
718  }
719 
720  // save question solution
721  if ($this->canSaveResult() || $force) {
722  // but only if the ending time is not reached
723  $q_id = $this->testSequence->getQuestionForSequence($this->testrequest->int('sequence'));
724 
725  if ($this->isParticipantsAnswerFixed($q_id)) {
726  // should only be reached by firebugging the disabled form in ui
727  throw new ilTestException('not allowed request');
728  }
729 
730  if (is_numeric($q_id) && (int) $q_id) {
731  $questionOBJ = $this->getQuestionInstance($q_id);
732 
733  $active_id = (int) $this->test_session->getActiveId();
734  $pass = ilObjTest::_getPass($active_id);
735  $this->save_result = $questionOBJ->persistWorkingState(
736  $active_id,
737  $pass,
738  $this->object->areObligationsEnabled(),
739  $authorized
740  );
741 
742  if ($authorized && $this->test_session->isObjectiveOriented()) {
743  $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($this->test_session);
744  $objectivesAdapter->updateQuestionResult($this->test_session, $questionOBJ);
745  }
746 
747  if ($authorized && $this->object->isSkillServiceToBeConsidered()) {
748  $this->handleSkillTriggering($this->test_session);
749  }
750  }
751  }
752 
753  if (!$this->save_result || ($questionOBJ instanceof ilAssQuestionPartiallySaveable && !$questionOBJ->validateSolutionSubmit())) {
754  $this->ctrl->setParameter($this, "save_error", "1");
755  ilSession::set("previouspost", $_POST);
756  }
757 
758  return $this->save_result;
759  }
760 
761  protected function showInstantResponseCmd()
762  {
763  $questionId = $this->testSequence->getQuestionForSequence(
765  );
766 
767  if (!$this->isParticipantsAnswerFixed($questionId)) {
768  if ($this->saveQuestionSolution(true)) {
770  $this->setAnswerChangedParameter(false);
771  } else {
772  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
773  }
774  $this->testSequence->setQuestionChecked($questionId);
775  $this->testSequence->saveToDb();
776  } elseif ($this->object->isForceInstantFeedbackEnabled()) {
777  $this->testSequence->setQuestionChecked($questionId);
778  $this->testSequence->saveToDb();
779  }
780 
781  $this->ctrl->setParameter($this, 'instresp', 1);
782 
783  // fau: testNav - handle navigation after feedback
784  if ($this->getNavigationUrlParameter()) {
787  }
788  // fau.
789  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
790  }
791 
792  protected function handleQuestionActionCmd()
793  {
794  $questionId = $this->testSequence->getQuestionForSequence(
796  );
797 
798  if (!$this->isParticipantsAnswerFixed($questionId)) {
799  $this->updateWorkingTime();
800  $this->saveQuestionSolution(false);
801  // fau: testNav - add changed status of the question
802  $this->setAnswerChangedParameter(true);
803  // fau.
804  }
805 
806  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
807  }
808 
810  {
811  if ($this->test_session->getActiveId() > 0) {
812  if ($this->testSequence->hasRandomQuestionsForPass($this->test_session->getActiveId(), $this->test_session->getPass()) > 0) {
813  $this->logging_services->root()->write(
814  __METHOD__ . ' Random Questions allready exists for user ' .
815  $this->user->getId() . ' in test ' . $this->object->getTestId()
816  );
817 
818  return true;
819  }
820  } else {
821  $this->logging_services->root()->write(__METHOD__ . ' ' . sprintf(
822  $this->lng->txt("error_random_question_generation"),
823  $this->user->getId(),
824  $this->object->getTestId()
825  ));
826 
827  return true;
828  };
829 
830  return false;
831  }
832 
834  {
835  $questionSetConfig = new ilTestRandomQuestionSetConfig(
836  $this->tree,
837  $this->db,
838  $this->lng,
839  $this->logging_services->root(),
841  $this->object,
843  );
844  $questionSetConfig->loadFromDb();
845 
846  $sourcePoolDefinitionFactory = new ilTestRandomQuestionSetSourcePoolDefinitionFactory($this->db, $this->object);
847 
848  $sourcePoolDefinitionList = new ilTestRandomQuestionSetSourcePoolDefinitionList($this->db, $this->object, $sourcePoolDefinitionFactory);
849  $sourcePoolDefinitionList->loadDefinitions();
850 
851  $this->processLocker->executeRandomPassBuildOperation(function () use ($questionSetConfig, $sourcePoolDefinitionList) {
853  $stagingPoolQuestionList = new ilTestRandomQuestionSetStagingPoolQuestionList($this->db, $this->component_repository);
854 
855  $questionSetBuilder = ilTestRandomQuestionSetBuilder::getInstance(
856  $this->db,
857  $this->lng,
858  $this->logging_services->root(),
859  $this->object,
860  $questionSetConfig,
861  $sourcePoolDefinitionList,
862  $stagingPoolQuestionList
863  );
864 
865  $questionSetBuilder->performBuild($this->test_session);
866  }
867  }, $sourcePoolDefinitionList->hasTaxonomyFilters());
868  }
869 
873  protected function resumePlayerCmd()
874  {
875  $this->handleUserSettings();
876 
877  $active_id = $this->test_session->getActiveId();
878  $this->ctrl->setParameter($this, "active_id", $active_id);
879 
880  $active_time_id = $this->object->startWorkingTime($active_id, $this->test_session->getPass());
881  ilSession::set("active_time_id", $active_time_id);
882  ilSession::set('tst_pass_finish', 0);
883 
884  if ($this->object->isRandomTest()) {
885  if (!$this->testSequence->hasRandomQuestionsForPass($active_id, $this->test_session->getPass())) {
886  // create a new set of random questions
888  }
889  }
890 
891  $shuffle = $this->object->getShuffleQuestions();
892  if ($this->object->isRandomTest()) {
893  $shuffle = false;
894  }
895 
896  $this->object->updateTestPassResults(
897  $active_id,
898  $this->test_session->getPass(),
899  $this->object->areObligationsEnabled(),
900  null,
901  $this->object->getId()
902  );
903 
904  // ensure existing test sequence
905  if (!$this->testSequence->hasSequence()) {
906  $this->testSequence->createNewSequence($this->object->getQuestionCount(), $shuffle);
907  $this->testSequence->saveToDb();
908  }
909 
910  if ($this->object->getListOfQuestionsStart()) {
911  $this->ctrl->redirect($this, ilTestPlayerCommands::QUESTION_SUMMARY);
912  }
913 
914  $this->ctrl->setParameter($this, 'sequence', $this->test_session->getLastSequence());
915  $this->ctrl->setParameter($this, 'pmode', '');
916  $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
917  }
918 
919  protected function isShowingPostponeStatusReguired($questionId): bool
920  {
921  return $this->testSequence->isPostponedQuestion($questionId);
922  }
923 
925  {
926  $assSettings = new ilSetting('assessment');
927 
928  $isAssessmentLogEnabled = ilObjAssessmentFolder::_enabledAssessmentLogging();
929 
930  $userSolutionAdopter = new ilAssQuestionUserSolutionAdopter($this->db, $assSettings, $isAssessmentLogEnabled);
931 
932  $userSolutionAdopter->setUserId($this->user->getId());
933  $userSolutionAdopter->setActiveId($this->test_session->getActiveId());
934  $userSolutionAdopter->setTargetPass($this->testSequence->getPass());
935  $userSolutionAdopter->setQuestionIds($this->testSequence->getOptionalQuestions());
936 
937  $userSolutionAdopter->perform();
938  }
939 
940  abstract protected function populateQuestionOptionalMessage();
941 
942  protected function isOptionalQuestionAnsweringConfirmationRequired($sequenceKey): bool
943  {
944  if ($this->testSequence->isAnsweringOptionalQuestionsConfirmed()) {
945  return false;
946  }
947 
948  $questionId = $this->testSequence->getQuestionForSequence($sequenceKey);
949 
950  if (!$this->testSequence->isQuestionOptional($questionId)) {
951  return false;
952  }
953 
954  return true;
955  }
956 
957  protected function isQuestionSummaryFinishTestButtonRequired(): bool
958  {
959  return true;
960  }
961 
963  {
964  $this->tpl->setOnScreenMessage('failure', sprintf($this->lng->txt('tst_objective_oriented_test_pass_without_questions'), $this->object->getTitle()), true);
965  $this->ctrl->redirectByClass(ilObjTestGUI::class);
966  }
967 
968  protected function handlePrimaryButton(ilTestNavigationToolbarGUI $navigationToolbarGUI, int $currentQuestionId): bool
969  {
970  $isNextPrimary = true;
971 
972  if ($this->object->isForceInstantFeedbackEnabled()) {
973  $isNextPrimary = false;
974  }
975 
976  $questionsMissingResult = $this->questioninfo->getQuestionsMissingResultRecord(
977  $this->test_session->getActiveId(),
978  $this->test_session->getPass(),
979  $this->testSequence->getOrderedSequenceQuestions()
980  );
981 
982  if ($questionsMissingResult === []) {
983  $navigationToolbarGUI->setFinishTestButtonPrimary(true);
984  return false;
985  }
986 
987  if (count($questionsMissingResult) === 1
988  && $currentQuestionId === current($questionsMissingResult)) {
989  $navigationToolbarGUI->setFinishTestButtonPrimary(true);
990  return false;
991  }
992 
993  return $isNextPrimary;
994  }
995 
996  protected function getTestPlayerTitle(): string
997  {
998  $titleContent = $this->ui_factory->listing()->property();
999 
1000  if ($this->object->getShowKioskModeParticipant()) {
1001  $testParticipantNameLabel = $this->lng->txt("conf_user_name");
1002  // this is a placeholder solution with inline html tags to differentiate the different elements
1003  // should be removed when a title component with grouping and visual weighting is available
1004  // see: https://github.com/ILIAS-eLearning/ILIAS/pull/7311
1005  $testParticipantNameValue = "<span class='il-test-kiosk-head__participant-name'>" . $this->user->getFullname() . "</span>";
1006  $titleContent = $titleContent->withProperty($testParticipantNameLabel, $testParticipantNameValue, false);
1007  }
1008 
1009  if ($this->object->isShowExamIdInTestPassEnabled()) {
1010  $testExamIdLabel = $this->lng->txt("exam_id_label");
1011  $testExamIdValue = ilObjTest::buildExamId(
1012  $this->test_session->getActiveId(),
1013  $this->test_session->getPass(),
1014  $this->object->getId()
1015  );
1016  $titleContent = $titleContent->withProperty($testExamIdLabel, $testExamIdValue);
1017  }
1018 
1019  if ($this->object->getShowKioskModeTitle()) {
1020  $testNameLabel = $this->lng->txt("test");
1021  $testNameValue = $this->object->getTitle();
1022  $titleContent = $titleContent->withProperty($testNameLabel, $testNameValue, false);
1023  }
1024 
1025  return $this->ui_renderer->render($titleContent);
1026  }
1027 }
isShowingPostponeStatusReguired($questionId)
getQuestionInstance(int $question_id, bool $from_cache=true)
static get(string $a_var)
ilComponentRepository $component_repository
getQuestionGuiInstance($question_id, $fromCache=true)
exit
Definition: login.php:29
populateQuestionOptionalMessage()
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
setAnswerChangedParameter($changed=true)
Set the &#39;answer changed&#39; url parameter for generated links.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
showQuestionEditable(assQuestionGUI $questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse)
populateTestNavigationToolbar(ilTestNavigationToolbarGUI $toolbar_gui)
Output class for assessment test execution.
updateWorkingTime()
updates working time and stores state saveresult to see if question has to be stored or not ...
Class ilPageObjectGUI.
static getInstance(ilDBInterface $db, ilLanguage $lng, ilLogger $log, ilObjTest $testOBJ, ilTestRandomQuestionSetConfig $questionSetConfig, ilTestRandomQuestionSetSourcePoolDefinitionList $sourcePoolDefinitionList, ilTestRandomQuestionSetStagingPoolQuestionList $stagingPoolQuestionList)
executeCommand()
Execute Command.
getSession(?int $active_id=null)
Creates and returns an instance of a test sequence that corresponds to the current test mode...
resumePlayerCmd()
Resume a test at the last position.
handleUserSettings()
Handles some form parameters on starting and resuming a test.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
ilTestQuestionRelatedObjectivesList $question_related_objectives_list
Base Exception for all Exceptions relating to Modules/Test.
isLastQuestionInSequence($sequence_element)
populateInstantResponseBlocks(assQuestionGUI $questionGui, $authorizedSolution)
static isQuestionObligatory($question_id)
checks wether the question with given id is marked as obligatory or not
isValidSequenceElement($sequence_element)
checkTestSessionUser(ilTestSession $test_session)
populateQuestionNavigation($sequence_element, $primary_next)
static getInstance(ilTestSession $a_test_session)
isOptionalQuestionAnsweringConfirmationRequired($sequenceKey)
Class ilTestSubmissionReviewGUI.
getCommand($cmd)
Retrieves the ilCtrl command.
handleSkillTriggering(ilTestSession $test_session)
saveNavigationPreventConfirmation()
Save the save the switch to prevent the navigation confirmation.
ensureExistingTestSession(ilTestSession $test_session)
ilTestSessionFactory $testSessionFactory
handlePrimaryButton(ilTestNavigationToolbarGUI $navigationToolbarGUI, int $currentQuestionId)
getAnswerChangedParameter()
Get the &#39;answer changed&#39; status from the current request It may be set by ilTestPlayerNavigationContr...
Basic GUI class for assessment questions.
ILIAS TestQuestionPool QuestionInfoService $questioninfo
setFinishTestButtonPrimary($finishTestButtonPrimary)
static redirect(string $a_script)
registerForcedFeedbackNavUrl($forcedFeedbackNavUrl)
saveQuestionSolution($authorized=true, $force=false)
saves the user input of a question
showQuestionViewable(assQuestionGUI $questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse)
static initjQuery(ilGlobalTemplateInterface $a_tpl=null)
inits and adds the jQuery JS-File to the global or a passed template
isFirstQuestionInSequence($sequence_element)
populateInstantResponseModal(assQuestionGUI $questionGui, $navUrl)
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
static buildExamId($active_id, $pass, $test_obj_id=null)
handleTearsAndAngerQuestionIsNull(int $question_id, $sequence_element)
static set(string $a_var, $a_val)
Set a value.
static initConnectionWithAnimation()
Init YUI connection and animation module used in Modules/Test (Jan 2022)
canSaveResult()
Returns TRUE if the answers of the current user could be saved.
static _updateStatus(int $a_obj_id, int $a_usr_id, ?object $a_obj=null, bool $a_percentage=false, bool $a_force_raise=false)
Output class for assessment test execution.
prepareTestPage($sequenceElement, $questionId)
handleQuestionPostponing($sequence_element)