Fix for POP3 by disabling TLS explicitly.
[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
54a67a59 16define("SHORTANSWER", "1");
17define("TRUEFALSE", "2");
18define("MULTICHOICE", "3");
19define("RANDOM", "4");
20define("MATCH", "5");
21define("RANDOMSAMATCH", "6");
401c8de6 22define("DESCRIPTION", "7");
361f649d 23define("NUMERICAL", "8");
54a67a59 24
25$QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz"),
26 TRUEFALSE => get_string("truefalse", "quiz"),
27 SHORTANSWER => get_string("shortanswer", "quiz"),
361f649d 28 NUMERICAL => get_string("numerical", "quiz"),
54a67a59 29 MATCH => get_string("match", "quiz"),
361f649d 30 DESCRIPTION => get_string("description", "quiz"),
34d52ad7 31 RANDOM => get_string("random", "quiz"),
361f649d 32 RANDOMSAMATCH => get_string("randomsamatch", "quiz")
33 );
a5e1f35c 34
49220fa7 35$QUIZ_FILE_FORMAT = array ( "custom" => get_string("custom", "quiz"),
c6bcd06a 36 "missingword" => get_string("missingword", "quiz"),
37 "blackboard" => get_string("blackboard", "quiz"),
38 "aon" => "AON"
39 );
49220fa7 40
5325f8b8 41define("QUIZ_PICTURE_MAX_HEIGHT", "600"); // Not currently implemented
42define("QUIZ_PICTURE_MAX_WIDTH", "600"); // Not currently implemented
a5e1f35c 43
54a67a59 44define("QUIZ_MAX_NUMBER_ANSWERS", "8");
45
a5e1f35c 46/// FUNCTIONS ///////////////////////////////////////////////////////////////////
730fd187 47
48function quiz_add_instance($quiz) {
a5e1f35c 49/// Given an object containing all the necessary data,
50/// (defined by the form in mod.html) this function
51/// will create a new instance and return the id number
52/// of the new instance.
730fd187 53
49dcdd18 54 $quiz->created = time();
730fd187 55 $quiz->timemodified = time();
49dcdd18 56 $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday,
c04c41c7 57 $quiz->openhour, $quiz->openminute, 0);
49dcdd18 58 $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday,
c04c41c7 59 $quiz->closehour, $quiz->closeminute, 0);
730fd187 60
7bd1aa1d 61 if (!$quiz->id = insert_record("quiz", $quiz)) {
62 return false; // some error occurred
63 }
64
10b9291c 65 // The grades for every question in this quiz are stored in an array
7bd1aa1d 66 if ($quiz->grades) {
67 foreach ($quiz->grades as $question => $grade) {
401c8de6 68 if ($question) {
8d94f5a0 69 unset($questiongrade);
70 $questiongrade->quiz = $quiz->id;
71 $questiongrade->question = $question;
72 $questiongrade->grade = $grade;
73 if (!insert_record("quiz_question_grades", $questiongrade)) {
74 return false;
75 }
7bd1aa1d 76 }
77 }
78 }
730fd187 79
7bd1aa1d 80 return $quiz->id;
730fd187 81}
82
83
84function quiz_update_instance($quiz) {
a5e1f35c 85/// Given an object containing all the necessary data,
86/// (defined by the form in mod.html) this function
87/// will update an existing instance with new data.
730fd187 88
89 $quiz->timemodified = time();
49dcdd18 90 $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday,
c04c41c7 91 $quiz->openhour, $quiz->openminute, 0);
49dcdd18 92 $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday,
c04c41c7 93 $quiz->closehour, $quiz->closeminute, 0);
730fd187 94 $quiz->id = $quiz->instance;
95
7bd1aa1d 96 if (!update_record("quiz", $quiz)) {
97 return false; // some error occurred
98 }
730fd187 99
7bd1aa1d 100
10b9291c 101 // The grades for every question in this quiz are stored in an array
7bd1aa1d 102 // Insert or update records as appropriate
103
104 $existing = get_records("quiz_question_grades", "quiz", $quiz->id, "", "question,grade,id");
105
106 if ($quiz->grades) {
107 foreach ($quiz->grades as $question => $grade) {
401c8de6 108 if ($question) {
8d94f5a0 109 unset($questiongrade);
110 $questiongrade->quiz = $quiz->id;
111 $questiongrade->question = $question;
112 $questiongrade->grade = $grade;
113 if (isset($existing[$question])) {
114 if ($existing[$question]->grade != $grade) {
115 $questiongrade->id = $existing[$question]->id;
116 if (!update_record("quiz_question_grades", $questiongrade)) {
117 return false;
118 }
119 }
120 } else {
121 if (!insert_record("quiz_question_grades", $questiongrade)) {
7bd1aa1d 122 return false;
123 }
124 }
7bd1aa1d 125 }
126 }
127 }
128
129 return true;
730fd187 130}
131
132
133function quiz_delete_instance($id) {
a5e1f35c 134/// Given an ID of an instance of this module,
135/// this function will permanently delete the instance
136/// and any data that depends on it.
730fd187 137
138 if (! $quiz = get_record("quiz", "id", "$id")) {
139 return false;
140 }
141
142 $result = true;
143
10b9291c 144 if ($attempts = get_record("quiz_attempts", "quiz", "$quiz->id")) {
145 foreach ($attempts as $attempt) {
146 if (! delete_records("quiz_responses", "attempt", "$attempt->id")) {
147 $result = false;
148 }
149 }
150 }
151
152 if (! delete_records("quiz_attempts", "quiz", "$quiz->id")) {
153 $result = false;
154 }
155
156 if (! delete_records("quiz_grades", "quiz", "$quiz->id")) {
157 $result = false;
158 }
159
160 if (! delete_records("quiz_question_grades", "quiz", "$quiz->id")) {
161 $result = false;
162 }
730fd187 163
164 if (! delete_records("quiz", "id", "$quiz->id")) {
165 $result = false;
166 }
167
168 return $result;
169}
170
171function quiz_user_outline($course, $user, $mod, $quiz) {
a5e1f35c 172/// Return a small object with summary information about what a
173/// user has done with a given particular instance of this module
174/// Used for user activity reports.
175/// $return->time = the time they did it
176/// $return->info = a short text description
ebc3bd2b 177 if ($grade = get_record("quiz_grades", "userid", $user->id, "quiz", $quiz->id)) {
98092498 178
179 if ($grade->grade) {
180 $result->info = get_string("grade").": $grade->grade";
181 }
182 $result->time = $grade->timemodified;
183 return $result;
184 }
185 return NULL;
730fd187 186
187 return $return;
188}
189
190function quiz_user_complete($course, $user, $mod, $quiz) {
a5e1f35c 191/// Print a detailed representation of what a user has done with
192/// a given particular instance of this module, for user activity reports.
730fd187 193
194 return true;
195}
196
730fd187 197function quiz_cron () {
a5e1f35c 198/// Function to be run periodically according to the moodle cron
199/// This function searches for things that need to be done, such
200/// as sending out mail, toggling flags etc ...
730fd187 201
202 global $CFG;
203
204 return true;
205}
206
d0ac6bc2 207function quiz_grades($quizid) {
858deff0 208/// Must return an array of grades, indexed by user, and a max grade.
209
ebc3bd2b 210 $return->grades = get_records_menu("quiz_grades", "quiz", $quizid, "", "userid,grade");
858deff0 211 $return->maxgrade = get_field("quiz", "grade", "id", "$quizid");
212 return $return;
d0ac6bc2 213}
214
730fd187 215
bdc23be0 216/// SQL FUNCTIONS ////////////////////////////////////////////////////////////////////
217
218function quiz_move_questions($category1, $category2) {
219 global $CFG;
220 return execute_sql("UPDATE {$CFG->prefix}quiz_questions
221 SET category = '$category2'
222 WHERE category = '$category1'",
223 false);
224}
225
226function quiz_get_question_grades($quizid, $questionlist) {
227 global $CFG;
228
229 return get_records_sql("SELECT question,grade
230 FROM {$CFG->prefix}quiz_question_grades
231 WHERE quiz = '$quizid'
232 AND question IN ($questionlist)");
233}
234
34d52ad7 235function quiz_get_random_categories($questionlist) {
236/// Given an array of questions, this function looks for random
237/// questions among them and returns a list of categories with
238/// an associated count of random questions for each.
239
240 global $CFG;
241
242 return get_records_sql_menu("SELECT category,count(*)
243 FROM {$CFG->prefix}quiz_questions
244 WHERE id IN ($questionlist)
245 AND qtype = '".RANDOM."'
246 GROUP BY category ");
247}
248
bdc23be0 249function quiz_get_grade_records($quiz) {
250/// Gets all info required to display the table of quiz results
251/// for report.php
252 global $CFG;
253
254 return get_records_sql("SELECT qg.*, u.firstname, u.lastname, u.picture
255 FROM {$CFG->prefix}quiz_grades qg,
256 {$CFG->prefix}user u
257 WHERE qg.quiz = '$quiz->id'
ebc3bd2b 258 AND qg.userid = u.id");
bdc23be0 259}
260
261function quiz_get_answers($question) {
34d52ad7 262// Given a question, returns the correct answers for a given question
bdc23be0 263 global $CFG;
95dbc030 264
a2fe7cc0 265 switch ($question->qtype) {
95dbc030 266 case SHORTANSWER: // Could be multiple answers
34d52ad7 267 return get_records_sql("SELECT a.*, sa.usecase
bdc23be0 268 FROM {$CFG->prefix}quiz_shortanswer sa,
34d52ad7 269 {$CFG->prefix}quiz_answers a
bdc23be0 270 WHERE sa.question = '$question->id'
34d52ad7 271 AND sa.question = a.question ");
bdc23be0 272 break;
273
95dbc030 274 case TRUEFALSE: // Should be always two answers
34d52ad7 275 return get_records("quiz_answers", "question", $question->id);
bdc23be0 276 break;
277
95dbc030 278 case MULTICHOICE: // Should be multiple answers
34d52ad7 279 return get_records_sql("SELECT a.*, mc.single
bdc23be0 280 FROM {$CFG->prefix}quiz_multichoice mc,
34d52ad7 281 {$CFG->prefix}quiz_answers a
bdc23be0 282 WHERE mc.question = '$question->id'
34d52ad7 283 AND mc.question = a.question ");
bdc23be0 284 break;
285
54a67a59 286 case MATCH:
34d52ad7 287 return get_records("quiz_match_sub", "question", $question->id);
40b1a221 288 break;
54a67a59 289
290 case RANDOMSAMATCH: // Could be any of many answers, return them all
34d52ad7 291 return get_records_sql("SELECT a.*
95dbc030 292 FROM {$CFG->prefix}quiz_questions q,
34d52ad7 293 {$CFG->prefix}quiz_answers a
95dbc030 294 WHERE q.category = '$question->category'
295 AND q.qtype = ".SHORTANSWER."
34d52ad7 296 AND q.id = a.question ");
95dbc030 297 break;
298
361f649d 299 case NUMERICAL: // Logical support for multiple answers
300 return get_records_sql("SELECT a.*, n.min, n.max
301 FROM {$CFG->prefix}quiz_numerical n,
302 {$CFG->prefix}quiz_answers a
303 WHERE a.question = '$question->id'
304 AND n.answer = a.id ");
305 break;
306
307
bdc23be0 308 default:
309 return false;
310 }
311}
312
313
ef4145f6 314function quiz_get_attempt_responses($attempt, $quiz) {
bdc23be0 315// Given an attempt object, this function gets all the
316// stored responses and returns them in a format suitable
317// for regrading using quiz_grade_attempt_results()
318 global $CFG;
319
33de9f7b 320 if (!$responses = get_records_sql("SELECT q.id, q.qtype, q.category, q.questiontext, r.answer
bdc23be0 321 FROM {$CFG->prefix}quiz_responses r,
322 {$CFG->prefix}quiz_questions q
323 WHERE r.attempt = '$attempt->id'
34d52ad7 324 AND q.id = r.question")) {
bdc23be0 325 notify("Could not find any responses for that attempt!");
326 return false;
327 }
328
34d52ad7 329
330 foreach ($responses as $key => $response) {
331 if ($response->qtype == RANDOM) {
332 $responses[$key]->random = $response->answer;
333 $responses[$key]->answer = explode(",",$responses[$response->answer]->answer);
334 $responses[$response->answer]->delete = true;
3960a44b 335 } else if ($response->qtype == NUMERICAL or $response->qtype == SHORTANSWER) {
336 $responses[$key]->answer = array($response->answer);
34d52ad7 337 } else {
338 $responses[$key]->answer = explode(",",$response->answer);
339 }
340 }
bdc23be0 341 foreach ($responses as $key => $response) {
34d52ad7 342 if (!empty($response->delete)) {
343 unset($responses[$key]);
344 }
bdc23be0 345 }
346
347 return $responses;
348}
349
350
351
730fd187 352//////////////////////////////////////////////////////////////////////////////////////
a5e1f35c 353/// Any other quiz functions go here. Each of them must have a name that
354/// starts with quiz_
730fd187 355
a8a372cc 356function quiz_print_comment($text) {
357 global $THEME;
358
361f649d 359 echo "<span class=feedbacktext>".text_to_html($text, true, false)."</span>";
a8a372cc 360}
361
8db3eadd 362function quiz_print_correctanswer($text) {
363 global $THEME;
364
361f649d 365 echo "<p align=right><span class=highlight>$text</span></p>";
8db3eadd 366}
367
34d52ad7 368function quiz_print_question_icon($question, $editlink=true) {
c74a0ca5 369// Prints a question icon
cc3b8c75 370
371 global $QUIZ_QUESTION_TYPE;
372
34d52ad7 373 if ($editlink) {
361f649d 374 echo "<a href=\"question.php?id=$question->id\" title=\"".$QUIZ_QUESTION_TYPE[$question->qtype]."\">";
34d52ad7 375 }
a2fe7cc0 376 switch ($question->qtype) {
c74a0ca5 377 case SHORTANSWER:
361f649d 378 echo '<img border=0 height=16 width=16 src="pix/sa.gif">';
c74a0ca5 379 break;
380 case TRUEFALSE:
361f649d 381 echo '<img border=0 height=16 width=16 src="pix/tf.gif">';
c74a0ca5 382 break;
383 case MULTICHOICE:
361f649d 384 echo '<img border=0 height=16 width=16 src="pix/mc.gif">';
c74a0ca5 385 break;
386 case RANDOM:
361f649d 387 echo '<img border=0 height=16 width=16 src="pix/rs.gif">';
c74a0ca5 388 break;
54a67a59 389 case MATCH:
361f649d 390 echo '<img border=0 height=16 width=16 src="pix/ma.gif">';
54a67a59 391 break;
392 case RANDOMSAMATCH:
361f649d 393 echo '<img border=0 height=16 width=16 src="pix/rm.gif">';
95dbc030 394 break;
401c8de6 395 case DESCRIPTION:
361f649d 396 echo '<img border=0 height=16 width=16 src="pix/de.gif">';
397 break;
398 case NUMERICAL:
399 echo '<img border=0 height=16 width=16 src="pix/nu.gif">';
401c8de6 400 break;
c74a0ca5 401 }
34d52ad7 402 if ($editlink) {
361f649d 403 echo "</a>\n";
34d52ad7 404 }
c74a0ca5 405}
406
a8a372cc 407
34d52ad7 408
409function quiz_print_question($number, $question, $grade, $courseid,
410 $feedback=NULL, $response=NULL, $actualgrade=NULL, $correct=NULL,
4b85b717 411 $realquestion=NULL, $shuffleanswers=false) {
34d52ad7 412
413/// Prints a quiz question, any format
414/// $question is provided as an object
14d8c0b4 415
263cff8b 416 if ($question->image) {
417 if ($quizcategory = get_record("quiz_categories", "id", $question->category)) {
418 $question->course = $quizcategory->course;
419 } else {
420 $question->course = $courseid;
421 }
422 }
423
401c8de6 424 if ($question->qtype == DESCRIPTION) { // Special case question - has no answers etc
425 echo '<p align="center">';
426 echo text_to_html($question->questiontext);
427 if ($question->image) {
263cff8b 428 print_file_picture($question->image, $question->course);
401c8de6 429 }
430 echo '</p>';
431 return true;
432 }
433
a2fe7cc0 434 if (empty($actualgrade)) {
435 $actualgrade = 0;
436 }
437
14d8c0b4 438 $stranswer = get_string("answer", "quiz");
439 $strmarks = get_string("marks", "quiz");
440
361f649d 441 echo "<table width=100% cellspacing=10><tr><td nowrap width=100 valign=top>";
442 echo "<p align=center><b>$number</b></p>";
19c4f55c 443 if ($feedback or $response) {
361f649d 444 echo "<p align=center><font size=1>$strmarks: $actualgrade/$grade</font></p>";
a8a372cc 445 } else {
361f649d 446 echo "<p align=center><font size=1>$grade $strmarks</font></p>";
a8a372cc 447 }
14d8c0b4 448 print_spacer(1,100);
361f649d 449 echo "</td><td valign=top>";
34d52ad7 450
451 if (empty($realquestion)) {
452 $realquestion->id = $question->id;
453 } else { // Add a marker to connect this question to the actual random parent
454 echo "<input type=\"hidden\" name=\"q{$realquestion->id}rq$question->id\" value=\"x\">\n";
455 }
14d8c0b4 456
a2fe7cc0 457 switch ($question->qtype) {
34d52ad7 458
a5e1f35c 459 case SHORTANSWER:
361f649d 460 case NUMERICAL:
2a2c9725 461 echo text_to_html($question->questiontext);
14d8c0b4 462 if ($question->image) {
263cff8b 463 print_file_picture($question->image, $question->course);
14d8c0b4 464 }
a8a372cc 465 if ($response) {
466 $value = "VALUE=\"$response[0]\"";
a2fe7cc0 467 } else {
468 $value = "";
a8a372cc 469 }
34d52ad7 470 echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$realquestion->id SIZE=20 $value></P>";
a8a372cc 471 if ($feedback) {
472 quiz_print_comment("<P ALIGN=right>$feedback[0]</P>");
473 }
8db3eadd 474 if ($correct) {
c74a0ca5 475 $correctanswers = implode(", ", $correct);
8db3eadd 476 quiz_print_correctanswer($correctanswers);
477 }
14d8c0b4 478 break;
479
a5e1f35c 480 case TRUEFALSE:
14d8c0b4 481 if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
482 notify("Error: Missing question options!");
483 }
a2fe7cc0 484 if (!$true = get_record("quiz_answers", "id", $options->trueanswer)) {
14d8c0b4 485 notify("Error: Missing question answers!");
486 }
a2fe7cc0 487 if (!$false = get_record("quiz_answers", "id", $options->falseanswer)) {
14d8c0b4 488 notify("Error: Missing question answers!");
489 }
490 if (!$true->answer) {
491 $true->answer = get_string("true", "quiz");
492 }
493 if (!$false->answer) {
494 $false->answer = get_string("false", "quiz");
495 }
2a2c9725 496 echo text_to_html($question->questiontext);
14d8c0b4 497 if ($question->image) {
263cff8b 498 print_file_picture($question->image, $question->course);
14d8c0b4 499 }
a8a372cc 500
a2fe7cc0 501 $truechecked = "";
502 $falsechecked = "";
503
504 if (!empty($response[$true->id])) {
a8a372cc 505 $truechecked = "CHECKED";
506 $feedbackid = $true->id;
a2fe7cc0 507 } else if (!empty($response[$false->id])) {
a8a372cc 508 $falsechecked = "CHECKED";
509 $feedbackid = $false->id;
510 }
a2fe7cc0 511
512 $truecorrect = "";
513 $falsecorrect = "";
8db3eadd 514 if ($correct) {
a2fe7cc0 515 if (!empty($correct[$true->id])) {
8db3eadd 516 $truecorrect = "CLASS=highlight";
517 }
a2fe7cc0 518 if (!empty($correct[$false->id])) {
8db3eadd 519 $falsecorrect = "CLASS=highlight";
520 }
521 }
522 echo "<TABLE ALIGN=right cellpadding=5><TR><TD align=right>$stranswer:&nbsp;&nbsp;";
523 echo "<TD $truecorrect>";
34d52ad7 524 echo "<INPUT $truechecked TYPE=RADIO NAME=\"q$realquestion->id\" VALUE=\"$true->id\">$true->answer";
8db3eadd 525 echo "</TD><TD $falsecorrect>";
0bf2925d 526 echo "<INPUT $falsechecked TYPE=RADIO NAME=\"q$realquestion->id\" VALUE=\"$false->id\">$false->answer";
8db3eadd 527 echo "</TD></TR></TABLE><BR CLEAR=ALL>";
a8a372cc 528 if ($feedback) {
529 quiz_print_comment("<P ALIGN=right>$feedback[$feedbackid]</P>");
530 }
531
14d8c0b4 532 break;
533
a5e1f35c 534 case MULTICHOICE:
14d8c0b4 535 if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
536 notify("Error: Missing question options!");
537 }
a5e1f35c 538 if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
14d8c0b4 539 notify("Error: Missing question answers!");
540 }
2a2c9725 541 echo text_to_html($question->questiontext);
14d8c0b4 542 if ($question->image) {
263cff8b 543 print_file_picture($question->image, $question->course);
14d8c0b4 544 }
545 echo "<TABLE ALIGN=right>";
546 echo "<TR><TD valign=top>$stranswer:&nbsp;&nbsp;</TD><TD>";
81b635c3 547 echo "<TABLE>";
14d8c0b4 548 $answerids = explode(",", $options->answers);
a8a372cc 549
4b85b717 550 if ($shuffleanswers) {
551 $answerids = swapshuffle($answerids);
552 }
553
14d8c0b4 554 foreach ($answerids as $key => $answerid) {
555 $answer = $answers[$answerid];
68fefdbe 556 $qnumchar = chr(ord('a') + $key);
a8a372cc 557
8e6c87cc 558 if (empty($feedback) or empty($response[$answerid])) {
a8a372cc 559 $checked = "";
8e6c87cc 560 } else {
561 $checked = "CHECKED";
a8a372cc 562 }
14d8c0b4 563 echo "<TR><TD valign=top>";
a5e1f35c 564 if ($options->single) {
34d52ad7 565 echo "<INPUT $checked TYPE=RADIO NAME=q$realquestion->id VALUE=\"$answer->id\">";
14d8c0b4 566 } else {
34d52ad7 567 echo "<INPUT $checked TYPE=CHECKBOX NAME=q$realquestion->id"."a$answer->id VALUE=\"$answer->id\">";
14d8c0b4 568 }
569 echo "</TD>";
8e6c87cc 570 if (empty($feedback) or empty($correct[$answer->id])) {
68fefdbe 571 echo "<TD valign=top>$qnumchar. $answer->answer</TD>";
8e6c87cc 572 } else {
68fefdbe 573 echo "<TD valign=top CLASS=highlight>$qnumchar. $answer->answer</TD>";
8db3eadd 574 }
8e6c87cc 575 if (!empty($feedback)) {
a8a372cc 576 echo "<TD valign=top>&nbsp;";
8e6c87cc 577 if (!empty($response[$answerid])) {
a8a372cc 578 quiz_print_comment($feedback[$answerid]);
579 }
580 echo "</TD>";
581 }
14d8c0b4 582 echo "</TR>";
583 }
584 echo "</TABLE>";
585 echo "</TABLE>";
586 break;
587
54a67a59 588 case MATCH:
589 if (!$options = get_record("quiz_match", "question", $question->id)) {
590 notify("Error: Missing question options!");
591 }
592 if (!$subquestions = get_records_list("quiz_match_sub", "id", $options->subquestions)) {
593 notify("Error: Missing subquestions for this question!");
594 }
96192c44 595 if (!empty($question->questiontext)) {
596 echo text_to_html($question->questiontext);
4b85b717 597 }
96192c44 598 if (!empty($question->image)) {
263cff8b 599 print_file_picture($question->image, $question->course);
54a67a59 600 }
601
96192c44 602 if ($shuffleanswers) {
603 $subquestions = draw_rand_array($subquestions, count($subquestions));
604 }
54a67a59 605 foreach ($subquestions as $subquestion) {
606 $answers[$subquestion->id] = $subquestion->answertext;
607 }
608
609 $answers = draw_rand_array($answers, count($answers));
610
611 echo "<table border=0 cellpadding=10 align=right>";
612 foreach ($subquestions as $key => $subquestion) {
613 echo "<tr><td align=left valign=top>";
614 echo $subquestion->questiontext;
615 echo "</td>";
616 if (empty($response)) {
617 echo "<td align=right valign=top>";
34d52ad7 618 choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id");
54a67a59 619 } else {
620 if (empty($response[$key])) {
621 echo "<td align=right valign=top>";
34d52ad7 622 choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id");
54a67a59 623 } else {
624 if ($response[$key] == $correct[$key]) {
625 echo "<td align=right valign=top class=highlight>";
34d52ad7 626 choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id", $response[$key]);
54a67a59 627 } else {
628 echo "<td align=right valign=top>";
34d52ad7 629 choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id", $response[$key]);
54a67a59 630 }
631 }
632
633 if (!empty($feedback[$key])) {
634 quiz_print_comment($feedback[$key]);
635 }
636 }
637 echo "</td></tr>";
638 }
639 echo "</table>";
640
641 break;
642
643 case RANDOMSAMATCH:
644 if (!$options = get_record("quiz_randomsamatch", "question", $question->id)) {
95dbc030 645 notify("Error: Missing question options!");
646 }
647 echo text_to_html($question->questiontext);
648 if ($question->image) {
263cff8b 649 print_file_picture($question->image, $question->course);
95dbc030 650 }
651
652 /// First, get all the questions available
653
654 $allquestions = get_records_select("quiz_questions",
655 "category = $question->category AND qtype = ".SHORTANSWER);
656 if (count($allquestions) < $options->choose) {
657 notify("Error: could not find enough Short Answer questions in the database!");
658 notify("Found ".count($allquestions).", need $options->choose.");
659 break;
660 }
661
662 if (empty($response)) { // Randomly pick the questions
663 if (!$randomquestions = draw_rand_array($allquestions, $options->choose)) {
664 notify("Error choosing $options->choose random questions");
665 break;
666 }
667 } else { // Use existing questions
668 $randomquestions = array();
669 foreach ($response as $key => $rrr) {
670 $rrr = explode("-", $rrr);
671 $randomquestions[$key] = $allquestions[$key];
672 $responseanswer[$key] = $rrr[1];
673 }
674 }
675
676 /// For each selected, find the best matching answers
677
678 foreach ($randomquestions as $randomquestion) {
679 $shortanswerquestion = get_record("quiz_shortanswer", "question", $randomquestion->id);
680 $questionanswers = get_records_list("quiz_answers", "id", $shortanswerquestion->answers);
681 $bestfraction = 0;
682 $bestanswer = NULL;
683 foreach ($questionanswers as $questionanswer) {
684 if ($questionanswer->fraction > $bestfraction) {
685 $bestanswer = $questionanswer;
686 }
687 }
688 if (empty($bestanswer)) {
689 notify("Error: Could not find the best answer for question: ".$randomquestions->name);
690 break;
691 }
692 $randomanswers[$bestanswer->id] = trim($bestanswer->answer);
693 }
694
695 if (!$randomanswers = draw_rand_array($randomanswers, $options->choose)) { // Mix them up
696 notify("Error randomising answers!");
697 break;
698 }
699
700 echo "<table border=0 cellpadding=10>";
701 foreach ($randomquestions as $key => $randomquestion) {
702 echo "<tr><td align=left valign=top>";
703 echo $randomquestion->questiontext;
704 echo "</td>";
705 echo "<td align=right valign=top>";
706 if (empty($response)) {
34d52ad7 707 choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id");
95dbc030 708 } else {
709 if (!empty($correct[$key])) {
710 if ($randomanswers[$responseanswer[$key]] == $correct[$key]) {
711 echo "<span=highlight>";
34d52ad7 712 choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id", $responseanswer[$key]);
95dbc030 713 echo "</span><br \>";
714 } else {
34d52ad7 715 choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id", $responseanswer[$key]);
95dbc030 716 quiz_print_correctanswer($correct[$key]);
717 }
718 } else {
34d52ad7 719 choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id", $responseanswer[$key]);
95dbc030 720 }
721 if (!empty($feedback[$key])) {
722 quiz_print_comment($feedback[$key]);
723 }
724 }
725 echo "</td></tr>";
726 }
727 echo "</table>";
728 break;
729
34d52ad7 730 case RANDOM:
731 echo "<P>Random questions should not be printed this way!</P>";
732 break;
8db3eadd 733
14d8c0b4 734 default:
735 notify("Error: Unknown question type!");
736 }
737
738 echo "</TD></TR></TABLE>";
3a506ca2 739}
740
34d52ad7 741
742
96192c44 743function quiz_print_quiz_questions($quiz, $results=NULL, $questions=NULL, $shuffleorder=NULL) {
a5e1f35c 744// Prints a whole quiz on one page.
745
34d52ad7 746 /// Get the questions
747
34d52ad7 748 if (!$questions) {
434802d5 749 if (empty($quiz->questions)) {
750 notify("No questions have been defined!");
751 return false;
752 }
753
34d52ad7 754 if (!$questions = get_records_list("quiz_questions", "id", $quiz->questions, "")) {
434802d5 755 notify("Error when reading questions from the database!");
34d52ad7 756 return false;
757 }
d288fa52 758 }
4b85b717 759
d288fa52 760 if (!$shuffleorder) {
761 if (!empty($quiz->shufflequestions)) { // Mix everything up
4b85b717 762 $questions = swapshuffle_assoc($questions);
ada728e2 763 } else {
d288fa52 764 $shuffleorder = explode(",", $quiz->questions); // Use originally defined order
4b85b717 765 }
34d52ad7 766 }
767
e1122620 768 if ($shuffleorder) { // Order has been defined, so reorder questions
769 $oldquestions = $questions;
770 $questions = array();
771 foreach ($shuffleorder as $key) {
772 $questions[] = $oldquestions[$key]; // This loses the index key, but doesn't matter
773 }
774 }
775
434802d5 776 if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) {
777 notify("No grades were found for these questions!");
778 return false;
779 }
780
34d52ad7 781
782 /// Examine the set of questions for random questions, and retrieve them
783
434802d5 784 if (empty($results)) { // Choose some new random questions
34d52ad7 785 if ($randomcats = quiz_get_random_categories($quiz->questions)) {
786 foreach ($randomcats as $randomcat => $randomdraw) {
787 /// Get the appropriate amount of random questions from this category
7bfa4fad 788 if (!$catquestions[$randomcat] = quiz_choose_random_questions($randomcat, $randomdraw, $quiz->questions)) {
434802d5 789 notify(get_string("toomanyrandom", "quiz", $randomcat));
34d52ad7 790 return false;
791 }
792 }
793 }
434802d5 794 } else { // Get the previously chosen questions
795 $chosen = array();
796 foreach ($questions as $question) {
797 if (isset($question->random)) {
798 $chosen[] = $question->random;
799 }
800 }
801 if ($chosen) {
802 $chosenlist = implode(",", $chosen);
803 if (!$chosen = get_records_list("quiz_questions", "id", $chosenlist, "")) {
804 notify("Error when reading questions from the database!");
805 return false;
806 }
807 }
a5e1f35c 808 }
809
9307692f 810 $strconfirmattempt = addslashes(get_string("readytosend", "quiz"));
811
812 echo "<FORM METHOD=POST ACTION=attempt.php onsubmit=\"return confirm('$strconfirmattempt');\">";
a5e1f35c 813 echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">";
a8a372cc 814
34d52ad7 815 $count = 0;
96192c44 816 $questionorder = array();
817
34d52ad7 818 foreach ($questions as $question) {
401c8de6 819
820 if ($question->qtype != DESCRIPTION) { // Description questions are not counted
821 $count++;
822 }
34d52ad7 823
e1122620 824 $questionorder[] = $question->id;
825
8db3eadd 826 $feedback = NULL;
827 $response = NULL;
828 $actualgrades = NULL;
829 $correct = NULL;
34d52ad7 830 $randomquestion = NULL;
831
832 if (empty($results)) {
833 if ($question->qtype == RANDOM ) { // Set up random questions
834 $randomquestion = $question;
835 $question = array_pop($catquestions[$randomquestion->category]);
836 $grades[$question->id]->grade = $grades[$randomquestion->id]->grade;
8e6c87cc 837 }
34d52ad7 838 } else {
839 if (!empty($results->feedback[$question->id])) {
840 $feedback = $results->feedback[$question->id];
8e6c87cc 841 }
34d52ad7 842 if (!empty($results->response[$question->id])) {
843 $response = $results->response[$question->id];
844 }
845 if (!empty($results->grades[$question->id])) {
846 $actualgrades = $results->grades[$question->id];
8e6c87cc 847 }
8db3eadd 848 if ($quiz->correctanswers) {
34d52ad7 849 if (!empty($results->correct[$question->id])) {
850 $correct = $results->correct[$question->id];
8e6c87cc 851 }
8db3eadd 852 }
34d52ad7 853 if (!empty($question->random)) {
854 $randomquestion = $question;
434802d5 855 $question = $chosen[$question->random];
34d52ad7 856 $grades[$question->id]->grade = $grades[$randomquestion->id]->grade;
857 }
a8a372cc 858 }
8db3eadd 859
2408867e 860
a5e1f35c 861 print_simple_box_start("CENTER", "90%");
34d52ad7 862 quiz_print_question($count, $question, $grades[$question->id]->grade, $quiz->course,
4b85b717 863 $feedback, $response, $actualgrades, $correct,
864 $randomquestion, $quiz->shuffleanswers);
a5e1f35c 865 print_simple_box_end();
96192c44 866 echo "<br \>";
a5e1f35c 867 }
a8a372cc 868
8e6c87cc 869 if (empty($results)) {
96192c44 870 if (!empty($quiz->shufflequestions)) { // Things have been mixed up, so pass the question order
871 $shuffleorder = implode(',', $questionorder);
872 echo "<input type=hidden name=shuffleorder value=\"$shuffleorder\">\n";
873 }
874 echo "<center><input type=submit value=\"".get_string("savemyanswers", "quiz")."\"></center>";
a8a372cc 875 }
96192c44 876 echo "</form>";
10b9291c 877
878 return true;
a5e1f35c 879}
34d52ad7 880
881
6a952ce7 882
883function quiz_get_default_category($courseid) {
34d52ad7 884/// Returns the current category
885
6a952ce7 886 if ($categories = get_records("quiz_categories", "course", $courseid, "id")) {
887 foreach ($categories as $category) {
888 return $category; // Return the first one (lowest id)
889 }
890 }
891
892 // Otherwise, we need to make one
10b9291c 893 $category->name = get_string("default", "quiz");
894 $category->info = get_string("defaultinfo", "quiz");
6a952ce7 895 $category->course = $courseid;
896 $category->publish = 0;
897
898 if (!$category->id = insert_record("quiz_categories", $category)) {
899 notify("Error creating a default category!");
900 return false;
901 }
902 return $category;
903}
904
c74a0ca5 905function quiz_get_category_menu($courseid, $published=false) {
da5fb074 906/// Returns the list of categories
907 $publish = "";
c74a0ca5 908 if ($published) {
909 $publish = "OR publish = '1'";
910 }
bdc23be0 911 return get_records_select_menu("quiz_categories", "course='$courseid' $publish", "name ASC", "id,name");
c74a0ca5 912}
913
6a952ce7 914function quiz_print_category_form($course, $current) {
915// Prints a form to choose categories
916
bdc23be0 917 if (!$categories = get_records_select("quiz_categories", "course='$course->id' OR publish = '1'", "name ASC")) {
6a952ce7 918 if (!$category = quiz_get_default_category($course->id)) {
919 notify("Error creating a default category!");
920 return false;
921 }
cb62c00a 922 $categories[$category->id] = $category;
6a952ce7 923 }
8d94f5a0 924 foreach ($categories as $key => $category) {
925 if ($category->publish) {
b55a466b 926 if ($catcourse = get_record("course", "id", $category->course)) {
927 $category->name .= " ($catcourse->shortname)";
8d94f5a0 928 }
929 }
b55a466b 930 $catmenu[$category->id] = $category->name;
8d94f5a0 931 }
6a952ce7 932 $strcategory = get_string("category", "quiz");
933 $strshow = get_string("show", "quiz");
6b069ece 934 $streditcats = get_string("editcategories", "quiz");
6a952ce7 935
467aaec6 936 echo "<TABLE width=\"100%\"><TR><TD NOWRAP>";
6a952ce7 937 echo "<FORM METHOD=POST ACTION=edit.php>";
938 echo "<B>$strcategory:</B>&nbsp;";
b55a466b 939 choose_from_menu($catmenu, "cat", "$current");
92a3c884 940 echo "<INPUT TYPE=submit VALUE=\"$strshow\">";
6a952ce7 941 echo "</FORM>";
6b069ece 942 echo "</TD><TD align=right>";
943 echo "<FORM METHOD=GET ACTION=category.php>";
944 echo "<INPUT TYPE=hidden NAME=id VALUE=\"$course->id\">";
945 echo "<INPUT TYPE=submit VALUE=\"$streditcats\">";
946 echo "</FORM>";
947 echo "</TD></TR></TABLE>";
6a952ce7 948}
949
950
34d52ad7 951
7bfa4fad 952function quiz_choose_random_questions($category, $draws, $excluded=0) {
34d52ad7 953/// Given a question category and a number of draws, this function
954/// creates a random subset of that size - returned as an array of questions
955
956 if (!$pool = get_records_select_menu("quiz_questions",
575de48f 957 "category = '$category' AND id NOT IN ($excluded)
958 AND qtype <> ".RANDOM."
959 AND qtype <> ".DESCRIPTION,
960 "", "id,qtype")) {
34d52ad7 961 return false;
962 }
963
964 $countpool = count($pool);
965
966 if ($countpool == $draws) {
967 $chosen = $pool;
968 } else if ($countpool < $draws) {
969 return false;
970 } else {
971 $chosen = draw_rand_array($pool, $draws);
972 }
973
974 $chosenlist = implode(",", array_keys($chosen));
975 return get_records_list("quiz_questions", "id", $chosenlist);
976}
977
978
7bd1aa1d 979function quiz_get_all_question_grades($questionlist, $quizid) {
980// Given a list of question IDs, finds grades or invents them to
981// create an array of matching grades
982
5a25f84d 983 if (empty($questionlist)) {
984 return array();
985 }
986
bdc23be0 987 $questions = quiz_get_question_grades($quizid, $questionlist);
7bd1aa1d 988
989 $list = explode(",", $questionlist);
990 $grades = array();
991
992 foreach ($list as $qid) {
993 if (isset($questions[$qid])) {
994 $grades[$qid] = $questions[$qid]->grade;
995 } else {
996 $grades[$qid] = 1;
997 }
998 }
999 return $grades;
1000}
1001
1002
1003function quiz_print_question_list($questionlist, $grades) {
6a952ce7 1004// Prints a list of quiz questions in a small layout form with knobs
7bd1aa1d 1005// $questionlist is comma-separated list
1006// $grades is an array of corresponding grades
6a952ce7 1007
1008 global $THEME;
1009
1010 if (!$questionlist) {
1011 echo "<P align=center>";
1012 print_string("noquestions", "quiz");
1013 echo "</P>";
1014 return;
1015 }
1016
1017 $order = explode(",", $questionlist);
1018
7bd1aa1d 1019 if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) {
9a652ddb 1020 echo "<P align=center>";
1021 print_string("noquestions", "quiz");
1022 echo "</P>";
1023 return;
1024
6a952ce7 1025 }
1026
1027 $strorder = get_string("order");
1028 $strquestionname = get_string("questionname", "quiz");
1029 $strgrade = get_string("grade");
1030 $strdelete = get_string("delete");
1031 $stredit = get_string("edit");
1032 $strmoveup = get_string("moveup");
1033 $strmovedown = get_string("movedown");
1034 $strsavegrades = get_string("savegrades", "quiz");
c74a0ca5 1035 $strtype = get_string("type", "quiz");
6a952ce7 1036
10b9291c 1037 for ($i=10; $i>=0; $i--) {
7bd1aa1d 1038 $gradesmenu[$i] = $i;
6a952ce7 1039 }
1040 $count = 0;
1041 $sumgrade = 0;
1042 $total = count($order);
1043 echo "<FORM METHOD=post ACTION=edit.php>";
1044 echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
467aaec6 1045 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 1046 foreach ($order as $qnum) {
95dbc030 1047 if (empty($questions[$qnum])) {
1048 continue;
1049 }
bca64a12 1050 $question = $questions[$qnum];
6a952ce7 1051 $count++;
1052 echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
1053 echo "<TD>$count</TD>";
1054 echo "<TD>";
1055 if ($count != 1) {
1056 echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG
1057 SRC=\"../../pix/t/up.gif\" BORDER=0></A>";
1058 }
1059 echo "</TD>";
1060 echo "<TD>";
1061 if ($count != $total) {
1062 echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG
1063 SRC=\"../../pix/t/down.gif\" BORDER=0></A>";
1064 }
1065 echo "</TD>";
bca64a12 1066 echo "<TD>$question->name</TD>";
467aaec6 1067 echo "<TD ALIGN=CENTER>";
bca64a12 1068 quiz_print_question_icon($question);
c74a0ca5 1069 echo "</TD>";
6a952ce7 1070 echo "<TD>";
bca64a12 1071 if ($question->qtype == DESCRIPTION) {
1072 echo "<INPUT TYPE=hidden NAME=q$qnum VALUE=\"0\"> ";
1073 } else {
1074 choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], "");
1075 }
6a952ce7 1076 echo "<TD>";
1077 echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG
1078 SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
1079 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG
1080 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
1081 echo "</TD>";
1082
7bd1aa1d 1083 $sumgrade += $grades[$qnum];
6a952ce7 1084 }
c74a0ca5 1085 echo "<TR><TD COLSPAN=5 ALIGN=right>";
6a952ce7 1086 echo "<INPUT TYPE=submit VALUE=\"$strsavegrades:\">";
8d94f5a0 1087 echo "<INPUT TYPE=hidden NAME=setgrades VALUE=\"save\">";
6a952ce7 1088 echo "<TD ALIGN=LEFT BGCOLOR=\"$THEME->cellcontent\">";
1089 echo "<B>$sumgrade</B>";
1090 echo "</TD><TD></TD></TR>";
1091 echo "</TABLE>";
1092 echo "</FORM>";
10b9291c 1093
1094 return $sumgrade;
6a952ce7 1095}
1096
1097
1098function quiz_print_cat_question_list($categoryid) {
1099// Prints a form to choose categories
1100
1101 global $THEME, $QUIZ_QUESTION_TYPE;
1102
10b9291c 1103 $strcategory = get_string("category", "quiz");
6a952ce7 1104 $strquestion = get_string("question", "quiz");
49220fa7 1105 $straddquestions = get_string("addquestions", "quiz");
1106 $strimportquestions = get_string("importquestions", "quiz");
6a952ce7 1107 $strnoquestions = get_string("noquestions", "quiz");
1108 $strselect = get_string("select", "quiz");
a01b2571 1109 $strselectall = get_string("selectall", "quiz");
6a952ce7 1110 $strcreatenewquestion = get_string("createnewquestion", "quiz");
1111 $strquestionname = get_string("questionname", "quiz");
1112 $strdelete = get_string("delete");
1113 $stredit = get_string("edit");
1114 $straddselectedtoquiz = get_string("addselectedtoquiz", "quiz");
c74a0ca5 1115 $strtype = get_string("type", "quiz");
c6eed097 1116 $strcreatemultiple = get_string("createmultiple", "quiz");
6a952ce7 1117
1118 if (!$categoryid) {
5325f8b8 1119 echo "<p align=center><b>";
6a952ce7 1120 print_string("selectcategoryabove", "quiz");
5325f8b8 1121 echo "</b></p>";
1122 echo "<p>";
1123 print_string("addingquestions", "quiz");
1124 echo "</p>";
6a952ce7 1125 return;
1126 }
a5e1f35c 1127
6a952ce7 1128 if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
1129 notify("Category not found!");
1130 return;
1131 }
8d94f5a0 1132 echo "<CENTER>";
10b9291c 1133 echo text_to_html($category->info);
6a952ce7 1134
49220fa7 1135 echo "<TABLE><TR>";
1136 echo "<TD valign=top><B>$straddquestions:</B></TD>";
1137 echo "<TD valign=top align=right>";
10b9291c 1138 echo "<FORM METHOD=GET ACTION=question.php>";
a2fe7cc0 1139 choose_from_menu($QUIZ_QUESTION_TYPE, "qtype", "", "");
6a952ce7 1140 echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
7d2e5b65 1141 echo "<INPUT TYPE=submit VALUE=\"$strcreatenewquestion\">";
cd63d77e 1142 helpbutton("questiontypes", $strcreatenewquestion, "quiz");
6a952ce7 1143 echo "</FORM>";
49220fa7 1144
1145 echo "<FORM METHOD=GET ACTION=import.php>";
1146 echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
1147 echo "<INPUT TYPE=submit VALUE=\"$strimportquestions\">";
1148 helpbutton("import", $strimportquestions, "quiz");
1149 echo "</FORM>";
1150
c6eed097 1151 echo "<FORM METHOD=GET ACTION=multiple.php>";
1152 echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
1153 echo "<INPUT TYPE=submit VALUE=\"$strcreatemultiple\">";
1154 helpbutton("createmultiple", $strcreatemultiple, "quiz");
1155 echo "</FORM>";
1156
49220fa7 1157 echo "</TR></TABLE>";
1158
8d94f5a0 1159 echo "</CENTER>";
6a952ce7 1160
14bdb238 1161 if (!$questions = get_records("quiz_questions", "category", $category->id, "qtype ASC")) {
6a952ce7 1162 echo "<P align=center>";
1163 print_string("noquestions", "quiz");
1164 echo "</P>";
1165 return;
1166 }
1167
10b9291c 1168 $canedit = isteacher($category->course);
1169
6a952ce7 1170 echo "<FORM METHOD=post ACTION=edit.php>";
1171 echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
467aaec6 1172 echo "<TR><TH width=\"*\" NOWRAP>$strselect</TH><TH width=\"100%\" align=left NOWRAP>$strquestionname</TH><TH WIDTH=\"*\" NOWRAP>$strtype</TH>";
10b9291c 1173 if ($canedit) {
467aaec6 1174 echo "<TH width=\"*\" NOWRAP>$stredit</TH>";
10b9291c 1175 }
1176 echo "</TR>";
6a952ce7 1177 foreach ($questions as $question) {
1178 echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
1179 echo "<TD ALIGN=CENTER>";
1180 echo "<INPUT TYPE=CHECKBOX NAME=q$question->id VALUE=\"1\">";
1181 echo "</TD>";
1182 echo "<TD>".$question->name."</TD>";
467aaec6 1183 echo "<TD ALIGN=CENTER>";
c74a0ca5 1184 quiz_print_question_icon($question);
1185 echo "</TD>";
10b9291c 1186 if ($canedit) {
1187 echo "<TD>";
e1c91df0 1188 echo "<A TITLE=\"$strdelete\" HREF=\"question.php?id=$question->id&delete=$question->id\"><IMG
10b9291c 1189 SRC=\"../../pix/t/delete.gif\" BORDER=0></A>&nbsp;";
1190 echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG
1191 SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
1192 echo "</TD></TR>";
1193 }
1194 echo "</TR>";
6a952ce7 1195 }
1196 echo "<TR><TD COLSPAN=3>";
a01b2571 1197 echo "<INPUT TYPE=submit NAME=add VALUE=\"<< $straddselectedtoquiz\">";
1198 //echo "<INPUT TYPE=submit NAME=delete VALUE=\"XX Delete selected\">";
1199 echo "<INPUT type=button onclick=\"checkall()\" value=\"$strselectall\">";
6a952ce7 1200 echo "</TD></TR>";
1201 echo "</TABLE>";
1202 echo "</FORM>";
1203}
a5e1f35c 1204
3a506ca2 1205
958aafe2 1206function quiz_start_attempt($quizid, $userid, $numattempt) {
1207 $attempt->quiz = $quizid;
ebc3bd2b 1208 $attempt->userid = $userid;
958aafe2 1209 $attempt->attempt = $numattempt;
1210 $attempt->timestart = time();
1211 $attempt->timefinish = 0;
1212 $attempt->timemodified = time();
1213
1214 return insert_record("quiz_attempts", $attempt);
1215}
1216
1217function quiz_get_user_attempt_unfinished($quizid, $userid) {
1218// Returns an object containing an unfinished attempt (if there is one)
ebc3bd2b 1219 return get_record("quiz_attempts", "quiz", $quizid, "userid", $userid, "timefinish", 0);
958aafe2 1220}
1221
3a506ca2 1222function quiz_get_user_attempts($quizid, $userid) {
a5e1f35c 1223// Returns a list of all attempts by a user
ebc3bd2b 1224 return get_records_select("quiz_attempts", "quiz = '$quizid' AND userid = '$userid' AND timefinish > 0",
bdc23be0 1225 "attempt ASC");
3a506ca2 1226}
1227
8d94f5a0 1228
1229function quiz_get_user_attempts_string($quiz, $attempts, $bestgrade) {
1230/// Returns a simple little comma-separated list of all attempts,
6d86b5dc 1231/// with each grade linked to the feedback report and with the best grade highlighted
8d94f5a0 1232
1233 $bestgrade = format_float($bestgrade);
1234 foreach ($attempts as $attempt) {
1235 $attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade);
1236 if ($attemptgrade == $bestgrade) {
29d5d0b4 1237 $userattempts[] = "<span class=highlight><a href=\"review.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</a></span>";
8d94f5a0 1238 } else {
29d5d0b4 1239 $userattempts[] = "<a href=\"review.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</a>";
8d94f5a0 1240 }
1241 }
1242 return implode(",", $userattempts);
1243}
1244
a5e1f35c 1245function quiz_get_best_grade($quizid, $userid) {
1246/// Get the best current grade for a particular user in a quiz
ebc3bd2b 1247 if (!$grade = get_record("quiz_grades", "quiz", $quizid, "userid", $userid)) {
3a506ca2 1248 return 0;
1249 }
1250
2383cadb 1251 return (round($grade->grade,0));
3a506ca2 1252}
1253
e331eb06 1254function quiz_save_best_grade($quiz, $userid) {
a5e1f35c 1255/// Calculates the best grade out of all attempts at a quiz for a user,
1256/// and then saves that grade in the quiz_grades table.
1257
e331eb06 1258 if (!$attempts = quiz_get_user_attempts($quiz->id, $userid)) {
e909c8d0 1259 notify("Could not find any user attempts");
a5e1f35c 1260 return false;
1261 }
1262
1263 $bestgrade = quiz_calculate_best_grade($quiz, $attempts);
1264 $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade);
1265
ebc3bd2b 1266 if ($grade = get_record("quiz_grades", "quiz", $quiz->id, "userid", $userid)) {
1d2603b1 1267 $grade->grade = round($bestgrade, 2);
a5e1f35c 1268 $grade->timemodified = time();
1269 if (!update_record("quiz_grades", $grade)) {
e909c8d0 1270 notify("Could not update best grade");
a5e1f35c 1271 return false;
1272 }
1273 } else {
1274 $grade->quiz = $quiz->id;
ebc3bd2b 1275 $grade->userid = $userid;
38f03e5a 1276 $grade->grade = round($bestgrade, 2);
a5e1f35c 1277 $grade->timemodified = time();
1278 if (!insert_record("quiz_grades", $grade)) {
e909c8d0 1279 notify("Could not insert new best grade");
a5e1f35c 1280 return false;
1281 }
1282 }
1283 return true;
1284}
1285
1286
3a506ca2 1287function quiz_calculate_best_grade($quiz, $attempts) {
a5e1f35c 1288/// Calculate the best grade for a quiz given a number of attempts by a particular user.
3a506ca2 1289
1290 switch ($quiz->grademethod) {
a5e1f35c 1291
1292 case ATTEMPTFIRST:
3a506ca2 1293 foreach ($attempts as $attempt) {
a5e1f35c 1294 return $attempt->sumgrades;
3a506ca2 1295 }
a5e1f35c 1296 break;
1297
1298 case ATTEMPTLAST:
1299 foreach ($attempts as $attempt) {
1300 $final = $attempt->sumgrades;
1301 }
1302 return $final;
3a506ca2 1303
a5e1f35c 1304 case GRADEAVERAGE:
3a506ca2 1305 $sum = 0;
1306 $count = 0;
1307 foreach ($attempts as $attempt) {
a5e1f35c 1308 $sum += $attempt->sumgrades;
3a506ca2 1309 $count++;
1310 }
1311 return (float)$sum/$count;
1312
3a506ca2 1313 default:
a5e1f35c 1314 case GRADEHIGHEST:
1315 $max = 0;
3a506ca2 1316 foreach ($attempts as $attempt) {
a5e1f35c 1317 if ($attempt->sumgrades > $max) {
1318 $max = $attempt->sumgrades;
1319 }
3a506ca2 1320 }
a5e1f35c 1321 return $max;
1322 }
1323}
1324
34d52ad7 1325
40b1a221 1326function quiz_calculate_best_attempt($quiz, $attempts) {
1327/// Return the attempt with the best grade for a quiz
1328
1329 switch ($quiz->grademethod) {
1330
1331 case ATTEMPTFIRST:
1332 foreach ($attempts as $attempt) {
1333 return $attempt;
1334 }
1335 break;
1336
1337 case GRADEAVERAGE: // need to do something with it :-)
1338 case ATTEMPTLAST:
1339 foreach ($attempts as $attempt) {
1340 $final = $attempt;
1341 }
1342 return $final;
1343
1344 default:
1345 case GRADEHIGHEST:
1346 $max = -1;
1347 foreach ($attempts as $attempt) {
1348 if ($attempt->sumgrades > $max) {
1349 $max = $attempt->sumgrades;
1350 $maxattempt = $attempt;
1351 }
1352 }
1353 return $maxattempt;
1354 }
1355}
1356
1357
a5e1f35c 1358function quiz_save_attempt($quiz, $questions, $result, $attemptnum) {
1359/// Given a quiz, a list of attempted questions and a total grade
1360/// this function saves EVERYTHING so it can be reconstructed later
1361/// if necessary.
1362
1363 global $USER;
1364
958aafe2 1365 // First find the attempt in the database (start of attempt)
1366
1367 if (!$attempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
1368 notify("Trying to save an attempt that was not started!");
1369 return false;
1370 }
1371
1372 if ($attempt->attempt != $attemptnum) { // Double check.
1373 notify("Number of this attempt is different to the unfinished one!");
1374 return false;
1375 }
1376
1377 // Now let's complete this record and save it
a5e1f35c 1378
a5e1f35c 1379 $attempt->sumgrades = $result->sumgrades;
958aafe2 1380 $attempt->timefinish = time();
a5e1f35c 1381 $attempt->timemodified = time();
1382
958aafe2 1383 if (! update_record("quiz_attempts", $attempt)) {
7520988b 1384 notify("Error while saving attempt");
a5e1f35c 1385 return false;
1386 }
1387
1388 // Now let's save all the questions for this attempt
1389
1390 foreach ($questions as $question) {
1391 $response->attempt = $attempt->id;
1392 $response->question = $question->id;
1393 $response->grade = $result->grades[$question->id];
34d52ad7 1394
77cff589 1395 if (!empty($question->random)) {
34d52ad7 1396 // First save the response of the random question
1397 // the answer is the id of the REAL response
1398 $response->answer = $question->random;
1399 if (!insert_record("quiz_responses", $response)) {
1400 notify("Error while saving response");
1401 return false;
1402 }
1403 $response->question = $question->random;
1404 }
1405
54d0590b 1406 if (!empty($question->answer)) {
a5e1f35c 1407 $response->answer = implode(",",$question->answer);
1408 } else {
1409 $response->answer = "";
1410 }
1411 if (!insert_record("quiz_responses", $response)) {
7520988b 1412 notify("Error while saving response");
a5e1f35c 1413 return false;
1414 }
3a506ca2 1415 }
a5e1f35c 1416 return true;
3a506ca2 1417}
730fd187 1418
a5e1f35c 1419
1420function quiz_grade_attempt_results($quiz, $questions) {
1421/// Given a list of questions (including answers for each one)
1422/// this function does all the hard work of calculating the
1423/// grades for each question, as well as a total grade for
1424/// for the whole quiz. It returns everything in a structure
1425/// that looks like:
1426/// $result->sumgrades (sum of all grades for all questions)
1427/// $result->percentage (Percentage of grades that were correct)
1428/// $result->grade (final grade result for the whole quiz)
1429/// $result->grades[] (array of grades, indexed by question id)
a8a372cc 1430/// $result->response[] (array of response arrays, indexed by question id)
a5e1f35c 1431/// $result->feedback[] (array of feedback arrays, indexed by question id)
8db3eadd 1432/// $result->correct[] (array of feedback arrays, indexed by question id)
a5e1f35c 1433
1434 if (!$questions) {
1435 error("No questions!");
1436 }
34d52ad7 1437
1438 if (!$grades = get_records_menu("quiz_question_grades", "quiz", $quiz->id, "", "question,grade")) {
1439 error("No grades defined for these quiz questions!");
1440 }
1441
a5e1f35c 1442 $result->sumgrades = 0;
1443
1444 foreach ($questions as $question) {
34d52ad7 1445
1446 if (!empty($question->random)) { // This question has been randomly chosen
1447 $randomquestion = $question; // Save it for later
1448 if (!$question = get_record("quiz_questions", "id", $question->random)) {
1449 error("Could not find the real question behind this random question!");
1450 }
61367e64 1451 if (isset($randomquestion->answer)) {
1452 $question->answer = $randomquestion->answer;
1453 } else {
1454 $question->answer = "";
1455 }
34d52ad7 1456 $question->grade = $grades[$randomquestion->id];
1457 } else {
1458 $question->grade = $grades[$question->id];
1459 }
1460
401c8de6 1461 if ($question->qtype != DESCRIPTION) { // All real questions need answers defined
1462 if (!$answers = quiz_get_answers($question)) {
1463 error("No answers defined for question id $question->id!");
1464 }
a5e1f35c 1465 }
1466
1467 $grade = 0; // default
34d52ad7 1468 $correct = array();
19c4f55c 1469 $feedback = array();
1470 $response = array();
a5e1f35c 1471
a2fe7cc0 1472 switch ($question->qtype) {
a5e1f35c 1473 case SHORTANSWER:
1474 if ($question->answer) {
41b95af2 1475 $question->answer = trim(stripslashes($question->answer[0]));
a5e1f35c 1476 } else {
19c4f55c 1477 $question->answer = "";
a5e1f35c 1478 }
a8a372cc 1479 $response[0] = $question->answer;
a2fe7cc0 1480 $bestshortanswer = 0;
361f649d 1481 foreach ($answers as $answer) { // There might be multiple right answers
8db3eadd 1482 if ($answer->fraction > $bestshortanswer) {
1483 $correct[$answer->id] = $answer->answer;
a2fe7cc0 1484 $bestshortanswer = $answer->fraction;
8db3eadd 1485 }
2a2c9725 1486 if (!$answer->usecase) { // Don't compare case
a5e1f35c 1487 $answer->answer = strtolower($answer->answer);
1488 $question->answer = strtolower($question->answer);
1489 }
1490 if ($question->answer == $answer->answer) {
a8a372cc 1491 $feedback[0] = $answer->feedback;
34d52ad7 1492 $grade = (float)$answer->fraction * $question->grade;
a5e1f35c 1493 }
1494 }
1495 break;
1496
361f649d 1497 case NUMERICAL:
1498 if ($question->answer) {
1499 $question->answer = trim(stripslashes($question->answer[0]));
1500 } else {
1501 $question->answer = "";
1502 }
1503 $response[0] = $question->answer;
1504 $bestshortanswer = 0;
1505 foreach ($answers as $answer) { // There might be multiple right answers
1506 if ($answer->fraction > $bestshortanswer) {
1507 $correct[$answer->id] = $answer->answer;
1508 $bestshortanswer = $answer->fraction;
1509 }
1510 if ( ((float)$question->answer >= (float)$answer->min) and
1511 ((float)$question->answer <= (float)$answer->max) ) {
1512 $feedback[0] = $answer->feedback;
1513 $grade = (float)$answer->fraction * $question->grade;
1514 }
1515 }
1516 break;
a5e1f35c 1517
1518 case TRUEFALSE:
1519 if ($question->answer) {
1520 $question->answer = $question->answer[0];
1521 } else {
1522 $question->answer = NULL;
1523 }
1524 foreach($answers as $answer) { // There should be two answers (true and false)
1525 $feedback[$answer->id] = $answer->feedback;
8db3eadd 1526 if ($answer->fraction > 0) {
1527 $correct[$answer->id] = true;
1528 }
a5e1f35c 1529 if ($question->answer == $answer->id) {
34d52ad7 1530 $grade = (float)$answer->fraction * $question->grade;
a8a372cc 1531 $response[$answer->id] = true;
a5e1f35c 1532 }
1533 }
1534 break;
1535
1536
1537 case MULTICHOICE:
1538 foreach($answers as $answer) { // There will be multiple answers, perhaps more than one is right
1539 $feedback[$answer->id] = $answer->feedback;
8db3eadd 1540 if ($answer->fraction > 0) {
1541 $correct[$answer->id] = true;
1542 }
54d0590b 1543 if (!empty($question->answer)) {
a5e1f35c 1544 foreach ($question->answer as $questionanswer) {
1545 if ($questionanswer == $answer->id) {
95dbc030 1546 $response[$answer->id] = true;
a5e1f35c 1547 if ($answer->single) {
34d52ad7 1548 $grade = (float)$answer->fraction * $question->grade;
a5e1f35c 1549 continue;
1550 } else {
34d52ad7 1551 $grade += (float)$answer->fraction * $question->grade;
a5e1f35c 1552 }
1553 }
1554 }
1555 }
1556 }
1557 break;
95dbc030 1558
54a67a59 1559 case MATCH:
1560 $matchcount = $totalcount = 0;
1561
34d52ad7 1562 foreach ($question->answer as $questionanswer) { // Each answer is "subquestionid-answerid"
54a67a59 1563 $totalcount++;
34d52ad7 1564 $qarr = explode('-', $questionanswer); // Extract subquestion/answer.
1565 $subquestionid = $qarr[0];
1566 $subanswerid = $qarr[1];
e1122620 1567 if ($subquestionid and $subanswerid and (($subquestionid == $subanswerid) or
1568 ($answers[$subquestionid]->answertext == $answers[$subanswerid]->answertext))) {
34d52ad7 1569 // Either the ids match exactly, or the answertexts match exactly
1570 // (in case two subquestions had the same answer)
54a67a59 1571 $matchcount++;
34d52ad7 1572 $correct[$subquestionid] = true;
54a67a59 1573 } else {
34d52ad7 1574 $correct[$subquestionid] = false;
54a67a59 1575 }
34d52ad7 1576 $response[$subquestionid] = $subanswerid;
54a67a59 1577 }
1578
34d52ad7 1579 $grade = $question->grade * $matchcount / $totalcount;
54a67a59 1580
1581 break;
1582
1583 case RANDOMSAMATCH:
95dbc030 1584 $bestanswer = array();
1585 foreach ($answers as $answer) { // Loop through them all looking for correct answers
1586 if (empty($bestanswer[$answer->question])) {
1587 $bestanswer[$answer->question] = 0;
1588 $correct[$answer->question] = "";
1589 }
1590 if ($answer->fraction > $bestanswer[$answer->question]) {
1591 $bestanswer[$answer->question] = $answer->fraction;
1592 $correct[$answer->question] = $answer->answer;
1593 }
1594 }
1595 $answerfraction = 1.0 / (float) count($question->answer);
1596 foreach ($question->answer as $questionanswer) { // For each random answered question
1597 $rqarr = explode('-', $questionanswer); // Extract question/answer.
1598 $rquestion = $rqarr[0];
1599 $ranswer = $rqarr[1];
1600 $response[$rquestion] = $questionanswer;
1601 if (isset($answers[$ranswer])) { // If the answer exists in the list
1602 $answer = $answers[$ranswer];
1603 $feedback[$rquestion] = $answer->feedback;
1604 if ($answer->question == $rquestion) { // Check that this answer matches the question
34d52ad7 1605 $grade += (float)$answer->fraction * $question->grade * $answerfraction;
95dbc030 1606 }
1607 }
1608 }
8db3eadd 1609 break;
a5e1f35c 1610
a5e1f35c 1611 }
34d52ad7 1612
1613 if (!empty($randomquestion)) { // This question has been randomly chosen
1614 $question = $randomquestion; // Restore the question->id
1615 unset($randomquestion);
1616 }
1617
a5e1f35c 1618 if ($grade < 0.0) { // No negative grades
1619 $grade = 0.0;
1620 }
10b9291c 1621
3a50203f 1622 $result->grades[$question->id] = round($grade, 2);
a5e1f35c 1623 $result->sumgrades += $grade;
1624 $result->feedback[$question->id] = $feedback;
a8a372cc 1625 $result->response[$question->id] = $response;
8db3eadd 1626 $result->correct[$question->id] = $correct;
a5e1f35c 1627 }
1628
8d94f5a0 1629 $fraction = (float)($result->sumgrades / $quiz->sumgrades);
1630 $result->percentage = format_float($fraction * 100.0);
1631 $result->grade = format_float($fraction * $quiz->grade);
3a50203f 1632 $result->sumgrades = round($result->sumgrades, 2);
a5e1f35c 1633
1634 return $result;
1635}
6d86b5dc 1636
1637
49220fa7 1638function quiz_save_question_options($question) {
1639/// Given some question info and some data about the the answers
1640/// this function parses, organises and saves the question
1641/// It is used by question.php when saving new data from a
1642/// form, and also by import.php when importing questions
77cff589 1643///
1644/// If this is an update, and old answers already exist, then
1645/// these are overwritten using an update(). To do this, it
1646/// it is assumed that the IDs in quiz_answers are in the same
1647/// sort order as the new answers being saved. This should always
1648/// be true, but it's something to keep in mind if fiddling with
1649/// question.php
49220fa7 1650///
1651/// Returns $result->error or $result->notice
a5e1f35c 1652
49220fa7 1653 switch ($question->qtype) {
1654 case SHORTANSWER:
77cff589 1655
e1122620 1656 if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
1657 $oldanswers = array();
1658 }
49220fa7 1659
1660 $answers = array();
1661 $maxfraction = -1;
1662
1663 // Insert all the new answers
1664 foreach ($question->answer as $key => $dataanswer) {
1665 if ($dataanswer != "") {
77cff589 1666 if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
1667 $answer = $oldanswer;
1668 $answer->answer = $dataanswer;
1669 $answer->fraction = $question->fraction[$key];
1670 $answer->feedback = $question->feedback[$key];
1671 if (!update_record("quiz_answers", $answer)) {
1672 $result->error = "Could not update quiz answer! (id=$answer->id)";
1673 return $result;
1674 }
1675 } else { // This is a completely new answer
1676 unset($answer);
1677 $answer->answer = $dataanswer;
1678 $answer->question = $question->id;
1679 $answer->fraction = $question->fraction[$key];
1680 $answer->feedback = $question->feedback[$key];
1681 if (!$answer->id = insert_record("quiz_answers", $answer)) {
1682 $result->error = "Could not insert quiz answer!";
1683 return $result;
1684 }
49220fa7 1685 }
1686 $answers[] = $answer->id;
1687 if ($question->fraction[$key] > $maxfraction) {
1688 $maxfraction = $question->fraction[$key];
1689 }
1690 }
1691 }
1692
77cff589 1693 if ($options = get_record("quiz_shortanswer", "question", $question->id)) {
1694 $options->answers = implode(",",$answers);
1695 $options->usecase = $question->usecase;
1696 if (!update_record("quiz_shortanswer", $options)) {
1697 $result->error = "Could not update quiz shortanswer options! (id=$options->id)";
1698 return $result;
1699 }
1700 } else {
1701 unset($options);
1702 $options->question = $question->id;
1703 $options->answers = implode(",",$answers);
1704 $options->usecase = $question->usecase;
1705 if (!insert_record("quiz_shortanswer", $options)) {
1706 $result->error = "Could not insert quiz shortanswer options!";
1707 return $result;
1708 }
49220fa7 1709 }
1710
1711 /// Perform sanity checks on fractional grades
1712 if ($maxfraction != 1) {
1713 $maxfraction = $maxfraction * 100;
1714 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
1715 return $result;
1716 }
1717 break;
77cff589 1718
361f649d 1719 case NUMERICAL: // Note similarities to SHORTANSWER
1720
1721 if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
1722 $oldanswers = array();
1723 }
1724
1725 $answers = array();
1726 $maxfraction = -1;
1727
1728 // Insert all the new answers
1729 foreach ($question->answer as $key => $dataanswer) {
1730 if ($dataanswer != "") {
1731 if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
1732 $answer = $oldanswer;
1733 $answer->answer = $dataanswer;
1734 $answer->fraction = $question->fraction[$key];
1735 $answer->feedback = $question->feedback[$key];
1736 if (!update_record("quiz_answers", $answer)) {
1737 $result->error = "Could not update quiz answer! (id=$answer->id)";
1738 return $result;
1739 }
1740 } else { // This is a completely new answer
1741 unset($answer);
1742 $answer->answer = $dataanswer;
1743 $answer->question = $question->id;
1744 $answer->fraction = $question->fraction[$key];
1745 $answer->feedback = $question->feedback[$key];
1746 if (!$answer->id = insert_record("quiz_answers", $answer)) {
1747 $result->error = "Could not insert quiz answer!";
1748 return $result;
1749 }
1750 }
1751 $answers[] = $answer->id;
1752 if ($question->fraction[$key] > $maxfraction) {
1753 $maxfraction = $question->fraction[$key];
1754 }
1755
1756 if ($options = get_record("quiz_numerical", "answer", $answer->id)) {
1757 $options->min= $question->min[$key];
1758 $options->max= $question->max[$key];
1759 if (!update_record("quiz_numerical", $options)) {
1760 $result->error = "Could not update quiz numerical options! (id=$options->id)";
1761 return $result;
1762 }
1763 } else { // completely new answer
1764 unset($options);
9c026610 1765 $options->question = $question->id;
1766 $options->answer = $answer->id;
1767 $options->min = $question->min[$key];
1768 $options->max = $question->max[$key];
361f649d 1769 if (!insert_record("quiz_numerical", $options)) {
1770 $result->error = "Could not insert quiz numerical options!";
1771 return $result;
1772 }
1773 }
1774 }
1775 }
1776
1777 /// Perform sanity checks on fractional grades
1778 if ($maxfraction != 1) {
1779 $maxfraction = $maxfraction * 100;
1780 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
1781 return $result;
1782 }
1783 break;
77cff589 1784
1785
49220fa7 1786 case TRUEFALSE:
77cff589 1787
e1122620 1788 if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
1789 $oldanswers = array();
1790 }
77cff589 1791
1792 if ($true = array_shift($oldanswers)) { // Existing answer, so reuse it
4d01ada3 1793 $true->answer = get_string("true", "quiz");
77cff589 1794 $true->fraction = $question->answer;
1795 $true->feedback = $question->feedbacktrue;
1796 if (!update_record("quiz_answers", $true)) {
1797 $result->error = "Could not update quiz answer \"true\")!";
1798 return $result;
1799 }
1800 } else {
1801 unset($true);
4d01ada3 1802 $true->answer = get_string("true", "quiz");
77cff589 1803 $true->question = $question->id;
1804 $true->fraction = $question->answer;
1805 $true->feedback = $question->feedbacktrue;
1806 if (!$true->id = insert_record("quiz_answers", $true)) {
1807 $result->error = "Could not insert quiz answer \"true\")!";
1808 return $result;
1809 }
49220fa7 1810 }
1811
77cff589 1812 if ($false = array_shift($oldanswers)) { // Existing answer, so reuse it
4d01ada3 1813 $false->answer = get_string("false", "quiz");
77cff589 1814 $false->fraction = 1 - (int)$question->answer;
1815 $false->feedback = $question->feedbackfalse;
1816 if (!update_record("quiz_answers", $false)) {
1817 $result->error = "Could not insert quiz answer \"false\")!";
1818 return $result;
1819 }
1820 } else {
1821 unset($false);
4d01ada3 1822 $false->answer = get_string("false", "quiz");
77cff589 1823 $false->question = $question->id;
1824 $false->fraction = 1 - (int)$question->answer;
1825 $false->feedback = $question->feedbackfalse;
1826 if (!$false->id = insert_record("quiz_answers", $false)) {
1827 $result->error = "Could not insert quiz answer \"false\")!";
1828 return $result;
1829 }
49220fa7 1830 }
1831
77cff589 1832 if ($options = get_record("quiz_truefalse", "question", $question->id)) {
1833 // No need to do anything, since the answer IDs won't have changed
1834 // But we'll do it anyway, just for robustness
1835 $options->trueanswer = $true->id;
1836 $options->falseanswer = $false->id;
1837 if (!update_record("quiz_truefalse", $options)) {
1838 $result->error = "Could not update quiz truefalse options! (id=$options->id)";
1839 return $result;
1840 }
1841 } else {
1842 unset($options);
1843 $options->question = $question->id;
1844 $options->trueanswer = $true->id;
1845 $options->falseanswer = $false->id;
1846 if (!insert_record("quiz_truefalse", $options)) {
1847 $result->error = "Could not insert quiz truefalse options!";
1848 return $result;
1849 }
49220fa7 1850 }
1851 break;
77cff589 1852
1853
49220fa7 1854 case MULTICHOICE:
77cff589 1855
e1122620 1856 if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
1857 $oldanswers = array();
1858 }
49220fa7 1859
1860 $totalfraction = 0;
1861 $maxfraction = -1;
1862
1863 $answers = array();
1864
1865 // Insert all the new answers
1866 foreach ($question->answer as $key => $dataanswer) {
1867 if ($dataanswer != "") {
77cff589 1868 if ($answer = array_shift($oldanswers)) { // Existing answer, so reuse it
1869 $answer->answer = $dataanswer;
1870 $answer->fraction = $question->fraction[$key];
1871 $answer->feedback = $question->feedback[$key];
1872 if (!update_record("quiz_answers", $answer)) {
1873 $result->error = "Could not update quiz answer! (id=$answer->id)";
1874 return $result;
1875 }
1876 } else {
1877 unset($answer);
1878 $answer->answer = $dataanswer;
1879 $answer->question = $question->id;
1880 $answer->fraction = $question->fraction[$key];
1881 $answer->feedback = $question->feedback[$key];
1882 if (!$answer->id = insert_record("quiz_answers", $answer)) {
1883 $result->error = "Could not insert quiz answer! ";
1884 return $result;
1885 }
49220fa7 1886 }
1887 $answers[] = $answer->id;
1888
1889 if ($question->fraction[$key] > 0) { // Sanity checks
1890 $totalfraction += $question->fraction[$key];
1891 }
1892 if ($question->fraction[$key] > $maxfraction) {
1893 $maxfraction = $question->fraction[$key];
1894 }
1895 }
1896 }
1897
77cff589 1898 if ($options = get_record("quiz_multichoice", "question", $question->id)) {
1899 $options->answers = implode(",",$answers);
1900 $options->single = $question->single;
1901 if (!update_record("quiz_multichoice", $options)) {
1902 $result->error = "Could not update quiz multichoice options! (id=$options->id)";
1903 return $result;
1904 }
1905 } else {
1906 unset($options);
1907 $options->question = $question->id;
1908 $options->answers = implode(",",$answers);
1909 $options->single = $question->single;
1910 if (!insert_record("quiz_multichoice", $options)) {
1911 $result->error = "Could not insert quiz multichoice options!";
1912 return $result;
1913 }
49220fa7 1914 }
1915
1916 /// Perform sanity checks on fractional grades
1917 if ($options->single) {
1918 if ($maxfraction != 1) {
1919 $maxfraction = $maxfraction * 100;
1920 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
1921 return $result;
1922 }
1923 } else {
1924 $totalfraction = round($totalfraction,2);
1925 if ($totalfraction != 1) {
1926 $totalfraction = $totalfraction * 100;
1927 $result->notice = get_string("fractionsaddwrong", "quiz", $totalfraction);
1928 return $result;
1929 }
1930 }
1931 break;
95dbc030 1932
54a67a59 1933 case MATCH:
77cff589 1934
e1122620 1935 if (!$oldsubquestions = get_records("quiz_match_sub", "question", $question->id, "id ASC")) {
1936 $oldsubquestions = array();
1937 }
54a67a59 1938
1939 $subquestions = array();
1940
1941 // Insert all the new question+answer pairs
1942 foreach ($question->subquestions as $key => $questiontext) {
1943 $answertext = $question->subanswers[$key];
1944 if (!empty($questiontext) and !empty($answertext)) {
77cff589 1945 if ($subquestion = array_shift($oldsubquestions)) { // Existing answer, so reuse it
1946 $subquestion->questiontext = $questiontext;
1947 $subquestion->answertext = $answertext;
1948 if (!update_record("quiz_match_sub", $subquestion)) {
1949 $result->error = "Could not insert quiz match subquestion! (id=$subquestion->id)";
1950 return $result;
1951 }
1952 } else {
1953 unset($subquestion);
1954 $subquestion->question = $question->id;
1955 $subquestion->questiontext = $questiontext;
1956 $subquestion->answertext = $answertext;
1957 if (!$subquestion->id = insert_record("quiz_match_sub", $subquestion)) {
1958 $result->error = "Could not insert quiz match subquestion!";
1959 return $result;
1960 }
54a67a59 1961 }
1962 $subquestions[] = $subquestion->id;
1963 }
1964 }
1965
1966 if (count($subquestions) < 3) {
1967 $result->notice = get_string("notenoughsubquestions", "quiz");
1968 return $result;
1969 }
1970
77cff589 1971 if ($options = get_record("quiz_match", "question", $question->id)) {
1972 $options->subquestions = implode(",",$subquestions);
1973 if (!update_record("quiz_match", $options)) {
1974 $result->error = "Could not update quiz match options! (id=$options->id)";
1975 return $result;
1976 }
1977 } else {
1978 unset($options);
1979 $options->question = $question->id;
1980 $options->subquestions = implode(",",$subquestions);
1981 if (!insert_record("quiz_match", $options)) {
1982 $result->error = "Could not insert quiz match options!";
1983 return $result;
1984 }
54a67a59 1985 }
1986
1987 break;
1988
77cff589 1989
54a67a59 1990 case RANDOMSAMATCH:
95dbc030 1991 $options->question = $question->id;
1992 $options->choose = $question->choose;
54a67a59 1993 if ($existing = get_record("quiz_randomsamatch", "question", $options->question)) {
95dbc030 1994 $options->id = $existing->id;
54a67a59 1995 if (!update_record("quiz_randomsamatch", $options)) {
1996 $result->error = "Could not update quiz randomsamatch options!";
95dbc030 1997 return $result;
1998 }
1999 } else {
54a67a59 2000 if (!insert_record("quiz_randomsamatch", $options)) {
2001 $result->error = "Could not insert quiz randomsamatch options!";
95dbc030 2002 return $result;
2003 }
2004 }
2005 break;
2006
34d52ad7 2007 case RANDOM:
2008 break;
2009
401c8de6 2010 case DESCRIPTION:
2011 break;
2012
49220fa7 2013 default:
2014 $result->error = "Unsupported question type ($question->qtype)!";
2015 return $result;
2016 break;
2017 }
2018 return true;
2019}
2020
2021
29d5d0b4 2022function quiz_remove_unwanted_questions(&$questions, $quiz) {
40b1a221 2023/// Given an array of questions, and a list of question IDs,
2024/// this function removes unwanted questions from the array
2025/// Used by review.php to counter changing quizzes
29d5d0b4 2026
2027 $quizquestions = array();
2028 $quizids = explode(",", $quiz->questions);
2029 foreach ($quizids as $quizid) {
2030 $quizquestions[$quizid] = true;
2031 }
2032 foreach ($questions as $key => $question) {
2033 if (!isset($quizquestions[$question->id])) {
2034 unset($questions[$key]);
2035 }
2036 }
2037}
2038
95dbc030 2039
2040
730fd187 2041?>