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