1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4require_once 'Modules/Test/classes/class.ilTestPlayerAbstractGUI.php';
5
6/**
7 * Output class for assessment test execution
8 *
9 * The ilTestOutputGUI class creates the output for the ilObjTestGUI
10 * class when learners execute a test. This saves some heap space because
11 * the ilObjTestGUI class will be much smaller then
12 *
13 * @extends ilTestPlayerAbstractGUI
14 *
15 * @author		Björn Heyser <bheyser@databay.de>
16 * @version		$Id$
17 *
18 * @package		Modules/Test
19 *
20 * @ilCtrl_Calls ilTestPlayerDynamicQuestionSetGUI: ilAssGenFeedbackPageGUI
21 * @ilCtrl_Calls ilTestPlayerDynamicQuestionSetGUI: ilAssSpecFeedbackPageGUI
22 * @ilCtrl_Calls ilTestPlayerDynamicQuestionSetGUI: ilAssQuestionHintRequestGUI
23 * @ilCtrl_Calls ilTestPlayerDynamicQuestionSetGUI: ilAssQuestionPageGUI
24 * @ilCtrl_Calls ilTestPlayerDynamicQuestionSetGUI: ilTestDynamicQuestionSetStatisticTableGUI
25 * @ilCtrl_Calls ilTestPlayerDynamicQuestionSetGUI: ilToolbarGUI
26 * @ilCtrl_Calls ilTestPlayerDynamicQuestionSetGUI: ilTestSubmissionReviewGUI
27 * @ilCtrl_Calls ilTestPlayerDynamicQuestionSetGUI: ilTestPasswordProtectionGUI
28 */
29class ilTestPlayerDynamicQuestionSetGUI extends ilTestPlayerAbstractGUI
30{
31    /**
32     * @var ilObjTestDynamicQuestionSetConfig
33     */
34    private $dynamicQuestionSetConfig = null;
35
36    /**
37     * @var ilTestSequenceDynamicQuestionSet
38     */
39    protected $testSequence;
40
41    /**
42     * @var ilTestSessionDynamicQuestionSet
43     */
44    protected $testSession;
45
46    /**
47     * execute command
48     */
49    public function executeCommand()
50    {
51        global $DIC;
52        $ilDB = $DIC['ilDB'];
53        $lng = $DIC['lng'];
54        $ilPluginAdmin = $DIC['ilPluginAdmin'];
55        $ilTabs = $DIC['ilTabs'];
56        $tree = $DIC['tree'];
57
58        $ilTabs->clearTargets();
59
60        $this->ctrl->saveParameter($this, "sequence");
61        $this->ctrl->saveParameter($this, "active_id");
62
63        $this->initAssessmentSettings();
64
65        $DIC->globalScreen()->tool()->context()->current()->addAdditionalData(
66            ilTestPlayerLayoutProvider::TEST_PLAYER_KIOSK_MODE_ENABLED,
67            $this->object->getKioskMode()
68        );
69
70        require_once 'Modules/Test/classes/class.ilObjTestDynamicQuestionSetConfig.php';
71        $this->dynamicQuestionSetConfig = new ilObjTestDynamicQuestionSetConfig($tree, $ilDB, $ilPluginAdmin, $this->object);
72        $this->dynamicQuestionSetConfig->loadFromDb();
73
74        $testSessionFactory = new ilTestSessionFactory($this->object);
75        $this->testSession = $testSessionFactory->getSession($_GET['active_id']);
76
77        $this->ensureExistingTestSession($this->testSession);
78        $this->checkTestSessionUser($this->testSession);
79        $this->initProcessLocker($this->testSession->getActiveId());
80
81        $testSequenceFactory = new ilTestSequenceFactory($ilDB, $lng, $ilPluginAdmin, $this->object);
82        $this->testSequence = $testSequenceFactory->getSequenceByTestSession($this->testSession);
83        $this->testSequence->loadFromDb();
84
85        if ($this->object->isInstantFeedbackAnswerFixationEnabled()) {
86            $this->testSequence->setPreventCheckedQuestionsFromComingUpEnabled(true);
87        }
88
89        include_once 'Services/jQuery/classes/class.iljQueryUtil.php';
90        iljQueryUtil::initjQuery();
91        include_once "./Services/YUI/classes/class.ilYuiUtil.php";
92        ilYuiUtil::initConnectionWithAnimation();
93        if ($this->object->getKioskMode()) {
94            include_once 'Services/UIComponent/Overlay/classes/class.ilOverlayGUI.php';
95            ilOverlayGUI::initJavascript();
96        }
97
98        $this->handlePasswordProtectionRedirect();
99
100        $cmd = $this->ctrl->getCmd();
101        $nextClass = $this->ctrl->getNextClass($this);
102
103        switch ($nextClass) {
104            case 'ilassquestionpagegui':
105
106                $questionId = $this->testSession->getCurrentQuestionId();
107
108                require_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPageGUI.php";
109                $page_gui = new ilAssQuestionPageGUI($questionId);
110                $ret = $this->ctrl->forwardCommand($page_gui);
111                break;
112
113            case 'ilassquestionhintrequestgui':
114
115                $this->ctrl->saveParameter($this, 'pmode');
116
117                $questionGUI = $this->object->createQuestionGUI(
118                    "",
119                    $this->testSession->getCurrentQuestionId()
120                );
121
122                require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
123                $questionHintTracking = new ilAssQuestionHintTracking(
124                    $questionGUI->object->getId(),
125                    $this->testSession->getActiveId(),
126                    $this->testSession->getPass()
127                );
128
129                require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintRequestGUI.php';
130                $gui = new ilAssQuestionHintRequestGUI(
131                    $this,
132                    ilTestPlayerCommands::SHOW_QUESTION,
133                    $questionGUI,
134                    $questionHintTracking
135                );
136
137// fau: testNav - save the 'answer changed status' for viewing hint requests
138                $this->setAnswerChangedParameter($this->getAnswerChangedParameter());
139// fau.
140                $this->ctrl->forwardCommand($gui);
141
142                break;
143
144            case 'ildynamicquestionsetstatistictablegui':
145
146                $this->ctrl->forwardCommand($this->buildQuestionSetFilteredStatisticTableGUI());
147
148                break;
149
150            case 'iltestpasswordprotectiongui':
151                require_once 'Modules/Test/classes/class.ilTestPasswordProtectionGUI.php';
152                $gui = new ilTestPasswordProtectionGUI($this->ctrl, $this->tpl, $this->lng, $this, $this->passwordChecker);
153                $ret = $this->ctrl->forwardCommand($gui);
154                break;
155
156            default:
157
158                $cmd .= 'Cmd';
159                $ret = &$this->$cmd();
160                break;
161        }
162
163        return $ret;
164    }
165
166    /**
167     * @return integer
168     */
169    protected function getCurrentQuestionId()
170    {
171        return $this->testSession->getCurrentQuestionId();
172    }
173
174    /**
175     * Resume a test at the last position
176     */
177    protected function resumePlayerCmd()
178    {
179        if ($this->object->checkMaximumAllowedUsers() == false) {
180            return $this->showMaximumAllowedUsersReachedMessage();
181        }
182
183        $this->handleUserSettings();
184
185        if ($this->dynamicQuestionSetConfig->isAnyQuestionFilterEnabled()) {
186            $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION_SELECTION);
187        }
188
189        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
190    }
191
192    protected function startTestCmd()
193    {
194        $this->testSession->setCurrentQuestionId(null); // no question "came up" yet
195
196        $this->testSession->saveToDb();
197
198        $this->ctrl->setParameter($this, 'active_id', $this->testSession->getActiveId());
199
200        assQuestion::_updateTestPassResults($this->testSession->getActiveId(), $this->testSession->getPass(), false, null, $this->object->id);
201
202        $_SESSION['active_time_id'] = $this->object->startWorkingTime(
203            $this->testSession->getActiveId(),
204            $this->testSession->getPass()
205        );
206
207        $this->ctrl->saveParameter($this, 'tst_javascript');
208
209        if ($this->dynamicQuestionSetConfig->isAnyQuestionFilterEnabled()) {
210            $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION_SELECTION);
211        }
212
213        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
214    }
215
216    protected function showQuestionSelectionCmd()
217    {
218        $this->prepareSummaryPage();
219
220        $this->testSequence->loadQuestions(
221            $this->dynamicQuestionSetConfig,
222            $this->testSession->getQuestionSetFilterSelection()
223        );
224
225        $this->testSequence->cleanupQuestions($this->testSession);
226
227        $this->testSequence->saveToDb();
228
229        require_once 'Services/UIComponent/Toolbar/classes/class.ilToolbarGUI.php';
230        $toolbarGUI = new ilToolbarGUI();
231
232        require_once 'Services/UIComponent/Button/classes/class.ilLinkButton.php';
233        $button = ilLinkButton::getInstance();
234        $button->setUrl($this->getStartTestFromQuestionSelectionLink());
235        $button->setCaption($this->getEnterTestButtonLangVar());
236        $button->setPrimary(true);
237        $toolbarGUI->addButtonInstance($button);
238
239        if ($this->object->getShowCancel()) {
240            $button = ilLinkButton::getInstance();
241            $button->setUrl($this->ctrl->getLinkTarget(
242                $this,
243                ilTestPlayerCommands::SUSPEND_TEST
244            ));
245            $button->setCaption('cancel_test');
246            $toolbarGUI->addButtonInstance($button);
247        }
248
249        if ($this->object->isPassDeletionAllowed()) {
250            require_once 'Modules/Test/classes/confirmations/class.ilTestPassDeletionConfirmationGUI.php';
251
252            $toolbarGUI->addButton(
253                $this->lng->txt('tst_dyn_test_pass_deletion_button'),
254                $this->getPassDeletionTarget(ilTestPassDeletionConfirmationGUI::CONTEXT_DYN_TEST_PLAYER)
255            );
256        }
257
258        $filteredData = array($this->buildQuestionSetAnswerStatisticRowArray(
259            $this->testSequence->getFilteredQuestionsData(),
260            $this->testSequence->getTrackedQuestionList()
261        )); #vd($filteredData);
262        $filteredTableGUI = $this->buildQuestionSetFilteredStatisticTableGUI();
263        $filteredTableGUI->setData($filteredData);
264
265        $completeData = array($this->buildQuestionSetAnswerStatisticRowArray(
266            $this->testSequence->getCompleteQuestionsData(),
267            $this->testSequence->getTrackedQuestionList()
268        )); #vd($completeData);
269        $completeTableGUI = $this->buildQuestionSetCompleteStatisticTableGUI();
270        $completeTableGUI->setData($completeData);
271
272        $content = $this->ctrl->getHTML($toolbarGUI);
273        $content .= $this->ctrl->getHTML($filteredTableGUI);
274        $content .= $this->ctrl->getHTML($completeTableGUI);
275
276        $this->tpl->setVariable('TABLE_LIST_OF_QUESTIONS', $content);
277
278        if ($this->object->getEnableProcessingTime()) {
279            $this->outProcessingTime($this->testSession->getActiveId());
280        }
281    }
282
283    protected function filterQuestionSelectionCmd()
284    {
285        $tableGUI = $this->buildQuestionSetFilteredStatisticTableGUI();
286        $tableGUI->writeFilterToSession();
287
288        $taxFilterSelection = array();
289        $answerStatusFilterSelection = ilAssQuestionList::ANSWER_STATUS_FILTER_ALL_NON_CORRECT;
290
291        foreach ($tableGUI->getFilterItems() as $item) {
292            if (strpos($item->getPostVar(), 'tax_') !== false) {
293                $taxId = substr($item->getPostVar(), strlen('tax_'));
294                $taxFilterSelection[$taxId] = $item->getValue();
295            } elseif ($item->getPostVar() == 'question_answer_status') {
296                $answerStatusFilterSelection = $item->getValue();
297            }
298        }
299
300        $this->testSession->getQuestionSetFilterSelection()->setTaxonomySelection($taxFilterSelection);
301        $this->testSession->getQuestionSetFilterSelection()->setAnswerStatusSelection($answerStatusFilterSelection);
302        $this->testSession->saveToDb();
303
304        $this->testSequence->resetTrackedQuestionList();
305        $this->testSequence->saveToDb();
306
307
308        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION_SELECTION);
309    }
310
311    protected function resetQuestionSelectionCmd()
312    {
313        $tableGUI = $this->buildQuestionSetFilteredStatisticTableGUI();
314        $tableGUI->resetFilter();
315
316        $this->testSession->getQuestionSetFilterSelection()->setTaxonomySelection(array());
317        $this->testSession->getQuestionSetFilterSelection()->setAnswerStatusSelection(null);
318        $this->testSession->saveToDb();
319
320        $this->testSequence->resetTrackedQuestionList();
321        $this->testSequence->saveToDb();
322
323        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION_SELECTION);
324    }
325
326    protected function previousQuestionCmd()
327    {
328        // nothing to do, won't be called
329    }
330
331    protected function fromPassDeletionCmd()
332    {
333        $this->resetCurrentQuestion();
334        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
335    }
336
337    protected function nextQuestionCmd()
338    {
339        $isWorkedThrough = assQuestion::_isWorkedThrough(
340            $this->testSession->getActiveId(),
341            $this->testSession->getCurrentQuestionId(),
342            $this->testSession->getPass()
343        );
344
345        if (!$isWorkedThrough) {
346            $this->testSequence->setQuestionPostponed($this->testSession->getCurrentQuestionId());
347            $this->testSequence->saveToDb();
348        }
349
350        $this->resetCurrentQuestion();
351
352        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
353    }
354
355    protected function markQuestionCmd()
356    {
357        global $DIC;
358        $ilUser = $DIC['ilUser'];
359        $this->object->setQuestionSetSolved(1, $this->testSession->getCurrentQuestionId(), $ilUser->getId());
360
361        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
362    }
363
364    protected function unmarkQuestionCmd()
365    {
366        global $DIC;
367        $ilUser = $DIC['ilUser'];
368        $this->object->setQuestionSetSolved(0, $this->testSession->getCurrentQuestionId(), $ilUser->getId());
369
370        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
371    }
372
373    protected function editSolutionCmd()
374    {
375        $this->ctrl->setParameter($this, 'pmode', ilTestPlayerAbstractGUI::PRESENTATION_MODE_EDIT);
376        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
377    }
378
379    protected function submitSolutionAndNextCmd()
380    {
381        if ($this->object->isForceInstantFeedbackEnabled()) {
382            return $this->submitSolutionCmd();
383        }
384
385        if ($this->saveQuestionSolution(true, false)) {
386            $questionId = $this->testSession->getCurrentQuestionId();
387
388            $this->getQuestionInstance($questionId)->removeIntermediateSolution(
389                $this->testSession->getActiveId(),
390                $this->testSession->getPass()
391            );
392
393            $this->persistQuestionAnswerStatus();
394
395            $this->ctrl->setParameter($this, 'pmode', '');
396
397            $this->resetCurrentQuestion();
398        }
399
400        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
401    }
402
403    protected function submitSolutionCmd()
404    {
405        if ($this->saveQuestionSolution(true, false)) {
406            $questionId = $this->testSession->getCurrentQuestionId();
407
408            $this->getQuestionInstance($questionId)->removeIntermediateSolution(
409                $this->testSession->getActiveId(),
410                $this->testSession->getPass()
411            );
412
413            $this->persistQuestionAnswerStatus();
414
415            if ($this->object->isForceInstantFeedbackEnabled()) {
416                $this->ctrl->setParameter($this, 'instresp', 1);
417
418                $this->testSequence->unsetQuestionPostponed($questionId);
419                $this->testSequence->setQuestionChecked($questionId);
420                $this->testSequence->saveToDb();
421            }
422
423            if ($this->getNextCommandParameter()) {
424                if ($this->getNextSequenceParameter()) {
425                    $this->ctrl->setParameter($this, 'sequence', $this->getNextSequenceParameter());
426                    $this->ctrl->setParameter($this, 'pmode', '');
427                }
428
429                $this->ctrl->redirect($this, $this->getNextCommandParameter());
430            }
431
432            $this->ctrl->setParameter($this, 'pmode', ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW);
433        } else {
434            $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
435        }
436
437        // fau: testNav - remember to prevent the navigation confirmation
438        $this->saveNavigationPreventConfirmation();
439        // fau.
440
441        // fau: testNav - handle navigation after saving
442        if ($this->getNavigationUrlParameter()) {
443            ilUtil::redirect($this->getNavigationUrlParameter());
444        } else {
445            $this->ctrl->saveParameter($this, 'sequence');
446        }
447        // fau.
448        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
449    }
450
451    protected function discardSolutionCmd()
452    {
453        $questionId = $this->testSession->getCurrentQuestionId();
454
455        $currentQuestionOBJ = $this->getQuestionInstance($questionId);
456
457        $currentQuestionOBJ->resetUsersAnswer(
458            $this->testSession->getActiveId(),
459            $this->testSession->getPass()
460        );
461
462        $this->ctrl->setParameter($this, 'pmode', ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW);
463
464        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
465    }
466
467    protected function skipQuestionCmd()
468    {
469        $this->nextQuestionCmd();
470    }
471
472    protected function isCheckedQuestionResettingConfirmationRequired()
473    {
474        if (!$this->getResetCheckedParameter()) {
475            return false;
476        }
477
478        if ($this->testSession->getQuestionSetFilterSelection()->isAnswerStatusSelectionWrongAnswered()) {
479            $this->testSequence->loadQuestions(
480                $this->dynamicQuestionSetConfig,
481                $this->testSession->getQuestionSetFilterSelection()
482            );
483
484            if ($this->testSequence->hasFilteredQuestionListCheckedQuestions()) {
485                return true;
486            }
487        }
488
489        return false;
490    }
491
492    protected function showQuestionCmd()
493    {
494        $this->updateWorkingTime();
495
496        $this->testSequence->loadQuestions(
497            $this->dynamicQuestionSetConfig,
498            $this->testSession->getQuestionSetFilterSelection()
499        );
500
501        $this->testSequence->cleanupQuestions($this->testSession);
502
503        if ($this->isCheckedQuestionResettingConfirmationRequired()) {
504            $this->showCheckedQuestionResettingConfirmation();
505            return;
506        }
507
508        if ($this->testSequence->getQuestionSet()->getSelectionQuestionList()->isInList($this->getQuestionIdParameter())) {
509            $this->testSession->setCurrentQuestionId($this->getQuestionIdParameter());
510        } else {
511            $this->resetQuestionIdParameter();
512        }
513
514        if (!$this->testSession->getCurrentQuestionId()) {
515            $upComingQuestionId = $this->testSequence->getUpcomingQuestionId();
516
517            $this->testSession->setCurrentQuestionId($upComingQuestionId);
518
519            // seems to be a first try of freezing answers not too hard
520            /*if( $this->testSequence->isQuestionChecked($upComingQuestionId) )
521            {
522                $this->testSequence->setQuestionUnchecked($upComingQuestionId);
523            }*/
524        }
525
526        $navigationToolbarGUI = $this->getTestNavigationToolbarGUI();
527        $navigationToolbarGUI->setQuestionSelectionButtonEnabled(true);
528
529        if ($this->testSession->getCurrentQuestionId()) {
530            $questionGui = $this->getQuestionGuiInstance($this->testSession->getCurrentQuestionId());
531            $this->testSequence->setCurrentQuestionId($this->testSession->getCurrentQuestionId());
532
533            $questionGui->setQuestionCount(
534                $this->testSequence->getLastPositionIndex()
535            );
536            $questionGui->setSequenceNumber(
537                $this->testSequence->getCurrentPositionIndex($this->testSession->getCurrentQuestionId())
538            );
539
540            if (!($questionGui instanceof assQuestionGUI)) {
541                $this->handleTearsAndAngerQuestionIsNull(
542                    $this->testSession->getCurrentQuestionId(),
543                    $this->testSession->getCurrentQuestionId()
544                );
545            }
546
547            $isQuestionWorkedThrough = assQuestion::_isWorkedThrough(
548                $this->testSession->getActiveId(),
549                $this->testSession->getCurrentQuestionId(),
550                $this->testSession->getPass()
551            );
552
553            require_once 'Modules/Test/classes/class.ilTestQuestionHeaderBlockBuilder.php';
554            $headerBlockBuilder = new ilTestQuestionHeaderBlockBuilder($this->lng);
555            $headerBlockBuilder->setHeaderMode(
556                // avoid legacy setting combination: ctm without question titles
557                $this->object->getTitleOutput() == 2 ? 1 : $this->object->getTitleOutput()
558            );
559            $headerBlockBuilder->setQuestionTitle($questionGui->object->getTitle());
560            $headerBlockBuilder->setQuestionPoints($questionGui->object->getPoints());
561            /* avoid showing Qst X of Y within CTMs
562            $headerBlockBuilder->setQuestionPosition(
563                $this->testSequence->getCurrentPositionIndex($this->testSession->getCurrentQuestionId())
564            );
565            $headerBlockBuilder->setQuestionCount($this->testSequence->getLastPositionIndex());
566            */
567            $headerBlockBuilder->setQuestionPostponed(
568                $this->testSequence->isPostponedQuestion(
569                    $this->testSession->getCurrentQuestionId()
570            )
571            );
572            $headerBlockBuilder->setQuestionObligatory(
573                $this->object->areObligationsEnabled() && ilObjTest::isQuestionObligatory($this->object->getId())
574            );
575            $questionGui->setQuestionHeaderBlockBuilder($headerBlockBuilder);
576
577            // fau: testNav - always use edit mode, except for fixed answer
578            if ($this->isParticipantsAnswerFixed($this->testSession->getCurrentQuestionId())) {
579                $instantResponse = true;
580                $presentationMode = ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW;
581            } else {
582                $instantResponse = $this->getInstantResponseParameter();
583                $presentationMode = ilTestPlayerAbstractGUI::PRESENTATION_MODE_EDIT;
584            }
585            // fau.
586
587            $this->prepareTestPage(
588                $presentationMode,
589                $this->testSession->getCurrentQuestionId(),
590                $this->testSession->getCurrentQuestionId()
591            );
592
593            $this->ctrl->setParameter($this, 'sequence', $this->testSession->getCurrentQuestionId());
594            $this->ctrl->setParameter($this, 'pmode', $presentationMode);
595            $formAction = $this->ctrl->getFormAction($this, ilTestPlayerCommands::SUBMIT_INTERMEDIATE_SOLUTION);
596
597            switch ($presentationMode) {
598                case ilTestPlayerAbstractGUI::PRESENTATION_MODE_EDIT:
599
600// fau: testNav - enable navigation toolbar in edit mode
601                    $navigationToolbarGUI->setDisabledStateEnabled(false);
602// fau.
603                    $this->showQuestionEditable($questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse);
604
605                    break;
606
607                case ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW:
608
609                    $this->showQuestionViewable($questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse);
610
611                    break;
612
613                default:
614
615                    require_once 'Modules/Test/exceptions/class.ilTestException.php';
616                    throw new ilTestException('no presentation mode given');
617            }
618
619            $navigationToolbarGUI->build();
620            $this->populateTestNavigationToolbar($navigationToolbarGUI);
621
622            // fau: testNav - enable the question navigation in edit mode
623            $this->populateQuestionNavigation(
624                $this->testSession->getCurrentQuestionId(),
625                false,
626                $this->object->isForceInstantFeedbackEnabled()
627            );
628            // fau.
629
630            if ($instantResponse) {
631                // fau: testNav - always use authorized solution for instant feedback
632                $this->populateInstantResponseBlocks(
633                    $questionGui,
634                    true
635                );
636                // fau.
637                $this->testSession->getQuestionSetFilterSelection()->setForcedQuestionIds(array());
638            }
639
640            // fau: testNav - add feedback modal
641            if ($this->isForcedFeedbackNavUrlRegistered()) {
642                $this->populateInstantResponseModal($questionGui, $this->getRegisteredForcedFeedbackNavUrl());
643                $this->unregisterForcedFeedbackNavUrl();
644            }
645            // fau.
646        } else {
647            $this->prepareTestPage(ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW, null, null);
648
649            $navigationToolbarGUI->build();
650            $this->populateTestNavigationToolbar($navigationToolbarGUI);
651
652            $this->outCurrentlyFinishedPage();
653        }
654
655        $this->testSequence->saveToDb();
656        $this->testSession->saveToDb();
657    }
658
659    protected function showInstantResponseCmd()
660    {
661        $questionId = $this->testSession->getCurrentQuestionId();
662
663        $filterSelection = $this->testSession->getQuestionSetFilterSelection();
664
665        $filterSelection->setForcedQuestionIds(array($this->testSession->getCurrentQuestionId()));
666
667        $this->testSequence->loadQuestions($this->dynamicQuestionSetConfig, $filterSelection);
668        $this->testSequence->cleanupQuestions($this->testSession);
669        $this->testSequence->saveToDb();
670
671        if (!$this->isParticipantsAnswerFixed($questionId)) {
672            if ($this->saveQuestionSolution(true)) {
673                $this->removeIntermediateSolution();
674                $this->persistQuestionAnswerStatus();
675                $this->setAnswerChangedParameter(false);
676            } else {
677                $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
678            }
679            $this->testSequence->unsetQuestionPostponed($questionId);
680            $this->testSequence->setQuestionChecked($questionId);
681            $this->testSequence->saveToDb();
682        }
683
684        $this->ctrl->setParameter(
685            $this,
686            'sequence',
687            $this->testSession->getCurrentQuestionId()
688        );
689
690        $this->ctrl->setParameter($this, 'instresp', 1);
691
692        // fau: testNav - handle navigation after feedback
693        if ($this->getNavigationUrlParameter()) {
694            $this->saveNavigationPreventConfirmation();
695            $this->registerForcedFeedbackNavUrl($this->getNavigationUrlParameter());
696        }
697        // fau.
698        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
699    }
700
701    protected function handleQuestionActionCmd()
702    {
703        $questionId = $this->testSession->getCurrentQuestionId();
704
705        if ($questionId && !$this->isParticipantsAnswerFixed($questionId)) {
706            $this->saveQuestionSolution(false);
707            // fau: testNav - add changed status of the question
708            $this->setAnswerChangedParameter(true);
709            // fau.
710        }
711
712        $this->ctrl->setParameter(
713            $this,
714            'sequence',
715            $this->testSession->getCurrentQuestionId()
716        );
717
718        $this->ctrl->saveParameter($this, 'pmode');
719
720        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
721    }
722
723    private function outCurrentlyFinishedPage()
724    {
725        if ($this->testSequence->openQuestionExists()) {
726            $message = $this->lng->txt('tst_dyn_test_msg_currently_finished_selection');
727        } else {
728            $message = $this->lng->txt('tst_dyn_test_msg_currently_finished_completely');
729            $message .= "<br /><br />{$this->buildFinishPagePassDeletionLink()}";
730        }
731
732        $msgHtml = ilUtil::getSystemMessageHTML($message);
733
734        $tpl = new ilTemplate('tpl.test_currently_finished_msg.html', true, true, 'Modules/Test');
735        $tpl->setVariable('TEST_CURRENTLY_FINISHED_MSG', $msgHtml);
736
737        $this->tpl->setVariable('QUESTION_OUTPUT', $tpl->get());
738    }
739
740    protected function isFirstQuestionInSequence($sequenceElement)
741    {
742        return !$this->testSequence->trackedQuestionExists();
743    }
744
745    protected function isLastQuestionInSequence($sequenceElement)
746    {
747        return false; // always
748    }
749
750    /**
751     * Returns TRUE if the answers of the current user could be saved
752     *
753     * @return boolean TRUE if the answers could be saved, FALSE otherwise
754     */
755    protected function canSaveResult()
756    {
757        return !$this->object->endingTimeReached();
758    }
759
760    /**
761     * saves the user input of a question
762     */
763    public function saveQuestionSolution($authorized = true, $force = false)
764    {
765        // what is this formtimestamp ??
766        if (!$force) {
767            $formtimestamp = $_POST["formtimestamp"];
768            if (strlen($formtimestamp) == 0) {
769                $formtimestamp = $_GET["formtimestamp"];
770            }
771            if ($formtimestamp != $_SESSION["formtimestamp"]) {
772                $_SESSION["formtimestamp"] = $formtimestamp;
773            } else {
774                return false;
775            }
776        }
777
778        // determine current question
779
780        $qId = $this->testSession->getCurrentQuestionId();
781
782        if (!$qId || $qId != $_GET["sequence"]) {
783            return false;
784        }
785
786        // save question solution
787
788        $this->saveResult = false;
789
790        if ($this->canSaveResult($qId) || $force) {
791            $questionGUI = $this->object->createQuestionGUI("", $qId);
792
793            if ($this->object->getJavaScriptOutput()) {
794                $questionGUI->object->setOutputType(OUTPUT_JAVASCRIPT);
795            }
796
797            $activeId = $this->testSession->getActiveId();
798
799            $this->saveResult = $questionGUI->object->persistWorkingState(
800                $activeId,
801                $pass = null,
802                $this->object->areObligationsEnabled(),
803                $authorized
804                );
805
806            if ($authorized && $this->object->isSkillServiceToBeConsidered()) {
807                $this->handleSkillTriggering($this->testSession);
808            }
809        }
810
811        if ($this->saveResult == false) {
812            $this->ctrl->setParameter($this, "save_error", "1");
813            $_SESSION["previouspost"] = $_POST;
814        }
815
816        return $this->saveResult;
817    }
818
819    private function isQuestionAnsweredCorrect($questionId, $activeId, $pass)
820    {
821        $questionGUI = $this->object->createQuestionGUI("", $questionId);
822
823        $reachedPoints = assQuestion::_getReachedPoints($activeId, $questionId, $pass);
824        $maxPoints = $questionGUI->object->getMaximumPoints();
825
826        if ($reachedPoints < $maxPoints) {
827            return false;
828        }
829
830        return true;
831    }
832
833    protected function buildQuestionsTableDataArray($questions, $marked_questions)
834    {
835        $data = array();
836
837        foreach ($questions as $key => $value) {
838            $this->ctrl->setParameter($this, 'sequence', $value['question_id']);
839            $href = $this->ctrl->getLinkTarget($this, 'gotoQuestion');
840            $this->ctrl->setParameter($this, 'sequence', '');
841
842            $description = "";
843            if ($this->object->getListOfQuestionsDescription()) {
844                $description = $value["description"];
845            }
846
847            $marked = false;
848            if (count($marked_questions)) {
849                if (isset($marked_questions[$value["question_id"]])) {
850                    if ($marked_questions[$value["question_id"]]["solved"] == 1) {
851                        $marked = true;
852                    }
853                }
854            }
855
856            array_push($data, array(
857                'href' => $href,
858                'title' => $this->object->getQuestionTitle($value["title"]),
859                'description' => $description,
860                'worked_through' => $this->testSequence->isAnsweredQuestion($value["question_id"]),
861                'postponed' => $this->testSequence->isPostponedQuestion($value["question_id"]),
862                'marked' => $marked
863            ));
864        }
865
866        return $data;
867    }
868
869    protected function buildQuestionSetAnswerStatisticRowArray($questions, $trackedQuestions)
870    {
871        $questionAnswerStats = array(
872            'total_all' => count($questions),
873            'total_open' => 0,
874            'non_answered_notseen' => 0,
875            'non_answered_skipped' => 0,
876            'wrong_answered' => 0,
877            'correct_answered' => 0
878        );
879
880        foreach ($questions as $key => $value) {
881            switch ($value['question_answer_status']) {
882                case ilAssQuestionList::QUESTION_ANSWER_STATUS_NON_ANSWERED:
883                    if (isset($trackedQuestions[$key])) {
884                        $questionAnswerStats['non_answered_skipped']++;
885                    } else {
886                        $questionAnswerStats['non_answered_notseen']++;
887                    }
888                    $questionAnswerStats['total_open']++;
889                    break;
890                case ilAssQuestionList::QUESTION_ANSWER_STATUS_WRONG_ANSWERED:
891                    $questionAnswerStats['wrong_answered']++;
892                    $questionAnswerStats['total_open']++;
893                    break;
894                case ilAssQuestionList::QUESTION_ANSWER_STATUS_CORRECT_ANSWERED:
895                    $questionAnswerStats['correct_answered']++;
896                    break;
897            }
898        }
899
900        return $questionAnswerStats;
901    }
902
903    private function buildQuestionSetCompleteStatisticTableGUI()
904    {
905        require_once 'Modules/Test/classes/tables/class.ilTestDynamicQuestionSetStatisticTableGUI.php';
906        $gui = $this->buildQuestionSetStatisticTableGUI(
907            ilTestDynamicQuestionSetStatisticTableGUI::COMPLETE_TABLE_ID
908        );
909
910        $gui->initTitle('tst_dynamic_question_set_complete');
911        $gui->initColumns('tst_num_all_questions');
912
913        return $gui;
914    }
915
916    private function buildQuestionSetFilteredStatisticTableGUI()
917    {
918        require_once 'Modules/Test/classes/tables/class.ilTestDynamicQuestionSetStatisticTableGUI.php';
919        $gui = $this->buildQuestionSetStatisticTableGUI(
920            ilTestDynamicQuestionSetStatisticTableGUI::FILTERED_TABLE_ID
921        );
922
923        $gui->initTitle('tst_dynamic_question_set_selection');
924        $gui->initColumns('tst_num_selected_questions');
925
926        require_once 'Services/Taxonomy/classes/class.ilObjTaxonomy.php';
927        $gui->setTaxIds(ilObjTaxonomy::getUsageOfObject(
928            $this->dynamicQuestionSetConfig->getSourceQuestionPoolId()
929        ));
930
931        $gui->setTaxonomyFilterEnabled($this->dynamicQuestionSetConfig->isTaxonomyFilterEnabled());
932        $gui->setAnswerStatusFilterEnabled($this->dynamicQuestionSetConfig->isAnswerStatusFilterEnabled());
933
934        $gui->setFilterSelection($this->testSession->getQuestionSetFilterSelection());
935        $gui->initFilter();
936        $gui->setFilterCommand('filterQuestionSelection');
937        $gui->setResetCommand('resetQuestionSelection');
938
939        return $gui;
940    }
941
942    private function buildQuestionSetStatisticTableGUI($tableId)
943    {
944        require_once 'Modules/Test/classes/tables/class.ilTestDynamicQuestionSetStatisticTableGUI.php';
945        $gui = new ilTestDynamicQuestionSetStatisticTableGUI(
946            $this->ctrl,
947            $this->lng,
948            $this,
949            ilTestPlayerCommands::SHOW_QUESTION_SELECTION,
950            $tableId
951        );
952
953        return $gui;
954    }
955
956    private function getEnterTestButtonLangVar()
957    {
958        if ($this->testSequence->trackedQuestionExists()) {
959            return 'tst_resume_dyn_test_with_cur_quest_sel';
960        }
961
962        return 'tst_start_dyn_test_with_cur_quest_sel';
963    }
964
965    protected function persistQuestionAnswerStatus()
966    {
967        $questionId = $this->testSession->getCurrentQuestionId();
968        $activeId = $this->testSession->getActiveId();
969        $pass = $this->testSession->getPass();
970
971        if ($this->isQuestionAnsweredCorrect($questionId, $activeId, $pass)) {
972            $this->testSequence->setQuestionAnsweredCorrect($questionId);
973        } else {
974            $this->testSequence->setQuestionAnsweredWrong($questionId);
975        }
976
977        $this->testSequence->saveToDb();
978    }
979
980    private function resetCurrentQuestion()
981    {
982        $this->testSession->setCurrentQuestionId(null);
983        $this->testSession->saveToDb();
984
985        $this->ctrl->setParameter($this, 'sequence', $this->testSession->getCurrentQuestionId());
986        $this->ctrl->setParameter($this, 'pmode', '');
987    }
988
989    /**
990     * @return string
991     */
992    private function buildFinishPagePassDeletionLink()
993    {
994        $href = $this->getPassDeletionTarget();
995
996        $label = $this->lng->txt('tst_dyn_test_msg_pass_deletion_link');
997
998        return "<a href=\"{$href}\">{$label}</a>";
999    }
1000
1001    /**
1002     * @return string
1003     */
1004    private function getPassDeletionTarget()
1005    {
1006        require_once 'Modules/Test/classes/confirmations/class.ilTestPassDeletionConfirmationGUI.php';
1007
1008        $this->ctrl->setParameterByClass('ilTestEvaluationGUI', 'context', ilTestPassDeletionConfirmationGUI::CONTEXT_DYN_TEST_PLAYER);
1009        $this->ctrl->setParameterByClass('ilTestEvaluationGUI', 'active_id', $this->testSession->getActiveId());
1010        $this->ctrl->setParameterByClass('ilTestEvaluationGUI', 'pass', $this->testSession->getPass());
1011
1012        return $this->ctrl->getLinkTargetByClass('ilTestEvaluationGUI', 'confirmDeletePass');
1013    }
1014
1015    protected function resetQuestionIdParameter()
1016    {
1017        $this->resetSequenceElementParameter();
1018    }
1019
1020    protected function getQuestionIdParameter()
1021    {
1022        return $this->getSequenceElementParameter();
1023    }
1024
1025    protected function getResetCheckedParameter()
1026    {
1027        if (isset($_GET['reset_checked'])) {
1028            return $_GET['reset_checked'];
1029        }
1030
1031        return null;
1032    }
1033
1034    public function outQuestionSummaryCmd($fullpage = true, $contextFinishTest = false, $obligationsNotAnswered = false, $obligationsFilter = false)
1035    {
1036        $this->testSequence->loadQuestions(
1037            $this->dynamicQuestionSetConfig,
1038            $this->testSession->getQuestionSetFilterSelection()
1039        );
1040
1041        $this->testSequence->setCurrentQuestionId($this->testSession->getCurrentQuestionId());
1042
1043        parent::outQuestionSummaryCmd($fullpage, $contextFinishTest, $obligationsNotAnswered, $obligationsFilter);
1044    }
1045
1046    protected function showCheckedQuestionResettingConfirmation()
1047    {
1048        require_once 'Services/Utilities/classes/class.ilConfirmationGUI.php';
1049        $confirmation = new ilConfirmationGUI();
1050        $confirmation->setFormAction($this->ctrl->getFormAction($this));
1051        $confirmation->setHeaderText($this->lng->txt('tst_dyn_unfreeze_answers_confirmation'));
1052        $confirmation->setConfirm($this->lng->txt('tst_dyn_unfreeze_answers'), ilTestPlayerCommands::UNFREEZE_ANSWERS);
1053        $confirmation->setCancel($this->lng->txt('tst_dyn_keep_answ_freeze'), ilTestPlayerCommands::SHOW_QUESTION);
1054
1055        $this->populateMessageContent($confirmation->getHtml());
1056    }
1057
1058    protected function unfreezeCheckedQuestionsAnswersCmd()
1059    {
1060        $this->testSequence->loadQuestions(
1061            $this->dynamicQuestionSetConfig,
1062            $this->testSession->getQuestionSetFilterSelection()
1063        );
1064
1065        $this->testSequence->resetFilteredQuestionListsCheckedStatus();
1066        $this->testSequence->saveToDb();
1067
1068        $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION);
1069    }
1070
1071    protected function populateQuestionNavigation($sequenceElement, $disabled, $primaryNext)
1072    {
1073        if (!$this->isLastQuestionInSequence($sequenceElement)) {
1074            $this->populateNextButtons($disabled, $primaryNext);
1075        }
1076    }
1077
1078    protected function getStartTestFromQuestionSelectionLink()
1079    {
1080        $this->ctrl->setParameter($this, 'reset_checked', 1);
1081        $link = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::SHOW_QUESTION);
1082        $this->ctrl->setParameter($this, 'reset_checked', '');
1083
1084        return $link;
1085    }
1086
1087    protected function isShowingPostponeStatusReguired($questionId)
1088    {
1089        return false;
1090    }
1091
1092    protected function buildTestPassQuestionList()
1093    {
1094        global $DIC;
1095        $ilPluginAdmin = $DIC['ilPluginAdmin'];
1096
1097        require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionList.php';
1098        $questionList = new ilAssQuestionList($this->db, $this->lng, $ilPluginAdmin);
1099        $questionList->setParentObjId($this->dynamicQuestionSetConfig->getSourceQuestionPoolId());
1100        $questionList->setQuestionInstanceTypeFilter(ilAssQuestionList::QUESTION_INSTANCE_TYPE_ORIGINALS);
1101
1102        return $questionList;
1103    }
1104
1105    protected function isQuestionSummaryFinishTestButtonRequired()
1106    {
1107        return false;
1108    }
1109
1110    protected function isOptionalQuestionAnsweringConfirmationRequired($sequenceKey)
1111    {
1112        return false;
1113    }
1114}
1115