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"); |
19 | $QUIZ_QUESTION_TYPE = array ( SHORTANSWER => get_string("shortanswer", "quiz"), |
20 | TRUEFALSE => get_string("truefalse", "quiz"), |
21 | MULTICHOICE => get_string("multichoice", "quiz")); |
22 | |
23 | |
24 | |
25 | /// FUNCTIONS /////////////////////////////////////////////////////////////////// |
730fd187 |
26 | |
27 | function quiz_add_instance($quiz) { |
a5e1f35c |
28 | /// Given an object containing all the necessary data, |
29 | /// (defined by the form in mod.html) this function |
30 | /// will create a new instance and return the id number |
31 | /// of the new instance. |
730fd187 |
32 | |
33 | $quiz->timemodified = time(); |
34 | |
35 | # May have to add extra stuff in here # |
36 | |
37 | return insert_record("quiz", $quiz); |
38 | } |
39 | |
40 | |
41 | function quiz_update_instance($quiz) { |
a5e1f35c |
42 | /// Given an object containing all the necessary data, |
43 | /// (defined by the form in mod.html) this function |
44 | /// will update an existing instance with new data. |
730fd187 |
45 | |
46 | $quiz->timemodified = time(); |
47 | $quiz->id = $quiz->instance; |
48 | |
49 | # May have to add extra stuff in here # |
50 | |
51 | return update_record("quiz", $quiz); |
52 | } |
53 | |
54 | |
55 | function quiz_delete_instance($id) { |
a5e1f35c |
56 | /// Given an ID of an instance of this module, |
57 | /// this function will permanently delete the instance |
58 | /// and any data that depends on it. |
730fd187 |
59 | |
60 | if (! $quiz = get_record("quiz", "id", "$id")) { |
61 | return false; |
62 | } |
63 | |
64 | $result = true; |
65 | |
66 | # Delete any dependent records here # |
67 | |
68 | if (! delete_records("quiz", "id", "$quiz->id")) { |
69 | $result = false; |
70 | } |
71 | |
72 | return $result; |
73 | } |
74 | |
75 | function quiz_user_outline($course, $user, $mod, $quiz) { |
a5e1f35c |
76 | /// Return a small object with summary information about what a |
77 | /// user has done with a given particular instance of this module |
78 | /// Used for user activity reports. |
79 | /// $return->time = the time they did it |
80 | /// $return->info = a short text description |
730fd187 |
81 | |
82 | return $return; |
83 | } |
84 | |
85 | function quiz_user_complete($course, $user, $mod, $quiz) { |
a5e1f35c |
86 | /// Print a detailed representation of what a user has done with |
87 | /// a given particular instance of this module, for user activity reports. |
730fd187 |
88 | |
89 | return true; |
90 | } |
91 | |
92 | function quiz_print_recent_activity(&$logs, $isteacher=false) { |
a5e1f35c |
93 | /// Given a list of logs, assumed to be those since the last login |
94 | /// this function prints a short list of changes related to this module |
95 | /// If isteacher is true then perhaps additional information is printed. |
96 | /// This function is called from course/lib.php: print_recent_activity() |
730fd187 |
97 | |
98 | global $CFG, $COURSE_TEACHER_COLOR; |
99 | |
100 | return $content; // True if anything was printed, otherwise false |
101 | } |
102 | |
103 | function quiz_cron () { |
a5e1f35c |
104 | /// Function to be run periodically according to the moodle cron |
105 | /// This function searches for things that need to be done, such |
106 | /// as sending out mail, toggling flags etc ... |
730fd187 |
107 | |
108 | global $CFG; |
109 | |
110 | return true; |
111 | } |
112 | |
113 | |
114 | ////////////////////////////////////////////////////////////////////////////////////// |
a5e1f35c |
115 | /// Any other quiz functions go here. Each of them must have a name that |
116 | /// starts with quiz_ |
730fd187 |
117 | |
14d8c0b4 |
118 | function quiz_print_question($number, $questionid, $grade, $courseid) { |
a5e1f35c |
119 | /// Prints a quiz question, any format |
14d8c0b4 |
120 | |
121 | if (!$question = get_record("quiz_questions", "id", $questionid)) { |
122 | notify("Error: Question not found!"); |
123 | } |
124 | |
125 | $stranswer = get_string("answer", "quiz"); |
126 | $strmarks = get_string("marks", "quiz"); |
127 | |
128 | echo "<TABLE WIDTH=100% CELLSPACING=10><TR><TD NOWRAP WIDTH=100 VALIGN=top>"; |
129 | echo "<P ALIGN=CENTER><B>$number</B><BR><FONT SIZE=1>$grade $strmarks</FONT></P>"; |
130 | print_spacer(1,100); |
131 | echo "</TD><TD VALIGN=TOP>"; |
132 | |
133 | switch ($question->type) { |
a5e1f35c |
134 | case SHORTANSWER: |
14d8c0b4 |
135 | if (!$options = get_record("quiz_shortanswer", "question", $question->id)) { |
136 | notify("Error: Missing question options!"); |
137 | } |
14d8c0b4 |
138 | echo "<P>$question->question</P>"; |
139 | if ($question->image) { |
140 | print_file_picture($question->image, $courseid, 200); |
141 | } |
142 | echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$question->id SIZE=20></P>"; |
143 | break; |
144 | |
a5e1f35c |
145 | case TRUEFALSE: |
14d8c0b4 |
146 | if (!$options = get_record("quiz_truefalse", "question", $question->id)) { |
147 | notify("Error: Missing question options!"); |
148 | } |
149 | if (!$true = get_record("quiz_answers", "id", $options->true)) { |
150 | notify("Error: Missing question answers!"); |
151 | } |
152 | if (!$false = get_record("quiz_answers", "id", $options->false)) { |
153 | notify("Error: Missing question answers!"); |
154 | } |
155 | if (!$true->answer) { |
156 | $true->answer = get_string("true", "quiz"); |
157 | } |
158 | if (!$false->answer) { |
159 | $false->answer = get_string("false", "quiz"); |
160 | } |
161 | echo "<P>$question->question</P>"; |
162 | if ($question->image) { |
163 | print_file_picture($question->image, $courseid, 200); |
164 | } |
165 | echo "<P ALIGN=RIGHT>$stranswer: "; |
166 | echo "<INPUT TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$true->id\">$true->answer"; |
167 | echo " "; |
168 | echo "<INPUT TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$false->id\">$false->answer</P>"; |
169 | break; |
170 | |
a5e1f35c |
171 | case MULTICHOICE: |
14d8c0b4 |
172 | if (!$options = get_record("quiz_multichoice", "question", $question->id)) { |
173 | notify("Error: Missing question options!"); |
174 | } |
a5e1f35c |
175 | if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) { |
14d8c0b4 |
176 | notify("Error: Missing question answers!"); |
177 | } |
178 | echo "<P>$question->question</P>"; |
179 | if ($question->image) { |
180 | print_file_picture($question->image, $courseid, 200); |
181 | } |
182 | echo "<TABLE ALIGN=right>"; |
183 | echo "<TR><TD valign=top>$stranswer: </TD><TD>"; |
184 | echo "<TABLE ALIGN=right>"; |
185 | $answerids = explode(",", $options->answers); |
186 | foreach ($answerids as $key => $answerid) { |
187 | $answer = $answers[$answerid]; |
188 | $qnum = $key + 1; |
189 | echo "<TR><TD valign=top>"; |
a5e1f35c |
190 | if ($options->single) { |
14d8c0b4 |
191 | echo "<INPUT TYPE=RADIO NAME=q$question->id VALUE=\"$answer->id\">"; |
192 | } else { |
a5e1f35c |
193 | echo "<INPUT TYPE=CHECKBOX NAME=q$question->id"."a$answer->id VALUE=\"$answer->id\">"; |
14d8c0b4 |
194 | } |
195 | echo "</TD>"; |
196 | echo "<TD valign=top>$qnum. $answer->answer</TD>"; |
197 | echo "</TR>"; |
198 | } |
199 | echo "</TABLE>"; |
200 | echo "</TABLE>"; |
201 | break; |
202 | |
203 | default: |
204 | notify("Error: Unknown question type!"); |
205 | } |
206 | |
207 | echo "</TD></TR></TABLE>"; |
3a506ca2 |
208 | } |
209 | |
a5e1f35c |
210 | function quiz_print_quiz_questions($quiz, $results=NULL) { |
211 | // Prints a whole quiz on one page. |
212 | |
213 | if (!$quiz->questions) { |
214 | error("No questions have been defined!", "view.php?id=$cm->id"); |
215 | } |
216 | |
217 | $questions = explode(",", $quiz->questions); |
218 | |
219 | if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) { |
220 | error("No grades were found for these questions!"); |
221 | } |
222 | |
223 | echo "<FORM METHOD=POST ACTION=attempt.php>"; |
224 | echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">"; |
225 | foreach ($questions as $key => $questionid) { |
226 | print_simple_box_start("CENTER", "90%"); |
227 | quiz_print_question($key+1, $questionid, $grades[$questionid]->grade, $course->id); |
228 | print_simple_box_end(); |
229 | echo "<BR>"; |
230 | } |
231 | echo "<CENTER><INPUT TYPE=submit VALUE=\"".get_string("savemyanswers", "quiz")."\"></CENTER>"; |
232 | echo "</FORM>"; |
233 | } |
234 | |
235 | |
3a506ca2 |
236 | |
237 | function quiz_get_user_attempts($quizid, $userid) { |
a5e1f35c |
238 | // Returns a list of all attempts by a user |
3a506ca2 |
239 | return get_records_sql("SELECT * FROM quiz_attempts WHERE quiz = '$quizid' and user = '$userid' ORDER by attempt ASC"); |
240 | } |
241 | |
a5e1f35c |
242 | function quiz_get_best_grade($quizid, $userid) { |
243 | /// Get the best current grade for a particular user in a quiz |
3a506ca2 |
244 | if (!$grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz = '$quizid' and user = '$userid'")) { |
245 | return 0; |
246 | } |
247 | |
248 | return $grade->grade; |
249 | } |
250 | |
579ddad5 |
251 | function quiz_get_grade_records($quiz) { |
252 | /// Gets all info required to display the table of quiz results |
253 | /// for report.php |
254 | |
255 | return get_records_sql("SELECT qg.*, u.firstname, u.lastname, u.picture |
256 | FROM quiz_grades qg, user u |
257 | WHERE qg.quiz = '$quiz->id' |
258 | AND qg.user = u.id"); |
259 | } |
260 | |
a5e1f35c |
261 | function quiz_save_best_grade($quiz, $user) { |
262 | /// Calculates the best grade out of all attempts at a quiz for a user, |
263 | /// and then saves that grade in the quiz_grades table. |
264 | |
265 | if (!$attempts = quiz_get_user_attempts($quiz->id, $user->id)) { |
266 | return false; |
267 | } |
268 | |
269 | $bestgrade = quiz_calculate_best_grade($quiz, $attempts); |
270 | $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade); |
271 | |
272 | if ($grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz='$quiz->id' AND user='$user->id'")) { |
273 | $grade->grade = $bestgrade; |
274 | $grade->timemodified = time(); |
275 | if (!update_record("quiz_grades", $grade)) { |
276 | return false; |
277 | } |
278 | } else { |
279 | $grade->quiz = $quiz->id; |
280 | $grade->user = $user->id; |
281 | $grade->grade = $bestgrade; |
282 | $grade->timemodified = time(); |
283 | if (!insert_record("quiz_grades", $grade)) { |
284 | return false; |
285 | } |
286 | } |
287 | return true; |
288 | } |
289 | |
290 | |
291 | function quiz_get_answer($question) { |
292 | // Given a question, returns the correct answers and grades |
293 | switch ($question->type) { |
294 | case SHORTANSWER; // Could be multiple answers |
295 | return get_records_sql("SELECT a.*, sa.case, g.grade |
296 | FROM quiz_shortanswer sa, quiz_answers a, quiz_question_grades g |
297 | WHERE sa.question = '$question->id' |
298 | AND sa.question = a.question |
299 | AND sa.question = g.question"); |
300 | break; |
301 | |
302 | case TRUEFALSE; // Should be always two answers |
303 | return get_records_sql("SELECT a.*, g.grade |
304 | FROM quiz_answers a, quiz_question_grades g |
305 | WHERE a.question = '$question->id' |
306 | AND a.question = g.question"); |
307 | break; |
308 | |
309 | case MULTICHOICE; // Should be multiple answers |
310 | return get_records_sql("SELECT a.*, mc.single, g.grade |
311 | FROM quiz_multichoice mc, quiz_answers a, quiz_question_grades g |
312 | WHERE mc.question = '$question->id' |
313 | AND mc.question = a.question |
314 | AND mc.question = g.question"); |
315 | break; |
316 | |
317 | default: |
318 | return false; |
319 | } |
320 | } |
321 | |
3a506ca2 |
322 | function quiz_calculate_best_grade($quiz, $attempts) { |
a5e1f35c |
323 | /// Calculate the best grade for a quiz given a number of attempts by a particular user. |
3a506ca2 |
324 | |
325 | switch ($quiz->grademethod) { |
a5e1f35c |
326 | |
327 | case ATTEMPTFIRST: |
3a506ca2 |
328 | foreach ($attempts as $attempt) { |
a5e1f35c |
329 | return $attempt->sumgrades; |
3a506ca2 |
330 | } |
a5e1f35c |
331 | break; |
332 | |
333 | case ATTEMPTLAST: |
334 | foreach ($attempts as $attempt) { |
335 | $final = $attempt->sumgrades; |
336 | } |
337 | return $final; |
3a506ca2 |
338 | |
a5e1f35c |
339 | case GRADEAVERAGE: |
3a506ca2 |
340 | $sum = 0; |
341 | $count = 0; |
342 | foreach ($attempts as $attempt) { |
a5e1f35c |
343 | $sum += $attempt->sumgrades; |
3a506ca2 |
344 | $count++; |
345 | } |
346 | return (float)$sum/$count; |
347 | |
3a506ca2 |
348 | default: |
a5e1f35c |
349 | case GRADEHIGHEST: |
350 | $max = 0; |
3a506ca2 |
351 | foreach ($attempts as $attempt) { |
a5e1f35c |
352 | if ($attempt->sumgrades > $max) { |
353 | $max = $attempt->sumgrades; |
354 | } |
3a506ca2 |
355 | } |
a5e1f35c |
356 | return $max; |
357 | } |
358 | } |
359 | |
360 | function quiz_save_attempt($quiz, $questions, $result, $attemptnum) { |
361 | /// Given a quiz, a list of attempted questions and a total grade |
362 | /// this function saves EVERYTHING so it can be reconstructed later |
363 | /// if necessary. |
364 | |
365 | global $USER; |
366 | |
367 | // First let's save the attempt record itself |
368 | |
369 | $attempt->quiz = $quiz->id; |
370 | $attempt->user = $USER->id; |
371 | $attempt->attempt = $attemptnum; |
372 | $attempt->sumgrades = $result->sumgrades; |
373 | $attempt->timemodified = time(); |
374 | |
375 | if (!$attempt->id = insert_record("quiz_attempts", $attempt)) { |
376 | return false; |
377 | } |
378 | |
379 | // Now let's save all the questions for this attempt |
380 | |
381 | foreach ($questions as $question) { |
382 | $response->attempt = $attempt->id; |
383 | $response->question = $question->id; |
384 | $response->grade = $result->grades[$question->id]; |
385 | if ($question->answer) { |
386 | $response->answer = implode(",",$question->answer); |
387 | } else { |
388 | $response->answer = ""; |
389 | } |
390 | if (!insert_record("quiz_responses", $response)) { |
391 | return false; |
392 | } |
3a506ca2 |
393 | } |
a5e1f35c |
394 | return true; |
3a506ca2 |
395 | } |
730fd187 |
396 | |
a5e1f35c |
397 | |
398 | function quiz_grade_attempt_results($quiz, $questions) { |
399 | /// Given a list of questions (including answers for each one) |
400 | /// this function does all the hard work of calculating the |
401 | /// grades for each question, as well as a total grade for |
402 | /// for the whole quiz. It returns everything in a structure |
403 | /// that looks like: |
404 | /// $result->sumgrades (sum of all grades for all questions) |
405 | /// $result->percentage (Percentage of grades that were correct) |
406 | /// $result->grade (final grade result for the whole quiz) |
407 | /// $result->grades[] (array of grades, indexed by question id) |
408 | /// $result->feedback[] (array of feedback arrays, indexed by question id) |
409 | |
410 | if (!$questions) { |
411 | error("No questions!"); |
412 | } |
413 | |
414 | $result->sumgrades = 0; |
415 | |
416 | foreach ($questions as $question) { |
417 | if (!$answers = quiz_get_answer($question)) { |
418 | error("No answer defined for question id $question->id!"); |
419 | } |
420 | |
421 | $grade = 0; // default |
422 | $feedback = array (); |
423 | |
424 | switch ($question->type) { |
425 | case SHORTANSWER: |
426 | if ($question->answer) { |
427 | $question->answer = $question->answer[0]; |
428 | } else { |
429 | $question->answer = NULL; |
430 | } |
431 | foreach($answers as $answer) { // There might be multiple right answers |
432 | $feedback[$answer->id] = $answer->feedback; |
433 | if (!$answer->case) { // Don't compare case |
434 | $answer->answer = strtolower($answer->answer); |
435 | $question->answer = strtolower($question->answer); |
436 | } |
437 | if ($question->answer == $answer->answer) { |
438 | $grade = (float)$answer->fraction * $answer->grade; |
439 | } |
440 | } |
441 | break; |
442 | |
443 | |
444 | case TRUEFALSE: |
445 | if ($question->answer) { |
446 | $question->answer = $question->answer[0]; |
447 | } else { |
448 | $question->answer = NULL; |
449 | } |
450 | foreach($answers as $answer) { // There should be two answers (true and false) |
451 | $feedback[$answer->id] = $answer->feedback; |
452 | if ($question->answer == $answer->id) { |
453 | $grade = (float)$answer->fraction * $answer->grade; |
454 | } |
455 | } |
456 | break; |
457 | |
458 | |
459 | case MULTICHOICE: |
460 | foreach($answers as $answer) { // There will be multiple answers, perhaps more than one is right |
461 | $feedback[$answer->id] = $answer->feedback; |
462 | if ($question->answer) { |
463 | foreach ($question->answer as $questionanswer) { |
464 | if ($questionanswer == $answer->id) { |
465 | if ($answer->single) { |
466 | $grade = (float)$answer->fraction * $answer->grade; |
467 | continue; |
468 | } else { |
469 | $grade += (float)$answer->fraction * $answer->grade; |
470 | } |
471 | } |
472 | } |
473 | } |
474 | } |
475 | break; |
476 | |
477 | |
478 | } |
479 | if ($grade < 0.0) { // No negative grades |
480 | $grade = 0.0; |
481 | } |
482 | $result->grades[$question->id] = $grade; |
483 | $result->sumgrades += $grade; |
484 | $result->feedback[$question->id] = $feedback; |
485 | } |
486 | |
487 | $result->percentage = ($result->sumgrades / $quiz->sumgrades); |
488 | $result->grade = $result->percentage * $quiz->grade; |
489 | |
490 | return $result; |
491 | } |
492 | |
730fd187 |
493 | ?> |