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