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
226 global $QUIZ_QUESTION_TYPE;
228 echo "<A HREF=\"question.php?id=$question->id\" TITLE=\"".$QUIZ_QUESTION_TYPE[$question->type]."\">";
229 switch ($question->type) {
231 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/sa.gif\">";
234 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/tf.gif\">";
237 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/mc.gif\">";
240 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/rs.gif\">";
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!");
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>";
262 echo "<P ALIGN=CENTER><FONT SIZE=1>$grade $strmarks</FONT></P>";
265 echo "</TD><TD VALIGN=TOP>";
267 switch ($question->type) {
269 if (!$options = get_record("quiz_shortanswer", "question", $question->id)) {
270 notify("Error: Missing question options!");
272 echo text_to_html($question->questiontext);
273 if ($question->image) {
274 print_file_picture($question->image, $courseid, 200);
277 $value = "VALUE=\"$response[0]\"";
279 echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$question->id SIZE=20 $value></P>";
281 quiz_print_comment("<P ALIGN=right>$feedback[0]</P>");
284 $correctanswers = implode(", ", $correct);
285 quiz_print_correctanswer($correctanswers);
290 if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
291 notify("Error: Missing question options!");
293 if (!$true = get_record("quiz_answers", "id", $options->true)) {
294 notify("Error: Missing question answers!");
296 if (!$false = get_record("quiz_answers", "id", $options->false)) {
297 notify("Error: Missing question answers!");
299 if (!$true->answer) {
300 $true->answer = get_string("true", "quiz");
302 if (!$false->answer) {
303 $false->answer = get_string("false", "quiz");
305 echo text_to_html($question->questiontext);
306 if ($question->image) {
307 print_file_picture($question->image, $courseid, 200);
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;
318 if ($correct[$true->id]) {
319 $truecorrect = "CLASS=highlight";
321 if ($correct[$false->id]) {
322 $falsecorrect = "CLASS=highlight";
325 echo "<TABLE ALIGN=right cellpadding=5><TR><TD align=right>$stranswer: ";
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>";
332 quiz_print_comment("<P ALIGN=right>$feedback[$feedbackid]</P>");
338 if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
339 notify("Error: Missing question options!");
341 if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
342 notify("Error: Missing question answers!");
344 echo text_to_html($question->questiontext);
345 if ($question->image) {
346 print_file_picture($question->image, $courseid, 200);
348 echo "<TABLE ALIGN=right>";
349 echo "<TR><TD valign=top>$stranswer: </TD><TD>";
350 echo "<TABLE ALIGN=right>";
351 $answerids = explode(",", $options->answers);
353 foreach ($answerids as $key => $answerid) {
354 $answer = $answers[$answerid];
357 if ($feedback and $response[$answerid]) {
358 $checked = "CHECKED";
362 echo "<TR><TD valign=top>";
363 if ($options->single) {
364 echo "<INPUT $checked TYPE=RADIO NAME=q$question->id VALUE=\"$answer->id\">";
366 echo "<INPUT $checked TYPE=CHECKBOX NAME=q$question->id"."a$answer->id VALUE=\"$answer->id\">";
369 if ($feedback and $correct[$answer->id]) {
370 echo "<TD valign=top CLASS=highlight>$qnum. $answer->answer</TD>";
372 echo "<TD valign=top>$qnum. $answer->answer</TD>";
375 echo "<TD valign=top> ";
376 if ($response[$answerid]) {
377 quiz_print_comment($feedback[$answerid]);
388 echo "<P>Random questions not supported yet</P>";
393 notify("Error: Unknown question type!");
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");
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!");
414 echo "<FORM METHOD=POST ACTION=attempt.php>";
415 echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">";
417 foreach ($questions as $key => $questionid) {
420 $actualgrades = NULL;
423 $feedback = $results->feedback[$questionid];
424 $response = $results->response[$questionid];
425 $actualgrades = $results->grades[$questionid];
426 if ($quiz->correctanswers) {
427 $correct = $results->correct[$questionid];
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();
439 echo "<CENTER><INPUT TYPE=submit VALUE=\"".get_string("savemyanswers", "quiz")."\"></CENTER>";
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)
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!");
466 function quiz_get_category_menu($courseid, $published=false) {
468 $publish = "OR publish = '1'";
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!");
481 $categories[$category->id] = $category;
483 foreach ($categories as $key => $category) {
484 if ($category->publish) {
485 if ($catcourse = get_record("course", "id", $category->course)) {
486 $category->name .= " ($catcourse->shortname)";
489 $catmenu[$category->id] = $category->name;
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> ";
498 choose_from_menu($catmenu, "cat", "$current");
499 echo "<INPUT TYPE=submit VALUE=\"$strshow\">";
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\">";
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);
521 foreach ($list as $qid) {
522 if (isset($questions[$qid])) {
523 $grades[$qid] = $questions[$qid]->grade;
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
539 if (!$questionlist) {
540 echo "<P align=center>";
541 print_string("noquestions", "quiz");
546 $order = explode(",", $questionlist);
548 if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) {
549 error("No questions were found!");
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;
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) {
573 echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
574 echo "<TD>$count</TD>";
577 echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG
578 SRC=\"../../pix/t/up.gif\" BORDER=0></A>";
582 if ($count != $total) {
583 echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG
584 SRC=\"../../pix/t/down.gif\" BORDER=0></A>";
587 echo "<TD>".$questions[$qnum]->name."</TD>";
588 echo "<TD WIDTH=16 ALIGN=CENTER>";
589 quiz_print_question_icon($questions[$qnum]);
592 choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], "");
594 echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG
595 SRC=\"../../pix/t/delete.gif\" BORDER=0></A> ";
596 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG
597 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
600 $sumgrade += $grades[$qnum];
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>";
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");
632 echo "<P align=center>";
633 print_string("selectcategoryabove", "quiz");
638 if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
639 notify("Category not found!");
643 echo text_to_html($category->info);
645 echo "<FORM METHOD=GET ACTION=question.php>";
646 echo "<B>$strquestion:</B> ";
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");
654 if (!$questions = get_records("quiz_questions", "category", $category->id)) {
655 echo "<P align=center>";
656 print_string("noquestions", "quiz");
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>";
667 echo "<TH width=10>$stredit</TH>";
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\">";
675 echo "<TD>".$question->name."</TD>";
676 echo "<TD WIDTH=16 ALIGN=CENTER>";
677 quiz_print_question_icon($question);
681 echo "<A TITLE=\"$strdelete\" HREF=\"question.php?id=$question->id&delete=$question->id\"><IMG
682 SRC=\"../../pix/t/delete.gif\" BORDER=0></A> ";
683 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG
684 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
689 echo "<TR><TD COLSPAN=3>";
690 echo "<INPUT TYPE=hidden NAME=add VALUE=\"1\">";
691 echo "<INPUT TYPE=submit VALUE=\"<< $straddselectedtoquiz\">";
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>";
733 $userattempts[] = "<A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A>";
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'")) {
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
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");
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");
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");
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");
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");
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");
818 return false; // Not done yet
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) {
832 foreach ($attempts as $attempt) {
833 return $attempt->sumgrades;
838 foreach ($attempts as $attempt) {
839 $final = $attempt->sumgrades;
846 foreach ($attempts as $attempt) {
847 $sum += $attempt->sumgrades;
850 return (float)$sum/$count;
855 foreach ($attempts as $attempt) {
856 if ($attempt->sumgrades > $max) {
857 $max = $attempt->sumgrades;
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
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!");
878 if ($attempt->attempt != $attemptnum) { // Double check.
879 notify("Number of this attempt is different to the unfinished one!");
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");
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);
903 $response->answer = "";
905 if (!insert_record("quiz_responses", $response)) {
906 notify("Error while saving response");
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
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)
929 error("No questions!");
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!");
939 $grade = 0; // default
944 switch ($question->type) {
946 if ($question->answer) {
947 $question->answer = trim($question->answer[0]);
949 $question->answer = "";
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;
956 if (!$answer->usecase) { // Don't compare case
957 $answer->answer = strtolower($answer->answer);
958 $question->answer = strtolower($question->answer);
960 if ($question->answer == $answer->answer) {
961 $feedback[0] = $answer->feedback;
962 $grade = (float)$answer->fraction * $answer->grade;
969 if ($question->answer) {
970 $question->answer = $question->answer[0];
972 $question->answer = NULL;
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;
979 if ($question->answer == $answer->id) {
980 $grade = (float)$answer->fraction * $answer->grade;
981 $response[$answer->id] = true;
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;
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;
1001 $grade += (float)$answer->fraction * $answer->grade;
1002 $response[$answer->id] = true;
1015 if ($grade < 0.0) { // No negative grades
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;
1026 $fraction = (float)($result->sumgrades / $quiz->sumgrades);
1027 $result->percentage = format_float($fraction * 100.0);
1028 $result->grade = format_float($fraction * $quiz->grade);
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()
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!");
1047 foreach ($responses as $key => $response) {
1048 $responses[$key]->answer = explode(",",$response->answer);