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