ILIAS  Release_5_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
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 
15 {
19  private $db = null;
20 
24  private $questionSet = null;
25 
29  private $activeId = null;
30 
35 
39  private $questionTracking = array();
40 
45 
50 
54  private $postponedQuestions = array();
55 
60 
65 
70 
75 
79  private $correctAnsweredQuestions = array();
80 
84  private $wrongAnsweredQuestions = array();
85 
90 
95 
102  {
103  $this->db = $db;
104  $this->questionSet = $questionSet;
105  $this->activeId = $activeId;
106 
107  $this->newlyTrackedQuestion = null;
108  $this->newlyTrackedQuestionsStatus = null;
109 
110  $this->newlyPostponedQuestion = null;
111  $this->newlyPostponedQuestionsCount = null;
112 
113  $this->newlyAnsweredQuestion = null;
114  $this->newlyAnsweredQuestionsAnswerStatus = null;
115 
116  $this->alreadyCheckedQuestions = array();
117  $this->newlyCheckedQuestion = null;
118 
119  $this->preventCheckedQuestionsFromComingUpEnabled = false;
120  }
121 
122  public function getActiveId()
123  {
124  return $this->activeId;
125  }
126 
128  {
129  $this->preventCheckedQuestionsFromComingUpEnabled = $preventCheckedQuestionsFromComingUpEnabled;
130  }
131 
133  {
135  }
136 
137  public function loadFromDb()
138  {
139  $this->loadQuestionTracking();
140  $this->loadAnswerStatus();
141  $this->loadPostponedQuestions();
142  $this->loadCheckedQuestions();
143  }
144 
145  private function loadQuestionTracking()
146  {
147  $query = "
148  SELECT question_fi, status
149  FROM tst_seq_qst_tracking
150  WHERE active_fi = %s
151  AND pass = %s
152  ORDER BY orderindex ASC
153  ";
154 
155  $res = $this->db->queryF($query, array('integer','integer'), array($this->activeId, 0));
156 
157  $this->questionTracking = array();
158 
159  while( $row = $this->db->fetchAssoc($res) )
160  {
161  $this->questionTracking[] = array(
162  'qid' => $row['question_fi'],
163  'status' => $row['status']
164  );
165  }
166  }
167 
168  private function loadAnswerStatus()
169  {
170  $query = "
171  SELECT question_fi, correctness
172  FROM tst_seq_qst_answstatus
173  WHERE active_fi = %s
174  AND pass = %s
175  ";
176 
177  $res = $this->db->queryF($query, array('integer','integer'), array($this->activeId, 0));
178 
179  $this->correctAnsweredQuestions = array();
180  $this->wrongAnsweredQuestions = array();
181 
182  while( $row = $this->db->fetchAssoc($res) )
183  {
184  if( $row['correctness'] )
185  {
186  $this->correctAnsweredQuestions[ $row['question_fi'] ] = $row['question_fi'];
187  }
188  else
189  {
190  $this->wrongAnsweredQuestions[ $row['question_fi'] ] = $row['question_fi'];
191  }
192  }
193  }
194 
195  private function loadPostponedQuestions()
196  {
197  $query = "
198  SELECT question_fi, cnt
199  FROM tst_seq_qst_postponed
200  WHERE active_fi = %s
201  AND pass = %s
202  ";
203 
204  $res = $this->db->queryF($query, array('integer','integer'), array($this->activeId, 0));
205 
206  $this->postponedQuestions = array();
207 
208  while( $row = $this->db->fetchAssoc($res) )
209  {
210  $this->postponedQuestions[ $row['question_fi'] ] = $row['cnt'];
211  }
212  }
213 
214  private function loadCheckedQuestions()
215  {
216  $res = $this->db->queryF("SELECT question_fi FROM tst_seq_qst_checked WHERE active_fi = %s AND pass = %s",
217  array('integer','integer'), array($this->getActiveId(), 0)
218  );
219 
220  while( $row = $this->db->fetchAssoc($res) )
221  {
222  $this->alreadyCheckedQuestions[ $row['question_fi'] ] = $row['question_fi'];
223  }
224  }
225 
226  public function saveToDb()
227  {
228  $this->db->manipulateF(
229  "DELETE FROM tst_sequence WHERE active_fi = %s AND pass = %s",
230  array('integer','integer'), array($this->getActiveId(), 0)
231  );
232 
233  $this->db->insert('tst_sequence', array(
234  'active_fi' => array('integer', $this->getActiveId()),
235  'pass' => array('integer', 0),
236  'sequence' => array('clob', null),
237  'postponed' => array('text', null),
238  'hidden' => array('text', null),
239  'tstamp' => array('integer', time())
240  ));
241 
242  $this->saveNewlyTrackedQuestion();
246  $this->saveNewlyCheckedQuestion();
248  }
249 
250  private function saveNewlyTrackedQuestion()
251  {
252  if( (int)$this->newlyTrackedQuestion )
253  {
254  $newOrderIndex = $this->getNewOrderIndexForQuestionTracking();
255 
256  $this->db->replace('tst_seq_qst_tracking',
257  array(
258  'active_fi' => array('integer', (int)$this->getActiveId()),
259  'pass' => array('integer', 0),
260  'question_fi' => array('integer', (int)$this->newlyTrackedQuestion)
261  ),
262  array(
263  'status' => array('text', $this->newlyTrackedQuestionsStatus),
264  'orderindex' => array('integer', $newOrderIndex)
265  )
266  );
267  }
268  }
269 
271  {
272  $query = "
273  SELECT (MAX(orderindex) + 1) new_order_index
274  FROM tst_seq_qst_tracking
275  WHERE active_fi = %s
276  AND pass = %s
277  ";
278 
279  $res = $this->db->queryF($query, array('integer','integer'), array($this->getActiveId(), 0));
280 
281  $row = $this->db->fetchAssoc($res);
282 
283  if( $row['new_order_index'] )
284  {
285  return $row['new_order_index'];
286  }
287 
288  return 1;
289  }
290 
292  {
293  if( (int)$this->newlyAnsweredQuestion )
294  {
295  $this->db->replace('tst_seq_qst_answstatus',
296  array(
297  'active_fi' => array('integer', (int)$this->getActiveId()),
298  'pass' => array('integer', 0),
299  'question_fi' => array('integer', (int)$this->newlyAnsweredQuestion)
300  ),
301  array(
302  'correctness' => array('integer', (int)$this->newlyAnsweredQuestionsAnswerStatus)
303  )
304  );
305  }
306  }
307 
308  private function saveNewlyPostponedQuestion()
309  {
310  if( (int)$this->newlyPostponedQuestion )
311  {
312  $this->db->replace('tst_seq_qst_postponed',
313  array(
314  'active_fi' => array('integer', (int)$this->getActiveId()),
315  'pass' => array('integer', 0),
316  'question_fi' => array('integer', (int)$this->newlyPostponedQuestion)
317  ),
318  array(
319  'cnt' => array('integer', (int)$this->newlyPostponedQuestionsCount)
320  )
321  );
322  }
323  }
324 
326  {
327  $INquestions = $this->db->in('question_fi', array_keys($this->postponedQuestions), true, 'integer');
328 
329  $query = "
330  DELETE FROM tst_seq_qst_postponed
331  WHERE active_fi = %s
332  AND pass = %s
333  AND $INquestions
334  ";
335 
336  $this->db->manipulateF($query, array('integer','integer'), array($this->getActiveId(), 0));
337  }
338 
339  private function saveNewlyCheckedQuestion()
340  {
341  if( (int)$this->newlyCheckedQuestion )
342  {
343  $this->db->replace('tst_seq_qst_checked', array(
344  'active_fi' => array('integer', (int)$this->getActiveId()),
345  'pass' => array('integer', 0),
346  'question_fi' => array('integer', (int)$this->newlyCheckedQuestion)
347  ), array());
348  }
349  }
350 
352  {
353  $NOT_IN_checkedQuestions = $this->db->in('question_fi', $this->alreadyCheckedQuestions, true, 'integer');
354 
355  // BEGIN: FIX IN QUERY
356  if($NOT_IN_checkedQuestions == ' 1=2 ') $NOT_IN_checkedQuestions = ' 1=1 ';
357  // END: FIX IN QUERY
358 
359  $query = "
360  DELETE FROM tst_seq_qst_checked
361  WHERE active_fi = %s
362  AND pass = %s
363  AND $NOT_IN_checkedQuestions
364  ";
365 
366  $this->db->manipulateF($query, array('integer', 'integer'), array((int)$this->getActiveId(), 0));
367  }
368 
369  public function loadQuestions(ilObjTestDynamicQuestionSetConfig $dynamicQuestionSetConfig, ilTestDynamicQuestionSetFilterSelection $filterSelection)
370  {
371  $this->questionSet->load($dynamicQuestionSetConfig, $filterSelection);
372 
373 // echo "<table><tr>";
374 // echo "<td width='200'><pre>".print_r($this->questionSet->getActualQuestionSequence(), 1)."</pre></td>";
375 // echo "<td width='200'><pre>".print_r($this->correctAnsweredQuestions, 1)."</pre></td>";
376 // echo "<td width='200'><pre>".print_r($this->wrongAnsweredQuestions, 1)."</pre></td>";
377 // echo "</tr></table>";
378  }
379 
380  // -----------------------------------------------------------------------------------------------------------------
381 
382  public function cleanupQuestions(ilTestSessionDynamicQuestionSet $testSession)
383  {
384  switch( true )
385  {
386  case !$this->questionSet->questionExists($testSession->getCurrentQuestionId()):
387  case !$this->isFilteredQuestion($testSession->getCurrentQuestionId()):
388 
389  $testSession->setCurrentQuestionId(null);
390  }
391 
392  foreach($this->postponedQuestions as $questionId)
393  {
394  if( !$this->questionSet->questionExists($questionId) )
395  {
396  unset($this->postponedQuestions[$questionId]);
397  }
398  }
399 
400  foreach($this->wrongAnsweredQuestions as $questionId)
401  {
402  if( !$this->questionSet->questionExists($questionId) )
403  {
404  unset($this->wrongAnsweredQuestions[$questionId]);
405  }
406  }
407 
408  foreach($this->correctAnsweredQuestions as $questionId)
409  {
410  if( !$this->questionSet->questionExists($questionId) )
411  {
412  unset($this->correctAnsweredQuestions[$questionId]);
413  }
414  }
415  }
416 
417  // -----------------------------------------------------------------------------------------------------------------
418 
419  public function getUpcomingQuestionId()
420  {
421  if( $questionId = $this->fetchUpcomingQuestionId(true, true) )
422  return $questionId;
423 
424  if( $questionId = $this->fetchUpcomingQuestionId(true, false) )
425  return $questionId;
426 
427  if( $questionId = $this->fetchUpcomingQuestionId(false, true) )
428  return $questionId;
429 
430  if( $questionId = $this->fetchUpcomingQuestionId(false, false) )
431  return $questionId;
432 
433  return null;
434  }
435 
436  private function fetchUpcomingQuestionId($excludePostponedQuestions, $forceNonAnsweredQuestion)
437  {
438  foreach($this->questionSet->getActualQuestionSequence() as $level => $questions)
439  {
440  $postponedQuestions = array();
441 
442  foreach($questions as $pos => $qId)
443  {
444  if( isset($this->correctAnsweredQuestions[$qId]) )
445  {
446  continue;
447  }
448 
450  {
451  continue;
452  }
453 
454  if( $forceNonAnsweredQuestion && isset($this->wrongAnsweredQuestions[$qId]) )
455  {
456  continue;
457  }
458 
459  if( isset($this->postponedQuestions[$qId]) )
460  {
461  $postponedQuestions[$qId] = $this->postponedQuestions[$qId];
462  continue;
463  }
464 
465  return $qId;
466  }
467 
468  if( !$excludePostponedQuestions && count($postponedQuestions) )
469  {
471  }
472  }
473 
474  return null;
475  }
476 
477  public function isAnsweredQuestion($questionId)
478  {
479  return (
480  isset($this->correctAnsweredQuestions[$questionId])
481  || isset($this->wrongAnsweredQuestions[$questionId])
482  );
483  }
484 
485  public function isPostponedQuestion($questionId)
486  {
487  return isset($this->postponedQuestions[$questionId]);
488  }
489 
490  public function isFilteredQuestion($questionId)
491  {
492  foreach($this->questionSet->getActualQuestionSequence() as $level => $questions)
493  {
494  if( in_array($questionId, $questions) )
495  {
496  return true;
497  }
498  }
499 
500  return false;
501  }
502 
503  public function trackedQuestionExists()
504  {
505  return (bool)count($this->questionTracking);
506  }
507 
508  public function getTrackedQuestionList($currentQuestionId = null)
509  {
510  $questionList = array();
511 
512  if( $currentQuestionId )
513  {
514  $questionList[$currentQuestionId] = $this->questionSet->getQuestionData($currentQuestionId);
515  }
516 
517  foreach( array_reverse($this->questionTracking) as $trackedQuestion)
518  {
519  if( !isset($questionList[ $trackedQuestion['qid'] ]) )
520  {
521  $questionList[ $trackedQuestion['qid'] ] = $this->questionSet->getQuestionData($trackedQuestion['qid']);
522  }
523  }
524 
525  return $questionList;
526  }
527 
528  public function resetTrackedQuestionList()
529  {
530  $this->questionTracking = array();
531  }
532 
533  public function openQuestionExists()
534  {
535  return count($this->getOpenQuestions()) > 0;
536  }
537 
538  public function getOpenQuestions()
539  {
540  $completeQuestionIds = array_keys( $this->questionSet->getAllQuestionsData() );
541 
542  $openQuestions = array_diff($completeQuestionIds, $this->correctAnsweredQuestions);
543 
544  return $openQuestions;
545  }
546 
547  public function getTrackedQuestionCount()
548  {
549  $uniqueQuestions = array();
550 
551  foreach($this->questionTracking as $trackedQuestion)
552  {
553  $uniqueQuestions[$trackedQuestion['qid']] = $trackedQuestion['qid'];
554  }
555 
556  return count($uniqueQuestions);
557  }
558 
559  public function getCurrentPositionIndex($questionId)
560  {
561  $i = 0;
562 
563  foreach($this->questionSet->getActualQuestionSequence() as $level => $questions)
564  {
565  foreach($questions as $pos => $qId)
566  {
567  $i++;
568 
569  if($qId == $questionId)
570  {
571  return $i;
572  }
573  }
574  }
575 
576  return null;
577  }
578 
579  public function getLastPositionIndex()
580  {
581  $count = 0;
582 
583  foreach($this->questionSet->getActualQuestionSequence() as $level => $questions)
584  {
585  $count += count($questions);
586  }
587 
588  return $count;
589  }
590 
591  // -----------------------------------------------------------------------------------------------------------------
592 
593  public function setQuestionUnchecked($questionId)
594  {
595  unset($this->alreadyCheckedQuestions[$questionId]);
596  }
597 
598  public function setQuestionChecked($questionId)
599  {
600  $this->newlyCheckedQuestion = $questionId;
601  $this->alreadyCheckedQuestions[$questionId] = $questionId;
602  }
603 
604  public function isQuestionChecked($questionId)
605  {
606  return isset($this->alreadyCheckedQuestions[$questionId]);
607  }
608 
609  public function setQuestionPostponed($questionId)
610  {
611  $this->trackQuestion($questionId, 'postponed');
612 
613  if( !isset($this->postponedQuestions[$questionId]) )
614  {
615  $this->postponedQuestions[$questionId] = 0;
616  }
617 
618  $this->postponedQuestions[$questionId]++;
619 
620  $this->newlyPostponedQuestion = $questionId;
621  $this->newlyPostponedQuestionsCount = $this->postponedQuestions[$questionId];
622  }
623 
624  public function unsetQuestionPostponed($questionId)
625  {
626  if( isset($this->postponedQuestions[$questionId]) )
627  unset($this->postponedQuestions[$questionId]);
628  }
629 
630  public function setQuestionAnsweredCorrect($questionId)
631  {
632  $this->trackQuestion($questionId, 'correct');
633 
634  $this->correctAnsweredQuestions[$questionId] = $questionId;
635 
636  if( isset($this->wrongAnsweredQuestions[$questionId]) )
637  unset($this->wrongAnsweredQuestions[$questionId]);
638 
639  $this->newlyAnsweredQuestion = $questionId;
640  $this->newlyAnsweredQuestionsAnswerStatus = true;
641  }
642 
643  public function setQuestionAnsweredWrong($questionId)
644  {
645  $this->trackQuestion($questionId, 'wrong');
646 
647  $this->wrongAnsweredQuestions[$questionId] = $questionId;
648 
649  if( isset($this->correctAnsweredQuestions[$questionId]) )
650  unset($this->correctAnsweredQuestions[$questionId]);
651 
652  $this->newlyAnsweredQuestion = $questionId;
653  $this->newlyAnsweredQuestionsAnswerStatus = false;
654  }
655 
656  private function trackQuestion($questionId, $answerStatus)
657  {
658  $this->questionTracking[] = array(
659  'qid' => $questionId, 'status' => $answerStatus
660  );
661 
662  $this->newlyTrackedQuestion = $questionId;
663  $this->newlyTrackedQuestionsStatus = $answerStatus;
664  }
665 
666  // -----------------------------------------------------------------------------------------------------------------
667 
668  public function hasStarted()
669  {
670  return $this->trackedQuestionExists();
671  }
672 
673  // -----------------------------------------------------------------------------------------------------------------
674 
675  public function getCompleteQuestionsData()
676  {
677  return $this->questionSet->getCompleteQuestionList()->getQuestionDataArray();
678  }
679 
680  public function getFilteredQuestionsData()
681  {
682  return $this->questionSet->getFilteredQuestionList()->getQuestionDataArray();
683  }
684 
685  // -----------------------------------------------------------------------------------------------------------------
686 
687  public function getUserSequenceQuestions()
688  {
689  //return array_keys( $this->getTrackedQuestionList() );
690 
691  $questionSequence = array();
692 
693  foreach( $this->questionSet->getActualQuestionSequence() as $level => $questions )
694  {
695  $questionSequence = array_merge($questionSequence, $questions);
696  }
697 
698  return $questionSequence;
699  }
700 
706  {
707  $minPostponeCount = null;
708  $minPostponeItem = null;
709 
710  foreach(array_reverse($postponedQuestions, true) as $qId => $postponeCount)
711  {
712  if($minPostponeCount === null || $postponeCount <= $minPostponeCount)
713  {
714  $minPostponeCount = $postponeCount;
715  $minPostponeItem = $qId;
716  }
717  }
718  return $minPostponeItem;
719  }
720 
721 }
722