e788b2029f9be4fa083d74ff6735a61251d9a645
[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
226     global $QUIZ_QUESTION_TYPE;
228     echo "<A HREF=\"question.php?id=$question->id\" TITLE=\"".$QUIZ_QUESTION_TYPE[$question->type]."\">";
229     switch ($question->type) {
230         case SHORTANSWER:
231             echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/sa.gif\">";
232             break;
233         case TRUEFALSE:
234             echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/tf.gif\">";
235             break;
236         case MULTICHOICE:
237             echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/mc.gif\">";
238             break;
239         case RANDOM:
240             echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/rs.gif\">";
241             break;
242     }
243     echo "</A>\n";
246 function quiz_print_question($number, $questionid, $grade, $courseid, 
247                               $feedback=NULL, $response=NULL, $actualgrade=NULL, $correct=NULL) {
248 /// Prints a quiz question, any format
250     if (!$question = get_record("quiz_questions", "id", $questionid)) {
251         notify("Error: Question not found!");
252     }
254     $stranswer = get_string("answer", "quiz");
255     $strmarks  = get_string("marks", "quiz");
257     echo "<TABLE WIDTH=100% CELLSPACING=10><TR><TD NOWRAP WIDTH=100 VALIGN=top>";
258     echo "<P ALIGN=CENTER><B>$number</B></P>";
259     if ($feedback or $response) {
260         echo "<P ALIGN=CENTER><FONT SIZE=1>$strmarks: $actualgrade/$grade</FONT></P>";
261     } else {
262         echo "<P ALIGN=CENTER><FONT SIZE=1>$grade $strmarks</FONT></P>";
263     }
264     print_spacer(1,100);
265     echo "</TD><TD VALIGN=TOP>";
267     switch ($question->type) {
268        case SHORTANSWER: 
269            if (!$options = get_record("quiz_shortanswer", "question", $question->id)) {
270                notify("Error: Missing question options!");
271            }
272            echo text_to_html($question->questiontext);
273            if ($question->image) {
274                print_file_picture($question->image, $courseid, 200);
275            }
276            if ($response) {
277                $value = "VALUE=\"$response[0]\"";
278            }
279            echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$question->id SIZE=20 $value></P>";
280            if ($feedback) {
281                quiz_print_comment("<P ALIGN=right>$feedback[0]</P>");
282            }
283            if ($correct) {
284                $correctanswers = implode(", ", $correct);
285                quiz_print_correctanswer($correctanswers);
286            }
287            break;
289        case TRUEFALSE:
290            if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
291                notify("Error: Missing question options!");
292            }
293            if (!$true = get_record("quiz_answers", "id", $options->true)) {
294                notify("Error: Missing question answers!");
295            }
296            if (!$false = get_record("quiz_answers", "id", $options->false)) {
297                notify("Error: Missing question answers!");
298            }
299            if (!$true->answer) {
300                $true->answer = get_string("true", "quiz");
301            }
302            if (!$false->answer) {
303                $false->answer = get_string("false", "quiz");
304            }
305            echo text_to_html($question->questiontext);
306            if ($question->image) {
307                print_file_picture($question->image, $courseid, 200);
308            }
310            if ($response[$true->id]) {
311                $truechecked = "CHECKED";
312                $feedbackid = $true->id;
313            } else if ($response[$false->id]) {
314                $falsechecked = "CHECKED";
315                $feedbackid = $false->id;
316            }
317            if ($correct) {
318                if ($correct[$true->id]) {
319                    $truecorrect = "CLASS=highlight";
320                }
321                if ($correct[$false->id]) {
322                    $falsecorrect = "CLASS=highlight";
323                }
324            }
325            echo "<TABLE ALIGN=right cellpadding=5><TR><TD align=right>$stranswer:&nbsp;&nbsp;";
326            echo "<TD $truecorrect>";
327            echo "<INPUT $truechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$true->id\">$true->answer";
328            echo "</TD><TD $falsecorrect>";
329            echo "<INPUT $falsechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$false->id\">$false->answer</P>";
330            echo "</TD></TR></TABLE><BR CLEAR=ALL>";
331            if ($feedback) {
332                quiz_print_comment("<P ALIGN=right>$feedback[$feedbackid]</P>");
333            }
335            break;
337        case MULTICHOICE:
338            if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
339                notify("Error: Missing question options!");
340            }
341            if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
342                notify("Error: Missing question answers!");
343            }
344            echo text_to_html($question->questiontext);
345            if ($question->image) {
346                print_file_picture($question->image, $courseid, 200);
347            }
348            echo "<TABLE ALIGN=right>";
349            echo "<TR><TD valign=top>$stranswer:&nbsp;&nbsp;</TD><TD>";
350            echo "<TABLE ALIGN=right>";
351            $answerids = explode(",", $options->answers);
353            foreach ($answerids as $key => $answerid) {
354                $answer = $answers[$answerid];
355                $qnum = $key + 1;
357                if ($feedback and $response[$answerid]) {
358                    $checked = "CHECKED";
359                } else {
360                    $checked = "";
361                }
362                echo "<TR><TD valign=top>";
363                if ($options->single) {
364                    echo "<INPUT $checked TYPE=RADIO NAME=q$question->id VALUE=\"$answer->id\">";
365                } else {
366                    echo "<INPUT $checked TYPE=CHECKBOX NAME=q$question->id"."a$answer->id VALUE=\"$answer->id\">";
367                }
368                echo "</TD>";
369                if ($feedback and $correct[$answer->id]) {
370                    echo "<TD valign=top CLASS=highlight>$qnum. $answer->answer</TD>";
371                } else {
372                    echo "<TD valign=top>$qnum. $answer->answer</TD>";
373                }
374                if ($feedback) {
375                    echo "<TD valign=top>&nbsp;";
376                    if ($response[$answerid]) {
377                        quiz_print_comment($feedback[$answerid]);
378                    }
379                    echo "</TD>";
380                }
381                echo "</TR>";
382            }
383            echo "</TABLE>";
384            echo "</TABLE>";
385            break;
387        case RANDOM:
388            echo "<P>Random questions not supported yet</P>";
389            break;
392        default: 
393            notify("Error: Unknown question type!");
394     }
396     echo "</TD></TR></TABLE>";
399 function quiz_print_quiz_questions($quiz, $results=NULL) {
400 // Prints a whole quiz on one page.
402     if (!$quiz->questions) {
403         notify("No questions have been defined!", "view.php?id=$cm->id");
404         return false;
405     }
407     $questions = explode(",", $quiz->questions);
409     if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) {
410         notify("No grades were found for these questions!");
411         return false;
412     }
414     echo "<FORM METHOD=POST ACTION=attempt.php>";
415     echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">";
417     foreach ($questions as $key => $questionid) {
418         $feedback       = NULL;
419         $response       = NULL;
420         $actualgrades   = NULL;
421         $correct        = NULL;
422         if ($results) {
423             $feedback      = $results->feedback[$questionid];
424             $response      = $results->response[$questionid];
425             $actualgrades  = $results->grades[$questionid];
426             if ($quiz->correctanswers) {
427                 $correct   = $results->correct[$questionid];
428             }
429         }
431         print_simple_box_start("CENTER", "90%");
432         quiz_print_question($key+1, $questionid, $grades[$questionid]->grade, $quiz->course, 
433                             $feedback, $response, $actualgrades, $correct);
434         print_simple_box_end();
435         echo "<BR>";
436     }
438     if (!$results) {
439         echo "<CENTER><INPUT TYPE=submit VALUE=\"".get_string("savemyanswers", "quiz")."\"></CENTER>";
440     }
441     echo "</FORM>";
443     return true;
445  
446 function quiz_get_default_category($courseid) {
447     if ($categories = get_records("quiz_categories", "course", $courseid, "id")) {
448         foreach ($categories as $category) {
449             return $category;   // Return the first one (lowest id)
450         }
451     }
453     // Otherwise, we need to make one
454     $category->name = get_string("default", "quiz");
455     $category->info = get_string("defaultinfo", "quiz");
456     $category->course = $courseid;
457     $category->publish = 0;
459     if (!$category->id = insert_record("quiz_categories", $category)) {
460         notify("Error creating a default category!");
461         return false;
462     }
463     return $category;
466 function quiz_get_category_menu($courseid, $published=false) {
467     if ($published) {
468         $publish = "OR publish = '1'";
469     }
470     return get_records_sql_menu("SELECT id,name FROM quiz_categories WHERE course='$courseid' $publish ORDER by name ASC");
473 function quiz_print_category_form($course, $current) {
474 // Prints a form to choose categories
476     if (!$categories = get_records_sql("SELECT * FROM quiz_categories WHERE course='$course->id' OR publish = '1' ORDER by name ASC")) {
477         if (!$category = quiz_get_default_category($course->id)) {
478             notify("Error creating a default category!");
479             return false;
480         }
481     }
482     foreach ($categories as $key => $category) {
483        if ($category->publish) {
484            if ($catcourse = get_record("course", "id", $category->course)) {
485                $category->name .= " ($catcourse->shortname)";
486            }
487        }
488        $catmenu[$category->id] = $category->name;
489     }
490     $strcategory = get_string("category", "quiz");
491     $strshow = get_string("show", "quiz");
492     $streditcats = get_string("editcategories", "quiz");
494     echo "<TABLE width=\"100%\"><TR><TD>";
495     echo "<FORM METHOD=POST ACTION=edit.php>"; 
496     echo "<B>$strcategory:</B>&nbsp;";
497     choose_from_menu($catmenu, "cat", "$current");
498     echo "<INPUT TYPE=submit VALUE=\"$strshow\">";
499     echo "</FORM>";
500     echo "</TD><TD align=right>";
501     echo "<FORM METHOD=GET ACTION=category.php>"; 
502     echo "<INPUT TYPE=hidden NAME=id VALUE=\"$course->id\">";
503     echo "<INPUT TYPE=submit VALUE=\"$streditcats\">";
504     echo "</FORM>";
505     echo "</TD></TR></TABLE>";
509 function quiz_get_all_question_grades($questionlist, $quizid) {
510 // Given a list of question IDs, finds grades or invents them to 
511 // create an array of matching grades
513     $questions = get_records_sql("SELECT question,grade FROM quiz_question_grades 
514                                   WHERE quiz = '$quizid' 
515                                     AND question IN ($questionlist)");
517     $list = explode(",", $questionlist);
518     $grades = array();
520     foreach ($list as $qid) {
521         if (isset($questions[$qid])) {
522             $grades[$qid] = $questions[$qid]->grade;
523         } else {
524             $grades[$qid] = 1;
525         }
526     }
527     return $grades;
531 function quiz_print_question_list($questionlist, $grades) {
532 // Prints a list of quiz questions in a small layout form with knobs
533 // $questionlist is comma-separated list
534 // $grades is an array of corresponding grades
536     global $THEME;
538     if (!$questionlist) {
539         echo "<P align=center>";
540         print_string("noquestions", "quiz");
541         echo "</P>";
542         return;
543     }
545     $order = explode(",", $questionlist);
547     if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) {
548         error("No questions were found!");
549     }
551     $strorder = get_string("order");
552     $strquestionname = get_string("questionname", "quiz");
553     $strgrade = get_string("grade");
554     $strdelete = get_string("delete");
555     $stredit = get_string("edit");
556     $strmoveup = get_string("moveup");
557     $strmovedown = get_string("movedown");
558     $strsavegrades = get_string("savegrades", "quiz");
559     $strtype = get_string("type", "quiz");
561     for ($i=10; $i>=0; $i--) {
562         $gradesmenu[$i] = $i;
563     }
564     $count = 0;
565     $sumgrade = 0;
566     $total = count($order);
567     echo "<FORM METHOD=post ACTION=edit.php>";
568     echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
569     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>";
570     foreach ($order as $qnum) {
571         $count++;
572         echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
573         echo "<TD>$count</TD>";
574         echo "<TD>";
575         if ($count != 1) {
576             echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG 
577                  SRC=\"../../pix/t/up.gif\" BORDER=0></A>";
578         }
579         echo "</TD>";
580         echo "<TD>";
581         if ($count != $total) {
582             echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG 
583                  SRC=\"../../pix/t/down.gif\" BORDER=0></A>";
584         }
585         echo "</TD>";
586         echo "<TD>".$questions[$qnum]->name."</TD>";
587         echo "<TD WIDTH=16 ALIGN=CENTER>";
588         quiz_print_question_icon($questions[$qnum]);
589         echo "</TD>";
590         echo "<TD>";
591         choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], "");
592         echo "<TD>";
593             echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG 
594                  SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
595             echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG 
596                  SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
597         echo "</TD>";
599         $sumgrade += $grades[$qnum];
600     }
601     echo "<TR><TD COLSPAN=5 ALIGN=right>";
602     echo "<INPUT TYPE=submit VALUE=\"$strsavegrades:\">";
603     echo "<INPUT TYPE=hidden NAME=setgrades VALUE=\"save\">";
604     echo "<TD ALIGN=LEFT BGCOLOR=\"$THEME->cellcontent\">";
605     echo "<B>$sumgrade</B>";
606     echo "</TD><TD></TD></TR>";
607     echo "</TABLE>";
608     echo "</FORM>";
610     return $sumgrade;
614 function quiz_print_cat_question_list($categoryid) {
615 // Prints a form to choose categories
617     global $THEME, $QUIZ_QUESTION_TYPE;
619     $strcategory = get_string("category", "quiz");
620     $strquestion = get_string("question", "quiz");
621     $strnoquestions = get_string("noquestions", "quiz");
622     $strselect = get_string("select", "quiz");
623     $strcreatenewquestion = get_string("createnewquestion", "quiz");
624     $strquestionname = get_string("questionname", "quiz");
625     $strdelete = get_string("delete");
626     $stredit = get_string("edit");
627     $straddselectedtoquiz = get_string("addselectedtoquiz", "quiz");
628     $strtype = get_string("type", "quiz");
630     if (!$categoryid) {
631         echo "<P align=center>";
632         print_string("selectcategoryabove", "quiz");
633         echo "</P>";
634         return;
635     }
637     if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
638         notify("Category not found!");
639         return;
640     }
641     echo "<CENTER>";
642     echo text_to_html($category->info);
644     echo "<FORM METHOD=GET ACTION=question.php>"; 
645     echo "<B>$strquestion:</B>&nbsp;";
646     choose_from_menu($QUIZ_QUESTION_TYPE, "type", "", "");
647     echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
648     echo "<INPUT TYPE=submit NAME=new VALUE=\"$strcreatenewquestion\">";
649     helpbutton("questiontypes", $strcreatenewquestion, "quiz");
650     echo "</FORM>";
651     echo "</CENTER>";
653     if (!$questions = get_records("quiz_questions", "category", $category->id)) {
654         echo "<P align=center>";
655         print_string("noquestions", "quiz");
656         echo "</P>";
657         return;
658     }
660     $canedit = isteacher($category->course);
662     echo "<FORM METHOD=post ACTION=edit.php>";
663     echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
664     echo "<TR><TH width=10>$strselect</TH><TH width=* align=left>$strquestionname</TH><TH WIDTH=16>$strtype</TH>";
665     if ($canedit) {
666         echo "<TH width=10>$stredit</TH>";
667     }
668     echo "</TR>";
669     foreach ($questions as $question) {
670         echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
671         echo "<TD ALIGN=CENTER>";
672         echo "<INPUT TYPE=CHECKBOX NAME=q$question->id VALUE=\"1\">";
673         echo "</TD>";
674         echo "<TD>".$question->name."</TD>";
675         echo "<TD WIDTH=16 ALIGN=CENTER>";
676         quiz_print_question_icon($question);
677         echo "</TD>";
678         if ($canedit) {
679             echo "<TD>";
680                 echo "<A TITLE=\"$strdelete\" HREF=\"question.php?id=$question->id&delete=$question->id\"><IMG 
681                      SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
682                 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG 
683                      SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
684             echo "</TD></TR>";
685         }
686         echo "</TR>";
687     }
688     echo "<TR><TD COLSPAN=3>";
689     echo "<INPUT TYPE=hidden NAME=add VALUE=\"1\">";
690     echo "<INPUT TYPE=submit VALUE=\"<< $straddselectedtoquiz\">";
691     echo "</TD></TR>";
692     echo "</TABLE>";
693     echo "</FORM>";
697 function quiz_start_attempt($quizid, $userid, $numattempt) {
698     $attempt->quiz = $quizid;
699     $attempt->user = $userid;
700     $attempt->attempt = $numattempt;
701     $attempt->timestart = time();
702     $attempt->timefinish = 0; 
703     $attempt->timemodified = time();
705     return insert_record("quiz_attempts", $attempt);
708 function quiz_get_user_attempt_unfinished($quizid, $userid) {
709 // Returns an object containing an unfinished attempt (if there is one)
710     return get_record_sql("SELECT * FROM quiz_attempts 
711                            WHERE quiz = '$quizid' and user = '$userid' AND timefinish = 0");
714 function quiz_get_user_attempts($quizid, $userid) {
715 // Returns a list of all attempts by a user
716     return get_records_sql("SELECT * FROM quiz_attempts 
717                             WHERE quiz = '$quizid' and user = '$userid' AND timefinish > 0 
718                             ORDER by attempt ASC");
722 function quiz_get_user_attempts_string($quiz, $attempts, $bestgrade) {
723 /// Returns a simple little comma-separated list of all attempts, 
724 /// with each grade linked to the feedback report and with the best grade highlighted
726     $bestgrade = format_float($bestgrade);
727     foreach ($attempts as $attempt) {
728         $attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade);
729         if ($attemptgrade == $bestgrade) {
730             $userattempts[] = "<SPAN CLASS=highlight><A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A></SPAN>";
731         } else {
732             $userattempts[] = "<A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A>";
733         }
734     }
735     return implode(",", $userattempts);
738 function quiz_get_best_grade($quizid, $userid) {
739 /// Get the best current grade for a particular user in a quiz
740     if (!$grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz = '$quizid' and user = '$userid'")) {
741         return 0;
742     }
744     return (round($grade->grade,0));
747 function quiz_get_grade_records($quiz) {
748 /// Gets all info required to display the table of quiz results
749 /// for report.php
751     return get_records_sql("SELECT qg.*, u.firstname, u.lastname, u.picture 
752                             FROM quiz_grades qg, user u
753                             WHERE qg.quiz = '$quiz->id'
754                               AND qg.user = u.id");
757 function quiz_save_best_grade($quiz, $userid) {
758 /// Calculates the best grade out of all attempts at a quiz for a user,
759 /// and then saves that grade in the quiz_grades table.
761     if (!$attempts = quiz_get_user_attempts($quiz->id, $userid)) {
762         notify("Could not find any user attempts");
763         return false;
764     }
766     $bestgrade = quiz_calculate_best_grade($quiz, $attempts);
767     $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade);
769     if ($grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz='$quiz->id' AND user='$userid'")) {
770         $grade->grade = $bestgrade;
771         $grade->timemodified = time();
772         if (!update_record("quiz_grades", $grade)) {
773             notify("Could not update best grade");
774             return false;
775         }
776     } else {
777         $grade->quiz = $quiz->id;
778         $grade->user = $userid;
779         $grade->grade = round($bestgrade, 2);
780         $grade->timemodified = time();
781         if (!insert_record("quiz_grades", $grade)) {
782             notify("Could not insert new best grade");
783             return false;
784         }
785     }
786     return true;
790 function quiz_get_answers($question) {
791 // Given a question, returns the correct answers and grades
792     switch ($question->type) {
793         case SHORTANSWER;       // Could be multiple answers
794             return get_records_sql("SELECT a.*, sa.usecase, g.grade
795                                       FROM quiz_shortanswer sa, quiz_answers a, quiz_question_grades g
796                                      WHERE sa.question = '$question->id' 
797                                        AND sa.question = a.question
798                                        AND sa.question = g.question");
799             break;
801         case TRUEFALSE;         // Should be always two answers
802             return get_records_sql("SELECT a.*, g.grade
803                                       FROM quiz_answers a, quiz_question_grades g
804                                      WHERE a.question = '$question->id' 
805                                        AND a.question = g.question");
806             break;
808         case MULTICHOICE;       // Should be multiple answers
809             return get_records_sql("SELECT a.*, mc.single, g.grade
810                                       FROM quiz_multichoice mc, quiz_answers a, quiz_question_grades g
811                                      WHERE mc.question = '$question->id' 
812                                        AND mc.question = a.question 
813                                        AND mc.question = g.question");
814             break;
816        case RANDOM:
817            return false;  // Not done yet
818            break;
820         default:
821             return false;
822     }
825 function quiz_calculate_best_grade($quiz, $attempts) {
826 /// Calculate the best grade for a quiz given a number of attempts by a particular user.
828     switch ($quiz->grademethod) {
830         case ATTEMPTFIRST:
831             foreach ($attempts as $attempt) {
832                 return $attempt->sumgrades;
833             }
834             break;
836         case ATTEMPTLAST:
837             foreach ($attempts as $attempt) {
838                 $final = $attempt->sumgrades;
839             }
840             return $final;
842         case GRADEAVERAGE:
843             $sum = 0;
844             $count = 0;
845             foreach ($attempts as $attempt) {
846                 $sum += $attempt->sumgrades;
847                 $count++;
848             }
849             return (float)$sum/$count;
851         default:
852         case GRADEHIGHEST:
853             $max = 0;
854             foreach ($attempts as $attempt) {
855                 if ($attempt->sumgrades > $max) {
856                     $max = $attempt->sumgrades;
857                 }
858             }
859             return $max;
860     }
863 function quiz_save_attempt($quiz, $questions, $result, $attemptnum) {
864 /// Given a quiz, a list of attempted questions and a total grade 
865 /// this function saves EVERYTHING so it can be reconstructed later
866 /// if necessary.
868     global $USER;
870     // First find the attempt in the database (start of attempt)
872     if (!$attempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
873         notify("Trying to save an attempt that was not started!");
874         return false;
875     }
877     if ($attempt->attempt != $attemptnum) {  // Double check.
878         notify("Number of this attempt is different to the unfinished one!");
879         return false;
880     }
882     // Now let's complete this record and save it
884     $attempt->sumgrades = $result->sumgrades;
885     $attempt->timefinish = time();
886     $attempt->timemodified = time();
888     if (! update_record("quiz_attempts", $attempt)) {
889         notify("Error while saving attempt");
890         return false;
891     }
893     // Now let's save all the questions for this attempt
895     foreach ($questions as $question) {
896         $response->attempt = $attempt->id;
897         $response->question = $question->id;
898         $response->grade = $result->grades[$question->id];
899         if ($question->answer) {
900             $response->answer = implode(",",$question->answer);
901         } else {
902             $response->answer = "";
903         }
904         if (!insert_record("quiz_responses", $response)) {
905             notify("Error while saving response");
906             return false;
907         }
908     }
909     return true;
913 function quiz_grade_attempt_results($quiz, $questions) {
914 /// Given a list of questions (including answers for each one)
915 /// this function does all the hard work of calculating the 
916 /// grades for each question, as well as a total grade for 
917 /// for the whole quiz.  It returns everything in a structure 
918 /// that looks like:
919 /// $result->sumgrades    (sum of all grades for all questions)
920 /// $result->percentage   (Percentage of grades that were correct)
921 /// $result->grade        (final grade result for the whole quiz)
922 /// $result->grades[]     (array of grades, indexed by question id)
923 /// $result->response[]   (array of response arrays, indexed by question id)
924 /// $result->feedback[]   (array of feedback arrays, indexed by question id)
925 /// $result->correct[]    (array of feedback arrays, indexed by question id)
927     if (!$questions) {
928         error("No questions!");
929     }
930     
931     $result->sumgrades = 0;
933     foreach ($questions as $question) {
934         if (!$answers = quiz_get_answers($question)) {
935             error("No answers defined for question id $question->id!");
936         }
938         $grade    = 0;   // default
939         $correct = array();
940         $feedback = array();
941         $response = array();
943         switch ($question->type) {
944             case SHORTANSWER:
945                 if ($question->answer) {
946                     $question->answer = trim($question->answer[0]);
947                 } else {
948                     $question->answer = "";
949                 }
950                 $response[0] = $question->answer;
951                 foreach($answers as $answer) {  // There might be multiple right answers
952                     if ($answer->fraction > $bestshortanswer) {
953                         $correct[$answer->id] = $answer->answer;
954                     }
955                     if (!$answer->usecase) {       // Don't compare case
956                         $answer->answer = strtolower($answer->answer);
957                         $question->answer = strtolower($question->answer);
958                     }
959                     if ($question->answer == $answer->answer) {
960                         $feedback[0] = $answer->feedback;
961                         $grade = (float)$answer->fraction * $answer->grade;
962                     }
963                 }
964                 break;
967             case TRUEFALSE:
968                 if ($question->answer) {
969                     $question->answer = $question->answer[0];
970                 } else {
971                     $question->answer = NULL;
972                 }
973                 foreach($answers as $answer) {  // There should be two answers (true and false)
974                     $feedback[$answer->id] = $answer->feedback;
975                     if ($answer->fraction > 0) {
976                         $correct[$answer->id]  = true;
977                     }
978                     if ($question->answer == $answer->id) {
979                         $grade = (float)$answer->fraction * $answer->grade;
980                         $response[$answer->id] = true;
981                     }
982                 }
983                 break;
986             case MULTICHOICE:
987                 foreach($answers as $answer) {  // There will be multiple answers, perhaps more than one is right
988                     $feedback[$answer->id] = $answer->feedback;
989                     if ($answer->fraction > 0) {
990                         $correct[$answer->id] = true;
991                     }
992                     if ($question->answer) {
993                         foreach ($question->answer as $questionanswer) {
994                             if ($questionanswer == $answer->id) {
995                                 if ($answer->single) {
996                                     $grade = (float)$answer->fraction * $answer->grade;
997                                     $response[$answer->id] = true;
998                                     continue;
999                                 } else {
1000                                     $grade += (float)$answer->fraction * $answer->grade;
1001                                     $response[$answer->id] = true;
1002                                 }
1003                             }
1004                         }
1005                     }
1006                 }
1007                 break;
1008             case RANDOM:
1009                           // Not done yet
1010                 break;
1012             
1013         }
1014         if ($grade < 0.0) {   // No negative grades
1015             $grade = 0.0;
1016         }
1018         $result->grades[$question->id] = $grade;
1019         $result->sumgrades += $grade;
1020         $result->feedback[$question->id] = $feedback;
1021         $result->response[$question->id] = $response;
1022         $result->correct[$question->id] = $correct;
1023     }
1025     $fraction = (float)($result->sumgrades / $quiz->sumgrades);
1026     $result->percentage = format_float($fraction * 100.0);
1027     $result->grade      = format_float($fraction * $quiz->grade);
1029     return $result;
1033 function quiz_get_attempt_responses($attempt) {
1034 // Given an attempt object, this function gets all the 
1035 // stored responses and returns them in a format suitable
1036 // for regrading using quiz_grade_attempt_results()
1037    
1038     if (!$responses = get_records_sql("SELECT q.id, q.type, r.answer 
1039                                         FROM quiz_responses r, quiz_questions q
1040                                        WHERE r.attempt = '$attempt->id' 
1041                                          AND q.id = r.question")) {
1042         notify("Could not find any responses for that attempt!");
1043         return false;
1044     }
1046     foreach ($responses as $key => $response) {
1047         $responses[$key]->answer = explode(",",$response->answer);
1048     }
1050     return $responses;
1053     
1054 ?>