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