Improvements to formatting on old Netscape browsers
[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");
8db3eadd 19define("RANDOM", "4");
8d94f5a0 20$QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz"),
a5e1f35c 21 TRUEFALSE => get_string("truefalse", "quiz"),
e1c91df0 22 SHORTANSWER => get_string("shortanswer", "quiz") );
a5e1f35c 23
24
25
26/// FUNCTIONS ///////////////////////////////////////////////////////////////////
730fd187 27
28function quiz_add_instance($quiz) {
a5e1f35c 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.
730fd187 33
49dcdd18 34 $quiz->created = time();
730fd187 35 $quiz->timemodified = time();
49dcdd18 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);
730fd187 40
7bd1aa1d 41 if (!$quiz->id = insert_record("quiz", $quiz)) {
42 return false; // some error occurred
43 }
44
10b9291c 45 // The grades for every question in this quiz are stored in an array
7bd1aa1d 46 if ($quiz->grades) {
47 foreach ($quiz->grades as $question => $grade) {
8d94f5a0 48 if ($question and $grade) {
49 unset($questiongrade);
50 $questiongrade->quiz = $quiz->id;
51 $questiongrade->question = $question;
52 $questiongrade->grade = $grade;
53 if (!insert_record("quiz_question_grades", $questiongrade)) {
54 return false;
55 }
7bd1aa1d 56 }
57 }
58 }
730fd187 59
7bd1aa1d 60 return $quiz->id;
730fd187 61}
62
63
64function quiz_update_instance($quiz) {
a5e1f35c 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.
730fd187 68
69 $quiz->timemodified = time();
49dcdd18 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);
730fd187 74 $quiz->id = $quiz->instance;
75
7bd1aa1d 76 if (!update_record("quiz", $quiz)) {
77 return false; // some error occurred
78 }
730fd187 79
7bd1aa1d 80
10b9291c 81 // The grades for every question in this quiz are stored in an array
7bd1aa1d 82 // Insert or update records as appropriate
83
84 $existing = get_records("quiz_question_grades", "quiz", $quiz->id, "", "question,grade,id");
85
86 if ($quiz->grades) {
87 foreach ($quiz->grades as $question => $grade) {
8d94f5a0 88 if ($question and $grade) {
89 unset($questiongrade);
90 $questiongrade->quiz = $quiz->id;
91 $questiongrade->question = $question;
92 $questiongrade->grade = $grade;
93 if (isset($existing[$question])) {
94 if ($existing[$question]->grade != $grade) {
95 $questiongrade->id = $existing[$question]->id;
96 if (!update_record("quiz_question_grades", $questiongrade)) {
97 return false;
98 }
99 }
100 } else {
101 if (!insert_record("quiz_question_grades", $questiongrade)) {
7bd1aa1d 102 return false;
103 }
104 }
7bd1aa1d 105 }
106 }
107 }
108
109 return true;
730fd187 110}
111
112
113function quiz_delete_instance($id) {
a5e1f35c 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.
730fd187 117
118 if (! $quiz = get_record("quiz", "id", "$id")) {
119 return false;
120 }
121
122 $result = true;
123
10b9291c 124 if ($attempts = get_record("quiz_attempts", "quiz", "$quiz->id")) {
125 foreach ($attempts as $attempt) {
126 if (! delete_records("quiz_responses", "attempt", "$attempt->id")) {
127 $result = false;
128 }
129 }
130 }
131
132 if (! delete_records("quiz_attempts", "quiz", "$quiz->id")) {
133 $result = false;
134 }
135
136 if (! delete_records("quiz_grades", "quiz", "$quiz->id")) {
137 $result = false;
138 }
139
140 if (! delete_records("quiz_question_grades", "quiz", "$quiz->id")) {
141 $result = false;
142 }
730fd187 143
144 if (! delete_records("quiz", "id", "$quiz->id")) {
145 $result = false;
146 }
147
148 return $result;
149}
150
151function quiz_user_outline($course, $user, $mod, $quiz) {
a5e1f35c 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
98092498 157 if ($grade = get_record_sql("SELECT * FROM quiz_grades WHERE user = '$user->id' AND quiz = '$quiz->id'")) {
158
159 if ($grade->grade) {
160 $result->info = get_string("grade").": $grade->grade";
161 }
162 $result->time = $grade->timemodified;
163 return $result;
164 }
165 return NULL;
730fd187 166
167 return $return;
168}
169
170function quiz_user_complete($course, $user, $mod, $quiz) {
a5e1f35c 171/// Print a detailed representation of what a user has done with
172/// a given particular instance of this module, for user activity reports.
730fd187 173
174 return true;
175}
176
177function quiz_print_recent_activity(&$logs, $isteacher=false) {
a5e1f35c 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()
730fd187 182
183 global $CFG, $COURSE_TEACHER_COLOR;
184
185 return $content; // True if anything was printed, otherwise false
186}
187
188function quiz_cron () {
a5e1f35c 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 ...
730fd187 192
193 global $CFG;
194
195 return true;
196}
197
d0ac6bc2 198function quiz_grades($quizid) {
858deff0 199/// Must return an array of grades, indexed by user, and a max grade.
200
201 $return->grades = get_records_sql_menu("SELECT user,grade FROM quiz_grades WHERE quiz = '$quizid'");
202 $return->maxgrade = get_field("quiz", "grade", "id", "$quizid");
203 return $return;
d0ac6bc2 204}
205
730fd187 206
207//////////////////////////////////////////////////////////////////////////////////////
a5e1f35c 208/// Any other quiz functions go here. Each of them must have a name that
209/// starts with quiz_
730fd187 210
a8a372cc 211function quiz_print_comment($text) {
212 global $THEME;
213
214 echo "<FONT COLOR=\"$THEME->cellheading2\">".text_to_html($text, true, false)."</FONT>";
215}
216
8db3eadd 217function quiz_print_correctanswer($text) {
218 global $THEME;
219
220 echo "<P ALIGN=RIGHT><SPAN CLASS=highlight>$text</SPAN></P>";
221}
222
c74a0ca5 223function quiz_print_question_icon($question) {
224// Prints a question icon
cc3b8c75 225
226 global $QUIZ_QUESTION_TYPE;
227
228 echo "<A HREF=\"question.php?id=$question->id\" TITLE=\"".$QUIZ_QUESTION_TYPE[$question->type]."\">";
c74a0ca5 229 switch ($question->type) {
230 case SHORTANSWER:
cc3b8c75 231 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/sa.gif\">";
c74a0ca5 232 break;
233 case TRUEFALSE:
cc3b8c75 234 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/tf.gif\">";
c74a0ca5 235 break;
236 case MULTICHOICE:
cc3b8c75 237 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/mc.gif\">";
c74a0ca5 238 break;
239 case RANDOM:
cc3b8c75 240 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/rs.gif\">";
c74a0ca5 241 break;
242 }
cc3b8c75 243 echo "</A>\n";
c74a0ca5 244}
245
246function quiz_print_question($number, $questionid, $grade, $courseid,
8db3eadd 247 $feedback=NULL, $response=NULL, $actualgrade=NULL, $correct=NULL) {
a5e1f35c 248/// Prints a quiz question, any format
a8a372cc 249
14d8c0b4 250 if (!$question = get_record("quiz_questions", "id", $questionid)) {
251 notify("Error: Question not found!");
252 }
253
254 $stranswer = get_string("answer", "quiz");
255 $strmarks = get_string("marks", "quiz");
256
257 echo "<TABLE WIDTH=100% CELLSPACING=10><TR><TD NOWRAP WIDTH=100 VALIGN=top>";
a8a372cc 258 echo "<P ALIGN=CENTER><B>$number</B></P>";
19c4f55c 259 if ($feedback or $response) {
a8a372cc 260 echo "<P ALIGN=CENTER><FONT SIZE=1>$strmarks: $actualgrade/$grade</FONT></P>";
261 } else {
262 echo "<P ALIGN=CENTER><FONT SIZE=1>$grade $strmarks</FONT></P>";
263 }
14d8c0b4 264 print_spacer(1,100);
265 echo "</TD><TD VALIGN=TOP>";
266
267 switch ($question->type) {
a5e1f35c 268 case SHORTANSWER:
14d8c0b4 269 if (!$options = get_record("quiz_shortanswer", "question", $question->id)) {
270 notify("Error: Missing question options!");
271 }
2a2c9725 272 echo text_to_html($question->questiontext);
14d8c0b4 273 if ($question->image) {
274 print_file_picture($question->image, $courseid, 200);
275 }
a8a372cc 276 if ($response) {
277 $value = "VALUE=\"$response[0]\"";
278 }
279 echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$question->id SIZE=20 $value></P>";
280 if ($feedback) {
281 quiz_print_comment("<P ALIGN=right>$feedback[0]</P>");
282 }
8db3eadd 283 if ($correct) {
c74a0ca5 284 $correctanswers = implode(", ", $correct);
8db3eadd 285 quiz_print_correctanswer($correctanswers);
286 }
14d8c0b4 287 break;
288
a5e1f35c 289 case TRUEFALSE:
14d8c0b4 290 if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
291 notify("Error: Missing question options!");
292 }
293 if (!$true = get_record("quiz_answers", "id", $options->true)) {
294 notify("Error: Missing question answers!");
295 }
296 if (!$false = get_record("quiz_answers", "id", $options->false)) {
297 notify("Error: Missing question answers!");
298 }
299 if (!$true->answer) {
300 $true->answer = get_string("true", "quiz");
301 }
302 if (!$false->answer) {
303 $false->answer = get_string("false", "quiz");
304 }
2a2c9725 305 echo text_to_html($question->questiontext);
14d8c0b4 306 if ($question->image) {
307 print_file_picture($question->image, $courseid, 200);
308 }
a8a372cc 309
310 if ($response[$true->id]) {
311 $truechecked = "CHECKED";
312 $feedbackid = $true->id;
313 } else if ($response[$false->id]) {
314 $falsechecked = "CHECKED";
315 $feedbackid = $false->id;
316 }
8db3eadd 317 if ($correct) {
318 if ($correct[$true->id]) {
319 $truecorrect = "CLASS=highlight";
320 }
321 if ($correct[$false->id]) {
322 $falsecorrect = "CLASS=highlight";
323 }
324 }
325 echo "<TABLE ALIGN=right cellpadding=5><TR><TD align=right>$stranswer:&nbsp;&nbsp;";
326 echo "<TD $truecorrect>";
a8a372cc 327 echo "<INPUT $truechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$true->id\">$true->answer";
8db3eadd 328 echo "</TD><TD $falsecorrect>";
a8a372cc 329 echo "<INPUT $falsechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$false->id\">$false->answer</P>";
8db3eadd 330 echo "</TD></TR></TABLE><BR CLEAR=ALL>";
a8a372cc 331 if ($feedback) {
332 quiz_print_comment("<P ALIGN=right>$feedback[$feedbackid]</P>");
333 }
334
14d8c0b4 335 break;
336
a5e1f35c 337 case MULTICHOICE:
14d8c0b4 338 if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
339 notify("Error: Missing question options!");
340 }
a5e1f35c 341 if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
14d8c0b4 342 notify("Error: Missing question answers!");
343 }
2a2c9725 344 echo text_to_html($question->questiontext);
14d8c0b4 345 if ($question->image) {
346 print_file_picture($question->image, $courseid, 200);
347 }
348 echo "<TABLE ALIGN=right>";
349 echo "<TR><TD valign=top>$stranswer:&nbsp;&nbsp;</TD><TD>";
81b635c3 350 echo "<TABLE>";
14d8c0b4 351 $answerids = explode(",", $options->answers);
a8a372cc 352
14d8c0b4 353 foreach ($answerids as $key => $answerid) {
354 $answer = $answers[$answerid];
355 $qnum = $key + 1;
a8a372cc 356
357 if ($feedback and $response[$answerid]) {
358 $checked = "CHECKED";
359 } else {
360 $checked = "";
361 }
14d8c0b4 362 echo "<TR><TD valign=top>";
a5e1f35c 363 if ($options->single) {
a8a372cc 364 echo "<INPUT $checked TYPE=RADIO NAME=q$question->id VALUE=\"$answer->id\">";
14d8c0b4 365 } else {
a8a372cc 366 echo "<INPUT $checked TYPE=CHECKBOX NAME=q$question->id"."a$answer->id VALUE=\"$answer->id\">";
14d8c0b4 367 }
368 echo "</TD>";
8db3eadd 369 if ($feedback and $correct[$answer->id]) {
370 echo "<TD valign=top CLASS=highlight>$qnum. $answer->answer</TD>";
371 } else {
372 echo "<TD valign=top>$qnum. $answer->answer</TD>";
373 }
a8a372cc 374 if ($feedback) {
375 echo "<TD valign=top>&nbsp;";
376 if ($response[$answerid]) {
377 quiz_print_comment($feedback[$answerid]);
378 }
379 echo "</TD>";
380 }
14d8c0b4 381 echo "</TR>";
382 }
383 echo "</TABLE>";
384 echo "</TABLE>";
385 break;
386
8db3eadd 387 case RANDOM:
388 echo "<P>Random questions not supported yet</P>";
389 break;
390
391
14d8c0b4 392 default:
393 notify("Error: Unknown question type!");
394 }
395
396 echo "</TD></TR></TABLE>";
3a506ca2 397}
398
a5e1f35c 399function quiz_print_quiz_questions($quiz, $results=NULL) {
400// Prints a whole quiz on one page.
401
402 if (!$quiz->questions) {
10b9291c 403 notify("No questions have been defined!", "view.php?id=$cm->id");
404 return false;
a5e1f35c 405 }
406
407 $questions = explode(",", $quiz->questions);
408
409 if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) {
10b9291c 410 notify("No grades were found for these questions!");
411 return false;
a5e1f35c 412 }
413
414 echo "<FORM METHOD=POST ACTION=attempt.php>";
415 echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">";
a8a372cc 416
a5e1f35c 417 foreach ($questions as $key => $questionid) {
8db3eadd 418 $feedback = NULL;
419 $response = NULL;
420 $actualgrades = NULL;
421 $correct = NULL;
a8a372cc 422 if ($results) {
8db3eadd 423 $feedback = $results->feedback[$questionid];
424 $response = $results->response[$questionid];
425 $actualgrades = $results->grades[$questionid];
426 if ($quiz->correctanswers) {
427 $correct = $results->correct[$questionid];
428 }
a8a372cc 429 }
8db3eadd 430
a5e1f35c 431 print_simple_box_start("CENTER", "90%");
a8a372cc 432 quiz_print_question($key+1, $questionid, $grades[$questionid]->grade, $quiz->course,
8db3eadd 433 $feedback, $response, $actualgrades, $correct);
a5e1f35c 434 print_simple_box_end();
435 echo "<BR>";
436 }
a8a372cc 437
438 if (!$results) {
439 echo "<CENTER><INPUT TYPE=submit VALUE=\"".get_string("savemyanswers", "quiz")."\"></CENTER>";
440 }
a5e1f35c 441 echo "</FORM>";
10b9291c 442
443 return true;
a5e1f35c 444}
6a952ce7 445
446function quiz_get_default_category($courseid) {
447 if ($categories = get_records("quiz_categories", "course", $courseid, "id")) {
448 foreach ($categories as $category) {
449 return $category; // Return the first one (lowest id)
450 }
451 }
452
453 // Otherwise, we need to make one
10b9291c 454 $category->name = get_string("default", "quiz");
455 $category->info = get_string("defaultinfo", "quiz");
6a952ce7 456 $category->course = $courseid;
457 $category->publish = 0;
458
459 if (!$category->id = insert_record("quiz_categories", $category)) {
460 notify("Error creating a default category!");
461 return false;
462 }
463 return $category;
464}
465
c74a0ca5 466function quiz_get_category_menu($courseid, $published=false) {
467 if ($published) {
468 $publish = "OR publish = '1'";
469 }
470 return get_records_sql_menu("SELECT id,name FROM quiz_categories WHERE course='$courseid' $publish ORDER by name ASC");
471}
472
6a952ce7 473function quiz_print_category_form($course, $current) {
474// Prints a form to choose categories
475
b55a466b 476 if (!$categories = get_records_sql("SELECT * FROM quiz_categories WHERE course='$course->id' OR publish = '1' ORDER by name ASC")) {
6a952ce7 477 if (!$category = quiz_get_default_category($course->id)) {
478 notify("Error creating a default category!");
479 return false;
480 }
cb62c00a 481 $categories[$category->id] = $category;
6a952ce7 482 }
8d94f5a0 483 foreach ($categories as $key => $category) {
484 if ($category->publish) {
b55a466b 485 if ($catcourse = get_record("course", "id", $category->course)) {
486 $category->name .= " ($catcourse->shortname)";
8d94f5a0 487 }
488 }
b55a466b 489 $catmenu[$category->id] = $category->name;
8d94f5a0 490 }
6a952ce7 491 $strcategory = get_string("category", "quiz");
492 $strshow = get_string("show", "quiz");
6b069ece 493 $streditcats = get_string("editcategories", "quiz");
6a952ce7 494
6b069ece 495 echo "<TABLE width=\"100%\"><TR><TD>";
6a952ce7 496 echo "<FORM METHOD=POST ACTION=edit.php>";
497 echo "<B>$strcategory:</B>&nbsp;";
b55a466b 498 choose_from_menu($catmenu, "cat", "$current");
92a3c884 499 echo "<INPUT TYPE=submit VALUE=\"$strshow\">";
6a952ce7 500 echo "</FORM>";
6b069ece 501 echo "</TD><TD align=right>";
502 echo "<FORM METHOD=GET ACTION=category.php>";
503 echo "<INPUT TYPE=hidden NAME=id VALUE=\"$course->id\">";
504 echo "<INPUT TYPE=submit VALUE=\"$streditcats\">";
505 echo "</FORM>";
506 echo "</TD></TR></TABLE>";
6a952ce7 507}
508
509
7bd1aa1d 510function 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
513
92a3c884 514 $questions = get_records_sql("SELECT question,grade FROM quiz_question_grades
7bd1aa1d 515 WHERE quiz = '$quizid'
516 AND question IN ($questionlist)");
517
518 $list = explode(",", $questionlist);
519 $grades = array();
520
521 foreach ($list as $qid) {
522 if (isset($questions[$qid])) {
523 $grades[$qid] = $questions[$qid]->grade;
524 } else {
525 $grades[$qid] = 1;
526 }
527 }
528 return $grades;
529}
530
531
532function quiz_print_question_list($questionlist, $grades) {
6a952ce7 533// Prints a list of quiz questions in a small layout form with knobs
7bd1aa1d 534// $questionlist is comma-separated list
535// $grades is an array of corresponding grades
6a952ce7 536
537 global $THEME;
538
539 if (!$questionlist) {
540 echo "<P align=center>";
541 print_string("noquestions", "quiz");
542 echo "</P>";
543 return;
544 }
545
546 $order = explode(",", $questionlist);
547
7bd1aa1d 548 if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) {
6a952ce7 549 error("No questions were found!");
550 }
551
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");
c74a0ca5 560 $strtype = get_string("type", "quiz");
6a952ce7 561
10b9291c 562 for ($i=10; $i>=0; $i--) {
7bd1aa1d 563 $gradesmenu[$i] = $i;
6a952ce7 564 }
565 $count = 0;
566 $sumgrade = 0;
567 $total = count($order);
568 echo "<FORM METHOD=post ACTION=edit.php>";
569 echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
c74a0ca5 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>";
6a952ce7 571 foreach ($order as $qnum) {
572 $count++;
573 echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
574 echo "<TD>$count</TD>";
575 echo "<TD>";
576 if ($count != 1) {
577 echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG
578 SRC=\"../../pix/t/up.gif\" BORDER=0></A>";
579 }
580 echo "</TD>";
581 echo "<TD>";
582 if ($count != $total) {
583 echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG
584 SRC=\"../../pix/t/down.gif\" BORDER=0></A>";
585 }
586 echo "</TD>";
587 echo "<TD>".$questions[$qnum]->name."</TD>";
c74a0ca5 588 echo "<TD WIDTH=16 ALIGN=CENTER>";
589 quiz_print_question_icon($questions[$qnum]);
590 echo "</TD>";
6a952ce7 591 echo "<TD>";
8d94f5a0 592 choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], "");
6a952ce7 593 echo "<TD>";
594 echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG
595 SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
596 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG
597 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
598 echo "</TD>";
599
7bd1aa1d 600 $sumgrade += $grades[$qnum];
6a952ce7 601 }
c74a0ca5 602 echo "<TR><TD COLSPAN=5 ALIGN=right>";
6a952ce7 603 echo "<INPUT TYPE=submit VALUE=\"$strsavegrades:\">";
8d94f5a0 604 echo "<INPUT TYPE=hidden NAME=setgrades VALUE=\"save\">";
6a952ce7 605 echo "<TD ALIGN=LEFT BGCOLOR=\"$THEME->cellcontent\">";
606 echo "<B>$sumgrade</B>";
607 echo "</TD><TD></TD></TR>";
608 echo "</TABLE>";
609 echo "</FORM>";
10b9291c 610
611 return $sumgrade;
6a952ce7 612}
613
614
615function quiz_print_cat_question_list($categoryid) {
616// Prints a form to choose categories
617
618 global $THEME, $QUIZ_QUESTION_TYPE;
619
10b9291c 620 $strcategory = get_string("category", "quiz");
6a952ce7 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");
c74a0ca5 629 $strtype = get_string("type", "quiz");
6a952ce7 630
631 if (!$categoryid) {
632 echo "<P align=center>";
633 print_string("selectcategoryabove", "quiz");
634 echo "</P>";
635 return;
636 }
a5e1f35c 637
6a952ce7 638 if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
639 notify("Category not found!");
640 return;
641 }
8d94f5a0 642 echo "<CENTER>";
10b9291c 643 echo text_to_html($category->info);
6a952ce7 644
10b9291c 645 echo "<FORM METHOD=GET ACTION=question.php>";
6a952ce7 646 echo "<B>$strquestion:</B>&nbsp;";
647 choose_from_menu($QUIZ_QUESTION_TYPE, "type", "", "");
648 echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
649 echo "<INPUT TYPE=submit NAME=new VALUE=\"$strcreatenewquestion\">";
cd63d77e 650 helpbutton("questiontypes", $strcreatenewquestion, "quiz");
6a952ce7 651 echo "</FORM>";
8d94f5a0 652 echo "</CENTER>";
6a952ce7 653
654 if (!$questions = get_records("quiz_questions", "category", $category->id)) {
655 echo "<P align=center>";
656 print_string("noquestions", "quiz");
657 echo "</P>";
658 return;
659 }
660
10b9291c 661 $canedit = isteacher($category->course);
662
6a952ce7 663 echo "<FORM METHOD=post ACTION=edit.php>";
664 echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
c74a0ca5 665 echo "<TR><TH width=10>$strselect</TH><TH width=* align=left>$strquestionname</TH><TH WIDTH=16>$strtype</TH>";
10b9291c 666 if ($canedit) {
667 echo "<TH width=10>$stredit</TH>";
668 }
669 echo "</TR>";
6a952ce7 670 foreach ($questions as $question) {
671 echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
672 echo "<TD ALIGN=CENTER>";
673 echo "<INPUT TYPE=CHECKBOX NAME=q$question->id VALUE=\"1\">";
674 echo "</TD>";
675 echo "<TD>".$question->name."</TD>";
c74a0ca5 676 echo "<TD WIDTH=16 ALIGN=CENTER>";
677 quiz_print_question_icon($question);
678 echo "</TD>";
10b9291c 679 if ($canedit) {
680 echo "<TD>";
e1c91df0 681 echo "<A TITLE=\"$strdelete\" HREF=\"question.php?id=$question->id&delete=$question->id\"><IMG
10b9291c 682 SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
683 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG
684 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
685 echo "</TD></TR>";
686 }
687 echo "</TR>";
6a952ce7 688 }
689 echo "<TR><TD COLSPAN=3>";
690 echo "<INPUT TYPE=hidden NAME=add VALUE=\"1\">";
691 echo "<INPUT TYPE=submit VALUE=\"<< $straddselectedtoquiz\">";
692 echo "</TD></TR>";
693 echo "</TABLE>";
694 echo "</FORM>";
695}
a5e1f35c 696
3a506ca2 697
958aafe2 698function 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();
705
706 return insert_record("quiz_attempts", $attempt);
707}
708
709function 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");
713}
714
3a506ca2 715function quiz_get_user_attempts($quizid, $userid) {
a5e1f35c 716// Returns a list of all attempts by a user
958aafe2 717 return get_records_sql("SELECT * FROM quiz_attempts
718 WHERE quiz = '$quizid' and user = '$userid' AND timefinish > 0
719 ORDER by attempt ASC");
3a506ca2 720}
721
8d94f5a0 722
723function quiz_get_user_attempts_string($quiz, $attempts, $bestgrade) {
724/// Returns a simple little comma-separated list of all attempts,
6d86b5dc 725/// with each grade linked to the feedback report and with the best grade highlighted
8d94f5a0 726
727 $bestgrade = format_float($bestgrade);
728 foreach ($attempts as $attempt) {
729 $attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade);
730 if ($attemptgrade == $bestgrade) {
6d86b5dc 731 $userattempts[] = "<SPAN CLASS=highlight><A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A></SPAN>";
8d94f5a0 732 } else {
6d86b5dc 733 $userattempts[] = "<A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A>";
8d94f5a0 734 }
735 }
736 return implode(",", $userattempts);
737}
738
a5e1f35c 739function quiz_get_best_grade($quizid, $userid) {
740/// Get the best current grade for a particular user in a quiz
3a506ca2 741 if (!$grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz = '$quizid' and user = '$userid'")) {
742 return 0;
743 }
744
2383cadb 745 return (round($grade->grade,0));
3a506ca2 746}
747
579ddad5 748function quiz_get_grade_records($quiz) {
749/// Gets all info required to display the table of quiz results
750/// for report.php
751
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");
756}
757
e331eb06 758function quiz_save_best_grade($quiz, $userid) {
a5e1f35c 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.
761
e331eb06 762 if (!$attempts = quiz_get_user_attempts($quiz->id, $userid)) {
e909c8d0 763 notify("Could not find any user attempts");
a5e1f35c 764 return false;
765 }
766
767 $bestgrade = quiz_calculate_best_grade($quiz, $attempts);
768 $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade);
769
e331eb06 770 if ($grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz='$quiz->id' AND user='$userid'")) {
a5e1f35c 771 $grade->grade = $bestgrade;
772 $grade->timemodified = time();
773 if (!update_record("quiz_grades", $grade)) {
e909c8d0 774 notify("Could not update best grade");
a5e1f35c 775 return false;
776 }
777 } else {
778 $grade->quiz = $quiz->id;
e331eb06 779 $grade->user = $userid;
38f03e5a 780 $grade->grade = round($bestgrade, 2);
a5e1f35c 781 $grade->timemodified = time();
782 if (!insert_record("quiz_grades", $grade)) {
e909c8d0 783 notify("Could not insert new best grade");
a5e1f35c 784 return false;
785 }
786 }
787 return true;
788}
789
790
2a2c9725 791function quiz_get_answers($question) {
a5e1f35c 792// Given a question, returns the correct answers and grades
793 switch ($question->type) {
794 case SHORTANSWER; // Could be multiple answers
2a2c9725 795 return get_records_sql("SELECT a.*, sa.usecase, g.grade
a5e1f35c 796 FROM quiz_shortanswer sa, quiz_answers a, quiz_question_grades g
797 WHERE sa.question = '$question->id'
798 AND sa.question = a.question
799 AND sa.question = g.question");
800 break;
801
802 case TRUEFALSE; // Should be always two answers
803 return get_records_sql("SELECT a.*, g.grade
804 FROM quiz_answers a, quiz_question_grades g
805 WHERE a.question = '$question->id'
806 AND a.question = g.question");
807 break;
808
809 case MULTICHOICE; // Should be multiple answers
810 return get_records_sql("SELECT a.*, mc.single, g.grade
811 FROM quiz_multichoice mc, quiz_answers a, quiz_question_grades g
812 WHERE mc.question = '$question->id'
813 AND mc.question = a.question
814 AND mc.question = g.question");
815 break;
816
8db3eadd 817 case RANDOM:
818 return false; // Not done yet
819 break;
820
a5e1f35c 821 default:
822 return false;
823 }
824}
825
3a506ca2 826function quiz_calculate_best_grade($quiz, $attempts) {
a5e1f35c 827/// Calculate the best grade for a quiz given a number of attempts by a particular user.
3a506ca2 828
829 switch ($quiz->grademethod) {
a5e1f35c 830
831 case ATTEMPTFIRST:
3a506ca2 832 foreach ($attempts as $attempt) {
a5e1f35c 833 return $attempt->sumgrades;
3a506ca2 834 }
a5e1f35c 835 break;
836
837 case ATTEMPTLAST:
838 foreach ($attempts as $attempt) {
839 $final = $attempt->sumgrades;
840 }
841 return $final;
3a506ca2 842
a5e1f35c 843 case GRADEAVERAGE:
3a506ca2 844 $sum = 0;
845 $count = 0;
846 foreach ($attempts as $attempt) {
a5e1f35c 847 $sum += $attempt->sumgrades;
3a506ca2 848 $count++;
849 }
850 return (float)$sum/$count;
851
3a506ca2 852 default:
a5e1f35c 853 case GRADEHIGHEST:
854 $max = 0;
3a506ca2 855 foreach ($attempts as $attempt) {
a5e1f35c 856 if ($attempt->sumgrades > $max) {
857 $max = $attempt->sumgrades;
858 }
3a506ca2 859 }
a5e1f35c 860 return $max;
861 }
862}
863
864function quiz_save_attempt($quiz, $questions, $result, $attemptnum) {
865/// Given a quiz, a list of attempted questions and a total grade
866/// this function saves EVERYTHING so it can be reconstructed later
867/// if necessary.
868
869 global $USER;
870
958aafe2 871 // First find the attempt in the database (start of attempt)
872
873 if (!$attempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
874 notify("Trying to save an attempt that was not started!");
875 return false;
876 }
877
878 if ($attempt->attempt != $attemptnum) { // Double check.
879 notify("Number of this attempt is different to the unfinished one!");
880 return false;
881 }
882
883 // Now let's complete this record and save it
a5e1f35c 884
a5e1f35c 885 $attempt->sumgrades = $result->sumgrades;
958aafe2 886 $attempt->timefinish = time();
a5e1f35c 887 $attempt->timemodified = time();
888
958aafe2 889 if (! update_record("quiz_attempts", $attempt)) {
7520988b 890 notify("Error while saving attempt");
a5e1f35c 891 return false;
892 }
893
894 // Now let's save all the questions for this attempt
895
896 foreach ($questions as $question) {
897 $response->attempt = $attempt->id;
898 $response->question = $question->id;
899 $response->grade = $result->grades[$question->id];
900 if ($question->answer) {
901 $response->answer = implode(",",$question->answer);
902 } else {
903 $response->answer = "";
904 }
905 if (!insert_record("quiz_responses", $response)) {
7520988b 906 notify("Error while saving response");
a5e1f35c 907 return false;
908 }
3a506ca2 909 }
a5e1f35c 910 return true;
3a506ca2 911}
730fd187 912
a5e1f35c 913
914function quiz_grade_attempt_results($quiz, $questions) {
915/// Given a list of questions (including answers for each one)
916/// this function does all the hard work of calculating the
917/// grades for each question, as well as a total grade for
918/// for the whole quiz. It returns everything in a structure
919/// that looks like:
920/// $result->sumgrades (sum of all grades for all questions)
921/// $result->percentage (Percentage of grades that were correct)
922/// $result->grade (final grade result for the whole quiz)
923/// $result->grades[] (array of grades, indexed by question id)
a8a372cc 924/// $result->response[] (array of response arrays, indexed by question id)
a5e1f35c 925/// $result->feedback[] (array of feedback arrays, indexed by question id)
8db3eadd 926/// $result->correct[] (array of feedback arrays, indexed by question id)
a5e1f35c 927
928 if (!$questions) {
929 error("No questions!");
930 }
931
932 $result->sumgrades = 0;
933
934 foreach ($questions as $question) {
2a2c9725 935 if (!$answers = quiz_get_answers($question)) {
936 error("No answers defined for question id $question->id!");
a5e1f35c 937 }
938
939 $grade = 0; // default
8db3eadd 940 $correct = array();
19c4f55c 941 $feedback = array();
942 $response = array();
a5e1f35c 943
944 switch ($question->type) {
945 case SHORTANSWER:
946 if ($question->answer) {
cd63d77e 947 $question->answer = trim($question->answer[0]);
a5e1f35c 948 } else {
19c4f55c 949 $question->answer = "";
a5e1f35c 950 }
a8a372cc 951 $response[0] = $question->answer;
a5e1f35c 952 foreach($answers as $answer) { // There might be multiple right answers
8db3eadd 953 if ($answer->fraction > $bestshortanswer) {
954 $correct[$answer->id] = $answer->answer;
955 }
2a2c9725 956 if (!$answer->usecase) { // Don't compare case
a5e1f35c 957 $answer->answer = strtolower($answer->answer);
958 $question->answer = strtolower($question->answer);
959 }
960 if ($question->answer == $answer->answer) {
a8a372cc 961 $feedback[0] = $answer->feedback;
a5e1f35c 962 $grade = (float)$answer->fraction * $answer->grade;
963 }
964 }
965 break;
966
967
968 case TRUEFALSE:
969 if ($question->answer) {
970 $question->answer = $question->answer[0];
971 } else {
972 $question->answer = NULL;
973 }
974 foreach($answers as $answer) { // There should be two answers (true and false)
975 $feedback[$answer->id] = $answer->feedback;
8db3eadd 976 if ($answer->fraction > 0) {
977 $correct[$answer->id] = true;
978 }
a5e1f35c 979 if ($question->answer == $answer->id) {
980 $grade = (float)$answer->fraction * $answer->grade;
a8a372cc 981 $response[$answer->id] = true;
a5e1f35c 982 }
983 }
984 break;
985
986
987 case MULTICHOICE:
988 foreach($answers as $answer) { // There will be multiple answers, perhaps more than one is right
989 $feedback[$answer->id] = $answer->feedback;
8db3eadd 990 if ($answer->fraction > 0) {
991 $correct[$answer->id] = true;
992 }
a5e1f35c 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;
a8a372cc 998 $response[$answer->id] = true;
a5e1f35c 999 continue;
1000 } else {
1001 $grade += (float)$answer->fraction * $answer->grade;
a8a372cc 1002 $response[$answer->id] = true;
a5e1f35c 1003 }
1004 }
1005 }
1006 }
1007 }
1008 break;
8db3eadd 1009 case RANDOM:
1010 // Not done yet
1011 break;
a5e1f35c 1012
1013
1014 }
1015 if ($grade < 0.0) { // No negative grades
1016 $grade = 0.0;
1017 }
10b9291c 1018
3a50203f 1019 $result->grades[$question->id] = round($grade, 2);
a5e1f35c 1020 $result->sumgrades += $grade;
1021 $result->feedback[$question->id] = $feedback;
a8a372cc 1022 $result->response[$question->id] = $response;
8db3eadd 1023 $result->correct[$question->id] = $correct;
a5e1f35c 1024 }
1025
8d94f5a0 1026 $fraction = (float)($result->sumgrades / $quiz->sumgrades);
1027 $result->percentage = format_float($fraction * 100.0);
1028 $result->grade = format_float($fraction * $quiz->grade);
3a50203f 1029 $result->sumgrades = round($result->sumgrades, 2);
a5e1f35c 1030
1031 return $result;
1032}
6d86b5dc 1033
1034
1035function quiz_get_attempt_responses($attempt) {
1036// Given an attempt object, this function gets all the
1037// stored responses and returns them in a format suitable
1038// for regrading using quiz_grade_attempt_results()
1039
1040 if (!$responses = get_records_sql("SELECT q.id, q.type, r.answer
1041 FROM quiz_responses r, quiz_questions q
1042 WHERE r.attempt = '$attempt->id'
1043 AND q.id = r.question")) {
1044 notify("Could not find any responses for that attempt!");
1045 return false;
1046 }
1047
1048 foreach ($responses as $key => $response) {
1049 $responses[$key]->answer = explode(",",$response->answer);
1050 }
1051
1052 return $responses;
1053}
1054
a5e1f35c 1055
730fd187 1056?>