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 | |
16 | define("SHORTANSWER", "1"); |
17 | define("TRUEFALSE", "2"); |
18 | define("MULTICHOICE", "3"); |
8db3eadd |
19 | define("RANDOM", "4"); |
95dbc030 |
20 | define("MATCH", "5"); |
21 | define("RANDOMMATCH", "6"); |
22 | |
8d94f5a0 |
23 | $QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz"), |
a5e1f35c |
24 | TRUEFALSE => get_string("truefalse", "quiz"), |
95dbc030 |
25 | SHORTANSWER => get_string("shortanswer", "quiz"), |
26 | RANDOMMATCH => get_string("randommatch", "quiz") ); |
a5e1f35c |
27 | |
49220fa7 |
28 | $QUIZ_FILE_FORMAT = array ( "custom" => get_string("custom", "quiz"), |
29 | "webct" => get_string("webct", "quiz"), |
30 | "qti" => get_string("qti", "quiz"), |
31 | "missingword" => get_string("missingword", "quiz") ); |
32 | |
7d2e5b65 |
33 | define("QUIZ_PICTURE_DEFAULT_HEIGHT", "200"); |
a5e1f35c |
34 | |
35 | /// FUNCTIONS /////////////////////////////////////////////////////////////////// |
730fd187 |
36 | |
37 | function quiz_add_instance($quiz) { |
a5e1f35c |
38 | /// Given an object containing all the necessary data, |
39 | /// (defined by the form in mod.html) this function |
40 | /// will create a new instance and return the id number |
41 | /// of the new instance. |
730fd187 |
42 | |
49dcdd18 |
43 | $quiz->created = time(); |
730fd187 |
44 | $quiz->timemodified = time(); |
49dcdd18 |
45 | $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday, |
c04c41c7 |
46 | $quiz->openhour, $quiz->openminute, 0); |
49dcdd18 |
47 | $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday, |
c04c41c7 |
48 | $quiz->closehour, $quiz->closeminute, 0); |
730fd187 |
49 | |
7bd1aa1d |
50 | if (!$quiz->id = insert_record("quiz", $quiz)) { |
51 | return false; // some error occurred |
52 | } |
53 | |
10b9291c |
54 | // The grades for every question in this quiz are stored in an array |
7bd1aa1d |
55 | if ($quiz->grades) { |
56 | foreach ($quiz->grades as $question => $grade) { |
8d94f5a0 |
57 | if ($question and $grade) { |
58 | unset($questiongrade); |
59 | $questiongrade->quiz = $quiz->id; |
60 | $questiongrade->question = $question; |
61 | $questiongrade->grade = $grade; |
62 | if (!insert_record("quiz_question_grades", $questiongrade)) { |
63 | return false; |
64 | } |
7bd1aa1d |
65 | } |
66 | } |
67 | } |
730fd187 |
68 | |
7bd1aa1d |
69 | return $quiz->id; |
730fd187 |
70 | } |
71 | |
72 | |
73 | function quiz_update_instance($quiz) { |
a5e1f35c |
74 | /// Given an object containing all the necessary data, |
75 | /// (defined by the form in mod.html) this function |
76 | /// will update an existing instance with new data. |
730fd187 |
77 | |
78 | $quiz->timemodified = time(); |
49dcdd18 |
79 | $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday, |
c04c41c7 |
80 | $quiz->openhour, $quiz->openminute, 0); |
49dcdd18 |
81 | $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday, |
c04c41c7 |
82 | $quiz->closehour, $quiz->closeminute, 0); |
730fd187 |
83 | $quiz->id = $quiz->instance; |
84 | |
7bd1aa1d |
85 | if (!update_record("quiz", $quiz)) { |
86 | return false; // some error occurred |
87 | } |
730fd187 |
88 | |
7bd1aa1d |
89 | |
10b9291c |
90 | // The grades for every question in this quiz are stored in an array |
7bd1aa1d |
91 | // Insert or update records as appropriate |
92 | |
93 | $existing = get_records("quiz_question_grades", "quiz", $quiz->id, "", "question,grade,id"); |
94 | |
95 | if ($quiz->grades) { |
96 | foreach ($quiz->grades as $question => $grade) { |
8d94f5a0 |
97 | if ($question and $grade) { |
98 | unset($questiongrade); |
99 | $questiongrade->quiz = $quiz->id; |
100 | $questiongrade->question = $question; |
101 | $questiongrade->grade = $grade; |
102 | if (isset($existing[$question])) { |
103 | if ($existing[$question]->grade != $grade) { |
104 | $questiongrade->id = $existing[$question]->id; |
105 | if (!update_record("quiz_question_grades", $questiongrade)) { |
106 | return false; |
107 | } |
108 | } |
109 | } else { |
110 | if (!insert_record("quiz_question_grades", $questiongrade)) { |
7bd1aa1d |
111 | return false; |
112 | } |
113 | } |
7bd1aa1d |
114 | } |
115 | } |
116 | } |
117 | |
118 | return true; |
730fd187 |
119 | } |
120 | |
121 | |
122 | function quiz_delete_instance($id) { |
a5e1f35c |
123 | /// Given an ID of an instance of this module, |
124 | /// this function will permanently delete the instance |
125 | /// and any data that depends on it. |
730fd187 |
126 | |
127 | if (! $quiz = get_record("quiz", "id", "$id")) { |
128 | return false; |
129 | } |
130 | |
131 | $result = true; |
132 | |
10b9291c |
133 | if ($attempts = get_record("quiz_attempts", "quiz", "$quiz->id")) { |
134 | foreach ($attempts as $attempt) { |
135 | if (! delete_records("quiz_responses", "attempt", "$attempt->id")) { |
136 | $result = false; |
137 | } |
138 | } |
139 | } |
140 | |
141 | if (! delete_records("quiz_attempts", "quiz", "$quiz->id")) { |
142 | $result = false; |
143 | } |
144 | |
145 | if (! delete_records("quiz_grades", "quiz", "$quiz->id")) { |
146 | $result = false; |
147 | } |
148 | |
149 | if (! delete_records("quiz_question_grades", "quiz", "$quiz->id")) { |
150 | $result = false; |
151 | } |
730fd187 |
152 | |
153 | if (! delete_records("quiz", "id", "$quiz->id")) { |
154 | $result = false; |
155 | } |
156 | |
157 | return $result; |
158 | } |
159 | |
160 | function quiz_user_outline($course, $user, $mod, $quiz) { |
a5e1f35c |
161 | /// Return a small object with summary information about what a |
162 | /// user has done with a given particular instance of this module |
163 | /// Used for user activity reports. |
164 | /// $return->time = the time they did it |
165 | /// $return->info = a short text description |
ebc3bd2b |
166 | if ($grade = get_record("quiz_grades", "userid", $user->id, "quiz", $quiz->id)) { |
98092498 |
167 | |
168 | if ($grade->grade) { |
169 | $result->info = get_string("grade").": $grade->grade"; |
170 | } |
171 | $result->time = $grade->timemodified; |
172 | return $result; |
173 | } |
174 | return NULL; |
730fd187 |
175 | |
176 | return $return; |
177 | } |
178 | |
179 | function quiz_user_complete($course, $user, $mod, $quiz) { |
a5e1f35c |
180 | /// Print a detailed representation of what a user has done with |
181 | /// a given particular instance of this module, for user activity reports. |
730fd187 |
182 | |
183 | return true; |
184 | } |
185 | |
186 | function quiz_print_recent_activity(&$logs, $isteacher=false) { |
a5e1f35c |
187 | /// Given a list of logs, assumed to be those since the last login |
188 | /// this function prints a short list of changes related to this module |
189 | /// If isteacher is true then perhaps additional information is printed. |
190 | /// This function is called from course/lib.php: print_recent_activity() |
730fd187 |
191 | |
192 | global $CFG, $COURSE_TEACHER_COLOR; |
193 | |
9c9f7d77 |
194 | $content = ""; |
195 | |
730fd187 |
196 | return $content; // True if anything was printed, otherwise false |
197 | } |
198 | |
199 | function quiz_cron () { |
a5e1f35c |
200 | /// Function to be run periodically according to the moodle cron |
201 | /// This function searches for things that need to be done, such |
202 | /// as sending out mail, toggling flags etc ... |
730fd187 |
203 | |
204 | global $CFG; |
205 | |
206 | return true; |
207 | } |
208 | |
d0ac6bc2 |
209 | function quiz_grades($quizid) { |
858deff0 |
210 | /// Must return an array of grades, indexed by user, and a max grade. |
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 | |
730fd187 |
217 | |
bdc23be0 |
218 | /// SQL FUNCTIONS //////////////////////////////////////////////////////////////////// |
219 | |
220 | function quiz_move_questions($category1, $category2) { |
221 | global $CFG; |
222 | return execute_sql("UPDATE {$CFG->prefix}quiz_questions |
223 | SET category = '$category2' |
224 | WHERE category = '$category1'", |
225 | false); |
226 | } |
227 | |
228 | function quiz_get_question_grades($quizid, $questionlist) { |
229 | global $CFG; |
230 | |
231 | return get_records_sql("SELECT question,grade |
232 | FROM {$CFG->prefix}quiz_question_grades |
233 | WHERE quiz = '$quizid' |
234 | AND question IN ($questionlist)"); |
235 | } |
236 | |
237 | function quiz_get_grade_records($quiz) { |
238 | /// Gets all info required to display the table of quiz results |
239 | /// for report.php |
240 | global $CFG; |
241 | |
242 | return get_records_sql("SELECT qg.*, u.firstname, u.lastname, u.picture |
243 | FROM {$CFG->prefix}quiz_grades qg, |
244 | {$CFG->prefix}user u |
245 | WHERE qg.quiz = '$quiz->id' |
ebc3bd2b |
246 | AND qg.userid = u.id"); |
bdc23be0 |
247 | } |
248 | |
249 | function quiz_get_answers($question) { |
250 | // Given a question, returns the correct answers and grades |
251 | global $CFG; |
95dbc030 |
252 | |
a2fe7cc0 |
253 | switch ($question->qtype) { |
95dbc030 |
254 | case SHORTANSWER: // Could be multiple answers |
bdc23be0 |
255 | return get_records_sql("SELECT a.*, sa.usecase, g.grade |
256 | FROM {$CFG->prefix}quiz_shortanswer sa, |
257 | {$CFG->prefix}quiz_answers a, |
258 | {$CFG->prefix}quiz_question_grades g |
259 | WHERE sa.question = '$question->id' |
260 | AND sa.question = a.question |
261 | AND sa.question = g.question"); |
262 | break; |
263 | |
95dbc030 |
264 | case TRUEFALSE: // Should be always two answers |
bdc23be0 |
265 | return get_records_sql("SELECT a.*, g.grade |
266 | FROM {$CFG->prefix}quiz_answers a, |
267 | {$CFG->prefix}quiz_question_grades g |
268 | WHERE a.question = '$question->id' |
269 | AND a.question = g.question"); |
270 | break; |
271 | |
95dbc030 |
272 | case MULTICHOICE: // Should be multiple answers |
bdc23be0 |
273 | return get_records_sql("SELECT a.*, mc.single, g.grade |
274 | FROM {$CFG->prefix}quiz_multichoice mc, |
275 | {$CFG->prefix}quiz_answers a, |
276 | {$CFG->prefix}quiz_question_grades g |
277 | WHERE mc.question = '$question->id' |
278 | AND mc.question = a.question |
279 | AND mc.question = g.question"); |
280 | break; |
281 | |
95dbc030 |
282 | case RANDOM: |
bdc23be0 |
283 | return false; // Not done yet |
284 | break; |
285 | |
95dbc030 |
286 | case RANDOMMATCH: // Could be any of many answers, return them all |
287 | return get_records_sql("SELECT a.*, g.grade |
288 | FROM {$CFG->prefix}quiz_questions q, |
289 | {$CFG->prefix}quiz_answers a, |
290 | {$CFG->prefix}quiz_question_grades g |
291 | WHERE q.category = '$question->category' |
292 | AND q.qtype = ".SHORTANSWER." |
293 | AND q.id = a.question |
294 | AND g.question = '$question->id'"); |
295 | break; |
296 | |
bdc23be0 |
297 | default: |
298 | return false; |
299 | } |
300 | } |
301 | |
302 | |
ef4145f6 |
303 | function quiz_get_attempt_responses($attempt, $quiz) { |
bdc23be0 |
304 | // Given an attempt object, this function gets all the |
305 | // stored responses and returns them in a format suitable |
306 | // for regrading using quiz_grade_attempt_results() |
307 | global $CFG; |
308 | |
95dbc030 |
309 | if (!$responses = get_records_sql("SELECT q.id, q.qtype, q.category, r.answer |
bdc23be0 |
310 | FROM {$CFG->prefix}quiz_responses r, |
311 | {$CFG->prefix}quiz_questions q |
312 | WHERE r.attempt = '$attempt->id' |
ef4145f6 |
313 | AND q.id = r.question |
314 | AND q.id IN ($quiz->questions)")) { |
bdc23be0 |
315 | notify("Could not find any responses for that attempt!"); |
316 | return false; |
317 | } |
318 | |
319 | foreach ($responses as $key => $response) { |
320 | $responses[$key]->answer = explode(",",$response->answer); |
321 | } |
322 | |
323 | return $responses; |
324 | } |
325 | |
326 | |
327 | |
730fd187 |
328 | ////////////////////////////////////////////////////////////////////////////////////// |
a5e1f35c |
329 | /// Any other quiz functions go here. Each of them must have a name that |
330 | /// starts with quiz_ |
730fd187 |
331 | |
a8a372cc |
332 | function quiz_print_comment($text) { |
333 | global $THEME; |
334 | |
c897eac5 |
335 | echo "<SPAN CLASS=feedbacktext>".text_to_html($text, true, false)."</SPAN>"; |
a8a372cc |
336 | } |
337 | |
8db3eadd |
338 | function quiz_print_correctanswer($text) { |
339 | global $THEME; |
340 | |
341 | echo "<P ALIGN=RIGHT><SPAN CLASS=highlight>$text</SPAN></P>"; |
342 | } |
343 | |
c74a0ca5 |
344 | function quiz_print_question_icon($question) { |
345 | // Prints a question icon |
cc3b8c75 |
346 | |
347 | global $QUIZ_QUESTION_TYPE; |
348 | |
a2fe7cc0 |
349 | echo "<A HREF=\"question.php?id=$question->id\" TITLE=\"".$QUIZ_QUESTION_TYPE[$question->qtype]."\">"; |
350 | switch ($question->qtype) { |
c74a0ca5 |
351 | case SHORTANSWER: |
cc3b8c75 |
352 | echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/sa.gif\">"; |
c74a0ca5 |
353 | break; |
354 | case TRUEFALSE: |
cc3b8c75 |
355 | echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/tf.gif\">"; |
c74a0ca5 |
356 | break; |
357 | case MULTICHOICE: |
cc3b8c75 |
358 | echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/mc.gif\">"; |
c74a0ca5 |
359 | break; |
360 | case RANDOM: |
cc3b8c75 |
361 | echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/rs.gif\">"; |
c74a0ca5 |
362 | break; |
95dbc030 |
363 | case RANDOMMATCH: |
364 | echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/rm.gif\">"; |
365 | break; |
c74a0ca5 |
366 | } |
cc3b8c75 |
367 | echo "</A>\n"; |
c74a0ca5 |
368 | } |
369 | |
370 | function quiz_print_question($number, $questionid, $grade, $courseid, |
8db3eadd |
371 | $feedback=NULL, $response=NULL, $actualgrade=NULL, $correct=NULL) { |
a5e1f35c |
372 | /// Prints a quiz question, any format |
a8a372cc |
373 | |
14d8c0b4 |
374 | if (!$question = get_record("quiz_questions", "id", $questionid)) { |
375 | notify("Error: Question not found!"); |
376 | } |
377 | |
a2fe7cc0 |
378 | if (empty($actualgrade)) { |
379 | $actualgrade = 0; |
380 | } |
381 | |
14d8c0b4 |
382 | $stranswer = get_string("answer", "quiz"); |
383 | $strmarks = get_string("marks", "quiz"); |
384 | |
385 | echo "<TABLE WIDTH=100% CELLSPACING=10><TR><TD NOWRAP WIDTH=100 VALIGN=top>"; |
a8a372cc |
386 | echo "<P ALIGN=CENTER><B>$number</B></P>"; |
19c4f55c |
387 | if ($feedback or $response) { |
a8a372cc |
388 | echo "<P ALIGN=CENTER><FONT SIZE=1>$strmarks: $actualgrade/$grade</FONT></P>"; |
389 | } else { |
390 | echo "<P ALIGN=CENTER><FONT SIZE=1>$grade $strmarks</FONT></P>"; |
391 | } |
14d8c0b4 |
392 | print_spacer(1,100); |
393 | echo "</TD><TD VALIGN=TOP>"; |
394 | |
a2fe7cc0 |
395 | switch ($question->qtype) { |
a5e1f35c |
396 | case SHORTANSWER: |
14d8c0b4 |
397 | if (!$options = get_record("quiz_shortanswer", "question", $question->id)) { |
398 | notify("Error: Missing question options!"); |
399 | } |
2a2c9725 |
400 | echo text_to_html($question->questiontext); |
14d8c0b4 |
401 | if ($question->image) { |
7d2e5b65 |
402 | print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT); |
14d8c0b4 |
403 | } |
a8a372cc |
404 | if ($response) { |
405 | $value = "VALUE=\"$response[0]\""; |
a2fe7cc0 |
406 | } else { |
407 | $value = ""; |
a8a372cc |
408 | } |
409 | echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$question->id SIZE=20 $value></P>"; |
410 | if ($feedback) { |
411 | quiz_print_comment("<P ALIGN=right>$feedback[0]</P>"); |
412 | } |
8db3eadd |
413 | if ($correct) { |
c74a0ca5 |
414 | $correctanswers = implode(", ", $correct); |
8db3eadd |
415 | quiz_print_correctanswer($correctanswers); |
416 | } |
14d8c0b4 |
417 | break; |
418 | |
a5e1f35c |
419 | case TRUEFALSE: |
14d8c0b4 |
420 | if (!$options = get_record("quiz_truefalse", "question", $question->id)) { |
421 | notify("Error: Missing question options!"); |
422 | } |
a2fe7cc0 |
423 | if (!$true = get_record("quiz_answers", "id", $options->trueanswer)) { |
14d8c0b4 |
424 | notify("Error: Missing question answers!"); |
425 | } |
a2fe7cc0 |
426 | if (!$false = get_record("quiz_answers", "id", $options->falseanswer)) { |
14d8c0b4 |
427 | notify("Error: Missing question answers!"); |
428 | } |
429 | if (!$true->answer) { |
430 | $true->answer = get_string("true", "quiz"); |
431 | } |
432 | if (!$false->answer) { |
433 | $false->answer = get_string("false", "quiz"); |
434 | } |
2a2c9725 |
435 | echo text_to_html($question->questiontext); |
14d8c0b4 |
436 | if ($question->image) { |
7d2e5b65 |
437 | print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT); |
14d8c0b4 |
438 | } |
a8a372cc |
439 | |
a2fe7cc0 |
440 | $truechecked = ""; |
441 | $falsechecked = ""; |
442 | |
443 | if (!empty($response[$true->id])) { |
a8a372cc |
444 | $truechecked = "CHECKED"; |
445 | $feedbackid = $true->id; |
a2fe7cc0 |
446 | } else if (!empty($response[$false->id])) { |
a8a372cc |
447 | $falsechecked = "CHECKED"; |
448 | $feedbackid = $false->id; |
449 | } |
a2fe7cc0 |
450 | |
451 | $truecorrect = ""; |
452 | $falsecorrect = ""; |
8db3eadd |
453 | if ($correct) { |
a2fe7cc0 |
454 | if (!empty($correct[$true->id])) { |
8db3eadd |
455 | $truecorrect = "CLASS=highlight"; |
456 | } |
a2fe7cc0 |
457 | if (!empty($correct[$false->id])) { |
8db3eadd |
458 | $falsecorrect = "CLASS=highlight"; |
459 | } |
460 | } |
461 | echo "<TABLE ALIGN=right cellpadding=5><TR><TD align=right>$stranswer: "; |
462 | echo "<TD $truecorrect>"; |
a8a372cc |
463 | echo "<INPUT $truechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$true->id\">$true->answer"; |
8db3eadd |
464 | echo "</TD><TD $falsecorrect>"; |
a8a372cc |
465 | echo "<INPUT $falsechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$false->id\">$false->answer</P>"; |
8db3eadd |
466 | echo "</TD></TR></TABLE><BR CLEAR=ALL>"; |
a8a372cc |
467 | if ($feedback) { |
468 | quiz_print_comment("<P ALIGN=right>$feedback[$feedbackid]</P>"); |
469 | } |
470 | |
14d8c0b4 |
471 | break; |
472 | |
a5e1f35c |
473 | case MULTICHOICE: |
14d8c0b4 |
474 | if (!$options = get_record("quiz_multichoice", "question", $question->id)) { |
475 | notify("Error: Missing question options!"); |
476 | } |
a5e1f35c |
477 | if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) { |
14d8c0b4 |
478 | notify("Error: Missing question answers!"); |
479 | } |
2a2c9725 |
480 | echo text_to_html($question->questiontext); |
14d8c0b4 |
481 | if ($question->image) { |
7d2e5b65 |
482 | print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT); |
14d8c0b4 |
483 | } |
484 | echo "<TABLE ALIGN=right>"; |
485 | echo "<TR><TD valign=top>$stranswer: </TD><TD>"; |
81b635c3 |
486 | echo "<TABLE>"; |
14d8c0b4 |
487 | $answerids = explode(",", $options->answers); |
a8a372cc |
488 | |
14d8c0b4 |
489 | foreach ($answerids as $key => $answerid) { |
490 | $answer = $answers[$answerid]; |
68fefdbe |
491 | $qnumchar = chr(ord('a') + $key); |
a8a372cc |
492 | |
8e6c87cc |
493 | if (empty($feedback) or empty($response[$answerid])) { |
a8a372cc |
494 | $checked = ""; |
8e6c87cc |
495 | } else { |
496 | $checked = "CHECKED"; |
a8a372cc |
497 | } |
14d8c0b4 |
498 | echo "<TR><TD valign=top>"; |
a5e1f35c |
499 | if ($options->single) { |
a8a372cc |
500 | echo "<INPUT $checked TYPE=RADIO NAME=q$question->id VALUE=\"$answer->id\">"; |
14d8c0b4 |
501 | } else { |
a8a372cc |
502 | echo "<INPUT $checked TYPE=CHECKBOX NAME=q$question->id"."a$answer->id VALUE=\"$answer->id\">"; |
14d8c0b4 |
503 | } |
504 | echo "</TD>"; |
8e6c87cc |
505 | if (empty($feedback) or empty($correct[$answer->id])) { |
68fefdbe |
506 | echo "<TD valign=top>$qnumchar. $answer->answer</TD>"; |
8e6c87cc |
507 | } else { |
68fefdbe |
508 | echo "<TD valign=top CLASS=highlight>$qnumchar. $answer->answer</TD>"; |
8db3eadd |
509 | } |
8e6c87cc |
510 | if (!empty($feedback)) { |
a8a372cc |
511 | echo "<TD valign=top> "; |
8e6c87cc |
512 | if (!empty($response[$answerid])) { |
a8a372cc |
513 | quiz_print_comment($feedback[$answerid]); |
514 | } |
515 | echo "</TD>"; |
516 | } |
14d8c0b4 |
517 | echo "</TR>"; |
518 | } |
519 | echo "</TABLE>"; |
520 | echo "</TABLE>"; |
521 | break; |
522 | |
8db3eadd |
523 | case RANDOM: |
524 | echo "<P>Random questions not supported yet</P>"; |
525 | break; |
526 | |
95dbc030 |
527 | case RANDOMMATCH: |
528 | if (!$options = get_record("quiz_randommatch", "question", $question->id)) { |
529 | notify("Error: Missing question options!"); |
530 | } |
531 | echo text_to_html($question->questiontext); |
532 | if ($question->image) { |
533 | print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT); |
534 | } |
535 | |
536 | /// First, get all the questions available |
537 | |
538 | $allquestions = get_records_select("quiz_questions", |
539 | "category = $question->category AND qtype = ".SHORTANSWER); |
540 | if (count($allquestions) < $options->choose) { |
541 | notify("Error: could not find enough Short Answer questions in the database!"); |
542 | notify("Found ".count($allquestions).", need $options->choose."); |
543 | break; |
544 | } |
545 | |
546 | if (empty($response)) { // Randomly pick the questions |
547 | if (!$randomquestions = draw_rand_array($allquestions, $options->choose)) { |
548 | notify("Error choosing $options->choose random questions"); |
549 | break; |
550 | } |
551 | } else { // Use existing questions |
552 | $randomquestions = array(); |
553 | foreach ($response as $key => $rrr) { |
554 | $rrr = explode("-", $rrr); |
555 | $randomquestions[$key] = $allquestions[$key]; |
556 | $responseanswer[$key] = $rrr[1]; |
557 | } |
558 | } |
559 | |
560 | /// For each selected, find the best matching answers |
561 | |
562 | foreach ($randomquestions as $randomquestion) { |
563 | $shortanswerquestion = get_record("quiz_shortanswer", "question", $randomquestion->id); |
564 | $questionanswers = get_records_list("quiz_answers", "id", $shortanswerquestion->answers); |
565 | $bestfraction = 0; |
566 | $bestanswer = NULL; |
567 | foreach ($questionanswers as $questionanswer) { |
568 | if ($questionanswer->fraction > $bestfraction) { |
569 | $bestanswer = $questionanswer; |
570 | } |
571 | } |
572 | if (empty($bestanswer)) { |
573 | notify("Error: Could not find the best answer for question: ".$randomquestions->name); |
574 | break; |
575 | } |
576 | $randomanswers[$bestanswer->id] = trim($bestanswer->answer); |
577 | } |
578 | |
579 | if (!$randomanswers = draw_rand_array($randomanswers, $options->choose)) { // Mix them up |
580 | notify("Error randomising answers!"); |
581 | break; |
582 | } |
583 | |
584 | echo "<table border=0 cellpadding=10>"; |
585 | foreach ($randomquestions as $key => $randomquestion) { |
586 | echo "<tr><td align=left valign=top>"; |
587 | echo $randomquestion->questiontext; |
588 | echo "</td>"; |
589 | echo "<td align=right valign=top>"; |
590 | if (empty($response)) { |
591 | choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id"); |
592 | } else { |
593 | if (!empty($correct[$key])) { |
594 | if ($randomanswers[$responseanswer[$key]] == $correct[$key]) { |
595 | echo "<span=highlight>"; |
596 | choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]); |
597 | echo "</span><br \>"; |
598 | } else { |
599 | choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]); |
600 | quiz_print_correctanswer($correct[$key]); |
601 | } |
602 | } else { |
603 | choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]); |
604 | } |
605 | if (!empty($feedback[$key])) { |
606 | quiz_print_comment($feedback[$key]); |
607 | } |
608 | } |
609 | echo "</td></tr>"; |
610 | } |
611 | echo "</table>"; |
612 | break; |
613 | |
8db3eadd |
614 | |
14d8c0b4 |
615 | default: |
616 | notify("Error: Unknown question type!"); |
617 | } |
618 | |
619 | echo "</TD></TR></TABLE>"; |
3a506ca2 |
620 | } |
621 | |
a5e1f35c |
622 | function quiz_print_quiz_questions($quiz, $results=NULL) { |
623 | // Prints a whole quiz on one page. |
624 | |
8e6c87cc |
625 | if (empty($quiz->questions)) { |
10b9291c |
626 | notify("No questions have been defined!", "view.php?id=$cm->id"); |
627 | return false; |
a5e1f35c |
628 | } |
629 | |
630 | $questions = explode(",", $quiz->questions); |
631 | |
632 | if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) { |
10b9291c |
633 | notify("No grades were found for these questions!"); |
634 | return false; |
a5e1f35c |
635 | } |
636 | |
9307692f |
637 | $strconfirmattempt = addslashes(get_string("readytosend", "quiz")); |
638 | |
639 | echo "<FORM METHOD=POST ACTION=attempt.php onsubmit=\"return confirm('$strconfirmattempt');\">"; |
a5e1f35c |
640 | echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">"; |
a8a372cc |
641 | |
a5e1f35c |
642 | foreach ($questions as $key => $questionid) { |
8db3eadd |
643 | $feedback = NULL; |
644 | $response = NULL; |
645 | $actualgrades = NULL; |
646 | $correct = NULL; |
8e6c87cc |
647 | if (!empty($results)) { |
648 | if (!empty($results->feedback[$questionid])) { |
649 | $feedback = $results->feedback[$questionid]; |
650 | } |
651 | if (!empty($results->response[$questionid])) { |
652 | $response = $results->response[$questionid]; |
653 | } |
654 | if (!empty($results->grades[$questionid])) { |
655 | $actualgrades = $results->grades[$questionid]; |
656 | } |
8db3eadd |
657 | if ($quiz->correctanswers) { |
8e6c87cc |
658 | if (!empty($results->correct[$questionid])) { |
659 | $correct = $results->correct[$questionid]; |
660 | } |
8db3eadd |
661 | } |
a8a372cc |
662 | } |
8db3eadd |
663 | |
a5e1f35c |
664 | print_simple_box_start("CENTER", "90%"); |
a8a372cc |
665 | quiz_print_question($key+1, $questionid, $grades[$questionid]->grade, $quiz->course, |
8db3eadd |
666 | $feedback, $response, $actualgrades, $correct); |
a5e1f35c |
667 | print_simple_box_end(); |
668 | echo "<BR>"; |
669 | } |
a8a372cc |
670 | |
8e6c87cc |
671 | if (empty($results)) { |
a8a372cc |
672 | echo "<CENTER><INPUT TYPE=submit VALUE=\"".get_string("savemyanswers", "quiz")."\"></CENTER>"; |
673 | } |
a5e1f35c |
674 | echo "</FORM>"; |
10b9291c |
675 | |
676 | return true; |
a5e1f35c |
677 | } |
6a952ce7 |
678 | |
679 | function quiz_get_default_category($courseid) { |
680 | if ($categories = get_records("quiz_categories", "course", $courseid, "id")) { |
681 | foreach ($categories as $category) { |
682 | return $category; // Return the first one (lowest id) |
683 | } |
684 | } |
685 | |
686 | // Otherwise, we need to make one |
10b9291c |
687 | $category->name = get_string("default", "quiz"); |
688 | $category->info = get_string("defaultinfo", "quiz"); |
6a952ce7 |
689 | $category->course = $courseid; |
690 | $category->publish = 0; |
691 | |
692 | if (!$category->id = insert_record("quiz_categories", $category)) { |
693 | notify("Error creating a default category!"); |
694 | return false; |
695 | } |
696 | return $category; |
697 | } |
698 | |
c74a0ca5 |
699 | function quiz_get_category_menu($courseid, $published=false) { |
700 | if ($published) { |
701 | $publish = "OR publish = '1'"; |
702 | } |
bdc23be0 |
703 | return get_records_select_menu("quiz_categories", "course='$courseid' $publish", "name ASC", "id,name"); |
c74a0ca5 |
704 | } |
705 | |
6a952ce7 |
706 | function quiz_print_category_form($course, $current) { |
707 | // Prints a form to choose categories |
708 | |
bdc23be0 |
709 | if (!$categories = get_records_select("quiz_categories", "course='$course->id' OR publish = '1'", "name ASC")) { |
6a952ce7 |
710 | if (!$category = quiz_get_default_category($course->id)) { |
711 | notify("Error creating a default category!"); |
712 | return false; |
713 | } |
cb62c00a |
714 | $categories[$category->id] = $category; |
6a952ce7 |
715 | } |
8d94f5a0 |
716 | foreach ($categories as $key => $category) { |
717 | if ($category->publish) { |
b55a466b |
718 | if ($catcourse = get_record("course", "id", $category->course)) { |
719 | $category->name .= " ($catcourse->shortname)"; |
8d94f5a0 |
720 | } |
721 | } |
b55a466b |
722 | $catmenu[$category->id] = $category->name; |
8d94f5a0 |
723 | } |
6a952ce7 |
724 | $strcategory = get_string("category", "quiz"); |
725 | $strshow = get_string("show", "quiz"); |
6b069ece |
726 | $streditcats = get_string("editcategories", "quiz"); |
6a952ce7 |
727 | |
467aaec6 |
728 | echo "<TABLE width=\"100%\"><TR><TD NOWRAP>"; |
6a952ce7 |
729 | echo "<FORM METHOD=POST ACTION=edit.php>"; |
730 | echo "<B>$strcategory:</B> "; |
b55a466b |
731 | choose_from_menu($catmenu, "cat", "$current"); |
92a3c884 |
732 | echo "<INPUT TYPE=submit VALUE=\"$strshow\">"; |
6a952ce7 |
733 | echo "</FORM>"; |
6b069ece |
734 | echo "</TD><TD align=right>"; |
735 | echo "<FORM METHOD=GET ACTION=category.php>"; |
736 | echo "<INPUT TYPE=hidden NAME=id VALUE=\"$course->id\">"; |
737 | echo "<INPUT TYPE=submit VALUE=\"$streditcats\">"; |
738 | echo "</FORM>"; |
739 | echo "</TD></TR></TABLE>"; |
6a952ce7 |
740 | } |
741 | |
742 | |
7bd1aa1d |
743 | function quiz_get_all_question_grades($questionlist, $quizid) { |
744 | // Given a list of question IDs, finds grades or invents them to |
745 | // create an array of matching grades |
746 | |
bdc23be0 |
747 | $questions = quiz_get_question_grades($quizid, $questionlist); |
7bd1aa1d |
748 | |
749 | $list = explode(",", $questionlist); |
750 | $grades = array(); |
751 | |
752 | foreach ($list as $qid) { |
753 | if (isset($questions[$qid])) { |
754 | $grades[$qid] = $questions[$qid]->grade; |
755 | } else { |
756 | $grades[$qid] = 1; |
757 | } |
758 | } |
759 | return $grades; |
760 | } |
761 | |
762 | |
763 | function quiz_print_question_list($questionlist, $grades) { |
6a952ce7 |
764 | // Prints a list of quiz questions in a small layout form with knobs |
7bd1aa1d |
765 | // $questionlist is comma-separated list |
766 | // $grades is an array of corresponding grades |
6a952ce7 |
767 | |
768 | global $THEME; |
769 | |
770 | if (!$questionlist) { |
771 | echo "<P align=center>"; |
772 | print_string("noquestions", "quiz"); |
773 | echo "</P>"; |
774 | return; |
775 | } |
776 | |
777 | $order = explode(",", $questionlist); |
778 | |
7bd1aa1d |
779 | if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) { |
6a952ce7 |
780 | error("No questions were found!"); |
781 | } |
782 | |
783 | $strorder = get_string("order"); |
784 | $strquestionname = get_string("questionname", "quiz"); |
785 | $strgrade = get_string("grade"); |
786 | $strdelete = get_string("delete"); |
787 | $stredit = get_string("edit"); |
788 | $strmoveup = get_string("moveup"); |
789 | $strmovedown = get_string("movedown"); |
790 | $strsavegrades = get_string("savegrades", "quiz"); |
c74a0ca5 |
791 | $strtype = get_string("type", "quiz"); |
6a952ce7 |
792 | |
10b9291c |
793 | for ($i=10; $i>=0; $i--) { |
7bd1aa1d |
794 | $gradesmenu[$i] = $i; |
6a952ce7 |
795 | } |
796 | $count = 0; |
797 | $sumgrade = 0; |
798 | $total = count($order); |
799 | echo "<FORM METHOD=post ACTION=edit.php>"; |
800 | echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">"; |
467aaec6 |
801 | 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 |
802 | foreach ($order as $qnum) { |
95dbc030 |
803 | if (empty($questions[$qnum])) { |
804 | continue; |
805 | } |
6a952ce7 |
806 | $count++; |
807 | echo "<TR BGCOLOR=\"$THEME->cellcontent\">"; |
808 | echo "<TD>$count</TD>"; |
809 | echo "<TD>"; |
810 | if ($count != 1) { |
811 | echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG |
812 | SRC=\"../../pix/t/up.gif\" BORDER=0></A>"; |
813 | } |
814 | echo "</TD>"; |
815 | echo "<TD>"; |
816 | if ($count != $total) { |
817 | echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG |
818 | SRC=\"../../pix/t/down.gif\" BORDER=0></A>"; |
819 | } |
820 | echo "</TD>"; |
821 | echo "<TD>".$questions[$qnum]->name."</TD>"; |
467aaec6 |
822 | echo "<TD ALIGN=CENTER>"; |
c74a0ca5 |
823 | quiz_print_question_icon($questions[$qnum]); |
824 | echo "</TD>"; |
6a952ce7 |
825 | echo "<TD>"; |
8d94f5a0 |
826 | choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], ""); |
6a952ce7 |
827 | echo "<TD>"; |
828 | echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG |
829 | SRC=\"../../pix/t/delete.gif\" BORDER=0></A> "; |
830 | echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG |
831 | SRC=\"../../pix/t/edit.gif\" BORDER=0></A>"; |
832 | echo "</TD>"; |
833 | |
7bd1aa1d |
834 | $sumgrade += $grades[$qnum]; |
6a952ce7 |
835 | } |
c74a0ca5 |
836 | echo "<TR><TD COLSPAN=5 ALIGN=right>"; |
6a952ce7 |
837 | echo "<INPUT TYPE=submit VALUE=\"$strsavegrades:\">"; |
8d94f5a0 |
838 | echo "<INPUT TYPE=hidden NAME=setgrades VALUE=\"save\">"; |
6a952ce7 |
839 | echo "<TD ALIGN=LEFT BGCOLOR=\"$THEME->cellcontent\">"; |
840 | echo "<B>$sumgrade</B>"; |
841 | echo "</TD><TD></TD></TR>"; |
842 | echo "</TABLE>"; |
843 | echo "</FORM>"; |
10b9291c |
844 | |
845 | return $sumgrade; |
6a952ce7 |
846 | } |
847 | |
848 | |
849 | function quiz_print_cat_question_list($categoryid) { |
850 | // Prints a form to choose categories |
851 | |
852 | global $THEME, $QUIZ_QUESTION_TYPE; |
853 | |
10b9291c |
854 | $strcategory = get_string("category", "quiz"); |
6a952ce7 |
855 | $strquestion = get_string("question", "quiz"); |
49220fa7 |
856 | $straddquestions = get_string("addquestions", "quiz"); |
857 | $strimportquestions = get_string("importquestions", "quiz"); |
6a952ce7 |
858 | $strnoquestions = get_string("noquestions", "quiz"); |
859 | $strselect = get_string("select", "quiz"); |
a01b2571 |
860 | $strselectall = get_string("selectall", "quiz"); |
6a952ce7 |
861 | $strcreatenewquestion = get_string("createnewquestion", "quiz"); |
862 | $strquestionname = get_string("questionname", "quiz"); |
863 | $strdelete = get_string("delete"); |
864 | $stredit = get_string("edit"); |
865 | $straddselectedtoquiz = get_string("addselectedtoquiz", "quiz"); |
c74a0ca5 |
866 | $strtype = get_string("type", "quiz"); |
6a952ce7 |
867 | |
868 | if (!$categoryid) { |
869 | echo "<P align=center>"; |
870 | print_string("selectcategoryabove", "quiz"); |
871 | echo "</P>"; |
872 | return; |
873 | } |
a5e1f35c |
874 | |
6a952ce7 |
875 | if (!$category = get_record("quiz_categories", "id", "$categoryid")) { |
876 | notify("Category not found!"); |
877 | return; |
878 | } |
8d94f5a0 |
879 | echo "<CENTER>"; |
10b9291c |
880 | echo text_to_html($category->info); |
6a952ce7 |
881 | |
49220fa7 |
882 | echo "<TABLE><TR>"; |
883 | echo "<TD valign=top><B>$straddquestions:</B></TD>"; |
884 | echo "<TD valign=top align=right>"; |
10b9291c |
885 | echo "<FORM METHOD=GET ACTION=question.php>"; |
a2fe7cc0 |
886 | choose_from_menu($QUIZ_QUESTION_TYPE, "qtype", "", ""); |
6a952ce7 |
887 | echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">"; |
7d2e5b65 |
888 | echo "<INPUT TYPE=submit VALUE=\"$strcreatenewquestion\">"; |
cd63d77e |
889 | helpbutton("questiontypes", $strcreatenewquestion, "quiz"); |
6a952ce7 |
890 | echo "</FORM>"; |
49220fa7 |
891 | |
892 | echo "<FORM METHOD=GET ACTION=import.php>"; |
893 | echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">"; |
894 | echo "<INPUT TYPE=submit VALUE=\"$strimportquestions\">"; |
895 | helpbutton("import", $strimportquestions, "quiz"); |
896 | echo "</FORM>"; |
897 | |
898 | echo "</TR></TABLE>"; |
899 | |
8d94f5a0 |
900 | echo "</CENTER>"; |
6a952ce7 |
901 | |
14bdb238 |
902 | if (!$questions = get_records("quiz_questions", "category", $category->id, "qtype ASC")) { |
6a952ce7 |
903 | echo "<P align=center>"; |
904 | print_string("noquestions", "quiz"); |
905 | echo "</P>"; |
906 | return; |
907 | } |
908 | |
10b9291c |
909 | $canedit = isteacher($category->course); |
910 | |
6a952ce7 |
911 | echo "<FORM METHOD=post ACTION=edit.php>"; |
912 | echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">"; |
467aaec6 |
913 | echo "<TR><TH width=\"*\" NOWRAP>$strselect</TH><TH width=\"100%\" align=left NOWRAP>$strquestionname</TH><TH WIDTH=\"*\" NOWRAP>$strtype</TH>"; |
10b9291c |
914 | if ($canedit) { |
467aaec6 |
915 | echo "<TH width=\"*\" NOWRAP>$stredit</TH>"; |
10b9291c |
916 | } |
917 | echo "</TR>"; |
6a952ce7 |
918 | foreach ($questions as $question) { |
919 | echo "<TR BGCOLOR=\"$THEME->cellcontent\">"; |
920 | echo "<TD ALIGN=CENTER>"; |
921 | echo "<INPUT TYPE=CHECKBOX NAME=q$question->id VALUE=\"1\">"; |
922 | echo "</TD>"; |
923 | echo "<TD>".$question->name."</TD>"; |
467aaec6 |
924 | echo "<TD ALIGN=CENTER>"; |
c74a0ca5 |
925 | quiz_print_question_icon($question); |
926 | echo "</TD>"; |
10b9291c |
927 | if ($canedit) { |
928 | echo "<TD>"; |
e1c91df0 |
929 | echo "<A TITLE=\"$strdelete\" HREF=\"question.php?id=$question->id&delete=$question->id\"><IMG |
10b9291c |
930 | SRC=\"../../pix/t/delete.gif\" BORDER=0></A> "; |
931 | echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG |
932 | SRC=\"../../pix/t/edit.gif\" BORDER=0></A>"; |
933 | echo "</TD></TR>"; |
934 | } |
935 | echo "</TR>"; |
6a952ce7 |
936 | } |
937 | echo "<TR><TD COLSPAN=3>"; |
a01b2571 |
938 | echo "<INPUT TYPE=submit NAME=add VALUE=\"<< $straddselectedtoquiz\">"; |
939 | //echo "<INPUT TYPE=submit NAME=delete VALUE=\"XX Delete selected\">"; |
940 | echo "<INPUT type=button onclick=\"checkall()\" value=\"$strselectall\">"; |
6a952ce7 |
941 | echo "</TD></TR>"; |
942 | echo "</TABLE>"; |
943 | echo "</FORM>"; |
944 | } |
a5e1f35c |
945 | |
3a506ca2 |
946 | |
958aafe2 |
947 | function quiz_start_attempt($quizid, $userid, $numattempt) { |
948 | $attempt->quiz = $quizid; |
ebc3bd2b |
949 | $attempt->userid = $userid; |
958aafe2 |
950 | $attempt->attempt = $numattempt; |
951 | $attempt->timestart = time(); |
952 | $attempt->timefinish = 0; |
953 | $attempt->timemodified = time(); |
954 | |
955 | return insert_record("quiz_attempts", $attempt); |
956 | } |
957 | |
958 | function quiz_get_user_attempt_unfinished($quizid, $userid) { |
959 | // Returns an object containing an unfinished attempt (if there is one) |
ebc3bd2b |
960 | return get_record("quiz_attempts", "quiz", $quizid, "userid", $userid, "timefinish", 0); |
958aafe2 |
961 | } |
962 | |
3a506ca2 |
963 | function quiz_get_user_attempts($quizid, $userid) { |
a5e1f35c |
964 | // Returns a list of all attempts by a user |
ebc3bd2b |
965 | return get_records_select("quiz_attempts", "quiz = '$quizid' AND userid = '$userid' AND timefinish > 0", |
bdc23be0 |
966 | "attempt ASC"); |
3a506ca2 |
967 | } |
968 | |
8d94f5a0 |
969 | |
970 | function quiz_get_user_attempts_string($quiz, $attempts, $bestgrade) { |
971 | /// Returns a simple little comma-separated list of all attempts, |
6d86b5dc |
972 | /// with each grade linked to the feedback report and with the best grade highlighted |
8d94f5a0 |
973 | |
974 | $bestgrade = format_float($bestgrade); |
975 | foreach ($attempts as $attempt) { |
976 | $attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade); |
977 | if ($attemptgrade == $bestgrade) { |
6d86b5dc |
978 | $userattempts[] = "<SPAN CLASS=highlight><A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A></SPAN>"; |
8d94f5a0 |
979 | } else { |
6d86b5dc |
980 | $userattempts[] = "<A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A>"; |
8d94f5a0 |
981 | } |
982 | } |
983 | return implode(",", $userattempts); |
984 | } |
985 | |
a5e1f35c |
986 | function quiz_get_best_grade($quizid, $userid) { |
987 | /// Get the best current grade for a particular user in a quiz |
ebc3bd2b |
988 | if (!$grade = get_record("quiz_grades", "quiz", $quizid, "userid", $userid)) { |
3a506ca2 |
989 | return 0; |
990 | } |
991 | |
2383cadb |
992 | return (round($grade->grade,0)); |
3a506ca2 |
993 | } |
994 | |
e331eb06 |
995 | function quiz_save_best_grade($quiz, $userid) { |
a5e1f35c |
996 | /// Calculates the best grade out of all attempts at a quiz for a user, |
997 | /// and then saves that grade in the quiz_grades table. |
998 | |
e331eb06 |
999 | if (!$attempts = quiz_get_user_attempts($quiz->id, $userid)) { |
e909c8d0 |
1000 | notify("Could not find any user attempts"); |
a5e1f35c |
1001 | return false; |
1002 | } |
1003 | |
1004 | $bestgrade = quiz_calculate_best_grade($quiz, $attempts); |
1005 | $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade); |
1006 | |
ebc3bd2b |
1007 | if ($grade = get_record("quiz_grades", "quiz", $quiz->id, "userid", $userid)) { |
1d2603b1 |
1008 | $grade->grade = round($bestgrade, 2); |
a5e1f35c |
1009 | $grade->timemodified = time(); |
1010 | if (!update_record("quiz_grades", $grade)) { |
e909c8d0 |
1011 | notify("Could not update best grade"); |
a5e1f35c |
1012 | return false; |
1013 | } |
1014 | } else { |
1015 | $grade->quiz = $quiz->id; |
ebc3bd2b |
1016 | $grade->userid = $userid; |
38f03e5a |
1017 | $grade->grade = round($bestgrade, 2); |
a5e1f35c |
1018 | $grade->timemodified = time(); |
1019 | if (!insert_record("quiz_grades", $grade)) { |
e909c8d0 |
1020 | notify("Could not insert new best grade"); |
a5e1f35c |
1021 | return false; |
1022 | } |
1023 | } |
1024 | return true; |
1025 | } |
1026 | |
1027 | |
3a506ca2 |
1028 | function quiz_calculate_best_grade($quiz, $attempts) { |
a5e1f35c |
1029 | /// Calculate the best grade for a quiz given a number of attempts by a particular user. |
3a506ca2 |
1030 | |
1031 | switch ($quiz->grademethod) { |
a5e1f35c |
1032 | |
1033 | case ATTEMPTFIRST: |
3a506ca2 |
1034 | foreach ($attempts as $attempt) { |
a5e1f35c |
1035 | return $attempt->sumgrades; |
3a506ca2 |
1036 | } |
a5e1f35c |
1037 | break; |
1038 | |
1039 | case ATTEMPTLAST: |
1040 | foreach ($attempts as $attempt) { |
1041 | $final = $attempt->sumgrades; |
1042 | } |
1043 | return $final; |
3a506ca2 |
1044 | |
a5e1f35c |
1045 | case GRADEAVERAGE: |
3a506ca2 |
1046 | $sum = 0; |
1047 | $count = 0; |
1048 | foreach ($attempts as $attempt) { |
a5e1f35c |
1049 | $sum += $attempt->sumgrades; |
3a506ca2 |
1050 | $count++; |
1051 | } |
1052 | return (float)$sum/$count; |
1053 | |
3a506ca2 |
1054 | default: |
a5e1f35c |
1055 | case GRADEHIGHEST: |
1056 | $max = 0; |
3a506ca2 |
1057 | foreach ($attempts as $attempt) { |
a5e1f35c |
1058 | if ($attempt->sumgrades > $max) { |
1059 | $max = $attempt->sumgrades; |
1060 | } |
3a506ca2 |
1061 | } |
a5e1f35c |
1062 | return $max; |
1063 | } |
1064 | } |
1065 | |
1066 | function quiz_save_attempt($quiz, $questions, $result, $attemptnum) { |
1067 | /// Given a quiz, a list of attempted questions and a total grade |
1068 | /// this function saves EVERYTHING so it can be reconstructed later |
1069 | /// if necessary. |
1070 | |
1071 | global $USER; |
1072 | |
958aafe2 |
1073 | // First find the attempt in the database (start of attempt) |
1074 | |
1075 | if (!$attempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) { |
1076 | notify("Trying to save an attempt that was not started!"); |
1077 | return false; |
1078 | } |
1079 | |
1080 | if ($attempt->attempt != $attemptnum) { // Double check. |
1081 | notify("Number of this attempt is different to the unfinished one!"); |
1082 | return false; |
1083 | } |
1084 | |
1085 | // Now let's complete this record and save it |
a5e1f35c |
1086 | |
a5e1f35c |
1087 | $attempt->sumgrades = $result->sumgrades; |
958aafe2 |
1088 | $attempt->timefinish = time(); |
a5e1f35c |
1089 | $attempt->timemodified = time(); |
1090 | |
958aafe2 |
1091 | if (! update_record("quiz_attempts", $attempt)) { |
7520988b |
1092 | notify("Error while saving attempt"); |
a5e1f35c |
1093 | return false; |
1094 | } |
1095 | |
1096 | // Now let's save all the questions for this attempt |
1097 | |
1098 | foreach ($questions as $question) { |
1099 | $response->attempt = $attempt->id; |
1100 | $response->question = $question->id; |
1101 | $response->grade = $result->grades[$question->id]; |
54d0590b |
1102 | if (!empty($question->answer)) { |
a5e1f35c |
1103 | $response->answer = implode(",",$question->answer); |
1104 | } else { |
1105 | $response->answer = ""; |
1106 | } |
1107 | if (!insert_record("quiz_responses", $response)) { |
7520988b |
1108 | notify("Error while saving response"); |
a5e1f35c |
1109 | return false; |
1110 | } |
3a506ca2 |
1111 | } |
a5e1f35c |
1112 | return true; |
3a506ca2 |
1113 | } |
730fd187 |
1114 | |
a5e1f35c |
1115 | |
1116 | function quiz_grade_attempt_results($quiz, $questions) { |
1117 | /// Given a list of questions (including answers for each one) |
1118 | /// this function does all the hard work of calculating the |
1119 | /// grades for each question, as well as a total grade for |
1120 | /// for the whole quiz. It returns everything in a structure |
1121 | /// that looks like: |
1122 | /// $result->sumgrades (sum of all grades for all questions) |
1123 | /// $result->percentage (Percentage of grades that were correct) |
1124 | /// $result->grade (final grade result for the whole quiz) |
1125 | /// $result->grades[] (array of grades, indexed by question id) |
a8a372cc |
1126 | /// $result->response[] (array of response arrays, indexed by question id) |
a5e1f35c |
1127 | /// $result->feedback[] (array of feedback arrays, indexed by question id) |
8db3eadd |
1128 | /// $result->correct[] (array of feedback arrays, indexed by question id) |
a5e1f35c |
1129 | |
1130 | if (!$questions) { |
1131 | error("No questions!"); |
1132 | } |
1133 | |
1134 | $result->sumgrades = 0; |
1135 | |
1136 | foreach ($questions as $question) { |
2a2c9725 |
1137 | if (!$answers = quiz_get_answers($question)) { |
1138 | error("No answers defined for question id $question->id!"); |
a5e1f35c |
1139 | } |
1140 | |
1141 | $grade = 0; // default |
8db3eadd |
1142 | $correct = array(); |
19c4f55c |
1143 | $feedback = array(); |
1144 | $response = array(); |
a5e1f35c |
1145 | |
a2fe7cc0 |
1146 | switch ($question->qtype) { |
a5e1f35c |
1147 | case SHORTANSWER: |
1148 | if ($question->answer) { |
41b95af2 |
1149 | $question->answer = trim(stripslashes($question->answer[0])); |
a5e1f35c |
1150 | } else { |
19c4f55c |
1151 | $question->answer = ""; |
a5e1f35c |
1152 | } |
a8a372cc |
1153 | $response[0] = $question->answer; |
a2fe7cc0 |
1154 | $bestshortanswer = 0; |
a5e1f35c |
1155 | foreach($answers as $answer) { // There might be multiple right answers |
8db3eadd |
1156 | if ($answer->fraction > $bestshortanswer) { |
1157 | $correct[$answer->id] = $answer->answer; |
a2fe7cc0 |
1158 | $bestshortanswer = $answer->fraction; |
8db3eadd |
1159 | } |
2a2c9725 |
1160 | if (!$answer->usecase) { // Don't compare case |
a5e1f35c |
1161 | $answer->answer = strtolower($answer->answer); |
1162 | $question->answer = strtolower($question->answer); |
1163 | } |
1164 | if ($question->answer == $answer->answer) { |
a8a372cc |
1165 | $feedback[0] = $answer->feedback; |
a5e1f35c |
1166 | $grade = (float)$answer->fraction * $answer->grade; |
1167 | } |
1168 | } |
1169 | break; |
1170 | |
1171 | |
1172 | case TRUEFALSE: |
1173 | if ($question->answer) { |
1174 | $question->answer = $question->answer[0]; |
1175 | } else { |
1176 | $question->answer = NULL; |
1177 | } |
1178 | foreach($answers as $answer) { // There should be two answers (true and false) |
1179 | $feedback[$answer->id] = $answer->feedback; |
8db3eadd |
1180 | if ($answer->fraction > 0) { |
1181 | $correct[$answer->id] = true; |
1182 | } |
a5e1f35c |
1183 | if ($question->answer == $answer->id) { |
1184 | $grade = (float)$answer->fraction * $answer->grade; |
a8a372cc |
1185 | $response[$answer->id] = true; |
a5e1f35c |
1186 | } |
1187 | } |
1188 | break; |
1189 | |
1190 | |
1191 | case MULTICHOICE: |
1192 | foreach($answers as $answer) { // There will be multiple answers, perhaps more than one is right |
1193 | $feedback[$answer->id] = $answer->feedback; |
8db3eadd |
1194 | if ($answer->fraction > 0) { |
1195 | $correct[$answer->id] = true; |
1196 | } |
54d0590b |
1197 | if (!empty($question->answer)) { |
a5e1f35c |
1198 | foreach ($question->answer as $questionanswer) { |
1199 | if ($questionanswer == $answer->id) { |
95dbc030 |
1200 | $response[$answer->id] = true; |
a5e1f35c |
1201 | if ($answer->single) { |
1202 | $grade = (float)$answer->fraction * $answer->grade; |
1203 | continue; |
1204 | } else { |
1205 | $grade += (float)$answer->fraction * $answer->grade; |
1206 | } |
1207 | } |
1208 | } |
1209 | } |
1210 | } |
1211 | break; |
95dbc030 |
1212 | |
1213 | case RANDOMMATCH: |
1214 | $bestanswer = array(); |
1215 | foreach ($answers as $answer) { // Loop through them all looking for correct answers |
1216 | if (empty($bestanswer[$answer->question])) { |
1217 | $bestanswer[$answer->question] = 0; |
1218 | $correct[$answer->question] = ""; |
1219 | } |
1220 | if ($answer->fraction > $bestanswer[$answer->question]) { |
1221 | $bestanswer[$answer->question] = $answer->fraction; |
1222 | $correct[$answer->question] = $answer->answer; |
1223 | } |
1224 | } |
1225 | $answerfraction = 1.0 / (float) count($question->answer); |
1226 | foreach ($question->answer as $questionanswer) { // For each random answered question |
1227 | $rqarr = explode('-', $questionanswer); // Extract question/answer. |
1228 | $rquestion = $rqarr[0]; |
1229 | $ranswer = $rqarr[1]; |
1230 | $response[$rquestion] = $questionanswer; |
1231 | if (isset($answers[$ranswer])) { // If the answer exists in the list |
1232 | $answer = $answers[$ranswer]; |
1233 | $feedback[$rquestion] = $answer->feedback; |
1234 | if ($answer->question == $rquestion) { // Check that this answer matches the question |
1235 | $grade += (float)$answer->fraction * $answer->grade * $answerfraction; |
1236 | } |
1237 | } |
1238 | } |
8db3eadd |
1239 | break; |
a5e1f35c |
1240 | |
a5e1f35c |
1241 | } |
1242 | if ($grade < 0.0) { // No negative grades |
1243 | $grade = 0.0; |
1244 | } |
10b9291c |
1245 | |
3a50203f |
1246 | $result->grades[$question->id] = round($grade, 2); |
a5e1f35c |
1247 | $result->sumgrades += $grade; |
1248 | $result->feedback[$question->id] = $feedback; |
a8a372cc |
1249 | $result->response[$question->id] = $response; |
8db3eadd |
1250 | $result->correct[$question->id] = $correct; |
a5e1f35c |
1251 | } |
1252 | |
8d94f5a0 |
1253 | $fraction = (float)($result->sumgrades / $quiz->sumgrades); |
1254 | $result->percentage = format_float($fraction * 100.0); |
1255 | $result->grade = format_float($fraction * $quiz->grade); |
3a50203f |
1256 | $result->sumgrades = round($result->sumgrades, 2); |
a5e1f35c |
1257 | |
1258 | return $result; |
1259 | } |
6d86b5dc |
1260 | |
1261 | |
49220fa7 |
1262 | function quiz_save_question_options($question) { |
1263 | /// Given some question info and some data about the the answers |
1264 | /// this function parses, organises and saves the question |
1265 | /// It is used by question.php when saving new data from a |
1266 | /// form, and also by import.php when importing questions |
1267 | /// |
1268 | /// Returns $result->error or $result->notice |
a5e1f35c |
1269 | |
49220fa7 |
1270 | switch ($question->qtype) { |
1271 | case SHORTANSWER: |
1272 | // Delete all the old answers |
1273 | // FIXME - instead of deleting, update existing answers |
1274 | // so as not to break existing references to them |
1275 | delete_records("quiz_answers", "question", $question->id); |
1276 | delete_records("quiz_shortanswer", "question", $question->id); |
1277 | |
1278 | $answers = array(); |
1279 | $maxfraction = -1; |
1280 | |
1281 | // Insert all the new answers |
1282 | foreach ($question->answer as $key => $dataanswer) { |
1283 | if ($dataanswer != "") { |
1284 | unset($answer); |
1285 | $answer->answer = $dataanswer; |
1286 | $answer->question = $question->id; |
1287 | $answer->fraction = $question->fraction[$key]; |
1288 | $answer->feedback = $question->feedback[$key]; |
1289 | if (!$answer->id = insert_record("quiz_answers", $answer)) { |
1290 | $result->error = "Could not insert quiz answer!"; |
1291 | return $result; |
1292 | } |
1293 | $answers[] = $answer->id; |
1294 | if ($question->fraction[$key] > $maxfraction) { |
1295 | $maxfraction = $question->fraction[$key]; |
1296 | } |
1297 | } |
1298 | } |
1299 | |
1300 | unset($options); |
1301 | $options->question = $question->id; |
1302 | $options->answers = implode(",",$answers); |
1303 | $options->usecase = $question->usecase; |
1304 | if (!insert_record("quiz_shortanswer", $options)) { |
1305 | $result->error = "Could not insert quiz shortanswer options!"; |
1306 | return $result; |
1307 | } |
1308 | |
1309 | /// Perform sanity checks on fractional grades |
1310 | if ($maxfraction != 1) { |
1311 | $maxfraction = $maxfraction * 100; |
1312 | $result->notice = get_string("fractionsnomax", "quiz", $maxfraction); |
1313 | return $result; |
1314 | } |
1315 | break; |
1316 | case TRUEFALSE: |
1317 | // FIXME - instead of deleting, update existing answers |
1318 | // so as not to break existing references to them |
1319 | delete_records("quiz_answers", "question", $question->id); |
1320 | delete_records("quiz_truefalse", "question", $question->id); |
1321 | |
1322 | $true->answer = get_string("true", "quiz"); |
1323 | $true->question = $question->id; |
1324 | $true->fraction = $question->answer; |
1325 | $true->feedback = $question->feedbacktrue; |
1326 | if (!$true->id = insert_record("quiz_answers", $true)) { |
1327 | $result->error = "Could not insert quiz answer \"true\")!"; |
1328 | return $result; |
1329 | } |
1330 | |
1331 | $false->answer = get_string("false", "quiz"); |
1332 | $false->question = $question->id; |
1333 | $false->fraction = 1 - (int)$question->answer; |
1334 | $false->feedback = $question->feedbackfalse; |
1335 | if (!$false->id = insert_record("quiz_answers", $false)) { |
1336 | $result->error = "Could not insert quiz answer \"false\")!"; |
1337 | return $result; |
1338 | } |
1339 | |
1340 | unset($options); |
1341 | $options->question = $question->id; |
1342 | $options->trueanswer = $true->id; |
1343 | $options->falseanswer = $false->id; |
1344 | if (!insert_record("quiz_truefalse", $options)) { |
1345 | $result->error = "Could not insert quiz truefalse options!"; |
1346 | return $result; |
1347 | } |
1348 | break; |
1349 | case MULTICHOICE: |
1350 | // FIXME - instead of deleting, update existing answers |
1351 | // so as not to break existing references to them |
1352 | delete_records("quiz_answers", "question", $question->id); |
1353 | delete_records("quiz_multichoice", "question", $question->id); |
1354 | |
1355 | $totalfraction = 0; |
1356 | $maxfraction = -1; |
1357 | |
1358 | $answers = array(); |
1359 | |
1360 | // Insert all the new answers |
1361 | foreach ($question->answer as $key => $dataanswer) { |
1362 | if ($dataanswer != "") { |
1363 | unset($answer); |
1364 | $answer->answer = $dataanswer; |
1365 | $answer->question = $question->id; |
1366 | $answer->fraction = $question->fraction[$key]; |
1367 | $answer->feedback = $question->feedback[$key]; |
1368 | if (!$answer->id = insert_record("quiz_answers", $answer)) { |
1369 | $result->error = "Could not insert quiz answer!"; |
1370 | return $result; |
1371 | } |
1372 | $answers[] = $answer->id; |
1373 | |
1374 | if ($question->fraction[$key] > 0) { // Sanity checks |
1375 | $totalfraction += $question->fraction[$key]; |
1376 | } |
1377 | if ($question->fraction[$key] > $maxfraction) { |
1378 | $maxfraction = $question->fraction[$key]; |
1379 | } |
1380 | } |
1381 | } |
1382 | |
1383 | unset($options); |
1384 | $options->question = $question->id; |
1385 | $options->answers = implode(",",$answers); |
1386 | $options->single = $question->single; |
1387 | if (!insert_record("quiz_multichoice", $options)) { |
1388 | $result->error = "Could not insert quiz multichoice options!"; |
1389 | return $result; |
1390 | } |
1391 | |
1392 | /// Perform sanity checks on fractional grades |
1393 | if ($options->single) { |
1394 | if ($maxfraction != 1) { |
1395 | $maxfraction = $maxfraction * 100; |
1396 | $result->notice = get_string("fractionsnomax", "quiz", $maxfraction); |
1397 | return $result; |
1398 | } |
1399 | } else { |
1400 | $totalfraction = round($totalfraction,2); |
1401 | if ($totalfraction != 1) { |
1402 | $totalfraction = $totalfraction * 100; |
1403 | $result->notice = get_string("fractionsaddwrong", "quiz", $totalfraction); |
1404 | return $result; |
1405 | } |
1406 | } |
1407 | break; |
95dbc030 |
1408 | |
1409 | case RANDOMMATCH: |
1410 | $options->question = $question->id; |
1411 | $options->choose = $question->choose; |
1412 | if ($existing = get_record("quiz_randommatch", "question", $options->question)) { |
1413 | $options->id = $existing->id; |
1414 | if (!update_record("quiz_randommatch", $options)) { |
1415 | $result->error = "Could not update quiz randommatch options!"; |
1416 | return $result; |
1417 | } |
1418 | } else { |
1419 | if (!insert_record("quiz_randommatch", $options)) { |
1420 | $result->error = "Could not insert quiz randommatch options!"; |
1421 | return $result; |
1422 | } |
1423 | } |
1424 | break; |
1425 | |
49220fa7 |
1426 | default: |
1427 | $result->error = "Unsupported question type ($question->qtype)!"; |
1428 | return $result; |
1429 | break; |
1430 | } |
1431 | return true; |
1432 | } |
1433 | |
1434 | |
95dbc030 |
1435 | |
1436 | |
730fd187 |
1437 | ?> |