ILIAS  release_5-0 Revision 5.0.0-1144-gc4397b1f870
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
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
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
Database Wrapper.
Definition: class.ilDB.php:29
setPreventCheckedQuestionsFromComingUpEnabled($preventCheckedQuestionsFromComingUpEnabled)
loadQuestions(ilObjTestDynamicQuestionSetConfig $dynamicQuestionSetConfig, ilTestDynamicQuestionSetFilterSelection $filterSelection)
__construct(ilDB $db, ilTestDynamicQuestionSet $questionSet, $activeId)
Constructor.
fetchUpcomingQuestionId($excludePostponedQuestions, $forceNonAnsweredQuestion)
cleanupQuestions(ilTestSessionDynamicQuestionSet $testSession)