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