1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4require_once 'Modules/Test/interfaces/interface.ilTestSequenceSummaryProvider.php';
5
6/**
7 * Test sequence handler
8 *
9 * This class manages the sequence settings for a given user
10 * and a dynamic question set test
11 *
12 * @author	Björn Heyser <bheyser@databay.de>
13 * @version	$Id$
14 * @package	Modules/Test
15 */
16class ilTestSequenceDynamicQuestionSet implements ilTestSequenceSummaryProvider
17{
18    /**
19     * @var ilDBInterface
20     */
21    private $db = null;
22
23    /**
24     * @var ilTestDynamicQuestionSet
25     */
26    private $questionSet = null;
27
28    /**
29     * @var integer
30     */
31    private $activeId = null;
32
33    /**
34     * @var bool
35     */
36    private $preventCheckedQuestionsFromComingUpEnabled;
37
38    /**
39     * @var array
40     */
41    private $questionTracking = array();
42
43    /**
44     * @var integer
45     */
46    private $newlyTrackedQuestion;
47
48    /**
49     * @var string
50     */
51    private $newlyTrackedQuestionsStatus;
52
53    /**
54     * @var array
55     */
56    private $postponedQuestions = array();
57
58    /**
59     * @var integer
60     */
61    private $newlyPostponedQuestion;
62
63    /**
64     * @var integer
65     */
66    private $newlyPostponedQuestionsCount;
67
68    /**
69     * @var array
70     */
71    private $alreadyCheckedQuestions;
72
73    /**
74     * @var integer
75     */
76    private $newlyCheckedQuestion;
77
78    /**
79     * @var array
80     */
81    private $correctAnsweredQuestions = array();
82
83    /**
84     * @var array
85     */
86    private $wrongAnsweredQuestions = array();
87
88    /**
89     * @var integer
90     */
91    private $newlyAnsweredQuestion;
92
93    /**
94     * @var boolean
95     */
96    private $newlyAnsweredQuestionsAnswerStatus;
97
98    /**
99     * @var integer
100     */
101    private $currentQuestionId;
102
103    /**
104     * Constructor
105     *
106     * @param ilTestDynamicQuestionSet $questionSet
107     */
108    public function __construct(ilDBInterface $db, ilTestDynamicQuestionSet $questionSet, $activeId)
109    {
110        $this->db = $db;
111        $this->questionSet = $questionSet;
112        $this->activeId = $activeId;
113
114        $this->newlyTrackedQuestion = null;
115        $this->newlyTrackedQuestionsStatus = null;
116
117        $this->newlyPostponedQuestion = null;
118        $this->newlyPostponedQuestionsCount = null;
119
120        $this->newlyAnsweredQuestion = null;
121        $this->newlyAnsweredQuestionsAnswerStatus = null;
122
123        $this->alreadyCheckedQuestions = array();
124        $this->newlyCheckedQuestion = null;
125
126        $this->preventCheckedQuestionsFromComingUpEnabled = false;
127
128        $this->currentQuestionId = null;
129    }
130
131    public function getActiveId()
132    {
133        return $this->activeId;
134    }
135
136    public function setPreventCheckedQuestionsFromComingUpEnabled($preventCheckedQuestionsFromComingUpEnabled)
137    {
138        $this->preventCheckedQuestionsFromComingUpEnabled = $preventCheckedQuestionsFromComingUpEnabled;
139    }
140
141    public function isPreventCheckedQuestionsFromComingUpEnabled()
142    {
143        return $this->preventCheckedQuestionsFromComingUpEnabled;
144    }
145
146    /**
147     * @return int
148     */
149    public function getCurrentQuestionId()
150    {
151        return $this->currentQuestionId;
152    }
153
154    /**
155     * @param int $currentQuestionId
156     */
157    public function setCurrentQuestionId($currentQuestionId)
158    {
159        $this->currentQuestionId = $currentQuestionId;
160    }
161
162    public function loadFromDb()
163    {
164        $this->loadQuestionTracking();
165        $this->loadAnswerStatus();
166        $this->loadPostponedQuestions();
167        $this->loadCheckedQuestions();
168    }
169
170    private function loadQuestionTracking()
171    {
172        $query = "
173			SELECT		question_fi, status
174			FROM		tst_seq_qst_tracking
175			WHERE		active_fi = %s
176			AND			pass = %s
177			ORDER BY	orderindex ASC
178		";
179
180        $res = $this->db->queryF($query, array('integer','integer'), array($this->activeId, 0));
181
182        $this->questionTracking = array();
183
184        while ($row = $this->db->fetchAssoc($res)) {
185            $this->questionTracking[] = array(
186                'qid' => $row['question_fi'],
187                'status' => $row['status']
188            );
189        }
190    }
191
192    private function loadAnswerStatus()
193    {
194        $query = "
195			SELECT	question_fi, correctness
196			FROM	tst_seq_qst_answstatus
197			WHERE	active_fi = %s
198			AND		pass = %s
199		";
200
201        $res = $this->db->queryF($query, array('integer','integer'), array($this->activeId, 0));
202
203        $this->correctAnsweredQuestions = array();
204        $this->wrongAnsweredQuestions = array();
205
206        while ($row = $this->db->fetchAssoc($res)) {
207            if ($row['correctness']) {
208                $this->correctAnsweredQuestions[ $row['question_fi'] ] = $row['question_fi'];
209            } else {
210                $this->wrongAnsweredQuestions[ $row['question_fi'] ] = $row['question_fi'];
211            }
212        }
213    }
214
215    private function loadPostponedQuestions()
216    {
217        $query = "
218			SELECT	question_fi, cnt
219			FROM	tst_seq_qst_postponed
220			WHERE	active_fi = %s
221			AND		pass = %s
222		";
223
224        $res = $this->db->queryF($query, array('integer','integer'), array($this->activeId, 0));
225
226        $this->postponedQuestions = array();
227
228        while ($row = $this->db->fetchAssoc($res)) {
229            $this->postponedQuestions[ $row['question_fi'] ] = $row['cnt'];
230        }
231    }
232
233    private function loadCheckedQuestions()
234    {
235        $res = $this->db->queryF(
236            "SELECT question_fi FROM tst_seq_qst_checked WHERE active_fi = %s AND pass = %s",
237            array('integer','integer'),
238            array($this->getActiveId(), 0)
239        );
240
241        while ($row = $this->db->fetchAssoc($res)) {
242            $this->alreadyCheckedQuestions[ $row['question_fi'] ] = $row['question_fi'];
243        }
244    }
245
246    public function saveToDb()
247    {
248        $this->db->manipulateF(
249            "DELETE FROM tst_sequence WHERE active_fi = %s AND pass = %s",
250            array('integer','integer'),
251            array($this->getActiveId(), 0)
252        );
253
254        $this->db->insert('tst_sequence', array(
255            'active_fi' => array('integer', $this->getActiveId()),
256            'pass' => array('integer', 0),
257            'sequence' => array('clob', null),
258            'postponed' => array('text', null),
259            'hidden' => array('text', null),
260            'tstamp' => array('integer', time())
261        ));
262
263        $this->saveNewlyTrackedQuestion();
264        $this->saveNewlyAnsweredQuestionsAnswerStatus();
265        $this->saveNewlyPostponedQuestion();
266        $this->removeQuestionsNotPostponedAnymore();
267        $this->saveNewlyCheckedQuestion();
268        $this->removeQuestionsNotCheckedAnymore();
269    }
270
271    private function saveNewlyTrackedQuestion()
272    {
273        if ((int) $this->newlyTrackedQuestion) {
274            $newOrderIndex = $this->getNewOrderIndexForQuestionTracking();
275
276            $this->db->replace(
277                'tst_seq_qst_tracking',
278                array(
279                    'active_fi' => array('integer', (int) $this->getActiveId()),
280                    'pass' => array('integer', 0),
281                    'question_fi' => array('integer', (int) $this->newlyTrackedQuestion)
282                ),
283                array(
284                    'status' => array('text', $this->newlyTrackedQuestionsStatus),
285                    'orderindex' => array('integer', $newOrderIndex)
286                )
287            );
288        }
289    }
290
291    private function getNewOrderIndexForQuestionTracking()
292    {
293        $query = "
294				SELECT (MAX(orderindex) + 1) new_order_index
295				FROM tst_seq_qst_tracking
296				WHERE active_fi = %s
297				AND pass = %s
298			";
299
300        $res = $this->db->queryF($query, array('integer','integer'), array($this->getActiveId(), 0));
301
302        $row = $this->db->fetchAssoc($res);
303
304        if ($row['new_order_index']) {
305            return $row['new_order_index'];
306        }
307
308        return 1;
309    }
310
311    private function saveNewlyAnsweredQuestionsAnswerStatus()
312    {
313        if ((int) $this->newlyAnsweredQuestion) {
314            $this->db->replace(
315                'tst_seq_qst_answstatus',
316                array(
317                    'active_fi' => array('integer', (int) $this->getActiveId()),
318                    'pass' => array('integer', 0),
319                    'question_fi' => array('integer', (int) $this->newlyAnsweredQuestion)
320                ),
321                array(
322                    'correctness' => array('integer', (int) $this->newlyAnsweredQuestionsAnswerStatus)
323                )
324            );
325        }
326    }
327
328    private function saveNewlyPostponedQuestion()
329    {
330        if ((int) $this->newlyPostponedQuestion) {
331            $this->db->replace(
332                'tst_seq_qst_postponed',
333                array(
334                    'active_fi' => array('integer', (int) $this->getActiveId()),
335                    'pass' => array('integer', 0),
336                    'question_fi' => array('integer', (int) $this->newlyPostponedQuestion)
337                ),
338                array(
339                    'cnt' => array('integer', (int) $this->newlyPostponedQuestionsCount)
340                )
341            );
342        }
343    }
344
345    private function removeQuestionsNotPostponedAnymore()
346    {
347        $INquestions = $this->db->in('question_fi', array_keys($this->postponedQuestions), true, 'integer');
348
349        $query = "
350			DELETE FROM tst_seq_qst_postponed
351			WHERE active_fi = %s
352			AND pass = %s
353			AND $INquestions
354		";
355
356        $this->db->manipulateF($query, array('integer','integer'), array($this->getActiveId(), 0));
357    }
358
359    private function saveNewlyCheckedQuestion()
360    {
361        if ((int) $this->newlyCheckedQuestion) {
362            $this->db->replace('tst_seq_qst_checked', array(
363                'active_fi' => array('integer', (int) $this->getActiveId()),
364                'pass' => array('integer', 0),
365                'question_fi' => array('integer', (int) $this->newlyCheckedQuestion)
366            ), array());
367        }
368    }
369
370    private function removeQuestionsNotCheckedAnymore()
371    {
372        $NOT_IN_checkedQuestions = $this->db->in('question_fi', $this->alreadyCheckedQuestions, true, 'integer');
373
374        // BEGIN: FIX IN QUERY
375        if ($NOT_IN_checkedQuestions == ' 1=2 ') {
376            $NOT_IN_checkedQuestions = ' 1=1 ';
377        }
378        // END: FIX IN QUERY
379
380        $query = "
381			DELETE FROM tst_seq_qst_checked
382			WHERE active_fi = %s
383			AND pass = %s
384			AND $NOT_IN_checkedQuestions
385		";
386
387        $this->db->manipulateF($query, array('integer', 'integer'), array((int) $this->getActiveId(), 0));
388    }
389
390    public function loadQuestions(ilObjTestDynamicQuestionSetConfig $dynamicQuestionSetConfig, ilTestDynamicQuestionSetFilterSelection $filterSelection)
391    {
392        $this->questionSet->load($dynamicQuestionSetConfig, $filterSelection);
393
394        //		echo "<table><tr>";
395//		echo "<td width='200'><pre>".print_r($this->questionSet->getActualQuestionSequence(), 1)."</pre></td>";
396//		echo "<td width='200'><pre>".print_r($this->correctAnsweredQuestions, 1)."</pre></td>";
397//		echo "<td width='200'><pre>".print_r($this->wrongAnsweredQuestions, 1)."</pre></td>";
398//		echo "</tr></table>";
399    }
400
401    // -----------------------------------------------------------------------------------------------------------------
402
403    public function cleanupQuestions(ilTestSessionDynamicQuestionSet $testSession)
404    {
405        switch (true) {
406            case !$this->questionSet->questionExists($testSession->getCurrentQuestionId()):
407            case !$this->isFilteredQuestion($testSession->getCurrentQuestionId()):
408
409                $testSession->setCurrentQuestionId(null);
410        }
411
412        foreach ($this->postponedQuestions as $questionId) {
413            if (!$this->questionSet->questionExists($questionId)) {
414                unset($this->postponedQuestions[$questionId]);
415            }
416        }
417
418        foreach ($this->wrongAnsweredQuestions as $questionId) {
419            if (!$this->questionSet->questionExists($questionId)) {
420                unset($this->wrongAnsweredQuestions[$questionId]);
421            }
422        }
423
424        foreach ($this->correctAnsweredQuestions as $questionId) {
425            if (!$this->questionSet->questionExists($questionId)) {
426                unset($this->correctAnsweredQuestions[$questionId]);
427            }
428        }
429    }
430
431    // -----------------------------------------------------------------------------------------------------------------
432
433    public function getUpcomingQuestionId()
434    {
435        if ($questionId = $this->fetchUpcomingQuestionId(true, true)) {
436            return $questionId;
437        }
438
439        if ($questionId = $this->fetchUpcomingQuestionId(false, true)) {
440            return $questionId;
441        }
442
443        if ($questionId = $this->fetchUpcomingQuestionId(true, false)) {
444            return $questionId;
445        }
446
447        if ($questionId = $this->fetchUpcomingQuestionId(false, false)) {
448            return $questionId;
449        }
450
451        return null;
452    }
453
454    private function fetchUpcomingQuestionId($excludePostponedQuestions, $forceNonAnsweredQuestion)
455    {
456        foreach ($this->questionSet->getActualQuestionSequence() as $level => $questions) {
457            $postponedQuestions = array();
458
459            foreach ($questions as $pos => $qId) {
460                if (isset($this->correctAnsweredQuestions[$qId])) {
461                    continue;
462                }
463
464                if ($this->isPreventCheckedQuestionsFromComingUpEnabled() && $this->isQuestionChecked($qId)) {
465                    continue;
466                }
467
468                if ($forceNonAnsweredQuestion && isset($this->wrongAnsweredQuestions[$qId])) {
469                    continue;
470                }
471
472                if (isset($this->postponedQuestions[$qId])) {
473                    $postponedQuestions[$qId] = $this->postponedQuestions[$qId];
474                    continue;
475                }
476
477                return $qId;
478            }
479
480            if (!$excludePostponedQuestions && count($postponedQuestions)) {
481                return $this->fetchMostLeastPostponedQuestion($postponedQuestions);
482            }
483        }
484
485        return null;
486    }
487
488    public function isAnsweredQuestion($questionId)
489    {
490        return (
491            isset($this->correctAnsweredQuestions[$questionId])
492            || isset($this->wrongAnsweredQuestions[$questionId])
493        );
494    }
495
496    public function isPostponedQuestion($questionId)
497    {
498        return isset($this->postponedQuestions[$questionId]);
499    }
500
501    public function isFilteredQuestion($questionId)
502    {
503        foreach ($this->questionSet->getActualQuestionSequence() as $level => $questions) {
504            if (in_array($questionId, $questions)) {
505                return true;
506            }
507        }
508
509        return false;
510    }
511
512    public function trackedQuestionExists()
513    {
514        return (bool) count($this->questionTracking);
515    }
516
517    public function getTrackedQuestionList($currentQuestionId = null)
518    {
519        $questionList = array();
520
521        if ($currentQuestionId) {
522            $questionList[$currentQuestionId] = $this->questionSet->getQuestionData($currentQuestionId);
523        }
524
525        foreach (array_reverse($this->questionTracking) as $trackedQuestion) {
526            if (!isset($questionList[ $trackedQuestion['qid'] ])) {
527                $questionList[ $trackedQuestion['qid'] ] = $this->questionSet->getQuestionData($trackedQuestion['qid']);
528            }
529        }
530
531        return $questionList;
532    }
533
534    public function resetTrackedQuestionList()
535    {
536        $this->questionTracking = array();
537    }
538
539    public function openQuestionExists()
540    {
541        return count($this->getOpenQuestions()) > 0;
542    }
543
544    public function getOpenQuestions()
545    {
546        $completeQuestionIds = array_keys($this->questionSet->getAllQuestionsData());
547
548        $openQuestions = array_diff($completeQuestionIds, $this->correctAnsweredQuestions);
549
550        return $openQuestions;
551    }
552
553    public function getTrackedQuestionCount()
554    {
555        $uniqueQuestions = array();
556
557        foreach ($this->questionTracking as $trackedQuestion) {
558            $uniqueQuestions[$trackedQuestion['qid']] = $trackedQuestion['qid'];
559        }
560
561        return count($uniqueQuestions);
562    }
563
564    public function getCurrentPositionIndex($questionId)
565    {
566        $i = 0;
567
568        foreach ($this->getSelectionOrderedSequence() as $qId) {
569            $i++;
570
571            if ($qId == $questionId) {
572                return $i;
573            }
574        }
575
576        return null;
577    }
578
579    public function getLastPositionIndex()
580    {
581        return count($this->getSelectionOrderedSequence());
582    }
583
584    // -----------------------------------------------------------------------------------------------------------------
585
586    public function setQuestionUnchecked($questionId)
587    {
588        unset($this->alreadyCheckedQuestions[$questionId]);
589    }
590
591    public function setQuestionChecked($questionId)
592    {
593        $this->newlyCheckedQuestion = $questionId;
594        $this->alreadyCheckedQuestions[$questionId] = $questionId;
595    }
596
597    public function isQuestionChecked($questionId)
598    {
599        return isset($this->alreadyCheckedQuestions[$questionId]);
600    }
601
602    public function setQuestionPostponed($questionId)
603    {
604        $this->trackQuestion($questionId, 'postponed');
605
606        if (!isset($this->postponedQuestions[$questionId])) {
607            $this->postponedQuestions[$questionId] = 0;
608        }
609
610        $this->postponedQuestions[$questionId]++;
611
612        $this->newlyPostponedQuestion = $questionId;
613        $this->newlyPostponedQuestionsCount = $this->postponedQuestions[$questionId];
614    }
615
616    public function unsetQuestionPostponed($questionId)
617    {
618        if (isset($this->postponedQuestions[$questionId])) {
619            unset($this->postponedQuestions[$questionId]);
620        }
621    }
622
623    public function setQuestionAnsweredCorrect($questionId)
624    {
625        $this->trackQuestion($questionId, 'correct');
626
627        $this->correctAnsweredQuestions[$questionId] = $questionId;
628
629        if (isset($this->wrongAnsweredQuestions[$questionId])) {
630            unset($this->wrongAnsweredQuestions[$questionId]);
631        }
632
633        $this->newlyAnsweredQuestion = $questionId;
634        $this->newlyAnsweredQuestionsAnswerStatus = true;
635    }
636
637    public function setQuestionAnsweredWrong($questionId)
638    {
639        $this->trackQuestion($questionId, 'wrong');
640
641        $this->wrongAnsweredQuestions[$questionId] = $questionId;
642
643        if (isset($this->correctAnsweredQuestions[$questionId])) {
644            unset($this->correctAnsweredQuestions[$questionId]);
645        }
646
647        $this->newlyAnsweredQuestion = $questionId;
648        $this->newlyAnsweredQuestionsAnswerStatus = false;
649    }
650
651    private function trackQuestion($questionId, $answerStatus)
652    {
653        $this->questionTracking[] = array(
654            'qid' => $questionId, 'status' => $answerStatus
655        );
656
657        $this->newlyTrackedQuestion = $questionId;
658        $this->newlyTrackedQuestionsStatus = $answerStatus;
659    }
660
661    // -----------------------------------------------------------------------------------------------------------------
662
663    public function hasStarted()
664    {
665        return $this->trackedQuestionExists();
666    }
667
668    // -----------------------------------------------------------------------------------------------------------------
669
670    /**
671     * @return ilTestDynamicQuestionSet
672     */
673    public function getQuestionSet()
674    {
675        return $this->questionSet;
676    }
677
678    public function getCompleteQuestionsData()
679    {
680        return $this->questionSet->getCompleteQuestionList()->getQuestionDataArray();
681    }
682
683    public function getFilteredQuestionsData()
684    {
685        return $this->questionSet->getFilteredQuestionList()->getQuestionDataArray();
686    }
687
688    // -----------------------------------------------------------------------------------------------------------------
689
690    public function getUserSequenceQuestions()
691    {
692        //return array_keys( $this->getTrackedQuestionList() );
693
694        $questionSequence = array();
695
696        foreach ($this->questionSet->getActualQuestionSequence() as $level => $questions) {
697            $questionSequence = array_merge($questionSequence, $questions);
698        }
699
700        return $questionSequence;
701    }
702
703    /**
704     * @param $postponedQuestions
705     * @return int|null|string
706     */
707    private function fetchMostLeastPostponedQuestion($postponedQuestions)
708    {
709        $minPostponeCount = null;
710        $minPostponeItem = null;
711
712        foreach (array_reverse($postponedQuestions, true) as $qId => $postponeCount) {
713            if ($minPostponeCount === null || $postponeCount <= $minPostponeCount) {
714                $minPostponeCount = $postponeCount;
715                $minPostponeItem = $qId;
716            }
717        }
718        return $minPostponeItem;
719    }
720
721    public function getPass()
722    {
723        return 0;
724    }
725
726    // -----------------------------------------------------------------------------------------------------------------
727
728    private function orderQuestionsByPostponeCount($postponedQuestions)
729    {
730        $maxPostponeCount = max($postponedQuestions);
731
732        $orderedSequence = array();
733        $postponedCountDomain = array_flip($postponedQuestions);
734
735        for ($i = 1; $i <= $maxPostponeCount; $i++) {
736            if (!isset($postponedCountDomain[$i])) {
737                continue;
738            }
739
740            foreach ($postponedQuestions as $qId => $postponeCount) {
741                if ($postponeCount == $i) {
742                    $orderedSequence[] = $qId;
743                }
744            }
745        }
746
747        return $orderedSequence;
748    }
749
750    private function fetchQuestionSequence($nonPostponedQuestions, $nonAnsweredQuestions)
751    {
752        $questionSequence = array();
753
754        foreach ($this->questionSet->getActualQuestionSequence() as $level => $questions) {
755            $postponedQuestions = array();
756
757            foreach ($questions as $pos => $qId) {
758                if (isset($this->correctAnsweredQuestions[$qId])) {
759                    continue;
760                }
761
762                if ($nonAnsweredQuestions && isset($this->wrongAnsweredQuestions[$qId])) {
763                    continue;
764                } elseif (!$nonAnsweredQuestions && !isset($this->wrongAnsweredQuestions[$qId])) {
765                    continue;
766                }
767
768                if (!$nonPostponedQuestions && isset($this->postponedQuestions[$qId])) {
769                    $postponedQuestions[$qId] = $this->postponedQuestions[$qId];
770                    continue;
771                } elseif ($nonPostponedQuestions && !isset($this->postponedQuestions[$qId])) {
772                    $questionSequence[] = $qId;
773                }
774            }
775
776            if (!$nonPostponedQuestions && count($postponedQuestions)) {
777                $questionSequence = array_merge(
778                    $questionSequence,
779                    $this->orderQuestionsByPostponeCount($postponedQuestions)
780                );
781            }
782        }
783
784        return $questionSequence;
785    }
786
787    private function fetchTrackedCorrectAnsweredSequence()
788    {
789        $questionSequence = array();
790
791        foreach ($this->questionTracking as $key => $question) {
792            $qId = $question['qid'];
793
794            if (!isset($this->correctAnsweredQuestions[$qId])) {
795                continue;
796            }
797
798            $questionSequence[] = $qId;
799        }
800
801        return $questionSequence;
802    }
803
804    private function getOrderedSequence()
805    {
806        $correctAnsweredQuestions = $this->fetchTrackedCorrectAnsweredSequence();
807
808        $nonAnsweredQuestions = $this->fetchQuestionSequence(
809            true,
810            true
811        );
812
813        $postponedNonAnsweredQuestions = $this->fetchQuestionSequence(
814            false,
815            true
816        );
817
818        $wrongAnsweredQuestions = $this->fetchQuestionSequence(
819            true,
820            false
821        );
822
823        $postponedWrongAnsweredQuestions = $this->fetchQuestionSequence(
824            false,
825            false
826        );
827
828        $questionOrder = array_merge(
829            $correctAnsweredQuestions,
830            $nonAnsweredQuestions,
831            $postponedNonAnsweredQuestions,
832            $wrongAnsweredQuestions,
833            $postponedWrongAnsweredQuestions
834        );
835
836        return $questionOrder;
837    }
838
839    public function getSelectionOrderedSequence()
840    {
841        $sequence = array();
842
843        foreach ($this->getOrderedSequence() as $qId) {
844            if (!$this->getQuestionSet()->getSelectionQuestionList()->isInList($qId)) {
845                continue;
846            }
847
848            $sequence[] = $qId;
849        }
850
851        return $sequence;
852    }
853
854    public function getSequenceSummary($obligationsFilterEnabled = false)
855    {
856        $questionOrder = $this->getSelectionOrderedSequence();
857
858        $solved_questions = ilObjTest::_getSolvedQuestions($this->getActiveId());
859
860        $key = 1;
861
862        $summary = array();
863
864        foreach ($questionOrder as $qId) {
865            $question = &ilObjTest::_instanciateQuestion($qId);
866            if (is_object($question)) {
867                $worked_through = $question->_isWorkedThrough($this->getActiveId(), $question->getId(), $this->getPass());
868                $solved = 0;
869                if (array_key_exists($question->getId(), $solved_questions)) {
870                    $solved = $solved_questions[$question->getId()]["solved"];
871                }
872
873                // do not show postponing, since this happens implicit on dircarding solutions (CTM only)
874                //$is_postponed = $this->isPostponedQuestion($question->getId());
875
876                $row = array("nr" => "$key", "title" => $question->getTitle(), "qid" => $question->getId(), "visited" => $worked_through, "solved" => (($solved) ? "1" : "0"), "description" => $question->getComment(), "points" => $question->getMaximumPoints(), "worked_through" => $worked_through, "postponed" => $is_postponed, "sequence" => $qId, "obligatory" => ilObjTest::isQuestionObligatory($question->getId()), 'isAnswered' => $question->isAnswered($this->getActiveId(), $this->getPass()));
877
878                if (!$obligationsFilterEnabled || $row['obligatory']) {
879                    $summary[] = $row;
880                }
881
882                $key++;
883            }
884        }
885
886        return $summary;
887    }
888
889    public function hasFilteredQuestionListCheckedQuestions()
890    {
891        $filteredQuestions = $this->questionSet->getFilteredQuestionList()->getQuestionDataArray();
892
893        foreach ($filteredQuestions as $filteredQuestion) {
894            if ($this->isQuestionChecked($filteredQuestion['question_id'])) {
895                return true;
896            }
897        }
898
899        return false;
900    }
901
902    public function resetFilteredQuestionListsCheckedStatus()
903    {
904        $filteredQuestions = $this->questionSet->getFilteredQuestionList()->getQuestionDataArray();
905
906        foreach ($filteredQuestions as $filteredQuestion) {
907            if ($this->isQuestionChecked($filteredQuestion['question_id'])) {
908                $this->setQuestionUnchecked($filteredQuestion['question_id']);
909            }
910        }
911    }
912}
913