ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
class.ilTestSequence.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.ilTestQuestionSequence.php';
5require_once 'Modules/Test/interfaces/interface.ilTestSequenceSummaryProvider.php';
6
17{
24
31
38
44 var $pass;
45
52
57
62
67
72
77
82
92 public function __construct($active_id, $pass, $randomtest)
93 {
94 $this->active_id = $active_id;
95 $this->pass = $pass;
96 $this->isRandomTest = $randomtest;
97 $this->sequencedata = array(
98 "sequence" => array(),
99 "postponed" => array(),
100 "hidden" => array()
101 );
102
103 $this->alreadyCheckedQuestions = array();
104 $this->newlyCheckedQuestion = null;
105
106 $this->optionalQuestions = array();
107 $this->answeringOptionalQuestionsConfirmed = false;
108
109 $this->considerHiddenQuestionsEnabled = false;
110 $this->considerOptionalQuestionsEnabled = true;
111 }
112
113 function getActiveId()
114 {
115 return $this->active_id;
116 }
117
118 function createNewSequence($max, $shuffle)
119 {
120 $newsequence = array();
121 if ($max > 0)
122 {
123 for ($i = 1; $i <= $max; $i++)
124 {
125 array_push($newsequence, $i);
126 }
127 if ($shuffle) $newsequence = $this->pcArrayShuffle($newsequence);
128 }
129 $this->sequencedata["sequence"] = $newsequence;
130 }
131
135 public function loadQuestions(ilTestQuestionSetConfig $testQuestionSetConfig = null, $taxonomyFilterSelection = array())
136 {
137 global $ilDB;
138
139 $this->questions = array();
140
141 $result = $ilDB->queryF("SELECT tst_test_question.* FROM tst_test_question, qpl_questions, tst_active WHERE tst_active.active_id = %s AND tst_test_question.test_fi = tst_active.test_fi AND qpl_questions.question_id = tst_test_question.question_fi ORDER BY tst_test_question.sequence",
142 array('integer'),
143 array($this->active_id)
144 );
145
146 $index = 1;
147
148 // TODO bheyser: There might be "sequence" gaps which lead to issues with tst_sequence when deleting/adding questions before any participant starts the test
149 while ($data = $ilDB->fetchAssoc($result))
150 {
151 $this->questions[$index++] = $data["question_fi"];
152 }
153 }
154
160 public function loadFromDb()
161 {
162 $this->loadQuestionSequence();
163 $this->loadCheckedQuestions();
164 $this->loadOptionalQuestions();
165 }
166
167 private function loadQuestionSequence()
168 {
169 global $ilDB;
170 $result = $ilDB->queryF("SELECT * FROM tst_sequence WHERE active_fi = %s AND pass = %s",
171 array('integer','integer'),
172 array($this->active_id, $this->pass)
173 );
174 if ($result->numRows())
175 {
176 $row = $ilDB->fetchAssoc($result);
177 $this->sequencedata = array(
178 "sequence" => unserialize($row["sequence"]),
179 "postponed" => unserialize($row["postponed"]),
180 "hidden" => unserialize($row["hidden"])
181 );
182 if (!is_array($this->sequencedata["sequence"])) $this->sequencedata["sequence"] = array();
183 if (!is_array($this->sequencedata["postponed"])) $this->sequencedata["postponed"] = array();
184 if (!is_array($this->sequencedata["hidden"])) $this->sequencedata["hidden"] = array();
185
186 $this->setAnsweringOptionalQuestionsConfirmed((bool)$row['ans_opt_confirmed']);
187 }
188 }
189
190 private function loadCheckedQuestions()
191 {
192 global $ilDB;
193
194 $res = $ilDB->queryF("SELECT question_fi FROM tst_seq_qst_checked WHERE active_fi = %s AND pass = %s",
195 array('integer','integer'), array($this->active_id, $this->pass)
196 );
197
198 while( $row = $ilDB->fetchAssoc($res) )
199 {
200 $this->alreadyCheckedQuestions[ $row['question_fi'] ] = $row['question_fi'];
201 }
202 }
203
204 private function loadOptionalQuestions()
205 {
206 global $ilDB;
207
208 $res = $ilDB->queryF("SELECT question_fi FROM tst_seq_qst_optional WHERE active_fi = %s AND pass = %s",
209 array('integer','integer'), array($this->active_id, $this->pass)
210 );
211
212 while( $row = $ilDB->fetchAssoc($res) )
213 {
214 $this->optionalQuestions[ $row['question_fi'] ] = $row['question_fi'];
215 }
216 }
217
223 public function saveToDb()
224 {
225 $this->saveQuestionSequence();
227 $this->saveOptionalQuestions();
228 }
229
230 private function saveQuestionSequence()
231 {
232 global $ilDB;
233
234 $postponed = NULL;
235 if ((is_array($this->sequencedata["postponed"])) && (count($this->sequencedata["postponed"])))
236 {
237 $postponed = serialize($this->sequencedata["postponed"]);
238 }
239 $hidden = NULL;
240 if ((is_array($this->sequencedata["hidden"])) && (count($this->sequencedata["hidden"])))
241 {
242 $hidden = serialize($this->sequencedata["hidden"]);
243 }
244
245 $affectedRows = $ilDB->manipulateF("DELETE FROM tst_sequence WHERE active_fi = %s AND pass = %s",
246 array('integer','integer'),
247 array($this->active_id, $this->pass)
248 );
249
250 $affectedRows = $ilDB->insert("tst_sequence", array(
251 "active_fi" => array("integer", $this->active_id),
252 "pass" => array("integer", $this->pass),
253 "sequence" => array("clob", serialize($this->sequencedata["sequence"])),
254 "postponed" => array("text", $postponed),
255 "hidden" => array("text", $hidden),
256 "tstamp" => array("integer", time()),
257 'ans_opt_confirmed' => array('integer', (int)$this->isAnsweringOptionalQuestionsConfirmed())
258 ));
259 }
260
264 private function saveNewlyCheckedQuestion()
265 {
266 if( (int)$this->newlyCheckedQuestion )
267 {
268 global $ilDB;
269
270 $ilDB->replace('tst_seq_qst_checked', array(
271 'active_fi' => array('integer', (int)$this->active_id),
272 'pass' => array('integer', (int)$this->pass),
273 'question_fi' => array('integer', (int)$this->newlyCheckedQuestion)
274 ), array());
275 }
276 }
277
281 private function saveOptionalQuestions()
282 {
283 global $ilDB;
284
285 $NOT_IN_questions = $ilDB->in('question_fi', $this->optionalQuestions, true, 'integer');
286
287 $ilDB->queryF(
288 "DELETE FROM tst_seq_qst_optional WHERE active_fi = %s AND pass = %s AND $NOT_IN_questions",
289 array('integer', 'integer'), array($this->active_id, $this->pass)
290 );
291
292 foreach($this->optionalQuestions as $questionId)
293 {
294 $ilDB->replace('tst_seq_qst_optional', array(
295 'active_fi' => array('integer', (int)$this->active_id),
296 'pass' => array('integer', (int)$this->pass),
297 'question_fi' => array('integer', (int)$questionId)
298 ), array());
299 }
300 }
301
302 function postponeQuestion($question_id)
303 {
304 if (!$this->isPostponedQuestion($question_id))
305 {
306 array_push($this->sequencedata["postponed"], intval($question_id));
307 }
308 }
309
310 function hideQuestion($question_id)
311 {
312 if (!$this->isHiddenQuestion($question_id))
313 {
314 array_push($this->sequencedata["hidden"], intval($question_id));
315 }
316 }
317
318 function isPostponedQuestion($question_id)
319 {
320 if (!is_array($this->sequencedata["postponed"])) return FALSE;
321 if (!in_array($question_id, $this->sequencedata["postponed"]))
322 {
323 return FALSE;
324 }
325 else
326 {
327 return TRUE;
328 }
329 }
330
331 function isHiddenQuestion($question_id)
332 {
333 if (!is_array($this->sequencedata["hidden"])) return FALSE;
334 if (!in_array($question_id, $this->sequencedata["hidden"]))
335 {
336 return FALSE;
337 }
338 else
339 {
340 return TRUE;
341 }
342 }
343
344 function isPostponedSequence($sequence)
345 {
346 if (!array_key_exists($sequence, $this->questions)) return FALSE;
347 if (!is_array($this->sequencedata["postponed"])) return FALSE;
348 if (!in_array($this->questions[$sequence], $this->sequencedata["postponed"]))
349 {
350 return FALSE;
351 }
352 else
353 {
354 return TRUE;
355 }
356 }
357
358 function isHiddenSequence($sequence)
359 {
360 if (!array_key_exists($sequence, $this->questions)) return FALSE;
361 if (!is_array($this->sequencedata["hidden"])) return FALSE;
362 if (!in_array($this->questions[$sequence], $this->sequencedata["hidden"]))
363 {
364 return FALSE;
365 }
366 else
367 {
368 return TRUE;
369 }
370 }
371
372 function postponeSequence($sequence)
373 {
374 if (!$this->isPostponedSequence($sequence))
375 {
376 if (array_key_exists($sequence, $this->questions))
377 {
378 if (!is_array($this->sequencedata["postponed"])) $this->sequencedata["postponed"] = array();
379 array_push($this->sequencedata["postponed"], intval($this->questions[$sequence]));
380 }
381 }
382 }
383
384 function hideSequence($sequence)
385 {
386 if (!$this->isHiddenSequence($sequence))
387 {
388 if (array_key_exists($sequence, $this->questions))
389 {
390 if (!is_array($this->sequencedata["hidden"])) $this->sequencedata["hidden"] = array();
391 array_push($this->sequencedata["hidden"], intval($this->questions[$sequence]));
392 }
393 }
394 }
395
396 public function setQuestionChecked($questionId)
397 {
398 $this->newlyCheckedQuestion = $questionId;
399 }
400
401 public function isQuestionChecked($questionId)
402 {
403 return isset($this->alreadyCheckedQuestions[$questionId]);
404 }
405
406 function getPositionOfSequence($sequence)
407 {
408 $correctedsequence = $this->getCorrectedSequence();
409 $sequencekey = array_search($sequence, $correctedsequence);
410 if ($sequencekey !== FALSE)
411 {
412 return $sequencekey + 1;
413 }
414 else
415 {
416 return "";
417 }
418 }
419
421 {
422 return count($this->getCorrectedSequence());
423 }
424
426 {
427 $sequenceKeys = array();
428
429 foreach(array_keys($this->questions) as $sequenceKey)
430 {
431 if( $this->isHiddenSequence($sequenceKey) && !$this->isConsiderHiddenQuestionsEnabled() )
432 {
433 continue;
434 }
435
436 if( $this->isSequenceOptional($sequenceKey) && !$this->isConsiderOptionalQuestionsEnabled() )
437 {
438 continue;
439 }
440
441 $sequenceKeys[] = $sequenceKey;
442 }
443
444 return $sequenceKeys;
445 }
446
448 {
449 $questions = array();
450
451 foreach($this->questions as $questionId)
452 {
453 if( $this->isHiddenQuestion($questionId) && !$this->isConsiderHiddenQuestionsEnabled() )
454 {
455 continue;
456 }
457
458 if( $this->isQuestionOptional($questionId) && !$this->isConsiderOptionalQuestionsEnabled() )
459 {
460 continue;
461 }
462
463 $questions[] = $questionId;
464 }
465
466 return $questions;
467 }
468
470 {
471 return $this->getCorrectedSequence();
472 }
473
475 {
476 $seq = $this->getCorrectedSequence();
477 $found = array();
478 foreach ($seq as $sequence)
479 {
480 array_push($found, $this->getQuestionForSequence($sequence));
481 }
482 return $found;
483 }
484
485 private function ensureQuestionNotInSequence($sequence, $questionId)
486 {
487 $questionKey = array_search($questionId, $this->questions);
488
489 if( $questionKey === false )
490 {
491 return $sequence;
492 }
493
494 $sequenceKey = array_search($questionKey, $sequence);
495
496 if( $sequenceKey === FALSE )
497 {
498 return $sequence;
499 }
500
501 unset($sequence[$sequenceKey]);
502
503 return $sequence;
504 }
505
506 protected function getCorrectedSequence()
507 {
508 $correctedsequence = $this->sequencedata["sequence"];
510 {
511 if (is_array($this->sequencedata["hidden"]))
512 {
513 foreach ($this->sequencedata["hidden"] as $question_id)
514 {
515 $correctedsequence = $this->ensureQuestionNotInSequence($correctedsequence, $question_id);
516 }
517 }
518 }
520 {
521 foreach($this->optionalQuestions as $questionId)
522 {
523 $correctedsequence = $this->ensureQuestionNotInSequence($correctedsequence, $questionId);
524 }
525 }
526 if (is_array($this->sequencedata["postponed"]))
527 {
528 foreach ($this->sequencedata["postponed"] as $question_id)
529 {
530 $foundsequence = array_search($question_id, $this->questions);
531 if ($foundsequence !== FALSE)
532 {
533 $sequencekey = array_search($foundsequence, $correctedsequence);
534 if ($sequencekey !== FALSE)
535 {
536 unset($correctedsequence[$sequencekey]);
537 array_push($correctedsequence, $foundsequence);
538 }
539 }
540 }
541 }
542 return array_values($correctedsequence);
543 }
544
545 function getSequenceForQuestion($question_id)
546 {
547 return array_search($question_id, $this->questions);
548 }
549
551 {
552 $correctedsequence = $this->getCorrectedSequence();
553 if (count($correctedsequence))
554 {
555 return reset($correctedsequence);
556 }
557 else
558 {
559 return FALSE;
560 }
561 }
562
564 {
565 $correctedsequence = $this->getCorrectedSequence();
566 if (count($correctedsequence))
567 {
568 return end($correctedsequence);
569 }
570 else
571 {
572 return FALSE;
573 }
574 }
575
576 function getNextSequence($sequence)
577 {
578 $correctedsequence = $this->getCorrectedSequence();
579 $sequencekey = array_search($sequence, $correctedsequence);
580 if ($sequencekey !== FALSE)
581 {
582 $nextsequencekey = $sequencekey + 1;
583 if (array_key_exists($nextsequencekey, $correctedsequence))
584 {
585 return $correctedsequence[$nextsequencekey];
586 }
587 }
588 return FALSE;
589 }
590
591 function getPreviousSequence($sequence)
592 {
593 $correctedsequence = $this->getCorrectedSequence();
594 $sequencekey = array_search($sequence, $correctedsequence);
595 if ($sequencekey !== FALSE)
596 {
597 $prevsequencekey = $sequencekey - 1;
598 if (($prevsequencekey >= 0) && (array_key_exists($prevsequencekey, $correctedsequence)))
599 {
600 return $correctedsequence[$prevsequencekey];
601 }
602 }
603 return FALSE;
604 }
605
614 function pcArrayShuffle($array)
615 {
616 $keys = array_keys($array);
617 shuffle($keys);
618 $result = array();
619 foreach ($keys as $key)
620 {
621 $result[$key] = $array[$key];
622 }
623 return $result;
624 }
625
626 function getQuestionForSequence($sequence)
627 {
628 if ($sequence < 1) return FALSE;
629 if (array_key_exists($sequence, $this->questions))
630 {
631 return $this->questions[$sequence];
632 }
633 else
634 {
635 return FALSE;
636 }
637 }
638
639 public function getSequenceSummary($obligationsFilterEnabled = false)
640 {
641 $correctedsequence = $this->getCorrectedSequence();
642 $result_array = array();
643 include_once "./Modules/Test/classes/class.ilObjTest.php";
644 $solved_questions = ilObjTest::_getSolvedQuestions($this->active_id);
645 $key = 1;
646 foreach ($correctedsequence as $sequence)
647 {
648 $question =& ilObjTest::_instanciateQuestion($this->getQuestionForSequence($sequence));
649 if (is_object($question))
650 {
651 $worked_through = $question->_isWorkedThrough($this->active_id, $question->getId(), $this->pass);
652 $solved = 0;
653 if (array_key_exists($question->getId(), $solved_questions))
654 {
655 $solved = $solved_questions[$question->getId()]["solved"];
656 }
657 $is_postponed = $this->isPostponedQuestion($question->getId());
658
659 $row = array(
660 "nr" => "$key",
661 "title" => $question->getTitle(),
662 "qid" => $question->getId(),
663 "visited" => $worked_through,
664 "solved" => (($solved)?"1":"0"),
665 "description" => $question->getComment(),
666 "points" => $question->getMaximumPoints(),
667 "worked_through" => $worked_through,
668 "postponed" => $is_postponed,
669 "sequence" => $sequence,
670 "obligatory" => ilObjTest::isQuestionObligatory($question->getId()),
671 'isAnswered' => $question->isAnswered($this->active_id, $this->pass)
672 );
673
674 if( !$obligationsFilterEnabled || $row['obligatory'] )
675 {
676 array_push($result_array, $row);
677 }
678
679 $key++;
680 }
681 }
682 return $result_array;
683 }
684
685 function getPass()
686 {
687 return $this->pass;
688 }
689
690 function setPass($pass)
691 {
692 $this->pass = $pass;
693 }
694
695 function hasSequence()
696 {
697 if ((is_array($this->sequencedata["sequence"])) && (count($this->sequencedata["sequence"]) > 0))
698 {
699 return TRUE;
700 }
701 else
702 {
703 return FALSE;
704 }
705 }
706
708 {
709 if ((is_array($this->sequencedata["hidden"])) && (count($this->sequencedata["hidden"]) > 0))
710 {
711 return TRUE;
712 }
713 else
714 {
715 return FALSE;
716 }
717 }
718
720 {
721 $this->sequencedata["hidden"] = array();
722 }
723
724 private function hideCorrectAnsweredQuestions(ilObjTest $testOBJ, $activeId, $pass)
725 {
726 if( $activeId > 0 )
727 {
728 $result = $testOBJ->getTestResult($activeId, $pass, TRUE);
729
730 foreach( $result as $sequence => $question )
731 {
732 if( is_numeric($sequence) )
733 {
734 if( $question['reached'] == $question['max'] )
735 {
736 $this->hideQuestion($question['qid']);
737 }
738 }
739 }
740
741 $this->saveToDb();
742 }
743 }
744
745 public function hasStarted(ilTestSession $testSession)
746 {
747 if( $testSession->getLastSequence() < 1 )
748 {
749 return false;
750 }
751
752 // WTF ?? heard about tests with only one question !?
753 if( $testSession->getLastSequence() == $this->getFirstSequence() )
754 {
755 return false;
756 }
757
758 return true;
759 }
760
761 public function openQuestionExists()
762 {
763 return $this->getFirstSequence() !== false;
764 }
765
766 public function getQuestionIds()
767 {
768 return array_values($this->questions);
769 }
770
771 public function questionExists($questionId)
772 {
773 return in_array($questionId, $this->questions);
774 }
775
776 public function setQuestionOptional($questionId)
777 {
778 $this->optionalQuestions[$questionId] = $questionId;
779 }
780
781 public function isQuestionOptional($questionId)
782 {
783 return isset($this->optionalQuestions[$questionId]);
784 }
785
786 public function hasOptionalQuestions()
787 {
788 return (bool)count($this->optionalQuestions);
789 }
790
791 public function getOptionalQuestions()
792 {
794 }
795
796 public function clearOptionalQuestions()
797 {
798 $this->optionalQuestions = array();
799 }
800
802 {
803 $optionalSequenceKeys = array();
804
805 foreach($this->sequencedata['sequence'] as $index => $sequenceKey)
806 {
807 if( $this->isQuestionOptional($this->getQuestionForSequence($sequenceKey)) )
808 {
809 $optionalSequenceKeys[$index] = $sequenceKey;
810 unset($this->sequencedata['sequence'][$index]);
811 }
812 }
813
814 foreach($optionalSequenceKeys as $index => $sequenceKey)
815 {
816 $this->sequencedata['sequence'][$index] = $sequenceKey;
817 }
818 }
819
824 {
826 }
827
832 {
833 $this->answeringOptionalQuestionsConfirmed = $answeringOptionalQuestionsConfirmed;
834 }
835
840 {
842 }
843
848 {
849 $this->considerHiddenQuestionsEnabled = $considerHiddenQuestionsEnabled;
850 }
851
856 {
858 }
859
864 {
865 $this->considerOptionalQuestionsEnabled = $considerOptionalQuestionsEnabled;
866 }
867}
868
869?>
$result
An exception for terminatinating execution or to throw for unit testing.
static _instanciateQuestion($question_id)
Creates an instance of a question with a given question id.
& getTestResult($active_id, $pass=NULL, $ordered_sequence=FALSE, $considerHiddenQuestions=true, $considerOptionalQuestions=true)
Calculates the results of a test for a given user and returns an array with all test results.
static _getSolvedQuestions($active_id, $question_fi=null)
get solved questions
static isQuestionObligatory($question_id)
checks wether the question with given id is marked as obligatory or not
Test sequence handler.
questionExists($questionId)
isHiddenQuestion($question_id)
setAnsweringOptionalQuestionsConfirmed($answeringOptionalQuestionsConfirmed)
isPostponedSequence($sequence)
pcArrayShuffle($array)
Shuffles the values of a given array.
getQuestionForSequence($sequence)
hideQuestion($question_id)
getSequenceSummary($obligationsFilterEnabled=false)
setQuestionChecked($questionId)
isQuestionChecked($questionId)
postponeQuestion($question_id)
loadQuestions(ilTestQuestionSetConfig $testQuestionSetConfig=null, $taxonomyFilterSelection=array())
Loads the question mapping.
saveToDb()
Saves the sequence data for a given pass to the database.
saveNewlyCheckedQuestion()
@global ilDBInterface $ilDB
isQuestionOptional($questionId)
saveOptionalQuestions()
@global ilDBInterface $ilDB
hasStarted(ilTestSession $testSession)
__construct($active_id, $pass, $randomtest)
ilTestSequence constructor
hideCorrectAnsweredQuestions(ilObjTest $testOBJ, $activeId, $pass)
getPositionOfSequence($sequence)
createNewSequence($max, $shuffle)
getPreviousSequence($sequence)
setQuestionOptional($questionId)
setConsiderOptionalQuestionsEnabled($considerOptionalQuestionsEnabled)
loadFromDb()
Loads the sequence data for a given active id.
getSequenceForQuestion($question_id)
ensureQuestionNotInSequence($sequence, $questionId)
isPostponedQuestion($question_id)
setConsiderHiddenQuestionsEnabled($considerHiddenQuestionsEnabled)
Test session handler.
global $ilDB