If group isn't detected when restoring events, default it to 0
[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");
8b439f8c 24define("MULTIANSWER", "9");
54a67a59 25
26$QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz"),
27 TRUEFALSE => get_string("truefalse", "quiz"),
28 SHORTANSWER => get_string("shortanswer", "quiz"),
361f649d 29 NUMERICAL => get_string("numerical", "quiz"),
54a67a59 30 MATCH => get_string("match", "quiz"),
361f649d 31 DESCRIPTION => get_string("description", "quiz"),
34d52ad7 32 RANDOM => get_string("random", "quiz"),
8b439f8c 33 RANDOMSAMATCH => get_string("randomsamatch", "quiz"),
34 MULTIANSWER => get_string("multianswer", "quiz")
361f649d 35 );
a5e1f35c 36
49220fa7 37
5325f8b8 38define("QUIZ_PICTURE_MAX_HEIGHT", "600"); // Not currently implemented
39define("QUIZ_PICTURE_MAX_WIDTH", "600"); // Not currently implemented
a5e1f35c 40
29fb5974 41define("QUIZ_MAX_NUMBER_ANSWERS", "10");
54a67a59 42
a5e1f35c 43/// FUNCTIONS ///////////////////////////////////////////////////////////////////
730fd187 44
45function quiz_add_instance($quiz) {
a5e1f35c 46/// Given an object containing all the necessary data,
47/// (defined by the form in mod.html) this function
48/// will create a new instance and return the id number
49/// of the new instance.
730fd187 50
49dcdd18 51 $quiz->created = time();
730fd187 52 $quiz->timemodified = time();
49dcdd18 53 $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday,
c04c41c7 54 $quiz->openhour, $quiz->openminute, 0);
49dcdd18 55 $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday,
c04c41c7 56 $quiz->closehour, $quiz->closeminute, 0);
730fd187 57
7bd1aa1d 58 if (!$quiz->id = insert_record("quiz", $quiz)) {
59 return false; // some error occurred
60 }
61
10b9291c 62 // The grades for every question in this quiz are stored in an array
7bd1aa1d 63 if ($quiz->grades) {
64 foreach ($quiz->grades as $question => $grade) {
401c8de6 65 if ($question) {
8d94f5a0 66 unset($questiongrade);
67 $questiongrade->quiz = $quiz->id;
68 $questiongrade->question = $question;
69 $questiongrade->grade = $grade;
70 if (!insert_record("quiz_question_grades", $questiongrade)) {
71 return false;
72 }
7bd1aa1d 73 }
74 }
75 }
730fd187 76
7bd1aa1d 77 return $quiz->id;
730fd187 78}
79
80
81function quiz_update_instance($quiz) {
a5e1f35c 82/// Given an object containing all the necessary data,
83/// (defined by the form in mod.html) this function
84/// will update an existing instance with new data.
730fd187 85
86 $quiz->timemodified = time();
49dcdd18 87 $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday,
c04c41c7 88 $quiz->openhour, $quiz->openminute, 0);
49dcdd18 89 $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday,
c04c41c7 90 $quiz->closehour, $quiz->closeminute, 0);
730fd187 91 $quiz->id = $quiz->instance;
92
7bd1aa1d 93 if (!update_record("quiz", $quiz)) {
94 return false; // some error occurred
95 }
730fd187 96
7bd1aa1d 97
10b9291c 98 // The grades for every question in this quiz are stored in an array
7bd1aa1d 99 // Insert or update records as appropriate
100
101 $existing = get_records("quiz_question_grades", "quiz", $quiz->id, "", "question,grade,id");
102
103 if ($quiz->grades) {
104 foreach ($quiz->grades as $question => $grade) {
401c8de6 105 if ($question) {
8d94f5a0 106 unset($questiongrade);
107 $questiongrade->quiz = $quiz->id;
108 $questiongrade->question = $question;
109 $questiongrade->grade = $grade;
110 if (isset($existing[$question])) {
111 if ($existing[$question]->grade != $grade) {
112 $questiongrade->id = $existing[$question]->id;
113 if (!update_record("quiz_question_grades", $questiongrade)) {
114 return false;
115 }
116 }
117 } else {
118 if (!insert_record("quiz_question_grades", $questiongrade)) {
7bd1aa1d 119 return false;
120 }
121 }
7bd1aa1d 122 }
123 }
124 }
125
126 return true;
730fd187 127}
128
129
130function quiz_delete_instance($id) {
a5e1f35c 131/// Given an ID of an instance of this module,
132/// this function will permanently delete the instance
133/// and any data that depends on it.
730fd187 134
135 if (! $quiz = get_record("quiz", "id", "$id")) {
136 return false;
137 }
138
139 $result = true;
140
464edd6d 141 if ($attempts = get_records("quiz_attempts", "quiz", "$quiz->id")) {
10b9291c 142 foreach ($attempts as $attempt) {
143 if (! delete_records("quiz_responses", "attempt", "$attempt->id")) {
144 $result = false;
145 }
146 }
147 }
148
149 if (! delete_records("quiz_attempts", "quiz", "$quiz->id")) {
150 $result = false;
151 }
152
153 if (! delete_records("quiz_grades", "quiz", "$quiz->id")) {
154 $result = false;
155 }
156
157 if (! delete_records("quiz_question_grades", "quiz", "$quiz->id")) {
158 $result = false;
159 }
730fd187 160
161 if (! delete_records("quiz", "id", "$quiz->id")) {
162 $result = false;
163 }
164
165 return $result;
166}
167
b2d594c8 168function quiz_delete_course($course) {
169/// Given a course object, this function will clean up anything that
170/// would be leftover after all the instances were deleted
464edd6d 171/// In this case, all non-publish quiz categories and questions
b2d594c8 172
464edd6d 173 if ($categories = get_records_select("quiz_categories", "course = '$course->id' AND publish = '0'")) {
b2d594c8 174 foreach ($categories as $category) {
175 if ($questions = get_records("quiz_questions", "category", $category->id)) {
176 foreach ($questions as $question) {
177 delete_records("quiz_answers", "question", $question->id);
178 delete_records("quiz_match", "question", $question->id);
179 delete_records("quiz_match_sub", "question", $question->id);
180 delete_records("quiz_multianswers", "question", $question->id);
181 delete_records("quiz_multichoice", "question", $question->id);
182 delete_records("quiz_numerical", "question", $question->id);
183 delete_records("quiz_randommatch", "question", $question->id);
184 delete_records("quiz_responses", "question", $question->id);
185 delete_records("quiz_shortanswer", "question", $question->id);
186 delete_records("quiz_truefalse", "question", $question->id);
187 }
188 delete_records("quiz_questions", "category", $category->id);
189 }
190 }
191 return delete_records("quiz_categories", "course", $course->id);
192 }
193 return true;
194}
195
196
730fd187 197function quiz_user_outline($course, $user, $mod, $quiz) {
a5e1f35c 198/// Return a small object with summary information about what a
199/// user has done with a given particular instance of this module
200/// Used for user activity reports.
201/// $return->time = the time they did it
202/// $return->info = a short text description
ebc3bd2b 203 if ($grade = get_record("quiz_grades", "userid", $user->id, "quiz", $quiz->id)) {
98092498 204
205 if ($grade->grade) {
206 $result->info = get_string("grade").": $grade->grade";
207 }
208 $result->time = $grade->timemodified;
209 return $result;
210 }
211 return NULL;
730fd187 212
213 return $return;
214}
215
216function quiz_user_complete($course, $user, $mod, $quiz) {
a5e1f35c 217/// Print a detailed representation of what a user has done with
218/// a given particular instance of this module, for user activity reports.
730fd187 219
220 return true;
221}
222
730fd187 223function quiz_cron () {
a5e1f35c 224/// Function to be run periodically according to the moodle cron
225/// This function searches for things that need to be done, such
226/// as sending out mail, toggling flags etc ...
730fd187 227
228 global $CFG;
229
230 return true;
231}
232
d0ac6bc2 233function quiz_grades($quizid) {
858deff0 234/// Must return an array of grades, indexed by user, and a max grade.
235
ed1daaa9 236 $quiz = get_record("quiz", "id", $quizid);
237 if (empty($quiz) or empty($quiz->grade)) {
238 return NULL;
239 }
240
ebc3bd2b 241 $return->grades = get_records_menu("quiz_grades", "quiz", $quizid, "", "userid,grade");
858deff0 242 $return->maxgrade = get_field("quiz", "grade", "id", "$quizid");
243 return $return;
d0ac6bc2 244}
245
d061d883 246function quiz_get_participants($quizid) {
247/// Returns an array of users who have data in a given quiz
248/// (users with records in quiz_attempts, students)
249
250 global $CFG;
251
252 return get_records_sql("SELECT DISTINCT u.*
253 FROM {$CFG->prefix}user u,
254 {$CFG->prefix}quiz_attempts a
255 WHERE a.quiz = '$quizid' and
256 u.id = a.userid");
257}
730fd187 258
bdc23be0 259/// SQL FUNCTIONS ////////////////////////////////////////////////////////////////////
260
261function quiz_move_questions($category1, $category2) {
262 global $CFG;
263 return execute_sql("UPDATE {$CFG->prefix}quiz_questions
264 SET category = '$category2'
265 WHERE category = '$category1'",
266 false);
267}
268
269function quiz_get_question_grades($quizid, $questionlist) {
270 global $CFG;
271
272 return get_records_sql("SELECT question,grade
273 FROM {$CFG->prefix}quiz_question_grades
274 WHERE quiz = '$quizid'
275 AND question IN ($questionlist)");
276}
277
34d52ad7 278function quiz_get_random_categories($questionlist) {
279/// Given an array of questions, this function looks for random
280/// questions among them and returns a list of categories with
281/// an associated count of random questions for each.
282
283 global $CFG;
284
285 return get_records_sql_menu("SELECT category,count(*)
286 FROM {$CFG->prefix}quiz_questions
287 WHERE id IN ($questionlist)
288 AND qtype = '".RANDOM."'
289 GROUP BY category ");
290}
291
bdc23be0 292function quiz_get_grade_records($quiz) {
293/// Gets all info required to display the table of quiz results
294/// for report.php
295 global $CFG;
296
297 return get_records_sql("SELECT qg.*, u.firstname, u.lastname, u.picture
298 FROM {$CFG->prefix}quiz_grades qg,
299 {$CFG->prefix}user u
300 WHERE qg.quiz = '$quiz->id'
ebc3bd2b 301 AND qg.userid = u.id");
bdc23be0 302}
303
8b439f8c 304function quiz_get_answers($question, $answerids=NULL) {
34d52ad7 305// Given a question, returns the correct answers for a given question
bdc23be0 306 global $CFG;
95dbc030 307
8b439f8c 308 if (empty($answerids)) {
309 $answeridconstraint = '';
310 } else {
311 $answeridconstraint = " AND a.id IN ($answerids) ";
312 }
313
a2fe7cc0 314 switch ($question->qtype) {
95dbc030 315 case SHORTANSWER: // Could be multiple answers
34d52ad7 316 return get_records_sql("SELECT a.*, sa.usecase
bdc23be0 317 FROM {$CFG->prefix}quiz_shortanswer sa,
34d52ad7 318 {$CFG->prefix}quiz_answers a
bdc23be0 319 WHERE sa.question = '$question->id'
8b439f8c 320 AND sa.question = a.question "
321 . $answeridconstraint);
bdc23be0 322
95dbc030 323 case TRUEFALSE: // Should be always two answers
34d52ad7 324 return get_records("quiz_answers", "question", $question->id);
bdc23be0 325
95dbc030 326 case MULTICHOICE: // Should be multiple answers
34d52ad7 327 return get_records_sql("SELECT a.*, mc.single
bdc23be0 328 FROM {$CFG->prefix}quiz_multichoice mc,
34d52ad7 329 {$CFG->prefix}quiz_answers a
bdc23be0 330 WHERE mc.question = '$question->id'
8b439f8c 331 AND mc.question = a.question "
332 . $answeridconstraint);
bdc23be0 333
54a67a59 334 case MATCH:
34d52ad7 335 return get_records("quiz_match_sub", "question", $question->id);
54a67a59 336
337 case RANDOMSAMATCH: // Could be any of many answers, return them all
34d52ad7 338 return get_records_sql("SELECT a.*
95dbc030 339 FROM {$CFG->prefix}quiz_questions q,
34d52ad7 340 {$CFG->prefix}quiz_answers a
95dbc030 341 WHERE q.category = '$question->category'
342 AND q.qtype = ".SHORTANSWER."
34d52ad7 343 AND q.id = a.question ");
95dbc030 344
361f649d 345 case NUMERICAL: // Logical support for multiple answers
346 return get_records_sql("SELECT a.*, n.min, n.max
347 FROM {$CFG->prefix}quiz_numerical n,
348 {$CFG->prefix}quiz_answers a
349 WHERE a.question = '$question->id'
8b439f8c 350 AND n.answer = a.id "
351 . $answeridconstraint);
361f649d 352
44fc346f 353 case DESCRIPTION:
354 return true; // there are no answers for description
355
356 case RANDOM:
357 return quiz_get_answers
358 (get_record('quiz_questions', 'id', $question->random));
361f649d 359
8b439f8c 360 case MULTIANSWER: // Includes subanswers
52a88a87 361 $answers = array();
362
8b439f8c 363 $virtualquestion->id = $question->id;
364
52a88a87 365 if ($multianswers = get_records('quiz_multianswers', 'question', $question->id)) {
366 foreach ($multianswers as $multianswer) {
367 $virtualquestion->qtype = $multianswer->answertype;
368 // Recursive call for subanswers
369 $multianswer->subanswers = quiz_get_answers($virtualquestion, $multianswer->answers);
370 $answers[] = $multianswer;
371 }
8b439f8c 372 }
373 return $answers;
374
bdc23be0 375 default:
376 return false;
377 }
378}
379
380
586b2c82 381function quiz_get_attempt_responses($attempt) {
bdc23be0 382// Given an attempt object, this function gets all the
383// stored responses and returns them in a format suitable
384// for regrading using quiz_grade_attempt_results()
385 global $CFG;
386
5ca03851 387 if (!$responses = get_records_sql("SELECT q.id, q.qtype, q.category, q.questiontext,
388 q.defaultgrade, q.image, r.answer
bdc23be0 389 FROM {$CFG->prefix}quiz_responses r,
390 {$CFG->prefix}quiz_questions q
391 WHERE r.attempt = '$attempt->id'
34d52ad7 392 AND q.id = r.question")) {
bdc23be0 393 notify("Could not find any responses for that attempt!");
394 return false;
395 }
396
34d52ad7 397
398 foreach ($responses as $key => $response) {
399 if ($response->qtype == RANDOM) {
400 $responses[$key]->random = $response->answer;
34d52ad7 401 $responses[$response->answer]->delete = true;
0a660b03 402
403 $realanswer = $responses[$response->answer]->answer;
404
405 if (is_array($realanswer)) {
406 $responses[$key]->answer = $realanswer;
407 } else {
408 $responses[$key]->answer = explode(",", $realanswer);
409 }
410
3960a44b 411 } else if ($response->qtype == NUMERICAL or $response->qtype == SHORTANSWER) {
412 $responses[$key]->answer = array($response->answer);
34d52ad7 413 } else {
414 $responses[$key]->answer = explode(",",$response->answer);
415 }
416 }
bdc23be0 417 foreach ($responses as $key => $response) {
34d52ad7 418 if (!empty($response->delete)) {
419 unset($responses[$key]);
420 }
bdc23be0 421 }
422
423 return $responses;
424}
425
426
a740ff66 427function get_list_of_questions($questionlist) {
428/// Returns an ordered list of questions, including course for each
429
430 global $CFG;
431
432 return get_records_sql("SELECT q.*,c.course
433 FROM {$CFG->prefix}quiz_questions q,
434 {$CFG->prefix}quiz_categories c
435 WHERE q.id in ($questionlist)
436 AND q.category = c.id");
437}
bdc23be0 438
730fd187 439//////////////////////////////////////////////////////////////////////////////////////
a5e1f35c 440/// Any other quiz functions go here. Each of them must have a name that
441/// starts with quiz_
730fd187 442
a8a372cc 443function quiz_print_comment($text) {
444 global $THEME;
445
a0beda7a 446 echo "<span class=\"feedbacktext\">".format_text($text, true, false)."</span>";
a8a372cc 447}
448
8db3eadd 449function quiz_print_correctanswer($text) {
450 global $THEME;
451
d7512435 452 echo "<p align=\"right\"><span class=\"highlight\">$text</span></p>";
8db3eadd 453}
454
34d52ad7 455function quiz_print_question_icon($question, $editlink=true) {
c74a0ca5 456// Prints a question icon
cc3b8c75 457
458 global $QUIZ_QUESTION_TYPE;
459
34d52ad7 460 if ($editlink) {
361f649d 461 echo "<a href=\"question.php?id=$question->id\" title=\"".$QUIZ_QUESTION_TYPE[$question->qtype]."\">";
34d52ad7 462 }
a2fe7cc0 463 switch ($question->qtype) {
c74a0ca5 464 case SHORTANSWER:
89b8d337 465 echo '<img border="0" height="16" width="16" src="pix/sa.gif">';
c74a0ca5 466 break;
467 case TRUEFALSE:
89b8d337 468 echo '<img border="0" height="16" width="16" src="pix/tf.gif">';
c74a0ca5 469 break;
470 case MULTICHOICE:
89b8d337 471 echo '<img border="0" height="16" width="16" src="pix/mc.gif">';
c74a0ca5 472 break;
473 case RANDOM:
89b8d337 474 echo '<img border="0" height="16" width="16" src="pix/rs.gif">';
c74a0ca5 475 break;
54a67a59 476 case MATCH:
89b8d337 477 echo '<img border="0" height="16" width="16" src="pix/ma.gif">';
54a67a59 478 break;
479 case RANDOMSAMATCH:
89b8d337 480 echo '<img border="0" height="16" width="16" src="pix/rm.gif">';
95dbc030 481 break;
401c8de6 482 case DESCRIPTION:
89b8d337 483 echo '<img border="0" height="16" width="16" src="pix/de.gif">';
361f649d 484 break;
485 case NUMERICAL:
89b8d337 486 echo '<img border="0" height="16" width="16" src="pix/nu.gif">';
401c8de6 487 break;
8b439f8c 488 case MULTIANSWER:
89b8d337 489 echo '<img border="0" height="16" width="16" src="pix/mu.gif">';
8b439f8c 490 break;
c74a0ca5 491 }
34d52ad7 492 if ($editlink) {
361f649d 493 echo "</a>\n";
34d52ad7 494 }
c74a0ca5 495}
496
fe98ea90 497function quiz_print_possible_question_image($quizid, $question) {
498// Includes the question image is there is one
a8a372cc 499
fe98ea90 500 global $CFG;
501
502 if ($question->image) {
89b8d337 503 echo '<img border="0" src="';
fe98ea90 504
89b8d337 505 if (substr(strtolower($question->image), 0, 7) == 'http://') {
fe98ea90 506 echo $question->image;
507
508 } else if ($CFG->slasharguments) { // Use this method if possible for better caching
509 echo "$CFG->wwwroot/mod/quiz/quizfile.php/$quizid/$question->id/$question->image";
510
511 } else {
512 echo "$CFG->wwwroot/mod/quiz/quizfile.php?file=/$quizid/$question->id/$question->image";
513 }
89b8d337 514 echo '" />';
fe98ea90 515
516 }
517}
34d52ad7 518
fe98ea90 519function quiz_print_question($number, $question, $grade, $quizid,
34d52ad7 520 $feedback=NULL, $response=NULL, $actualgrade=NULL, $correct=NULL,
0c29498e 521 $realquestion=NULL, $shuffleanswers=false, $showgrades=true, $courseid=0) {
34d52ad7 522
523/// Prints a quiz question, any format
524/// $question is provided as an object
14d8c0b4 525
c18490b8 526 $question->questiontextformat = isset($question->questiontextformat) ? $question->questiontextformat : NULL;
263cff8b 527
401c8de6 528 if ($question->qtype == DESCRIPTION) { // Special case question - has no answers etc
529 echo '<p align="center">';
5cdb8a85 530 echo format_text($question->questiontext, $question->questiontextformat, NULL, $courseid);
fe98ea90 531 quiz_print_possible_question_image($quizid, $question);
401c8de6 532 echo '</p>';
533 return true;
534 }
535
a2fe7cc0 536 if (empty($actualgrade)) {
537 $actualgrade = 0;
538 }
539
14d8c0b4 540 $stranswer = get_string("answer", "quiz");
541 $strmarks = get_string("marks", "quiz");
542
89b8d337 543 echo '<table width="100%" cellspacing="10">';
544 echo '<tr><td nowrap="nowrap" width="100" valign="top">';
e0971655 545 echo '<p align="center"><b>' . $number . '</b></p>';
081bf74f 546 if ($showgrades) {
547 if ($feedback or $response) {
d7512435 548 echo "<p align=\"center\"><font size=\"1\">$strmarks: $actualgrade/$grade</font></p>";
081bf74f 549 } else {
d7512435 550 echo "<p align=\"center\"><font size=\"1\">$grade $strmarks</font></p>";
081bf74f 551 }
a8a372cc 552 }
14d8c0b4 553 print_spacer(1,100);
5a24a018 554
fe98ea90 555 if (isset($question->recentlyadded) and $question->recentlyadded) {
89b8d337 556 echo '</td><td valign="top" align="right">';
5a24a018 557 // Notify the user of this recently added question
558 echo '<font color="red">';
559 echo get_string('recentlyaddedquestion', 'quiz');
560 echo '</font>';
89b8d337 561 echo '</td></tr><tr><td></td><td valign="top">';
5a24a018 562
563 } else { // The normal case
89b8d337 564 echo '</td><td valign="top">';
5a24a018 565 }
566
34d52ad7 567
568 if (empty($realquestion)) {
569 $realquestion->id = $question->id;
570 } else { // Add a marker to connect this question to the actual random parent
d7512435 571 echo "<input type=\"hidden\" name=\"q{$realquestion->id}rq$question->id\" value=\"x\" />\n";
34d52ad7 572 }
14d8c0b4 573
a2fe7cc0 574 switch ($question->qtype) {
34d52ad7 575
a5e1f35c 576 case SHORTANSWER:
361f649d 577 case NUMERICAL:
5cdb8a85 578 echo format_text($question->questiontext, $question->questiontextformat, NULL, $courseid);
fe98ea90 579 quiz_print_possible_question_image($quizid, $question);
a8a372cc 580 if ($response) {
a8d909c7 581 $value = 'value="'.s($response[0]).'"';
a2fe7cc0 582 } else {
a8d909c7 583 $value = '';
a8a372cc 584 }
a9f80673 585 echo "<p align=\"right\">$stranswer: <input type=\"text\" name=\"q$realquestion->id\" size=\"80\" $value /></p>";
a8a372cc 586 if ($feedback) {
d7512435 587 quiz_print_comment("<p align=\"right\">$feedback[0]</p>");
a8a372cc 588 }
8db3eadd 589 if ($correct) {
c74a0ca5 590 $correctanswers = implode(", ", $correct);
8db3eadd 591 quiz_print_correctanswer($correctanswers);
592 }
14d8c0b4 593 break;
594
a5e1f35c 595 case TRUEFALSE:
14d8c0b4 596 if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
597 notify("Error: Missing question options!");
598 }
a2fe7cc0 599 if (!$true = get_record("quiz_answers", "id", $options->trueanswer)) {
14d8c0b4 600 notify("Error: Missing question answers!");
601 }
a2fe7cc0 602 if (!$false = get_record("quiz_answers", "id", $options->falseanswer)) {
14d8c0b4 603 notify("Error: Missing question answers!");
604 }
605 if (!$true->answer) {
606 $true->answer = get_string("true", "quiz");
607 }
608 if (!$false->answer) {
609 $false->answer = get_string("false", "quiz");
610 }
5cdb8a85 611 echo format_text($question->questiontext, $question->questiontextformat, NULL, $courseid);
fe98ea90 612 quiz_print_possible_question_image($quizid, $question);
a8a372cc 613
a2fe7cc0 614 $truechecked = "";
615 $falsechecked = "";
616
617 if (!empty($response[$true->id])) {
89b8d337 618 $truechecked = 'checked="checked"';
a8a372cc 619 $feedbackid = $true->id;
a2fe7cc0 620 } else if (!empty($response[$false->id])) {
89b8d337 621 $falsechecked = 'checked="checked"';
a8a372cc 622 $feedbackid = $false->id;
623 }
a2fe7cc0 624
625 $truecorrect = "";
626 $falsecorrect = "";
8db3eadd 627 if ($correct) {
a2fe7cc0 628 if (!empty($correct[$true->id])) {
89b8d337 629 $truecorrect = 'class="highlight"';
8db3eadd 630 }
a2fe7cc0 631 if (!empty($correct[$false->id])) {
89b8d337 632 $falsecorrect = 'class="highlight"';
8db3eadd 633 }
634 }
d7512435 635 echo "<table align=\"right\" cellpadding=\"5\"><tr><td align=\"right\">$stranswer:&nbsp;&nbsp;";
636 echo "<td $truecorrect>";
637 echo "<input $truechecked type=\"radio\" name=\"q$realquestion->id\" value=\"$true->id\" />$true->answer";
638 echo "</td><td $falsecorrect>";
639 echo "<input $falsechecked type=\"radio\" name=\"q$realquestion->id\" value=\"$false->id\" />$false->answer";
640 echo "</td></tr></table><br clear=\"all\">";// changed from CLEAR=ALL jm
a8a372cc 641 if ($feedback) {
d7512435 642 quiz_print_comment("<p align=\"right\">$feedback[$feedbackid]</p>");
a8a372cc 643 }
644
14d8c0b4 645 break;
646
a5e1f35c 647 case MULTICHOICE:
14d8c0b4 648 if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
649 notify("Error: Missing question options!");
650 }
a5e1f35c 651 if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
14d8c0b4 652 notify("Error: Missing question answers!");
653 }
5cdb8a85 654 echo format_text($question->questiontext, $question->questiontextformat, NULL, $courseid);
fe98ea90 655 quiz_print_possible_question_image($quizid, $question);
d7512435 656 echo "<table align=\"right\">";
657 echo "<tr><td valign=\"top\">$stranswer:&nbsp;&nbsp;</td><td>";
658 echo "<table>";
14d8c0b4 659 $answerids = explode(",", $options->answers);
a8a372cc 660
4b85b717 661 if ($shuffleanswers) {
662 $answerids = swapshuffle($answerids);
663 }
664
14d8c0b4 665 foreach ($answerids as $key => $answerid) {
666 $answer = $answers[$answerid];
68fefdbe 667 $qnumchar = chr(ord('a') + $key);
a8a372cc 668
586b2c82 669 if (empty($response[$answerid])) {
a8a372cc 670 $checked = "";
8e6c87cc 671 } else {
89b8d337 672 $checked = 'checked="checked"';
a8a372cc 673 }
89b8d337 674 echo '<tr><td valign="top">';
a5e1f35c 675 if ($options->single) {
d7512435 676 echo "<input $checked type=\"radio\" name=\"q$realquestion->id\" value=\"$answer->id\" />";
14d8c0b4 677 } else {
d7512435 678 echo "<input $checked type=\"checkbox\" name=\"q$realquestion->id"."a$answer->id\" value=\"$answer->id\" />";
14d8c0b4 679 }
d7512435 680 echo "</td>";
8e6c87cc 681 if (empty($feedback) or empty($correct[$answer->id])) {
e8da8d70 682 echo '<td valign="top">'.format_text("$qnumchar. $answer->answer").'</td>';
8e6c87cc 683 } else {
e8da8d70 684 echo '<td valign="top" class="highlight">'.format_text("$qnumchar. $answer->answer").'</td>';
8db3eadd 685 }
8e6c87cc 686 if (!empty($feedback)) {
d7512435 687 echo "<td valign=\"top\">&nbsp;";
8e6c87cc 688 if (!empty($response[$answerid])) {
a8a372cc 689 quiz_print_comment($feedback[$answerid]);
690 }
d7512435 691 echo "</td>";
a8a372cc 692 }
d7512435 693 echo "</tr>";
14d8c0b4 694 }
d7512435 695 echo "</table>";
696 echo "</td></tr></table>";
14d8c0b4 697 break;
698
54a67a59 699 case MATCH:
700 if (!$options = get_record("quiz_match", "question", $question->id)) {
701 notify("Error: Missing question options!");
702 }
703 if (!$subquestions = get_records_list("quiz_match_sub", "id", $options->subquestions)) {
704 notify("Error: Missing subquestions for this question!");
705 }
96192c44 706 if (!empty($question->questiontext)) {
5cdb8a85 707 echo format_text($question->questiontext, $question->questiontextformat, NULL, $courseid);
4b85b717 708 }
fe98ea90 709 quiz_print_possible_question_image($quizid, $question);
54a67a59 710
96192c44 711 if ($shuffleanswers) {
712 $subquestions = draw_rand_array($subquestions, count($subquestions));
713 }
54a67a59 714 foreach ($subquestions as $subquestion) {
715 $answers[$subquestion->id] = $subquestion->answertext;
716 }
717
718 $answers = draw_rand_array($answers, count($answers));
719
89b8d337 720 echo '<table border="0" cellpadding="10" align="right">';
54a67a59 721 foreach ($subquestions as $key => $subquestion) {
89b8d337 722 echo '<tr><td align="left" valign="top">';
54a67a59 723 echo $subquestion->questiontext;
89b8d337 724 echo '</td>';
54a67a59 725 if (empty($response)) {
89b8d337 726 echo '<td align="right" valign="top">';
34d52ad7 727 choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id");
54a67a59 728 } else {
729 if (empty($response[$key])) {
89b8d337 730 echo '<td align="right" valign="top">';
34d52ad7 731 choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id");
54a67a59 732 } else {
733 if ($response[$key] == $correct[$key]) {
89b8d337 734 echo '<td align="right" valign="top" class="highlight">';
34d52ad7 735 choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id", $response[$key]);
54a67a59 736 } else {
89b8d337 737 echo '<td align="right" valign="top">';
34d52ad7 738 choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id", $response[$key]);
54a67a59 739 }
740 }
741
742 if (!empty($feedback[$key])) {
743 quiz_print_comment($feedback[$key]);
744 }
745 }
89b8d337 746 echo '</td></tr>';
54a67a59 747 }
89b8d337 748 echo '</table>';
54a67a59 749
750 break;
751
752 case RANDOMSAMATCH:
753 if (!$options = get_record("quiz_randomsamatch", "question", $question->id)) {
95dbc030 754 notify("Error: Missing question options!");
755 }
5cdb8a85 756 echo format_text($question->questiontext, $question->questiontextformat, NULL, $courseid);
fe98ea90 757 quiz_print_possible_question_image($quizid, $question);
95dbc030 758
759 /// First, get all the questions available
760
761 $allquestions = get_records_select("quiz_questions",
762 "category = $question->category AND qtype = ".SHORTANSWER);
763 if (count($allquestions) < $options->choose) {
764 notify("Error: could not find enough Short Answer questions in the database!");
765 notify("Found ".count($allquestions).", need $options->choose.");
766 break;
767 }
768
769 if (empty($response)) { // Randomly pick the questions
770 if (!$randomquestions = draw_rand_array($allquestions, $options->choose)) {
771 notify("Error choosing $options->choose random questions");
772 break;
773 }
774 } else { // Use existing questions
775 $randomquestions = array();
776 foreach ($response as $key => $rrr) {
777 $rrr = explode("-", $rrr);
778 $randomquestions[$key] = $allquestions[$key];
779 $responseanswer[$key] = $rrr[1];
780 }
781 }
782
783 /// For each selected, find the best matching answers
784
785 foreach ($randomquestions as $randomquestion) {
786 $shortanswerquestion = get_record("quiz_shortanswer", "question", $randomquestion->id);
787 $questionanswers = get_records_list("quiz_answers", "id", $shortanswerquestion->answers);
788 $bestfraction = 0;
789 $bestanswer = NULL;
790 foreach ($questionanswers as $questionanswer) {
791 if ($questionanswer->fraction > $bestfraction) {
792 $bestanswer = $questionanswer;
793 }
794 }
795 if (empty($bestanswer)) {
796 notify("Error: Could not find the best answer for question: ".$randomquestions->name);
797 break;
798 }
799 $randomanswers[$bestanswer->id] = trim($bestanswer->answer);
800 }
801
802 if (!$randomanswers = draw_rand_array($randomanswers, $options->choose)) { // Mix them up
803 notify("Error randomising answers!");
804 break;
805 }
806
89b8d337 807 echo '<table border="0" cellpadding="10">';
95dbc030 808 foreach ($randomquestions as $key => $randomquestion) {
89b8d337 809 echo '<tr><td align="left" valign="top">';
95dbc030 810 echo $randomquestion->questiontext;
89b8d337 811 echo '</td>';
812 echo '<td align="right" valign="top">';
95dbc030 813 if (empty($response)) {
34d52ad7 814 choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id");
95dbc030 815 } else {
816 if (!empty($correct[$key])) {
817 if ($randomanswers[$responseanswer[$key]] == $correct[$key]) {
89b8d337 818 echo '<span="highlight">';
34d52ad7 819 choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id", $responseanswer[$key]);
d7143408 820 echo '</span><br />';
95dbc030 821 } else {
34d52ad7 822 choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id", $responseanswer[$key]);
95dbc030 823 quiz_print_correctanswer($correct[$key]);
824 }
825 } else {
34d52ad7 826 choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id", $responseanswer[$key]);
95dbc030 827 }
828 if (!empty($feedback[$key])) {
829 quiz_print_comment($feedback[$key]);
830 }
831 }
89b8d337 832 echo '</td></tr>';
95dbc030 833 }
89b8d337 834 echo '</table>';
95dbc030 835 break;
836
8b439f8c 837 case MULTIANSWER:
838 // For this question type, we better print the image on top:
fe98ea90 839 quiz_print_possible_question_image($quizid, $question);
8b439f8c 840
d396ba79 841 $qtextremaining = format_text($question->questiontext, $question->questiontextformat, NULL, $courseid);
842
843 // The regex will recognize text snippets of type {#X}
844 // where the X can be any text not containg } or white-space characters.
845
846 while (ereg('\{#([^[:space:]}]*)}', $qtextremaining, $regs)) {
847 $qtextsplits = explode($regs[0], $qtextremaining, 2);
848 echo $qtextsplits[0];
849 $qtextremaining = $qtextsplits[1];
850
851 $multianswer = get_record('quiz_multianswers', 'question', $question->id, 'positionkey', $regs[1]);
852
853 $inputname= " name=\"q{$realquestion->id}ma$multianswer->id\" ";
854
855 if (!empty($response) && ereg('(.[^-]*)-(.+)', array_shift($response), $responseitems)) {
856 $responsefractiongrade = (float)$responseitems[1];
857 $actualresponse = $responseitems[2];
858
859 if (1.0 == $responsefractiongrade) {
860 $style = 'style="background-color:lime"';
861 } else if (0.0 < $responsefractiongrade) {
862 $style = 'style="background-color:yellow"';
863 } else if ('' != $actualresponse) {
864 // The response must have been totally wrong:
865 $style = 'style="background-color:red"';
866 } else {
867 // There was no response given
868 $style = '';
869 }
870 } else {
871 $responsefractiongrade = 0.0;
872 $actualresponse = '';
873 $style = '';
874 }
8b439f8c 875
d396ba79 876 $feedbackitem = '';
877 switch ($multianswer->answertype) {
878 case SHORTANSWER:
879 case NUMERICAL:
880 if (isset($feedback[$regs[1]-1])) {
881 $title = " title=\"".s($feedback[$regs[1]-1])."\" ";
882 } else {
883 $title = "";
884 }
885 echo " <input $style $title $inputname value=\"$actualresponse\" type=\"text\" size=\"12\" /> ";
886 break;
887 case MULTICHOICE:
888 $outputoptions = '';
889 $answers = get_records_list("quiz_answers", "id", $multianswer->answers);
890 $outputoptions .= '<option></option>'; // Default empty option
891 foreach ($answers as $answer) {
892 if ($answer->id == $actualresponse) {
893 $selected = 'selected';
894 $feedbackitem = $answer->feedback;
895 } else {
896 $selected = '';
897 }
898 $outputoptions .= "<option value=\"$answer->id\" $selected>$answer->answer</option>";
899 }
900 if ($feedbackitem) {
901 echo "<select title=\"".s($feedbackitem)."\" $style $inputname>";
902 } else {
903 echo "<select $style $inputname>";
904 }
905 echo $outputoptions;
906 echo '</select>';
907 break;
908 default:
909 error("Unable to recognized answertype $answer->answertype");
910 break;
911 }
912 }
8b439f8c 913
d396ba79 914 // Print the final piece of question text:
915 echo $qtextremaining;
916 break;
8b439f8c 917
34d52ad7 918 case RANDOM:
5a24a018 919 // This can only happen if it is a recently added question
920
d7512435 921 echo '<p>' . get_string('random', 'quiz') . '</p>';
34d52ad7 922 break;
8db3eadd 923
14d8c0b4 924 default:
925 notify("Error: Unknown question type!");
926 }
927
d7512435 928 echo "</td></tr></table>";
3a506ca2 929}
930
34d52ad7 931
932
96192c44 933function quiz_print_quiz_questions($quiz, $results=NULL, $questions=NULL, $shuffleorder=NULL) {
a5e1f35c 934// Prints a whole quiz on one page.
935
34d52ad7 936 /// Get the questions
937
34d52ad7 938 if (!$questions) {
434802d5 939 if (empty($quiz->questions)) {
940 notify("No questions have been defined!");
941 return false;
942 }
943
34d52ad7 944 if (!$questions = get_records_list("quiz_questions", "id", $quiz->questions, "")) {
434802d5 945 notify("Error when reading questions from the database!");
34d52ad7 946 return false;
947 }
d288fa52 948 }
4b85b717 949
d288fa52 950 if (!$shuffleorder) {
951 if (!empty($quiz->shufflequestions)) { // Mix everything up
4b85b717 952 $questions = swapshuffle_assoc($questions);
ada728e2 953 } else {
d288fa52 954 $shuffleorder = explode(",", $quiz->questions); // Use originally defined order
4b85b717 955 }
34d52ad7 956 }
957
e1122620 958 if ($shuffleorder) { // Order has been defined, so reorder questions
959 $oldquestions = $questions;
960 $questions = array();
5a24a018 961 foreach ($shuffleorder as $key) {
962 if (empty($oldquestions[$key])) { // Check for recently added questions
963 if ($recentlyaddedquestion =
964 get_record("quiz_questions", "id", $key)) {
965 $recentlyaddedquestion->recentlyadded = true;
966 $questions[] = $recentlyaddedquestion;
967 }
968 } else {
969 $questions[] = $oldquestions[$key]; // This loses the index key, but doesn't matter
970 }
e1122620 971 }
972 }
973
434802d5 974 if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) {
975 notify("No grades were found for these questions!");
976 return false;
977 }
978
34d52ad7 979
980 /// Examine the set of questions for random questions, and retrieve them
981
434802d5 982 if (empty($results)) { // Choose some new random questions
34d52ad7 983 if ($randomcats = quiz_get_random_categories($quiz->questions)) {
984 foreach ($randomcats as $randomcat => $randomdraw) {
985 /// Get the appropriate amount of random questions from this category
7bfa4fad 986 if (!$catquestions[$randomcat] = quiz_choose_random_questions($randomcat, $randomdraw, $quiz->questions)) {
434802d5 987 notify(get_string("toomanyrandom", "quiz", $randomcat));
34d52ad7 988 return false;
989 }
990 }
991 }
434802d5 992 } else { // Get the previously chosen questions
993 $chosen = array();
994 foreach ($questions as $question) {
995 if (isset($question->random)) {
996 $chosen[] = $question->random;
997 }
998 }
999 if ($chosen) {
1000 $chosenlist = implode(",", $chosen);
1001 if (!$chosen = get_records_list("quiz_questions", "id", $chosenlist, "")) {
1002 notify("Error when reading questions from the database!");
1003 return false;
1004 }
1005 }
a5e1f35c 1006 }
1007
9307692f 1008 $strconfirmattempt = addslashes(get_string("readytosend", "quiz"));
1009
45376a51 1010 if (empty($quiz->grade)) {
1011 $onsubmit = "";
1012 } else {
1013 $onsubmit = "onsubmit=\"return confirm('$strconfirmattempt');\"";
1014 }
1015
d7512435 1016 echo "<form method=\"post\" action=\"attempt.php\" $onsubmit>\n";
1017 echo "<input type=\"hidden\" name=\"q\" value=\"$quiz->id\" />\n";
a8a372cc 1018
34d52ad7 1019 $count = 0;
96192c44 1020 $questionorder = array();
1021
34d52ad7 1022 foreach ($questions as $question) {
401c8de6 1023
1024 if ($question->qtype != DESCRIPTION) { // Description questions are not counted
1025 $count++;
1026 }
34d52ad7 1027
e1122620 1028 $questionorder[] = $question->id;
1029
8db3eadd 1030 $feedback = NULL;
1031 $response = NULL;
1032 $actualgrades = NULL;
1033 $correct = NULL;
34d52ad7 1034 $randomquestion = NULL;
1035
1036 if (empty($results)) {
1037 if ($question->qtype == RANDOM ) { // Set up random questions
1038 $randomquestion = $question;
1039 $question = array_pop($catquestions[$randomquestion->category]);
1040 $grades[$question->id]->grade = $grades[$randomquestion->id]->grade;
8e6c87cc 1041 }
34d52ad7 1042 } else {
1043 if (!empty($results->feedback[$question->id])) {
1044 $feedback = $results->feedback[$question->id];
8e6c87cc 1045 }
34d52ad7 1046 if (!empty($results->response[$question->id])) {
1047 $response = $results->response[$question->id];
1048 }
1049 if (!empty($results->grades[$question->id])) {
1050 $actualgrades = $results->grades[$question->id];
8e6c87cc 1051 }
8db3eadd 1052 if ($quiz->correctanswers) {
34d52ad7 1053 if (!empty($results->correct[$question->id])) {
1054 $correct = $results->correct[$question->id];
8e6c87cc 1055 }
8db3eadd 1056 }
34d52ad7 1057 if (!empty($question->random)) {
1058 $randomquestion = $question;
434802d5 1059 $question = $chosen[$question->random];
34d52ad7 1060 $grades[$question->id]->grade = $grades[$randomquestion->id]->grade;
1061 }
a8a372cc 1062 }
8db3eadd 1063
d7512435 1064 print_simple_box_start("center", "90%");
fe98ea90 1065 quiz_print_question($count, $question, $grades[$question->id]->grade, $quiz->id,
4b85b717 1066 $feedback, $response, $actualgrades, $correct,
0c29498e 1067 $randomquestion, $quiz->shuffleanswers, $quiz->grade, $quiz->course);
a5e1f35c 1068 print_simple_box_end();
d7512435 1069 echo "<br />";
a5e1f35c 1070 }
a8a372cc 1071
ff4c50a9 1072 $attemptbuildsonthelast = isset($results->attemptbuildsonthelast) ? $results->attemptbuildsonthelast : NULL;
c18490b8 1073
ff4c50a9 1074 if (empty($results) || $attemptbuildsonthelast) {
96192c44 1075 if (!empty($quiz->shufflequestions)) { // Things have been mixed up, so pass the question order
1076 $shuffleorder = implode(',', $questionorder);
d7512435 1077 echo "<input type=\"hidden\" name=\"shuffleorder\" value=\"$shuffleorder\" />\n";
96192c44 1078 }
d7512435 1079 echo "<center>\n<input type=\"submit\" value=\"".get_string("savemyanswers", "quiz")."\" />\n</center>";
a8a372cc 1080 }
96192c44 1081 echo "</form>";
10b9291c 1082
1083 return true;
a5e1f35c 1084}
34d52ad7 1085
1086
6a952ce7 1087
1088function quiz_get_default_category($courseid) {
34d52ad7 1089/// Returns the current category
1090
6a952ce7 1091 if ($categories = get_records("quiz_categories", "course", $courseid, "id")) {
1092 foreach ($categories as $category) {
1093 return $category; // Return the first one (lowest id)
1094 }
1095 }
1096
1097 // Otherwise, we need to make one
10b9291c 1098 $category->name = get_string("default", "quiz");
1099 $category->info = get_string("defaultinfo", "quiz");
6a952ce7 1100 $category->course = $courseid;
1101 $category->publish = 0;
25b6ff9d 1102 $category->stamp = make_unique_id_code();
6a952ce7 1103
1104 if (!$category->id = insert_record("quiz_categories", $category)) {
1105 notify("Error creating a default category!");
1106 return false;
1107 }
1108 return $category;
1109}
1110
c74a0ca5 1111function quiz_get_category_menu($courseid, $published=false) {
da5fb074 1112/// Returns the list of categories
1113 $publish = "";
c74a0ca5 1114 if ($published) {
1115 $publish = "OR publish = '1'";
1116 }
bdc23be0 1117 return get_records_select_menu("quiz_categories", "course='$courseid' $publish", "name ASC", "id,name");
c74a0ca5 1118}
1119
6a952ce7 1120function quiz_print_category_form($course, $current) {
1121// Prints a form to choose categories
1122
facbf40f 1123 if (!$categories = get_records_select("quiz_categories", "course = '$course->id' OR publish = '1'", "name ASC")) {
6a952ce7 1124 if (!$category = quiz_get_default_category($course->id)) {
1125 notify("Error creating a default category!");
1126 return false;
1127 }
cb62c00a 1128 $categories[$category->id] = $category;
6a952ce7 1129 }
8d94f5a0 1130 foreach ($categories as $key => $category) {
facbf40f 1131 if ($catcourse = get_record("course", "id", $category->course)) {
1132 if ($category->publish) {
b55a466b 1133 $category->name .= " ($catcourse->shortname)";
8d94f5a0 1134 }
facbf40f 1135 $catmenu[$category->id] = $category->name;
8d94f5a0 1136 }
1137 }
6a952ce7 1138 $strcategory = get_string("category", "quiz");
1139 $strshow = get_string("show", "quiz");
6b069ece 1140 $streditcats = get_string("editcategories", "quiz");
6a952ce7 1141
2d1c22bb 1142 echo "<table width=\"100%\"><tr><td width=\"20\" nowrap=\"nowrap\">";
d7512435 1143 echo "<b>$strcategory:</b>&nbsp;";
2d1c22bb 1144 echo "</td><td>";
22592150 1145 popup_form ("edit.php?cat=", $catmenu, "catmenu", $current, "choose", "", "", false, "self");
d7512435 1146 echo "</td><td align=\"right\">";
1147 echo "<form method=\"get\" action=\"category.php\">";
1148 echo "<input type=\"hidden\" name=\"id\" value=\"$course->id\" />";
1149 echo "<input type=\"submit\" value=\"$streditcats\" />";
1150 echo "</form>";
1151 echo "</td></tr></table>";
6a952ce7 1152}
1153
1154
34d52ad7 1155
7bfa4fad 1156function quiz_choose_random_questions($category, $draws, $excluded=0) {
34d52ad7 1157/// Given a question category and a number of draws, this function
1158/// creates a random subset of that size - returned as an array of questions
1159
1160 if (!$pool = get_records_select_menu("quiz_questions",
575de48f 1161 "category = '$category' AND id NOT IN ($excluded)
1162 AND qtype <> ".RANDOM."
1163 AND qtype <> ".DESCRIPTION,
1164 "", "id,qtype")) {
34d52ad7 1165 return false;
1166 }
1167
1168 $countpool = count($pool);
1169
1170 if ($countpool == $draws) {
1171 $chosen = $pool;
1172 } else if ($countpool < $draws) {
1173 return false;
1174 } else {
1175 $chosen = draw_rand_array($pool, $draws);
1176 }
1177
1178 $chosenlist = implode(",", array_keys($chosen));
1179 return get_records_list("quiz_questions", "id", $chosenlist);
1180}
1181
1182
7bd1aa1d 1183function quiz_get_all_question_grades($questionlist, $quizid) {
1184// Given a list of question IDs, finds grades or invents them to
1185// create an array of matching grades
1186
5a25f84d 1187 if (empty($questionlist)) {
1188 return array();
1189 }
1190
bdc23be0 1191 $questions = quiz_get_question_grades($quizid, $questionlist);
7bd1aa1d 1192
1193 $list = explode(",", $questionlist);
1194 $grades = array();
1195
1196 foreach ($list as $qid) {
1197 if (isset($questions[$qid])) {
1198 $grades[$qid] = $questions[$qid]->grade;
1199 } else {
1200 $grades[$qid] = 1;
1201 }
1202 }
1203 return $grades;
1204}
1205
004c02e0 1206function quiz_gradesmenu_options($defaultgrade) {
1207// Especially for multianswer questions it is often
1208// desirable to have the grade of the question in a quiz
1209// larger than the earlier maximum of 10 points.
1210// This function makes quiz question list grade selector drop-down
1211// have the maximum grade option set to the highest value between 10
1212// and the defaultgrade of the question.
1213
1214 if ($defaultgrade && $defaultgrade>10) {
1215 $maxgrade = $defaultgrade;
1216 } else {
1217 $maxgrade = 10;
1218 }
1219
1220 unset($gradesmenu);
1221 for ($i=$maxgrade ; $i>=0 ; --$i) {
1222 $gradesmenu[$i] = $i;
1223 }
1224 return $gradesmenu;
1225}
7bd1aa1d 1226
1227function quiz_print_question_list($questionlist, $grades) {
6a952ce7 1228// Prints a list of quiz questions in a small layout form with knobs
7bd1aa1d 1229// $questionlist is comma-separated list
1230// $grades is an array of corresponding grades
6a952ce7 1231
1232 global $THEME;
1233
1234 if (!$questionlist) {
d7512435 1235 echo "<p align=\"center\">";
6a952ce7 1236 print_string("noquestions", "quiz");
d7512435 1237 echo "</p>";
6a952ce7 1238 return;
1239 }
1240
1241 $order = explode(",", $questionlist);
1242
a740ff66 1243 if (!$questions = get_list_of_questions($questionlist)) {
d7512435 1244 echo "<p align=\"center\">";
9a652ddb 1245 print_string("noquestions", "quiz");
d7512435 1246 echo "</p>";
9a652ddb 1247 return;
1248
6a952ce7 1249 }
1250
1251 $strorder = get_string("order");
1252 $strquestionname = get_string("questionname", "quiz");
1253 $strgrade = get_string("grade");
1254 $strdelete = get_string("delete");
1255 $stredit = get_string("edit");
1256 $strmoveup = get_string("moveup");
1257 $strmovedown = get_string("movedown");
1258 $strsavegrades = get_string("savegrades", "quiz");
c74a0ca5 1259 $strtype = get_string("type", "quiz");
6a952ce7 1260
6a952ce7 1261 $count = 0;
1262 $sumgrade = 0;
1263 $total = count($order);
d7512435 1264 echo "<form method=\"post\" action=\"edit.php\">";
1265 echo "<table border=\"0\" cellpadding=\"5\" cellspacing=\"2\" width=\"100%\">\n";
1266 echo "<tr><th width=\"*\" colspan=\"3\" nowrap=\"nowrap\">$strorder</th><th align=\"left\" width=\"100%\" nowrap=\"nowrap\">$strquestionname</th><th width=\"*\" nowrap=\"nowrap\">$strtype</th><th width=\"*\" nowrap=\"nowrap\">$strgrade</th><th width=\"*\" nowrap=\"nowrap\">$stredit</th></tr>\n";
6a952ce7 1267 foreach ($order as $qnum) {
95dbc030 1268 if (empty($questions[$qnum])) {
1269 continue;
1270 }
bca64a12 1271 $question = $questions[$qnum];
a740ff66 1272 $canedit = isteacheredit($question->course);
6a952ce7 1273 $count++;
d7512435 1274 echo "<tr bgcolor=\"$THEME->cellcontent\">";
1275 echo "<td>$count</td>";
1276 echo "<td>";
6a952ce7 1277 if ($count != 1) {
d7512435 1278 echo "<a title=\"$strmoveup\" href=\"edit.php?up=$qnum\"><img
1279 src=\"../../pix/t/up.gif\" border=\"0\"></a>";
6a952ce7 1280 }
d7512435 1281 echo "</td>";
1282 echo "<td>";
6a952ce7 1283 if ($count != $total) {
d7512435 1284 echo "<a title=\"$strmovedown\" href=\"edit.php?down=$qnum\"><img
1285 src=\"../../pix/t/down.gif\" border=\"0\"></a>";
6a952ce7 1286 }
d7512435 1287 echo "</td>";
1288 echo "<td>$question->name</td>";
1289 echo "<td align=\"center\">";
a740ff66 1290 quiz_print_question_icon($question, $canedit);
d7512435 1291 echo "</td>";
1292 echo "<td>";
bca64a12 1293 if ($question->qtype == DESCRIPTION) {
d7512435 1294 echo "<input type=\"hidden\" name=\"q$qnum\" value=\"0\" /> \n";
bca64a12 1295 } else {
004c02e0 1296 choose_from_menu(quiz_gradesmenu_options($question->defaultgrade),
1297 "q$qnum", (string)$grades[$qnum], "");
bca64a12 1298 }
d7512435 1299 echo "<td>";
1300 echo "<a title=\"$strdelete\" href=\"edit.php?delete=$qnum\"><img
1301 src=\"../../pix/t/delete.gif\" border=\"0\"></a>&nbsp;";
a740ff66 1302 if ($canedit) {
1303 echo "<a title=\"$stredit\" href=\"question.php?id=$qnum\"><img
1304 src=\"../../pix/t/edit.gif\" border=\"0\"></a>\n";
1305 }
d7512435 1306 echo "</td>";
6a952ce7 1307
7bd1aa1d 1308 $sumgrade += $grades[$qnum];
6a952ce7 1309 }
d7512435 1310 echo "<tr><td colspan=5 align=\"right\">\n";
1311 echo "<input type=\"submit\" value=\"$strsavegrades:\" />\n";
1312 echo "<input type=\"hidden\" name=\"setgrades\" value=\"save\" />\n";
1313 echo "<td align=\"left\" bgcolor=\"$THEME->cellcontent\">\n";
1314 echo "<b>$sumgrade</b>";
1315 echo "</td><td>\n</td></tr>\n";
1316 echo "</table>\n";
1317 echo "</form>\n";
10b9291c 1318
1319 return $sumgrade;
6a952ce7 1320}
1321
1322
1e085edc 1323function quiz_print_cat_question_list($categoryid, $quizselected=true) {
6a952ce7 1324// Prints a form to choose categories
1325
1326 global $THEME, $QUIZ_QUESTION_TYPE;
1327
10b9291c 1328 $strcategory = get_string("category", "quiz");
6a952ce7 1329 $strquestion = get_string("question", "quiz");
49220fa7 1330 $straddquestions = get_string("addquestions", "quiz");
1331 $strimportquestions = get_string("importquestions", "quiz");
6a952ce7 1332 $strnoquestions = get_string("noquestions", "quiz");
1333 $strselect = get_string("select", "quiz");
a01b2571 1334 $strselectall = get_string("selectall", "quiz");
6a952ce7 1335 $strcreatenewquestion = get_string("createnewquestion", "quiz");
1336 $strquestionname = get_string("questionname", "quiz");
1337 $strdelete = get_string("delete");
1338 $stredit = get_string("edit");
1339 $straddselectedtoquiz = get_string("addselectedtoquiz", "quiz");
c74a0ca5 1340 $strtype = get_string("type", "quiz");
c6eed097 1341 $strcreatemultiple = get_string("createmultiple", "quiz");
6a952ce7 1342
1343 if (!$categoryid) {
d7512435 1344 echo "<p align=\"center\"><b>";
6a952ce7 1345 print_string("selectcategoryabove", "quiz");
5325f8b8 1346 echo "</b></p>";
1e085edc 1347 if ($quizselected) {
1348 echo "<p>";
1349 print_string("addingquestions", "quiz");
1350 echo "</p>";
1351 }
6a952ce7 1352 return;
1353 }
a5e1f35c 1354
6a952ce7 1355 if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
1356 notify("Category not found!");
1357 return;
1358 }
d7512435 1359 echo "<center>";
2d1c22bb 1360 echo format_text($category->info, FORMAT_MOODLE);
1361
1362 echo '<table><tr>';
1363 echo "<td valign=\"top\"><b>$strcreatenewquestion:</b></td>";
1364 echo '<td valign="top" align="right">';
1365 popup_form ("question.php?category=$category->id&qtype=", $QUIZ_QUESTION_TYPE, "addquestion",
22592150 1366 "", "choose", "", "", false, "self");
2d1c22bb 1367 echo '<td width="10" valign="top" align="right">';
cd63d77e 1368 helpbutton("questiontypes", $strcreatenewquestion, "quiz");
2d1c22bb 1369 echo '</td></tr>';
49220fa7 1370
2d1c22bb 1371 echo '<tr><td colspan="3" align="right">';
1372 echo '<form method="get" action="import.php">';
d7512435 1373 echo "<input type=\"hidden\" name=\"category\" value=\"$category->id\" />";
1374 echo "<input type=\"submit\" value=\"$strimportquestions\" />";
49220fa7 1375 helpbutton("import", $strimportquestions, "quiz");
2d1c22bb 1376 echo '</form>';
1377 echo '</td></tr>';
49220fa7 1378
2d1c22bb 1379 echo '<tr><td colspan="3" align="right">';
1380 echo '<form method="get" action="multiple.php">';
d7512435 1381 echo "<input type=\"hidden\" name=\"category\" value=\"$category->id\" />";
1382 echo "<input type=\"submit\" value=\"$strcreatemultiple\" />";
c6eed097 1383 helpbutton("createmultiple", $strcreatemultiple, "quiz");
2d1c22bb 1384 echo '</form>';
1385 echo '</td></tr>';
c6eed097 1386
2d1c22bb 1387 echo '</table>';
49220fa7 1388
2d1c22bb 1389 echo '</center>';
6a952ce7 1390
14bdb238 1391 if (!$questions = get_records("quiz_questions", "category", $category->id, "qtype ASC")) {
d7512435 1392 echo "<p align=\"center\">";
6a952ce7 1393 print_string("noquestions", "quiz");
d7512435 1394 echo "</p>";
6a952ce7 1395 return;
1396 }
1397
1e085edc 1398 $canedit = isteacheredit($category->course);
10b9291c 1399
d7512435 1400 echo "<form method=\"post\" action=\"edit.php\">";
1401 echo "<table border=\"0\" cellpadding=\"5\" cellspacing=\"2\" width=\"100%\">";
1402 echo "<tr>";
1e085edc 1403 if ($quizselected) {
d7512435 1404 echo "<th width=\"*\" nowrap=\"nowrap\">$strselect</th>";
1e085edc 1405 }
d7512435 1406 echo "<th width=\"100%\" align=\"left\" nowrap=\"nowrap\">$strquestionname</th><th width=\"*\" nowrap=\"nowrap\">$strtype</th>";
10b9291c 1407 if ($canedit) {
d7512435 1408 echo "<th width=\"*\" nowrap=\"nowrap\">$stredit</th>";
10b9291c 1409 }
d7512435 1410 echo "</tr>\n";
6a952ce7 1411 foreach ($questions as $question) {
d7512435 1412 echo "<tr bgcolor=\"$THEME->cellcontent\">\n";
1e085edc 1413 if ($quizselected) {
d7512435 1414 echo "<td align=\"center\">";
1415 echo "<input type=\"checkbox\" name=\"q$question->id\" value=\"1\" />\n";
1416 echo "</td>";
1e085edc 1417 }
d7512435 1418 echo "<td>".$question->name."</td>\n";
1419 echo "<td align=\"center\">\n";
a740ff66 1420 quiz_print_question_icon($question, $canedit);
d7512435 1421 echo "</td>\n";
10b9291c 1422 if ($canedit) {
d7512435 1423 echo "<td>\n";
1424 echo "<a title=\"$strdelete\" href=\"question.php?id=$question->id&delete=$question->id\">\n<img
1425 src=\"../../pix/t/delete.gif\" border=0></a>&nbsp;";
1426 echo "<a title=\"$stredit\" href=\"question.php?id=$question->id\"><img
1427 src=\"../../pix/t/edit.gif\" border=0></a>";
1428 echo "</td>\n";// deleted </tr> jm
10b9291c 1429 }
d7512435 1430 echo "</tr>\n";
6a952ce7 1431 }
1e085edc 1432 if ($quizselected) {
d7512435 1433 echo "<tr>\n<td colspan=\"3\">";
1434 echo "<input type=\"submit\" name=\"add\" value=\"<< $straddselectedtoquiz\" />\n";
1435 //echo "<input type=submit name=\"delete\" value=\"XX Delete selected\">";
89b8d337 1436 echo "<input type=\"button\" onclick=\"checkall()\" value=\"$strselectall\" />\n";
d7512435 1437 echo "</td></tr>";
1438 }
1439 echo "</table>\n";
1440 echo "</form>\n";
6a952ce7 1441}
a5e1f35c 1442
3a506ca2 1443
958aafe2 1444function quiz_start_attempt($quizid, $userid, $numattempt) {
1445 $attempt->quiz = $quizid;
ebc3bd2b 1446 $attempt->userid = $userid;
958aafe2 1447 $attempt->attempt = $numattempt;
1448 $attempt->timestart = time();
1449 $attempt->timefinish = 0;
1450 $attempt->timemodified = time();
1451
1452 return insert_record("quiz_attempts", $attempt);
1453}
1454
1455function quiz_get_user_attempt_unfinished($quizid, $userid) {
1456// Returns an object containing an unfinished attempt (if there is one)
ebc3bd2b 1457 return get_record("quiz_attempts", "quiz", $quizid, "userid", $userid, "timefinish", 0);
958aafe2 1458}
1459
3a506ca2 1460function quiz_get_user_attempts($quizid, $userid) {
a5e1f35c 1461// Returns a list of all attempts by a user
ebc3bd2b 1462 return get_records_select("quiz_attempts", "quiz = '$quizid' AND userid = '$userid' AND timefinish > 0",
bdc23be0 1463 "attempt ASC");
3a506ca2 1464}
1465
8d94f5a0 1466
1467function quiz_get_user_attempts_string($quiz, $attempts, $bestgrade) {
1468/// Returns a simple little comma-separated list of all attempts,
6d86b5dc 1469/// with each grade linked to the feedback report and with the best grade highlighted
8d94f5a0 1470
1471 $bestgrade = format_float($bestgrade);
1472 foreach ($attempts as $attempt) {
1473 $attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade);
1474 if ($attemptgrade == $bestgrade) {
d7512435 1475 $userattempts[] = "<span class=\"highlight\"><a href=\"review.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</a></span>";
8d94f5a0 1476 } else {
29d5d0b4 1477 $userattempts[] = "<a href=\"review.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</a>";
8d94f5a0 1478 }
1479 }
1480 return implode(",", $userattempts);
1481}
1482
a5e1f35c 1483function quiz_get_best_grade($quizid, $userid) {
1484/// Get the best current grade for a particular user in a quiz
ebc3bd2b 1485 if (!$grade = get_record("quiz_grades", "quiz", $quizid, "userid", $userid)) {
b0be6c79 1486 return "";
3a506ca2 1487 }
1488
2383cadb 1489 return (round($grade->grade,0));
3a506ca2 1490}
1491
e331eb06 1492function quiz_save_best_grade($quiz, $userid) {
a5e1f35c 1493/// Calculates the best grade out of all attempts at a quiz for a user,
1494/// and then saves that grade in the quiz_grades table.
1495
e331eb06 1496 if (!$attempts = quiz_get_user_attempts($quiz->id, $userid)) {
e909c8d0 1497 notify("Could not find any user attempts");
a5e1f35c 1498 return false;
1499 }
1500
1501 $bestgrade = quiz_calculate_best_grade($quiz, $attempts);
1502 $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade);
1503
ebc3bd2b 1504 if ($grade = get_record("quiz_grades", "quiz", $quiz->id, "userid", $userid)) {
1d2603b1 1505 $grade->grade = round($bestgrade, 2);
a5e1f35c 1506 $grade->timemodified = time();
1507 if (!update_record("quiz_grades", $grade)) {
e909c8d0 1508 notify("Could not update best grade");
a5e1f35c 1509 return false;
1510 }
1511 } else {
1512 $grade->quiz = $quiz->id;
ebc3bd2b 1513 $grade->userid = $userid;
38f03e5a 1514 $grade->grade = round($bestgrade, 2);
a5e1f35c 1515 $grade->timemodified = time();
1516 if (!insert_record("quiz_grades", $grade)) {
e909c8d0 1517 notify("Could not insert new best grade");
a5e1f35c 1518 return false;
1519 }
1520 }
1521 return true;
1522}
1523
1524
3a506ca2 1525function quiz_calculate_best_grade($quiz, $attempts) {
a5e1f35c 1526/// Calculate the best grade for a quiz given a number of attempts by a particular user.
3a506ca2 1527
1528 switch ($quiz->grademethod) {
a5e1f35c 1529
1530 case ATTEMPTFIRST:
3a506ca2 1531 foreach ($attempts as $attempt) {
a5e1f35c 1532 return $attempt->sumgrades;
3a506ca2 1533 }
a5e1f35c 1534 break;
1535
1536 case ATTEMPTLAST:
1537 foreach ($attempts as $attempt) {
1538 $final = $attempt->sumgrades;
1539 }
1540 return $final;
3a506ca2 1541
a5e1f35c 1542 case GRADEAVERAGE:
3a506ca2 1543 $sum = 0;
1544 $count = 0;
1545 foreach ($attempts as $attempt) {
a5e1f35c 1546 $sum += $attempt->sumgrades;
3a506ca2 1547 $count++;
1548 }
1549 return (float)$sum/$count;
1550
3a506ca2 1551 default:
a5e1f35c 1552 case GRADEHIGHEST:
1553 $max = 0;
3a506ca2 1554 foreach ($attempts as $attempt) {
a5e1f35c 1555 if ($attempt->sumgrades > $max) {
1556 $max = $attempt->sumgrades;
1557 }
3a506ca2 1558 }
a5e1f35c 1559 return $max;
1560 }
1561}
1562
34d52ad7 1563
40b1a221 1564function quiz_calculate_best_attempt($quiz, $attempts) {
1565/// Return the attempt with the best grade for a quiz
1566
1567 switch ($quiz->grademethod) {
1568
1569 case ATTEMPTFIRST:
1570 foreach ($attempts as $attempt) {
1571 return $attempt;
1572 }
1573 break;
1574
1575 case GRADEAVERAGE: // need to do something with it :-)
1576 case ATTEMPTLAST:
1577 foreach ($attempts as $attempt) {
1578 $final = $attempt;
1579 }
1580 return $final;
1581
1582 default:
1583 case GRADEHIGHEST:
1584 $max = -1;
1585 foreach ($attempts as $attempt) {
1586 if ($attempt->sumgrades > $max) {
1587 $max = $attempt->sumgrades;
1588 $maxattempt = $attempt;
1589 }
1590 }
1591 return $maxattempt;
1592 }
1593}
1594
1595
a5e1f35c 1596function quiz_save_attempt($quiz, $questions, $result, $attemptnum) {
1597/// Given a quiz, a list of attempted questions and a total grade
1598/// this function saves EVERYTHING so it can be reconstructed later
1599/// if necessary.
1600
1601 global $USER;
1602
958aafe2 1603 // First find the attempt in the database (start of attempt)
1604
1605 if (!$attempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
1606 notify("Trying to save an attempt that was not started!");
1607 return false;
1608 }
1609
1610 if ($attempt->attempt != $attemptnum) { // Double check.
1611 notify("Number of this attempt is different to the unfinished one!");
1612 return false;
1613 }
1614
1615 // Now let's complete this record and save it
a5e1f35c 1616
a5e1f35c 1617 $attempt->sumgrades = $result->sumgrades;
958aafe2 1618 $attempt->timefinish = time();
a5e1f35c 1619 $attempt->timemodified = time();
1620
958aafe2 1621 if (! update_record("quiz_attempts", $attempt)) {
7520988b 1622 notify("Error while saving attempt");
a5e1f35c 1623 return false;
1624 }
1625
1626 // Now let's save all the questions for this attempt
1627
1628 foreach ($questions as $question) {
1629 $response->attempt = $attempt->id;
1630 $response->question = $question->id;
1631 $response->grade = $result->grades[$question->id];
34d52ad7 1632
77cff589 1633 if (!empty($question->random)) {
34d52ad7 1634 // First save the response of the random question
1635 // the answer is the id of the REAL response
1636 $response->answer = $question->random;
1637 if (!insert_record("quiz_responses", $response)) {
1638 notify("Error while saving response");
1639 return false;
1640 }
1641 $response->question = $question->random;
1642 }
1643
54d0590b 1644 if (!empty($question->answer)) {
a5e1f35c 1645 $response->answer = implode(",",$question->answer);
1646 } else {
1647 $response->answer = "";
1648 }
1649 if (!insert_record("quiz_responses", $response)) {
7520988b 1650 notify("Error while saving response");
a5e1f35c 1651 return false;
1652 }
3a506ca2 1653 }
fc4a5a2f 1654 return $attempt;
3a506ca2 1655}
730fd187 1656
7c9c2a8d 1657function quiz_grade_attempt_question_result($question,
1658 $answers,
1659 $gradecanbenegative= false)
1660{
44fc346f 1661 $grade = 0; // default
1662 $correct = array();
1663 $feedback = array();
1664 $response = array();
1665
1666 switch ($question->qtype) {
1667 case SHORTANSWER:
1668 if ($question->answer) {
1669 $question->answer = trim(stripslashes($question->answer[0]));
1670 } else {
1671 $question->answer = "";
1672 }
1673 $response[0] = $question->answer;
5276dc48 1674 $feedback[0] = ''; // Default
44fc346f 1675 foreach ($answers as $answer) { // There might be multiple right answers
d7512435 1676
1677 $answer->answer = trim($answer->answer); // Just in case
1678
5276dc48 1679 if ($answer->fraction >= 1.0) {
1680 $correct[] = $answer->answer;
44fc346f 1681 }
1682 if (!$answer->usecase) { // Don't compare case
1683 $answer->answer = strtolower($answer->answer);
1684 $question->answer = strtolower($question->answer);
1685 }
0ce4aa1a 1686
5276dc48 1687 $potentialgrade = (float)$answer->fraction * $question->grade;
1688
8c0f4966 1689 if ($potentialgrade >= $grade and (strpos(' '.$answer->answer, '*'))) {
1690 $answer->answer = str_replace('\*','@@@@@@',$answer->answer);
1691 $answer->answer = str_replace('*','.*',$answer->answer);
1692 $answer->answer = str_replace('@@@@@@', '\*',$answer->answer);
a3848092 1693 $answer->answer = str_replace('+', '\+',$answer->answer);
0ce4aa1a 1694 if (eregi('^'.$answer->answer.'$', $question->answer)) {
1695 $feedback[0] = $answer->feedback;
5276dc48 1696 $grade = $potentialgrade;
0ce4aa1a 1697 }
5276dc48 1698
1699 } else if ($answer->answer == $question->answer) {
1700 $feedback[0] = $answer->feedback;
1701 $grade = $potentialgrade;
44fc346f 1702 }
1703 }
d396ba79 1704
44fc346f 1705 break;
1706
1707 case NUMERICAL:
1708 if ($question->answer) {
1709 $question->answer = trim(stripslashes($question->answer[0]));
1710 } else {
1711 $question->answer = "";
1712 }
1713 $response[0] = $question->answer;
1714 $bestshortanswer = 0;
1715 foreach ($answers as $answer) { // There might be multiple right answers
1716 if ($answer->fraction > $bestshortanswer) {
1717 $correct[$answer->id] = $answer->answer;
1718 $bestshortanswer = $answer->fraction;
16a19172 1719 $feedback[0] = $answer->feedback; // Show feedback for best answer
44fc346f 1720 }
bdb63d64 1721 if ('' != $question->answer // Must not be mixed up with zero!
1722 && (float)$answer->fraction > (float)$grade // Do we need to bother?
1723 and // and has lower procedence than && and ||.
1724 strtolower($question->answer) == strtolower($answer->answer)
1725 || '' != trim($answer->min)
1726 && ((float)$question->answer >= (float)$answer->min)
1727 && ((float)$question->answer <= (float)$answer->max))
1728 {
16a19172 1729 //$feedback[0] = $answer->feedback; No feedback was shown for wrong answers
bdb63d64 1730 $grade = (float)$answer->fraction;
44fc346f 1731 }
1732 }
bdb63d64 1733 $grade *= $question->grade; // Normalize to correct weight
44fc346f 1734 break;
1735
1736 case TRUEFALSE:
1737 if ($question->answer) {
1738 $question->answer = $question->answer[0];
1739 } else {
1740 $question->answer = NULL;
1741 }
1742 foreach($answers as $answer) { // There should be two answers (true and false)
1743 $feedback[$answer->id] = $answer->feedback;
1744 if ($answer->fraction > 0) {
1745 $correct[$answer->id] = true;
1746 }
1747 if ($question->answer == $answer->id) {
1748 $grade = (float)$answer->fraction * $question->grade;
1749 $response[$answer->id] = true;
1750 }
1751 }
1752 break;
1753
1754
1755 case MULTICHOICE:
1756 foreach($answers as $answer) { // There will be multiple answers, perhaps more than one is right
1757 $feedback[$answer->id] = $answer->feedback;
1758 if ($answer->fraction > 0) {
1759 $correct[$answer->id] = true;
1760 }
1761 if (!empty($question->answer)) {
1762 foreach ($question->answer as $questionanswer) {
1763 if ($questionanswer == $answer->id) {
1764 $response[$answer->id] = true;
1765 if ($answer->single) {
1766 $grade = (float)$answer->fraction * $question->grade;
1767 continue;
1768 } else {
1769 $grade += (float)$answer->fraction * $question->grade;
1770 }
1771 }
1772 }
1773 }
1774 }
1775 break;
1776
1777 case MATCH:
1778 $matchcount = $totalcount = 0;
1779
1780 foreach ($question->answer as $questionanswer) { // Each answer is "subquestionid-answerid"
1781 $totalcount++;
1782 $qarr = explode('-', $questionanswer); // Extract subquestion/answer.
1783 $subquestionid = $qarr[0];
1784 $subanswerid = $qarr[1];
1785 if ($subquestionid and $subanswerid and (($subquestionid == $subanswerid) or
1786 ($answers[$subquestionid]->answertext == $answers[$subanswerid]->answertext))) {
1787 // Either the ids match exactly, or the answertexts match exactly
1788 // (in case two subquestions had the same answer)
1789 $matchcount++;
1790 $correct[$subquestionid] = true;
1791 } else {
1792 $correct[$subquestionid] = false;
1793 }
1794 $response[$subquestionid] = $subanswerid;
1795 }
1796
1797 $grade = $question->grade * $matchcount / $totalcount;
1798
1799 break;
1800
1801 case RANDOMSAMATCH:
1802 $bestanswer = array();
1803 foreach ($answers as $answer) { // Loop through them all looking for correct answers
1804 if (empty($bestanswer[$answer->question])) {
1805 $bestanswer[$answer->question] = 0;
1806 $correct[$answer->question] = "";
1807 }
1808 if ($answer->fraction > $bestanswer[$answer->question]) {
1809 $bestanswer[$answer->question] = $answer->fraction;
1810 $correct[$answer->question] = $answer->answer;
1811 }
1812 }
1813 $answerfraction = 1.0 / (float) count($question->answer);
1814 foreach ($question->answer as $questionanswer) { // For each random answered question
1815 $rqarr = explode('-', $questionanswer); // Extract question/answer.
1816 $rquestion = $rqarr[0];
1817 $ranswer = $rqarr[1];
1818 $response[$rquestion] = $questionanswer;
1819 if (isset($answers[$ranswer])) { // If the answer exists in the list
1820 $answer = $answers[$ranswer];
1821 $feedback[$rquestion] = $answer->feedback;
1822 if ($answer->question == $rquestion) { // Check that this answer matches the question
1823 $grade += (float)$answer->fraction * $question->grade * $answerfraction;
1824 }
1825 }
1826 }
1827 break;
1828
8b439f8c 1829 case MULTIANSWER:
1830 // Default setting that avoids a possible divide by zero:
1831 $subquestion->grade = 1.0;
1832
1833 foreach ($question->answer as $questionanswer) {
1834
1835 // Resetting default values for subresult:
1836 $subresult->grade = 0.0;
1837 $subresult->correct = array();
1838 $subresult->feedback = array();
1839
1840 // Resetting subquestion responses:
1841 $subquestion->answer = array();
1842
1843 $qarr = explode('-', $questionanswer, 2);
1844 $subquestion->answer[] = $qarr[1]; // Always single answer for subquestions
1845 foreach ($answers as $multianswer) {
1846 if ($multianswer->id == $qarr[0]) {
1847 $subquestion->qtype = $multianswer->answertype;
1848 $subquestion->grade = $multianswer->norm;
d396ba79 1849 $subresult = quiz_grade_attempt_question_result($subquestion, $multianswer->subanswers, true);
8b439f8c 1850 break;
1851 }
1852 }
1853
d396ba79 1854
8b439f8c 1855 // Summarize subquestion results:
1856 $grade += $subresult->grade;
1857 $feedback[] = $subresult->feedback[0];
1858 $correct[] = $subresult->correct[0];
1859
1860 // Each response instance also contains the partial
1861 // fraction grade for the response:
1862 $response[] = $subresult->grade/$subquestion->grade
1863 . '-' . $subquestion->answer[0];
1864 }
1865 // Normalize grade:
1866 $grade *= $question->grade/($question->defaultgrade);
1867 break;
1868
44fc346f 1869 case DESCRIPTION: // Descriptions are not graded.
1870 break;
1871
1872 case RANDOM: // Returns a recursive call with the real question
1873 $realquestion = get_record
1874 ('quiz_questions', 'id', $question->random);
1875 $realquestion->answer = $question->answer;
1876 $realquestion->grade = $question->grade;
1877 return quiz_grade_attempt_question_result($realquestion, $answers);
1878 }
1879
7c9c2a8d 1880 $result->grade =
1881 $gradecanbenegative ? $grade // Grade can be negative
1882 : max(0.0, $grade); // Grade must not be negative
44fc346f 1883 $result->correct = $correct;
1884 $result->feedback = $feedback;
1885 $result->response = $response;
1886 return $result;
1887}
a5e1f35c 1888
1889function quiz_grade_attempt_results($quiz, $questions) {
1890/// Given a list of questions (including answers for each one)
1891/// this function does all the hard work of calculating the
1892/// grades for each question, as well as a total grade for
1893/// for the whole quiz. It returns everything in a structure
1894/// that looks like:
1895/// $result->sumgrades (sum of all grades for all questions)
1896/// $result->percentage (Percentage of grades that were correct)
1897/// $result->grade (final grade result for the whole quiz)
1898/// $result->grades[] (array of grades, indexed by question id)
a8a372cc 1899/// $result->response[] (array of response arrays, indexed by question id)
a5e1f35c 1900/// $result->feedback[] (array of feedback arrays, indexed by question id)
8db3eadd 1901/// $result->correct[] (array of feedback arrays, indexed by question id)
a5e1f35c 1902
1903 if (!$questions) {
1904 error("No questions!");
1905 }
34d52ad7 1906
1907 if (!$grades = get_records_menu("quiz_question_grades", "quiz", $quiz->id, "", "question,grade")) {
1908 error("No grades defined for these quiz questions!");
1909 }
1910
a5e1f35c 1911 $result->sumgrades = 0;
1912
1913 foreach ($questions as $question) {
34d52ad7 1914
44fc346f 1915 $question->grade = $grades[$question->id];
34d52ad7 1916
44fc346f 1917 if (!$answers = quiz_get_answers($question)) {
1918 error("No answers defined for question id $question->id!");
a5e1f35c 1919 }
34d52ad7 1920
44fc346f 1921 $questionresult = quiz_grade_attempt_question_result($question,
1922 $answers);
10b9291c 1923
44fc346f 1924 $result->grades[$question->id] = round($questionresult->grade, 2);
1925 $result->sumgrades += $questionresult->grade;
1926 $result->feedback[$question->id] = $questionresult->feedback;
1927 $result->response[$question->id] = $questionresult->response;
1928 $result->correct[$question->id] = $questionresult->correct;
a5e1f35c 1929 }
1930
8d94f5a0 1931 $fraction = (float)($result->sumgrades / $quiz->sumgrades);
1932 $result->percentage = format_float($fraction * 100.0);
1933 $result->grade = format_float($fraction * $quiz->grade);
3a50203f 1934 $result->sumgrades = round($result->sumgrades, 2);
a5e1f35c 1935
1936 return $result;
1937}
6d86b5dc 1938
1939
49220fa7 1940function quiz_save_question_options($question) {
1941/// Given some question info and some data about the the answers
1942/// this function parses, organises and saves the question
1943/// It is used by question.php when saving new data from a
1944/// form, and also by import.php when importing questions
77cff589 1945///
1946/// If this is an update, and old answers already exist, then
1947/// these are overwritten using an update(). To do this, it
1948/// it is assumed that the IDs in quiz_answers are in the same
1949/// sort order as the new answers being saved. This should always
1950/// be true, but it's something to keep in mind if fiddling with
1951/// question.php
49220fa7 1952///
1953/// Returns $result->error or $result->notice
a5e1f35c 1954
49220fa7 1955 switch ($question->qtype) {
1956 case SHORTANSWER:
77cff589 1957
e1122620 1958 if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
1959 $oldanswers = array();
1960 }
49220fa7 1961
1962 $answers = array();
1963 $maxfraction = -1;
1964
1965 // Insert all the new answers
1966 foreach ($question->answer as $key => $dataanswer) {
1967 if ($dataanswer != "") {
77cff589 1968 if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
1969 $answer = $oldanswer;
d7512435 1970 $answer->answer = trim($dataanswer);
77cff589 1971 $answer->fraction = $question->fraction[$key];
1972 $answer->feedback = $question->feedback[$key];
1973 if (!update_record("quiz_answers", $answer)) {
1974 $result->error = "Could not update quiz answer! (id=$answer->id)";
1975 return $result;
1976 }
1977 } else { // This is a completely new answer
1978 unset($answer);
d7512435 1979 $answer->answer = trim($dataanswer);
77cff589 1980 $answer->question = $question->id;
1981 $answer->fraction = $question->fraction[$key];
1982 $answer->feedback = $question->feedback[$key];
1983 if (!$answer->id = insert_record("quiz_answers", $answer)) {
1984 $result->error = "Could not insert quiz answer!";
1985 return $result;
1986 }
49220fa7 1987 }
1988 $answers[] = $answer->id;
1989 if ($question->fraction[$key] > $maxfraction) {
1990 $maxfraction = $question->fraction[$key];
1991 }
1992 }
1993 }
1994
77cff589 1995 if ($options = get_record("quiz_shortanswer", "question", $question->id)) {
1996 $options->answers = implode(",",$answers);
1997 $options->usecase = $question->usecase;
1998 if (!update_record("quiz_shortanswer", $options)) {
1999 $result->error = "Could not update quiz shortanswer options! (id=$options->id)";
2000 return $result;
2001 }
2002 } else {
2003 unset($options);
2004 $options->question = $question->id;
2005 $options->answers = implode(",",$answers);
2006 $options->usecase = $question->usecase;
2007 if (!insert_record("quiz_shortanswer", $options)) {
2008 $result->error = "Could not insert quiz shortanswer options!";
2009 return $result;
2010 }
49220fa7 2011 }
2012
2013 /// Perform sanity checks on fractional grades
2014 if ($maxfraction != 1) {
2015 $maxfraction = $maxfraction * 100;
2016 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
2017 return $result;
2018 }
2019 break;
77cff589 2020
361f649d 2021 case NUMERICAL: // Note similarities to SHORTANSWER
2022
2023 if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
2024 $oldanswers = array();
2025 }
2026
2027 $answers = array();
2028 $maxfraction = -1;
2029
2030 // Insert all the new answers
2031 foreach ($question->answer as $key => $dataanswer) {
2032 if ($dataanswer != "") {
2033 if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
2034 $answer = $oldanswer;
2035 $answer->answer = $dataanswer;
2036 $answer->fraction = $question->fraction[$key];
2037 $answer->feedback = $question->feedback[$key];
2038 if (!update_record("quiz_answers", $answer)) {
2039 $result->error = "Could not update quiz answer! (id=$answer->id)";
2040 return $result;
2041 }
2042 } else { // This is a completely new answer
2043 unset($answer);
2044 $answer->answer = $dataanswer;
2045 $answer->question = $question->id;
2046 $answer->fraction = $question->fraction[$key];
2047 $answer->feedback = $question->feedback[$key];
2048 if (!$answer->id = insert_record("quiz_answers", $answer)) {
2049 $result->error = "Could not insert quiz answer!";
2050 return $result;
2051 }
2052 }
2053 $answers[] = $answer->id;
2054 if ($question->fraction[$key] > $maxfraction) {
2055 $maxfraction = $question->fraction[$key];
2056 }
2057
2058 if ($options = get_record("quiz_numerical", "answer", $answer->id)) {
2059 $options->min= $question->min[$key];
2060 $options->max= $question->max[$key];
2061 if (!update_record("quiz_numerical", $options)) {
2062 $result->error = "Could not update quiz numerical options! (id=$options->id)";
2063 return $result;
2064 }
2065 } else { // completely new answer
2066 unset($options);
9c026610 2067 $options->question = $question->id;
2068 $options->answer = $answer->id;
2069 $options->min = $question->min[$key];
2070 $options->max = $question->max[$key];
361f649d 2071 if (!insert_record("quiz_numerical", $options)) {
2072 $result->error = "Could not insert quiz numerical options!";
2073 return $result;
2074 }
2075 }
2076 }
2077 }
2078
2079 /// Perform sanity checks on fractional grades
2080 if ($maxfraction != 1) {
2081 $maxfraction = $maxfraction * 100;
2082 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
2083 return $result;
2084 }
2085 break;
77cff589 2086
2087
49220fa7 2088 case TRUEFALSE:
77cff589 2089
e1122620 2090 if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
2091 $oldanswers = array();
2092 }
77cff589 2093
2094 if ($true = array_shift($oldanswers)) { // Existing answer, so reuse it
4d01ada3 2095 $true->answer = get_string("true", "quiz");
77cff589 2096 $true->fraction = $question->answer;
2097 $true->feedback = $question->feedbacktrue;
2098 if (!update_record("quiz_answers", $true)) {
2099 $result->error = "Could not update quiz answer \"true\")!";
2100 return $result;
2101 }
2102 } else {
2103 unset($true);
4d01ada3 2104 $true->answer = get_string("true", "quiz");
77cff589 2105 $true->question = $question->id;
2106 $true->fraction = $question->answer;
2107 $true->feedback = $question->feedbacktrue;
2108 if (!$true->id = insert_record("quiz_answers", $true)) {
2109 $result->error = "Could not insert quiz answer \"true\")!";
2110 return $result;
2111 }
49220fa7 2112 }
2113
77cff589 2114 if ($false = array_shift($oldanswers)) { // Existing answer, so reuse it
4d01ada3 2115 $false->answer = get_string("false", "quiz");
77cff589 2116 $false->fraction = 1 - (int)$question->answer;
2117 $false->feedback = $question->feedbackfalse;
2118 if (!update_record("quiz_answers", $false)) {
2119 $result->error = "Could not insert quiz answer \"false\")!";
2120 return $result;
2121 }
2122 } else {
2123 unset($false);
4d01ada3 2124 $false->answer = get_string("false", "quiz");
77cff589 2125 $false->question = $question->id;
2126 $false->fraction = 1 - (int)$question->answer;
2127 $false->feedback = $question->feedbackfalse;
2128 if (!$false->id = insert_record("quiz_answers", $false)) {
2129 $result->error = "Could not insert quiz answer \"false\")!";
2130 return $result;
2131 }
49220fa7 2132 }
2133
77cff589 2134 if ($options = get_record("quiz_truefalse", "question", $question->id)) {
2135 // No need to do anything, since the answer IDs won't have changed
2136 // But we'll do it anyway, just for robustness
2137 $options->trueanswer = $true->id;
2138 $options->falseanswer = $false->id;
2139 if (!update_record("quiz_truefalse", $options)) {
2140 $result->error = "Could not update quiz truefalse options! (id=$options->id)";
2141 return $result;
2142 }
2143 } else {
2144 unset($options);
2145 $options->question = $question->id;
2146 $options->trueanswer = $true->id;
2147 $options->falseanswer = $false->id;
2148 if (!insert_record("quiz_truefalse", $options)) {
2149 $result->error = "Could not insert quiz truefalse options!";
2150 return $result;
2151 }
49220fa7 2152 }
2153 break;
77cff589 2154
2155
49220fa7 2156 case MULTICHOICE:
77cff589 2157
e1122620 2158 if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
2159 $oldanswers = array();
2160 }
49220fa7 2161
2162 $totalfraction = 0;
2163 $maxfraction = -1;
2164
2165 $answers = array();
2166
2167 // Insert all the new answers
2168 foreach ($question->answer as $key => $dataanswer) {
2169 if ($dataanswer != "") {
77cff589 2170 if ($answer = array_shift($oldanswers)) { // Existing answer, so reuse it
2171 $answer->answer = $dataanswer;
2172 $answer->fraction = $question->fraction[$key];
2173 $answer->feedback = $question->feedback[$key];
2174 if (!update_record("quiz_answers", $answer)) {
2175 $result->error = "Could not update quiz answer! (id=$answer->id)";
2176 return $result;
2177 }
2178 } else {
2179 unset($answer);
2180 $answer->answer = $dataanswer;
2181 $answer->question = $question->id;
2182 $answer->fraction = $question->fraction[$key];
2183 $answer->feedback = $question->feedback[$key];
2184 if (!$answer->id = insert_record("quiz_answers", $answer)) {
2185 $result->error = "Could not insert quiz answer! ";
2186 return $result;
2187 }
49220fa7 2188 }
2189 $answers[] = $answer->id;
2190
2191 if ($question->fraction[$key] > 0) { // Sanity checks
2192 $totalfraction += $question->fraction[$key];
2193 }
2194 if ($question->fraction[$key] > $maxfraction) {
2195 $maxfraction = $question->fraction[$key];
2196 }
2197 }
2198 }
2199
77cff589 2200 if ($options = get_record("quiz_multichoice", "question", $question->id)) {
2201 $options->answers = implode(",",$answers);
2202 $options->single = $question->single;
2203 if (!update_record("quiz_multichoice", $options)) {
2204 $result->error = "Could not update quiz multichoice options! (id=$options->id)";
2205 return $result;
2206 }
2207 } else {
2208 unset($options);
2209 $options->question = $question->id;
2210 $options->answers = implode(",",$answers);
2211 $options->single = $question->single;
2212 if (!insert_record("quiz_multichoice", $options)) {
2213 $result->error = "Could not insert quiz multichoice options!";
2214 return $result;
2215 }
49220fa7 2216 }
2217
2218 /// Perform sanity checks on fractional grades
2219 if ($options->single) {
2220 if ($maxfraction != 1) {
2221 $maxfraction = $maxfraction * 100;
2222 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
2223 return $result;
2224 }
2225 } else {
2226 $totalfraction = round($totalfraction,2);
2227 if ($totalfraction != 1) {
2228 $totalfraction = $totalfraction * 100;
2229 $result->notice = get_string("fractionsaddwrong", "quiz", $totalfraction);
2230 return $result;
2231 }
2232 }
2233 break;
95dbc030 2234
54a67a59 2235 case MATCH:
77cff589 2236
e1122620 2237 if (!$oldsubquestions = get_records("quiz_match_sub", "question", $question->id, "id ASC")) {
2238 $oldsubquestions = array();
2239 }
54a67a59 2240
2241 $subquestions = array();
2242
2243 // Insert all the new question+answer pairs
2244 foreach ($question->subquestions as $key => $questiontext) {
2245 $answertext = $question->subanswers[$key];
2246 if (!empty($questiontext) and !empty($answertext)) {
77cff589 2247 if ($subquestion = array_shift($oldsubquestions)) { // Existing answer, so reuse it
2248 $subquestion->questiontext = $questiontext;
2249 $subquestion->answertext = $answertext;
2250 if (!update_record("quiz_match_sub", $subquestion)) {
2251 $result->error = "Could not insert quiz match subquestion! (id=$subquestion->id)";
2252 return $result;
2253 }
2254 } else {
2255 unset($subquestion);
2256 $subquestion->question = $question->id;
2257 $subquestion->questiontext = $questiontext;
2258 $subquestion->answertext = $answertext;
2259 if (!$subquestion->id = insert_record("quiz_match_sub", $subquestion)) {
2260 $result->error = "Could not insert quiz match subquestion!";
2261 return $result;
2262 }
54a67a59 2263 }
2264 $subquestions[] = $subquestion->id;
2265 }
2266 }
2267
2268 if (count($subquestions) < 3) {
2269 $result->notice = get_string("notenoughsubquestions", "quiz");
2270 return $result;
2271 }
2272
77cff589 2273 if ($options = get_record("quiz_match", "question", $question->id)) {
2274 $options->subquestions = implode(",",$subquestions);
2275 if (!update_record("quiz_match", $options)) {
2276 $result->error = "Could not update quiz match options! (id=$options->id)";
2277 return $result;
2278 }
2279 } else {
2280 unset($options);
2281 $options->question = $question->id;
2282 $options->subquestions = implode(",",$subquestions);
2283 if (!insert_record("quiz_match", $options)) {
2284 $result->error = "Could not insert quiz match options!";
2285 return $result;
2286 }
54a67a59 2287 }
2288
2289 break;
2290
77cff589 2291
54a67a59 2292 case RANDOMSAMATCH:
95dbc030 2293 $options->question = $question->id;
2294 $options->choose = $question->choose;
54a67a59 2295 if ($existing = get_record("quiz_randomsamatch", "question", $options->question)) {
95dbc030 2296 $options->id = $existing->id;
54a67a59 2297 if (!update_record("quiz_randomsamatch", $options)) {
2298 $result->error = "Could not update quiz randomsamatch options!";
95dbc030 2299 return $result;
2300 }
2301 } else {
54a67a59 2302 if (!insert_record("quiz_randomsamatch", $options)) {
2303 $result->error = "Could not insert quiz randomsamatch options!";
95dbc030 2304 return $result;
2305 }
2306 }
2307 break;
2308
8b439f8c 2309 case MULTIANSWER:
2310 if (!$oldmultianswers = get_records("quiz_multianswers", "question", $question->id, "id ASC")) {
2311 $oldmultianswers = array();
2312 }
2313
2314 // Insert all the new multi answers
2315 foreach ($question->answers as $dataanswer) {
2316 if ($oldmultianswer = array_shift($oldmultianswers)) { // Existing answer, so reuse it
2317 $multianswer = $oldmultianswer;
2318 $multianswer->positionkey = $dataanswer->positionkey;
2319 $multianswer->norm = $dataanswer->norm;
2320 $multianswer->answertype = $dataanswer->answertype;
2321
2322 if (! $multianswer->answers = quiz_save_multianswer_alternatives
2323 ($question->id, $dataanswer->answertype,
2324 $dataanswer->alternatives, $oldmultianswer->answers))
2325 {
2326 $result->error = "Could not update multianswer alternatives! (id=$multianswer->id)";
2327 return $result;
2328 }
2329 if (!update_record("quiz_multianswers", $multianswer)) {
2330 $result->error = "Could not update quiz multianswer! (id=$multianswer->id)";
2331 return $result;
2332 }
2333 } else { // This is a completely new answer
2334 unset($multianswer);
2335 $multianswer->question = $question->id;
2336 $multianswer->positionkey = $dataanswer->positionkey;
2337 $multianswer->norm = $dataanswer->norm;
2338 $multianswer->answertype = $dataanswer->answertype;
2339
2340 if (! $multianswer->answers = quiz_save_multianswer_alternatives
2341 ($question->id, $dataanswer->answertype,
2342 $dataanswer->alternatives))
2343 {
2344 $result->error = "Could not insert multianswer alternatives! (questionid=$question->id)";
2345 return $result;
2346 }
2347 if (!insert_record("quiz_multianswers", $multianswer)) {
2348 $result->error = "Could not insert quiz multianswer!";
2349 return $result;
2350 }
2351 }
2352 }
2353 break;
2354
34d52ad7 2355 case RANDOM:
2356 break;
2357
401c8de6 2358 case DESCRIPTION:
2359 break;
2360
49220fa7 2361 default:
2362 $result->error = "Unsupported question type ($question->qtype)!";
2363 return $result;
2364 break;
2365 }
2366 return true;
2367}
2368
2369
29d5d0b4 2370function quiz_remove_unwanted_questions(&$questions, $quiz) {
40b1a221 2371/// Given an array of questions, and a list of question IDs,
2372/// this function removes unwanted questions from the array
586b2c82 2373/// Used by review.php and attempt.php to counter changing quizzes
29d5d0b4 2374
2375 $quizquestions = array();
2376 $quizids = explode(",", $quiz->questions);
2377 foreach ($quizids as $quizid) {
2378 $quizquestions[$quizid] = true;
2379 }
2380 foreach ($questions as $key => $question) {
2381 if (!isset($quizquestions[$question->id])) {
2382 unset($questions[$key]);
2383 }
2384 }
2385}
2386
8b439f8c 2387function quiz_save_multianswer_alternatives
2388 ($questionid, $answertype, $alternatives, $oldalternativeids= NULL)
2389{
2390// Returns false if something goes wrong,
2391// otherwise the ids of the answers.
95dbc030 2392
8b439f8c 2393 if (empty($oldalternativeids)
17546249 2394 or !($oldalternatives =
2395 get_records_list('quiz_answers', 'id', $oldalternativeids)))
8b439f8c 2396 {
2397 $oldalternatives = array();
2398 }
2399
2400 $alternativeids = array();
2401
2402 foreach ($alternatives as $altdata) {
2403
2404 if ($altold = array_shift($oldalternatives)) { // Use existing one...
2405 $alt = $altold;
2406 $alt->answer = $altdata->answer;
2407 $alt->fraction = $altdata->fraction;
2408 $alt->feedback = $altdata->feedback;
2409 if (!update_record("quiz_answers", $alt)) {
2410 return false;
2411 }
2412
2413 } else { // Completely new one
2414 unset($alt);
2415 $alt->question= $questionid;
2416 $alt->answer = $altdata->answer;
2417 $alt->fraction = $altdata->fraction;
2418 $alt->feedback = $altdata->feedback;
17546249 2419 if (!($alt->id = insert_record("quiz_answers", $alt))) {
8b439f8c 2420 return false;
2421 }
2422 }
2423
2424 // For the answer type numerical, each alternative has individual options:
2425 if ($answertype == NUMERICAL) {
2426 if ($numericaloptions =
2427 get_record('quiz_numerical', 'answer', $alt->id))
2428 {
2429 // Reuse existing numerical options
2430 $numericaloptions->min = $altdata->min;
2431 $numericaloptions->max = $altdata->max;
2432 if (!update_record('quiz_numerical', $numericaloptions)) {
2433 return false;
2434 }
2435 } else {
2436 // New numerical options
2437 $numericaloptions->answer = $alt->id;
2438 $numericaloptions->question = $questionid;
2439 $numericaloptions->min = $altdata->min;
2440 $numericaloptions->max = $altdata->max;
2441 if (!insert_record("quiz_numerical", $numericaloptions)) {
2442 return false;
2443 }
2444 }
2445 } else { // Delete obsolete numerical options
2446 delete_records('quiz_numerical', 'answer', $alt->id);
2447 } // end if NUMERICAL
2448
2449 $alternativeids[] = $alt->id;
2450 } // end foreach $alternatives
2451 $answers = implode(',', $alternativeids);
2452
2453 // Removal of obsolete alternatives from answers and quiz_numerical:
2454 while ($altobsolete = array_shift($oldalternatives)) {
2455 delete_records("quiz_answers", "id", $altobsolete->id);
2456
2457 // Possibly obsolute numerical options are also to be deleted:
17546249 2458 delete_records("quiz_numerical", 'answer', $altobsolete->id);
8b439f8c 2459 }
2460
2461 // Common alternative options and removal of obsolete options
2462 switch ($answertype) {
2463 case NUMERICAL:
2464 if (!empty($oldalternativeids)) {
2465 delete_records('quiz_shortanswer', 'answers',
2466$oldalternativeids);
2467 delete_records('quiz_multichoice', 'answers',
2468$oldalternativeids);
2469 }
2470 break;
2471 case SHORTANSWER:
2472 if (!empty($oldalternativeids)) {
2473 delete_records('quiz_multichoice', 'answers',
2474$oldalternativeids);
2475 $options = get_record('quiz_shortanswer',
2476 'answers', $oldalternativeids);
2477 } else {
2478 unset($options);
2479 }
2480 if (empty($options)) {
2481 // Create new shortanswer options
2482 $options->question = $questionid;
2483 $options->usecase = 0;
2484 $options->answers = $answers;
2485 if (!insert_record('quiz_shortanswer', $options)) {
2486 return false;
2487 }
2488 } else if ($answers != $oldalternativeids) {
2489 // Shortanswer options needs update:
2490 $options->answers = $answers;
2491 if (!update_record('quiz_shortanswer', $options)) {
2492 return false;
2493 }
2494 }
2495 break;
2496 case MULTICHOICE:
2497 if (!empty($oldalternativeids)) {
2498 delete_records('quiz_shortanswer', 'answers',
2499$oldalternativeids);
2500 $options = get_record('quiz_multichoice',
2501 'answers', $oldalternativeids);
2502 } else {
2503 unset($options);
2504 }
2505 if (empty($options)) {
2506 // Create new multichoice options
2507 $options->question = $questionid;
2508 $options->layout = 0;
2509 $options->single = 1;
2510 $options->answers = $answers;
2511 if (!insert_record('quiz_multichoice', $options)) {
2512 return false;
2513 }
2514 } else if ($answers != $oldalternativeids) {
2515 // Multichoice options needs update:
2516 $options->answers = $answers;
2517 if (!update_record('quiz_multichoice', $options)) {
2518 return false;
2519 }
2520 }
2521 break;
2522 default:
2523 return false;
2524 }
2525 return $answers;
2526}
95dbc030 2527
740f94d8 2528function quiz_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $quiz="0", $user="", $groupid="") {
fdba9bb4 2529// Returns all quizzes since a given time. If quiz is specified then
2530// this restricts the results
2531
2532 global $CFG;
2533
2534 if ($quiz) {
740f94d8 2535 $quizselect = " AND cm.id = '$quiz'";
fdba9bb4 2536 } else {
2537 $quizselect = "";
2538 }
0eb7764d 2539 if ($user) {
2540 $userselect = " AND u.id = '$user'";
2541 } else {
2542 $userselect = "";
2543 }
fdba9bb4 2544
740f94d8 2545 $quizzes = get_records_sql("SELECT qa.*, q.name, u.firstname, u.lastname, u.picture,
2546 q.course, q.sumgrades as maxgrade, cm.instance, cm.section
2547 FROM {$CFG->prefix}quiz_attempts qa,
2548 {$CFG->prefix}quiz q,
2549 {$CFG->prefix}user u,
2550 {$CFG->prefix}course_modules cm
2551 WHERE qa.timefinish > '$sincetime'
2552 AND qa.userid = u.id $userselect
2553 AND qa.quiz = q.id $quizselect
2554 AND cm.instance = q.id
2555 AND cm.course = '$courseid'
2556 AND q.course = cm.course
2557 ORDER BY qa.timefinish ASC");
fdba9bb4 2558
6a0c9cf8 2559 if (empty($quizzes))
2560 return;
fdba9bb4 2561
6a0c9cf8 2562 foreach ($quizzes as $quiz) {
2563 if (empty($groupid) || ismember($groupid, $quiz->userid)) {
fdba9bb4 2564
6a0c9cf8 2565 $tmpactivity->type = "quiz";
2566 $tmpactivity->defaultindex = $index;
2567 $tmpactivity->instance = $quiz->quiz;
fdba9bb4 2568
6a0c9cf8 2569 $tmpactivity->name = $quiz->name;
2570 $tmpactivity->section = $quiz->section;
fdba9bb4 2571
6a0c9cf8 2572 $tmpactivity->content->attemptid = $quiz->id;
2573 $tmpactivity->content->sumgrades = $quiz->sumgrades;
2574 $tmpactivity->content->maxgrade = $quiz->maxgrade;
2575 $tmpactivity->content->attempt = $quiz->attempt;
fdba9bb4 2576
6a0c9cf8 2577 $tmpactivity->user->userid = $quiz->userid;
2578 $tmpactivity->user->fullname = fullname($quiz);
2579 $tmpactivity->user->picture = $quiz->picture;
fdba9bb4 2580
6a0c9cf8 2581 $tmpactivity->timestamp = $quiz->timefinish;
fdba9bb4 2582
6a0c9cf8 2583 $activities[] = $tmpactivity;
2584
2585 $index++;
2586 }
2587 }
fdba9bb4 2588
740f94d8 2589 return;
2590}
fdba9bb4 2591
6a0c9cf8 2592
740f94d8 2593function quiz_print_recent_mod_activity($activity, $course, $detail=false) {
2594 global $CFG, $THEME;
fdba9bb4 2595
740f94d8 2596 echo '<table border="0" cellpadding="3" cellspacing="0">';
fdba9bb4 2597
740f94d8 2598 echo "<tr><td bgcolor=\"$THEME->cellcontent2\" class=\"forumpostpicture\" width=\"35\" valign=\"top\">";
2599 print_user_picture($activity->user->userid, $course, $activity->user->picture);
2600 echo "</td><td width=\"100%\"><font size=2>";
2601
2602 if ($detail) {
2603 echo "<img src=\"$CFG->modpixpath/$activity->type/icon.gif\" ".
2604 "height=16 width=16 alt=\"$activity->type\"> ";
2605 echo "<a href=\"$CFG->wwwroot/mod/quiz/view.php?id=" . $activity->instance . "\">"
2606 . $activity->name . "</a> - ";
2607
2608 }
2609
2610 if (isteacher($USER)) {
2611 $grades = "(" . $activity->content->sumgrades . " / " . $activity->content->maxgrade . ") ";
2612 echo "<a href=\"$CFG->wwwroot/mod/quiz/review.php?q="
2613 . $activity->instance . "&attempt="
2614 . $activity->content->attemptid . "\">" . $grades . "</a> ";
2615
2616 echo get_string("attempt", "quiz") . " - " . $activity->content->attempt . "<br>";
fdba9bb4 2617 }
740f94d8 2618 echo "<a href=\"$CFG->wwwroot/user/view.php?id="
2619 . $activity->user->userid . "&course=$course\">"
2620 . $activity->user->fullname . "</a> ";
2621
2622 echo " - " . userdate($activity->timestamp);
2623
2624 echo "</font></td></tr>";
2625 echo "</table>";
2626
2627 return;
fdba9bb4 2628}
eb452548 2629
730fd187 2630?>