1<?php
2
3// version 1.0  First release
4
5class quiz {
6//  Quiz plugin for Dokuwiki
7//  Copyright (C) 2010  Luigi Micco
8//  based on osQuest5 PHP/XML based Quiz software (and associated Text to XML module)
9//  Copyright (C) 2009  Jon-Michael C. Brook
10//  based on osQuest4 PHP/XML based Quiz software (and associated Text to XML module)
11//  Copyright (C) 2007  Jay Banks
12//
13//  These program are free software: you can redistribute them and/or modify
14//  them under the terms of the GNU Affero General Public License as
15//  published by the Free Software Foundation, either version 3 of the
16//  License, or (at your option) any later version.
17//
18//  This program is distributed in the hope that it will be useful,
19//  but WITHOUT ANY WARRANTY; without even the implied warranty of
20//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21//  GNU Affero General Public License for more details.
22//
23//  A copy of the GNU Affero General Public License is provided below
24//  and it applies to all programs in this archive.
25//  See also: <http://www.gnu.org/licenses/> or
26//  http://www.fsf.org/licensing/licenses/agpl-3.0.html.
27//
28//
29
30    // change below values to adjust preferences
31    //
32
33    var $quizID;
34    var $mode;
35    var $rndquest;
36    var $rndchoice;
37    var $highscore;
38    var $showintro;
39    var $current_question = 1;
40    var $error = '';
41
42    var $in_question = 0;
43    var $in_section = '';
44
45    var $parseQuiz_cache = null;
46    var $quizobj;
47
48    function quiz($quizID, &$param) {
49      $this->quizID = $quizID;
50      $this->mode = $param['learning'];
51      $this->rndquest = $param['rndquest'];
52      $this->rndchoice = $param['rndchoice'];
53      $this->highscore = $param['highscores'];
54      $this->showintro = $param['showintro'];
55
56      $this->quizobj = plugin_load('syntax','quiz');
57
58      if (!($this->mode < 0 || $this->mode > 1 )) {
59        $this->error = 'Error: "' . $this->mode .'" is not a valid quiz mode. Valid modes are "test (0)" and "learning (1)"';
60      }
61    }
62
63    function do_quiz_here() {
64      if (isset($_POST['next_question'])) {
65        $next = $_POST['next_question']; // + 1;
66        if ((!isset($_POST['answers'][$next - 1])) && ($next > 1)) { $next = $next - 1; }
67      } else {
68        unset($_POST);
69        $next = 0;
70      }
71      return $this->print_question($next);
72    }
73
74    function print_question($question_num) {
75
76      $html = "";
77      $question_num = (int)$question_num;
78      $data = $this->parseQuiz();
79      if ($data !== false) {
80        $a = $data['questions'][$question_num];
81        $count_a = count($data['questions']);
82
83        if ($question_num <= $count_a) {
84          if ($question_num <= 0)  {
85            $html .= '<h1>'.$data['title'].'</h1>';
86
87            $html .= $data['intro'];
88            if (($question_num != 0) && ($this->highscore )) $html .= $this->print_highscore();
89
90            $html .= '<div class="quiz-block" id="quiz-form__'.$this->quizID.'">';
91          } else {
92            $html .= '<h1>'.$data['title'].' - '.$question_num.'/'.$count_a.'</h1>';
93            if ($this->showintro) $html .= $data['intro'];
94            $html .= '<div class="quiz-block" id="quiz-form__'.$this->quizID.'">';
95            $html .= '<div class="quiz-question">'.$a['question'].'</div>';
96          }
97
98          $html .= '<form action="getform.php" method="post" onsubmit="plgQuiz_send(\''.$this->quizID.'\'); return false;">';
99          $html .= '<input type="hidden" name="quiz_id" value="' . $this->quizID. '">' . "\n";
100          $html .= '<input type="hidden" name="quiz_learning" value="' . $this->mode. '">' . "\n";
101          $html .= '<input type="hidden" name="quiz_showintro" value="' . $this->showintro. '">' . "\n";
102          $html .= '<input type="hidden" name="quiz_rndquest" value="' . $this->rndquest. '">' . "\n";
103          $html .= '<input type="hidden" name="quiz_rndchoice" value="' . $this->rndchoice. '">' . "\n";
104          $html .= '<input type="hidden" name="quiz_highscore" value="' . $this->highscore. '">' . "\n";
105
106          if ($question_num <= 0)  {
107            $html .= '<div class="quiz-button">';
108            $html .= '<button class="button" type="submit" onClick="plgQuiz_send(\''.$this->quizID.'\',1); return false;" > '.$this->quizobj->getLang('quiz_start').' </button>';
109
110            if (($question_num == 0) && ($this->highscore )) {
111              $html .= '<button class="button" type="submit" onClick="plgQuiz_send(\''.$this->quizID.'\',-1); return false;" > '.$this->quizobj->getLang('quiz_highscore').' </button>';
112            }
113
114            $html .= '</div>';
115            $html .= '</form>';
116            $html .= '</div>';
117
118          } else {
119
120            $html .= '<div class="quiz-choice">';
121
122            // se si torna alla domanda precedente, cancella i dati doppi
123            $temp = $_POST['answers'][$question_num];
124            if (isset($_POST['prev'])) {
125              unset($_POST['answers'][$question_num]);
126            }
127            //
128
129            // se presenti, aggiunge in POST i dati delle risposte precedenti
130            if (isset($_POST) && isset($_POST['answers'])) {
131              foreach ($_POST['answers'] as $key => $value) {
132                if (!is_array($value)) {
133                  $html .= '<input type="hidden" name="answers[' . $key . ']" value="' . $value .'">' . "\n";
134                } else {
135                  foreach ($value as $literal_key => $valueison) {
136                    $html .= '<input type="hidden" name="answers[' . $key . '][' . $literal_key .']" value="on">' . "\n";
137                  }
138                }
139              }
140            }
141            //
142
143            if ($a['type'] == 'single') {
144              for($n=1; $n<=count($a['choices']); $n++) {
145                $state = '';
146                if ($temp == $n) $state = ' checked';
147                $html .= '<input '.$state.' type="radio" name="answers[' . $question_num . ']" id="'.$n.'" value="'.$n.'">&nbsp;<label for="' . $n .
148                    '">' . $a['choices'][$n]['text'] . '</label><br>' . "\n";
149              }
150            } else {
151              if ($a['type'] == 'text') {
152                $html .= '<input type="text" name="answers[' . $question_num .
153                    ']" value="'.$temp.'" size="50"><br>' . "\n";
154              } else {
155                if ($a['type'] == 'multi') {
156                  for($n=1; $n<=count($a['choices']); $n++) {
157                    $state = '';
158                    if (isset($temp[$n]) && ($temp[$n] =='on')) $state = ' checked';
159                    $html .='<input '.$state.' type="checkbox" id="answers[' . $question_num . '][' . $n . ']" name="answers[' . $question_num . '][' . $n .']">
160                    &nbsp;<label for="answers[' . $question_num . '][' . $n . ']">' . $a['choices'][$n]['text'] .
161                        '</label><br>' . "\n";
162                  }
163                }
164              }
165            }
166            $html .= '</div>';
167            $html .= '<div class="quiz-button">';
168            if ($question_num > 1) {
169              $html .= '<button class="button" name="next_question" value="'.($question_num-1).'" type="submit" onClick="plgQuiz_send(\''.$this->quizID.'\','.($question_num-1).'); return false;" > '.$this->quizobj->getLang('quiz_prevquest').' </button>';
170            }
171            $html .= '<button class="button" name="next_question" value="'.($question_num+1).'" type="submit" onClick="plgQuiz_send(\''.$this->quizID.'\','.($question_num+1).'); return false;"> ' .  $this->quizobj->getLang(($count_a == $question_num ?'quiz_finish':'quiz_nextquest')) . ' </button>';
172            $html .= '</form>';
173            $html .= '</div>';
174            $html .= '</div>';
175          }
176        } else {
177
178          // mostra le risposte giuste
179          //display test results
180          $total_answers = count($_POST['answers']);
181          $total_correct_answers = 0;
182          $total_score = 0;
183          foreach ($_POST['answers'] as $_question_num => $_users_answer) {
184            if ($this->checkanswer($_question_num)) {
185              $total_correct_answers++;
186              $total_score = $total_score + $data['questions'][$_question_num]['score'];
187            }
188          }
189
190          $html .= '<h1>'.$data['title'].' - '.$this->quizobj->getLang('quiz_result').'</h1>';
191          if ($this->showintro) $html .= $data['intro'];
192          $html .= '<div class="quiz-block">';
193          $html .= '<div class="quiz-result">';
194          $percent = round(($total_correct_answers / $total_answers) * 100, 2);
195          $html .= sprintf($this->quizobj->getLang('quiz_resulttext'), $total_correct_answers, $total_answers, $percent);
196          $html .= '<br>';
197          $html .= $this->quizobj->getLang('quiz_score').$total_score . '<br>';
198          $html .= '</div>';
199
200          // descrizione del risultato per scaglioni
201          if ($data['scores']) {
202            foreach ($data['scores'] as $scores) {
203              if (($total_score >= $scores['range'][0]) && ($total_score <= $scores['range'][1])) {
204                $html .= '<div class="quiz-report">';
205                $html .= $scores['text'];
206                $html .= '</div>';
207              }
208            }
209          }
210
211          $html .= '<div class="quiz-button">';
212          $html .= '<form action="" method="post">';
213          $html .= '<BUTTON class="button" name="again" type="submit" > '.$this->quizobj->getLang('quiz_tryagain').' </BUTTON>';
214          $html .= '</form>';
215          $html .= '</div>';
216
217
218          if ($this->mode == 1 && $question_num > 1) {
219            $html .= "<hr>";
220            for ($i = 1; $i <= $count_a; $i++) {
221              $html .= $this->learning_mode_print_answer($i );
222            }
223          }
224
225          $scorelist = $this->updateScore($total_score, $percent);
226          $html .= '</div>';
227        }
228      } else {
229        $html .= $this->quiz_error();
230      }
231      return $html;
232    }
233
234    function learning_mode_print_answer($question_num) {
235      $ok = false;
236
237      if ($this->checkanswer($question_num, &$answers))  {
238        $ok = true;
239        $div = "correct";
240      } else {
241        $div = "wrong";
242      }
243
244      $a = $this->parseQuiz();
245      $a = $a['questions'][$question_num];
246
247      $html = "";
248
249      $html .= '<div class="quiz-block">';
250      $html .= '<div class="quiz-question">';
251      $html .= '<div class="quiz-'.$div.'">';
252      $html .=  $question_num.") ".$a['question']; //."&nbsp;".$img;
253      $html .= '</div>';
254      $html .= '</div>';
255      $html .= '<div class="quiz-choice">';
256      $html .= '<ul>';
257
258      if ($a['type'] == 'text') {
259        if (!$ok)
260          $html .= '<div class="quiz-wrong">'. $answers[1].'</div>';
261
262        if (!$ok)
263          $html .= '<div class="quiz-missing">'. $a['answers'][0] .'</div>';
264        else
265          $html .= '<div class="quiz-correct">'. $a['answers'][0] .'</div>';
266      } else {
267        for($n=1; $n<=count($a['choices']); $n++) {
268
269          if (($answers[$n] == 1) && $a['choices'][$n]['correct']) {
270            $div = "correct";
271          } elseif (($answers[$n] == 1) && !$a['choices'][$n]['correct']) {
272            $div = "wrong";
273          } elseif (($answers[$n] == 0) && $a['choices'][$n]['correct']) {
274            $div = "missing";
275          } else {
276            $div = "";
277          }
278
279          $html .= '<div class="quiz-'.$div.'">';
280          $html .= '<li class="level1">'.$a['choices'][$n]['text'].'</li>';
281          $html .= '</div>' . "\n";
282        }
283      }
284      $html .= '</ul>';
285
286      if ($a['explanation'] != '') {
287        $html .= '<hr><b>'.$this->quizobj->getLang('quiz_explanation').'</b><br>' . $a['explanation'].'';
288      }
289      $html .= '</div>';
290      $html .= '</div>';
291      return $html ;
292    }
293
294    function checkanswer($question, $answer_array = null) {
295      $result = false;
296      if ($this->parseQuiz() !== false && $question > 0) {
297        $data = $this->parseQuiz();
298        $a = $data['questions'][$question];
299
300        $your_answer = '';
301        $correct_answer = '';
302        if ($a['type'] == 'single') {
303          $your_answer = $_POST['answers'][$question];
304          for($n=1; $n<=count($a['choices']); $n++) {
305            $answer_array[$n] = ($n == $your_answer?1:0);
306          }
307          $result = $a['choices'][$your_answer]['correct'];
308
309        } else {
310          if ($a['type'] == 'text') {
311            $your_answer = $_POST['answers'][$question];
312            $answer_array[1] = $your_answer;
313            $result = (strtolower($your_answer) == strtolower($a['answers'][0]));
314
315          } else {
316            if ($a['type'] == 'multi') {
317              $your_answers = $_POST['answers'][$question];
318              ksort($your_answers);
319
320              $result = true;
321              for($n=1; $n<=count($a['choices']); $n++) {
322                $answer_array[$n] = (isset($your_answers[$n])? 1:0);
323                if ( (($answer_array[$n] == 1) && !$a['choices'][$n]['correct']) ||
324                     (($answer_array[$n] == 0) && $a['choices'][$n]['correct']) ) {
325                  $result = false;
326                }
327              }
328            }
329          }
330        }
331      }
332      return $result;
333    }
334
335    function quiz_error() {
336      return $this->error;
337    }
338
339
340    function parseQuiz() {
341      if ($this->parseQuiz_cache === null) {
342        $page = $this->quizID;
343        $quizz = array();
344        $pos = strrpos(utf8_decode($page), ':');
345        $pageName = p_get_first_heading($page);
346        if($pageName == NULL) {
347          if($pos != FALSE) {
348            $pageName = utf8_substr($page, $pos+1, utf8_strlen($page));
349          } else {
350            $pageName = $page;
351          }
352          $pageName = str_replace('_', ' ', $pageName);
353        }
354        $quizz['title'] = $pageName;
355        $select= rawWiki($page);
356        $lines = explode("\n", $select);
357        $nr = count($lines);
358
359        $hRender = new Doku_Renderer_xhtml();
360
361        $nquest = 0;
362        $quizz['questions'] = array();
363
364        for($n=0; $n<$nr; $n++) {
365
366          if (substr($lines[$n], 0, 6) == "======") continue;
367
368          if (substr($lines[$n], 0, 5) == "=====") {
369            $temp = str_replace("=====",'',$lines[$n]);
370            $tmp_array = array();
371            $tmp_array['range']= explode("-", $temp);
372            for($k=$n+1; $k<$nr; $k++) {
373              if ((trim($lines[$k]) == '') || (substr($lines[$k], 0, 5) != "====="))  {
374                $tmp_array['text'] .= $lines[$k]."\n";
375              } else {
376                $n = $k - 1;
377                $k = $nr + 1;
378              }
379            };
380            $tmp_array['text'] = $hRender->render($tmp_array['text']);
381            $quizz['scores'][]= $tmp_array;
382
383          };
384
385          if ((trim($lines[$n]) == '') || (substr($lines[$n], 0, 4) != "  * "))  {
386            if ($nquest == 0) $quizz['intro'] .= $lines[$n]."\n";
387            continue;
388          }
389
390
391
392          $nquest++;;
393          $tmp = $hRender->render(str_replace("  * ",'',$lines[$n]));
394          $tmp = substr($tmp, 4);
395          $tmp = substr($tmp, 0, -5);
396          $quizz['questions'][$nquest]['question'] = $tmp;
397
398          $nchoices = 0;
399          $quizz['questions'][$nquest]['choices'] = array();
400          $quizz['questions'][$nquest]['answers'] = array();
401          $quizz['questions'][$nquest]['score']= 1;
402          $quizz['questions'][$nquest]['explanation'] = '';
403
404          $temp_choices = array();
405          for($k=$n+1; $k<$nr; $k++) {
406            if (trim($lines[$k]) == '') continue;
407            if (substr($lines[$k], 0, 6) == "    - ") {
408              $nchoices++;
409              $tmp = $hRender->render(str_replace("    - ",'',$lines[$k]));
410              $tmp = substr($tmp, 4);
411              $tmp = substr($tmp, 0, -5);
412
413              $quizz['questions'][$nquest]['choices'][$nchoices]['text'] = $tmp;
414              $quizz['questions'][$nquest]['choices'][$nchoices]['correct'] = false;
415
416            } elseif (substr($lines[$k], 0, 7) == "    * [") {
417              $temp = str_replace("    * [",'',$lines[$k]);
418              $temp = ereg_replace("].*", "", $temp);
419
420              $quizz['questions'][$nquest]['answers'] = explode("|", $temp);
421              sort($quizz['questions'][$nquest]['answers']);
422
423              for($i=0; $i<count($quizz['questions'][$nquest]['answers']); $i++) {
424                if (isset($quizz['questions'][$nquest]['choices'][$quizz['questions'][$nquest]['answers'][$i]]['text'])) {
425                  $quizz['questions'][$nquest]['choices'][$quizz['questions'][$nquest]['answers'][$i]]['correct'] = true;
426                }
427              }
428
429            } elseif (substr($lines[$k], 0, 7) == "    * (") {
430              $temp = str_replace("    * (",'',$lines[$k]);
431              $temp = ereg_replace(").*", "", $temp);
432
433              $quizz['questions'][$nquest]['score']= $temp;
434
435            } elseif (substr($lines[$k], 0, 6) == "    * ") {
436              $tmp = $hRender->render(str_replace("    * ",'',$lines[$k]));
437              $tmp = substr($tmp, 4);
438              $tmp = substr($tmp, 0, -5);
439
440              $quizz['questions'][$nquest]['explanation'] = $tmp;
441            } else {
442              $n = $k - 1;
443              $k = $nr + 1;
444            }
445          }
446
447          if ($this->rndchoice) $this->kshuffle($quizz['questions'][$nquest]['choices']);
448
449          if (count($quizz['questions'][$nquest]['choices']) == 0) {
450            $quizz['questions'][$nquest]['type'] = 'text';
451          } elseif (count($quizz['questions'][$nquest]['answers']) > 1) {
452            $quizz['questions'][$nquest]['type'] = 'multi';
453          } else {
454            $quizz['questions'][$nquest]['type'] = 'single';
455          }
456
457        }
458
459        if ($this->rndquest) $this->kshuffle($quizz['questions']);
460        $quizz['intro'] = $hRender->render($quizz['intro']);
461
462
463        $this->parseQuiz_cache = $quizz;
464      }
465      return $this->parseQuiz_cache;
466
467    }
468
469    // sequenza casuale, fissa per sessione
470    function kshuffle(&$items) {
471      $temp = $items;
472      srand(hexdec(session_id()));
473      shuffle($temp);
474      for($k=1; $k<=count($temp); $k++) {
475        $items[$k] = $temp[$k-1];
476      }
477    }
478
479    // visualizza i migliori 10 punteggi
480    function print_highscore() {
481			// get score file contents
482			$pfile = metaFN(md5($this->quizID), '.quiz');
483			$scorelist = unserialize(@file_get_contents($pfile));
484
485      $temp = $scorelist['rows'];
486      usort($temp, array($this,'cmp'));
487      $temp = array_slice($temp, 0, 10);
488
489      $html = "";
490      $html .= '<h2>10 '.$this->quizobj->getLang('quiz_highscore').' ('.$scorelist['total'].')</h2><br>';
491
492      $html .= '<table class="inline">';
493      $html .= '<tr class="row0">';
494      $html .= '<th class="col0 centeralign">  User  </th>
495                <th class="col1 centeralign">  IP  </th>
496                <th class="col2 centeralign">  Score </th>
497                <th class="col3 centeralign">  %  </th>
498                <th class="col3 centeralign">  Date  </th>';
499	    $html .= '</tr>';
500      foreach ($temp as $row) {
501        $html .= "<tr>";
502        $html .= '<td class="col0">'.$row['user'].'</td>';
503        $html .= '<td class="col1">'.$row['ip'].'</td>';
504        $html .= '<td class="col2">'.$row['score'].'</td>';
505        $html .= '<td class="col3">'.$row['percent'].'</td>';
506        $html .= '<td class="col3">'.dformat($row['timestamp']).'</td>';
507        $html .= "</tr>";
508      }
509
510      $html .= "</table>";
511
512      return $html;
513    }
514
515    // Comparison function
516    function cmp($a, $b) {
517        if ($a['score'] == $b['score']) {
518            return 0;
519        }
520        return ($a['score'] > $b['score']) ? -1 : 1;
521    }
522
523    // aggiorna il file dei risultati
524    function updateScore($userscore, $percent) {
525
526			// get score file contents
527			$pfile = metaFN(md5($this->quizID), '.quiz');
528			$score = unserialize(@file_get_contents($pfile));
529
530			if(!isset($score['total'])) $score['total'] = 0;
531			if(!isset($score['rows'])) {
532        $score['rows'] = array();
533      }
534
535			if(!isset($score['ips'])) $score['ips'] = array();
536			if(!isset($score['users'])) $score['users'] = array();
537
538			$ip = clientIP(true);
539
540      $score['total']++;
541      $tmp = '(anonymous)';
542      if($_SERVER['REMOTE_USER']) $tmp = $_SERVER['REMOTE_USER'];
543      $score['users'][] =$tmp;
544      $score['ips'][] = $ip;
545
546      $score['rows'][$score['total']]['percent'] = $percent;
547      $score['rows'][$score['total']]['score'] = $userscore;
548      $score['rows'][$score['total']]['user'] = $tmp;
549      $score['rows'][$score['total']]['ip'] = $ip;
550      $score['rows'][$score['total']]['timestamp'] = time();
551
552      if($fh = fopen($pfile, 'w')) {
553        fwrite($fh, serialize($score));
554        fclose($fh);
555      }
556      return $score;
557    }
558
559
560}
561
562?>
563
564