ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
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 
4 require_once 'Modules/Test/interfaces/interface.ilTestQuestionSequence.php';
5 require_once 'Modules/Test/interfaces/interface.ilTestSequenceSummaryProvider.php';
6 
17 {
23  public $sequencedata;
24 
30  public $questions;
31 
37  public $active_id;
38 
44  public $pass;
45 
51  public $isRandomTest;
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  public function getActiveId()
114  {
115  return $this->active_id;
116  }
117 
118  public function createNewSequence($max, $shuffle)
119  {
120  $newsequence = array();
121  if ($max > 0) {
122  for ($i = 1; $i <= $max; $i++) {
123  array_push($newsequence, $i);
124  }
125  if ($shuffle) {
126  $newsequence = $this->pcArrayShuffle($newsequence);
127  }
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(
142  "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",
143  array('integer'),
144  array($this->active_id)
145  );
146 
147  $index = 1;
148 
149  // TODO bheyser: There might be "sequence" gaps which lead to issues with tst_sequence when deleting/adding questions before any participant starts the test
150  while ($data = $ilDB->fetchAssoc($result)) {
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(
171  "SELECT * FROM tst_sequence WHERE active_fi = %s AND pass = %s",
172  array('integer','integer'),
173  array($this->active_id, $this->pass)
174  );
175  if ($result->numRows()) {
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"])) {
183  $this->sequencedata["sequence"] = array();
184  }
185  if (!is_array($this->sequencedata["postponed"])) {
186  $this->sequencedata["postponed"] = array();
187  }
188  if (!is_array($this->sequencedata["hidden"])) {
189  $this->sequencedata["hidden"] = array();
190  }
191 
192  $this->setAnsweringOptionalQuestionsConfirmed((bool) $row['ans_opt_confirmed']);
193  }
194  }
195 
196  private function loadCheckedQuestions()
197  {
198  global $ilDB;
199 
200  $res = $ilDB->queryF(
201  "SELECT question_fi FROM tst_seq_qst_checked WHERE active_fi = %s AND pass = %s",
202  array('integer','integer'),
203  array($this->active_id, $this->pass)
204  );
205 
206  while ($row = $ilDB->fetchAssoc($res)) {
207  $this->alreadyCheckedQuestions[ $row['question_fi'] ] = $row['question_fi'];
208  }
209  }
210 
211  private function loadOptionalQuestions()
212  {
213  global $ilDB;
214 
215  $res = $ilDB->queryF(
216  "SELECT question_fi FROM tst_seq_qst_optional WHERE active_fi = %s AND pass = %s",
217  array('integer','integer'),
218  array($this->active_id, $this->pass)
219  );
220 
221  while ($row = $ilDB->fetchAssoc($res)) {
222  $this->optionalQuestions[ $row['question_fi'] ] = $row['question_fi'];
223  }
224  }
225 
231  public function saveToDb()
232  {
233  $this->saveQuestionSequence();
234  $this->saveNewlyCheckedQuestion();
235  $this->saveOptionalQuestions();
236  }
237 
238  private function saveQuestionSequence()
239  {
240  global $ilDB;
241 
242  $postponed = null;
243  if ((is_array($this->sequencedata["postponed"])) && (count($this->sequencedata["postponed"]))) {
244  $postponed = serialize($this->sequencedata["postponed"]);
245  }
246  $hidden = null;
247  if ((is_array($this->sequencedata["hidden"])) && (count($this->sequencedata["hidden"]))) {
248  $hidden = serialize($this->sequencedata["hidden"]);
249  }
250 
251  $affectedRows = $ilDB->manipulateF(
252  "DELETE FROM tst_sequence WHERE active_fi = %s AND pass = %s",
253  array('integer','integer'),
254  array($this->active_id, $this->pass)
255  );
256 
257  $affectedRows = $ilDB->insert("tst_sequence", array(
258  "active_fi" => array("integer", $this->active_id),
259  "pass" => array("integer", $this->pass),
260  "sequence" => array("clob", serialize($this->sequencedata["sequence"])),
261  "postponed" => array("text", $postponed),
262  "hidden" => array("text", $hidden),
263  "tstamp" => array("integer", time()),
264  'ans_opt_confirmed' => array('integer', (int) $this->isAnsweringOptionalQuestionsConfirmed())
265  ));
266  }
267 
271  private function saveNewlyCheckedQuestion()
272  {
273  if ((int) $this->newlyCheckedQuestion) {
274  global $ilDB;
275 
276  $ilDB->replace('tst_seq_qst_checked', array(
277  'active_fi' => array('integer', (int) $this->active_id),
278  'pass' => array('integer', (int) $this->pass),
279  'question_fi' => array('integer', (int) $this->newlyCheckedQuestion)
280  ), array());
281  }
282  }
283 
287  private function saveOptionalQuestions()
288  {
289  global $ilDB;
290 
291  $NOT_IN_questions = $ilDB->in('question_fi', $this->optionalQuestions, true, 'integer');
292 
293  $ilDB->queryF(
294  "DELETE FROM tst_seq_qst_optional WHERE active_fi = %s AND pass = %s AND $NOT_IN_questions",
295  array('integer', 'integer'),
296  array($this->active_id, $this->pass)
297  );
298 
299  foreach ($this->optionalQuestions as $questionId) {
300  $ilDB->replace('tst_seq_qst_optional', array(
301  'active_fi' => array('integer', (int) $this->active_id),
302  'pass' => array('integer', (int) $this->pass),
303  'question_fi' => array('integer', (int) $questionId)
304  ), array());
305  }
306  }
307 
308  public function postponeQuestion($question_id)
309  {
310  if (!$this->isPostponedQuestion($question_id)) {
311  array_push($this->sequencedata["postponed"], intval($question_id));
312  }
313  }
314 
315  public function hideQuestion($question_id)
316  {
317  if (!$this->isHiddenQuestion($question_id)) {
318  array_push($this->sequencedata["hidden"], intval($question_id));
319  }
320  }
321 
322  public function isPostponedQuestion($question_id)
323  {
324  if (!is_array($this->sequencedata["postponed"])) {
325  return false;
326  }
327  if (!in_array($question_id, $this->sequencedata["postponed"])) {
328  return false;
329  } else {
330  return true;
331  }
332  }
333 
334  public function isHiddenQuestion($question_id)
335  {
336  if (!is_array($this->sequencedata["hidden"])) {
337  return false;
338  }
339  if (!in_array($question_id, $this->sequencedata["hidden"])) {
340  return false;
341  } else {
342  return true;
343  }
344  }
345 
346  public function isPostponedSequence($sequence)
347  {
348  if (!array_key_exists($sequence, $this->questions)) {
349  return false;
350  }
351  if (!is_array($this->sequencedata["postponed"])) {
352  return false;
353  }
354  if (!in_array($this->questions[$sequence], $this->sequencedata["postponed"])) {
355  return false;
356  } else {
357  return true;
358  }
359  }
360 
361  public function isHiddenSequence($sequence)
362  {
363  if (!array_key_exists($sequence, $this->questions)) {
364  return false;
365  }
366  if (!is_array($this->sequencedata["hidden"])) {
367  return false;
368  }
369  if (!in_array($this->questions[$sequence], $this->sequencedata["hidden"])) {
370  return false;
371  } else {
372  return true;
373  }
374  }
375 
376  public function postponeSequence($sequence)
377  {
378  if (!$this->isPostponedSequence($sequence)) {
379  if (array_key_exists($sequence, $this->questions)) {
380  if (!is_array($this->sequencedata["postponed"])) {
381  $this->sequencedata["postponed"] = array();
382  }
383  array_push($this->sequencedata["postponed"], intval($this->questions[$sequence]));
384  }
385  }
386  }
387 
388  public function hideSequence($sequence)
389  {
390  if (!$this->isHiddenSequence($sequence)) {
391  if (array_key_exists($sequence, $this->questions)) {
392  if (!is_array($this->sequencedata["hidden"])) {
393  $this->sequencedata["hidden"] = array();
394  }
395  array_push($this->sequencedata["hidden"], intval($this->questions[$sequence]));
396  }
397  }
398  }
399 
400  public function setQuestionChecked($questionId)
401  {
402  $this->newlyCheckedQuestion = $questionId;
403  }
404 
405  public function isQuestionChecked($questionId)
406  {
407  return isset($this->alreadyCheckedQuestions[$questionId]);
408  }
409 
410  public function getPositionOfSequence($sequence)
411  {
412  $correctedsequence = $this->getCorrectedSequence();
413  $sequencekey = array_search($sequence, $correctedsequence);
414  if ($sequencekey !== false) {
415  return $sequencekey + 1;
416  } else {
417  return "";
418  }
419  }
420 
421  public function getUserQuestionCount()
422  {
423  return count($this->getCorrectedSequence());
424  }
425 
426  public function getOrderedSequence()
427  {
428  $sequenceKeys = array();
429 
430  foreach (array_keys($this->questions) as $sequenceKey) {
431  if ($this->isHiddenSequence($sequenceKey) && !$this->isConsiderHiddenQuestionsEnabled()) {
432  continue;
433  }
434 
435  if ($this->isSequenceOptional($sequenceKey) && !$this->isConsiderOptionalQuestionsEnabled()) {
436  continue;
437  }
438 
439  $sequenceKeys[] = $sequenceKey;
440  }
441 
442  return $sequenceKeys;
443  }
444 
445  public function getOrderedSequenceQuestions()
446  {
447  $questions = array();
448 
449  foreach ($this->questions as $questionId) {
450  if ($this->isHiddenQuestion($questionId) && !$this->isConsiderHiddenQuestionsEnabled()) {
451  continue;
452  }
453 
454  if ($this->isQuestionOptional($questionId) && !$this->isConsiderOptionalQuestionsEnabled()) {
455  continue;
456  }
457 
458  $questions[] = $questionId;
459  }
460 
461  return $questions;
462  }
463 
464  public function getUserSequence()
465  {
466  return $this->getCorrectedSequence();
467  }
468 
469  public function getUserSequenceQuestions()
470  {
471  $seq = $this->getCorrectedSequence();
472  $found = array();
473  foreach ($seq as $sequence) {
474  array_push($found, $this->getQuestionForSequence($sequence));
475  }
476  return $found;
477  }
478 
479  private function ensureQuestionNotInSequence($sequence, $questionId)
480  {
481  $questionKey = array_search($questionId, $this->questions);
482 
483  if ($questionKey === false) {
484  return $sequence;
485  }
486 
487  $sequenceKey = array_search($questionKey, $sequence);
488 
489  if ($sequenceKey === false) {
490  return $sequence;
491  }
492 
493  unset($sequence[$sequenceKey]);
494 
495  return $sequence;
496  }
497 
498  protected function getCorrectedSequence()
499  {
500  $correctedsequence = $this->sequencedata["sequence"];
501  if (!$this->isConsiderHiddenQuestionsEnabled()) {
502  if (is_array($this->sequencedata["hidden"])) {
503  foreach ($this->sequencedata["hidden"] as $question_id) {
504  $correctedsequence = $this->ensureQuestionNotInSequence($correctedsequence, $question_id);
505  }
506  }
507  }
508  if (!$this->isConsiderOptionalQuestionsEnabled()) {
509  foreach ($this->optionalQuestions as $questionId) {
510  $correctedsequence = $this->ensureQuestionNotInSequence($correctedsequence, $questionId);
511  }
512  }
513  if (is_array($this->sequencedata["postponed"])) {
514  foreach ($this->sequencedata["postponed"] as $question_id) {
515  $foundsequence = array_search($question_id, $this->questions);
516  if ($foundsequence !== false) {
517  $sequencekey = array_search($foundsequence, $correctedsequence);
518  if ($sequencekey !== false) {
519  unset($correctedsequence[$sequencekey]);
520  array_push($correctedsequence, $foundsequence);
521  }
522  }
523  }
524  }
525  return array_values($correctedsequence);
526  }
527 
528  public function getSequenceForQuestion($question_id)
529  {
530  return array_search($question_id, $this->questions);
531  }
532 
533  public function getFirstSequence()
534  {
535  $correctedsequence = $this->getCorrectedSequence();
536  if (count($correctedsequence)) {
537  return reset($correctedsequence);
538  } else {
539  return false;
540  }
541  }
542 
543  public function getLastSequence()
544  {
545  $correctedsequence = $this->getCorrectedSequence();
546  if (count($correctedsequence)) {
547  return end($correctedsequence);
548  } else {
549  return false;
550  }
551  }
552 
553  public function getNextSequence($sequence)
554  {
555  $correctedsequence = $this->getCorrectedSequence();
556  $sequencekey = array_search($sequence, $correctedsequence);
557  if ($sequencekey !== false) {
558  $nextsequencekey = $sequencekey + 1;
559  if (array_key_exists($nextsequencekey, $correctedsequence)) {
560  return $correctedsequence[$nextsequencekey];
561  }
562  }
563  return false;
564  }
565 
566  public function getPreviousSequence($sequence)
567  {
568  $correctedsequence = $this->getCorrectedSequence();
569  $sequencekey = array_search($sequence, $correctedsequence);
570  if ($sequencekey !== false) {
571  $prevsequencekey = $sequencekey - 1;
572  if (($prevsequencekey >= 0) && (array_key_exists($prevsequencekey, $correctedsequence))) {
573  return $correctedsequence[$prevsequencekey];
574  }
575  }
576  return false;
577  }
578 
587  public function pcArrayShuffle($array)
588  {
589  $keys = array_keys($array);
590  shuffle($keys);
591  $result = array();
592  foreach ($keys as $key) {
593  $result[$key] = $array[$key];
594  }
595  return $result;
596  }
597 
598  public function getQuestionForSequence($sequence)
599  {
600  if ($sequence < 1) {
601  return false;
602  }
603  if (array_key_exists($sequence, $this->questions)) {
604  return $this->questions[$sequence];
605  } else {
606  return false;
607  }
608  }
609 
610  public function getSequenceSummary($obligationsFilterEnabled = false)
611  {
612  $correctedsequence = $this->getCorrectedSequence();
613  $result_array = array();
614  include_once "./Modules/Test/classes/class.ilObjTest.php";
615  $solved_questions = ilObjTest::_getSolvedQuestions($this->active_id);
616  $key = 1;
617  foreach ($correctedsequence as $sequence) {
618  $question =&ilObjTest::_instanciateQuestion($this->getQuestionForSequence($sequence));
619  if (is_object($question)) {
620  $worked_through = $question->_isWorkedThrough($this->active_id, $question->getId(), $this->pass);
621  $solved = 0;
622  if (array_key_exists($question->getId(), $solved_questions)) {
623  $solved = $solved_questions[$question->getId()]["solved"];
624  }
625  $is_postponed = $this->isPostponedQuestion($question->getId());
626 
627  $row = array(
628  "nr" => "$key",
629  "title" => $question->getTitle(),
630  "qid" => $question->getId(),
631  "visited" => $worked_through,
632  "solved" => (($solved)?"1":"0"),
633  "description" => $question->getComment(),
634  "points" => $question->getMaximumPoints(),
635  "worked_through" => $worked_through,
636  "postponed" => $is_postponed,
637  "sequence" => $sequence,
638  "obligatory" => ilObjTest::isQuestionObligatory($question->getId()),
639  'isAnswered' => $question->isAnswered($this->active_id, $this->pass)
640  );
641 
642  if (!$obligationsFilterEnabled || $row['obligatory']) {
643  array_push($result_array, $row);
644  }
645 
646  $key++;
647  }
648  }
649  return $result_array;
650  }
651 
652  public function getPass()
653  {
654  return $this->pass;
655  }
656 
657  public function setPass($pass)
658  {
659  $this->pass = $pass;
660  }
661 
662  public function hasSequence()
663  {
664  if ((is_array($this->sequencedata["sequence"])) && (count($this->sequencedata["sequence"]) > 0)) {
665  return true;
666  } else {
667  return false;
668  }
669  }
670 
671  public function hasHiddenQuestions()
672  {
673  if ((is_array($this->sequencedata["hidden"])) && (count($this->sequencedata["hidden"]) > 0)) {
674  return true;
675  } else {
676  return false;
677  }
678  }
679 
680  public function clearHiddenQuestions()
681  {
682  $this->sequencedata["hidden"] = array();
683  }
684 
685  private function hideCorrectAnsweredQuestions(ilObjTest $testOBJ, $activeId, $pass)
686  {
687  if ($activeId > 0) {
688  $result = $testOBJ->getTestResult($activeId, $pass, true);
689 
690  foreach ($result as $sequence => $question) {
691  if (is_numeric($sequence)) {
692  if ($question['reached'] == $question['max']) {
693  $this->hideQuestion($question['qid']);
694  }
695  }
696  }
697 
698  $this->saveToDb();
699  }
700  }
701 
702  public function hasStarted(ilTestSession $testSession)
703  {
704  if ($testSession->getLastSequence() < 1) {
705  return false;
706  }
707 
708  // WTF ?? heard about tests with only one question !?
709  if ($testSession->getLastSequence() == $this->getFirstSequence()) {
710  return false;
711  }
712 
713  return true;
714  }
715 
716  public function openQuestionExists()
717  {
718  return $this->getFirstSequence() !== false;
719  }
720 
721  public function getQuestionIds()
722  {
723  return array_values($this->questions);
724  }
725 
726  public function questionExists($questionId)
727  {
728  return in_array($questionId, $this->questions);
729  }
730 
731  public function setQuestionOptional($questionId)
732  {
733  $this->optionalQuestions[$questionId] = $questionId;
734  }
735 
736  public function isQuestionOptional($questionId)
737  {
738  return isset($this->optionalQuestions[$questionId]);
739  }
740 
741  public function hasOptionalQuestions()
742  {
743  return (bool) count($this->optionalQuestions);
744  }
745 
746  public function getOptionalQuestions()
747  {
749  }
750 
751  public function clearOptionalQuestions()
752  {
753  $this->optionalQuestions = array();
754  }
755 
757  {
758  $optionalSequenceKeys = array();
759 
760  foreach ($this->sequencedata['sequence'] as $index => $sequenceKey) {
761  if ($this->isQuestionOptional($this->getQuestionForSequence($sequenceKey))) {
762  $optionalSequenceKeys[$index] = $sequenceKey;
763  unset($this->sequencedata['sequence'][$index]);
764  }
765  }
766 
767  foreach ($optionalSequenceKeys as $index => $sequenceKey) {
768  $this->sequencedata['sequence'][$index] = $sequenceKey;
769  }
770  }
771 
776  {
778  }
779 
784  {
785  $this->answeringOptionalQuestionsConfirmed = $answeringOptionalQuestionsConfirmed;
786  }
787 
792  {
794  }
795 
800  {
801  $this->considerHiddenQuestionsEnabled = $considerHiddenQuestionsEnabled;
802  }
803 
808  {
810  }
811 
816  {
817  $this->considerOptionalQuestionsEnabled = $considerOptionalQuestionsEnabled;
818  }
819 }
loadFromDb()
Loads the sequence data for a given active id.
isPostponedQuestion($question_id)
loadQuestions(ilTestQuestionSetConfig $testQuestionSetConfig=null, $taxonomyFilterSelection=array())
Loads the question mapping.
$result
setConsiderHiddenQuestionsEnabled($considerHiddenQuestionsEnabled)
getSequenceForQuestion($question_id)
isQuestionChecked($questionId)
isQuestionOptional($questionId)
setAnsweringOptionalQuestionsConfirmed($answeringOptionalQuestionsConfirmed)
$index
Definition: metadata.php:60
$keys
hideQuestion($question_id)
postponeQuestion($question_id)
getQuestionForSequence($sequence)
getPositionOfSequence($sequence)
Test sequence handler.
static _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
saveToDb()
Saves the sequence data for a given pass to the database.
saveOptionalQuestions()
ilDBInterface $ilDB
setConsiderOptionalQuestionsEnabled($considerOptionalQuestionsEnabled)
foreach($_POST as $key=> $value) $res
hasStarted(ilTestSession $testSession)
static _getSolvedQuestions($active_id, $question_fi=null)
get solved questions
Test session handler.
__construct($active_id, $pass, $randomtest)
ilTestSequence constructor
getPreviousSequence($sequence)
Create styles array
The data for the language used.
isHiddenQuestion($question_id)
createNewSequence($max, $shuffle)
setQuestionOptional($questionId)
questionExists($questionId)
saveNewlyCheckedQuestion()
ilDBInterface $ilDB
isPostponedSequence($sequence)
global $ilDB
& 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...
setQuestionChecked($questionId)
$i
Definition: disco.tpl.php:19
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
hideCorrectAnsweredQuestions(ilObjTest $testOBJ, $activeId, $pass)
ensureQuestionNotInSequence($sequence, $questionId)
getSequenceSummary($obligationsFilterEnabled=false)
pcArrayShuffle($array)
Shuffles the values of a given array.
$key
Definition: croninfo.php:18