Fixed minor bug after creating default category.
[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         $categories[$category->id] = $category;
482     }
483     foreach ($categories as $key => $category) {
484        if ($category->publish) {
485            if ($catcourse = get_record("course", "id", $category->course)) {
486                $category->name .= " ($catcourse->shortname)";
487            }
488        }
489        $catmenu[$category->id] = $category->name;
490     }
491     $strcategory = get_string("category", "quiz");
492     $strshow = get_string("show", "quiz");
493     $streditcats = get_string("editcategories", "quiz");
495     echo "<TABLE width=\"100%\"><TR><TD>";
496     echo "<FORM METHOD=POST ACTION=edit.php>"; 
497     echo "<B>$strcategory:</B>&nbsp;";
498     choose_from_menu($catmenu, "cat", "$current");
499     echo "<INPUT TYPE=submit VALUE=\"$strshow\">";
500     echo "</FORM>";
501     echo "</TD><TD align=right>";
502     echo "<FORM METHOD=GET ACTION=category.php>"; 
503     echo "<INPUT TYPE=hidden NAME=id VALUE=\"$course->id\">";
504     echo "<INPUT TYPE=submit VALUE=\"$streditcats\">";
505     echo "</FORM>";
506     echo "</TD></TR></TABLE>";
510 function quiz_get_all_question_grades($questionlist, $quizid) {
511 // Given a list of question IDs, finds grades or invents them to 
512 // create an array of matching grades
514     $questions = get_records_sql("SELECT question,grade FROM quiz_question_grades 
515                                   WHERE quiz = '$quizid' 
516                                     AND question IN ($questionlist)");
518     $list = explode(",", $questionlist);
519     $grades = array();
521     foreach ($list as $qid) {
522         if (isset($questions[$qid])) {
523             $grades[$qid] = $questions[$qid]->grade;
524         } else {
525             $grades[$qid] = 1;
526         }
527     }
528     return $grades;
532 function quiz_print_question_list($questionlist, $grades) {
533 // Prints a list of quiz questions in a small layout form with knobs
534 // $questionlist is comma-separated list
535 // $grades is an array of corresponding grades
537     global $THEME;
539     if (!$questionlist) {
540         echo "<P align=center>";
541         print_string("noquestions", "quiz");
542         echo "</P>";
543         return;
544     }
546     $order = explode(",", $questionlist);
548     if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) {
549         error("No questions were found!");
550     }
552     $strorder = get_string("order");
553     $strquestionname = get_string("questionname", "quiz");
554     $strgrade = get_string("grade");
555     $strdelete = get_string("delete");
556     $stredit = get_string("edit");
557     $strmoveup = get_string("moveup");
558     $strmovedown = get_string("movedown");
559     $strsavegrades = get_string("savegrades", "quiz");
560     $strtype = get_string("type", "quiz");
562     for ($i=10; $i>=0; $i--) {
563         $gradesmenu[$i] = $i;
564     }
565     $count = 0;
566     $sumgrade = 0;
567     $total = count($order);
568     echo "<FORM METHOD=post ACTION=edit.php>";
569     echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
570     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>";
571     foreach ($order as $qnum) {
572         $count++;
573         echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
574         echo "<TD>$count</TD>";
575         echo "<TD>";
576         if ($count != 1) {
577             echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG 
578                  SRC=\"../../pix/t/up.gif\" BORDER=0></A>";
579         }
580         echo "</TD>";
581         echo "<TD>";
582         if ($count != $total) {
583             echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG 
584                  SRC=\"../../pix/t/down.gif\" BORDER=0></A>";
585         }
586         echo "</TD>";
587         echo "<TD>".$questions[$qnum]->name."</TD>";
588         echo "<TD WIDTH=16 ALIGN=CENTER>";
589         quiz_print_question_icon($questions[$qnum]);
590         echo "</TD>";
591         echo "<TD>";
592         choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], "");
593         echo "<TD>";
594             echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG 
595                  SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
596             echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG 
597                  SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
598         echo "</TD>";
600         $sumgrade += $grades[$qnum];
601     }
602     echo "<TR><TD COLSPAN=5 ALIGN=right>";
603     echo "<INPUT TYPE=submit VALUE=\"$strsavegrades:\">";
604     echo "<INPUT TYPE=hidden NAME=setgrades VALUE=\"save\">";
605     echo "<TD ALIGN=LEFT BGCOLOR=\"$THEME->cellcontent\">";
606     echo "<B>$sumgrade</B>";
607     echo "</TD><TD></TD></TR>";
608     echo "</TABLE>";
609     echo "</FORM>";
611     return $sumgrade;
615 function quiz_print_cat_question_list($categoryid) {
616 // Prints a form to choose categories
618     global $THEME, $QUIZ_QUESTION_TYPE;
620     $strcategory = get_string("category", "quiz");
621     $strquestion = get_string("question", "quiz");
622     $strnoquestions = get_string("noquestions", "quiz");
623     $strselect = get_string("select", "quiz");
624     $strcreatenewquestion = get_string("createnewquestion", "quiz");
625     $strquestionname = get_string("questionname", "quiz");
626     $strdelete = get_string("delete");
627     $stredit = get_string("edit");
628     $straddselectedtoquiz = get_string("addselectedtoquiz", "quiz");
629     $strtype = get_string("type", "quiz");
631     if (!$categoryid) {
632         echo "<P align=center>";
633         print_string("selectcategoryabove", "quiz");
634         echo "</P>";
635         return;
636     }
638     if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
639         notify("Category not found!");
640         return;
641     }
642     echo "<CENTER>";
643     echo text_to_html($category->info);
645     echo "<FORM METHOD=GET ACTION=question.php>"; 
646     echo "<B>$strquestion:</B>&nbsp;";
647     choose_from_menu($QUIZ_QUESTION_TYPE, "type", "", "");
648     echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
649     echo "<INPUT TYPE=submit NAME=new VALUE=\"$strcreatenewquestion\">";
650     helpbutton("questiontypes", $strcreatenewquestion, "quiz");
651     echo "</FORM>";
652     echo "</CENTER>";
654     if (!$questions = get_records("quiz_questions", "category", $category->id)) {
655         echo "<P align=center>";
656         print_string("noquestions", "quiz");
657         echo "</P>";
658         return;
659     }
661     $canedit = isteacher($category->course);
663     echo "<FORM METHOD=post ACTION=edit.php>";
664     echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
665     echo "<TR><TH width=10>$strselect</TH><TH width=* align=left>$strquestionname</TH><TH WIDTH=16>$strtype</TH>";
666     if ($canedit) {
667         echo "<TH width=10>$stredit</TH>";
668     }
669     echo "</TR>";
670     foreach ($questions as $question) {
671         echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
672         echo "<TD ALIGN=CENTER>";
673         echo "<INPUT TYPE=CHECKBOX NAME=q$question->id VALUE=\"1\">";
674         echo "</TD>";
675         echo "<TD>".$question->name."</TD>";
676         echo "<TD WIDTH=16 ALIGN=CENTER>";
677         quiz_print_question_icon($question);
678         echo "</TD>";
679         if ($canedit) {
680             echo "<TD>";
681                 echo "<A TITLE=\"$strdelete\" HREF=\"question.php?id=$question->id&delete=$question->id\"><IMG 
682                      SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
683                 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG 
684                      SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
685             echo "</TD></TR>";
686         }
687         echo "</TR>";
688     }
689     echo "<TR><TD COLSPAN=3>";
690     echo "<INPUT TYPE=hidden NAME=add VALUE=\"1\">";
691     echo "<INPUT TYPE=submit VALUE=\"<< $straddselectedtoquiz\">";
692     echo "</TD></TR>";
693     echo "</TABLE>";
694     echo "</FORM>";
698 function quiz_start_attempt($quizid, $userid, $numattempt) {
699     $attempt->quiz = $quizid;
700     $attempt->user = $userid;
701     $attempt->attempt = $numattempt;
702     $attempt->timestart = time();
703     $attempt->timefinish = 0; 
704     $attempt->timemodified = time();
706     return insert_record("quiz_attempts", $attempt);
709 function quiz_get_user_attempt_unfinished($quizid, $userid) {
710 // Returns an object containing an unfinished attempt (if there is one)
711     return get_record_sql("SELECT * FROM quiz_attempts 
712                            WHERE quiz = '$quizid' and user = '$userid' AND timefinish = 0");
715 function quiz_get_user_attempts($quizid, $userid) {
716 // Returns a list of all attempts by a user
717     return get_records_sql("SELECT * FROM quiz_attempts 
718                             WHERE quiz = '$quizid' and user = '$userid' AND timefinish > 0 
719                             ORDER by attempt ASC");
723 function quiz_get_user_attempts_string($quiz, $attempts, $bestgrade) {
724 /// Returns a simple little comma-separated list of all attempts, 
725 /// with each grade linked to the feedback report and with the best grade highlighted
727     $bestgrade = format_float($bestgrade);
728     foreach ($attempts as $attempt) {
729         $attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade);
730         if ($attemptgrade == $bestgrade) {
731             $userattempts[] = "<SPAN CLASS=highlight><A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A></SPAN>";
732         } else {
733             $userattempts[] = "<A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A>";
734         }
735     }
736     return implode(",", $userattempts);
739 function quiz_get_best_grade($quizid, $userid) {
740 /// Get the best current grade for a particular user in a quiz
741     if (!$grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz = '$quizid' and user = '$userid'")) {
742         return 0;
743     }
745     return (round($grade->grade,0));
748 function quiz_get_grade_records($quiz) {
749 /// Gets all info required to display the table of quiz results
750 /// for report.php
752     return get_records_sql("SELECT qg.*, u.firstname, u.lastname, u.picture 
753                             FROM quiz_grades qg, user u
754                             WHERE qg.quiz = '$quiz->id'
755                               AND qg.user = u.id");
758 function quiz_save_best_grade($quiz, $userid) {
759 /// Calculates the best grade out of all attempts at a quiz for a user,
760 /// and then saves that grade in the quiz_grades table.
762     if (!$attempts = quiz_get_user_attempts($quiz->id, $userid)) {
763         notify("Could not find any user attempts");
764         return false;
765     }
767     $bestgrade = quiz_calculate_best_grade($quiz, $attempts);
768     $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade);
770     if ($grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz='$quiz->id' AND user='$userid'")) {
771         $grade->grade = $bestgrade;
772         $grade->timemodified = time();
773         if (!update_record("quiz_grades", $grade)) {
774             notify("Could not update best grade");
775             return false;
776         }
777     } else {
778         $grade->quiz = $quiz->id;
779         $grade->user = $userid;
780         $grade->grade = round($bestgrade, 2);
781         $grade->timemodified = time();
782         if (!insert_record("quiz_grades", $grade)) {
783             notify("Could not insert new best grade");
784             return false;
785         }
786     }
787     return true;
791 function quiz_get_answers($question) {
792 // Given a question, returns the correct answers and grades
793     switch ($question->type) {
794         case SHORTANSWER;       // Could be multiple answers
795             return get_records_sql("SELECT a.*, sa.usecase, g.grade
796                                       FROM quiz_shortanswer sa, quiz_answers a, quiz_question_grades g
797                                      WHERE sa.question = '$question->id' 
798                                        AND sa.question = a.question
799                                        AND sa.question = g.question");
800             break;
802         case TRUEFALSE;         // Should be always two answers
803             return get_records_sql("SELECT a.*, g.grade
804                                       FROM quiz_answers a, quiz_question_grades g
805                                      WHERE a.question = '$question->id' 
806                                        AND a.question = g.question");
807             break;
809         case MULTICHOICE;       // Should be multiple answers
810             return get_records_sql("SELECT a.*, mc.single, g.grade
811                                       FROM quiz_multichoice mc, quiz_answers a, quiz_question_grades g
812                                      WHERE mc.question = '$question->id' 
813                                        AND mc.question = a.question 
814                                        AND mc.question = g.question");
815             break;
817        case RANDOM:
818            return false;  // Not done yet
819            break;
821         default:
822             return false;
823     }
826 function quiz_calculate_best_grade($quiz, $attempts) {
827 /// Calculate the best grade for a quiz given a number of attempts by a particular user.
829     switch ($quiz->grademethod) {
831         case ATTEMPTFIRST:
832             foreach ($attempts as $attempt) {
833                 return $attempt->sumgrades;
834             }
835             break;
837         case ATTEMPTLAST:
838             foreach ($attempts as $attempt) {
839                 $final = $attempt->sumgrades;
840             }
841             return $final;
843         case GRADEAVERAGE:
844             $sum = 0;
845             $count = 0;
846             foreach ($attempts as $attempt) {
847                 $sum += $attempt->sumgrades;
848                 $count++;
849             }
850             return (float)$sum/$count;
852         default:
853         case GRADEHIGHEST:
854             $max = 0;
855             foreach ($attempts as $attempt) {
856                 if ($attempt->sumgrades > $max) {
857                     $max = $attempt->sumgrades;
858                 }
859             }
860             return $max;
861     }
864 function quiz_save_attempt($quiz, $questions, $result, $attemptnum) {
865 /// Given a quiz, a list of attempted questions and a total grade 
866 /// this function saves EVERYTHING so it can be reconstructed later
867 /// if necessary.
869     global $USER;
871     // First find the attempt in the database (start of attempt)
873     if (!$attempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
874         notify("Trying to save an attempt that was not started!");
875         return false;
876     }
878     if ($attempt->attempt != $attemptnum) {  // Double check.
879         notify("Number of this attempt is different to the unfinished one!");
880         return false;
881     }
883     // Now let's complete this record and save it
885     $attempt->sumgrades = $result->sumgrades;
886     $attempt->timefinish = time();
887     $attempt->timemodified = time();
889     if (! update_record("quiz_attempts", $attempt)) {
890         notify("Error while saving attempt");
891         return false;
892     }
894     // Now let's save all the questions for this attempt
896     foreach ($questions as $question) {
897         $response->attempt = $attempt->id;
898         $response->question = $question->id;
899         $response->grade = $result->grades[$question->id];
900         if ($question->answer) {
901             $response->answer = implode(",",$question->answer);
902         } else {
903             $response->answer = "";
904         }
905         if (!insert_record("quiz_responses", $response)) {
906             notify("Error while saving response");
907             return false;
908         }
909     }
910     return true;
914 function quiz_grade_attempt_results($quiz, $questions) {
915 /// Given a list of questions (including answers for each one)
916 /// this function does all the hard work of calculating the 
917 /// grades for each question, as well as a total grade for 
918 /// for the whole quiz.  It returns everything in a structure 
919 /// that looks like:
920 /// $result->sumgrades    (sum of all grades for all questions)
921 /// $result->percentage   (Percentage of grades that were correct)
922 /// $result->grade        (final grade result for the whole quiz)
923 /// $result->grades[]     (array of grades, indexed by question id)
924 /// $result->response[]   (array of response arrays, indexed by question id)
925 /// $result->feedback[]   (array of feedback arrays, indexed by question id)
926 /// $result->correct[]    (array of feedback arrays, indexed by question id)
928     if (!$questions) {
929         error("No questions!");
930     }
931     
932     $result->sumgrades = 0;
934     foreach ($questions as $question) {
935         if (!$answers = quiz_get_answers($question)) {
936             error("No answers defined for question id $question->id!");
937         }
939         $grade    = 0;   // default
940         $correct = array();
941         $feedback = array();
942         $response = array();
944         switch ($question->type) {
945             case SHORTANSWER:
946                 if ($question->answer) {
947                     $question->answer = trim($question->answer[0]);
948                 } else {
949                     $question->answer = "";
950                 }
951                 $response[0] = $question->answer;
952                 foreach($answers as $answer) {  // There might be multiple right answers
953                     if ($answer->fraction > $bestshortanswer) {
954                         $correct[$answer->id] = $answer->answer;
955                     }
956                     if (!$answer->usecase) {       // Don't compare case
957                         $answer->answer = strtolower($answer->answer);
958                         $question->answer = strtolower($question->answer);
959                     }
960                     if ($question->answer == $answer->answer) {
961                         $feedback[0] = $answer->feedback;
962                         $grade = (float)$answer->fraction * $answer->grade;
963                     }
964                 }
965                 break;
968             case TRUEFALSE:
969                 if ($question->answer) {
970                     $question->answer = $question->answer[0];
971                 } else {
972                     $question->answer = NULL;
973                 }
974                 foreach($answers as $answer) {  // There should be two answers (true and false)
975                     $feedback[$answer->id] = $answer->feedback;
976                     if ($answer->fraction > 0) {
977                         $correct[$answer->id]  = true;
978                     }
979                     if ($question->answer == $answer->id) {
980                         $grade = (float)$answer->fraction * $answer->grade;
981                         $response[$answer->id] = true;
982                     }
983                 }
984                 break;
987             case MULTICHOICE:
988                 foreach($answers as $answer) {  // There will be multiple answers, perhaps more than one is right
989                     $feedback[$answer->id] = $answer->feedback;
990                     if ($answer->fraction > 0) {
991                         $correct[$answer->id] = true;
992                     }
993                     if ($question->answer) {
994                         foreach ($question->answer as $questionanswer) {
995                             if ($questionanswer == $answer->id) {
996                                 if ($answer->single) {
997                                     $grade = (float)$answer->fraction * $answer->grade;
998                                     $response[$answer->id] = true;
999                                     continue;
1000                                 } else {
1001                                     $grade += (float)$answer->fraction * $answer->grade;
1002                                     $response[$answer->id] = true;
1003                                 }
1004                             }
1005                         }
1006                     }
1007                 }
1008                 break;
1009             case RANDOM:
1010                           // Not done yet
1011                 break;
1013             
1014         }
1015         if ($grade < 0.0) {   // No negative grades
1016             $grade = 0.0;
1017         }
1019         $result->grades[$question->id] = $grade;
1020         $result->sumgrades += $grade;
1021         $result->feedback[$question->id] = $feedback;
1022         $result->response[$question->id] = $response;
1023         $result->correct[$question->id] = $correct;
1024     }
1026     $fraction = (float)($result->sumgrades / $quiz->sumgrades);
1027     $result->percentage = format_float($fraction * 100.0);
1028     $result->grade      = format_float($fraction * $quiz->grade);
1030     return $result;
1034 function quiz_get_attempt_responses($attempt) {
1035 // Given an attempt object, this function gets all the 
1036 // stored responses and returns them in a format suitable
1037 // for regrading using quiz_grade_attempt_results()
1038    
1039     if (!$responses = get_records_sql("SELECT q.id, q.type, r.answer 
1040                                         FROM quiz_responses r, quiz_questions q
1041                                        WHERE r.attempt = '$attempt->id' 
1042                                          AND q.id = r.question")) {
1043         notify("Could not find any responses for that attempt!");
1044         return false;
1045     }
1047     foreach ($responses as $key => $response) {
1048         $responses[$key]->answer = explode(",",$response->answer);
1049     }
1051     return $responses;
1054     
1055 ?>