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