Some tidy ups
[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
98092498 156 if ($grade = get_record_sql("SELECT * FROM quiz_grades WHERE user = '$user->id' AND quiz = '$quiz->id'")) {
157
158 if ($grade->grade) {
159 $result->info = get_string("grade").": $grade->grade";
160 }
161 $result->time = $grade->timemodified;
162 return $result;
163 }
164 return NULL;
730fd187 165
166 return $return;
167}
168
169function quiz_user_complete($course, $user, $mod, $quiz) {
a5e1f35c 170/// Print a detailed representation of what a user has done with
171/// a given particular instance of this module, for user activity reports.
730fd187 172
173 return true;
174}
175
176function quiz_print_recent_activity(&$logs, $isteacher=false) {
a5e1f35c 177/// Given a list of logs, assumed to be those since the last login
178/// this function prints a short list of changes related to this module
179/// If isteacher is true then perhaps additional information is printed.
180/// This function is called from course/lib.php: print_recent_activity()
730fd187 181
182 global $CFG, $COURSE_TEACHER_COLOR;
183
184 return $content; // True if anything was printed, otherwise false
185}
186
187function quiz_cron () {
a5e1f35c 188/// Function to be run periodically according to the moodle cron
189/// This function searches for things that need to be done, such
190/// as sending out mail, toggling flags etc ...
730fd187 191
192 global $CFG;
193
194 return true;
195}
196
d0ac6bc2 197function quiz_grades($quizid) {
858deff0 198/// Must return an array of grades, indexed by user, and a max grade.
199
200 $return->grades = get_records_sql_menu("SELECT user,grade FROM quiz_grades WHERE quiz = '$quizid'");
201 $return->maxgrade = get_field("quiz", "grade", "id", "$quizid");
202 return $return;
d0ac6bc2 203}
204
730fd187 205
206//////////////////////////////////////////////////////////////////////////////////////
a5e1f35c 207/// Any other quiz functions go here. Each of them must have a name that
208/// starts with quiz_
730fd187 209
a8a372cc 210function quiz_print_comment($text) {
211 global $THEME;
212
213 echo "<FONT COLOR=\"$THEME->cellheading2\">".text_to_html($text, true, false)."</FONT>";
214}
215
216function quiz_print_question($number, $questionid, $grade, $courseid,
217 $feedback=NULL, $response=NULL, $actualgrade=NULL) {
a5e1f35c 218/// Prints a quiz question, any format
a8a372cc 219 global $THEME;
220
221 $comment = $THEME->cellheading2;
14d8c0b4 222
223 if (!$question = get_record("quiz_questions", "id", $questionid)) {
224 notify("Error: Question not found!");
225 }
226
227 $stranswer = get_string("answer", "quiz");
228 $strmarks = get_string("marks", "quiz");
229
230 echo "<TABLE WIDTH=100% CELLSPACING=10><TR><TD NOWRAP WIDTH=100 VALIGN=top>";
a8a372cc 231 echo "<P ALIGN=CENTER><B>$number</B></P>";
19c4f55c 232 if ($feedback or $response) {
a8a372cc 233 echo "<P ALIGN=CENTER><FONT SIZE=1>$strmarks: $actualgrade/$grade</FONT></P>";
234 } else {
235 echo "<P ALIGN=CENTER><FONT SIZE=1>$grade $strmarks</FONT></P>";
236 }
14d8c0b4 237 print_spacer(1,100);
238 echo "</TD><TD VALIGN=TOP>";
239
240 switch ($question->type) {
a5e1f35c 241 case SHORTANSWER:
14d8c0b4 242 if (!$options = get_record("quiz_shortanswer", "question", $question->id)) {
243 notify("Error: Missing question options!");
244 }
2a2c9725 245 echo text_to_html($question->questiontext);
14d8c0b4 246 if ($question->image) {
247 print_file_picture($question->image, $courseid, 200);
248 }
a8a372cc 249 if ($response) {
250 $value = "VALUE=\"$response[0]\"";
251 }
252 echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$question->id SIZE=20 $value></P>";
253 if ($feedback) {
254 quiz_print_comment("<P ALIGN=right>$feedback[0]</P>");
255 }
14d8c0b4 256 break;
257
a5e1f35c 258 case TRUEFALSE:
14d8c0b4 259 if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
260 notify("Error: Missing question options!");
261 }
262 if (!$true = get_record("quiz_answers", "id", $options->true)) {
263 notify("Error: Missing question answers!");
264 }
265 if (!$false = get_record("quiz_answers", "id", $options->false)) {
266 notify("Error: Missing question answers!");
267 }
268 if (!$true->answer) {
269 $true->answer = get_string("true", "quiz");
270 }
271 if (!$false->answer) {
272 $false->answer = get_string("false", "quiz");
273 }
2a2c9725 274 echo text_to_html($question->questiontext);
14d8c0b4 275 if ($question->image) {
276 print_file_picture($question->image, $courseid, 200);
277 }
a8a372cc 278
279 if ($response[$true->id]) {
280 $truechecked = "CHECKED";
281 $feedbackid = $true->id;
282 } else if ($response[$false->id]) {
283 $falsechecked = "CHECKED";
284 $feedbackid = $false->id;
285 }
14d8c0b4 286 echo "<P ALIGN=RIGHT>$stranswer:&nbsp;&nbsp;";
a8a372cc 287 echo "<INPUT $truechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$true->id\">$true->answer";
14d8c0b4 288 echo "&nbsp;&nbsp;&nbsp;";
a8a372cc 289 echo "<INPUT $falsechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$false->id\">$false->answer</P>";
290 if ($feedback) {
291 quiz_print_comment("<P ALIGN=right>$feedback[$feedbackid]</P>");
292 }
293
14d8c0b4 294 break;
295
a5e1f35c 296 case MULTICHOICE:
14d8c0b4 297 if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
298 notify("Error: Missing question options!");
299 }
a5e1f35c 300 if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
14d8c0b4 301 notify("Error: Missing question answers!");
302 }
2a2c9725 303 echo text_to_html($question->questiontext);
14d8c0b4 304 if ($question->image) {
305 print_file_picture($question->image, $courseid, 200);
306 }
307 echo "<TABLE ALIGN=right>";
308 echo "<TR><TD valign=top>$stranswer:&nbsp;&nbsp;</TD><TD>";
309 echo "<TABLE ALIGN=right>";
310 $answerids = explode(",", $options->answers);
a8a372cc 311
14d8c0b4 312 foreach ($answerids as $key => $answerid) {
313 $answer = $answers[$answerid];
314 $qnum = $key + 1;
a8a372cc 315
316 if ($feedback and $response[$answerid]) {
317 $checked = "CHECKED";
318 } else {
319 $checked = "";
320 }
14d8c0b4 321 echo "<TR><TD valign=top>";
a5e1f35c 322 if ($options->single) {
a8a372cc 323 echo "<INPUT $checked TYPE=RADIO NAME=q$question->id VALUE=\"$answer->id\">";
14d8c0b4 324 } else {
a8a372cc 325 echo "<INPUT $checked TYPE=CHECKBOX NAME=q$question->id"."a$answer->id VALUE=\"$answer->id\">";
14d8c0b4 326 }
327 echo "</TD>";
328 echo "<TD valign=top>$qnum. $answer->answer</TD>";
a8a372cc 329 if ($feedback) {
330 echo "<TD valign=top>&nbsp;";
331 if ($response[$answerid]) {
332 quiz_print_comment($feedback[$answerid]);
333 }
334 echo "</TD>";
335 }
14d8c0b4 336 echo "</TR>";
337 }
338 echo "</TABLE>";
339 echo "</TABLE>";
340 break;
341
342 default:
343 notify("Error: Unknown question type!");
344 }
345
346 echo "</TD></TR></TABLE>";
3a506ca2 347}
348
a5e1f35c 349function quiz_print_quiz_questions($quiz, $results=NULL) {
350// Prints a whole quiz on one page.
351
352 if (!$quiz->questions) {
10b9291c 353 notify("No questions have been defined!", "view.php?id=$cm->id");
354 return false;
a5e1f35c 355 }
356
357 $questions = explode(",", $quiz->questions);
358
359 if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) {
10b9291c 360 notify("No grades were found for these questions!");
361 return false;
a5e1f35c 362 }
363
364 echo "<FORM METHOD=POST ACTION=attempt.php>";
365 echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">";
a8a372cc 366
a5e1f35c 367 foreach ($questions as $key => $questionid) {
a8a372cc 368 if ($results) {
369 $feedback = $results->feedback[$questionid];
370 } else {
371 $feedback = NULL;
372 }
a5e1f35c 373 print_simple_box_start("CENTER", "90%");
a8a372cc 374 quiz_print_question($key+1, $questionid, $grades[$questionid]->grade, $quiz->course,
375 $results->feedback[$questionid], $results->response[$questionid], $results->grades[$questionid]);
a5e1f35c 376 print_simple_box_end();
377 echo "<BR>";
378 }
a8a372cc 379
380 if (!$results) {
381 echo "<CENTER><INPUT TYPE=submit VALUE=\"".get_string("savemyanswers", "quiz")."\"></CENTER>";
382 }
a5e1f35c 383 echo "</FORM>";
10b9291c 384
385 return true;
a5e1f35c 386}
6a952ce7 387
388function quiz_get_default_category($courseid) {
389 if ($categories = get_records("quiz_categories", "course", $courseid, "id")) {
390 foreach ($categories as $category) {
391 return $category; // Return the first one (lowest id)
392 }
393 }
394
395 // Otherwise, we need to make one
10b9291c 396 $category->name = get_string("default", "quiz");
397 $category->info = get_string("defaultinfo", "quiz");
6a952ce7 398 $category->course = $courseid;
399 $category->publish = 0;
400
401 if (!$category->id = insert_record("quiz_categories", $category)) {
402 notify("Error creating a default category!");
403 return false;
404 }
405 return $category;
406}
407
408function quiz_print_category_form($course, $current) {
409// Prints a form to choose categories
410
411 if (!$categories = get_records_sql_menu("SELECT id,name FROM quiz_categories
412 WHERE course='$course->id' OR publish = '1'
413 ORDER by name ASC")) {
414 if (!$category = quiz_get_default_category($course->id)) {
415 notify("Error creating a default category!");
416 return false;
417 }
418 $categories[$category->id] = $category->name;
419 }
8d94f5a0 420 foreach ($categories as $key => $category) {
421 if ($category->publish) {
422 if ($course = get_record_sql("course", "id", $category->course)) {
423 $categories[$key]->name .= " ($course->shortname)";
424 }
425 }
426 }
6a952ce7 427 $strcategory = get_string("category", "quiz");
428 $strshow = get_string("show", "quiz");
6b069ece 429 $streditcats = get_string("editcategories", "quiz");
6a952ce7 430
6b069ece 431 echo "<TABLE width=\"100%\"><TR><TD>";
6a952ce7 432 echo "<FORM METHOD=POST ACTION=edit.php>";
433 echo "<B>$strcategory:</B>&nbsp;";
434 choose_from_menu($categories, "cat", "$current");
92a3c884 435 echo "<INPUT TYPE=submit VALUE=\"$strshow\">";
6a952ce7 436 echo "</FORM>";
6b069ece 437 echo "</TD><TD align=right>";
438 echo "<FORM METHOD=GET ACTION=category.php>";
439 echo "<INPUT TYPE=hidden NAME=id VALUE=\"$course->id\">";
440 echo "<INPUT TYPE=submit VALUE=\"$streditcats\">";
441 echo "</FORM>";
442 echo "</TD></TR></TABLE>";
6a952ce7 443}
444
445
7bd1aa1d 446function quiz_get_all_question_grades($questionlist, $quizid) {
447// Given a list of question IDs, finds grades or invents them to
448// create an array of matching grades
449
92a3c884 450 $questions = get_records_sql("SELECT question,grade FROM quiz_question_grades
7bd1aa1d 451 WHERE quiz = '$quizid'
452 AND question IN ($questionlist)");
453
454 $list = explode(",", $questionlist);
455 $grades = array();
456
457 foreach ($list as $qid) {
458 if (isset($questions[$qid])) {
459 $grades[$qid] = $questions[$qid]->grade;
460 } else {
461 $grades[$qid] = 1;
462 }
463 }
464 return $grades;
465}
466
467
468function quiz_print_question_list($questionlist, $grades) {
6a952ce7 469// Prints a list of quiz questions in a small layout form with knobs
7bd1aa1d 470// $questionlist is comma-separated list
471// $grades is an array of corresponding grades
6a952ce7 472
473 global $THEME;
474
475 if (!$questionlist) {
476 echo "<P align=center>";
477 print_string("noquestions", "quiz");
478 echo "</P>";
479 return;
480 }
481
482 $order = explode(",", $questionlist);
483
7bd1aa1d 484 if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) {
6a952ce7 485 error("No questions were found!");
486 }
487
488 $strorder = get_string("order");
489 $strquestionname = get_string("questionname", "quiz");
490 $strgrade = get_string("grade");
491 $strdelete = get_string("delete");
492 $stredit = get_string("edit");
493 $strmoveup = get_string("moveup");
494 $strmovedown = get_string("movedown");
495 $strsavegrades = get_string("savegrades", "quiz");
496
10b9291c 497 for ($i=10; $i>=0; $i--) {
7bd1aa1d 498 $gradesmenu[$i] = $i;
6a952ce7 499 }
500 $count = 0;
501 $sumgrade = 0;
502 $total = count($order);
503 echo "<FORM METHOD=post ACTION=edit.php>";
504 echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
10b9291c 505 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 506 foreach ($order as $qnum) {
507 $count++;
508 echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
509 echo "<TD>$count</TD>";
510 echo "<TD>";
511 if ($count != 1) {
512 echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG
513 SRC=\"../../pix/t/up.gif\" BORDER=0></A>";
514 }
515 echo "</TD>";
516 echo "<TD>";
517 if ($count != $total) {
518 echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG
519 SRC=\"../../pix/t/down.gif\" BORDER=0></A>";
520 }
521 echo "</TD>";
522 echo "<TD>".$questions[$qnum]->name."</TD>";
523 echo "<TD>";
8d94f5a0 524 choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], "");
6a952ce7 525 echo "<TD>";
526 echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG
527 SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
528 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG
529 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
530 echo "</TD>";
531
7bd1aa1d 532 $sumgrade += $grades[$qnum];
6a952ce7 533 }
534 echo "<TR><TD COLSPAN=3><TD ALIGN=right>";
535 echo "<INPUT TYPE=submit VALUE=\"$strsavegrades:\">";
8d94f5a0 536 echo "<INPUT TYPE=hidden NAME=setgrades VALUE=\"save\">";
6a952ce7 537 echo "<TD ALIGN=LEFT BGCOLOR=\"$THEME->cellcontent\">";
538 echo "<B>$sumgrade</B>";
539 echo "</TD><TD></TD></TR>";
540 echo "</TABLE>";
541 echo "</FORM>";
10b9291c 542
543 return $sumgrade;
6a952ce7 544}
545
546
547function quiz_print_cat_question_list($categoryid) {
548// Prints a form to choose categories
549
550 global $THEME, $QUIZ_QUESTION_TYPE;
551
10b9291c 552 $strcategory = get_string("category", "quiz");
6a952ce7 553 $strquestion = get_string("question", "quiz");
554 $strnoquestions = get_string("noquestions", "quiz");
555 $strselect = get_string("select", "quiz");
556 $strcreatenewquestion = get_string("createnewquestion", "quiz");
557 $strquestionname = get_string("questionname", "quiz");
558 $strdelete = get_string("delete");
559 $stredit = get_string("edit");
560 $straddselectedtoquiz = get_string("addselectedtoquiz", "quiz");
561
562 if (!$categoryid) {
563 echo "<P align=center>";
564 print_string("selectcategoryabove", "quiz");
565 echo "</P>";
566 return;
567 }
a5e1f35c 568
6a952ce7 569 if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
570 notify("Category not found!");
571 return;
572 }
8d94f5a0 573 echo "<CENTER>";
10b9291c 574 echo text_to_html($category->info);
6a952ce7 575
10b9291c 576 echo "<FORM METHOD=GET ACTION=question.php>";
6a952ce7 577 echo "<B>$strquestion:</B>&nbsp;";
578 choose_from_menu($QUIZ_QUESTION_TYPE, "type", "", "");
579 echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
580 echo "<INPUT TYPE=submit NAME=new VALUE=\"$strcreatenewquestion\">";
581 echo "</FORM>";
8d94f5a0 582 echo "</CENTER>";
6a952ce7 583
584 if (!$questions = get_records("quiz_questions", "category", $category->id)) {
585 echo "<P align=center>";
586 print_string("noquestions", "quiz");
587 echo "</P>";
588 return;
589 }
590
10b9291c 591 $canedit = isteacher($category->course);
592
6a952ce7 593 echo "<FORM METHOD=post ACTION=edit.php>";
594 echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
10b9291c 595 echo "<TR><TH width=10>$strselect</TH><TH width=* align=left>$strquestionname</TH>";
596 if ($canedit) {
597 echo "<TH width=10>$stredit</TH>";
598 }
599 echo "</TR>";
6a952ce7 600 foreach ($questions as $question) {
601 echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
602 echo "<TD ALIGN=CENTER>";
603 echo "<INPUT TYPE=CHECKBOX NAME=q$question->id VALUE=\"1\">";
604 echo "</TD>";
605 echo "<TD>".$question->name."</TD>";
10b9291c 606 if ($canedit) {
607 echo "<TD>";
608 echo "<A TITLE=\"$strdelete\" HREF=\"question.php?delete=$question->id\"><IMG
609 SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
610 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG
611 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
612 echo "</TD></TR>";
613 }
614 echo "</TR>";
6a952ce7 615 }
616 echo "<TR><TD COLSPAN=3>";
617 echo "<INPUT TYPE=hidden NAME=add VALUE=\"1\">";
618 echo "<INPUT TYPE=submit VALUE=\"<< $straddselectedtoquiz\">";
619 echo "</TD></TR>";
620 echo "</TABLE>";
621 echo "</FORM>";
622}
a5e1f35c 623
3a506ca2 624
625function quiz_get_user_attempts($quizid, $userid) {
a5e1f35c 626// Returns a list of all attempts by a user
3a506ca2 627 return get_records_sql("SELECT * FROM quiz_attempts WHERE quiz = '$quizid' and user = '$userid' ORDER by attempt ASC");
628}
629
8d94f5a0 630
631function quiz_get_user_attempts_string($quiz, $attempts, $bestgrade) {
632/// Returns a simple little comma-separated list of all attempts,
633/// with the best grade bolded
634
635 $bestgrade = format_float($bestgrade);
636 foreach ($attempts as $attempt) {
637 $attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade);
638 if ($attemptgrade == $bestgrade) {
639 $userattempts[] = "<B>$attemptgrade</B>";
640 } else {
641 $userattempts[] = "$attemptgrade";
642 }
643 }
644 return implode(",", $userattempts);
645}
646
a5e1f35c 647function quiz_get_best_grade($quizid, $userid) {
648/// Get the best current grade for a particular user in a quiz
3a506ca2 649 if (!$grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz = '$quizid' and user = '$userid'")) {
650 return 0;
651 }
652
653 return $grade->grade;
654}
655
579ddad5 656function quiz_get_grade_records($quiz) {
657/// Gets all info required to display the table of quiz results
658/// for report.php
659
660 return get_records_sql("SELECT qg.*, u.firstname, u.lastname, u.picture
661 FROM quiz_grades qg, user u
662 WHERE qg.quiz = '$quiz->id'
663 AND qg.user = u.id");
664}
665
a5e1f35c 666function quiz_save_best_grade($quiz, $user) {
667/// Calculates the best grade out of all attempts at a quiz for a user,
668/// and then saves that grade in the quiz_grades table.
669
670 if (!$attempts = quiz_get_user_attempts($quiz->id, $user->id)) {
671 return false;
672 }
673
674 $bestgrade = quiz_calculate_best_grade($quiz, $attempts);
675 $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade);
676
677 if ($grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz='$quiz->id' AND user='$user->id'")) {
678 $grade->grade = $bestgrade;
679 $grade->timemodified = time();
680 if (!update_record("quiz_grades", $grade)) {
681 return false;
682 }
683 } else {
684 $grade->quiz = $quiz->id;
685 $grade->user = $user->id;
686 $grade->grade = $bestgrade;
687 $grade->timemodified = time();
688 if (!insert_record("quiz_grades", $grade)) {
689 return false;
690 }
691 }
692 return true;
693}
694
695
2a2c9725 696function quiz_get_answers($question) {
a5e1f35c 697// Given a question, returns the correct answers and grades
698 switch ($question->type) {
699 case SHORTANSWER; // Could be multiple answers
2a2c9725 700 return get_records_sql("SELECT a.*, sa.usecase, g.grade
a5e1f35c 701 FROM quiz_shortanswer sa, quiz_answers a, quiz_question_grades g
702 WHERE sa.question = '$question->id'
703 AND sa.question = a.question
704 AND sa.question = g.question");
705 break;
706
707 case TRUEFALSE; // Should be always two answers
708 return get_records_sql("SELECT a.*, g.grade
709 FROM quiz_answers a, quiz_question_grades g
710 WHERE a.question = '$question->id'
711 AND a.question = g.question");
712 break;
713
714 case MULTICHOICE; // Should be multiple answers
715 return get_records_sql("SELECT a.*, mc.single, g.grade
716 FROM quiz_multichoice mc, quiz_answers a, quiz_question_grades g
717 WHERE mc.question = '$question->id'
718 AND mc.question = a.question
719 AND mc.question = g.question");
720 break;
721
722 default:
723 return false;
724 }
725}
726
3a506ca2 727function quiz_calculate_best_grade($quiz, $attempts) {
a5e1f35c 728/// Calculate the best grade for a quiz given a number of attempts by a particular user.
3a506ca2 729
730 switch ($quiz->grademethod) {
a5e1f35c 731
732 case ATTEMPTFIRST:
3a506ca2 733 foreach ($attempts as $attempt) {
a5e1f35c 734 return $attempt->sumgrades;
3a506ca2 735 }
a5e1f35c 736 break;
737
738 case ATTEMPTLAST:
739 foreach ($attempts as $attempt) {
740 $final = $attempt->sumgrades;
741 }
742 return $final;
3a506ca2 743
a5e1f35c 744 case GRADEAVERAGE:
3a506ca2 745 $sum = 0;
746 $count = 0;
747 foreach ($attempts as $attempt) {
a5e1f35c 748 $sum += $attempt->sumgrades;
3a506ca2 749 $count++;
750 }
751 return (float)$sum/$count;
752
3a506ca2 753 default:
a5e1f35c 754 case GRADEHIGHEST:
755 $max = 0;
3a506ca2 756 foreach ($attempts as $attempt) {
a5e1f35c 757 if ($attempt->sumgrades > $max) {
758 $max = $attempt->sumgrades;
759 }
3a506ca2 760 }
a5e1f35c 761 return $max;
762 }
763}
764
765function quiz_save_attempt($quiz, $questions, $result, $attemptnum) {
766/// Given a quiz, a list of attempted questions and a total grade
767/// this function saves EVERYTHING so it can be reconstructed later
768/// if necessary.
769
770 global $USER;
771
772 // First let's save the attempt record itself
773
774 $attempt->quiz = $quiz->id;
775 $attempt->user = $USER->id;
776 $attempt->attempt = $attemptnum;
777 $attempt->sumgrades = $result->sumgrades;
778 $attempt->timemodified = time();
779
780 if (!$attempt->id = insert_record("quiz_attempts", $attempt)) {
7520988b 781 notify("Error while saving attempt");
a5e1f35c 782 return false;
783 }
784
785 // Now let's save all the questions for this attempt
786
787 foreach ($questions as $question) {
788 $response->attempt = $attempt->id;
789 $response->question = $question->id;
790 $response->grade = $result->grades[$question->id];
791 if ($question->answer) {
792 $response->answer = implode(",",$question->answer);
793 } else {
794 $response->answer = "";
795 }
796 if (!insert_record("quiz_responses", $response)) {
7520988b 797 notify("Error while saving response");
a5e1f35c 798 return false;
799 }
3a506ca2 800 }
a5e1f35c 801 return true;
3a506ca2 802}
730fd187 803
a5e1f35c 804
805function quiz_grade_attempt_results($quiz, $questions) {
806/// Given a list of questions (including answers for each one)
807/// this function does all the hard work of calculating the
808/// grades for each question, as well as a total grade for
809/// for the whole quiz. It returns everything in a structure
810/// that looks like:
811/// $result->sumgrades (sum of all grades for all questions)
812/// $result->percentage (Percentage of grades that were correct)
813/// $result->grade (final grade result for the whole quiz)
814/// $result->grades[] (array of grades, indexed by question id)
a8a372cc 815/// $result->response[] (array of response arrays, indexed by question id)
a5e1f35c 816/// $result->feedback[] (array of feedback arrays, indexed by question id)
817
818 if (!$questions) {
819 error("No questions!");
820 }
821
822 $result->sumgrades = 0;
823
824 foreach ($questions as $question) {
2a2c9725 825 if (!$answers = quiz_get_answers($question)) {
826 error("No answers defined for question id $question->id!");
a5e1f35c 827 }
828
829 $grade = 0; // default
19c4f55c 830 $feedback = array();
831 $response = array();
a5e1f35c 832
833 switch ($question->type) {
834 case SHORTANSWER:
835 if ($question->answer) {
836 $question->answer = $question->answer[0];
837 } else {
19c4f55c 838 $question->answer = "";
a5e1f35c 839 }
a8a372cc 840 $response[0] = $question->answer;
a5e1f35c 841 foreach($answers as $answer) { // There might be multiple right answers
2a2c9725 842 if (!$answer->usecase) { // Don't compare case
a5e1f35c 843 $answer->answer = strtolower($answer->answer);
844 $question->answer = strtolower($question->answer);
845 }
846 if ($question->answer == $answer->answer) {
a8a372cc 847 $feedback[0] = $answer->feedback;
a5e1f35c 848 $grade = (float)$answer->fraction * $answer->grade;
a8a372cc 849 continue;
a5e1f35c 850 }
851 }
852 break;
853
854
855 case TRUEFALSE:
856 if ($question->answer) {
857 $question->answer = $question->answer[0];
858 } else {
859 $question->answer = NULL;
860 }
861 foreach($answers as $answer) { // There should be two answers (true and false)
862 $feedback[$answer->id] = $answer->feedback;
863 if ($question->answer == $answer->id) {
864 $grade = (float)$answer->fraction * $answer->grade;
a8a372cc 865 $response[$answer->id] = true;
a5e1f35c 866 }
867 }
868 break;
869
870
871 case MULTICHOICE:
872 foreach($answers as $answer) { // There will be multiple answers, perhaps more than one is right
873 $feedback[$answer->id] = $answer->feedback;
874 if ($question->answer) {
875 foreach ($question->answer as $questionanswer) {
876 if ($questionanswer == $answer->id) {
877 if ($answer->single) {
878 $grade = (float)$answer->fraction * $answer->grade;
a8a372cc 879 $response[$answer->id] = true;
a5e1f35c 880 continue;
881 } else {
882 $grade += (float)$answer->fraction * $answer->grade;
a8a372cc 883 $response[$answer->id] = true;
a5e1f35c 884 }
885 }
886 }
887 }
888 }
889 break;
890
891
892 }
893 if ($grade < 0.0) { // No negative grades
894 $grade = 0.0;
895 }
10b9291c 896
a5e1f35c 897 $result->grades[$question->id] = $grade;
898 $result->sumgrades += $grade;
899 $result->feedback[$question->id] = $feedback;
a8a372cc 900 $result->response[$question->id] = $response;
a5e1f35c 901 }
902
8d94f5a0 903 $fraction = (float)($result->sumgrades / $quiz->sumgrades);
904 $result->percentage = format_float($fraction * 100.0);
905 $result->grade = format_float($fraction * $quiz->grade);
a5e1f35c 906
907 return $result;
908}
909
730fd187 910?>