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