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