MDL-26416 avoid subtracting two unsigned ints, which breaks MySQL since 5.5.5.
[moodle.git] / mod / quiz / review.php
CommitLineData
83192608 1<?php
b2607ccc
TH
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
ee1fb969 18/**
f63a4ff2 19 * This page prints a review of a particular quiz attempt
20 *
b2607ccc
TH
21 * It is used either by the student whose attempts this is, after the attempt,
22 * or by a teacher reviewing another's attempt during or afterwards.
23 *
24 * @package mod
25 * @subpackage quiz
26 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
f63a4ff2 28 */
29d5d0b4 29
b2607ccc
TH
30require_once(dirname(__FILE__) . '/../../config.php');
31require_once($CFG->dirroot . '/mod/quiz/locallib.php');
32require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
33
34$attemptid = required_param('attempt', PARAM_INT);
35$page = optional_param('page', 0, PARAM_INT);
36$showall = optional_param('showall', 0, PARAM_BOOL);
37
38$url = new moodle_url('/mod/quiz/review.php', array('attempt'=>$attemptid));
39if ($page !== 0) {
40 $url->param('page', $page);
41}
42if ($showall !== 0) {
43 $url->param('showall', $showall);
44}
45$PAGE->set_url($url);
46
47$attemptobj = quiz_attempt::create($attemptid);
48
49// Check login.
50require_login($attemptobj->get_course(), false, $attemptobj->get_cm());
51$attemptobj->check_review_capability();
52
53// Create an object to manage all the other (non-roles) access rules.
54$accessmanager = $attemptobj->get_access_manager(time());
55$options = $attemptobj->get_display_options(true);
56
57// Permissions checks for normal users who do not have quiz:viewreports capability.
58if (!$attemptobj->has_capability('mod/quiz:viewreports')) {
59 // Can't review other users' attempts.
60 if (!$attemptobj->is_own_attempt()) {
61 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt');
55f599f0 62 }
b2607ccc
TH
63 // Can't review during the attempt - send them back to the attempt page.
64 if (!$attemptobj->is_finished()) {
65 redirect($attemptobj->attempt_url(0, $page));
29d5d0b4 66 }
b2607ccc
TH
67 // Can't review unless Students may review -> Responses option is turned on.
68 if (!$options->attempt) {
69 $accessmanager->back_to_view_page($attemptobj->is_preview_user(),
70 $accessmanager->cannot_review_message($attemptobj->get_attempt_state()));
83192608 71 }
b2607ccc
TH
72}
73
74// Load the questions and states needed by this page.
75if ($showall) {
76 $questionids = $attemptobj->get_slots();
77} else {
78 $questionids = $attemptobj->get_slots($page);
79}
80
81// Save the flag states, if they are being changed.
82if ($options->flags == question_display_options::EDITABLE && optional_param('savingflags', false, PARAM_BOOL)) {
83 require_sesskey();
84 $attemptobj->save_question_flags();
85 redirect($attemptobj->review_url(0, $page, $showall));
86}
87
88// Log this review.
89add_to_log($attemptobj->get_courseid(), 'quiz', 'review', 'review.php?attempt=' .
90 $attemptobj->get_attemptid(), $attemptobj->get_quizid(), $attemptobj->get_cmid());
91
92// Work out appropriate title and whether blocks should be shown
93if ($attemptobj->is_preview_user() && $attemptobj->is_own_attempt()) {
94 $strreviewtitle = get_string('reviewofpreview', 'quiz');
95 navigation_node::override_active_url($attemptobj->start_attempt_url());
96
97} else {
98 $strreviewtitle = get_string('reviewofattempt', 'quiz', $attemptobj->get_attempt_number());
99 if (empty($attemptobj->get_quiz()->showblocks) && !$attemptobj->is_preview_user()) {
100 $PAGE->blocks->show_only_fake_blocks();
f62040ed 101 }
b2607ccc
TH
102}
103
104// Arrange for the navigation to be displayed.
105$navbc = $attemptobj->get_navigation_panel('quiz_review_nav_panel', $page, $showall);
106$firstregion = reset($PAGE->blocks->get_regions());
107$PAGE->blocks->add_fake_block($navbc, $firstregion);
108
109// Print the page header
110$headtags = $attemptobj->get_html_head_contributions($page, $showall);
111if ($accessmanager->securewindow_required($attemptobj->is_preview_user())) {
112 $accessmanager->setup_secure_page($attemptobj->get_course()->shortname.': '.format_string($attemptobj->get_quiz_name()), $headtags);
113} elseif ($accessmanager->safebrowser_required($attemptobj->is_preview_user())) {
114 $PAGE->set_title($attemptobj->get_course()->shortname . ': '.format_string($attemptobj->get_quiz_name()));
115 $PAGE->set_heading($attemptobj->get_course()->fullname);
116 $PAGE->set_cacheable(false);
117 echo $OUTPUT->header();
118} else {
119 $PAGE->navbar->add($strreviewtitle);
120 $PAGE->set_title(format_string($attemptobj->get_quiz_name()));
121 $PAGE->set_heading($attemptobj->get_course()->fullname);
122 echo $OUTPUT->header();
123}
124
125// Print heading.
126if ($attemptobj->is_preview_user() && $attemptobj->is_own_attempt()) {
127 $attemptobj->print_restart_preview_button();
128}
129echo $OUTPUT->heading($strreviewtitle);
130
131// Summary table start ============================================================================
132
133// Work out some time-related things.
134$attempt = $attemptobj->get_attempt();
135$quiz = $attemptobj->get_quiz();
136$overtime = 0;
137
138if ($attempt->timefinish) {
139 if ($timetaken = ($attempt->timefinish - $attempt->timestart)) {
140 if($quiz->timelimit && $timetaken > ($quiz->timelimit + 60)) {
141 $overtime = $timetaken - $quiz->timelimit;
142 $overtime = format_time($overtime);
56ed242b 143 }
b2607ccc 144 $timetaken = format_time($timetaken);
ee1fb969 145 } else {
b2607ccc 146 $timetaken = "-";
99a1bf3d 147 }
b2607ccc
TH
148} else {
149 $timetaken = get_string('unfinished', 'quiz');
150}
151
152// Print summary table about the whole attempt.
153// First we assemble all the rows that are appopriate to the current situation in
154// an array, then later we only output the table if there are any rows to show.
155$rows = array();
156if (!$attemptobj->get_quiz()->showuserpicture && $attemptobj->get_userid() != $USER->id) {
157 // If showuserpicture is true, the picture is shown elsewhere, so don't repeat it.
158 $student = $DB->get_record('user', array('id' => $attemptobj->get_userid()));
159 $picture = $OUTPUT->user_picture($student, array('courseid'=>$attemptobj->get_courseid()));
160 $rows[] = '<tr><th scope="row" class="cell">' . $picture . '</th><td class="cell"><a href="' .
161 $CFG->wwwroot . '/user/view.php?id=' . $student->id . '&amp;course=' . $attemptobj->get_courseid() . '">' .
162 fullname($student, true) . '</a></td></tr>';
163}
164if ($attemptobj->has_capability('mod/quiz:viewreports')) {
165 $attemptlist = $attemptobj->links_to_other_attempts($attemptobj->review_url(0, $page, $showall));
166 if ($attemptlist) {
167 $rows[] = '<tr><th scope="row" class="cell">' . get_string('attempts', 'quiz') .
168 '</th><td class="cell">' . $attemptlist . '</td></tr>';
29d5d0b4 169 }
b2607ccc
TH
170}
171
172// Timing information.
173$rows[] = '<tr><th scope="row" class="cell">' . get_string('startedon', 'quiz') .
174 '</th><td class="cell">' . userdate($attempt->timestart) . '</td></tr>';
175if ($attempt->timefinish) {
176 $rows[] = '<tr><th scope="row" class="cell">' . get_string('completedon', 'quiz') . '</th><td class="cell">' .
177 userdate($attempt->timefinish) . '</td></tr>';
178 $rows[] = '<tr><th scope="row" class="cell">' . get_string('timetaken', 'quiz') . '</th><td class="cell">' .
179 $timetaken . '</td></tr>';
180}
181if (!empty($overtime)) {
182 $rows[] = '<tr><th scope="row" class="cell">' . get_string('overdue', 'quiz') . '</th><td class="cell">' . $overtime . '</td></tr>';
183}
184
185// Show marks (if the user is allowed to see marks at the moment).
186$grade = quiz_rescale_grade($attempt->sumgrades, $quiz, false);
187if ($options->marks && quiz_has_grades($quiz)) {
188
189 if (!$attempt->timefinish) {
190 $rows[] = '<tr><th scope="row" class="cell">' . get_string('grade') . '</th><td class="cell">' .
191 get_string('attemptstillinprogress', 'quiz') . '</td></tr>';
192
193 } else if (is_null($grade)) {
194 $rows[] = '<tr><th scope="row" class="cell">' . get_string('grade') . '</th><td class="cell">' .
195 quiz_format_grade($quiz, $grade) . '</td></tr>';
9c22b42e 196
b2607ccc
TH
197 } else {
198 // Show raw marks only if they are different from the grade (like on the view page).
199 if ($quiz->grade != $quiz->sumgrades) {
3b48ba9d 200 $a = new stdClass;
b2607ccc
TH
201 $a->grade = quiz_format_grade($quiz, $attempt->sumgrades);
202 $a->maxgrade = quiz_format_grade($quiz, $quiz->sumgrades);
203 $rows[] = '<tr><th scope="row" class="cell">' . get_string('marks', 'quiz') . '</th><td class="cell">' .
204 get_string('outofshort', 'quiz', $a) . '</td></tr>';
d4961620 205 }
4fc3d7e5 206
b2607ccc
TH
207 // Now the scaled grade.
208 $a = new stdClass;
209 $a->grade = '<b>' . quiz_format_grade($quiz, $grade) . '</b>';
210 $a->maxgrade = quiz_format_grade($quiz, $quiz->grade);
211 if ($quiz->grade != 100) {
212 $a->percent = '<b>' . round($attempt->sumgrades * 100 / $quiz->sumgrades, 0) . '</b>';
213 $formattedgrade = get_string('outofpercent', 'quiz', $a);
214 } else {
215 $formattedgrade = get_string('outof', 'quiz', $a);
216 }
217 $rows[] = '<tr><th scope="row" class="cell">' . get_string('grade') . '</th><td class="cell">' .
218 $formattedgrade . '</td></tr>';
ee1fb969 219 }
b2607ccc
TH
220}
221
222// Feedback if there is any, and the user is allowed to see it now.
223$feedback = $attemptobj->get_overall_feedback($grade);
224if ($options->overallfeedback && $feedback) {
225 $rows[] = '<tr><th scope="row" class="cell">' . get_string('feedback', 'quiz') .
226 '</th><td class="cell">' . $feedback . '</td></tr>';
227}
228
229// Now output the summary table, if there are any rows to be shown.
230if (!empty($rows)) {
231 echo '<table class="generaltable generalbox quizreviewsummary"><tbody>', "\n";
232 echo implode("\n", $rows);
233 echo "\n</tbody></table>\n";
234}
235
236// Summary table end ==============================================================================
237
238// Form for saving flags if necessary.
239if ($options->flags == question_display_options::EDITABLE) {
240 echo '<form action="' . $attemptobj->review_url(0, $page, $showall) .
241 '" method="post"><div>';
242 echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />';
243}
244
245// Print all the questions.
246if ($showall) {
247 $thispage = 'all';
248 $lastpage = true;
249} else {
250 $thispage = $page;
251 $lastpage = $attemptobj->is_last_page($page);
252}
253foreach ($attemptobj->get_slots($thispage) as $slot) {
254 echo $attemptobj->render_question($slot, true, $attemptobj->review_url($slot, $page, $showall));
255}
256
257// Close form if we opened it.
258if ($options->flags == question_display_options::EDITABLE) {
259 echo '<div class="submitbtns">' . "\n" .
260 '<input type="submit" class="questionflagsavebutton" name="savingflags" value="' .
261 get_string('saveflags', 'question') . '" />' .
262 "</div>\n" .
263 "\n</div></form>\n";
264 $PAGE->requires->js_init_call('M.mod_quiz.init_review_form', null, false, quiz_get_js_module());
265}
266
267// Print a link to the next page.
268echo '<div class="submitbtns">';
269if ($lastpage) {
270 $accessmanager->print_finish_review_link($attemptobj->is_preview_user());
271} else {
272 echo link_arrow_right(get_string('next'), $attemptobj->review_url(0, $page + 1));
273}
274echo '</div>';
275echo $OUTPUT->footer();