New strings
[moodle.git] / mod / quiz / lib.php
CommitLineData
730fd187 1<?PHP // $Id$
2
a5e1f35c 3/// Library of function for module quiz
730fd187 4
a5e1f35c 5/// CONSTANTS ///////////////////////////////////////////////////////////////////
730fd187 6
a5e1f35c 7define("GRADEHIGHEST", "1");
8define("GRADEAVERAGE", "2");
9define("ATTEMPTFIRST", "3");
10define("ATTEMPTLAST", "4");
11$QUIZ_GRADE_METHOD = array ( GRADEHIGHEST => get_string("gradehighest", "quiz"),
12 GRADEAVERAGE => get_string("gradeaverage", "quiz"),
13 ATTEMPTFIRST => get_string("attemptfirst", "quiz"),
14 ATTEMPTLAST => get_string("attemptlast", "quiz"));
15
16define("SHORTANSWER", "1");
17define("TRUEFALSE", "2");
18define("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
27function 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
41function 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
55function 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
75function 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
85function 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
92function 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
103function 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 118function 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:&nbsp;&nbsp;";
166 echo "<INPUT TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$true->id\">$true->answer";
167 echo "&nbsp;&nbsp;&nbsp;";
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:&nbsp;&nbsp;</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 210function 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
237function 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 242function 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 251function 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 261function 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
291function 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 322function 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
360function 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
398function 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?>