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
45 // The grades for every question in this quiz are stored in an array
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)) {
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
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");
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)) {
101 if (!insert_record("quiz_question_grades", $questiongrade)) {
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")) {
124 if ($attempts = get_record("quiz_attempts", "quiz", "$quiz->id")) {
125 foreach ($attempts as $attempt) {
126 if (! delete_records("quiz_responses", "attempt", "$attempt->id")) {
132 if (! delete_records("quiz_attempts", "quiz", "$quiz->id")) {
136 if (! delete_records("quiz_grades", "quiz", "$quiz->id")) {
140 if (! delete_records("quiz_question_grades", "quiz", "$quiz->id")) {
144 if (! delete_records("quiz", "id", "$quiz->id")) {
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'")) {
160 $result->info = get_string("grade").": $grade->grade";
162 $result->time = $grade->timemodified;
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.
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 ...
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");
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) {
214 echo "<FONT COLOR=\"$THEME->cellheading2\">".text_to_html($text, true, false)."</FONT>";
217 function quiz_print_correctanswer($text) {
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) {
227 echo "<IMG HEIGHT=16 WIDTH=16 SRC=\"pix/sa.gif\">";
230 echo "<IMG HEIGHT=16 WIDTH=16 SRC=\"pix/tf.gif\">";
233 echo "<IMG HEIGHT=16 WIDTH=16 SRC=\"pix/mc.gif\">";
236 echo "<IMG HEIGHT=16 WIDTH=16 SRC=\"pix/rs.gif\">";
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!");
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>";
257 echo "<P ALIGN=CENTER><FONT SIZE=1>$grade $strmarks</FONT></P>";
260 echo "</TD><TD VALIGN=TOP>";
262 switch ($question->type) {
264 if (!$options = get_record("quiz_shortanswer", "question", $question->id)) {
265 notify("Error: Missing question options!");
267 echo text_to_html($question->questiontext);
268 if ($question->image) {
269 print_file_picture($question->image, $courseid, 200);
272 $value = "VALUE=\"$response[0]\"";
274 echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$question->id SIZE=20 $value></P>";
276 quiz_print_comment("<P ALIGN=right>$feedback[0]</P>");
279 $correctanswers = implode(", ", $correct);
280 quiz_print_correctanswer($correctanswers);
285 if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
286 notify("Error: Missing question options!");
288 if (!$true = get_record("quiz_answers", "id", $options->true)) {
289 notify("Error: Missing question answers!");
291 if (!$false = get_record("quiz_answers", "id", $options->false)) {
292 notify("Error: Missing question answers!");
294 if (!$true->answer) {
295 $true->answer = get_string("true", "quiz");
297 if (!$false->answer) {
298 $false->answer = get_string("false", "quiz");
300 echo text_to_html($question->questiontext);
301 if ($question->image) {
302 print_file_picture($question->image, $courseid, 200);
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;
313 if ($correct[$true->id]) {
314 $truecorrect = "CLASS=highlight";
316 if ($correct[$false->id]) {
317 $falsecorrect = "CLASS=highlight";
320 echo "<TABLE ALIGN=right cellpadding=5><TR><TD align=right>$stranswer: ";
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>";
327 quiz_print_comment("<P ALIGN=right>$feedback[$feedbackid]</P>");
333 if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
334 notify("Error: Missing question options!");
336 if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
337 notify("Error: Missing question answers!");
339 echo text_to_html($question->questiontext);
340 if ($question->image) {
341 print_file_picture($question->image, $courseid, 200);
343 echo "<TABLE ALIGN=right>";
344 echo "<TR><TD valign=top>$stranswer: </TD><TD>";
345 echo "<TABLE ALIGN=right>";
346 $answerids = explode(",", $options->answers);
348 foreach ($answerids as $key => $answerid) {
349 $answer = $answers[$answerid];
352 if ($feedback and $response[$answerid]) {
353 $checked = "CHECKED";
357 echo "<TR><TD valign=top>";
358 if ($options->single) {
359 echo "<INPUT $checked TYPE=RADIO NAME=q$question->id VALUE=\"$answer->id\">";
361 echo "<INPUT $checked TYPE=CHECKBOX NAME=q$question->id"."a$answer->id VALUE=\"$answer->id\">";
364 if ($feedback and $correct[$answer->id]) {
365 echo "<TD valign=top CLASS=highlight>$qnum. $answer->answer</TD>";
367 echo "<TD valign=top>$qnum. $answer->answer</TD>";
370 echo "<TD valign=top> ";
371 if ($response[$answerid]) {
372 quiz_print_comment($feedback[$answerid]);
383 echo "<P>Random questions not supported yet</P>";
388 notify("Error: Unknown question type!");
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");
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!");
409 echo "<FORM METHOD=POST ACTION=attempt.php>";
410 echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">";
412 foreach ($questions as $key => $questionid) {
415 $actualgrades = NULL;
418 $feedback = $results->feedback[$questionid];
419 $response = $results->response[$questionid];
420 $actualgrades = $results->grades[$questionid];
421 if ($quiz->correctanswers) {
422 $correct = $results->correct[$questionid];
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();
434 echo "<CENTER><INPUT TYPE=submit VALUE=\"".get_string("savemyanswers", "quiz")."\"></CENTER>";
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)
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!");
461 function quiz_get_category_menu($courseid, $published=false) {
463 $publish = "OR publish = '1'";
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!");
477 foreach ($categories as $key => $category) {
478 if ($category->publish) {
479 if ($catcourse = get_record("course", "id", $category->course)) {
480 $category->name .= " ($catcourse->shortname)";
483 $catmenu[$category->id] = $category->name;
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> ";
492 choose_from_menu($catmenu, "cat", "$current");
493 echo "<INPUT TYPE=submit VALUE=\"$strshow\">";
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\">";
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);
515 foreach ($list as $qid) {
516 if (isset($questions[$qid])) {
517 $grades[$qid] = $questions[$qid]->grade;
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
533 if (!$questionlist) {
534 echo "<P align=center>";
535 print_string("noquestions", "quiz");
540 $order = explode(",", $questionlist);
542 if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) {
543 error("No questions were found!");
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;
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) {
567 echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
568 echo "<TD>$count</TD>";
571 echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG
572 SRC=\"../../pix/t/up.gif\" BORDER=0></A>";
576 if ($count != $total) {
577 echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG
578 SRC=\"../../pix/t/down.gif\" BORDER=0></A>";
581 echo "<TD>".$questions[$qnum]->name."</TD>";
582 echo "<TD WIDTH=16 ALIGN=CENTER>";
583 quiz_print_question_icon($questions[$qnum]);
586 choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], "");
588 echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG
589 SRC=\"../../pix/t/delete.gif\" BORDER=0></A> ";
590 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG
591 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
594 $sumgrade += $grades[$qnum];
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>";
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");
626 echo "<P align=center>";
627 print_string("selectcategoryabove", "quiz");
632 if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
633 notify("Category not found!");
637 echo text_to_html($category->info);
639 echo "<FORM METHOD=GET ACTION=question.php>";
640 echo "<B>$strquestion:</B> ";
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\">";
647 if (!$questions = get_records("quiz_questions", "category", $category->id)) {
648 echo "<P align=center>";
649 print_string("noquestions", "quiz");
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>";
660 echo "<TH width=10>$stredit</TH>";
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\">";
668 echo "<TD>".$question->name."</TD>";
669 echo "<TD WIDTH=16 ALIGN=CENTER>";
670 quiz_print_question_icon($question);
674 echo "<A TITLE=\"$strdelete\" HREF=\"question.php?id=$question->id&delete=$question->id\"><IMG
675 SRC=\"../../pix/t/delete.gif\" BORDER=0></A> ";
676 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG
677 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
682 echo "<TR><TD COLSPAN=3>";
683 echo "<INPUT TYPE=hidden NAME=add VALUE=\"1\">";
684 echo "<INPUT TYPE=submit VALUE=\"<< $straddselectedtoquiz\">";
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>";
726 $userattempts[] = "<A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A>";
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'")) {
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
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");
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");
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");
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");
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");
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");
811 return false; // Not done yet
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) {
825 foreach ($attempts as $attempt) {
826 return $attempt->sumgrades;
831 foreach ($attempts as $attempt) {
832 $final = $attempt->sumgrades;
839 foreach ($attempts as $attempt) {
840 $sum += $attempt->sumgrades;
843 return (float)$sum/$count;
848 foreach ($attempts as $attempt) {
849 if ($attempt->sumgrades > $max) {
850 $max = $attempt->sumgrades;
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
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!");
871 if ($attempt->attempt != $attemptnum) { // Double check.
872 notify("Number of this attempt is different to the unfinished one!");
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");
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);
896 $response->answer = "";
898 if (!insert_record("quiz_responses", $response)) {
899 notify("Error while saving response");
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
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)
922 error("No questions!");
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!");
932 $grade = 0; // default
937 switch ($question->type) {
939 if ($question->answer) {
940 $question->answer = $question->answer[0];
942 $question->answer = "";
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;
949 if (!$answer->usecase) { // Don't compare case
950 $answer->answer = strtolower($answer->answer);
951 $question->answer = strtolower($question->answer);
953 if ($question->answer == $answer->answer) {
954 $feedback[0] = $answer->feedback;
955 $grade = (float)$answer->fraction * $answer->grade;
962 if ($question->answer) {
963 $question->answer = $question->answer[0];
965 $question->answer = NULL;
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;
972 if ($question->answer == $answer->id) {
973 $grade = (float)$answer->fraction * $answer->grade;
974 $response[$answer->id] = true;
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;
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;
994 $grade += (float)$answer->fraction * $answer->grade;
995 $response[$answer->id] = true;
1008 if ($grade < 0.0) { // No negative grades
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;
1019 $fraction = (float)($result->sumgrades / $quiz->sumgrades);
1020 $result->percentage = format_float($fraction * 100.0);
1021 $result->grade = format_float($fraction * $quiz->grade);
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()
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!");
1040 foreach ($responses as $key => $response) {
1041 $responses[$key]->answer = explode(",",$response->answer);