*** empty log message ***
[moodle.git] / mod / quiz / lib.php
CommitLineData
730fd187 1<?PHP // $Id$
2
a5e1f35c 3/// Library of function for module quiz
730fd187 4
a5e1f35c 5/// CONSTANTS ///////////////////////////////////////////////////////////////////
730fd187 6
a5e1f35c 7define("GRADEHIGHEST", "1");
8define("GRADEAVERAGE", "2");
9define("ATTEMPTFIRST", "3");
10define("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"));
15
16define("SHORTANSWER", "1");
17define("TRUEFALSE", "2");
18define("MULTICHOICE", "3");
8d94f5a0 19$QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz"),
a5e1f35c 20 TRUEFALSE => get_string("truefalse", "quiz"),
8d94f5a0 21 SHORTANSWER => get_string("shortanswer", "quiz") );
a5e1f35c 22
23
24
25/// FUNCTIONS ///////////////////////////////////////////////////////////////////
730fd187 26
27function quiz_add_instance($quiz) {
a5e1f35c 28/// Given an object containing all the necessary data,
29/// (defined by the form in mod.html) this function
30/// will create a new instance and return the id number
31/// of the new instance.
730fd187 32
49dcdd18 33 $quiz->created = time();
730fd187 34 $quiz->timemodified = time();
49dcdd18 35 $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday,
36 $quiz->openhour, $quiz->openminute, $quiz->opensecond);
37 $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday,
38 $quiz->closehour, $quiz->closeminute, $quiz->closesecond);
730fd187 39
7bd1aa1d 40 if (!$quiz->id = insert_record("quiz", $quiz)) {
41 return false; // some error occurred
42 }
43
10b9291c 44 // The grades for every question in this quiz are stored in an array
7bd1aa1d 45 if ($quiz->grades) {
46 foreach ($quiz->grades as $question => $grade) {
8d94f5a0 47 if ($question and $grade) {
48 unset($questiongrade);
49 $questiongrade->quiz = $quiz->id;
50 $questiongrade->question = $question;
51 $questiongrade->grade = $grade;
52 if (!insert_record("quiz_question_grades", $questiongrade)) {
53 return false;
54 }
7bd1aa1d 55 }
56 }
57 }
730fd187 58
7bd1aa1d 59 return $quiz->id;
730fd187 60}
61
62
63function quiz_update_instance($quiz) {
a5e1f35c 64/// Given an object containing all the necessary data,
65/// (defined by the form in mod.html) this function
66/// will update an existing instance with new data.
730fd187 67
68 $quiz->timemodified = time();
49dcdd18 69 $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday,
70 $quiz->openhour, $quiz->openminute, $quiz->opensecond);
71 $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday,
72 $quiz->closehour, $quiz->closeminute, $quiz->closesecond);
730fd187 73 $quiz->id = $quiz->instance;
74
7bd1aa1d 75 if (!update_record("quiz", $quiz)) {
76 return false; // some error occurred
77 }
730fd187 78
7bd1aa1d 79
10b9291c 80 // The grades for every question in this quiz are stored in an array
7bd1aa1d 81 // Insert or update records as appropriate
82
83 $existing = get_records("quiz_question_grades", "quiz", $quiz->id, "", "question,grade,id");
84
85 if ($quiz->grades) {
86 foreach ($quiz->grades as $question => $grade) {
8d94f5a0 87 if ($question and $grade) {
88 unset($questiongrade);
89 $questiongrade->quiz = $quiz->id;
90 $questiongrade->question = $question;
91 $questiongrade->grade = $grade;
92 if (isset($existing[$question])) {
93 if ($existing[$question]->grade != $grade) {
94 $questiongrade->id = $existing[$question]->id;
95 if (!update_record("quiz_question_grades", $questiongrade)) {
96 return false;
97 }
98 }
99 } else {
100 if (!insert_record("quiz_question_grades", $questiongrade)) {
7bd1aa1d 101 return false;
102 }
103 }
7bd1aa1d 104 }
105 }
106 }
107
108 return true;
730fd187 109}
110
111
112function quiz_delete_instance($id) {
a5e1f35c 113/// Given an ID of an instance of this module,
114/// this function will permanently delete the instance
115/// and any data that depends on it.
730fd187 116
117 if (! $quiz = get_record("quiz", "id", "$id")) {
118 return false;
119 }
120
121 $result = true;
122
10b9291c 123 if ($attempts = get_record("quiz_attempts", "quiz", "$quiz->id")) {
124 foreach ($attempts as $attempt) {
125 if (! delete_records("quiz_responses", "attempt", "$attempt->id")) {
126 $result = false;
127 }
128 }
129 }
130
131 if (! delete_records("quiz_attempts", "quiz", "$quiz->id")) {
132 $result = false;
133 }
134
135 if (! delete_records("quiz_grades", "quiz", "$quiz->id")) {
136 $result = false;
137 }
138
139 if (! delete_records("quiz_question_grades", "quiz", "$quiz->id")) {
140 $result = false;
141 }
730fd187 142
143 if (! delete_records("quiz", "id", "$quiz->id")) {
144 $result = false;
145 }
146
147 return $result;
148}
149
150function quiz_user_outline($course, $user, $mod, $quiz) {
a5e1f35c 151/// Return a small object with summary information about what a
152/// user has done with a given particular instance of this module
153/// Used for user activity reports.
154/// $return->time = the time they did it
155/// $return->info = a short text description
730fd187 156
157 return $return;
158}
159
160function quiz_user_complete($course, $user, $mod, $quiz) {
a5e1f35c 161/// Print a detailed representation of what a user has done with
162/// a given particular instance of this module, for user activity reports.
730fd187 163
164 return true;
165}
166
167function quiz_print_recent_activity(&$logs, $isteacher=false) {
a5e1f35c 168/// Given a list of logs, assumed to be those since the last login
169/// this function prints a short list of changes related to this module
170/// If isteacher is true then perhaps additional information is printed.
171/// This function is called from course/lib.php: print_recent_activity()
730fd187 172
173 global $CFG, $COURSE_TEACHER_COLOR;
174
175 return $content; // True if anything was printed, otherwise false
176}
177
178function quiz_cron () {
a5e1f35c 179/// Function to be run periodically according to the moodle cron
180/// This function searches for things that need to be done, such
181/// as sending out mail, toggling flags etc ...
730fd187 182
183 global $CFG;
184
185 return true;
186}
187
188
189//////////////////////////////////////////////////////////////////////////////////////
a5e1f35c 190/// Any other quiz functions go here. Each of them must have a name that
191/// starts with quiz_
730fd187 192
14d8c0b4 193function quiz_print_question($number, $questionid, $grade, $courseid) {
a5e1f35c 194/// Prints a quiz question, any format
14d8c0b4 195
196 if (!$question = get_record("quiz_questions", "id", $questionid)) {
197 notify("Error: Question not found!");
198 }
199
200 $stranswer = get_string("answer", "quiz");
201 $strmarks = get_string("marks", "quiz");
202
203 echo "<TABLE WIDTH=100% CELLSPACING=10><TR><TD NOWRAP WIDTH=100 VALIGN=top>";
204 echo "<P ALIGN=CENTER><B>$number</B><BR><FONT SIZE=1>$grade $strmarks</FONT></P>";
205 print_spacer(1,100);
206 echo "</TD><TD VALIGN=TOP>";
207
208 switch ($question->type) {
a5e1f35c 209 case SHORTANSWER:
14d8c0b4 210 if (!$options = get_record("quiz_shortanswer", "question", $question->id)) {
211 notify("Error: Missing question options!");
212 }
2a2c9725 213 echo text_to_html($question->questiontext);
14d8c0b4 214 if ($question->image) {
215 print_file_picture($question->image, $courseid, 200);
216 }
217 echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$question->id SIZE=20></P>";
218 break;
219
a5e1f35c 220 case TRUEFALSE:
14d8c0b4 221 if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
222 notify("Error: Missing question options!");
223 }
224 if (!$true = get_record("quiz_answers", "id", $options->true)) {
225 notify("Error: Missing question answers!");
226 }
227 if (!$false = get_record("quiz_answers", "id", $options->false)) {
228 notify("Error: Missing question answers!");
229 }
230 if (!$true->answer) {
231 $true->answer = get_string("true", "quiz");
232 }
233 if (!$false->answer) {
234 $false->answer = get_string("false", "quiz");
235 }
2a2c9725 236 echo text_to_html($question->questiontext);
14d8c0b4 237 if ($question->image) {
238 print_file_picture($question->image, $courseid, 200);
239 }
240 echo "<P ALIGN=RIGHT>$stranswer:&nbsp;&nbsp;";
241 echo "<INPUT TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$true->id\">$true->answer";
242 echo "&nbsp;&nbsp;&nbsp;";
243 echo "<INPUT TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$false->id\">$false->answer</P>";
244 break;
245
a5e1f35c 246 case MULTICHOICE:
14d8c0b4 247 if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
248 notify("Error: Missing question options!");
249 }
a5e1f35c 250 if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
14d8c0b4 251 notify("Error: Missing question answers!");
252 }
2a2c9725 253 echo text_to_html($question->questiontext);
14d8c0b4 254 if ($question->image) {
255 print_file_picture($question->image, $courseid, 200);
256 }
257 echo "<TABLE ALIGN=right>";
258 echo "<TR><TD valign=top>$stranswer:&nbsp;&nbsp;</TD><TD>";
259 echo "<TABLE ALIGN=right>";
260 $answerids = explode(",", $options->answers);
261 foreach ($answerids as $key => $answerid) {
262 $answer = $answers[$answerid];
263 $qnum = $key + 1;
264 echo "<TR><TD valign=top>";
a5e1f35c 265 if ($options->single) {
14d8c0b4 266 echo "<INPUT TYPE=RADIO NAME=q$question->id VALUE=\"$answer->id\">";
267 } else {
a5e1f35c 268 echo "<INPUT TYPE=CHECKBOX NAME=q$question->id"."a$answer->id VALUE=\"$answer->id\">";
14d8c0b4 269 }
270 echo "</TD>";
271 echo "<TD valign=top>$qnum. $answer->answer</TD>";
272 echo "</TR>";
273 }
274 echo "</TABLE>";
275 echo "</TABLE>";
276 break;
277
278 default:
279 notify("Error: Unknown question type!");
280 }
281
282 echo "</TD></TR></TABLE>";
3a506ca2 283}
284
a5e1f35c 285function quiz_print_quiz_questions($quiz, $results=NULL) {
286// Prints a whole quiz on one page.
287
288 if (!$quiz->questions) {
10b9291c 289 notify("No questions have been defined!", "view.php?id=$cm->id");
290 return false;
a5e1f35c 291 }
292
293 $questions = explode(",", $quiz->questions);
294
295 if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) {
10b9291c 296 notify("No grades were found for these questions!");
297 return false;
a5e1f35c 298 }
299
300 echo "<FORM METHOD=POST ACTION=attempt.php>";
301 echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">";
302 foreach ($questions as $key => $questionid) {
303 print_simple_box_start("CENTER", "90%");
2a2c9725 304 quiz_print_question($key+1, $questionid, $grades[$questionid]->grade, $quiz->course);
a5e1f35c 305 print_simple_box_end();
306 echo "<BR>";
307 }
308 echo "<CENTER><INPUT TYPE=submit VALUE=\"".get_string("savemyanswers", "quiz")."\"></CENTER>";
309 echo "</FORM>";
10b9291c 310
311 return true;
a5e1f35c 312}
6a952ce7 313
314function quiz_get_default_category($courseid) {
315 if ($categories = get_records("quiz_categories", "course", $courseid, "id")) {
316 foreach ($categories as $category) {
317 return $category; // Return the first one (lowest id)
318 }
319 }
320
321 // Otherwise, we need to make one
10b9291c 322 $category->name = get_string("default", "quiz");
323 $category->info = get_string("defaultinfo", "quiz");
6a952ce7 324 $category->course = $courseid;
325 $category->publish = 0;
326
327 if (!$category->id = insert_record("quiz_categories", $category)) {
328 notify("Error creating a default category!");
329 return false;
330 }
331 return $category;
332}
333
334function quiz_print_category_form($course, $current) {
335// Prints a form to choose categories
336
337 if (!$categories = get_records_sql_menu("SELECT id,name FROM quiz_categories
338 WHERE course='$course->id' OR publish = '1'
339 ORDER by name ASC")) {
340 if (!$category = quiz_get_default_category($course->id)) {
341 notify("Error creating a default category!");
342 return false;
343 }
344 $categories[$category->id] = $category->name;
345 }
8d94f5a0 346 foreach ($categories as $key => $category) {
347 if ($category->publish) {
348 if ($course = get_record_sql("course", "id", $category->course)) {
349 $categories[$key]->name .= " ($course->shortname)";
350 }
351 }
352 }
6a952ce7 353 $strcategory = get_string("category", "quiz");
354 $strshow = get_string("show", "quiz");
6b069ece 355 $streditcats = get_string("editcategories", "quiz");
6a952ce7 356
6b069ece 357 echo "<TABLE width=\"100%\"><TR><TD>";
6a952ce7 358 echo "<FORM METHOD=POST ACTION=edit.php>";
359 echo "<B>$strcategory:</B>&nbsp;";
360 choose_from_menu($categories, "cat", "$current");
92a3c884 361 echo "<INPUT TYPE=submit VALUE=\"$strshow\">";
6a952ce7 362 echo "</FORM>";
6b069ece 363 echo "</TD><TD align=right>";
364 echo "<FORM METHOD=GET ACTION=category.php>";
365 echo "<INPUT TYPE=hidden NAME=id VALUE=\"$course->id\">";
366 echo "<INPUT TYPE=submit VALUE=\"$streditcats\">";
367 echo "</FORM>";
368 echo "</TD></TR></TABLE>";
6a952ce7 369}
370
371
7bd1aa1d 372function quiz_get_all_question_grades($questionlist, $quizid) {
373// Given a list of question IDs, finds grades or invents them to
374// create an array of matching grades
375
92a3c884 376 $questions = get_records_sql("SELECT question,grade FROM quiz_question_grades
7bd1aa1d 377 WHERE quiz = '$quizid'
378 AND question IN ($questionlist)");
379
380 $list = explode(",", $questionlist);
381 $grades = array();
382
383 foreach ($list as $qid) {
384 if (isset($questions[$qid])) {
385 $grades[$qid] = $questions[$qid]->grade;
386 } else {
387 $grades[$qid] = 1;
388 }
389 }
390 return $grades;
391}
392
393
394function quiz_print_question_list($questionlist, $grades) {
6a952ce7 395// Prints a list of quiz questions in a small layout form with knobs
7bd1aa1d 396// $questionlist is comma-separated list
397// $grades is an array of corresponding grades
6a952ce7 398
399 global $THEME;
400
401 if (!$questionlist) {
402 echo "<P align=center>";
403 print_string("noquestions", "quiz");
404 echo "</P>";
405 return;
406 }
407
408 $order = explode(",", $questionlist);
409
7bd1aa1d 410 if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) {
6a952ce7 411 error("No questions were found!");
412 }
413
414 $strorder = get_string("order");
415 $strquestionname = get_string("questionname", "quiz");
416 $strgrade = get_string("grade");
417 $strdelete = get_string("delete");
418 $stredit = get_string("edit");
419 $strmoveup = get_string("moveup");
420 $strmovedown = get_string("movedown");
421 $strsavegrades = get_string("savegrades", "quiz");
422
10b9291c 423 for ($i=10; $i>=0; $i--) {
7bd1aa1d 424 $gradesmenu[$i] = $i;
6a952ce7 425 }
426 $count = 0;
427 $sumgrade = 0;
428 $total = count($order);
429 echo "<FORM METHOD=post ACTION=edit.php>";
430 echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
10b9291c 431 echo "<TR><TH WIDTH=10 COLSPAN=3>$strorder</TH><TH align=left WIDTH=\"100%\">$strquestionname</TH><TH WIDTH=10>$strgrade</TH><TH WIDTH=10>$stredit</TH></TR>";
6a952ce7 432 foreach ($order as $qnum) {
433 $count++;
434 echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
435 echo "<TD>$count</TD>";
436 echo "<TD>";
437 if ($count != 1) {
438 echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG
439 SRC=\"../../pix/t/up.gif\" BORDER=0></A>";
440 }
441 echo "</TD>";
442 echo "<TD>";
443 if ($count != $total) {
444 echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG
445 SRC=\"../../pix/t/down.gif\" BORDER=0></A>";
446 }
447 echo "</TD>";
448 echo "<TD>".$questions[$qnum]->name."</TD>";
449 echo "<TD>";
8d94f5a0 450 choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], "");
6a952ce7 451 echo "<TD>";
452 echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG
453 SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
454 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG
455 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
456 echo "</TD>";
457
7bd1aa1d 458 $sumgrade += $grades[$qnum];
6a952ce7 459 }
460 echo "<TR><TD COLSPAN=3><TD ALIGN=right>";
461 echo "<INPUT TYPE=submit VALUE=\"$strsavegrades:\">";
8d94f5a0 462 echo "<INPUT TYPE=hidden NAME=setgrades VALUE=\"save\">";
6a952ce7 463 echo "<TD ALIGN=LEFT BGCOLOR=\"$THEME->cellcontent\">";
464 echo "<B>$sumgrade</B>";
465 echo "</TD><TD></TD></TR>";
466 echo "</TABLE>";
467 echo "</FORM>";
10b9291c 468
469 return $sumgrade;
6a952ce7 470}
471
472
473function quiz_print_cat_question_list($categoryid) {
474// Prints a form to choose categories
475
476 global $THEME, $QUIZ_QUESTION_TYPE;
477
10b9291c 478 $strcategory = get_string("category", "quiz");
6a952ce7 479 $strquestion = get_string("question", "quiz");
480 $strnoquestions = get_string("noquestions", "quiz");
481 $strselect = get_string("select", "quiz");
482 $strcreatenewquestion = get_string("createnewquestion", "quiz");
483 $strquestionname = get_string("questionname", "quiz");
484 $strdelete = get_string("delete");
485 $stredit = get_string("edit");
486 $straddselectedtoquiz = get_string("addselectedtoquiz", "quiz");
487
488 if (!$categoryid) {
489 echo "<P align=center>";
490 print_string("selectcategoryabove", "quiz");
491 echo "</P>";
492 return;
493 }
a5e1f35c 494
6a952ce7 495 if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
496 notify("Category not found!");
497 return;
498 }
8d94f5a0 499 echo "<CENTER>";
10b9291c 500 echo text_to_html($category->info);
6a952ce7 501
10b9291c 502 echo "<FORM METHOD=GET ACTION=question.php>";
6a952ce7 503 echo "<B>$strquestion:</B>&nbsp;";
504 choose_from_menu($QUIZ_QUESTION_TYPE, "type", "", "");
505 echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
506 echo "<INPUT TYPE=submit NAME=new VALUE=\"$strcreatenewquestion\">";
507 echo "</FORM>";
8d94f5a0 508 echo "</CENTER>";
6a952ce7 509
510 if (!$questions = get_records("quiz_questions", "category", $category->id)) {
511 echo "<P align=center>";
512 print_string("noquestions", "quiz");
513 echo "</P>";
514 return;
515 }
516
10b9291c 517 $canedit = isteacher($category->course);
518
6a952ce7 519 echo "<FORM METHOD=post ACTION=edit.php>";
520 echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
10b9291c 521 echo "<TR><TH width=10>$strselect</TH><TH width=* align=left>$strquestionname</TH>";
522 if ($canedit) {
523 echo "<TH width=10>$stredit</TH>";
524 }
525 echo "</TR>";
6a952ce7 526 foreach ($questions as $question) {
527 echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
528 echo "<TD ALIGN=CENTER>";
529 echo "<INPUT TYPE=CHECKBOX NAME=q$question->id VALUE=\"1\">";
530 echo "</TD>";
531 echo "<TD>".$question->name."</TD>";
10b9291c 532 if ($canedit) {
533 echo "<TD>";
534 echo "<A TITLE=\"$strdelete\" HREF=\"question.php?delete=$question->id\"><IMG
535 SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
536 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG
537 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
538 echo "</TD></TR>";
539 }
540 echo "</TR>";
6a952ce7 541 }
542 echo "<TR><TD COLSPAN=3>";
543 echo "<INPUT TYPE=hidden NAME=add VALUE=\"1\">";
544 echo "<INPUT TYPE=submit VALUE=\"<< $straddselectedtoquiz\">";
545 echo "</TD></TR>";
546 echo "</TABLE>";
547 echo "</FORM>";
548}
a5e1f35c 549
3a506ca2 550
551function quiz_get_user_attempts($quizid, $userid) {
a5e1f35c 552// Returns a list of all attempts by a user
3a506ca2 553 return get_records_sql("SELECT * FROM quiz_attempts WHERE quiz = '$quizid' and user = '$userid' ORDER by attempt ASC");
554}
555
8d94f5a0 556
557function quiz_get_user_attempts_string($quiz, $attempts, $bestgrade) {
558/// Returns a simple little comma-separated list of all attempts,
559/// with the best grade bolded
560
561 $bestgrade = format_float($bestgrade);
562 foreach ($attempts as $attempt) {
563 $attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade);
564 if ($attemptgrade == $bestgrade) {
565 $userattempts[] = "<B>$attemptgrade</B>";
566 } else {
567 $userattempts[] = "$attemptgrade";
568 }
569 }
570 return implode(",", $userattempts);
571}
572
a5e1f35c 573function quiz_get_best_grade($quizid, $userid) {
574/// Get the best current grade for a particular user in a quiz
3a506ca2 575 if (!$grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz = '$quizid' and user = '$userid'")) {
576 return 0;
577 }
578
579 return $grade->grade;
580}
581
579ddad5 582function quiz_get_grade_records($quiz) {
583/// Gets all info required to display the table of quiz results
584/// for report.php
585
586 return get_records_sql("SELECT qg.*, u.firstname, u.lastname, u.picture
587 FROM quiz_grades qg, user u
588 WHERE qg.quiz = '$quiz->id'
589 AND qg.user = u.id");
590}
591
a5e1f35c 592function quiz_save_best_grade($quiz, $user) {
593/// Calculates the best grade out of all attempts at a quiz for a user,
594/// and then saves that grade in the quiz_grades table.
595
596 if (!$attempts = quiz_get_user_attempts($quiz->id, $user->id)) {
597 return false;
598 }
599
600 $bestgrade = quiz_calculate_best_grade($quiz, $attempts);
601 $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade);
602
603 if ($grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz='$quiz->id' AND user='$user->id'")) {
604 $grade->grade = $bestgrade;
605 $grade->timemodified = time();
606 if (!update_record("quiz_grades", $grade)) {
607 return false;
608 }
609 } else {
610 $grade->quiz = $quiz->id;
611 $grade->user = $user->id;
612 $grade->grade = $bestgrade;
613 $grade->timemodified = time();
614 if (!insert_record("quiz_grades", $grade)) {
615 return false;
616 }
617 }
618 return true;
619}
620
621
2a2c9725 622function quiz_get_answers($question) {
a5e1f35c 623// Given a question, returns the correct answers and grades
624 switch ($question->type) {
625 case SHORTANSWER; // Could be multiple answers
2a2c9725 626 return get_records_sql("SELECT a.*, sa.usecase, g.grade
a5e1f35c 627 FROM quiz_shortanswer sa, quiz_answers a, quiz_question_grades g
628 WHERE sa.question = '$question->id'
629 AND sa.question = a.question
630 AND sa.question = g.question");
631 break;
632
633 case TRUEFALSE; // Should be always two answers
634 return get_records_sql("SELECT a.*, g.grade
635 FROM quiz_answers a, quiz_question_grades g
636 WHERE a.question = '$question->id'
637 AND a.question = g.question");
638 break;
639
640 case MULTICHOICE; // Should be multiple answers
641 return get_records_sql("SELECT a.*, mc.single, g.grade
642 FROM quiz_multichoice mc, quiz_answers a, quiz_question_grades g
643 WHERE mc.question = '$question->id'
644 AND mc.question = a.question
645 AND mc.question = g.question");
646 break;
647
648 default:
649 return false;
650 }
651}
652
3a506ca2 653function quiz_calculate_best_grade($quiz, $attempts) {
a5e1f35c 654/// Calculate the best grade for a quiz given a number of attempts by a particular user.
3a506ca2 655
656 switch ($quiz->grademethod) {
a5e1f35c 657
658 case ATTEMPTFIRST:
3a506ca2 659 foreach ($attempts as $attempt) {
a5e1f35c 660 return $attempt->sumgrades;
3a506ca2 661 }
a5e1f35c 662 break;
663
664 case ATTEMPTLAST:
665 foreach ($attempts as $attempt) {
666 $final = $attempt->sumgrades;
667 }
668 return $final;
3a506ca2 669
a5e1f35c 670 case GRADEAVERAGE:
3a506ca2 671 $sum = 0;
672 $count = 0;
673 foreach ($attempts as $attempt) {
a5e1f35c 674 $sum += $attempt->sumgrades;
3a506ca2 675 $count++;
676 }
677 return (float)$sum/$count;
678
3a506ca2 679 default:
a5e1f35c 680 case GRADEHIGHEST:
681 $max = 0;
3a506ca2 682 foreach ($attempts as $attempt) {
a5e1f35c 683 if ($attempt->sumgrades > $max) {
684 $max = $attempt->sumgrades;
685 }
3a506ca2 686 }
a5e1f35c 687 return $max;
688 }
689}
690
691function quiz_save_attempt($quiz, $questions, $result, $attemptnum) {
692/// Given a quiz, a list of attempted questions and a total grade
693/// this function saves EVERYTHING so it can be reconstructed later
694/// if necessary.
695
696 global $USER;
697
698 // First let's save the attempt record itself
699
700 $attempt->quiz = $quiz->id;
701 $attempt->user = $USER->id;
702 $attempt->attempt = $attemptnum;
703 $attempt->sumgrades = $result->sumgrades;
704 $attempt->timemodified = time();
705
706 if (!$attempt->id = insert_record("quiz_attempts", $attempt)) {
7520988b 707 notify("Error while saving attempt");
a5e1f35c 708 return false;
709 }
710
711 // Now let's save all the questions for this attempt
712
713 foreach ($questions as $question) {
714 $response->attempt = $attempt->id;
715 $response->question = $question->id;
716 $response->grade = $result->grades[$question->id];
717 if ($question->answer) {
718 $response->answer = implode(",",$question->answer);
719 } else {
720 $response->answer = "";
721 }
722 if (!insert_record("quiz_responses", $response)) {
7520988b 723 notify("Error while saving response");
a5e1f35c 724 return false;
725 }
3a506ca2 726 }
a5e1f35c 727 return true;
3a506ca2 728}
730fd187 729
a5e1f35c 730
731function quiz_grade_attempt_results($quiz, $questions) {
732/// Given a list of questions (including answers for each one)
733/// this function does all the hard work of calculating the
734/// grades for each question, as well as a total grade for
735/// for the whole quiz. It returns everything in a structure
736/// that looks like:
737/// $result->sumgrades (sum of all grades for all questions)
738/// $result->percentage (Percentage of grades that were correct)
739/// $result->grade (final grade result for the whole quiz)
740/// $result->grades[] (array of grades, indexed by question id)
741/// $result->feedback[] (array of feedback arrays, indexed by question id)
742
743 if (!$questions) {
744 error("No questions!");
745 }
746
747 $result->sumgrades = 0;
748
749 foreach ($questions as $question) {
2a2c9725 750 if (!$answers = quiz_get_answers($question)) {
751 error("No answers defined for question id $question->id!");
a5e1f35c 752 }
753
754 $grade = 0; // default
755 $feedback = array ();
756
757 switch ($question->type) {
758 case SHORTANSWER:
759 if ($question->answer) {
760 $question->answer = $question->answer[0];
761 } else {
762 $question->answer = NULL;
763 }
764 foreach($answers as $answer) { // There might be multiple right answers
765 $feedback[$answer->id] = $answer->feedback;
2a2c9725 766 if (!$answer->usecase) { // Don't compare case
a5e1f35c 767 $answer->answer = strtolower($answer->answer);
768 $question->answer = strtolower($question->answer);
769 }
770 if ($question->answer == $answer->answer) {
771 $grade = (float)$answer->fraction * $answer->grade;
772 }
773 }
774 break;
775
776
777 case TRUEFALSE:
778 if ($question->answer) {
779 $question->answer = $question->answer[0];
780 } else {
781 $question->answer = NULL;
782 }
783 foreach($answers as $answer) { // There should be two answers (true and false)
784 $feedback[$answer->id] = $answer->feedback;
785 if ($question->answer == $answer->id) {
786 $grade = (float)$answer->fraction * $answer->grade;
787 }
788 }
789 break;
790
791
792 case MULTICHOICE:
793 foreach($answers as $answer) { // There will be multiple answers, perhaps more than one is right
794 $feedback[$answer->id] = $answer->feedback;
795 if ($question->answer) {
796 foreach ($question->answer as $questionanswer) {
797 if ($questionanswer == $answer->id) {
798 if ($answer->single) {
799 $grade = (float)$answer->fraction * $answer->grade;
800 continue;
801 } else {
802 $grade += (float)$answer->fraction * $answer->grade;
803 }
804 }
805 }
806 }
807 }
808 break;
809
810
811 }
812 if ($grade < 0.0) { // No negative grades
813 $grade = 0.0;
814 }
10b9291c 815
a5e1f35c 816 $result->grades[$question->id] = $grade;
817 $result->sumgrades += $grade;
818 $result->feedback[$question->id] = $feedback;
819 }
820
8d94f5a0 821 $fraction = (float)($result->sumgrades / $quiz->sumgrades);
822 $result->percentage = format_float($fraction * 100.0);
823 $result->grade = format_float($fraction * $quiz->grade);
a5e1f35c 824
825 return $result;
826}
827
730fd187 828?>