03aa70df086ac052e1a0e9befaf3928ae8384b81
[moodle.git] / mod / quiz / lib.php
1 <?PHP  // $Id$
3 /// Library of function for module quiz
5 /// CONSTANTS ///////////////////////////////////////////////////////////////////
7 define("GRADEHIGHEST", "1");
8 define("GRADEAVERAGE", "2");
9 define("ATTEMPTFIRST", "3");
10 define("ATTEMPTLAST",  "4");
11 $QUIZ_GRADE_METHOD = array ( GRADEHIGHEST => get_string("gradehighest", "quiz"),
12                              GRADEAVERAGE => get_string("gradeaverage", "quiz"),
13                              ATTEMPTFIRST => get_string("attemptfirst", "quiz"),
14                              ATTEMPTLAST  => get_string("attemptlast", "quiz"));
16 define("SHORTANSWER", "1");
17 define("TRUEFALSE",   "2");
18 define("MULTICHOICE", "3");
19 define("RANDOM",      "4");
20 $QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz"),
21                               TRUEFALSE   => get_string("truefalse", "quiz"),
22                               SHORTANSWER => get_string("shortanswer", "quiz") );
26 /// FUNCTIONS ///////////////////////////////////////////////////////////////////
28 function quiz_add_instance($quiz) {
29 /// Given an object containing all the necessary data, 
30 /// (defined by the form in mod.html) this function 
31 /// will create a new instance and return the id number 
32 /// of the new instance.
34     $quiz->created      = time();
35     $quiz->timemodified = time();
36     $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday, 
37                                      $quiz->openhour, $quiz->openminute, $quiz->opensecond);
38     $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday, 
39                                       $quiz->closehour, $quiz->closeminute, $quiz->closesecond);
41     if (!$quiz->id = insert_record("quiz", $quiz)) {
42         return false;  // some error occurred
43     }
45     // The grades for every question in this quiz are stored in an array
46     if ($quiz->grades) {
47         foreach ($quiz->grades as $question => $grade) {
48             if ($question and $grade) {
49                 unset($questiongrade);
50                 $questiongrade->quiz = $quiz->id;
51                 $questiongrade->question = $question;
52                 $questiongrade->grade = $grade;
53                 if (!insert_record("quiz_question_grades", $questiongrade)) {
54                     return false;
55                 }
56             }
57         }
58     }
59     
60     return $quiz->id;
61 }
64 function quiz_update_instance($quiz) {
65 /// Given an object containing all the necessary data, 
66 /// (defined by the form in mod.html) this function 
67 /// will update an existing instance with new data.
69     $quiz->timemodified = time();
70     $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday, 
71                                      $quiz->openhour, $quiz->openminute, $quiz->opensecond);
72     $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday, 
73                                       $quiz->closehour, $quiz->closeminute, $quiz->closesecond);
74     $quiz->id = $quiz->instance;
76     if (!update_record("quiz", $quiz)) {
77         return false;  // some error occurred
78     }
81     // The grades for every question in this quiz are stored in an array
82     // Insert or update records as appropriate
84     $existing = get_records("quiz_question_grades", "quiz", $quiz->id, "", "question,grade,id");
86     if ($quiz->grades) {
87         foreach ($quiz->grades as $question => $grade) {
88             if ($question and $grade) {
89                 unset($questiongrade);
90                 $questiongrade->quiz = $quiz->id;
91                 $questiongrade->question = $question;
92                 $questiongrade->grade = $grade;
93                 if (isset($existing[$question])) {
94                     if ($existing[$question]->grade != $grade) {
95                         $questiongrade->id = $existing[$question]->id;
96                         if (!update_record("quiz_question_grades", $questiongrade)) {
97                             return false;
98                         }
99                     }
100                 } else {
101                     if (!insert_record("quiz_question_grades", $questiongrade)) {
102                         return false;
103                     }
104                 }
105             }
106         }
107     }
108     
109     return true;
113 function quiz_delete_instance($id) {
114 /// Given an ID of an instance of this module, 
115 /// this function will permanently delete the instance 
116 /// and any data that depends on it.  
118     if (! $quiz = get_record("quiz", "id", "$id")) {
119         return false;
120     }
122     $result = true;
124     if ($attempts = get_record("quiz_attempts", "quiz", "$quiz->id")) {
125         foreach ($attempts as $attempt) {
126             if (! delete_records("quiz_responses", "attempt", "$attempt->id")) {
127                 $result = false;
128             }
129         }
130     }
132     if (! delete_records("quiz_attempts", "quiz", "$quiz->id")) {
133         $result = false;
134     }
136     if (! delete_records("quiz_grades", "quiz", "$quiz->id")) {
137         $result = false;
138     }
140     if (! delete_records("quiz_question_grades", "quiz", "$quiz->id")) {
141         $result = false;
142     }
144     if (! delete_records("quiz", "id", "$quiz->id")) {
145         $result = false;
146     }
148     return $result;
151 function quiz_user_outline($course, $user, $mod, $quiz) {
152 /// Return a small object with summary information about what a 
153 /// user has done with a given particular instance of this module
154 /// Used for user activity reports.
155 /// $return->time = the time they did it
156 /// $return->info = a short text description
157     if ($grade = get_record_sql("SELECT * FROM quiz_grades WHERE user = '$user->id' AND quiz = '$quiz->id'")) {
158         
159         if ($grade->grade) {
160             $result->info = get_string("grade").": $grade->grade";
161         }
162         $result->time = $grade->timemodified;
163         return $result;
164     }
165     return NULL;
167     return $return;
170 function quiz_user_complete($course, $user, $mod, $quiz) {
171 /// Print a detailed representation of what a  user has done with 
172 /// a given particular instance of this module, for user activity reports.
174     return true;
177 function quiz_print_recent_activity(&$logs, $isteacher=false) {
178 /// Given a list of logs, assumed to be those since the last login 
179 /// this function prints a short list of changes related to this module
180 /// If isteacher is true then perhaps additional information is printed.
181 /// This function is called from course/lib.php: print_recent_activity()
183     global $CFG, $COURSE_TEACHER_COLOR;
185     return $content;  // True if anything was printed, otherwise false
188 function quiz_cron () {
189 /// Function to be run periodically according to the moodle cron
190 /// This function searches for things that need to be done, such 
191 /// as sending out mail, toggling flags etc ... 
193     global $CFG;
195     return true;
198 function quiz_grades($quizid) {
199 /// Must return an array of grades, indexed by user, and a max grade.
201     $return->grades = get_records_sql_menu("SELECT user,grade FROM quiz_grades WHERE quiz = '$quizid'");
202     $return->maxgrade = get_field("quiz", "grade", "id", "$quizid");
203     return $return;
207 //////////////////////////////////////////////////////////////////////////////////////
208 /// Any other quiz functions go here.  Each of them must have a name that 
209 /// starts with quiz_
211 function quiz_print_comment($text) {
212     global $THEME;
214     echo "<FONT COLOR=\"$THEME->cellheading2\">".text_to_html($text, true, false)."</FONT>";
217 function quiz_print_correctanswer($text) {
218     global $THEME;
220     echo "<P ALIGN=RIGHT><SPAN CLASS=highlight>$text</SPAN></P>";
223 function quiz_print_question_icon($question) {
224 // Prints a question icon
225     switch ($question->type) {
226         case SHORTANSWER:
227             echo "<IMG HEIGHT=16 WIDTH=16 SRC=\"pix/sa.gif\">";
228             break;
229         case TRUEFALSE:
230             echo "<IMG HEIGHT=16 WIDTH=16 SRC=\"pix/tf.gif\">";
231             break;
232         case MULTICHOICE:
233             echo "<IMG HEIGHT=16 WIDTH=16 SRC=\"pix/mc.gif\">";
234             break;
235         case RANDOM:
236             echo "<IMG HEIGHT=16 WIDTH=16 SRC=\"pix/rs.gif\">";
237             break;
238     }
241 function quiz_print_question($number, $questionid, $grade, $courseid, 
242                               $feedback=NULL, $response=NULL, $actualgrade=NULL, $correct=NULL) {
243 /// Prints a quiz question, any format
245     if (!$question = get_record("quiz_questions", "id", $questionid)) {
246         notify("Error: Question not found!");
247     }
249     $stranswer = get_string("answer", "quiz");
250     $strmarks  = get_string("marks", "quiz");
252     echo "<TABLE WIDTH=100% CELLSPACING=10><TR><TD NOWRAP WIDTH=100 VALIGN=top>";
253     echo "<P ALIGN=CENTER><B>$number</B></P>";
254     if ($feedback or $response) {
255         echo "<P ALIGN=CENTER><FONT SIZE=1>$strmarks: $actualgrade/$grade</FONT></P>";
256     } else {
257         echo "<P ALIGN=CENTER><FONT SIZE=1>$grade $strmarks</FONT></P>";
258     }
259     print_spacer(1,100);
260     echo "</TD><TD VALIGN=TOP>";
262     switch ($question->type) {
263        case SHORTANSWER: 
264            if (!$options = get_record("quiz_shortanswer", "question", $question->id)) {
265                notify("Error: Missing question options!");
266            }
267            echo text_to_html($question->questiontext);
268            if ($question->image) {
269                print_file_picture($question->image, $courseid, 200);
270            }
271            if ($response) {
272                $value = "VALUE=\"$response[0]\"";
273            }
274            echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$question->id SIZE=20 $value></P>";
275            if ($feedback) {
276                quiz_print_comment("<P ALIGN=right>$feedback[0]</P>");
277            }
278            if ($correct) {
279                $correctanswers = implode(", ", $correct);
280                quiz_print_correctanswer($correctanswers);
281            }
282            break;
284        case TRUEFALSE:
285            if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
286                notify("Error: Missing question options!");
287            }
288            if (!$true = get_record("quiz_answers", "id", $options->true)) {
289                notify("Error: Missing question answers!");
290            }
291            if (!$false = get_record("quiz_answers", "id", $options->false)) {
292                notify("Error: Missing question answers!");
293            }
294            if (!$true->answer) {
295                $true->answer = get_string("true", "quiz");
296            }
297            if (!$false->answer) {
298                $false->answer = get_string("false", "quiz");
299            }
300            echo text_to_html($question->questiontext);
301            if ($question->image) {
302                print_file_picture($question->image, $courseid, 200);
303            }
305            if ($response[$true->id]) {
306                $truechecked = "CHECKED";
307                $feedbackid = $true->id;
308            } else if ($response[$false->id]) {
309                $falsechecked = "CHECKED";
310                $feedbackid = $false->id;
311            }
312            if ($correct) {
313                if ($correct[$true->id]) {
314                    $truecorrect = "CLASS=highlight";
315                }
316                if ($correct[$false->id]) {
317                    $falsecorrect = "CLASS=highlight";
318                }
319            }
320            echo "<TABLE ALIGN=right cellpadding=5><TR><TD align=right>$stranswer:&nbsp;&nbsp;";
321            echo "<TD $truecorrect>";
322            echo "<INPUT $truechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$true->id\">$true->answer";
323            echo "</TD><TD $falsecorrect>";
324            echo "<INPUT $falsechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$false->id\">$false->answer</P>";
325            echo "</TD></TR></TABLE><BR CLEAR=ALL>";
326            if ($feedback) {
327                quiz_print_comment("<P ALIGN=right>$feedback[$feedbackid]</P>");
328            }
330            break;
332        case MULTICHOICE:
333            if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
334                notify("Error: Missing question options!");
335            }
336            if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
337                notify("Error: Missing question answers!");
338            }
339            echo text_to_html($question->questiontext);
340            if ($question->image) {
341                print_file_picture($question->image, $courseid, 200);
342            }
343            echo "<TABLE ALIGN=right>";
344            echo "<TR><TD valign=top>$stranswer:&nbsp;&nbsp;</TD><TD>";
345            echo "<TABLE ALIGN=right>";
346            $answerids = explode(",", $options->answers);
348            foreach ($answerids as $key => $answerid) {
349                $answer = $answers[$answerid];
350                $qnum = $key + 1;
352                if ($feedback and $response[$answerid]) {
353                    $checked = "CHECKED";
354                } else {
355                    $checked = "";
356                }
357                echo "<TR><TD valign=top>";
358                if ($options->single) {
359                    echo "<INPUT $checked TYPE=RADIO NAME=q$question->id VALUE=\"$answer->id\">";
360                } else {
361                    echo "<INPUT $checked TYPE=CHECKBOX NAME=q$question->id"."a$answer->id VALUE=\"$answer->id\">";
362                }
363                echo "</TD>";
364                if ($feedback and $correct[$answer->id]) {
365                    echo "<TD valign=top CLASS=highlight>$qnum. $answer->answer</TD>";
366                } else {
367                    echo "<TD valign=top>$qnum. $answer->answer</TD>";
368                }
369                if ($feedback) {
370                    echo "<TD valign=top>&nbsp;";
371                    if ($response[$answerid]) {
372                        quiz_print_comment($feedback[$answerid]);
373                    }
374                    echo "</TD>";
375                }
376                echo "</TR>";
377            }
378            echo "</TABLE>";
379            echo "</TABLE>";
380            break;
382        case RANDOM:
383            echo "<P>Random questions not supported yet</P>";
384            break;
387        default: 
388            notify("Error: Unknown question type!");
389     }
391     echo "</TD></TR></TABLE>";
394 function quiz_print_quiz_questions($quiz, $results=NULL) {
395 // Prints a whole quiz on one page.
397     if (!$quiz->questions) {
398         notify("No questions have been defined!", "view.php?id=$cm->id");
399         return false;
400     }
402     $questions = explode(",", $quiz->questions);
404     if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) {
405         notify("No grades were found for these questions!");
406         return false;
407     }
409     echo "<FORM METHOD=POST ACTION=attempt.php>";
410     echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">";
412     foreach ($questions as $key => $questionid) {
413         $feedback       = NULL;
414         $response       = NULL;
415         $actualgrades   = NULL;
416         $correct        = NULL;
417         if ($results) {
418             $feedback      = $results->feedback[$questionid];
419             $response      = $results->response[$questionid];
420             $actualgrades  = $results->grades[$questionid];
421             if ($quiz->correctanswers) {
422                 $correct   = $results->correct[$questionid];
423             }
424         }
426         print_simple_box_start("CENTER", "90%");
427         quiz_print_question($key+1, $questionid, $grades[$questionid]->grade, $quiz->course, 
428                             $feedback, $response, $actualgrades, $correct);
429         print_simple_box_end();
430         echo "<BR>";
431     }
433     if (!$results) {
434         echo "<CENTER><INPUT TYPE=submit VALUE=\"".get_string("savemyanswers", "quiz")."\"></CENTER>";
435     }
436     echo "</FORM>";
438     return true;
440  
441 function quiz_get_default_category($courseid) {
442     if ($categories = get_records("quiz_categories", "course", $courseid, "id")) {
443         foreach ($categories as $category) {
444             return $category;   // Return the first one (lowest id)
445         }
446     }
448     // Otherwise, we need to make one
449     $category->name = get_string("default", "quiz");
450     $category->info = get_string("defaultinfo", "quiz");
451     $category->course = $courseid;
452     $category->publish = 0;
454     if (!$category->id = insert_record("quiz_categories", $category)) {
455         notify("Error creating a default category!");
456         return false;
457     }
458     return $category;
461 function quiz_get_category_menu($courseid, $published=false) {
462     if ($published) {
463         $publish = "OR publish = '1'";
464     }
465     return get_records_sql_menu("SELECT id,name FROM quiz_categories WHERE course='$courseid' $publish ORDER by name ASC");
468 function quiz_print_category_form($course, $current) {
469 // Prints a form to choose categories
471     if (!$categories = get_records_sql("SELECT * FROM quiz_categories WHERE course='$course->id' OR publish = '1' ORDER by name ASC")) {
472         if (!$category = quiz_get_default_category($course->id)) {
473             notify("Error creating a default category!");
474             return false;
475         }
476     }
477     foreach ($categories as $key => $category) {
478        if ($category->publish) {
479            if ($catcourse = get_record("course", "id", $category->course)) {
480                $category->name .= " ($catcourse->shortname)";
481            }
482        }
483        $catmenu[$category->id] = $category->name;
484     }
485     $strcategory = get_string("category", "quiz");
486     $strshow = get_string("show", "quiz");
487     $streditcats = get_string("editcategories", "quiz");
489     echo "<TABLE width=\"100%\"><TR><TD>";
490     echo "<FORM METHOD=POST ACTION=edit.php>"; 
491     echo "<B>$strcategory:</B>&nbsp;";
492     choose_from_menu($catmenu, "cat", "$current");
493     echo "<INPUT TYPE=submit VALUE=\"$strshow\">";
494     echo "</FORM>";
495     echo "</TD><TD align=right>";
496     echo "<FORM METHOD=GET ACTION=category.php>"; 
497     echo "<INPUT TYPE=hidden NAME=id VALUE=\"$course->id\">";
498     echo "<INPUT TYPE=submit VALUE=\"$streditcats\">";
499     echo "</FORM>";
500     echo "</TD></TR></TABLE>";
504 function quiz_get_all_question_grades($questionlist, $quizid) {
505 // Given a list of question IDs, finds grades or invents them to 
506 // create an array of matching grades
508     $questions = get_records_sql("SELECT question,grade FROM quiz_question_grades 
509                                   WHERE quiz = '$quizid' 
510                                     AND question IN ($questionlist)");
512     $list = explode(",", $questionlist);
513     $grades = array();
515     foreach ($list as $qid) {
516         if (isset($questions[$qid])) {
517             $grades[$qid] = $questions[$qid]->grade;
518         } else {
519             $grades[$qid] = 1;
520         }
521     }
522     return $grades;
526 function quiz_print_question_list($questionlist, $grades) {
527 // Prints a list of quiz questions in a small layout form with knobs
528 // $questionlist is comma-separated list
529 // $grades is an array of corresponding grades
531     global $THEME;
533     if (!$questionlist) {
534         echo "<P align=center>";
535         print_string("noquestions", "quiz");
536         echo "</P>";
537         return;
538     }
540     $order = explode(",", $questionlist);
542     if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) {
543         error("No questions were found!");
544     }
546     $strorder = get_string("order");
547     $strquestionname = get_string("questionname", "quiz");
548     $strgrade = get_string("grade");
549     $strdelete = get_string("delete");
550     $stredit = get_string("edit");
551     $strmoveup = get_string("moveup");
552     $strmovedown = get_string("movedown");
553     $strsavegrades = get_string("savegrades", "quiz");
554     $strtype = get_string("type", "quiz");
556     for ($i=10; $i>=0; $i--) {
557         $gradesmenu[$i] = $i;
558     }
559     $count = 0;
560     $sumgrade = 0;
561     $total = count($order);
562     echo "<FORM METHOD=post ACTION=edit.php>";
563     echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
564     echo "<TR><TH WIDTH=10 COLSPAN=3>$strorder</TH><TH align=left WIDTH=\"100%\">$strquestionname</TH><TH width=16>$strtype</TH><TH WIDTH=10>$strgrade</TH><TH WIDTH=10>$stredit</TH></TR>";
565     foreach ($order as $qnum) {
566         $count++;
567         echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
568         echo "<TD>$count</TD>";
569         echo "<TD>";
570         if ($count != 1) {
571             echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG 
572                  SRC=\"../../pix/t/up.gif\" BORDER=0></A>";
573         }
574         echo "</TD>";
575         echo "<TD>";
576         if ($count != $total) {
577             echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG 
578                  SRC=\"../../pix/t/down.gif\" BORDER=0></A>";
579         }
580         echo "</TD>";
581         echo "<TD>".$questions[$qnum]->name."</TD>";
582         echo "<TD WIDTH=16 ALIGN=CENTER>";
583         quiz_print_question_icon($questions[$qnum]);
584         echo "</TD>";
585         echo "<TD>";
586         choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], "");
587         echo "<TD>";
588             echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG 
589                  SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
590             echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG 
591                  SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
592         echo "</TD>";
594         $sumgrade += $grades[$qnum];
595     }
596     echo "<TR><TD COLSPAN=5 ALIGN=right>";
597     echo "<INPUT TYPE=submit VALUE=\"$strsavegrades:\">";
598     echo "<INPUT TYPE=hidden NAME=setgrades VALUE=\"save\">";
599     echo "<TD ALIGN=LEFT BGCOLOR=\"$THEME->cellcontent\">";
600     echo "<B>$sumgrade</B>";
601     echo "</TD><TD></TD></TR>";
602     echo "</TABLE>";
603     echo "</FORM>";
605     return $sumgrade;
609 function quiz_print_cat_question_list($categoryid) {
610 // Prints a form to choose categories
612     global $THEME, $QUIZ_QUESTION_TYPE;
614     $strcategory = get_string("category", "quiz");
615     $strquestion = get_string("question", "quiz");
616     $strnoquestions = get_string("noquestions", "quiz");
617     $strselect = get_string("select", "quiz");
618     $strcreatenewquestion = get_string("createnewquestion", "quiz");
619     $strquestionname = get_string("questionname", "quiz");
620     $strdelete = get_string("delete");
621     $stredit = get_string("edit");
622     $straddselectedtoquiz = get_string("addselectedtoquiz", "quiz");
623     $strtype = get_string("type", "quiz");
625     if (!$categoryid) {
626         echo "<P align=center>";
627         print_string("selectcategoryabove", "quiz");
628         echo "</P>";
629         return;
630     }
632     if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
633         notify("Category not found!");
634         return;
635     }
636     echo "<CENTER>";
637     echo text_to_html($category->info);
639     echo "<FORM METHOD=GET ACTION=question.php>"; 
640     echo "<B>$strquestion:</B>&nbsp;";
641     choose_from_menu($QUIZ_QUESTION_TYPE, "type", "", "");
642     echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
643     echo "<INPUT TYPE=submit NAME=new VALUE=\"$strcreatenewquestion\">";
644     echo "</FORM>";
645     echo "</CENTER>";
647     if (!$questions = get_records("quiz_questions", "category", $category->id)) {
648         echo "<P align=center>";
649         print_string("noquestions", "quiz");
650         echo "</P>";
651         return;
652     }
654     $canedit = isteacher($category->course);
656     echo "<FORM METHOD=post ACTION=edit.php>";
657     echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
658     echo "<TR><TH width=10>$strselect</TH><TH width=* align=left>$strquestionname</TH><TH WIDTH=16>$strtype</TH>";
659     if ($canedit) {
660         echo "<TH width=10>$stredit</TH>";
661     }
662     echo "</TR>";
663     foreach ($questions as $question) {
664         echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
665         echo "<TD ALIGN=CENTER>";
666         echo "<INPUT TYPE=CHECKBOX NAME=q$question->id VALUE=\"1\">";
667         echo "</TD>";
668         echo "<TD>".$question->name."</TD>";
669         echo "<TD WIDTH=16 ALIGN=CENTER>";
670         quiz_print_question_icon($question);
671         echo "</TD>";
672         if ($canedit) {
673             echo "<TD>";
674                 echo "<A TITLE=\"$strdelete\" HREF=\"question.php?id=$question->id&delete=$question->id\"><IMG 
675                      SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
676                 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG 
677                      SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
678             echo "</TD></TR>";
679         }
680         echo "</TR>";
681     }
682     echo "<TR><TD COLSPAN=3>";
683     echo "<INPUT TYPE=hidden NAME=add VALUE=\"1\">";
684     echo "<INPUT TYPE=submit VALUE=\"<< $straddselectedtoquiz\">";
685     echo "</TD></TR>";
686     echo "</TABLE>";
687     echo "</FORM>";
691 function quiz_start_attempt($quizid, $userid, $numattempt) {
692     $attempt->quiz = $quizid;
693     $attempt->user = $userid;
694     $attempt->attempt = $numattempt;
695     $attempt->timestart = time();
696     $attempt->timefinish = 0; 
697     $attempt->timemodified = time();
699     return insert_record("quiz_attempts", $attempt);
702 function quiz_get_user_attempt_unfinished($quizid, $userid) {
703 // Returns an object containing an unfinished attempt (if there is one)
704     return get_record_sql("SELECT * FROM quiz_attempts 
705                            WHERE quiz = '$quizid' and user = '$userid' AND timefinish = 0");
708 function quiz_get_user_attempts($quizid, $userid) {
709 // Returns a list of all attempts by a user
710     return get_records_sql("SELECT * FROM quiz_attempts 
711                             WHERE quiz = '$quizid' and user = '$userid' AND timefinish > 0 
712                             ORDER by attempt ASC");
716 function quiz_get_user_attempts_string($quiz, $attempts, $bestgrade) {
717 /// Returns a simple little comma-separated list of all attempts, 
718 /// with each grade linked to the feedback report and with the best grade highlighted
720     $bestgrade = format_float($bestgrade);
721     foreach ($attempts as $attempt) {
722         $attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade);
723         if ($attemptgrade == $bestgrade) {
724             $userattempts[] = "<SPAN CLASS=highlight><A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A></SPAN>";
725         } else {
726             $userattempts[] = "<A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A>";
727         }
728     }
729     return implode(",", $userattempts);
732 function quiz_get_best_grade($quizid, $userid) {
733 /// Get the best current grade for a particular user in a quiz
734     if (!$grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz = '$quizid' and user = '$userid'")) {
735         return 0;
736     }
738     return (round($grade->grade,0));
741 function quiz_get_grade_records($quiz) {
742 /// Gets all info required to display the table of quiz results
743 /// for report.php
745     return get_records_sql("SELECT qg.*, u.firstname, u.lastname, u.picture 
746                             FROM quiz_grades qg, user u
747                             WHERE qg.quiz = '$quiz->id'
748                               AND qg.user = u.id");
751 function quiz_save_best_grade($quiz, $user) {
752 /// Calculates the best grade out of all attempts at a quiz for a user,
753 /// and then saves that grade in the quiz_grades table.
755     if (!$attempts = quiz_get_user_attempts($quiz->id, $user->id)) {
756         notify("Could not find any user attempts");
757         return false;
758     }
760     $bestgrade = quiz_calculate_best_grade($quiz, $attempts);
761     $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade);
763     if ($grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz='$quiz->id' AND user='$user->id'")) {
764         $grade->grade = $bestgrade;
765         $grade->timemodified = time();
766         if (!update_record("quiz_grades", $grade)) {
767             notify("Could not update best grade");
768             return false;
769         }
770     } else {
771         $grade->quiz = $quiz->id;
772         $grade->user = $user->id;
773         $grade->grade = round($bestgrade, 2);
774         $grade->timemodified = time();
775         if (!insert_record("quiz_grades", $grade)) {
776             notify("Could not insert new best grade");
777             return false;
778         }
779     }
780     return true;
784 function quiz_get_answers($question) {
785 // Given a question, returns the correct answers and grades
786     switch ($question->type) {
787         case SHORTANSWER;       // Could be multiple answers
788             return get_records_sql("SELECT a.*, sa.usecase, g.grade
789                                       FROM quiz_shortanswer sa, quiz_answers a, quiz_question_grades g
790                                      WHERE sa.question = '$question->id' 
791                                        AND sa.question = a.question
792                                        AND sa.question = g.question");
793             break;
795         case TRUEFALSE;         // Should be always two answers
796             return get_records_sql("SELECT a.*, g.grade
797                                       FROM quiz_answers a, quiz_question_grades g
798                                      WHERE a.question = '$question->id' 
799                                        AND a.question = g.question");
800             break;
802         case MULTICHOICE;       // Should be multiple answers
803             return get_records_sql("SELECT a.*, mc.single, g.grade
804                                       FROM quiz_multichoice mc, quiz_answers a, quiz_question_grades g
805                                      WHERE mc.question = '$question->id' 
806                                        AND mc.question = a.question 
807                                        AND mc.question = g.question");
808             break;
810        case RANDOM:
811            return false;  // Not done yet
812            break;
814         default:
815             return false;
816     }
819 function quiz_calculate_best_grade($quiz, $attempts) {
820 /// Calculate the best grade for a quiz given a number of attempts by a particular user.
822     switch ($quiz->grademethod) {
824         case ATTEMPTFIRST:
825             foreach ($attempts as $attempt) {
826                 return $attempt->sumgrades;
827             }
828             break;
830         case ATTEMPTLAST:
831             foreach ($attempts as $attempt) {
832                 $final = $attempt->sumgrades;
833             }
834             return $final;
836         case GRADEAVERAGE:
837             $sum = 0;
838             $count = 0;
839             foreach ($attempts as $attempt) {
840                 $sum += $attempt->sumgrades;
841                 $count++;
842             }
843             return (float)$sum/$count;
845         default:
846         case GRADEHIGHEST:
847             $max = 0;
848             foreach ($attempts as $attempt) {
849                 if ($attempt->sumgrades > $max) {
850                     $max = $attempt->sumgrades;
851                 }
852             }
853             return $max;
854     }
857 function quiz_save_attempt($quiz, $questions, $result, $attemptnum) {
858 /// Given a quiz, a list of attempted questions and a total grade 
859 /// this function saves EVERYTHING so it can be reconstructed later
860 /// if necessary.
862     global $USER;
864     // First find the attempt in the database (start of attempt)
866     if (!$attempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
867         notify("Trying to save an attempt that was not started!");
868         return false;
869     }
871     if ($attempt->attempt != $attemptnum) {  // Double check.
872         notify("Number of this attempt is different to the unfinished one!");
873         return false;
874     }
876     // Now let's complete this record and save it
878     $attempt->sumgrades = $result->sumgrades;
879     $attempt->timefinish = time();
880     $attempt->timemodified = time();
882     if (! update_record("quiz_attempts", $attempt)) {
883         notify("Error while saving attempt");
884         return false;
885     }
887     // Now let's save all the questions for this attempt
889     foreach ($questions as $question) {
890         $response->attempt = $attempt->id;
891         $response->question = $question->id;
892         $response->grade = $result->grades[$question->id];
893         if ($question->answer) {
894             $response->answer = implode(",",$question->answer);
895         } else {
896             $response->answer = "";
897         }
898         if (!insert_record("quiz_responses", $response)) {
899             notify("Error while saving response");
900             return false;
901         }
902     }
903     return true;
907 function quiz_grade_attempt_results($quiz, $questions) {
908 /// Given a list of questions (including answers for each one)
909 /// this function does all the hard work of calculating the 
910 /// grades for each question, as well as a total grade for 
911 /// for the whole quiz.  It returns everything in a structure 
912 /// that looks like:
913 /// $result->sumgrades    (sum of all grades for all questions)
914 /// $result->percentage   (Percentage of grades that were correct)
915 /// $result->grade        (final grade result for the whole quiz)
916 /// $result->grades[]     (array of grades, indexed by question id)
917 /// $result->response[]   (array of response arrays, indexed by question id)
918 /// $result->feedback[]   (array of feedback arrays, indexed by question id)
919 /// $result->correct[]    (array of feedback arrays, indexed by question id)
921     if (!$questions) {
922         error("No questions!");
923     }
924     
925     $result->sumgrades = 0;
927     foreach ($questions as $question) {
928         if (!$answers = quiz_get_answers($question)) {
929             error("No answers defined for question id $question->id!");
930         }
932         $grade    = 0;   // default
933         $correct = array();
934         $feedback = array();
935         $response = array();
937         switch ($question->type) {
938             case SHORTANSWER:
939                 if ($question->answer) {
940                     $question->answer = $question->answer[0];
941                 } else {
942                     $question->answer = "";
943                 }
944                 $response[0] = $question->answer;
945                 foreach($answers as $answer) {  // There might be multiple right answers
946                     if ($answer->fraction > $bestshortanswer) {
947                         $correct[$answer->id] = $answer->answer;
948                     }
949                     if (!$answer->usecase) {       // Don't compare case
950                         $answer->answer = strtolower($answer->answer);
951                         $question->answer = strtolower($question->answer);
952                     }
953                     if ($question->answer == $answer->answer) {
954                         $feedback[0] = $answer->feedback;
955                         $grade = (float)$answer->fraction * $answer->grade;
956                     }
957                 }
958                 break;
961             case TRUEFALSE:
962                 if ($question->answer) {
963                     $question->answer = $question->answer[0];
964                 } else {
965                     $question->answer = NULL;
966                 }
967                 foreach($answers as $answer) {  // There should be two answers (true and false)
968                     $feedback[$answer->id] = $answer->feedback;
969                     if ($answer->fraction > 0) {
970                         $correct[$answer->id]  = true;
971                     }
972                     if ($question->answer == $answer->id) {
973                         $grade = (float)$answer->fraction * $answer->grade;
974                         $response[$answer->id] = true;
975                     }
976                 }
977                 break;
980             case MULTICHOICE:
981                 foreach($answers as $answer) {  // There will be multiple answers, perhaps more than one is right
982                     $feedback[$answer->id] = $answer->feedback;
983                     if ($answer->fraction > 0) {
984                         $correct[$answer->id] = true;
985                     }
986                     if ($question->answer) {
987                         foreach ($question->answer as $questionanswer) {
988                             if ($questionanswer == $answer->id) {
989                                 if ($answer->single) {
990                                     $grade = (float)$answer->fraction * $answer->grade;
991                                     $response[$answer->id] = true;
992                                     continue;
993                                 } else {
994                                     $grade += (float)$answer->fraction * $answer->grade;
995                                     $response[$answer->id] = true;
996                                 }
997                             }
998                         }
999                     }
1000                 }
1001                 break;
1002             case RANDOM:
1003                           // Not done yet
1004                 break;
1006             
1007         }
1008         if ($grade < 0.0) {   // No negative grades
1009             $grade = 0.0;
1010         }
1012         $result->grades[$question->id] = $grade;
1013         $result->sumgrades += $grade;
1014         $result->feedback[$question->id] = $feedback;
1015         $result->response[$question->id] = $response;
1016         $result->correct[$question->id] = $correct;
1017     }
1019     $fraction = (float)($result->sumgrades / $quiz->sumgrades);
1020     $result->percentage = format_float($fraction * 100.0);
1021     $result->grade      = format_float($fraction * $quiz->grade);
1023     return $result;
1027 function quiz_get_attempt_responses($attempt) {
1028 // Given an attempt object, this function gets all the 
1029 // stored responses and returns them in a format suitable
1030 // for regrading using quiz_grade_attempt_results()
1031    
1032     if (!$responses = get_records_sql("SELECT q.id, q.type, r.answer 
1033                                         FROM quiz_responses r, quiz_questions q
1034                                        WHERE r.attempt = '$attempt->id' 
1035                                          AND q.id = r.question")) {
1036         notify("Could not find any responses for that attempt!");
1037         return false;
1038     }
1040     foreach ($responses as $key => $response) {
1041         $responses[$key]->answer = explode(",",$response->answer);
1042     }
1044     return $responses;
1047     
1048 ?>