36e1d94ca3fd0b8bda8d491963ce1fccf4d882c5
[moodle.git] / mod / quiz / view.php
1 <?php
3 /// This page prints a particular instance of quiz
5     require_once(dirname(__FILE__) . '/../../config.php');
6     require_once($CFG->libdir.'/gradelib.php');
7     require_once($CFG->dirroot.'/mod/quiz/locallib.php');
8     require_once($CFG->libdir . '/completionlib.php');
10     $id = optional_param('id', 0, PARAM_INT); // Course Module ID, or
11     $q = optional_param('q',  0, PARAM_INT);  // quiz ID
13     if ($id) {
14         if (! $cm = get_coursemodule_from_id('quiz', $id)) {
15             print_error('invalidcoursemodule');
16         }
17         if (! $course = $DB->get_record('course', array('id' => $cm->course))) {
18             print_error('coursemisconf');
19         }
20         if (! $quiz = $DB->get_record('quiz', array('id' => $cm->instance))) {
21             print_error('invalidcoursemodule');
22         }
23     } else {
24         if (! $quiz = $DB->get_record('quiz', array('id' => $q))) {
25             print_error('invalidquizid', 'quiz');
26         }
27         if (! $course = $DB->get_record('course', array('id' => $quiz->course))) {
28             print_error('invalidcourseid');
29         }
30         if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $course->id)) {
31             print_error('invalidcoursemodule');
32         }
33     }
35 /// Check login and get context.
36     require_login($course->id, false, $cm);
37     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
38     require_capability('mod/quiz:view', $context);
40 /// Cache some other capabilities we use several times.
41     $canattempt = has_capability('mod/quiz:attempt', $context);
42     $canreviewmine = has_capability('mod/quiz:reviewmyattempts', $context);
43     $canpreview = has_capability('mod/quiz:preview', $context);
45 /// Create an object to manage all the other (non-roles) access rules.
46     $timenow = time();
47     $accessmanager = new quiz_access_manager(quiz::create($quiz->id, $USER->id), $timenow,
48             has_capability('mod/quiz:ignoretimelimits', $context, NULL, false));
50 /// If no questions have been set up yet redirect to edit.php
51     if (!$quiz->questions && has_capability('mod/quiz:manage', $context)) {
52         redirect($CFG->wwwroot . '/mod/quiz/edit.php?cmid=' . $cm->id);
53     }
55 /// Log this request.
56     add_to_log($course->id, "quiz", "view", "view.php?id=$cm->id", $quiz->id, $cm->id);
58 /// Initialize $PAGE, compute blocks
59     $PAGE->set_url('/mod/quiz/view.php', array('id' => $cm->id));
61     $edit = optional_param('edit', -1, PARAM_BOOL);
62     if ($edit != -1 && $PAGE->user_allowed_editing()) {
63         $USER->editing = $edit;
64     }
66     $PAGE->requires->yui2_lib('event');
68     // Note: MDL-19010 there will be further changes to printing header and blocks.
69     // The code will be much nicer than this eventually.
70     $title = $course->shortname . ': ' . format_string($quiz->name);
72     if ($PAGE->user_allowed_editing()) {
73         $buttons = '<table><tr><td><form method="get" action="view.php"><div>'.
74             '<input type="hidden" name="id" value="'.$cm->id.'" />'.
75             '<input type="hidden" name="edit" value="'.($PAGE->user_is_editing()?'off':'on').'" />'.
76             '<input type="submit" value="'.get_string($PAGE->user_is_editing()?'blockseditoff':'blocksediton').'" /></div></form></td></tr></table>';
77         $PAGE->set_button($buttons);
78     }
80     $PAGE->set_title($title);
81     $PAGE->set_heading($course->fullname);
83     echo $OUTPUT->header();
85 /// Print quiz name and description
86     echo $OUTPUT->heading(format_string($quiz->name));
87     if (trim(strip_tags($quiz->intro))) {
88         echo $OUTPUT->box(format_module_intro('quiz', $quiz, $cm->id), 'generalbox', 'intro');
89     }
91 /// Display information about this quiz.
92     $messages = $accessmanager->describe_rules();
93     if ($quiz->attempts != 1) {
94         $messages[] = get_string('gradingmethod', 'quiz', quiz_get_grading_option_name($quiz->grademethod));
95     }
96     echo $OUTPUT->box_start('quizinfo');
97     $accessmanager->print_messages($messages);
98     echo $OUTPUT->box_end();
100 /// Show number of attempts summary to those who can view reports.
101     if (has_capability('mod/quiz:viewreports', $context)) {
102         if ($strattemptnum = quiz_attempt_summary_link_to_reports($quiz, $cm, $context)) {
103             echo '<div class="quizattemptcounts">' . $strattemptnum . "</div>\n";
104         }
105     }
107 /// Guests can't do a quiz, so offer them a choice of logging in or going back.
108     if (isguestuser()) {
109         echo $OUTPUT->confirm('<p>' . get_string('guestsno', 'quiz') . "</p>\n\n<p>" .
110                 get_string('liketologin') . "</p>\n", get_login_url(), get_referer(false));
111         echo $OUTPUT->footer();
112         exit;
113     }
115 /// If they are not enrolled in this course in a good enough role, tell them to enrol.
116     if (!($canattempt || $canpreview || $canreviewmine)) {
117         echo $OUTPUT->box('<p>' . get_string('youneedtoenrol', 'quiz') . "</p>\n\n<p>" .
118                 $OUTPUT->continue_button($CFG->wwwroot . '/course/view.php?id=' . $course->id) .
119                 "</p>\n", 'generalbox', 'notice');
120         echo $OUTPUT->footer();
121         exit;
122     }
124 /// Update the quiz with overrides for the current user
125     $quiz = quiz_update_effective_access($quiz, $USER->id);
127 /// Get this user's attempts.
128     $attempts = quiz_get_user_attempts($quiz->id, $USER->id);
129     $lastfinishedattempt = end($attempts);
130     $unfinished = false;
131     if ($unfinishedattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
132         $attempts[] = $unfinishedattempt;
133         $unfinished = true;
134     }
135     $numattempts = count($attempts);
137 /// Work out the final grade, checking whether it was overridden in the gradebook.
138     $mygrade = quiz_get_best_grade($quiz, $USER->id);
139     $mygradeoverridden = false;
140     $gradebookfeedback = '';
142     $grading_info = grade_get_grades($course->id, 'mod', 'quiz', $quiz->id, $USER->id);
143     if (!empty($grading_info->items)) {
144         $item = $grading_info->items[0];
145         if (isset($item->grades[$USER->id])) {
146             $grade = $item->grades[$USER->id];
148             if ($grade->overridden) {
149                 $mygrade = $grade->grade + 0; // Convert to number.
150                 $mygradeoverridden = true;
151             }
152             if (!empty($grade->str_feedback)) {
153                 $gradebookfeedback = $grade->str_feedback;
154             }
155         }
156     }
158 /// Print table with existing attempts
159     if ($attempts) {
161         echo $OUTPUT->heading(get_string('summaryofattempts', 'quiz'));
163         // Work out which columns we need, taking account what data is available in each attempt.
164         list($someoptions, $alloptions) = quiz_get_combined_reviewoptions($quiz, $attempts, $context);
166         $attemptcolumn = $quiz->attempts != 1;
168         $gradecolumn = $someoptions->scores && quiz_has_grades($quiz);
169         $markcolumn = $gradecolumn && ($quiz->grade != $quiz->sumgrades);
170         $overallstats = $alloptions->scores;
172         $feedbackcolumn = quiz_has_feedback($quiz) && $alloptions->overallfeedback;
174         // Prepare table header
175         $table = new html_table();
176         $table->attributes['class'] = 'generaltable quizattemptsummary';
177         $table->head = array();
178         $table->align = array();
179         $table->size = array();
180         if ($attemptcolumn) {
181             $table->head[] = get_string('attemptnumber', 'quiz');
182             $table->align[] = 'center';
183             $table->size[] = '';
184         }
185         $table->head[] = get_string('timecompleted', 'quiz');
186         $table->align[] = 'left';
187         $table->size[] = '';
188         if ($markcolumn) {
189             $table->head[] = get_string('marks', 'quiz') . ' / ' . quiz_format_grade($quiz, $quiz->sumgrades);
190             $table->align[] = 'center';
191             $table->size[] = '';
192         }
193         if ($gradecolumn) {
194             $table->head[] = get_string('grade') . ' / ' . quiz_format_grade($quiz, $quiz->grade);
195             $table->align[] = 'center';
196             $table->size[] = '';
197         }
198         if ($canreviewmine) {
199             $table->head[] = get_string('review', 'quiz');
200             $table->align[] = 'center';
201             $table->size[] = '';
202         }
203         if ($feedbackcolumn) {
204             $table->head[] = get_string('feedback', 'quiz');
205             $table->align[] = 'left';
206             $table->size[] = '';
207         }
208         if (isset($quiz->showtimetaken)) {
209             $table->head[] = get_string('timetaken', 'quiz');
210             $table->align[] = 'left';
211             $table->size[] = '';
212         }
214         // One row for each attempt
215         foreach ($attempts as $attempt) {
216             $attemptoptions = quiz_get_reviewoptions($quiz, $attempt, $context);
217             $row = array();
219             // Add the attempt number, making it a link, if appropriate.
220             if ($attemptcolumn) {
221                 if ($attempt->preview) {
222                     $row[] = get_string('preview', 'quiz');
223                 } else {
224                     $row[] = $attempt->attempt;
225                 }
226             }
228             // prepare strings for time taken and date completed
229             $timetaken = '';
230             $datecompleted = '';
231             if ($attempt->timefinish > 0) {
232                 // attempt has finished
233                 $timetaken = format_time($attempt->timefinish - $attempt->timestart);
234                 $datecompleted = userdate($attempt->timefinish);
235             } else if (!$quiz->timeclose || $timenow < $quiz->timeclose) {
236                 // The attempt is still in progress.
237                 $timetaken = format_time($timenow - $attempt->timestart);
238                 $datecompleted = '';
239             } else {
240                 $timetaken = format_time($quiz->timeclose - $attempt->timestart);
241                 $datecompleted = userdate($quiz->timeclose);
242             }
243             $row[] = $datecompleted;
245             if ($markcolumn) {
246                 if ($attemptoptions->scores && $attempt->timefinish > 0) {
247                     $row[] = quiz_format_grade($quiz, $attempt->sumgrades);
248                 } else {
249                     $row[] = '';
250                 }
251             }
253             // Ouside the if because we may be showing feedback but not grades.
254             $attemptgrade = quiz_rescale_grade($attempt->sumgrades, $quiz, false);
256             if ($gradecolumn) {
257                 if ($attemptoptions->scores && $attempt->timefinish > 0) {
258                     $formattedgrade = quiz_format_grade($quiz, $attemptgrade);
259                     // highlight the highest grade if appropriate
260                     if ($overallstats && !$attempt->preview && $numattempts > 1 && !is_null($mygrade) &&
261                             $attemptgrade == $mygrade && $quiz->grademethod == QUIZ_GRADEHIGHEST) {
262                         $table->rowclasses[$attempt->attempt] = 'bestrow';
263                     }
265                     $row[] = $formattedgrade;
266                 } else {
267                     $row[] = '';
268                 }
269             }
271             if ($canreviewmine) {
272                 $row[] = $accessmanager->make_review_link($attempt, $canpreview, $attemptoptions);
273             }
275             if ($feedbackcolumn && $attempt->timefinish > 0) {
276                 if ($attemptoptions->overallfeedback) {
277                     $row[] = quiz_feedback_for_grade($attemptgrade, $quiz, $context, $cm);
278                 } else {
279                     $row[] = '';
280                 }
281             }
283             if (isset($quiz->showtimetaken)) {
284                 $row[] = $timetaken;
285             }
287             if ($attempt->preview) {
288                 $table->data['preview'] = $row;
289             } else {
290                 $table->data[$attempt->attempt] = $row;
291             }
292         } // End of loop over attempts.
293         echo html_writer::table($table);
294     }
296 /// Print information about the student's best score for this quiz if possible.
297     $moreattempts = $unfinished || !$accessmanager->is_finished($numattempts, $lastfinishedattempt);
298     if (!$moreattempts) {
299         echo $OUTPUT->heading(get_string("nomoreattempts", "quiz"));
300     }
302     if ($numattempts && $gradecolumn && !is_null($mygrade)) {
303         $resultinfo = '';
305         if ($overallstats) {
306             if ($moreattempts) {
307                 $a = new stdClass;
308                 $a->method = quiz_get_grading_option_name($quiz->grademethod);
309                 $a->mygrade = quiz_format_grade($quiz, $mygrade);
310                 $a->quizgrade = quiz_format_grade($quiz, $quiz->grade);
311                 $resultinfo .= $OUTPUT->heading(get_string('gradesofar', 'quiz', $a), 2, 'main');
312             } else {
313                 $a = new stdClass;
314                 $a->grade = quiz_format_grade($quiz, $mygrade);
315                 $a->maxgrade = quiz_format_grade($quiz, $quiz->grade);
316                 $a = get_string('outofshort', 'quiz', $a);
317                 $resultinfo .= $OUTPUT->heading(get_string('yourfinalgradeis', 'quiz', $a), 2, 'main');
318             }
319         }
321         if ($mygradeoverridden) {
322             $resultinfo .= '<p class="overriddennotice">'.get_string('overriddennotice', 'grades')."</p>\n";
323         }
324         if ($gradebookfeedback) {
325             $resultinfo .= $OUTPUT->heading(get_string('comment', 'quiz'), 3, 'main');
326             $resultinfo .= '<p class="quizteacherfeedback">'.$gradebookfeedback."</p>\n";
327         }
328         if ($feedbackcolumn) {
329             $resultinfo .= $OUTPUT->heading(get_string('overallfeedback', 'quiz'), 3, 'main');
330             $resultinfo .= '<p class="quizgradefeedback">'.quiz_feedback_for_grade($mygrade, $quiz, $context, $cm)."</p>\n";
331         }
333         if ($resultinfo) {
334             echo $OUTPUT->box($resultinfo, 'generalbox', 'feedback');
335         }
336     }
338 /// Determine if we should be showing a start/continue attempt button,
339 /// or a button to go back to the course page.
340     echo $OUTPUT->box_start('quizattempt');
341     $buttontext = ''; // This will be set something if as start/continue attempt button should appear.
342     if (!$quiz->questions) {
343         echo $OUTPUT->heading(get_string("noquestions", "quiz"));
344     } else {
345         if ($unfinished) {
346             if ($canpreview) {
347                 $buttontext = get_string('continuepreview', 'quiz');
348             } else if ($canattempt) {
349                 $buttontext = get_string('continueattemptquiz', 'quiz');
350             }
351         } else {
352             if ($canpreview) {
353                 $buttontext = get_string('previewquiznow', 'quiz');
354             } else if ($canattempt) {
355                 $messages = $accessmanager->prevent_new_attempt($numattempts, $lastfinishedattempt);
356                 if ($messages) {
357                     $accessmanager->print_messages($messages);
358                 } else if ($numattempts == 0) {
359                     $buttontext = get_string('attemptquiznow', 'quiz');
360                 } else {
361                     $buttontext = get_string('reattemptquiz', 'quiz');
362                 }
363             }
364         }
366         // If, so far, we think a button should be printed, so check if they will be allowed to access it.
367         if ($buttontext) {
368             if (!$moreattempts) {
369                 $buttontext = '';
370             } else if ($canattempt && $messages = $accessmanager->prevent_access()) {
371                 $accessmanager->print_messages($messages);
372                 $buttontext = '';
373             }
374         }
375     }
377 /// Now actually print the appropriate button.
378     if ($buttontext) {
379         $accessmanager->print_start_attempt_button($canpreview, $buttontext, $unfinished);
380     } else {
381         echo $OUTPUT->continue_button($CFG->wwwroot . '/course/view.php?id=' . $course->id);
382     }
383     echo $OUTPUT->box_end();
385     // Mark module as viewed (note, we do this here and not in finish_page,
386     // otherwise the 'not enrolled' error conditions would result in marking
387     // 'viewed', I think it's better if they don't.)
388     $completion=new completion_info($course);
389     $completion->set_module_viewed($cm);
391     echo $OUTPUT->footer();