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