MDL-15539 - Rework review.php to use attemptlib.php - clean up how review options...
[moodle.git] / mod / quiz / review.php
CommitLineData
d1290cec 1<?php // $Id$
ee1fb969 2/**
f63a4ff2 3 * This page prints a review of a particular quiz attempt
4 *
5 * @author Martin Dougiamas and many others. This has recently been completely
6 * rewritten by Alex Smith, Julian Sedding and Gustav Delius as part of
7 * the Serving Mathematics project
8 * {@link http://maths.york.ac.uk/serving_maths}
9 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
10 * @package quiz
11 */
29d5d0b4 12
c52c62d1 13 require_once('../../config.php');
14 require_once($CFG->dirroot . '/mod/quiz/locallib.php');
29d5d0b4 15
c52c62d1 16 $attemptid = required_param('attempt', PARAM_INT); // A particular attempt ID for review
ee1fb969 17 $page = optional_param('page', 0, PARAM_INT); // The required page
18 $showall = optional_param('showall', 0, PARAM_BOOL);
29d5d0b4 19
c52c62d1 20 if (!$attempt = quiz_load_attempt($attemptid)) {
550f9654 21 print_error('invalidattemptid', 'quiz');
29d5d0b4 22 }
9cf4a18b 23 if (! $quiz = $DB->get_record('quiz', array('id' => $attempt->quiz))) {
550f9654 24 print_error('invalidquizid', 'quiz');
ee1fb969 25 }
9cf4a18b 26 if (! $course = $DB->get_record('course', array('id' => $quiz->course))) {
550f9654 27 print_error("invalidcoursemodule");
ee1fb969 28 }
29 if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $course->id)) {
550f9654 30 print_error("invalidcoursemodule");
ee1fb969 31 }
77ed3ba5 32
c52c62d1 33/// Check login and get contexts.
ec81373f 34 require_login($course->id, false, $cm);
a18fa6d5 35 $coursecontext = get_context_instance(CONTEXT_COURSE, $cm->course);
c52c62d1 36 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
37 $canpreview = has_capability('mod/quiz:preview', get_context_instance(CONTEXT_MODULE, $cm->id));
29d5d0b4 38
c52c62d1 39/// Create an object to manage all the other (non-roles) access rules.
a4faed69 40 $timenow = time();
78e7a3dd 41 $accessmanager = new quiz_access_manager(new quiz($quiz, $cm, $course), $timenow,
c52c62d1 42 has_capability('mod/quiz:ignoretimelimits', $context, NULL, false));
43 $options = quiz_get_reviewoptions($quiz, $attempt, $context);
44
45/// Work out if this is a student viewing their own attempt/teacher previewing,
46/// or someone with 'mod/quiz:viewreports' reviewing someone elses attempt.
47 $reviewofownattempt = $attempt->userid == $USER->id && (!$canpreview || $attempt->preview);
48
49/// Permissions checks for normal users who do not have quiz:viewreports capability.
121609d8 50 if (!has_capability('mod/quiz:viewreports', $context)) {
c52c62d1 51 /// Can't review during the attempt - send them back to the attempt page.
ee1fb969 52 if (!$attempt->timefinish) {
c52c62d1 53 redirect($CFG->wwwroot . '/mod/quiz/attempt.php?q=' . $quiz->id);
29d5d0b4 54 }
c52c62d1 55 /// Can't review other users' attempts.
56 if (!$reviewofownattempt) {
57 quiz_error($quiz, 'reviewnotallowed');
58 }
59 /// Can't review unless Students may review -> Responses option is turned on.
a4faed69 60 if (!$options->responses) {
c52c62d1 61 $accessmanager->back_to_view_page($canpreview,
62 $accessmanager->cannot_review_message($options));
34283aa8 63 }
29d5d0b4 64 }
65
c52c62d1 66/// Log this review.
839f2456 67 add_to_log($course->id, "quiz", "review", "review.php?id=$cm->id&amp;attempt=$attempt->id", "$quiz->id", "$cm->id");
29d5d0b4 68
c52c62d1 69/// load the questions needed by page
70 if ($showall) {
71 $questionlist = quiz_questions_in_quiz($attempt->layout);
72 } else {
73 $questionlist = quiz_questions_on_page($attempt->layout, $page);
2d05b472 74 }
c52c62d1 75 $pagequestions = explode(',', $questionlist);
76 $questions = question_load_questions($questionlist, 'qqi.grade AS maxgrade, qqi.id AS instance',
739b748b 77 '{quiz_question_instances} qqi ON qqi.quiz = ' . $quiz->id . ' AND q.id = qqi.question');
c52c62d1 78 if (is_string($questions)) {
79 quiz_error($quiz, 'loadingquestionsfailed', $questions);
2d05b472 80 }
81
c52c62d1 82/// Restore the question sessions to their most recent states creating new sessions where required.
2d05b472 83 if (!$states = get_question_states($questions, $quiz, $attempt)) {
550f9654 84 print_error('cannotrestore', 'quiz');
2d05b472 85 }
86
38e179a4 87/// Work out appropriate title.
c52c62d1 88 if ($canpreview && $reviewofownattempt) {
38e179a4 89 $strreviewtitle = get_string('reviewofpreview', 'quiz');
90 } else {
91 $strreviewtitle = get_string('reviewofattempt', 'quiz', $attempt->attempt);
92 }
93
c52c62d1 94/// Print the page header
2d05b472 95 $headtags = get_html_head_contributions($pagequestions, $questions, $states);
c52c62d1 96 if ($accessmanager->securewindow_required($canpreview)) {
97 $accessmanager->setup_secure_page($course->shortname.': '.format_string($quiz->name), $headtags);
ee1fb969 98 } else {
5cf38a57 99 $strupdatemodule = has_capability('moodle/course:manageactivities', $coursecontext)
ee1fb969 100 ? update_module_button($cm->id, $course->id, get_string('modulename', 'quiz'))
101 : "";
38e179a4 102 get_string('reviewofattempt', 'quiz', $attempt->attempt);
103 $navigation = build_navigation($strreviewtitle, $cm);
2d05b472 104 print_header_simple(format_string($quiz->name), "", $navigation, "", $headtags, true, $strupdatemodule);
ee1fb969 105 }
b7f35820 106 echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // for overlib
107
c52c62d1 108/// Print tabs if they should be there.
109 if ($canpreview) {
110 if ($reviewofownattempt) {
0a5b58af 111 $currenttab = 'preview';
112 } else {
113 $currenttab = 'reports';
114 $mode = '';
115 }
ee1fb969 116 include('tabs.php');
ee1fb969 117 }
c52c62d1 118
119/// Print heading.
38e179a4 120 print_heading(format_string($quiz->name));
c52c62d1 121 if ($canpreview && $reviewofownattempt) {
78e7a3dd 122 $attemptobj = new quiz_attempt($attemptid);
123 $attemptobj->print_restart_preview_button();
99a1bf3d 124 }
38e179a4 125 print_heading($strreviewtitle);
99a1bf3d 126
c52c62d1 127/// Finish review link.
128 if ($reviewofownattempt) {
129 $accessmanager->print_finish_review_link($canpreview);
99a1bf3d 130 }
ad1a74e0 131
9c22b42e 132/// Work out some time-related things.
3a00dbfd 133 $timelimit = (int)$quiz->timelimit * 60;
235987c5 134 $overtime = 0;
d4961620 135
ee1fb969 136 if ($attempt->timefinish) {
137 if ($timetaken = ($attempt->timefinish - $attempt->timestart)) {
138 if($timelimit && $timetaken > ($timelimit + 60)) {
139 $overtime = $timetaken - $timelimit;
140 $overtime = format_time($overtime);
141 }
142 $timetaken = format_time($timetaken);
143 } else {
144 $timetaken = "-";
d4961620 145 }
29d5d0b4 146 } else {
ee1fb969 147 $timetaken = get_string('unfinished', 'quiz');
29d5d0b4 148 }
9c22b42e 149
150/// Print summary table about the whole attempt.
151/// First we assemble all the rows that are appopriate to the current situation in
152/// an array, then later we only output the table if there are any rows to show.
153 $rows = array();
76cacec8 154 if ($attempt->userid <> $USER->id) {
9cf4a18b 155 $student = $DB->get_record('user', array('id' => $attempt->userid));
65bcf17b 156 $picture = print_user_picture($student, $course->id, $student->picture, false, true);
9c22b42e 157 $rows[] = '<tr><th scope="row" class="cell">' . $picture . '</th><td class="cell"><a href="' .
158 $CFG->wwwroot . '/user/view.php?id=' . $student->id . '&amp;course=' . $course->id . '">' .
159 fullname($student, true) . '</a></td></tr>';
76cacec8 160 }
9c22b42e 161 if (has_capability('mod/quiz:viewreports', $context) &&
9cf4a18b 162 count($attempts = $DB->get_records_select('quiz_attempts', "quiz = ? AND userid = ?", array($quiz->id, $attempt->userid), 'attempt ASC')) > 1) {
9c22b42e 163 /// List of all this user's attempts for people who can see reports.
164 $urloptions = '';
165 if ($showall) {
166 $urloptions .= '&amp;showall=true';
167 } else if ($page > 0) {
168 $urloptions .= '&amp;page=' . $page;
169 }
170 $attemptlist = array();
ee1fb969 171 foreach ($attempts as $at) {
9c22b42e 172 if ($at->id == $attempt->id) {
173 $attemptlist[] = '<strong>' . $at->attempt . '</strong>';
174 } else {
175 $attemptlist[] = '<a href="review.php?attempt=' . $at->id . $urloptions . ' ">' . $at->attempt . '</a>';
176 }
ee1fb969 177 }
9c22b42e 178 $rows[] = '<tr><th scope="row" class="cell">' . get_string('attempts', 'quiz') .
179 '</th><td class="cell">' . implode(', ', $attemptlist) . '</td></tr>';
ee1fb969 180 }
181
9c22b42e 182/// Timing information.
183 $rows[] = '<tr><th scope="row" class="cell">' . get_string('startedon', 'quiz') .
184 '</th><td class="cell">' . userdate($attempt->timestart) . '</td></tr>';
ee1fb969 185 if ($attempt->timefinish) {
9c22b42e 186 $rows[] = '<tr><th scope="row" class="cell">' . get_string('completedon', 'quiz') . '</th><td class="cell">' .
187 userdate($attempt->timefinish) . '</td></tr>';
188 $rows[] = '<tr><th scope="row" class="cell">' . get_string('timetaken', 'quiz') . '</th><td class="cell">' .
189 $timetaken . '</td></tr>';
ee1fb969 190 }
3a00dbfd 191 if (!empty($overtime)) {
9c22b42e 192 $rows[] = '<tr><th scope="row" class="cell">' . get_string('overdue', 'quiz') . '</th><td class="cell">' . $overtime . '</td></tr>';
d4961620 193 }
9c22b42e 194
195/// Show scores (if the user is allowed to see scores at the moment).
196 $grade = quiz_rescale_grade($attempt->sumgrades, $quiz);
15db443c 197 if ($options->scores) {
198 if ($quiz->grade and $quiz->sumgrades) {
199 if($overtime) {
200 $result->sumgrades = "0";
201 $result->grade = "0.0";
202 }
77ed3ba5 203
9c22b42e 204 /// Show raw marks only if they are different from the grade (like on the view page.
205 if ($quiz->grade != $quiz->sumgrades) {
206 $a = new stdClass;
207 $a->grade = round($attempt->sumgrades, $CFG->quiz_decimalpoints);
208 $a->maxgrade = $quiz->sumgrades;
209 $rows[] = '<tr><th scope="row" class="cell">' . get_string('marks', 'quiz') . '</th><td class="cell">' .
210 get_string('outofshort', 'quiz', $a) . '</td></tr>';
211 }
212
213 /// Now the scaled grade.
3b48ba9d 214 $a = new stdClass;
9c22b42e 215 $a->grade = '<b>' . $grade . '</b>';
3b48ba9d 216 $a->maxgrade = $quiz->grade;
9c22b42e 217 $a->percent = '<b>' . round(($attempt->sumgrades/$quiz->sumgrades)*100, 0) . '</b>';
218 $rows[] = '<tr><th scope="row" class="cell">' . get_string('grade') . '</th><td class="cell">' .
219 get_string('outofpercent', 'quiz', $a) . '</td></tr>';
d4961620 220 }
ee1fb969 221 }
9c22b42e 222
223/// Feedback if there is any, and the user is allowed to see it now.
224 $feedback = quiz_feedback_for_grade($grade, $attempt->quiz);
77ed3ba5 225 if ($options->overallfeedback && $feedback) {
9c22b42e 226 $rows[] = '<tr><th scope="row" class="cell">' . get_string('feedback', 'quiz') .
227 '</th><td class="cell">' . $feedback . '</td></tr>';
228 }
229
230/// Now output the summary table, if there are any rows to be shown.
231 if (!empty($rows)) {
232 echo '<table class="generaltable generalbox quizreviewsummary"><tbody>', "\n";
233 echo implode("\n", $rows);
234 echo "\n</tbody></table>\n";
9bc2d82a 235 }
236
ee1fb969 237/// Print the navigation panel if required
238 $numpages = quiz_number_of_pages($attempt->layout);
239 if ($numpages > 1 and !$showall) {
240 print_paging_bar($numpages, $page, 1, 'review.php?attempt='.$attempt->id.'&amp;');
09275894 241 echo '<div class="controls"><a href="review.php?attempt='.$attempt->id.'&amp;showall=true">';
ee1fb969 242 print_string('showall', 'quiz');
09275894 243 echo '</a></div>';
29d5d0b4 244 }
245
ee1fb969 246/// Print all the questions
c52c62d1 247 $number = quiz_first_questionnumber($attempt->layout, $questionlist);
ee1fb969 248 foreach ($pagequestions as $i) {
7bbe08a2 249 if (!isset($questions[$i])) {
250 print_simple_box_start('center', '90%');
09275894 251 echo '<strong><font size="+1">' . $number . '</font></strong><br />';
7bbe08a2 252 notify(get_string('errormissingquestion', 'quiz', $i));
253 print_simple_box_end();
254 $number++; // Just guessing that the missing question would have lenght 1
255 continue;
256 }
4f48fb42 257 $options->validation = QUESTION_EVENTVALIDATE === $states[$i]->event;
c52c62d1 258 $options->history = ($canpreview and !$attempt->preview) ? 'all' : 'graded';
ee1fb969 259 // Print the question
4f48fb42 260 print_question($questions[$i], $states[$i], $number, $quiz, $options);
ee1fb969 261 $number += $questions[$i]->length;
262 }
29d5d0b4 263
ee1fb969 264 // Print the navigation panel if required
265 if ($numpages > 1 and !$showall) {
266 print_paging_bar($numpages, $page, 1, 'review.php?attempt='.$attempt->id.'&amp;');
29d5d0b4 267 }
268
ee1fb969 269 // print javascript button to close the window, if necessary
c52c62d1 270 if ($reviewofownattempt) {
271 $accessmanager->print_finish_review_link($canpreview);
ee1fb969 272 }
29d5d0b4 273
c52c62d1 274 if ($accessmanager->securewindow_required($canpreview)) {
275 print_footer('empty');
276 } else {
ee1fb969 277 print_footer($course);
278 }
29d5d0b4 279?>