Fixed a slight typo
[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");
95dbc030 20define("MATCH", "5");
21define("RANDOMMATCH", "6");
22
8d94f5a0 23$QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz"),
a5e1f35c 24 TRUEFALSE => get_string("truefalse", "quiz"),
95dbc030 25 SHORTANSWER => get_string("shortanswer", "quiz"),
26 RANDOMMATCH => get_string("randommatch", "quiz") );
a5e1f35c 27
49220fa7 28$QUIZ_FILE_FORMAT = array ( "custom" => get_string("custom", "quiz"),
29 "webct" => get_string("webct", "quiz"),
30 "qti" => get_string("qti", "quiz"),
31 "missingword" => get_string("missingword", "quiz") );
32
7d2e5b65 33define("QUIZ_PICTURE_DEFAULT_HEIGHT", "200");
a5e1f35c 34
35/// FUNCTIONS ///////////////////////////////////////////////////////////////////
730fd187 36
37function quiz_add_instance($quiz) {
a5e1f35c 38/// Given an object containing all the necessary data,
39/// (defined by the form in mod.html) this function
40/// will create a new instance and return the id number
41/// of the new instance.
730fd187 42
49dcdd18 43 $quiz->created = time();
730fd187 44 $quiz->timemodified = time();
49dcdd18 45 $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday,
c04c41c7 46 $quiz->openhour, $quiz->openminute, 0);
49dcdd18 47 $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday,
c04c41c7 48 $quiz->closehour, $quiz->closeminute, 0);
730fd187 49
7bd1aa1d 50 if (!$quiz->id = insert_record("quiz", $quiz)) {
51 return false; // some error occurred
52 }
53
10b9291c 54 // The grades for every question in this quiz are stored in an array
7bd1aa1d 55 if ($quiz->grades) {
56 foreach ($quiz->grades as $question => $grade) {
8d94f5a0 57 if ($question and $grade) {
58 unset($questiongrade);
59 $questiongrade->quiz = $quiz->id;
60 $questiongrade->question = $question;
61 $questiongrade->grade = $grade;
62 if (!insert_record("quiz_question_grades", $questiongrade)) {
63 return false;
64 }
7bd1aa1d 65 }
66 }
67 }
730fd187 68
7bd1aa1d 69 return $quiz->id;
730fd187 70}
71
72
73function quiz_update_instance($quiz) {
a5e1f35c 74/// Given an object containing all the necessary data,
75/// (defined by the form in mod.html) this function
76/// will update an existing instance with new data.
730fd187 77
78 $quiz->timemodified = time();
49dcdd18 79 $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday,
c04c41c7 80 $quiz->openhour, $quiz->openminute, 0);
49dcdd18 81 $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday,
c04c41c7 82 $quiz->closehour, $quiz->closeminute, 0);
730fd187 83 $quiz->id = $quiz->instance;
84
7bd1aa1d 85 if (!update_record("quiz", $quiz)) {
86 return false; // some error occurred
87 }
730fd187 88
7bd1aa1d 89
10b9291c 90 // The grades for every question in this quiz are stored in an array
7bd1aa1d 91 // Insert or update records as appropriate
92
93 $existing = get_records("quiz_question_grades", "quiz", $quiz->id, "", "question,grade,id");
94
95 if ($quiz->grades) {
96 foreach ($quiz->grades as $question => $grade) {
8d94f5a0 97 if ($question and $grade) {
98 unset($questiongrade);
99 $questiongrade->quiz = $quiz->id;
100 $questiongrade->question = $question;
101 $questiongrade->grade = $grade;
102 if (isset($existing[$question])) {
103 if ($existing[$question]->grade != $grade) {
104 $questiongrade->id = $existing[$question]->id;
105 if (!update_record("quiz_question_grades", $questiongrade)) {
106 return false;
107 }
108 }
109 } else {
110 if (!insert_record("quiz_question_grades", $questiongrade)) {
7bd1aa1d 111 return false;
112 }
113 }
7bd1aa1d 114 }
115 }
116 }
117
118 return true;
730fd187 119}
120
121
122function quiz_delete_instance($id) {
a5e1f35c 123/// Given an ID of an instance of this module,
124/// this function will permanently delete the instance
125/// and any data that depends on it.
730fd187 126
127 if (! $quiz = get_record("quiz", "id", "$id")) {
128 return false;
129 }
130
131 $result = true;
132
10b9291c 133 if ($attempts = get_record("quiz_attempts", "quiz", "$quiz->id")) {
134 foreach ($attempts as $attempt) {
135 if (! delete_records("quiz_responses", "attempt", "$attempt->id")) {
136 $result = false;
137 }
138 }
139 }
140
141 if (! delete_records("quiz_attempts", "quiz", "$quiz->id")) {
142 $result = false;
143 }
144
145 if (! delete_records("quiz_grades", "quiz", "$quiz->id")) {
146 $result = false;
147 }
148
149 if (! delete_records("quiz_question_grades", "quiz", "$quiz->id")) {
150 $result = false;
151 }
730fd187 152
153 if (! delete_records("quiz", "id", "$quiz->id")) {
154 $result = false;
155 }
156
157 return $result;
158}
159
160function quiz_user_outline($course, $user, $mod, $quiz) {
a5e1f35c 161/// Return a small object with summary information about what a
162/// user has done with a given particular instance of this module
163/// Used for user activity reports.
164/// $return->time = the time they did it
165/// $return->info = a short text description
ebc3bd2b 166 if ($grade = get_record("quiz_grades", "userid", $user->id, "quiz", $quiz->id)) {
98092498 167
168 if ($grade->grade) {
169 $result->info = get_string("grade").": $grade->grade";
170 }
171 $result->time = $grade->timemodified;
172 return $result;
173 }
174 return NULL;
730fd187 175
176 return $return;
177}
178
179function quiz_user_complete($course, $user, $mod, $quiz) {
a5e1f35c 180/// Print a detailed representation of what a user has done with
181/// a given particular instance of this module, for user activity reports.
730fd187 182
183 return true;
184}
185
186function quiz_print_recent_activity(&$logs, $isteacher=false) {
a5e1f35c 187/// Given a list of logs, assumed to be those since the last login
188/// this function prints a short list of changes related to this module
189/// If isteacher is true then perhaps additional information is printed.
190/// This function is called from course/lib.php: print_recent_activity()
730fd187 191
192 global $CFG, $COURSE_TEACHER_COLOR;
193
9c9f7d77 194 $content = "";
195
730fd187 196 return $content; // True if anything was printed, otherwise false
197}
198
199function quiz_cron () {
a5e1f35c 200/// Function to be run periodically according to the moodle cron
201/// This function searches for things that need to be done, such
202/// as sending out mail, toggling flags etc ...
730fd187 203
204 global $CFG;
205
206 return true;
207}
208
d0ac6bc2 209function quiz_grades($quizid) {
858deff0 210/// Must return an array of grades, indexed by user, and a max grade.
211
ebc3bd2b 212 $return->grades = get_records_menu("quiz_grades", "quiz", $quizid, "", "userid,grade");
858deff0 213 $return->maxgrade = get_field("quiz", "grade", "id", "$quizid");
214 return $return;
d0ac6bc2 215}
216
730fd187 217
bdc23be0 218/// SQL FUNCTIONS ////////////////////////////////////////////////////////////////////
219
220function quiz_move_questions($category1, $category2) {
221 global $CFG;
222 return execute_sql("UPDATE {$CFG->prefix}quiz_questions
223 SET category = '$category2'
224 WHERE category = '$category1'",
225 false);
226}
227
228function quiz_get_question_grades($quizid, $questionlist) {
229 global $CFG;
230
231 return get_records_sql("SELECT question,grade
232 FROM {$CFG->prefix}quiz_question_grades
233 WHERE quiz = '$quizid'
234 AND question IN ($questionlist)");
235}
236
237function quiz_get_grade_records($quiz) {
238/// Gets all info required to display the table of quiz results
239/// for report.php
240 global $CFG;
241
242 return get_records_sql("SELECT qg.*, u.firstname, u.lastname, u.picture
243 FROM {$CFG->prefix}quiz_grades qg,
244 {$CFG->prefix}user u
245 WHERE qg.quiz = '$quiz->id'
ebc3bd2b 246 AND qg.userid = u.id");
bdc23be0 247}
248
249function quiz_get_answers($question) {
250// Given a question, returns the correct answers and grades
251 global $CFG;
95dbc030 252
a2fe7cc0 253 switch ($question->qtype) {
95dbc030 254 case SHORTANSWER: // Could be multiple answers
bdc23be0 255 return get_records_sql("SELECT a.*, sa.usecase, g.grade
256 FROM {$CFG->prefix}quiz_shortanswer sa,
257 {$CFG->prefix}quiz_answers a,
258 {$CFG->prefix}quiz_question_grades g
259 WHERE sa.question = '$question->id'
260 AND sa.question = a.question
261 AND sa.question = g.question");
262 break;
263
95dbc030 264 case TRUEFALSE: // Should be always two answers
bdc23be0 265 return get_records_sql("SELECT a.*, g.grade
266 FROM {$CFG->prefix}quiz_answers a,
267 {$CFG->prefix}quiz_question_grades g
268 WHERE a.question = '$question->id'
269 AND a.question = g.question");
270 break;
271
95dbc030 272 case MULTICHOICE: // Should be multiple answers
bdc23be0 273 return get_records_sql("SELECT a.*, mc.single, g.grade
274 FROM {$CFG->prefix}quiz_multichoice mc,
275 {$CFG->prefix}quiz_answers a,
276 {$CFG->prefix}quiz_question_grades g
277 WHERE mc.question = '$question->id'
278 AND mc.question = a.question
279 AND mc.question = g.question");
280 break;
281
95dbc030 282 case RANDOM:
bdc23be0 283 return false; // Not done yet
284 break;
285
95dbc030 286 case RANDOMMATCH: // Could be any of many answers, return them all
287 return get_records_sql("SELECT a.*, g.grade
288 FROM {$CFG->prefix}quiz_questions q,
289 {$CFG->prefix}quiz_answers a,
290 {$CFG->prefix}quiz_question_grades g
291 WHERE q.category = '$question->category'
292 AND q.qtype = ".SHORTANSWER."
293 AND q.id = a.question
294 AND g.question = '$question->id'");
295 break;
296
bdc23be0 297 default:
298 return false;
299 }
300}
301
302
ef4145f6 303function quiz_get_attempt_responses($attempt, $quiz) {
bdc23be0 304// Given an attempt object, this function gets all the
305// stored responses and returns them in a format suitable
306// for regrading using quiz_grade_attempt_results()
307 global $CFG;
308
95dbc030 309 if (!$responses = get_records_sql("SELECT q.id, q.qtype, q.category, r.answer
bdc23be0 310 FROM {$CFG->prefix}quiz_responses r,
311 {$CFG->prefix}quiz_questions q
312 WHERE r.attempt = '$attempt->id'
ef4145f6 313 AND q.id = r.question
314 AND q.id IN ($quiz->questions)")) {
bdc23be0 315 notify("Could not find any responses for that attempt!");
316 return false;
317 }
318
319 foreach ($responses as $key => $response) {
320 $responses[$key]->answer = explode(",",$response->answer);
321 }
322
323 return $responses;
324}
325
326
327
730fd187 328//////////////////////////////////////////////////////////////////////////////////////
a5e1f35c 329/// Any other quiz functions go here. Each of them must have a name that
330/// starts with quiz_
730fd187 331
a8a372cc 332function quiz_print_comment($text) {
333 global $THEME;
334
c897eac5 335 echo "<SPAN CLASS=feedbacktext>".text_to_html($text, true, false)."</SPAN>";
a8a372cc 336}
337
8db3eadd 338function quiz_print_correctanswer($text) {
339 global $THEME;
340
341 echo "<P ALIGN=RIGHT><SPAN CLASS=highlight>$text</SPAN></P>";
342}
343
c74a0ca5 344function quiz_print_question_icon($question) {
345// Prints a question icon
cc3b8c75 346
347 global $QUIZ_QUESTION_TYPE;
348
a2fe7cc0 349 echo "<A HREF=\"question.php?id=$question->id\" TITLE=\"".$QUIZ_QUESTION_TYPE[$question->qtype]."\">";
350 switch ($question->qtype) {
c74a0ca5 351 case SHORTANSWER:
cc3b8c75 352 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/sa.gif\">";
c74a0ca5 353 break;
354 case TRUEFALSE:
cc3b8c75 355 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/tf.gif\">";
c74a0ca5 356 break;
357 case MULTICHOICE:
cc3b8c75 358 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/mc.gif\">";
c74a0ca5 359 break;
360 case RANDOM:
cc3b8c75 361 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/rs.gif\">";
c74a0ca5 362 break;
95dbc030 363 case RANDOMMATCH:
364 echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/rm.gif\">";
365 break;
c74a0ca5 366 }
cc3b8c75 367 echo "</A>\n";
c74a0ca5 368}
369
370function quiz_print_question($number, $questionid, $grade, $courseid,
8db3eadd 371 $feedback=NULL, $response=NULL, $actualgrade=NULL, $correct=NULL) {
a5e1f35c 372/// Prints a quiz question, any format
a8a372cc 373
14d8c0b4 374 if (!$question = get_record("quiz_questions", "id", $questionid)) {
375 notify("Error: Question not found!");
376 }
377
a2fe7cc0 378 if (empty($actualgrade)) {
379 $actualgrade = 0;
380 }
381
14d8c0b4 382 $stranswer = get_string("answer", "quiz");
383 $strmarks = get_string("marks", "quiz");
384
385 echo "<TABLE WIDTH=100% CELLSPACING=10><TR><TD NOWRAP WIDTH=100 VALIGN=top>";
a8a372cc 386 echo "<P ALIGN=CENTER><B>$number</B></P>";
19c4f55c 387 if ($feedback or $response) {
a8a372cc 388 echo "<P ALIGN=CENTER><FONT SIZE=1>$strmarks: $actualgrade/$grade</FONT></P>";
389 } else {
390 echo "<P ALIGN=CENTER><FONT SIZE=1>$grade $strmarks</FONT></P>";
391 }
14d8c0b4 392 print_spacer(1,100);
393 echo "</TD><TD VALIGN=TOP>";
394
a2fe7cc0 395 switch ($question->qtype) {
a5e1f35c 396 case SHORTANSWER:
14d8c0b4 397 if (!$options = get_record("quiz_shortanswer", "question", $question->id)) {
398 notify("Error: Missing question options!");
399 }
2a2c9725 400 echo text_to_html($question->questiontext);
14d8c0b4 401 if ($question->image) {
7d2e5b65 402 print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT);
14d8c0b4 403 }
a8a372cc 404 if ($response) {
405 $value = "VALUE=\"$response[0]\"";
a2fe7cc0 406 } else {
407 $value = "";
a8a372cc 408 }
409 echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$question->id SIZE=20 $value></P>";
410 if ($feedback) {
411 quiz_print_comment("<P ALIGN=right>$feedback[0]</P>");
412 }
8db3eadd 413 if ($correct) {
c74a0ca5 414 $correctanswers = implode(", ", $correct);
8db3eadd 415 quiz_print_correctanswer($correctanswers);
416 }
14d8c0b4 417 break;
418
a5e1f35c 419 case TRUEFALSE:
14d8c0b4 420 if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
421 notify("Error: Missing question options!");
422 }
a2fe7cc0 423 if (!$true = get_record("quiz_answers", "id", $options->trueanswer)) {
14d8c0b4 424 notify("Error: Missing question answers!");
425 }
a2fe7cc0 426 if (!$false = get_record("quiz_answers", "id", $options->falseanswer)) {
14d8c0b4 427 notify("Error: Missing question answers!");
428 }
429 if (!$true->answer) {
430 $true->answer = get_string("true", "quiz");
431 }
432 if (!$false->answer) {
433 $false->answer = get_string("false", "quiz");
434 }
2a2c9725 435 echo text_to_html($question->questiontext);
14d8c0b4 436 if ($question->image) {
7d2e5b65 437 print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT);
14d8c0b4 438 }
a8a372cc 439
a2fe7cc0 440 $truechecked = "";
441 $falsechecked = "";
442
443 if (!empty($response[$true->id])) {
a8a372cc 444 $truechecked = "CHECKED";
445 $feedbackid = $true->id;
a2fe7cc0 446 } else if (!empty($response[$false->id])) {
a8a372cc 447 $falsechecked = "CHECKED";
448 $feedbackid = $false->id;
449 }
a2fe7cc0 450
451 $truecorrect = "";
452 $falsecorrect = "";
8db3eadd 453 if ($correct) {
a2fe7cc0 454 if (!empty($correct[$true->id])) {
8db3eadd 455 $truecorrect = "CLASS=highlight";
456 }
a2fe7cc0 457 if (!empty($correct[$false->id])) {
8db3eadd 458 $falsecorrect = "CLASS=highlight";
459 }
460 }
461 echo "<TABLE ALIGN=right cellpadding=5><TR><TD align=right>$stranswer:&nbsp;&nbsp;";
462 echo "<TD $truecorrect>";
a8a372cc 463 echo "<INPUT $truechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$true->id\">$true->answer";
8db3eadd 464 echo "</TD><TD $falsecorrect>";
a8a372cc 465 echo "<INPUT $falsechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$false->id\">$false->answer</P>";
8db3eadd 466 echo "</TD></TR></TABLE><BR CLEAR=ALL>";
a8a372cc 467 if ($feedback) {
468 quiz_print_comment("<P ALIGN=right>$feedback[$feedbackid]</P>");
469 }
470
14d8c0b4 471 break;
472
a5e1f35c 473 case MULTICHOICE:
14d8c0b4 474 if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
475 notify("Error: Missing question options!");
476 }
a5e1f35c 477 if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
14d8c0b4 478 notify("Error: Missing question answers!");
479 }
2a2c9725 480 echo text_to_html($question->questiontext);
14d8c0b4 481 if ($question->image) {
7d2e5b65 482 print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT);
14d8c0b4 483 }
484 echo "<TABLE ALIGN=right>";
485 echo "<TR><TD valign=top>$stranswer:&nbsp;&nbsp;</TD><TD>";
81b635c3 486 echo "<TABLE>";
14d8c0b4 487 $answerids = explode(",", $options->answers);
a8a372cc 488
14d8c0b4 489 foreach ($answerids as $key => $answerid) {
490 $answer = $answers[$answerid];
68fefdbe 491 $qnumchar = chr(ord('a') + $key);
a8a372cc 492
8e6c87cc 493 if (empty($feedback) or empty($response[$answerid])) {
a8a372cc 494 $checked = "";
8e6c87cc 495 } else {
496 $checked = "CHECKED";
a8a372cc 497 }
14d8c0b4 498 echo "<TR><TD valign=top>";
a5e1f35c 499 if ($options->single) {
a8a372cc 500 echo "<INPUT $checked TYPE=RADIO NAME=q$question->id VALUE=\"$answer->id\">";
14d8c0b4 501 } else {
a8a372cc 502 echo "<INPUT $checked TYPE=CHECKBOX NAME=q$question->id"."a$answer->id VALUE=\"$answer->id\">";
14d8c0b4 503 }
504 echo "</TD>";
8e6c87cc 505 if (empty($feedback) or empty($correct[$answer->id])) {
68fefdbe 506 echo "<TD valign=top>$qnumchar. $answer->answer</TD>";
8e6c87cc 507 } else {
68fefdbe 508 echo "<TD valign=top CLASS=highlight>$qnumchar. $answer->answer</TD>";
8db3eadd 509 }
8e6c87cc 510 if (!empty($feedback)) {
a8a372cc 511 echo "<TD valign=top>&nbsp;";
8e6c87cc 512 if (!empty($response[$answerid])) {
a8a372cc 513 quiz_print_comment($feedback[$answerid]);
514 }
515 echo "</TD>";
516 }
14d8c0b4 517 echo "</TR>";
518 }
519 echo "</TABLE>";
520 echo "</TABLE>";
521 break;
522
8db3eadd 523 case RANDOM:
524 echo "<P>Random questions not supported yet</P>";
525 break;
526
95dbc030 527 case RANDOMMATCH:
528 if (!$options = get_record("quiz_randommatch", "question", $question->id)) {
529 notify("Error: Missing question options!");
530 }
531 echo text_to_html($question->questiontext);
532 if ($question->image) {
533 print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT);
534 }
535
536 /// First, get all the questions available
537
538 $allquestions = get_records_select("quiz_questions",
539 "category = $question->category AND qtype = ".SHORTANSWER);
540 if (count($allquestions) < $options->choose) {
541 notify("Error: could not find enough Short Answer questions in the database!");
542 notify("Found ".count($allquestions).", need $options->choose.");
543 break;
544 }
545
546 if (empty($response)) { // Randomly pick the questions
547 if (!$randomquestions = draw_rand_array($allquestions, $options->choose)) {
548 notify("Error choosing $options->choose random questions");
549 break;
550 }
551 } else { // Use existing questions
552 $randomquestions = array();
553 foreach ($response as $key => $rrr) {
554 $rrr = explode("-", $rrr);
555 $randomquestions[$key] = $allquestions[$key];
556 $responseanswer[$key] = $rrr[1];
557 }
558 }
559
560 /// For each selected, find the best matching answers
561
562 foreach ($randomquestions as $randomquestion) {
563 $shortanswerquestion = get_record("quiz_shortanswer", "question", $randomquestion->id);
564 $questionanswers = get_records_list("quiz_answers", "id", $shortanswerquestion->answers);
565 $bestfraction = 0;
566 $bestanswer = NULL;
567 foreach ($questionanswers as $questionanswer) {
568 if ($questionanswer->fraction > $bestfraction) {
569 $bestanswer = $questionanswer;
570 }
571 }
572 if (empty($bestanswer)) {
573 notify("Error: Could not find the best answer for question: ".$randomquestions->name);
574 break;
575 }
576 $randomanswers[$bestanswer->id] = trim($bestanswer->answer);
577 }
578
579 if (!$randomanswers = draw_rand_array($randomanswers, $options->choose)) { // Mix them up
580 notify("Error randomising answers!");
581 break;
582 }
583
584 echo "<table border=0 cellpadding=10>";
585 foreach ($randomquestions as $key => $randomquestion) {
586 echo "<tr><td align=left valign=top>";
587 echo $randomquestion->questiontext;
588 echo "</td>";
589 echo "<td align=right valign=top>";
590 if (empty($response)) {
591 choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id");
592 } else {
593 if (!empty($correct[$key])) {
594 if ($randomanswers[$responseanswer[$key]] == $correct[$key]) {
595 echo "<span=highlight>";
596 choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]);
597 echo "</span><br \>";
598 } else {
599 choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]);
600 quiz_print_correctanswer($correct[$key]);
601 }
602 } else {
603 choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]);
604 }
605 if (!empty($feedback[$key])) {
606 quiz_print_comment($feedback[$key]);
607 }
608 }
609 echo "</td></tr>";
610 }
611 echo "</table>";
612 break;
613
8db3eadd 614
14d8c0b4 615 default:
616 notify("Error: Unknown question type!");
617 }
618
619 echo "</TD></TR></TABLE>";
3a506ca2 620}
621
a5e1f35c 622function quiz_print_quiz_questions($quiz, $results=NULL) {
623// Prints a whole quiz on one page.
624
8e6c87cc 625 if (empty($quiz->questions)) {
10b9291c 626 notify("No questions have been defined!", "view.php?id=$cm->id");
627 return false;
a5e1f35c 628 }
629
630 $questions = explode(",", $quiz->questions);
631
632 if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) {
10b9291c 633 notify("No grades were found for these questions!");
634 return false;
a5e1f35c 635 }
636
9307692f 637 $strconfirmattempt = addslashes(get_string("readytosend", "quiz"));
638
639 echo "<FORM METHOD=POST ACTION=attempt.php onsubmit=\"return confirm('$strconfirmattempt');\">";
a5e1f35c 640 echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">";
a8a372cc 641
a5e1f35c 642 foreach ($questions as $key => $questionid) {
8db3eadd 643 $feedback = NULL;
644 $response = NULL;
645 $actualgrades = NULL;
646 $correct = NULL;
8e6c87cc 647 if (!empty($results)) {
648 if (!empty($results->feedback[$questionid])) {
649 $feedback = $results->feedback[$questionid];
650 }
651 if (!empty($results->response[$questionid])) {
652 $response = $results->response[$questionid];
653 }
654 if (!empty($results->grades[$questionid])) {
655 $actualgrades = $results->grades[$questionid];
656 }
8db3eadd 657 if ($quiz->correctanswers) {
8e6c87cc 658 if (!empty($results->correct[$questionid])) {
659 $correct = $results->correct[$questionid];
660 }
8db3eadd 661 }
a8a372cc 662 }
8db3eadd 663
a5e1f35c 664 print_simple_box_start("CENTER", "90%");
a8a372cc 665 quiz_print_question($key+1, $questionid, $grades[$questionid]->grade, $quiz->course,
8db3eadd 666 $feedback, $response, $actualgrades, $correct);
a5e1f35c 667 print_simple_box_end();
668 echo "<BR>";
669 }
a8a372cc 670
8e6c87cc 671 if (empty($results)) {
a8a372cc 672 echo "<CENTER><INPUT TYPE=submit VALUE=\"".get_string("savemyanswers", "quiz")."\"></CENTER>";
673 }
a5e1f35c 674 echo "</FORM>";
10b9291c 675
676 return true;
a5e1f35c 677}
6a952ce7 678
679function quiz_get_default_category($courseid) {
680 if ($categories = get_records("quiz_categories", "course", $courseid, "id")) {
681 foreach ($categories as $category) {
682 return $category; // Return the first one (lowest id)
683 }
684 }
685
686 // Otherwise, we need to make one
10b9291c 687 $category->name = get_string("default", "quiz");
688 $category->info = get_string("defaultinfo", "quiz");
6a952ce7 689 $category->course = $courseid;
690 $category->publish = 0;
691
692 if (!$category->id = insert_record("quiz_categories", $category)) {
693 notify("Error creating a default category!");
694 return false;
695 }
696 return $category;
697}
698
c74a0ca5 699function quiz_get_category_menu($courseid, $published=false) {
700 if ($published) {
701 $publish = "OR publish = '1'";
702 }
bdc23be0 703 return get_records_select_menu("quiz_categories", "course='$courseid' $publish", "name ASC", "id,name");
c74a0ca5 704}
705
6a952ce7 706function quiz_print_category_form($course, $current) {
707// Prints a form to choose categories
708
bdc23be0 709 if (!$categories = get_records_select("quiz_categories", "course='$course->id' OR publish = '1'", "name ASC")) {
6a952ce7 710 if (!$category = quiz_get_default_category($course->id)) {
711 notify("Error creating a default category!");
712 return false;
713 }
cb62c00a 714 $categories[$category->id] = $category;
6a952ce7 715 }
8d94f5a0 716 foreach ($categories as $key => $category) {
717 if ($category->publish) {
b55a466b 718 if ($catcourse = get_record("course", "id", $category->course)) {
719 $category->name .= " ($catcourse->shortname)";
8d94f5a0 720 }
721 }
b55a466b 722 $catmenu[$category->id] = $category->name;
8d94f5a0 723 }
6a952ce7 724 $strcategory = get_string("category", "quiz");
725 $strshow = get_string("show", "quiz");
6b069ece 726 $streditcats = get_string("editcategories", "quiz");
6a952ce7 727
467aaec6 728 echo "<TABLE width=\"100%\"><TR><TD NOWRAP>";
6a952ce7 729 echo "<FORM METHOD=POST ACTION=edit.php>";
730 echo "<B>$strcategory:</B>&nbsp;";
b55a466b 731 choose_from_menu($catmenu, "cat", "$current");
92a3c884 732 echo "<INPUT TYPE=submit VALUE=\"$strshow\">";
6a952ce7 733 echo "</FORM>";
6b069ece 734 echo "</TD><TD align=right>";
735 echo "<FORM METHOD=GET ACTION=category.php>";
736 echo "<INPUT TYPE=hidden NAME=id VALUE=\"$course->id\">";
737 echo "<INPUT TYPE=submit VALUE=\"$streditcats\">";
738 echo "</FORM>";
739 echo "</TD></TR></TABLE>";
6a952ce7 740}
741
742
7bd1aa1d 743function quiz_get_all_question_grades($questionlist, $quizid) {
744// Given a list of question IDs, finds grades or invents them to
745// create an array of matching grades
746
bdc23be0 747 $questions = quiz_get_question_grades($quizid, $questionlist);
7bd1aa1d 748
749 $list = explode(",", $questionlist);
750 $grades = array();
751
752 foreach ($list as $qid) {
753 if (isset($questions[$qid])) {
754 $grades[$qid] = $questions[$qid]->grade;
755 } else {
756 $grades[$qid] = 1;
757 }
758 }
759 return $grades;
760}
761
762
763function quiz_print_question_list($questionlist, $grades) {
6a952ce7 764// Prints a list of quiz questions in a small layout form with knobs
7bd1aa1d 765// $questionlist is comma-separated list
766// $grades is an array of corresponding grades
6a952ce7 767
768 global $THEME;
769
770 if (!$questionlist) {
771 echo "<P align=center>";
772 print_string("noquestions", "quiz");
773 echo "</P>";
774 return;
775 }
776
777 $order = explode(",", $questionlist);
778
7bd1aa1d 779 if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) {
6a952ce7 780 error("No questions were found!");
781 }
782
783 $strorder = get_string("order");
784 $strquestionname = get_string("questionname", "quiz");
785 $strgrade = get_string("grade");
786 $strdelete = get_string("delete");
787 $stredit = get_string("edit");
788 $strmoveup = get_string("moveup");
789 $strmovedown = get_string("movedown");
790 $strsavegrades = get_string("savegrades", "quiz");
c74a0ca5 791 $strtype = get_string("type", "quiz");
6a952ce7 792
10b9291c 793 for ($i=10; $i>=0; $i--) {
7bd1aa1d 794 $gradesmenu[$i] = $i;
6a952ce7 795 }
796 $count = 0;
797 $sumgrade = 0;
798 $total = count($order);
799 echo "<FORM METHOD=post ACTION=edit.php>";
800 echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
467aaec6 801 echo "<TR><TH WIDTH=\"*\" COLSPAN=3 NOWRAP>$strorder</TH><TH align=left WIDTH=\"100%\" NOWRAP>$strquestionname</TH><TH width=\"*\" NOWRAP>$strtype</TH><TH WIDTH=\"*\" NOWRAP>$strgrade</TH><TH WIDTH=\"*\" NOWRAP>$stredit</TH></TR>";
6a952ce7 802 foreach ($order as $qnum) {
95dbc030 803 if (empty($questions[$qnum])) {
804 continue;
805 }
6a952ce7 806 $count++;
807 echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
808 echo "<TD>$count</TD>";
809 echo "<TD>";
810 if ($count != 1) {
811 echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG
812 SRC=\"../../pix/t/up.gif\" BORDER=0></A>";
813 }
814 echo "</TD>";
815 echo "<TD>";
816 if ($count != $total) {
817 echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG
818 SRC=\"../../pix/t/down.gif\" BORDER=0></A>";
819 }
820 echo "</TD>";
821 echo "<TD>".$questions[$qnum]->name."</TD>";
467aaec6 822 echo "<TD ALIGN=CENTER>";
c74a0ca5 823 quiz_print_question_icon($questions[$qnum]);
824 echo "</TD>";
6a952ce7 825 echo "<TD>";
8d94f5a0 826 choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], "");
6a952ce7 827 echo "<TD>";
828 echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG
829 SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
830 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG
831 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
832 echo "</TD>";
833
7bd1aa1d 834 $sumgrade += $grades[$qnum];
6a952ce7 835 }
c74a0ca5 836 echo "<TR><TD COLSPAN=5 ALIGN=right>";
6a952ce7 837 echo "<INPUT TYPE=submit VALUE=\"$strsavegrades:\">";
8d94f5a0 838 echo "<INPUT TYPE=hidden NAME=setgrades VALUE=\"save\">";
6a952ce7 839 echo "<TD ALIGN=LEFT BGCOLOR=\"$THEME->cellcontent\">";
840 echo "<B>$sumgrade</B>";
841 echo "</TD><TD></TD></TR>";
842 echo "</TABLE>";
843 echo "</FORM>";
10b9291c 844
845 return $sumgrade;
6a952ce7 846}
847
848
849function quiz_print_cat_question_list($categoryid) {
850// Prints a form to choose categories
851
852 global $THEME, $QUIZ_QUESTION_TYPE;
853
10b9291c 854 $strcategory = get_string("category", "quiz");
6a952ce7 855 $strquestion = get_string("question", "quiz");
49220fa7 856 $straddquestions = get_string("addquestions", "quiz");
857 $strimportquestions = get_string("importquestions", "quiz");
6a952ce7 858 $strnoquestions = get_string("noquestions", "quiz");
859 $strselect = get_string("select", "quiz");
a01b2571 860 $strselectall = get_string("selectall", "quiz");
6a952ce7 861 $strcreatenewquestion = get_string("createnewquestion", "quiz");
862 $strquestionname = get_string("questionname", "quiz");
863 $strdelete = get_string("delete");
864 $stredit = get_string("edit");
865 $straddselectedtoquiz = get_string("addselectedtoquiz", "quiz");
c74a0ca5 866 $strtype = get_string("type", "quiz");
6a952ce7 867
868 if (!$categoryid) {
869 echo "<P align=center>";
870 print_string("selectcategoryabove", "quiz");
871 echo "</P>";
872 return;
873 }
a5e1f35c 874
6a952ce7 875 if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
876 notify("Category not found!");
877 return;
878 }
8d94f5a0 879 echo "<CENTER>";
10b9291c 880 echo text_to_html($category->info);
6a952ce7 881
49220fa7 882 echo "<TABLE><TR>";
883 echo "<TD valign=top><B>$straddquestions:</B></TD>";
884 echo "<TD valign=top align=right>";
10b9291c 885 echo "<FORM METHOD=GET ACTION=question.php>";
a2fe7cc0 886 choose_from_menu($QUIZ_QUESTION_TYPE, "qtype", "", "");
6a952ce7 887 echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
7d2e5b65 888 echo "<INPUT TYPE=submit VALUE=\"$strcreatenewquestion\">";
cd63d77e 889 helpbutton("questiontypes", $strcreatenewquestion, "quiz");
6a952ce7 890 echo "</FORM>";
49220fa7 891
892 echo "<FORM METHOD=GET ACTION=import.php>";
893 echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
894 echo "<INPUT TYPE=submit VALUE=\"$strimportquestions\">";
895 helpbutton("import", $strimportquestions, "quiz");
896 echo "</FORM>";
897
898 echo "</TR></TABLE>";
899
8d94f5a0 900 echo "</CENTER>";
6a952ce7 901
14bdb238 902 if (!$questions = get_records("quiz_questions", "category", $category->id, "qtype ASC")) {
6a952ce7 903 echo "<P align=center>";
904 print_string("noquestions", "quiz");
905 echo "</P>";
906 return;
907 }
908
10b9291c 909 $canedit = isteacher($category->course);
910
6a952ce7 911 echo "<FORM METHOD=post ACTION=edit.php>";
912 echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
467aaec6 913 echo "<TR><TH width=\"*\" NOWRAP>$strselect</TH><TH width=\"100%\" align=left NOWRAP>$strquestionname</TH><TH WIDTH=\"*\" NOWRAP>$strtype</TH>";
10b9291c 914 if ($canedit) {
467aaec6 915 echo "<TH width=\"*\" NOWRAP>$stredit</TH>";
10b9291c 916 }
917 echo "</TR>";
6a952ce7 918 foreach ($questions as $question) {
919 echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
920 echo "<TD ALIGN=CENTER>";
921 echo "<INPUT TYPE=CHECKBOX NAME=q$question->id VALUE=\"1\">";
922 echo "</TD>";
923 echo "<TD>".$question->name."</TD>";
467aaec6 924 echo "<TD ALIGN=CENTER>";
c74a0ca5 925 quiz_print_question_icon($question);
926 echo "</TD>";
10b9291c 927 if ($canedit) {
928 echo "<TD>";
e1c91df0 929 echo "<A TITLE=\"$strdelete\" HREF=\"question.php?id=$question->id&delete=$question->id\"><IMG
10b9291c 930 SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
931 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG
932 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
933 echo "</TD></TR>";
934 }
935 echo "</TR>";
6a952ce7 936 }
937 echo "<TR><TD COLSPAN=3>";
a01b2571 938 echo "<INPUT TYPE=submit NAME=add VALUE=\"<< $straddselectedtoquiz\">";
939 //echo "<INPUT TYPE=submit NAME=delete VALUE=\"XX Delete selected\">";
940 echo "<INPUT type=button onclick=\"checkall()\" value=\"$strselectall\">";
6a952ce7 941 echo "</TD></TR>";
942 echo "</TABLE>";
943 echo "</FORM>";
944}
a5e1f35c 945
3a506ca2 946
958aafe2 947function quiz_start_attempt($quizid, $userid, $numattempt) {
948 $attempt->quiz = $quizid;
ebc3bd2b 949 $attempt->userid = $userid;
958aafe2 950 $attempt->attempt = $numattempt;
951 $attempt->timestart = time();
952 $attempt->timefinish = 0;
953 $attempt->timemodified = time();
954
955 return insert_record("quiz_attempts", $attempt);
956}
957
958function quiz_get_user_attempt_unfinished($quizid, $userid) {
959// Returns an object containing an unfinished attempt (if there is one)
ebc3bd2b 960 return get_record("quiz_attempts", "quiz", $quizid, "userid", $userid, "timefinish", 0);
958aafe2 961}
962
3a506ca2 963function quiz_get_user_attempts($quizid, $userid) {
a5e1f35c 964// Returns a list of all attempts by a user
ebc3bd2b 965 return get_records_select("quiz_attempts", "quiz = '$quizid' AND userid = '$userid' AND timefinish > 0",
bdc23be0 966 "attempt ASC");
3a506ca2 967}
968
8d94f5a0 969
970function quiz_get_user_attempts_string($quiz, $attempts, $bestgrade) {
971/// Returns a simple little comma-separated list of all attempts,
6d86b5dc 972/// with each grade linked to the feedback report and with the best grade highlighted
8d94f5a0 973
974 $bestgrade = format_float($bestgrade);
975 foreach ($attempts as $attempt) {
976 $attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade);
977 if ($attemptgrade == $bestgrade) {
6d86b5dc 978 $userattempts[] = "<SPAN CLASS=highlight><A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A></SPAN>";
8d94f5a0 979 } else {
6d86b5dc 980 $userattempts[] = "<A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A>";
8d94f5a0 981 }
982 }
983 return implode(",", $userattempts);
984}
985
a5e1f35c 986function quiz_get_best_grade($quizid, $userid) {
987/// Get the best current grade for a particular user in a quiz
ebc3bd2b 988 if (!$grade = get_record("quiz_grades", "quiz", $quizid, "userid", $userid)) {
3a506ca2 989 return 0;
990 }
991
2383cadb 992 return (round($grade->grade,0));
3a506ca2 993}
994
e331eb06 995function quiz_save_best_grade($quiz, $userid) {
a5e1f35c 996/// Calculates the best grade out of all attempts at a quiz for a user,
997/// and then saves that grade in the quiz_grades table.
998
e331eb06 999 if (!$attempts = quiz_get_user_attempts($quiz->id, $userid)) {
e909c8d0 1000 notify("Could not find any user attempts");
a5e1f35c 1001 return false;
1002 }
1003
1004 $bestgrade = quiz_calculate_best_grade($quiz, $attempts);
1005 $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade);
1006
ebc3bd2b 1007 if ($grade = get_record("quiz_grades", "quiz", $quiz->id, "userid", $userid)) {
1d2603b1 1008 $grade->grade = round($bestgrade, 2);
a5e1f35c 1009 $grade->timemodified = time();
1010 if (!update_record("quiz_grades", $grade)) {
e909c8d0 1011 notify("Could not update best grade");
a5e1f35c 1012 return false;
1013 }
1014 } else {
1015 $grade->quiz = $quiz->id;
ebc3bd2b 1016 $grade->userid = $userid;
38f03e5a 1017 $grade->grade = round($bestgrade, 2);
a5e1f35c 1018 $grade->timemodified = time();
1019 if (!insert_record("quiz_grades", $grade)) {
e909c8d0 1020 notify("Could not insert new best grade");
a5e1f35c 1021 return false;
1022 }
1023 }
1024 return true;
1025}
1026
1027
3a506ca2 1028function quiz_calculate_best_grade($quiz, $attempts) {
a5e1f35c 1029/// Calculate the best grade for a quiz given a number of attempts by a particular user.
3a506ca2 1030
1031 switch ($quiz->grademethod) {
a5e1f35c 1032
1033 case ATTEMPTFIRST:
3a506ca2 1034 foreach ($attempts as $attempt) {
a5e1f35c 1035 return $attempt->sumgrades;
3a506ca2 1036 }
a5e1f35c 1037 break;
1038
1039 case ATTEMPTLAST:
1040 foreach ($attempts as $attempt) {
1041 $final = $attempt->sumgrades;
1042 }
1043 return $final;
3a506ca2 1044
a5e1f35c 1045 case GRADEAVERAGE:
3a506ca2 1046 $sum = 0;
1047 $count = 0;
1048 foreach ($attempts as $attempt) {
a5e1f35c 1049 $sum += $attempt->sumgrades;
3a506ca2 1050 $count++;
1051 }
1052 return (float)$sum/$count;
1053
3a506ca2 1054 default:
a5e1f35c 1055 case GRADEHIGHEST:
1056 $max = 0;
3a506ca2 1057 foreach ($attempts as $attempt) {
a5e1f35c 1058 if ($attempt->sumgrades > $max) {
1059 $max = $attempt->sumgrades;
1060 }
3a506ca2 1061 }
a5e1f35c 1062 return $max;
1063 }
1064}
1065
1066function quiz_save_attempt($quiz, $questions, $result, $attemptnum) {
1067/// Given a quiz, a list of attempted questions and a total grade
1068/// this function saves EVERYTHING so it can be reconstructed later
1069/// if necessary.
1070
1071 global $USER;
1072
958aafe2 1073 // First find the attempt in the database (start of attempt)
1074
1075 if (!$attempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
1076 notify("Trying to save an attempt that was not started!");
1077 return false;
1078 }
1079
1080 if ($attempt->attempt != $attemptnum) { // Double check.
1081 notify("Number of this attempt is different to the unfinished one!");
1082 return false;
1083 }
1084
1085 // Now let's complete this record and save it
a5e1f35c 1086
a5e1f35c 1087 $attempt->sumgrades = $result->sumgrades;
958aafe2 1088 $attempt->timefinish = time();
a5e1f35c 1089 $attempt->timemodified = time();
1090
958aafe2 1091 if (! update_record("quiz_attempts", $attempt)) {
7520988b 1092 notify("Error while saving attempt");
a5e1f35c 1093 return false;
1094 }
1095
1096 // Now let's save all the questions for this attempt
1097
1098 foreach ($questions as $question) {
1099 $response->attempt = $attempt->id;
1100 $response->question = $question->id;
1101 $response->grade = $result->grades[$question->id];
54d0590b 1102 if (!empty($question->answer)) {
a5e1f35c 1103 $response->answer = implode(",",$question->answer);
1104 } else {
1105 $response->answer = "";
1106 }
1107 if (!insert_record("quiz_responses", $response)) {
7520988b 1108 notify("Error while saving response");
a5e1f35c 1109 return false;
1110 }
3a506ca2 1111 }
a5e1f35c 1112 return true;
3a506ca2 1113}
730fd187 1114
a5e1f35c 1115
1116function quiz_grade_attempt_results($quiz, $questions) {
1117/// Given a list of questions (including answers for each one)
1118/// this function does all the hard work of calculating the
1119/// grades for each question, as well as a total grade for
1120/// for the whole quiz. It returns everything in a structure
1121/// that looks like:
1122/// $result->sumgrades (sum of all grades for all questions)
1123/// $result->percentage (Percentage of grades that were correct)
1124/// $result->grade (final grade result for the whole quiz)
1125/// $result->grades[] (array of grades, indexed by question id)
a8a372cc 1126/// $result->response[] (array of response arrays, indexed by question id)
a5e1f35c 1127/// $result->feedback[] (array of feedback arrays, indexed by question id)
8db3eadd 1128/// $result->correct[] (array of feedback arrays, indexed by question id)
a5e1f35c 1129
1130 if (!$questions) {
1131 error("No questions!");
1132 }
1133
1134 $result->sumgrades = 0;
1135
1136 foreach ($questions as $question) {
2a2c9725 1137 if (!$answers = quiz_get_answers($question)) {
1138 error("No answers defined for question id $question->id!");
a5e1f35c 1139 }
1140
1141 $grade = 0; // default
8db3eadd 1142 $correct = array();
19c4f55c 1143 $feedback = array();
1144 $response = array();
a5e1f35c 1145
a2fe7cc0 1146 switch ($question->qtype) {
a5e1f35c 1147 case SHORTANSWER:
1148 if ($question->answer) {
41b95af2 1149 $question->answer = trim(stripslashes($question->answer[0]));
a5e1f35c 1150 } else {
19c4f55c 1151 $question->answer = "";
a5e1f35c 1152 }
a8a372cc 1153 $response[0] = $question->answer;
a2fe7cc0 1154 $bestshortanswer = 0;
a5e1f35c 1155 foreach($answers as $answer) { // There might be multiple right answers
8db3eadd 1156 if ($answer->fraction > $bestshortanswer) {
1157 $correct[$answer->id] = $answer->answer;
a2fe7cc0 1158 $bestshortanswer = $answer->fraction;
8db3eadd 1159 }
2a2c9725 1160 if (!$answer->usecase) { // Don't compare case
a5e1f35c 1161 $answer->answer = strtolower($answer->answer);
1162 $question->answer = strtolower($question->answer);
1163 }
1164 if ($question->answer == $answer->answer) {
a8a372cc 1165 $feedback[0] = $answer->feedback;
a5e1f35c 1166 $grade = (float)$answer->fraction * $answer->grade;
1167 }
1168 }
1169 break;
1170
1171
1172 case TRUEFALSE:
1173 if ($question->answer) {
1174 $question->answer = $question->answer[0];
1175 } else {
1176 $question->answer = NULL;
1177 }
1178 foreach($answers as $answer) { // There should be two answers (true and false)
1179 $feedback[$answer->id] = $answer->feedback;
8db3eadd 1180 if ($answer->fraction > 0) {
1181 $correct[$answer->id] = true;
1182 }
a5e1f35c 1183 if ($question->answer == $answer->id) {
1184 $grade = (float)$answer->fraction * $answer->grade;
a8a372cc 1185 $response[$answer->id] = true;
a5e1f35c 1186 }
1187 }
1188 break;
1189
1190
1191 case MULTICHOICE:
1192 foreach($answers as $answer) { // There will be multiple answers, perhaps more than one is right
1193 $feedback[$answer->id] = $answer->feedback;
8db3eadd 1194 if ($answer->fraction > 0) {
1195 $correct[$answer->id] = true;
1196 }
54d0590b 1197 if (!empty($question->answer)) {
a5e1f35c 1198 foreach ($question->answer as $questionanswer) {
1199 if ($questionanswer == $answer->id) {
95dbc030 1200 $response[$answer->id] = true;
a5e1f35c 1201 if ($answer->single) {
1202 $grade = (float)$answer->fraction * $answer->grade;
1203 continue;
1204 } else {
1205 $grade += (float)$answer->fraction * $answer->grade;
1206 }
1207 }
1208 }
1209 }
1210 }
1211 break;
95dbc030 1212
1213 case RANDOMMATCH:
1214 $bestanswer = array();
1215 foreach ($answers as $answer) { // Loop through them all looking for correct answers
1216 if (empty($bestanswer[$answer->question])) {
1217 $bestanswer[$answer->question] = 0;
1218 $correct[$answer->question] = "";
1219 }
1220 if ($answer->fraction > $bestanswer[$answer->question]) {
1221 $bestanswer[$answer->question] = $answer->fraction;
1222 $correct[$answer->question] = $answer->answer;
1223 }
1224 }
1225 $answerfraction = 1.0 / (float) count($question->answer);
1226 foreach ($question->answer as $questionanswer) { // For each random answered question
1227 $rqarr = explode('-', $questionanswer); // Extract question/answer.
1228 $rquestion = $rqarr[0];
1229 $ranswer = $rqarr[1];
1230 $response[$rquestion] = $questionanswer;
1231 if (isset($answers[$ranswer])) { // If the answer exists in the list
1232 $answer = $answers[$ranswer];
1233 $feedback[$rquestion] = $answer->feedback;
1234 if ($answer->question == $rquestion) { // Check that this answer matches the question
1235 $grade += (float)$answer->fraction * $answer->grade * $answerfraction;
1236 }
1237 }
1238 }
8db3eadd 1239 break;
a5e1f35c 1240
a5e1f35c 1241 }
1242 if ($grade < 0.0) { // No negative grades
1243 $grade = 0.0;
1244 }
10b9291c 1245
3a50203f 1246 $result->grades[$question->id] = round($grade, 2);
a5e1f35c 1247 $result->sumgrades += $grade;
1248 $result->feedback[$question->id] = $feedback;
a8a372cc 1249 $result->response[$question->id] = $response;
8db3eadd 1250 $result->correct[$question->id] = $correct;
a5e1f35c 1251 }
1252
8d94f5a0 1253 $fraction = (float)($result->sumgrades / $quiz->sumgrades);
1254 $result->percentage = format_float($fraction * 100.0);
1255 $result->grade = format_float($fraction * $quiz->grade);
3a50203f 1256 $result->sumgrades = round($result->sumgrades, 2);
a5e1f35c 1257
1258 return $result;
1259}
6d86b5dc 1260
1261
49220fa7 1262function quiz_save_question_options($question) {
1263/// Given some question info and some data about the the answers
1264/// this function parses, organises and saves the question
1265/// It is used by question.php when saving new data from a
1266/// form, and also by import.php when importing questions
1267///
1268/// Returns $result->error or $result->notice
a5e1f35c 1269
49220fa7 1270 switch ($question->qtype) {
1271 case SHORTANSWER:
1272 // Delete all the old answers
1273 // FIXME - instead of deleting, update existing answers
1274 // so as not to break existing references to them
1275 delete_records("quiz_answers", "question", $question->id);
1276 delete_records("quiz_shortanswer", "question", $question->id);
1277
1278 $answers = array();
1279 $maxfraction = -1;
1280
1281 // Insert all the new answers
1282 foreach ($question->answer as $key => $dataanswer) {
1283 if ($dataanswer != "") {
1284 unset($answer);
1285 $answer->answer = $dataanswer;
1286 $answer->question = $question->id;
1287 $answer->fraction = $question->fraction[$key];
1288 $answer->feedback = $question->feedback[$key];
1289 if (!$answer->id = insert_record("quiz_answers", $answer)) {
1290 $result->error = "Could not insert quiz answer!";
1291 return $result;
1292 }
1293 $answers[] = $answer->id;
1294 if ($question->fraction[$key] > $maxfraction) {
1295 $maxfraction = $question->fraction[$key];
1296 }
1297 }
1298 }
1299
1300 unset($options);
1301 $options->question = $question->id;
1302 $options->answers = implode(",",$answers);
1303 $options->usecase = $question->usecase;
1304 if (!insert_record("quiz_shortanswer", $options)) {
1305 $result->error = "Could not insert quiz shortanswer options!";
1306 return $result;
1307 }
1308
1309 /// Perform sanity checks on fractional grades
1310 if ($maxfraction != 1) {
1311 $maxfraction = $maxfraction * 100;
1312 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
1313 return $result;
1314 }
1315 break;
1316 case TRUEFALSE:
1317 // FIXME - instead of deleting, update existing answers
1318 // so as not to break existing references to them
1319 delete_records("quiz_answers", "question", $question->id);
1320 delete_records("quiz_truefalse", "question", $question->id);
1321
1322 $true->answer = get_string("true", "quiz");
1323 $true->question = $question->id;
1324 $true->fraction = $question->answer;
1325 $true->feedback = $question->feedbacktrue;
1326 if (!$true->id = insert_record("quiz_answers", $true)) {
1327 $result->error = "Could not insert quiz answer \"true\")!";
1328 return $result;
1329 }
1330
1331 $false->answer = get_string("false", "quiz");
1332 $false->question = $question->id;
1333 $false->fraction = 1 - (int)$question->answer;
1334 $false->feedback = $question->feedbackfalse;
1335 if (!$false->id = insert_record("quiz_answers", $false)) {
1336 $result->error = "Could not insert quiz answer \"false\")!";
1337 return $result;
1338 }
1339
1340 unset($options);
1341 $options->question = $question->id;
1342 $options->trueanswer = $true->id;
1343 $options->falseanswer = $false->id;
1344 if (!insert_record("quiz_truefalse", $options)) {
1345 $result->error = "Could not insert quiz truefalse options!";
1346 return $result;
1347 }
1348 break;
1349 case MULTICHOICE:
1350 // FIXME - instead of deleting, update existing answers
1351 // so as not to break existing references to them
1352 delete_records("quiz_answers", "question", $question->id);
1353 delete_records("quiz_multichoice", "question", $question->id);
1354
1355 $totalfraction = 0;
1356 $maxfraction = -1;
1357
1358 $answers = array();
1359
1360 // Insert all the new answers
1361 foreach ($question->answer as $key => $dataanswer) {
1362 if ($dataanswer != "") {
1363 unset($answer);
1364 $answer->answer = $dataanswer;
1365 $answer->question = $question->id;
1366 $answer->fraction = $question->fraction[$key];
1367 $answer->feedback = $question->feedback[$key];
1368 if (!$answer->id = insert_record("quiz_answers", $answer)) {
1369 $result->error = "Could not insert quiz answer!";
1370 return $result;
1371 }
1372 $answers[] = $answer->id;
1373
1374 if ($question->fraction[$key] > 0) { // Sanity checks
1375 $totalfraction += $question->fraction[$key];
1376 }
1377 if ($question->fraction[$key] > $maxfraction) {
1378 $maxfraction = $question->fraction[$key];
1379 }
1380 }
1381 }
1382
1383 unset($options);
1384 $options->question = $question->id;
1385 $options->answers = implode(",",$answers);
1386 $options->single = $question->single;
1387 if (!insert_record("quiz_multichoice", $options)) {
1388 $result->error = "Could not insert quiz multichoice options!";
1389 return $result;
1390 }
1391
1392 /// Perform sanity checks on fractional grades
1393 if ($options->single) {
1394 if ($maxfraction != 1) {
1395 $maxfraction = $maxfraction * 100;
1396 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
1397 return $result;
1398 }
1399 } else {
1400 $totalfraction = round($totalfraction,2);
1401 if ($totalfraction != 1) {
1402 $totalfraction = $totalfraction * 100;
1403 $result->notice = get_string("fractionsaddwrong", "quiz", $totalfraction);
1404 return $result;
1405 }
1406 }
1407 break;
95dbc030 1408
1409 case RANDOMMATCH:
1410 $options->question = $question->id;
1411 $options->choose = $question->choose;
1412 if ($existing = get_record("quiz_randommatch", "question", $options->question)) {
1413 $options->id = $existing->id;
1414 if (!update_record("quiz_randommatch", $options)) {
1415 $result->error = "Could not update quiz randommatch options!";
1416 return $result;
1417 }
1418 } else {
1419 if (!insert_record("quiz_randommatch", $options)) {
1420 $result->error = "Could not insert quiz randommatch options!";
1421 return $result;
1422 }
1423 }
1424 break;
1425
49220fa7 1426 default:
1427 $result->error = "Unsupported question type ($question->qtype)!";
1428 return $result;
1429 break;
1430 }
1431 return true;
1432}
1433
1434
95dbc030 1435
1436
730fd187 1437?>