ILIAS  release_5-1 Revision 5.0.0-5477-g43f3e3fab5f
class.ilTestSequenceDynamicQuestionSet.php
Go to the documentation of this file.
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
17{
21 private $db = null;
22
26 private $questionSet = null;
27
31 private $activeId = null;
32
37
41 private $questionTracking = array();
42
47
52
56 private $postponedQuestions = array();
57
62
67
72
77
81 private $correctAnsweredQuestions = array();
82
86 private $wrongAnsweredQuestions = array();
87
92
97
102
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
137 {
138 $this->preventCheckedQuestionsFromComingUpEnabled = $preventCheckedQuestionsFromComingUpEnabled;
139 }
140
142 {
144 }
145
149 public function getCurrentQuestionId()
150 {
152 }
153
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 {
186 $this->questionTracking[] = array(
187 'qid' => $row['question_fi'],
188 'status' => $row['status']
189 );
190 }
191 }
192
193 private function loadAnswerStatus()
194 {
195 $query = "
196 SELECT question_fi, correctness
197 FROM tst_seq_qst_answstatus
198 WHERE active_fi = %s
199 AND pass = %s
200 ";
201
202 $res = $this->db->queryF($query, array('integer','integer'), array($this->activeId, 0));
203
204 $this->correctAnsweredQuestions = array();
205 $this->wrongAnsweredQuestions = array();
206
207 while( $row = $this->db->fetchAssoc($res) )
208 {
209 if( $row['correctness'] )
210 {
211 $this->correctAnsweredQuestions[ $row['question_fi'] ] = $row['question_fi'];
212 }
213 else
214 {
215 $this->wrongAnsweredQuestions[ $row['question_fi'] ] = $row['question_fi'];
216 }
217 }
218 }
219
220 private function loadPostponedQuestions()
221 {
222 $query = "
223 SELECT question_fi, cnt
224 FROM tst_seq_qst_postponed
225 WHERE active_fi = %s
226 AND pass = %s
227 ";
228
229 $res = $this->db->queryF($query, array('integer','integer'), array($this->activeId, 0));
230
231 $this->postponedQuestions = array();
232
233 while( $row = $this->db->fetchAssoc($res) )
234 {
235 $this->postponedQuestions[ $row['question_fi'] ] = $row['cnt'];
236 }
237 }
238
239 private function loadCheckedQuestions()
240 {
241 $res = $this->db->queryF("SELECT question_fi FROM tst_seq_qst_checked WHERE active_fi = %s AND pass = %s",
242 array('integer','integer'), array($this->getActiveId(), 0)
243 );
244
245 while( $row = $this->db->fetchAssoc($res) )
246 {
247 $this->alreadyCheckedQuestions[ $row['question_fi'] ] = $row['question_fi'];
248 }
249 }
250
251 public function saveToDb()
252 {
253 $this->db->manipulateF(
254 "DELETE FROM tst_sequence WHERE active_fi = %s AND pass = %s",
255 array('integer','integer'), array($this->getActiveId(), 0)
256 );
257
258 $this->db->insert('tst_sequence', array(
259 'active_fi' => array('integer', $this->getActiveId()),
260 'pass' => array('integer', 0),
261 'sequence' => array('clob', null),
262 'postponed' => array('text', null),
263 'hidden' => array('text', null),
264 'tstamp' => array('integer', time())
265 ));
266
273 }
274
275 private function saveNewlyTrackedQuestion()
276 {
277 if( (int)$this->newlyTrackedQuestion )
278 {
279 $newOrderIndex = $this->getNewOrderIndexForQuestionTracking();
280
281 $this->db->replace('tst_seq_qst_tracking',
282 array(
283 'active_fi' => array('integer', (int)$this->getActiveId()),
284 'pass' => array('integer', 0),
285 'question_fi' => array('integer', (int)$this->newlyTrackedQuestion)
286 ),
287 array(
288 'status' => array('text', $this->newlyTrackedQuestionsStatus),
289 'orderindex' => array('integer', $newOrderIndex)
290 )
291 );
292 }
293 }
294
296 {
297 $query = "
298 SELECT (MAX(orderindex) + 1) new_order_index
299 FROM tst_seq_qst_tracking
300 WHERE active_fi = %s
301 AND pass = %s
302 ";
303
304 $res = $this->db->queryF($query, array('integer','integer'), array($this->getActiveId(), 0));
305
306 $row = $this->db->fetchAssoc($res);
307
308 if( $row['new_order_index'] )
309 {
310 return $row['new_order_index'];
311 }
312
313 return 1;
314 }
315
317 {
318 if( (int)$this->newlyAnsweredQuestion )
319 {
320 $this->db->replace('tst_seq_qst_answstatus',
321 array(
322 'active_fi' => array('integer', (int)$this->getActiveId()),
323 'pass' => array('integer', 0),
324 'question_fi' => array('integer', (int)$this->newlyAnsweredQuestion)
325 ),
326 array(
327 'correctness' => array('integer', (int)$this->newlyAnsweredQuestionsAnswerStatus)
328 )
329 );
330 }
331 }
332
333 private function saveNewlyPostponedQuestion()
334 {
335 if( (int)$this->newlyPostponedQuestion )
336 {
337 $this->db->replace('tst_seq_qst_postponed',
338 array(
339 'active_fi' => array('integer', (int)$this->getActiveId()),
340 'pass' => array('integer', 0),
341 'question_fi' => array('integer', (int)$this->newlyPostponedQuestion)
342 ),
343 array(
344 'cnt' => array('integer', (int)$this->newlyPostponedQuestionsCount)
345 )
346 );
347 }
348 }
349
351 {
352 $INquestions = $this->db->in('question_fi', array_keys($this->postponedQuestions), true, 'integer');
353
354 $query = "
355 DELETE FROM tst_seq_qst_postponed
356 WHERE active_fi = %s
357 AND pass = %s
358 AND $INquestions
359 ";
360
361 $this->db->manipulateF($query, array('integer','integer'), array($this->getActiveId(), 0));
362 }
363
364 private function saveNewlyCheckedQuestion()
365 {
366 if( (int)$this->newlyCheckedQuestion )
367 {
368 $this->db->replace('tst_seq_qst_checked', array(
369 'active_fi' => array('integer', (int)$this->getActiveId()),
370 'pass' => array('integer', 0),
371 'question_fi' => array('integer', (int)$this->newlyCheckedQuestion)
372 ), array());
373 }
374 }
375
377 {
378 $NOT_IN_checkedQuestions = $this->db->in('question_fi', $this->alreadyCheckedQuestions, true, 'integer');
379
380 // BEGIN: FIX IN QUERY
381 if($NOT_IN_checkedQuestions == ' 1=2 ') $NOT_IN_checkedQuestions = ' 1=1 ';
382 // END: FIX IN QUERY
383
384 $query = "
385 DELETE FROM tst_seq_qst_checked
386 WHERE active_fi = %s
387 AND pass = %s
388 AND $NOT_IN_checkedQuestions
389 ";
390
391 $this->db->manipulateF($query, array('integer', 'integer'), array((int)$this->getActiveId(), 0));
392 }
393
394 public function loadQuestions(ilObjTestDynamicQuestionSetConfig $dynamicQuestionSetConfig, ilTestDynamicQuestionSetFilterSelection $filterSelection)
395 {
396 $this->questionSet->load($dynamicQuestionSetConfig, $filterSelection);
397
398// echo "<table><tr>";
399// echo "<td width='200'><pre>".print_r($this->questionSet->getActualQuestionSequence(), 1)."</pre></td>";
400// echo "<td width='200'><pre>".print_r($this->correctAnsweredQuestions, 1)."</pre></td>";
401// echo "<td width='200'><pre>".print_r($this->wrongAnsweredQuestions, 1)."</pre></td>";
402// echo "</tr></table>";
403 }
404
405 // -----------------------------------------------------------------------------------------------------------------
406
408 {
409 switch( true )
410 {
411 case !$this->questionSet->questionExists($testSession->getCurrentQuestionId()):
412 case !$this->isFilteredQuestion($testSession->getCurrentQuestionId()):
413
414 $testSession->setCurrentQuestionId(null);
415 }
416
417 foreach($this->postponedQuestions as $questionId)
418 {
419 if( !$this->questionSet->questionExists($questionId) )
420 {
421 unset($this->postponedQuestions[$questionId]);
422 }
423 }
424
425 foreach($this->wrongAnsweredQuestions as $questionId)
426 {
427 if( !$this->questionSet->questionExists($questionId) )
428 {
429 unset($this->wrongAnsweredQuestions[$questionId]);
430 }
431 }
432
433 foreach($this->correctAnsweredQuestions as $questionId)
434 {
435 if( !$this->questionSet->questionExists($questionId) )
436 {
437 unset($this->correctAnsweredQuestions[$questionId]);
438 }
439 }
440 }
441
442 // -----------------------------------------------------------------------------------------------------------------
443
444 public function getUpcomingQuestionId()
445 {
446 if( $questionId = $this->fetchUpcomingQuestionId(true, true) )
447 return $questionId;
448
449 if( $questionId = $this->fetchUpcomingQuestionId(false, true) )
450 return $questionId;
451
452 if( $questionId = $this->fetchUpcomingQuestionId(true, false) )
453 return $questionId;
454
455 if( $questionId = $this->fetchUpcomingQuestionId(false, false) )
456 return $questionId;
457
458 return null;
459 }
460
461 private function fetchUpcomingQuestionId($excludePostponedQuestions, $forceNonAnsweredQuestion)
462 {
463 foreach($this->questionSet->getActualQuestionSequence() as $level => $questions)
464 {
465 $postponedQuestions = array();
466
467 foreach($questions as $pos => $qId)
468 {
469 if( isset($this->correctAnsweredQuestions[$qId]) )
470 {
471 continue;
472 }
473
475 {
476 continue;
477 }
478
479 if( $forceNonAnsweredQuestion && isset($this->wrongAnsweredQuestions[$qId]) )
480 {
481 continue;
482 }
483
484 if( isset($this->postponedQuestions[$qId]) )
485 {
486 $postponedQuestions[$qId] = $this->postponedQuestions[$qId];
487 continue;
488 }
489
490 return $qId;
491 }
492
493 if( !$excludePostponedQuestions && count($postponedQuestions) )
494 {
496 }
497 }
498
499 return null;
500 }
501
502 public function isAnsweredQuestion($questionId)
503 {
504 return (
505 isset($this->correctAnsweredQuestions[$questionId])
506 || isset($this->wrongAnsweredQuestions[$questionId])
507 );
508 }
509
510 public function isPostponedQuestion($questionId)
511 {
512 return isset($this->postponedQuestions[$questionId]);
513 }
514
515 public function isFilteredQuestion($questionId)
516 {
517 foreach($this->questionSet->getActualQuestionSequence() as $level => $questions)
518 {
519 if( in_array($questionId, $questions) )
520 {
521 return true;
522 }
523 }
524
525 return false;
526 }
527
528 public function trackedQuestionExists()
529 {
530 return (bool)count($this->questionTracking);
531 }
532
534 {
535 $questionList = array();
536
538 {
539 $questionList[$currentQuestionId] = $this->questionSet->getQuestionData($currentQuestionId);
540 }
541
542 foreach( array_reverse($this->questionTracking) as $trackedQuestion)
543 {
544 if( !isset($questionList[ $trackedQuestion['qid'] ]) )
545 {
546 $questionList[ $trackedQuestion['qid'] ] = $this->questionSet->getQuestionData($trackedQuestion['qid']);
547 }
548 }
549
550 return $questionList;
551 }
552
553 public function resetTrackedQuestionList()
554 {
555 $this->questionTracking = array();
556 }
557
558 public function openQuestionExists()
559 {
560 return count($this->getOpenQuestions()) > 0;
561 }
562
563 public function getOpenQuestions()
564 {
565 $completeQuestionIds = array_keys( $this->questionSet->getAllQuestionsData() );
566
567 $openQuestions = array_diff($completeQuestionIds, $this->correctAnsweredQuestions);
568
569 return $openQuestions;
570 }
571
572 public function getTrackedQuestionCount()
573 {
574 $uniqueQuestions = array();
575
576 foreach($this->questionTracking as $trackedQuestion)
577 {
578 $uniqueQuestions[$trackedQuestion['qid']] = $trackedQuestion['qid'];
579 }
580
581 return count($uniqueQuestions);
582 }
583
584 public function getCurrentPositionIndex($questionId)
585 {
586 $i = 0;
587
588 foreach($this->questionSet->getActualQuestionSequence() as $level => $questions)
589 {
590 foreach($questions as $pos => $qId)
591 {
592 $i++;
593
594 if($qId == $questionId)
595 {
596 return $i;
597 }
598 }
599 }
600
601 return null;
602 }
603
604 public function getLastPositionIndex()
605 {
606 $count = 0;
607
608 foreach($this->questionSet->getActualQuestionSequence() as $level => $questions)
609 {
610 $count += count($questions);
611 }
612
613 return $count;
614 }
615
616 // -----------------------------------------------------------------------------------------------------------------
617
618 public function setQuestionUnchecked($questionId)
619 {
620 unset($this->alreadyCheckedQuestions[$questionId]);
621 }
622
623 public function setQuestionChecked($questionId)
624 {
625 $this->newlyCheckedQuestion = $questionId;
626 $this->alreadyCheckedQuestions[$questionId] = $questionId;
627 }
628
629 public function isQuestionChecked($questionId)
630 {
631 return isset($this->alreadyCheckedQuestions[$questionId]);
632 }
633
634 public function setQuestionPostponed($questionId)
635 {
636 $this->trackQuestion($questionId, 'postponed');
637
638 if( !isset($this->postponedQuestions[$questionId]) )
639 {
640 $this->postponedQuestions[$questionId] = 0;
641 }
642
643 $this->postponedQuestions[$questionId]++;
644
645 $this->newlyPostponedQuestion = $questionId;
646 $this->newlyPostponedQuestionsCount = $this->postponedQuestions[$questionId];
647 }
648
649 public function unsetQuestionPostponed($questionId)
650 {
651 if( isset($this->postponedQuestions[$questionId]) )
652 unset($this->postponedQuestions[$questionId]);
653 }
654
655 public function setQuestionAnsweredCorrect($questionId)
656 {
657 $this->trackQuestion($questionId, 'correct');
658
659 $this->correctAnsweredQuestions[$questionId] = $questionId;
660
661 if( isset($this->wrongAnsweredQuestions[$questionId]) )
662 unset($this->wrongAnsweredQuestions[$questionId]);
663
664 $this->newlyAnsweredQuestion = $questionId;
665 $this->newlyAnsweredQuestionsAnswerStatus = true;
666 }
667
668 public function setQuestionAnsweredWrong($questionId)
669 {
670 $this->trackQuestion($questionId, 'wrong');
671
672 $this->wrongAnsweredQuestions[$questionId] = $questionId;
673
674 if( isset($this->correctAnsweredQuestions[$questionId]) )
675 unset($this->correctAnsweredQuestions[$questionId]);
676
677 $this->newlyAnsweredQuestion = $questionId;
678 $this->newlyAnsweredQuestionsAnswerStatus = false;
679 }
680
681 private function trackQuestion($questionId, $answerStatus)
682 {
683 $this->questionTracking[] = array(
684 'qid' => $questionId, 'status' => $answerStatus
685 );
686
687 $this->newlyTrackedQuestion = $questionId;
688 $this->newlyTrackedQuestionsStatus = $answerStatus;
689 }
690
691 // -----------------------------------------------------------------------------------------------------------------
692
693 public function hasStarted()
694 {
695 return $this->trackedQuestionExists();
696 }
697
698 // -----------------------------------------------------------------------------------------------------------------
699
700 public function getCompleteQuestionsData()
701 {
702 return $this->questionSet->getCompleteQuestionList()->getQuestionDataArray();
703 }
704
705 public function getFilteredQuestionsData()
706 {
707 return $this->questionSet->getFilteredQuestionList()->getQuestionDataArray();
708 }
709
710 // -----------------------------------------------------------------------------------------------------------------
711
712 public function getUserSequenceQuestions()
713 {
714 //return array_keys( $this->getTrackedQuestionList() );
715
716 $questionSequence = array();
717
718 foreach( $this->questionSet->getActualQuestionSequence() as $level => $questions )
719 {
720 $questionSequence = array_merge($questionSequence, $questions);
721 }
722
723 return $questionSequence;
724 }
725
731 {
732 $minPostponeCount = null;
733 $minPostponeItem = null;
734
735 foreach(array_reverse($postponedQuestions, true) as $qId => $postponeCount)
736 {
737 if($minPostponeCount === null || $postponeCount <= $minPostponeCount)
738 {
739 $minPostponeCount = $postponeCount;
740 $minPostponeItem = $qId;
741 }
742 }
743 return $minPostponeItem;
744 }
745
746 public function getPass()
747 {
748 return 0;
749 }
750
751 // -----------------------------------------------------------------------------------------------------------------
752
754 {
755 $maxPostponeCount = max($postponedQuestions);
756
757 $orderedSequence = array();
758 $postponedCountDomain = array_flip($postponedQuestions);
759
760 for($i = 1; $i <= $maxPostponeCount; $i++)
761 {
762 if(!isset($postponedCountDomain[$i]))
763 {
764 continue;
765 }
766
767 foreach($postponedQuestions as $qId => $postponeCount)
768 {
769 if($postponeCount == $i)
770 {
771 $orderedSequence[] = $qId;
772 }
773 }
774 }
775
776 return $orderedSequence;
777 }
778
779 private function fetchQuestionSequence($nonPostponedQuestions, $nonAnsweredQuestions, $excludeQuestionId)
780 {
781 $questionSequence = array();
782
783 foreach($this->questionSet->getActualQuestionSequence() as $level => $questions)
784 {
785 $postponedQuestions = array();
786
787 foreach($questions as $pos => $qId)
788 {
789 if( $qId == $excludeQuestionId )
790 {
791 continue;
792 }
793
794 if( isset($this->correctAnsweredQuestions[$qId]) )
795 {
796 continue;
797 }
798
799 if( $nonAnsweredQuestions && isset($this->wrongAnsweredQuestions[$qId]) )
800 {
801 continue;
802 }
803 elseif( !$nonAnsweredQuestions && !isset($this->wrongAnsweredQuestions[$qId]) )
804 {
805 continue;
806 }
807
808 if( !$nonPostponedQuestions && isset($this->postponedQuestions[$qId]) )
809 {
810 $postponedQuestions[$qId] = $this->postponedQuestions[$qId];
811 continue;
812 }
813 elseif($nonPostponedQuestions && !isset($this->postponedQuestions[$qId]))
814 {
815 $questionSequence[] = $qId;
816 }
817 }
818
819 if( !$nonPostponedQuestions && count($postponedQuestions) )
820 {
821 $questionSequence = array_merge(
822 $questionSequence, $this->orderQuestionsByPostponeCount($postponedQuestions)
823 );
824 }
825 }
826
827 return $questionSequence;
828 }
829
830 private function fetchTrackedCorrectAnsweredSequence($excludeQuestionId)
831 {
832 $questionSequence = array();
833
834 foreach($this->questionTracking as $key => $question)
835 {
836 $qId = $question['qid'];
837
838 if($qId == $excludeQuestionId)
839 {
840 continue;
841 }
842
843 if( !isset($this->correctAnsweredQuestions[$qId]) )
844 {
845 continue;
846 }
847
848 $questionSequence[] = $qId;
849 }
850
851 return $questionSequence;
852 }
853
854 private function getOrderedSequence()
855 {
857 $this->getCurrentQuestionId()
858 );
859
860 $nonAnsweredQuestions = $this->fetchQuestionSequence(
861 true, true, $this->getCurrentQuestionId()
862 );
863
864 $postponedNonAnsweredQuestions = $this->fetchQuestionSequence(
865 false, true, $this->getCurrentQuestionId()
866 );
867
869 true, false, $this->getCurrentQuestionId()
870 );
871
872 $postponedWrongAnsweredQuestions = $this->fetchQuestionSequence(
873 false, false, $this->getCurrentQuestionId()
874 );
875
876 $questionOrder = array_merge(
878 $nonAnsweredQuestions, $postponedNonAnsweredQuestions,
879 $wrongAnsweredQuestions, $postponedWrongAnsweredQuestions
880 );
881
882 return $questionOrder;
883 }
884
885 public function getSequenceSummary($obligationsFilterEnabled = false)
886 {
887 $questionOrder = $this->getOrderedSequence();
888
889 $solved_questions = ilObjTest::_getSolvedQuestions($this->getActiveId());
890
891 $key = 1;
892
893 foreach ($questionOrder as $qId)
894 {
895 $question =& ilObjTest::_instanciateQuestion($qId);
896 if(is_object($question))
897 {
898 $worked_through = $question->_isWorkedThrough($this->getActiveId(), $question->getId(), $this->getPass());
899 $solved = 0;
900 if(array_key_exists($question->getId(), $solved_questions))
901 {
902 $solved = $solved_questions[$question->getId()]["solved"];
903 }
904
905 // do not show postponing, since this happens implicit on dircarding solutions (CTM only)
906 //$is_postponed = $this->isPostponedQuestion($question->getId());
907
908 $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()));
909
910 if(!$obligationsFilterEnabled || $row['obligatory'])
911 {
912 $summary[] = $row;
913 }
914
915 $key++;
916 }
917 }
918
919 return $summary;
920 }
921
923 {
924 $filteredQuestions = $this->questionSet->getFilteredQuestionList()->getQuestionDataArray();
925
926 foreach($filteredQuestions as $filteredQuestion)
927 {
928 if( $this->isQuestionChecked($filteredQuestion['question_id']) )
929 {
930 return true;
931 }
932 }
933
934 return false;
935 }
936
938 {
939 $filteredQuestions = $this->questionSet->getFilteredQuestionList()->getQuestionDataArray();
940
941 foreach($filteredQuestions as $filteredQuestion)
942 {
943 if( $this->isQuestionChecked($filteredQuestion['question_id']) )
944 {
945 $this->setQuestionUnchecked($filteredQuestion['question_id']);
946 }
947 }
948 }
949}
950
Database Wrapper.
Definition: class.ilDB.php:29
_getSolvedQuestions($active_id, $question_fi=null)
get solved questions
& _instanciateQuestion($question_id)
Creates an instance of a question with a given question id.
static isQuestionObligatory($question_id)
checks wether the question with given id is marked as obligatory or not
setPreventCheckedQuestionsFromComingUpEnabled($preventCheckedQuestionsFromComingUpEnabled)
loadQuestions(ilObjTestDynamicQuestionSetConfig $dynamicQuestionSetConfig, ilTestDynamicQuestionSetFilterSelection $filterSelection)
__construct(ilDB $db, ilTestDynamicQuestionSet $questionSet, $activeId)
Constructor.
fetchUpcomingQuestionId($excludePostponedQuestions, $forceNonAnsweredQuestion)
cleanupQuestions(ilTestSessionDynamicQuestionSet $testSession)
fetchQuestionSequence($nonPostponedQuestions, $nonAnsweredQuestions, $excludeQuestionId)