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"); |
8d94f5a0 |
19 | $QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz"), |
a5e1f35c |
20 | TRUEFALSE => get_string("truefalse", "quiz"), |
8d94f5a0 |
21 | SHORTANSWER => get_string("shortanswer", "quiz") ); |
a5e1f35c |
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 | |
49dcdd18 |
33 | $quiz->created = time(); |
730fd187 |
34 | $quiz->timemodified = time(); |
49dcdd18 |
35 | $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday, |
36 | $quiz->openhour, $quiz->openminute, $quiz->opensecond); |
37 | $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday, |
38 | $quiz->closehour, $quiz->closeminute, $quiz->closesecond); |
730fd187 |
39 | |
7bd1aa1d |
40 | if (!$quiz->id = insert_record("quiz", $quiz)) { |
41 | return false; // some error occurred |
42 | } |
43 | |
10b9291c |
44 | // The grades for every question in this quiz are stored in an array |
7bd1aa1d |
45 | if ($quiz->grades) { |
46 | foreach ($quiz->grades as $question => $grade) { |
8d94f5a0 |
47 | if ($question and $grade) { |
48 | unset($questiongrade); |
49 | $questiongrade->quiz = $quiz->id; |
50 | $questiongrade->question = $question; |
51 | $questiongrade->grade = $grade; |
52 | if (!insert_record("quiz_question_grades", $questiongrade)) { |
53 | return false; |
54 | } |
7bd1aa1d |
55 | } |
56 | } |
57 | } |
730fd187 |
58 | |
7bd1aa1d |
59 | return $quiz->id; |
730fd187 |
60 | } |
61 | |
62 | |
63 | function quiz_update_instance($quiz) { |
a5e1f35c |
64 | /// Given an object containing all the necessary data, |
65 | /// (defined by the form in mod.html) this function |
66 | /// will update an existing instance with new data. |
730fd187 |
67 | |
68 | $quiz->timemodified = time(); |
49dcdd18 |
69 | $quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday, |
70 | $quiz->openhour, $quiz->openminute, $quiz->opensecond); |
71 | $quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday, |
72 | $quiz->closehour, $quiz->closeminute, $quiz->closesecond); |
730fd187 |
73 | $quiz->id = $quiz->instance; |
74 | |
7bd1aa1d |
75 | if (!update_record("quiz", $quiz)) { |
76 | return false; // some error occurred |
77 | } |
730fd187 |
78 | |
7bd1aa1d |
79 | |
10b9291c |
80 | // The grades for every question in this quiz are stored in an array |
7bd1aa1d |
81 | // Insert or update records as appropriate |
82 | |
83 | $existing = get_records("quiz_question_grades", "quiz", $quiz->id, "", "question,grade,id"); |
84 | |
85 | if ($quiz->grades) { |
86 | foreach ($quiz->grades as $question => $grade) { |
8d94f5a0 |
87 | if ($question and $grade) { |
88 | unset($questiongrade); |
89 | $questiongrade->quiz = $quiz->id; |
90 | $questiongrade->question = $question; |
91 | $questiongrade->grade = $grade; |
92 | if (isset($existing[$question])) { |
93 | if ($existing[$question]->grade != $grade) { |
94 | $questiongrade->id = $existing[$question]->id; |
95 | if (!update_record("quiz_question_grades", $questiongrade)) { |
96 | return false; |
97 | } |
98 | } |
99 | } else { |
100 | if (!insert_record("quiz_question_grades", $questiongrade)) { |
7bd1aa1d |
101 | return false; |
102 | } |
103 | } |
7bd1aa1d |
104 | } |
105 | } |
106 | } |
107 | |
108 | return true; |
730fd187 |
109 | } |
110 | |
111 | |
112 | function quiz_delete_instance($id) { |
a5e1f35c |
113 | /// Given an ID of an instance of this module, |
114 | /// this function will permanently delete the instance |
115 | /// and any data that depends on it. |
730fd187 |
116 | |
117 | if (! $quiz = get_record("quiz", "id", "$id")) { |
118 | return false; |
119 | } |
120 | |
121 | $result = true; |
122 | |
10b9291c |
123 | if ($attempts = get_record("quiz_attempts", "quiz", "$quiz->id")) { |
124 | foreach ($attempts as $attempt) { |
125 | if (! delete_records("quiz_responses", "attempt", "$attempt->id")) { |
126 | $result = false; |
127 | } |
128 | } |
129 | } |
130 | |
131 | if (! delete_records("quiz_attempts", "quiz", "$quiz->id")) { |
132 | $result = false; |
133 | } |
134 | |
135 | if (! delete_records("quiz_grades", "quiz", "$quiz->id")) { |
136 | $result = false; |
137 | } |
138 | |
139 | if (! delete_records("quiz_question_grades", "quiz", "$quiz->id")) { |
140 | $result = false; |
141 | } |
730fd187 |
142 | |
143 | if (! delete_records("quiz", "id", "$quiz->id")) { |
144 | $result = false; |
145 | } |
146 | |
147 | return $result; |
148 | } |
149 | |
150 | function quiz_user_outline($course, $user, $mod, $quiz) { |
a5e1f35c |
151 | /// Return a small object with summary information about what a |
152 | /// user has done with a given particular instance of this module |
153 | /// Used for user activity reports. |
154 | /// $return->time = the time they did it |
155 | /// $return->info = a short text description |
98092498 |
156 | if ($grade = get_record_sql("SELECT * FROM quiz_grades WHERE user = '$user->id' AND quiz = '$quiz->id'")) { |
157 | |
158 | if ($grade->grade) { |
159 | $result->info = get_string("grade").": $grade->grade"; |
160 | } |
161 | $result->time = $grade->timemodified; |
162 | return $result; |
163 | } |
164 | return NULL; |
730fd187 |
165 | |
166 | return $return; |
167 | } |
168 | |
169 | function quiz_user_complete($course, $user, $mod, $quiz) { |
a5e1f35c |
170 | /// Print a detailed representation of what a user has done with |
171 | /// a given particular instance of this module, for user activity reports. |
730fd187 |
172 | |
173 | return true; |
174 | } |
175 | |
176 | function quiz_print_recent_activity(&$logs, $isteacher=false) { |
a5e1f35c |
177 | /// Given a list of logs, assumed to be those since the last login |
178 | /// this function prints a short list of changes related to this module |
179 | /// If isteacher is true then perhaps additional information is printed. |
180 | /// This function is called from course/lib.php: print_recent_activity() |
730fd187 |
181 | |
182 | global $CFG, $COURSE_TEACHER_COLOR; |
183 | |
184 | return $content; // True if anything was printed, otherwise false |
185 | } |
186 | |
187 | function quiz_cron () { |
a5e1f35c |
188 | /// Function to be run periodically according to the moodle cron |
189 | /// This function searches for things that need to be done, such |
190 | /// as sending out mail, toggling flags etc ... |
730fd187 |
191 | |
192 | global $CFG; |
193 | |
194 | return true; |
195 | } |
196 | |
d0ac6bc2 |
197 | function quiz_grades($quizid) { |
858deff0 |
198 | /// Must return an array of grades, indexed by user, and a max grade. |
199 | |
200 | $return->grades = get_records_sql_menu("SELECT user,grade FROM quiz_grades WHERE quiz = '$quizid'"); |
201 | $return->maxgrade = get_field("quiz", "grade", "id", "$quizid"); |
202 | return $return; |
d0ac6bc2 |
203 | } |
204 | |
730fd187 |
205 | |
206 | ////////////////////////////////////////////////////////////////////////////////////// |
a5e1f35c |
207 | /// Any other quiz functions go here. Each of them must have a name that |
208 | /// starts with quiz_ |
730fd187 |
209 | |
a8a372cc |
210 | function quiz_print_comment($text) { |
211 | global $THEME; |
212 | |
213 | echo "<FONT COLOR=\"$THEME->cellheading2\">".text_to_html($text, true, false)."</FONT>"; |
214 | } |
215 | |
216 | function quiz_print_question($number, $questionid, $grade, $courseid, |
217 | $feedback=NULL, $response=NULL, $actualgrade=NULL) { |
a5e1f35c |
218 | /// Prints a quiz question, any format |
a8a372cc |
219 | global $THEME; |
220 | |
221 | $comment = $THEME->cellheading2; |
14d8c0b4 |
222 | |
223 | if (!$question = get_record("quiz_questions", "id", $questionid)) { |
224 | notify("Error: Question not found!"); |
225 | } |
226 | |
227 | $stranswer = get_string("answer", "quiz"); |
228 | $strmarks = get_string("marks", "quiz"); |
229 | |
230 | echo "<TABLE WIDTH=100% CELLSPACING=10><TR><TD NOWRAP WIDTH=100 VALIGN=top>"; |
a8a372cc |
231 | echo "<P ALIGN=CENTER><B>$number</B></P>"; |
19c4f55c |
232 | if ($feedback or $response) { |
a8a372cc |
233 | echo "<P ALIGN=CENTER><FONT SIZE=1>$strmarks: $actualgrade/$grade</FONT></P>"; |
234 | } else { |
235 | echo "<P ALIGN=CENTER><FONT SIZE=1>$grade $strmarks</FONT></P>"; |
236 | } |
14d8c0b4 |
237 | print_spacer(1,100); |
238 | echo "</TD><TD VALIGN=TOP>"; |
239 | |
240 | switch ($question->type) { |
a5e1f35c |
241 | case SHORTANSWER: |
14d8c0b4 |
242 | if (!$options = get_record("quiz_shortanswer", "question", $question->id)) { |
243 | notify("Error: Missing question options!"); |
244 | } |
2a2c9725 |
245 | echo text_to_html($question->questiontext); |
14d8c0b4 |
246 | if ($question->image) { |
247 | print_file_picture($question->image, $courseid, 200); |
248 | } |
a8a372cc |
249 | if ($response) { |
250 | $value = "VALUE=\"$response[0]\""; |
251 | } |
252 | echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$question->id SIZE=20 $value></P>"; |
253 | if ($feedback) { |
254 | quiz_print_comment("<P ALIGN=right>$feedback[0]</P>"); |
255 | } |
14d8c0b4 |
256 | break; |
257 | |
a5e1f35c |
258 | case TRUEFALSE: |
14d8c0b4 |
259 | if (!$options = get_record("quiz_truefalse", "question", $question->id)) { |
260 | notify("Error: Missing question options!"); |
261 | } |
262 | if (!$true = get_record("quiz_answers", "id", $options->true)) { |
263 | notify("Error: Missing question answers!"); |
264 | } |
265 | if (!$false = get_record("quiz_answers", "id", $options->false)) { |
266 | notify("Error: Missing question answers!"); |
267 | } |
268 | if (!$true->answer) { |
269 | $true->answer = get_string("true", "quiz"); |
270 | } |
271 | if (!$false->answer) { |
272 | $false->answer = get_string("false", "quiz"); |
273 | } |
2a2c9725 |
274 | echo text_to_html($question->questiontext); |
14d8c0b4 |
275 | if ($question->image) { |
276 | print_file_picture($question->image, $courseid, 200); |
277 | } |
a8a372cc |
278 | |
279 | if ($response[$true->id]) { |
280 | $truechecked = "CHECKED"; |
281 | $feedbackid = $true->id; |
282 | } else if ($response[$false->id]) { |
283 | $falsechecked = "CHECKED"; |
284 | $feedbackid = $false->id; |
285 | } |
14d8c0b4 |
286 | echo "<P ALIGN=RIGHT>$stranswer: "; |
a8a372cc |
287 | echo "<INPUT $truechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$true->id\">$true->answer"; |
14d8c0b4 |
288 | echo " "; |
a8a372cc |
289 | echo "<INPUT $falsechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$false->id\">$false->answer</P>"; |
290 | if ($feedback) { |
291 | quiz_print_comment("<P ALIGN=right>$feedback[$feedbackid]</P>"); |
292 | } |
293 | |
14d8c0b4 |
294 | break; |
295 | |
a5e1f35c |
296 | case MULTICHOICE: |
14d8c0b4 |
297 | if (!$options = get_record("quiz_multichoice", "question", $question->id)) { |
298 | notify("Error: Missing question options!"); |
299 | } |
a5e1f35c |
300 | if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) { |
14d8c0b4 |
301 | notify("Error: Missing question answers!"); |
302 | } |
2a2c9725 |
303 | echo text_to_html($question->questiontext); |
14d8c0b4 |
304 | if ($question->image) { |
305 | print_file_picture($question->image, $courseid, 200); |
306 | } |
307 | echo "<TABLE ALIGN=right>"; |
308 | echo "<TR><TD valign=top>$stranswer: </TD><TD>"; |
309 | echo "<TABLE ALIGN=right>"; |
310 | $answerids = explode(",", $options->answers); |
a8a372cc |
311 | |
14d8c0b4 |
312 | foreach ($answerids as $key => $answerid) { |
313 | $answer = $answers[$answerid]; |
314 | $qnum = $key + 1; |
a8a372cc |
315 | |
316 | if ($feedback and $response[$answerid]) { |
317 | $checked = "CHECKED"; |
318 | } else { |
319 | $checked = ""; |
320 | } |
14d8c0b4 |
321 | echo "<TR><TD valign=top>"; |
a5e1f35c |
322 | if ($options->single) { |
a8a372cc |
323 | echo "<INPUT $checked TYPE=RADIO NAME=q$question->id VALUE=\"$answer->id\">"; |
14d8c0b4 |
324 | } else { |
a8a372cc |
325 | echo "<INPUT $checked TYPE=CHECKBOX NAME=q$question->id"."a$answer->id VALUE=\"$answer->id\">"; |
14d8c0b4 |
326 | } |
327 | echo "</TD>"; |
328 | echo "<TD valign=top>$qnum. $answer->answer</TD>"; |
a8a372cc |
329 | if ($feedback) { |
330 | echo "<TD valign=top> "; |
331 | if ($response[$answerid]) { |
332 | quiz_print_comment($feedback[$answerid]); |
333 | } |
334 | echo "</TD>"; |
335 | } |
14d8c0b4 |
336 | echo "</TR>"; |
337 | } |
338 | echo "</TABLE>"; |
339 | echo "</TABLE>"; |
340 | break; |
341 | |
342 | default: |
343 | notify("Error: Unknown question type!"); |
344 | } |
345 | |
346 | echo "</TD></TR></TABLE>"; |
3a506ca2 |
347 | } |
348 | |
a5e1f35c |
349 | function quiz_print_quiz_questions($quiz, $results=NULL) { |
350 | // Prints a whole quiz on one page. |
351 | |
352 | if (!$quiz->questions) { |
10b9291c |
353 | notify("No questions have been defined!", "view.php?id=$cm->id"); |
354 | return false; |
a5e1f35c |
355 | } |
356 | |
357 | $questions = explode(",", $quiz->questions); |
358 | |
359 | if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) { |
10b9291c |
360 | notify("No grades were found for these questions!"); |
361 | return false; |
a5e1f35c |
362 | } |
363 | |
364 | echo "<FORM METHOD=POST ACTION=attempt.php>"; |
365 | echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">"; |
a8a372cc |
366 | |
a5e1f35c |
367 | foreach ($questions as $key => $questionid) { |
a8a372cc |
368 | if ($results) { |
369 | $feedback = $results->feedback[$questionid]; |
370 | } else { |
371 | $feedback = NULL; |
372 | } |
a5e1f35c |
373 | print_simple_box_start("CENTER", "90%"); |
a8a372cc |
374 | quiz_print_question($key+1, $questionid, $grades[$questionid]->grade, $quiz->course, |
375 | $results->feedback[$questionid], $results->response[$questionid], $results->grades[$questionid]); |
a5e1f35c |
376 | print_simple_box_end(); |
377 | echo "<BR>"; |
378 | } |
a8a372cc |
379 | |
380 | if (!$results) { |
381 | echo "<CENTER><INPUT TYPE=submit VALUE=\"".get_string("savemyanswers", "quiz")."\"></CENTER>"; |
382 | } |
a5e1f35c |
383 | echo "</FORM>"; |
10b9291c |
384 | |
385 | return true; |
a5e1f35c |
386 | } |
6a952ce7 |
387 | |
388 | function quiz_get_default_category($courseid) { |
389 | if ($categories = get_records("quiz_categories", "course", $courseid, "id")) { |
390 | foreach ($categories as $category) { |
391 | return $category; // Return the first one (lowest id) |
392 | } |
393 | } |
394 | |
395 | // Otherwise, we need to make one |
10b9291c |
396 | $category->name = get_string("default", "quiz"); |
397 | $category->info = get_string("defaultinfo", "quiz"); |
6a952ce7 |
398 | $category->course = $courseid; |
399 | $category->publish = 0; |
400 | |
401 | if (!$category->id = insert_record("quiz_categories", $category)) { |
402 | notify("Error creating a default category!"); |
403 | return false; |
404 | } |
405 | return $category; |
406 | } |
407 | |
408 | function quiz_print_category_form($course, $current) { |
409 | // Prints a form to choose categories |
410 | |
411 | if (!$categories = get_records_sql_menu("SELECT id,name FROM quiz_categories |
412 | WHERE course='$course->id' OR publish = '1' |
413 | ORDER by name ASC")) { |
414 | if (!$category = quiz_get_default_category($course->id)) { |
415 | notify("Error creating a default category!"); |
416 | return false; |
417 | } |
418 | $categories[$category->id] = $category->name; |
419 | } |
8d94f5a0 |
420 | foreach ($categories as $key => $category) { |
421 | if ($category->publish) { |
422 | if ($course = get_record_sql("course", "id", $category->course)) { |
423 | $categories[$key]->name .= " ($course->shortname)"; |
424 | } |
425 | } |
426 | } |
6a952ce7 |
427 | $strcategory = get_string("category", "quiz"); |
428 | $strshow = get_string("show", "quiz"); |
6b069ece |
429 | $streditcats = get_string("editcategories", "quiz"); |
6a952ce7 |
430 | |
6b069ece |
431 | echo "<TABLE width=\"100%\"><TR><TD>"; |
6a952ce7 |
432 | echo "<FORM METHOD=POST ACTION=edit.php>"; |
433 | echo "<B>$strcategory:</B> "; |
434 | choose_from_menu($categories, "cat", "$current"); |
92a3c884 |
435 | echo "<INPUT TYPE=submit VALUE=\"$strshow\">"; |
6a952ce7 |
436 | echo "</FORM>"; |
6b069ece |
437 | echo "</TD><TD align=right>"; |
438 | echo "<FORM METHOD=GET ACTION=category.php>"; |
439 | echo "<INPUT TYPE=hidden NAME=id VALUE=\"$course->id\">"; |
440 | echo "<INPUT TYPE=submit VALUE=\"$streditcats\">"; |
441 | echo "</FORM>"; |
442 | echo "</TD></TR></TABLE>"; |
6a952ce7 |
443 | } |
444 | |
445 | |
7bd1aa1d |
446 | function quiz_get_all_question_grades($questionlist, $quizid) { |
447 | // Given a list of question IDs, finds grades or invents them to |
448 | // create an array of matching grades |
449 | |
92a3c884 |
450 | $questions = get_records_sql("SELECT question,grade FROM quiz_question_grades |
7bd1aa1d |
451 | WHERE quiz = '$quizid' |
452 | AND question IN ($questionlist)"); |
453 | |
454 | $list = explode(",", $questionlist); |
455 | $grades = array(); |
456 | |
457 | foreach ($list as $qid) { |
458 | if (isset($questions[$qid])) { |
459 | $grades[$qid] = $questions[$qid]->grade; |
460 | } else { |
461 | $grades[$qid] = 1; |
462 | } |
463 | } |
464 | return $grades; |
465 | } |
466 | |
467 | |
468 | function quiz_print_question_list($questionlist, $grades) { |
6a952ce7 |
469 | // Prints a list of quiz questions in a small layout form with knobs |
7bd1aa1d |
470 | // $questionlist is comma-separated list |
471 | // $grades is an array of corresponding grades |
6a952ce7 |
472 | |
473 | global $THEME; |
474 | |
475 | if (!$questionlist) { |
476 | echo "<P align=center>"; |
477 | print_string("noquestions", "quiz"); |
478 | echo "</P>"; |
479 | return; |
480 | } |
481 | |
482 | $order = explode(",", $questionlist); |
483 | |
7bd1aa1d |
484 | if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) { |
6a952ce7 |
485 | error("No questions were found!"); |
486 | } |
487 | |
488 | $strorder = get_string("order"); |
489 | $strquestionname = get_string("questionname", "quiz"); |
490 | $strgrade = get_string("grade"); |
491 | $strdelete = get_string("delete"); |
492 | $stredit = get_string("edit"); |
493 | $strmoveup = get_string("moveup"); |
494 | $strmovedown = get_string("movedown"); |
495 | $strsavegrades = get_string("savegrades", "quiz"); |
496 | |
10b9291c |
497 | for ($i=10; $i>=0; $i--) { |
7bd1aa1d |
498 | $gradesmenu[$i] = $i; |
6a952ce7 |
499 | } |
500 | $count = 0; |
501 | $sumgrade = 0; |
502 | $total = count($order); |
503 | echo "<FORM METHOD=post ACTION=edit.php>"; |
504 | echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">"; |
10b9291c |
505 | echo "<TR><TH WIDTH=10 COLSPAN=3>$strorder</TH><TH align=left WIDTH=\"100%\">$strquestionname</TH><TH WIDTH=10>$strgrade</TH><TH WIDTH=10>$stredit</TH></TR>"; |
6a952ce7 |
506 | foreach ($order as $qnum) { |
507 | $count++; |
508 | echo "<TR BGCOLOR=\"$THEME->cellcontent\">"; |
509 | echo "<TD>$count</TD>"; |
510 | echo "<TD>"; |
511 | if ($count != 1) { |
512 | echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG |
513 | SRC=\"../../pix/t/up.gif\" BORDER=0></A>"; |
514 | } |
515 | echo "</TD>"; |
516 | echo "<TD>"; |
517 | if ($count != $total) { |
518 | echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG |
519 | SRC=\"../../pix/t/down.gif\" BORDER=0></A>"; |
520 | } |
521 | echo "</TD>"; |
522 | echo "<TD>".$questions[$qnum]->name."</TD>"; |
523 | echo "<TD>"; |
8d94f5a0 |
524 | choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], ""); |
6a952ce7 |
525 | echo "<TD>"; |
526 | echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG |
527 | SRC=\"../../pix/t/delete.gif\" BORDER=0></A> "; |
528 | echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG |
529 | SRC=\"../../pix/t/edit.gif\" BORDER=0></A>"; |
530 | echo "</TD>"; |
531 | |
7bd1aa1d |
532 | $sumgrade += $grades[$qnum]; |
6a952ce7 |
533 | } |
534 | echo "<TR><TD COLSPAN=3><TD ALIGN=right>"; |
535 | echo "<INPUT TYPE=submit VALUE=\"$strsavegrades:\">"; |
8d94f5a0 |
536 | echo "<INPUT TYPE=hidden NAME=setgrades VALUE=\"save\">"; |
6a952ce7 |
537 | echo "<TD ALIGN=LEFT BGCOLOR=\"$THEME->cellcontent\">"; |
538 | echo "<B>$sumgrade</B>"; |
539 | echo "</TD><TD></TD></TR>"; |
540 | echo "</TABLE>"; |
541 | echo "</FORM>"; |
10b9291c |
542 | |
543 | return $sumgrade; |
6a952ce7 |
544 | } |
545 | |
546 | |
547 | function quiz_print_cat_question_list($categoryid) { |
548 | // Prints a form to choose categories |
549 | |
550 | global $THEME, $QUIZ_QUESTION_TYPE; |
551 | |
10b9291c |
552 | $strcategory = get_string("category", "quiz"); |
6a952ce7 |
553 | $strquestion = get_string("question", "quiz"); |
554 | $strnoquestions = get_string("noquestions", "quiz"); |
555 | $strselect = get_string("select", "quiz"); |
556 | $strcreatenewquestion = get_string("createnewquestion", "quiz"); |
557 | $strquestionname = get_string("questionname", "quiz"); |
558 | $strdelete = get_string("delete"); |
559 | $stredit = get_string("edit"); |
560 | $straddselectedtoquiz = get_string("addselectedtoquiz", "quiz"); |
561 | |
562 | if (!$categoryid) { |
563 | echo "<P align=center>"; |
564 | print_string("selectcategoryabove", "quiz"); |
565 | echo "</P>"; |
566 | return; |
567 | } |
a5e1f35c |
568 | |
6a952ce7 |
569 | if (!$category = get_record("quiz_categories", "id", "$categoryid")) { |
570 | notify("Category not found!"); |
571 | return; |
572 | } |
8d94f5a0 |
573 | echo "<CENTER>"; |
10b9291c |
574 | echo text_to_html($category->info); |
6a952ce7 |
575 | |
10b9291c |
576 | echo "<FORM METHOD=GET ACTION=question.php>"; |
6a952ce7 |
577 | echo "<B>$strquestion:</B> "; |
578 | choose_from_menu($QUIZ_QUESTION_TYPE, "type", "", ""); |
579 | echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">"; |
580 | echo "<INPUT TYPE=submit NAME=new VALUE=\"$strcreatenewquestion\">"; |
581 | echo "</FORM>"; |
8d94f5a0 |
582 | echo "</CENTER>"; |
6a952ce7 |
583 | |
584 | if (!$questions = get_records("quiz_questions", "category", $category->id)) { |
585 | echo "<P align=center>"; |
586 | print_string("noquestions", "quiz"); |
587 | echo "</P>"; |
588 | return; |
589 | } |
590 | |
10b9291c |
591 | $canedit = isteacher($category->course); |
592 | |
6a952ce7 |
593 | echo "<FORM METHOD=post ACTION=edit.php>"; |
594 | echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">"; |
10b9291c |
595 | echo "<TR><TH width=10>$strselect</TH><TH width=* align=left>$strquestionname</TH>"; |
596 | if ($canedit) { |
597 | echo "<TH width=10>$stredit</TH>"; |
598 | } |
599 | echo "</TR>"; |
6a952ce7 |
600 | foreach ($questions as $question) { |
601 | echo "<TR BGCOLOR=\"$THEME->cellcontent\">"; |
602 | echo "<TD ALIGN=CENTER>"; |
603 | echo "<INPUT TYPE=CHECKBOX NAME=q$question->id VALUE=\"1\">"; |
604 | echo "</TD>"; |
605 | echo "<TD>".$question->name."</TD>"; |
10b9291c |
606 | if ($canedit) { |
607 | echo "<TD>"; |
608 | echo "<A TITLE=\"$strdelete\" HREF=\"question.php?delete=$question->id\"><IMG |
609 | SRC=\"../../pix/t/delete.gif\" BORDER=0></A> "; |
610 | echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG |
611 | SRC=\"../../pix/t/edit.gif\" BORDER=0></A>"; |
612 | echo "</TD></TR>"; |
613 | } |
614 | echo "</TR>"; |
6a952ce7 |
615 | } |
616 | echo "<TR><TD COLSPAN=3>"; |
617 | echo "<INPUT TYPE=hidden NAME=add VALUE=\"1\">"; |
618 | echo "<INPUT TYPE=submit VALUE=\"<< $straddselectedtoquiz\">"; |
619 | echo "</TD></TR>"; |
620 | echo "</TABLE>"; |
621 | echo "</FORM>"; |
622 | } |
a5e1f35c |
623 | |
3a506ca2 |
624 | |
958aafe2 |
625 | function quiz_start_attempt($quizid, $userid, $numattempt) { |
626 | $attempt->quiz = $quizid; |
627 | $attempt->user = $userid; |
628 | $attempt->attempt = $numattempt; |
629 | $attempt->timestart = time(); |
630 | $attempt->timefinish = 0; |
631 | $attempt->timemodified = time(); |
632 | |
633 | return insert_record("quiz_attempts", $attempt); |
634 | } |
635 | |
636 | function quiz_get_user_attempt_unfinished($quizid, $userid) { |
637 | // Returns an object containing an unfinished attempt (if there is one) |
638 | return get_record_sql("SELECT * FROM quiz_attempts |
639 | WHERE quiz = '$quizid' and user = '$userid' AND timefinish = 0"); |
640 | } |
641 | |
3a506ca2 |
642 | function quiz_get_user_attempts($quizid, $userid) { |
a5e1f35c |
643 | // Returns a list of all attempts by a user |
958aafe2 |
644 | return get_records_sql("SELECT * FROM quiz_attempts |
645 | WHERE quiz = '$quizid' and user = '$userid' AND timefinish > 0 |
646 | ORDER by attempt ASC"); |
3a506ca2 |
647 | } |
648 | |
8d94f5a0 |
649 | |
650 | function quiz_get_user_attempts_string($quiz, $attempts, $bestgrade) { |
651 | /// Returns a simple little comma-separated list of all attempts, |
652 | /// with the best grade bolded |
653 | |
654 | $bestgrade = format_float($bestgrade); |
655 | foreach ($attempts as $attempt) { |
656 | $attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade); |
657 | if ($attemptgrade == $bestgrade) { |
658 | $userattempts[] = "<B>$attemptgrade</B>"; |
659 | } else { |
660 | $userattempts[] = "$attemptgrade"; |
661 | } |
662 | } |
663 | return implode(",", $userattempts); |
664 | } |
665 | |
a5e1f35c |
666 | function quiz_get_best_grade($quizid, $userid) { |
667 | /// Get the best current grade for a particular user in a quiz |
3a506ca2 |
668 | if (!$grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz = '$quizid' and user = '$userid'")) { |
669 | return 0; |
670 | } |
671 | |
672 | return $grade->grade; |
673 | } |
674 | |
579ddad5 |
675 | function quiz_get_grade_records($quiz) { |
676 | /// Gets all info required to display the table of quiz results |
677 | /// for report.php |
678 | |
679 | return get_records_sql("SELECT qg.*, u.firstname, u.lastname, u.picture |
680 | FROM quiz_grades qg, user u |
681 | WHERE qg.quiz = '$quiz->id' |
682 | AND qg.user = u.id"); |
683 | } |
684 | |
a5e1f35c |
685 | function quiz_save_best_grade($quiz, $user) { |
686 | /// Calculates the best grade out of all attempts at a quiz for a user, |
687 | /// and then saves that grade in the quiz_grades table. |
688 | |
689 | if (!$attempts = quiz_get_user_attempts($quiz->id, $user->id)) { |
690 | return false; |
691 | } |
692 | |
693 | $bestgrade = quiz_calculate_best_grade($quiz, $attempts); |
694 | $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade); |
695 | |
696 | if ($grade = get_record_sql("SELECT * FROM quiz_grades WHERE quiz='$quiz->id' AND user='$user->id'")) { |
697 | $grade->grade = $bestgrade; |
698 | $grade->timemodified = time(); |
699 | if (!update_record("quiz_grades", $grade)) { |
700 | return false; |
701 | } |
702 | } else { |
703 | $grade->quiz = $quiz->id; |
704 | $grade->user = $user->id; |
705 | $grade->grade = $bestgrade; |
706 | $grade->timemodified = time(); |
707 | if (!insert_record("quiz_grades", $grade)) { |
708 | return false; |
709 | } |
710 | } |
711 | return true; |
712 | } |
713 | |
714 | |
2a2c9725 |
715 | function quiz_get_answers($question) { |
a5e1f35c |
716 | // Given a question, returns the correct answers and grades |
717 | switch ($question->type) { |
718 | case SHORTANSWER; // Could be multiple answers |
2a2c9725 |
719 | return get_records_sql("SELECT a.*, sa.usecase, g.grade |
a5e1f35c |
720 | FROM quiz_shortanswer sa, quiz_answers a, quiz_question_grades g |
721 | WHERE sa.question = '$question->id' |
722 | AND sa.question = a.question |
723 | AND sa.question = g.question"); |
724 | break; |
725 | |
726 | case TRUEFALSE; // Should be always two answers |
727 | return get_records_sql("SELECT a.*, g.grade |
728 | FROM quiz_answers a, quiz_question_grades g |
729 | WHERE a.question = '$question->id' |
730 | AND a.question = g.question"); |
731 | break; |
732 | |
733 | case MULTICHOICE; // Should be multiple answers |
734 | return get_records_sql("SELECT a.*, mc.single, g.grade |
735 | FROM quiz_multichoice mc, quiz_answers a, quiz_question_grades g |
736 | WHERE mc.question = '$question->id' |
737 | AND mc.question = a.question |
738 | AND mc.question = g.question"); |
739 | break; |
740 | |
741 | default: |
742 | return false; |
743 | } |
744 | } |
745 | |
3a506ca2 |
746 | function quiz_calculate_best_grade($quiz, $attempts) { |
a5e1f35c |
747 | /// Calculate the best grade for a quiz given a number of attempts by a particular user. |
3a506ca2 |
748 | |
749 | switch ($quiz->grademethod) { |
a5e1f35c |
750 | |
751 | case ATTEMPTFIRST: |
3a506ca2 |
752 | foreach ($attempts as $attempt) { |
a5e1f35c |
753 | return $attempt->sumgrades; |
3a506ca2 |
754 | } |
a5e1f35c |
755 | break; |
756 | |
757 | case ATTEMPTLAST: |
758 | foreach ($attempts as $attempt) { |
759 | $final = $attempt->sumgrades; |
760 | } |
761 | return $final; |
3a506ca2 |
762 | |
a5e1f35c |
763 | case GRADEAVERAGE: |
3a506ca2 |
764 | $sum = 0; |
765 | $count = 0; |
766 | foreach ($attempts as $attempt) { |
a5e1f35c |
767 | $sum += $attempt->sumgrades; |
3a506ca2 |
768 | $count++; |
769 | } |
770 | return (float)$sum/$count; |
771 | |
3a506ca2 |
772 | default: |
a5e1f35c |
773 | case GRADEHIGHEST: |
774 | $max = 0; |
3a506ca2 |
775 | foreach ($attempts as $attempt) { |
a5e1f35c |
776 | if ($attempt->sumgrades > $max) { |
777 | $max = $attempt->sumgrades; |
778 | } |
3a506ca2 |
779 | } |
a5e1f35c |
780 | return $max; |
781 | } |
782 | } |
783 | |
784 | function quiz_save_attempt($quiz, $questions, $result, $attemptnum) { |
785 | /// Given a quiz, a list of attempted questions and a total grade |
786 | /// this function saves EVERYTHING so it can be reconstructed later |
787 | /// if necessary. |
788 | |
789 | global $USER; |
790 | |
958aafe2 |
791 | // First find the attempt in the database (start of attempt) |
792 | |
793 | if (!$attempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) { |
794 | notify("Trying to save an attempt that was not started!"); |
795 | return false; |
796 | } |
797 | |
798 | if ($attempt->attempt != $attemptnum) { // Double check. |
799 | notify("Number of this attempt is different to the unfinished one!"); |
800 | return false; |
801 | } |
802 | |
803 | // Now let's complete this record and save it |
a5e1f35c |
804 | |
a5e1f35c |
805 | $attempt->sumgrades = $result->sumgrades; |
958aafe2 |
806 | $attempt->timefinish = time(); |
a5e1f35c |
807 | $attempt->timemodified = time(); |
808 | |
958aafe2 |
809 | if (! update_record("quiz_attempts", $attempt)) { |
7520988b |
810 | notify("Error while saving attempt"); |
a5e1f35c |
811 | return false; |
812 | } |
813 | |
814 | // Now let's save all the questions for this attempt |
815 | |
816 | foreach ($questions as $question) { |
817 | $response->attempt = $attempt->id; |
818 | $response->question = $question->id; |
819 | $response->grade = $result->grades[$question->id]; |
820 | if ($question->answer) { |
821 | $response->answer = implode(",",$question->answer); |
822 | } else { |
823 | $response->answer = ""; |
824 | } |
825 | if (!insert_record("quiz_responses", $response)) { |
7520988b |
826 | notify("Error while saving response"); |
a5e1f35c |
827 | return false; |
828 | } |
3a506ca2 |
829 | } |
a5e1f35c |
830 | return true; |
3a506ca2 |
831 | } |
730fd187 |
832 | |
a5e1f35c |
833 | |
834 | function quiz_grade_attempt_results($quiz, $questions) { |
835 | /// Given a list of questions (including answers for each one) |
836 | /// this function does all the hard work of calculating the |
837 | /// grades for each question, as well as a total grade for |
838 | /// for the whole quiz. It returns everything in a structure |
839 | /// that looks like: |
840 | /// $result->sumgrades (sum of all grades for all questions) |
841 | /// $result->percentage (Percentage of grades that were correct) |
842 | /// $result->grade (final grade result for the whole quiz) |
843 | /// $result->grades[] (array of grades, indexed by question id) |
a8a372cc |
844 | /// $result->response[] (array of response arrays, indexed by question id) |
a5e1f35c |
845 | /// $result->feedback[] (array of feedback arrays, indexed by question id) |
846 | |
847 | if (!$questions) { |
848 | error("No questions!"); |
849 | } |
850 | |
851 | $result->sumgrades = 0; |
852 | |
853 | foreach ($questions as $question) { |
2a2c9725 |
854 | if (!$answers = quiz_get_answers($question)) { |
855 | error("No answers defined for question id $question->id!"); |
a5e1f35c |
856 | } |
857 | |
858 | $grade = 0; // default |
19c4f55c |
859 | $feedback = array(); |
860 | $response = array(); |
a5e1f35c |
861 | |
862 | switch ($question->type) { |
863 | case SHORTANSWER: |
864 | if ($question->answer) { |
865 | $question->answer = $question->answer[0]; |
866 | } else { |
19c4f55c |
867 | $question->answer = ""; |
a5e1f35c |
868 | } |
a8a372cc |
869 | $response[0] = $question->answer; |
a5e1f35c |
870 | foreach($answers as $answer) { // There might be multiple right answers |
2a2c9725 |
871 | if (!$answer->usecase) { // Don't compare case |
a5e1f35c |
872 | $answer->answer = strtolower($answer->answer); |
873 | $question->answer = strtolower($question->answer); |
874 | } |
875 | if ($question->answer == $answer->answer) { |
a8a372cc |
876 | $feedback[0] = $answer->feedback; |
a5e1f35c |
877 | $grade = (float)$answer->fraction * $answer->grade; |
a8a372cc |
878 | continue; |
a5e1f35c |
879 | } |
880 | } |
881 | break; |
882 | |
883 | |
884 | case TRUEFALSE: |
885 | if ($question->answer) { |
886 | $question->answer = $question->answer[0]; |
887 | } else { |
888 | $question->answer = NULL; |
889 | } |
890 | foreach($answers as $answer) { // There should be two answers (true and false) |
891 | $feedback[$answer->id] = $answer->feedback; |
892 | if ($question->answer == $answer->id) { |
893 | $grade = (float)$answer->fraction * $answer->grade; |
a8a372cc |
894 | $response[$answer->id] = true; |
a5e1f35c |
895 | } |
896 | } |
897 | break; |
898 | |
899 | |
900 | case MULTICHOICE: |
901 | foreach($answers as $answer) { // There will be multiple answers, perhaps more than one is right |
902 | $feedback[$answer->id] = $answer->feedback; |
903 | if ($question->answer) { |
904 | foreach ($question->answer as $questionanswer) { |
905 | if ($questionanswer == $answer->id) { |
906 | if ($answer->single) { |
907 | $grade = (float)$answer->fraction * $answer->grade; |
a8a372cc |
908 | $response[$answer->id] = true; |
a5e1f35c |
909 | continue; |
910 | } else { |
911 | $grade += (float)$answer->fraction * $answer->grade; |
a8a372cc |
912 | $response[$answer->id] = true; |
a5e1f35c |
913 | } |
914 | } |
915 | } |
916 | } |
917 | } |
918 | break; |
919 | |
920 | |
921 | } |
922 | if ($grade < 0.0) { // No negative grades |
923 | $grade = 0.0; |
924 | } |
10b9291c |
925 | |
a5e1f35c |
926 | $result->grades[$question->id] = $grade; |
927 | $result->sumgrades += $grade; |
928 | $result->feedback[$question->id] = $feedback; |
a8a372cc |
929 | $result->response[$question->id] = $response; |
a5e1f35c |
930 | } |
931 | |
8d94f5a0 |
932 | $fraction = (float)($result->sumgrades / $quiz->sumgrades); |
933 | $result->percentage = format_float($fraction * 100.0); |
934 | $result->grade = format_float($fraction * $quiz->grade); |
a5e1f35c |
935 | |
936 | return $result; |
937 | } |
938 | |
730fd187 |
939 | ?> |