MDL-20636 Add missing defined('MOODLE_INTERNAL') || die();
[moodle.git] / mod / quiz / attemptlib.php
CommitLineData
36e413e3 1<?php
d18675a8 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
36e413e3 18/**
d18675a8 19 * Back-end code for handling data about quizzes and the current user's attempt.
20 *
21 * There are classes for loading all the information about a quiz and attempts,
22 * and for displaying the navigation panel.
23 *
ba643847 24 * @package mod
a1eb3a44 25 * @subpackage quiz
ba643847
TH
26 * @copyright 2008 onwards Tim Hunt
27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
d18675a8 28 */
36e413e3 29
36e413e3 30
a17b297d 31defined('MOODLE_INTERNAL') || die();
a1eb3a44
TH
32
33
36e413e3 34/**
b10c38a3 35 * Class for quiz exceptions. Just saves a couple of arguments on the
36 * constructor for a moodle_exception.
d18675a8 37 *
38 * @copyright 2008 Tim Hunt
39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 * @since Moodle 2.0
36e413e3 41 */
42class moodle_quiz_exception extends moodle_exception {
43 function __construct($quizobj, $errorcode, $a = NULL, $link = '', $debuginfo = null) {
44 if (!$link) {
45 $link = $quizobj->view_url();
46 }
47 parent::__construct($errorcode, 'quiz', $link, $a, $debuginfo);
48 }
49}
50
b10c38a3 51/**
d18675a8 52 * A class encapsulating a quiz and the questions it contains, and making the
53 * information available to scripts like view.php.
54 *
55 * Initially, it only loads a minimal amout of information about each question - loading
56 * extra information only when necessary or when asked. The class tracks which questions
57 * are loaded.
58 *
59 * @copyright 2008 Tim Hunt
60 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
61 * @since Moodle 2.0
b10c38a3 62 */
36e413e3 63class quiz {
64 // Fields initialised in the constructor.
65 protected $course;
66 protected $cm;
67 protected $quiz;
68 protected $context;
a1eb3a44 69 protected $questionids;
7b6757b0 70
36e413e3 71 // Fields set later if that data is needed.
78e7a3dd 72 protected $questions = null;
36e413e3 73 protected $accessmanager = null;
b10c38a3 74 protected $ispreviewuser = null;
36e413e3 75
76 // Constructor =========================================================================
b10c38a3 77 /**
78 * Constructor, assuming we already have the necessary data loaded.
79 *
80 * @param object $quiz the row from the quiz table.
81 * @param object $cm the course_module object for this quiz.
82 * @param object $course the row from the course table for the course we belong to.
739b0711 83 * @param boolean $getcontext intended for testing - stops the constructor getting the context.
b10c38a3 84 */
739b0711 85 function __construct($quiz, $cm, $course, $getcontext = true) {
36e413e3 86 $this->quiz = $quiz;
87 $this->cm = $cm;
a18ba12c 88 $this->quiz->cmid = $this->cm->id;
36e413e3 89 $this->course = $course;
739b0711 90 if ($getcontext && !empty($cm->id)) {
91 $this->context = get_context_instance(CONTEXT_MODULE, $cm->id);
92 }
a1eb3a44 93 $this->questionids = explode(',', quiz_questions_in_quiz($this->quiz->questions));
36e413e3 94 }
95
990650f9
TH
96 /**
97 * Static function to create a new quiz object for a specific user.
98 *
99 * @param integer $quizid the the quiz id.
100 * @param integer $userid the the userid.
a88ba570 101 * @return quiz the new quiz object
990650f9
TH
102 */
103 static public function create($quizid, $userid) {
104 global $DB;
105
106 if (!$quiz = $DB->get_record('quiz', array('id' => $quizid))) {
107 throw new moodle_exception('invalidquizid', 'quiz');
108 }
109 if (!$course = $DB->get_record('course', array('id' => $quiz->course))) {
110 throw new moodle_exception('invalidcoursemodule');
111 }
112 if (!$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id)) {
113 throw new moodle_exception('invalidcoursemodule');
114 }
115
116 // Update quiz with override information
117 $quiz = quiz_update_effective_access($quiz, $userid);
118
119 return new quiz($quiz, $cm, $course);
120 }
121
36e413e3 122 // Functions for loading more data =====================================================
78e7a3dd 123
d18675a8 124 /**
125 * Load just basic information about all the questions in this quiz.
126 */
78e7a3dd 127 public function preload_questions() {
128 if (empty($this->questionids)) {
129 throw new moodle_quiz_exception($this, 'noquestions', $this->edit_url());
130 }
131 $this->questions = question_preload_questions($this->questionids,
a1eb3a44 132 'qqi.grade AS maxmark, qqi.id AS instance',
78e7a3dd 133 '{quiz_question_instances} qqi ON qqi.quiz = :quizid AND q.id = qqi.question',
134 array('quizid' => $this->quiz->id));
36e413e3 135 }
136
81d833ad 137 /**
d18675a8 138 * Fully load some or all of the questions for this quiz. You must call {@link preload_questions()} first.
36e413e3 139 *
78e7a3dd 140 * @param array $questionids question ids of the questions to load. null for all.
36e413e3 141 */
78e7a3dd 142 public function load_questions($questionids = null) {
143 if (is_null($questionids)) {
144 $questionids = $this->questionids;
145 }
146 $questionstoprocess = array();
147 foreach ($questionids as $id) {
a1eb3a44
TH
148 if (array_key_exists($id, $this->questions)) {
149 $questionstoprocess[$id] = $this->questions[$id];
150 }
36e413e3 151 }
a8a8ec51 152 get_question_options($questionstoprocess);
36e413e3 153 }
154
155 // Simple getters ======================================================================
b10c38a3 156 /** @return integer the course id. */
36e413e3 157 public function get_courseid() {
158 return $this->course->id;
159 }
160
78e7a3dd 161 /** @return object the row of the course table. */
162 public function get_course() {
163 return $this->course;
164 }
165
b10c38a3 166 /** @return integer the quiz id. */
36e413e3 167 public function get_quizid() {
168 return $this->quiz->id;
169 }
170
b10c38a3 171 /** @return object the row of the quiz table. */
36e413e3 172 public function get_quiz() {
173 return $this->quiz;
174 }
175
b10c38a3 176 /** @return string the name of this quiz. */
36e413e3 177 public function get_quiz_name() {
178 return $this->quiz->name;
179 }
180
b9b3aa94 181 /** @return integer the number of attempts allowed at this quiz (0 = infinite). */
182 public function get_num_attempts_allowed() {
183 return $this->quiz->attempts;
184 }
185
b10c38a3 186 /** @return integer the course_module id. */
36e413e3 187 public function get_cmid() {
188 return $this->cm->id;
189 }
190
b10c38a3 191 /** @return object the course_module object. */
36e413e3 192 public function get_cm() {
193 return $this->cm;
194 }
195
a1eb3a44
TH
196 /** @return object the module context for this quiz. */
197 public function get_context() {
198 return $this->context;
199 }
200
b10c38a3 201 /**
7b6757b0 202 * @return boolean wether the current user is someone who previews the quiz,
b10c38a3 203 * rather than attempting it.
204 */
205 public function is_preview_user() {
206 if (is_null($this->ispreviewuser)) {
207 $this->ispreviewuser = has_capability('mod/quiz:preview', $this->context);
208 }
209 return $this->ispreviewuser;
210 }
211
78e7a3dd 212 /**
a1eb3a44 213 * @return whether any questions have been added to this quiz.
78e7a3dd 214 */
a1eb3a44
TH
215 public function has_questions() {
216 return !empty($this->questionids);
78e7a3dd 217 }
218
b10c38a3 219 /**
220 * @param integer $id the question id.
221 * @return object the question object with that id.
222 */
36e413e3 223 public function get_question($id) {
36e413e3 224 return $this->questions[$id];
225 }
226
78e7a3dd 227 /**
228 * @param array $questionids question ids of the questions to load. null for all.
229 */
230 public function get_questions($questionids = null) {
231 if (is_null($questionids)) {
232 $questionids = $this->questionids;
233 }
234 $questions = array();
235 foreach ($questionids as $id) {
a1eb3a44
TH
236 if (!array_key_exists($id, $this->questions)) {
237 throw new moodle_exception('cannotstartmissingquestion', 'quiz', $this->view_url());
238 }
78e7a3dd 239 $questions[$id] = $this->questions[$id];
240 $this->ensure_question_loaded($id);
241 }
242 return $questions;
243 }
244
b10c38a3 245 /**
246 * @param integer $timenow the current time as a unix timestamp.
ff065f96 247 * @return quiz_access_manager and instance of the quiz_access_manager class for this quiz at this time.
b10c38a3 248 */
36e413e3 249 public function get_access_manager($timenow) {
250 if (is_null($this->accessmanager)) {
78e7a3dd 251 $this->accessmanager = new quiz_access_manager($this, $timenow,
36e413e3 252 has_capability('mod/quiz:ignoretimelimits', $this->context, NULL, false));
253 }
254 return $this->accessmanager;
255 }
256
78e7a3dd 257 /**
258 * Wrapper round the has_capability funciton that automatically passes in the quiz context.
259 */
260 public function has_capability($capability, $userid = NULL, $doanything = true) {
261 return has_capability($capability, $this->context, $userid, $doanything);
262 }
263
264 /**
265 * Wrapper round the require_capability funciton that automatically passes in the quiz context.
266 */
267 public function require_capability($capability, $userid = NULL, $doanything = true) {
268 return require_capability($capability, $this->context, $userid, $doanything);
269 }
270
36e413e3 271 // URLs related to this attempt ========================================================
b10c38a3 272 /**
273 * @return string the URL of this quiz's view page.
274 */
36e413e3 275 public function view_url() {
276 global $CFG;
277 return $CFG->wwwroot . '/mod/quiz/view.php?id=' . $this->cm->id;
278 }
279
78e7a3dd 280 /**
281 * @return string the URL of this quiz's edit page.
282 */
283 public function edit_url() {
284 global $CFG;
285 return $CFG->wwwroot . '/mod/quiz/edit.php?cmid=' . $this->cm->id;
286 }
287
288 /**
289 * @param integer $attemptid the id of an attempt.
290 * @return string the URL of that attempt.
291 */
292 public function attempt_url($attemptid) {
293 global $CFG;
3c168fbb 294 return $CFG->wwwroot . '/mod/quiz/attempt.php?attempt=' . $attemptid;
78e7a3dd 295 }
296
297 /**
298 * @return string the URL of this quiz's edit page. Needs to be POSTed to with a cmid parameter.
299 */
300 public function start_attempt_url() {
c68287a9
TH
301 return new moodle_url('/mod/quiz/startattempt.php',
302 array('cmid' => $this->cm->id, 'sesskey' => sesskey()));
78e7a3dd 303 }
304
305 /**
306 * @param integer $attemptid the id of an attempt.
307 * @return string the URL of the review of that attempt.
308 */
309 public function review_url($attemptid) {
ea9dbf24 310 return new moodle_url('/mod/quiz/review.php', array('attempt' => $attemptid));
78e7a3dd 311 }
312
36e413e3 313 // Bits of content =====================================================================
36e413e3 314
b10c38a3 315 /**
316 * @param string $title the name of this particular quiz page.
317 * @return array the data that needs to be sent to print_header_simple as the $navigation
318 * parameter.
319 */
36e413e3 320 public function navigation($title) {
2698e9c1 321 global $PAGE;
322 $PAGE->navbar->add($title);
323 return '';
36e413e3 324 }
325
326 // Private methods =====================================================================
d18675a8 327 /**
328 * Check that the definition of a particular question is loaded, and if not throw an exception.
329 * @param $id a questionid.
330 */
78e7a3dd 331 protected function ensure_question_loaded($id) {
332 if (isset($this->questions[$id]->_partiallyloaded)) {
36e413e3 333 throw new moodle_quiz_exception($this, 'questionnotloaded', $id);
334 }
335 }
336}
337
b10c38a3 338/**
339 * This class extends the quiz class to hold data about the state of a particular attempt,
340 * in addition to the data about the quiz.
d18675a8 341 *
342 * @copyright 2008 Tim Hunt
343 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
344 * @since Moodle 2.0
b10c38a3 345 */
a1eb3a44 346class quiz_attempt {
36e413e3 347 // Fields initialised in the constructor.
a1eb3a44 348 protected $quizobj;
36e413e3 349 protected $attempt;
a1eb3a44 350 protected $quba;
36e413e3 351
352 // Fields set later if that data is needed.
a1eb3a44 353 protected $pagelayout; // array page no => array of numbers on the page in order.
4fc3d7e5 354 protected $reviewoptions = null;
36e413e3 355
356 // Constructor =========================================================================
b10c38a3 357 /**
990650f9
TH
358 * Constructor assuming we already have the necessary data loaded.
359 *
360 * @param object $attempt the row of the quiz_attempts table.
361 * @param object $quiz the quiz object for this attempt and user.
362 * @param object $cm the course_module object for this quiz.
363 * @param object $course the row from the course table for the course we belong to.
364 */
365 function __construct($attempt, $quiz, $cm, $course) {
366 $this->attempt = $attempt;
a1eb3a44
TH
367 $this->quizobj = new quiz($quiz, $cm, $course);
368 $this->quba = question_engine::load_questions_usage_by_activity($this->attempt->uniqueid);
369 $this->determine_layout();
370 $this->number_questions();
990650f9
TH
371 }
372
373 /**
56e82d99
TH
374 * Used by {create()} and {create_from_usage_id()}.
375 * @param array $conditions passed to $DB->get_record('quiz_attempts', $conditions).
b10c38a3 376 */
56e82d99 377 static protected function create_helper($conditions) {
36e413e3 378 global $DB;
990650f9 379
56e82d99
TH
380// TODO deal with the issue that makes this necessary.
381// if (!$DB->record_exists('question_sessions', array('attemptid' => $attempt->uniqueid))) {
382// // this attempt has not yet been upgraded to the new model
383// quiz_upgrade_states($attempt);
384// }
385
386 if (!$attempt = $DB->get_record('quiz_attempts', $conditions)) {
36e413e3 387 throw new moodle_exception('invalidattemptid', 'quiz');
388 }
990650f9 389 if (!$quiz = $DB->get_record('quiz', array('id' => $attempt->quiz))) {
36e413e3 390 throw new moodle_exception('invalidquizid', 'quiz');
391 }
392 if (!$course = $DB->get_record('course', array('id' => $quiz->course))) {
393 throw new moodle_exception('invalidcoursemodule');
394 }
395 if (!$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id)) {
396 throw new moodle_exception('invalidcoursemodule');
397 }
a1eb3a44 398
990650f9
TH
399 // Update quiz with override information
400 $quiz = quiz_update_effective_access($quiz, $attempt->userid);
401
402 return new quiz_attempt($attempt, $quiz, $cm, $course);
36e413e3 403 }
404
56e82d99
TH
405 /**
406 * Static function to create a new quiz_attempt object given an attemptid.
407 *
408 * @param integer $attemptid the attempt id.
409 * @return quiz_attempt the new quiz_attempt object
410 */
411 static public function create($attemptid) {
412 return self::create_helper(array('id' => $attemptid));
413 }
414
415 /**
416 * Static function to create a new quiz_attempt object given a usage id.
417 *
418 * @param integer $usageid the attempt usage id.
419 * @return quiz_attempt the new quiz_attempt object
420 */
421 static public function create_from_usage_id($usageid) {
422 return self::create_helper(array('uniqueid' => $usageid));
423 }
424
a1eb3a44
TH
425 private function determine_layout() {
426 $this->pagelayout = array();
427
428 // Break up the layout string into pages.
429 $pagelayouts = explode(',0', quiz_clean_layout($this->attempt->layout, true));
430
431 // Strip off any empty last page (normally there is one).
432 if (end($pagelayouts) == '') {
433 array_pop($pagelayouts);
36e413e3 434 }
a1eb3a44
TH
435
436 // File the ids into the arrays.
437 $this->pagelayout = array();
438 foreach ($pagelayouts as $page => $pagelayout) {
439 $pagelayout = trim($pagelayout, ',');
440 if ($pagelayout == '') {
441 continue;
442 }
443 $this->pagelayout[$page] = explode(',', $pagelayout);
78e7a3dd 444 }
a1eb3a44
TH
445 }
446
447 // Number the questions.
448 private function number_questions() {
449 $number = 1;
450 foreach ($this->pagelayout as $page => $slots) {
451 foreach ($slots as $slot) {
452 $question = $this->quba->get_question($slot);
453 if ($question->length > 0) {
454 $question->_number = $number;
455 $number += $question->length;
456 } else {
457 $question->_number = get_string('infoshort', 'quiz');
458 }
459 $question->_page = $page;
460 }
36e413e3 461 }
81d833ad 462 }
463
a1eb3a44
TH
464 // Simple getters ======================================================================
465 public function get_quiz() {
466 return $this->quizobj->get_quiz();
467 }
468
469 public function get_quizobj() {
470 return $this->quizobj;
471 }
472
473 /** @return integer the course id. */
474 public function get_courseid() {
475 return $this->quizobj->get_courseid();
476 }
477
478 /** @return integer the course id. */
479 public function get_course() {
480 return $this->quizobj->get_course();
481 }
482
483 /** @return integer the quiz id. */
484 public function get_quizid() {
485 return $this->quizobj->get_quizid();
486 }
487
488 /** @return string the name of this quiz. */
489 public function get_quiz_name() {
490 return $this->quizobj->get_quiz_name();
491 }
492
493 /** @return object the course_module object. */
494 public function get_cm() {
495 return $this->quizobj->get_cm();
496 }
497
498 /** @return object the course_module object. */
499 public function get_cmid() {
500 return $this->quizobj->get_cmid();
501 }
502
d18675a8 503 /**
a1eb3a44
TH
504 * @return boolean wether the current user is someone who previews the quiz,
505 * rather than attempting it.
d18675a8 506 */
a1eb3a44
TH
507 public function is_preview_user() {
508 return $this->quizobj->is_preview_user();
509 }
510
511 /** @return integer the number of attempts allowed at this quiz (0 = infinite). */
512 public function get_num_attempts_allowed() {
513 return $this->quizobj->get_num_attempts_allowed();
514 }
515
516 /** @return integer number fo pages in this quiz. */
517 public function get_num_pages() {
518 return count($this->pagelayout);
36e413e3 519 }
520
d18675a8 521 /**
a1eb3a44
TH
522 * @param integer $timenow the current time as a unix timestamp.
523 * @return quiz_access_manager and instance of the quiz_access_manager class for this quiz at this time.
d18675a8 524 */
a1eb3a44
TH
525 public function get_access_manager($timenow) {
526 return $this->quizobj->get_access_manager($timenow);
b55797b8 527 }
528
b10c38a3 529 /** @return integer the attempt id. */
36e413e3 530 public function get_attemptid() {
531 return $this->attempt->id;
532 }
533
766df8f7 534 /** @return integer the attempt unique id. */
535 public function get_uniqueid() {
536 return $this->attempt->uniqueid;
537 }
538
b10c38a3 539 /** @return object the row from the quiz_attempts table. */
36e413e3 540 public function get_attempt() {
541 return $this->attempt;
542 }
543
78e7a3dd 544 /** @return integer the number of this attemp (is it this user's first, second, ... attempt). */
545 public function get_attempt_number() {
546 return $this->attempt->attempt;
547 }
548
b10c38a3 549 /** @return integer the id of the user this attempt belongs to. */
36e413e3 550 public function get_userid() {
551 return $this->attempt->userid;
552 }
553
aafdb447 554 /** @return boolean whether this attempt has been finished (true) or is still in progress (false). */
36e413e3 555 public function is_finished() {
556 return $this->attempt->timefinish != 0;
557 }
558
fe6ce234 559 /** @return boolean whether this attempt is a preview attempt. */
4fc3d7e5 560 public function is_preview() {
561 return $this->attempt->preview;
562 }
563
b55797b8 564 /**
565 * Is this a student dealing with their own attempt/teacher previewing,
566 * or someone with 'mod/quiz:viewreports' reviewing someone elses attempt.
7b6757b0 567 *
b55797b8 568 * @return boolean whether this situation should be treated as someone looking at their own
569 * attempt. The distinction normally only matters when an attempt is being reviewed.
570 */
571 public function is_own_attempt() {
572 global $USER;
573 return $this->attempt->userid == $USER->id &&
574 (!$this->is_preview_user() || $this->attempt->preview);
b55797b8 575 }
576
8032cd79
TH
577 /**
578 * Is the current user allowed to review this attempt. This applies when
579 * {@link is_own_attempt()} returns false.
580 * @return bool whether the review should be allowed.
581 */
582 public function is_review_allowed() {
583 if (!$this->has_capability('mod/quiz:viewreports')) {
584 return false;
585 }
586
587 $cm = $this->get_cm();
588 if ($this->has_capability('moodle/site:accessallgroups') ||
589 groups_get_activity_groupmode($cm) != SEPARATEGROUPS) {
590 return true;
591 }
592
593 // Check the users have at least one group in common.
594 $teachersgroups = groups_get_activity_allowed_groups($cm);
595 $studentsgroups = groups_get_all_groups($cm->course, $this->attempt->userid, $cm->groupingid);
596 return $teachersgroups && $studentsgroups &&
597 array_intersect(array_keys($teachersgroups), array_keys($studentsgroups));
598 }
599
b2607ccc
TH
600 /**
601 * Get the overall feedback corresponding to a particular mark.
602 * @param $grade a particular grade.
603 */
604 public function get_overall_feedback($grade) {
605 return quiz_feedback_for_grade($grade, $this->get_quiz(),
2709ee45 606 $this->quizobj->get_context());
b2607ccc
TH
607 }
608
a1eb3a44
TH
609 /**
610 * Wrapper round the has_capability funciton that automatically passes in the quiz context.
611 */
612 public function has_capability($capability, $userid = NULL, $doanything = true) {
613 return $this->quizobj->has_capability($capability, $userid, $doanything);
614 }
615
616 /**
617 * Wrapper round the require_capability funciton that automatically passes in the quiz context.
618 */
619 public function require_capability($capability, $userid = NULL, $doanything = true) {
620 return $this->quizobj->require_capability($capability, $userid, $doanything);
621 }
622
96c7d771 623 /**
624 * Check the appropriate capability to see whether this user may review their own attempt.
625 * If not, prints an error.
626 */
627 public function check_review_capability() {
628 if (!$this->has_capability('mod/quiz:viewreports')) {
a1eb3a44 629 if ($this->get_attempt_state() == mod_quiz_display_options::IMMEDIATELY_AFTER) {
96c7d771 630 $this->require_capability('mod/quiz:attempt');
631 } else {
632 $this->require_capability('mod/quiz:reviewmyattempts');
633 }
634 }
635 }
636
d18675a8 637 /**
a1eb3a44
TH
638 * @return integer one of the mod_quiz_display_options::DURING,
639 * IMMEDIATELY_AFTER, LATER_WHILE_OPEN or AFTER_CLOSE constants.
d18675a8 640 */
a1eb3a44
TH
641 public function get_attempt_state() {
642 return quiz_attempt_state($this->get_quiz(), $this->attempt);
78e7a3dd 643 }
644
b10c38a3 645 /**
a1eb3a44
TH
646 * Wrapper that the correct mod_quiz_display_options for this quiz at the
647 * moment.
b10c38a3 648 *
a1eb3a44 649 * @return question_display_options the render options for this user on this attempt.
b10c38a3 650 */
a1eb3a44
TH
651 public function get_display_options($reviewing) {
652 if ($reviewing) {
653 if (is_null($this->reviewoptions)) {
7ee80cab 654 $this->reviewoptions = quiz_get_review_options($this->get_quiz(),
a1eb3a44
TH
655 $this->attempt, $this->quizobj->get_context());
656 }
657 return $this->reviewoptions;
658
659 } else {
660 $options = mod_quiz_display_options::make_from_quiz($this->get_quiz(),
661 mod_quiz_display_options::DURING);
662 $options->flags = quiz_get_flag_option($this->attempt, $this->quizobj->get_context());
663 return $options;
36e413e3 664 }
36e413e3 665 }
666
4fc3d7e5 667 /**
a1eb3a44
TH
668 * @param int $page page number
669 * @return boolean true if this is the last page of the quiz.
4fc3d7e5 670 */
a1eb3a44
TH
671 public function is_last_page($page) {
672 return $page == count($this->pagelayout) - 1;
4fc3d7e5 673 }
674
b10c38a3 675 /**
a1eb3a44
TH
676 * Return the list of question ids for either a given page of the quiz, or for the
677 * whole quiz.
b10c38a3 678 *
a1eb3a44
TH
679 * @param mixed $page string 'all' or integer page number.
680 * @return array the reqested list of question ids.
b10c38a3 681 */
a1eb3a44 682 public function get_slots($page = 'all') {
a1eb3a44
TH
683 if ($page === 'all') {
684 $numbers = array();
685 foreach ($this->pagelayout as $numbersonpage) {
686 $numbers = array_merge($numbers, $numbersonpage);
687 }
688 return $numbers;
689 } else {
690 return $this->pagelayout[$page];
691 }
36e413e3 692 }
693
b10c38a3 694 /**
a1eb3a44
TH
695 * Get the question_attempt object for a particular question in this attempt.
696 * @param integer $slot the number used to identify this question within this attempt.
697 * @return question_attempt
b10c38a3 698 */
a1eb3a44
TH
699 public function get_question_attempt($slot) {
700 return $this->quba->get_question_attempt($slot);
701 }
b10c38a3 702
a1eb3a44
TH
703 /**
704 * Is a particular question in this attempt a real question, or something like a description.
705 * @param integer $slot the number used to identify this question within this attempt.
706 * @return boolean whether that question is a real question.
707 */
708 public function is_real_question($slot) {
709 return $this->quba->get_question($slot)->length != 0;
36e413e3 710 }
711
62e76c67 712 /**
a1eb3a44
TH
713 * Is a particular question in this attempt a real question, or something like a description.
714 * @param integer $slot the number used to identify this question within this attempt.
715 * @return boolean whether that question is a real question.
62e76c67 716 */
a1eb3a44
TH
717 public function is_question_flagged($slot) {
718 return $this->quba->get_question_attempt($slot)->is_flagged();
62e76c67 719 }
720
36e413e3 721 /**
b10c38a3 722 * Return the grade obtained on a particular question, if the user is permitted to see it.
723 * You must previously have called load_question_states to load the state data about this question.
36e413e3 724 *
a1eb3a44 725 * @param integer $slot the number used to identify this question within this attempt.
36e413e3 726 * @return string the formatted grade, to the number of decimal places specified by the quiz.
727 */
a1eb3a44
TH
728 public function get_question_number($slot) {
729 return $this->quba->get_question($slot)->_number;
730 }
731
732 /**
733 * Return the grade obtained on a particular question, if the user is permitted to see it.
734 * You must previously have called load_question_states to load the state data about this question.
735 *
736 * @param integer $slot the number used to identify this question within this attempt.
737 * @return string the formatted grade, to the number of decimal places specified by the quiz.
738 */
739 public function get_question_name($slot) {
740 return $this->quba->get_question($slot)->name;
741 }
742
743 /**
744 * Return the grade obtained on a particular question, if the user is permitted to see it.
745 * You must previously have called load_question_states to load the state data about this question.
746 *
747 * @param integer $slot the number used to identify this question within this attempt.
748 * @param boolean $showcorrectness Whether right/partial/wrong states should
749 * be distinguised.
750 * @return string the formatted grade, to the number of decimal places specified by the quiz.
751 */
752 public function get_question_status($slot, $showcorrectness) {
753 return $this->quba->get_question_state_string($slot, $showcorrectness);
754 }
755
756 /**
757 * Return the grade obtained on a particular question.
758 * You must previously have called load_question_states to load the state
759 * data about this question.
760 *
761 * @param integer $slot the number used to identify this question within this attempt.
762 * @return string the formatted grade, to the number of decimal places specified by the quiz.
763 */
b2607ccc 764 public function get_question_mark($slot) {
a1eb3a44
TH
765 return quiz_format_question_grade($this->get_quiz(), $this->quba->get_question_mark($slot));
766 }
767
768 /**
769 * Get the time of the most recent action performed on a question.
770 * @param integer $slot the number used to identify this question within this usage.
771 * @return integer timestamp.
772 */
773 public function get_question_action_time($slot) {
774 return $this->quba->get_question_action_time($slot);
36e413e3 775 }
776
777 // URLs related to this attempt ========================================================
b10c38a3 778 /**
a1eb3a44
TH
779 * @return string quiz view url.
780 */
781 public function view_url() {
782 return $this->quizobj->view_url();
783 }
784
785 /**
786 * @return string the URL of this quiz's edit page. Needs to be POSTed to with a cmid parameter.
787 */
788 public function start_attempt_url() {
789 return $this->quizobj->start_attempt_url();
790 }
791
792 /**
793 * @param integer $slot if speified, the slot number of a specific question to link to.
794 * @param integer $page if specified, a particular page to link to. If not givem deduced
795 * from $slot, or goes to the first page.
78e7a3dd 796 * @param integer $questionid a question id. If set, will add a fragment to the URL
b10c38a3 797 * to jump to a particuar question on the page.
d18675a8 798 * @param integer $thispage if not -1, the current page. Will cause links to other things on
799 * this page to be output as only a fragment.
b10c38a3 800 * @return string the URL to continue this attempt.
801 */
56e82d99 802 public function attempt_url($slot = null, $page = -1, $thispage = -1) {
a1eb3a44 803 return $this->page_and_question_url('attempt', $slot, $page, false, $thispage);
36e413e3 804 }
805
b10c38a3 806 /**
807 * @return string the URL of this quiz's summary page.
808 */
36e413e3 809 public function summary_url() {
a1eb3a44 810 return new moodle_url('/mod/quiz/summary.php', array('attempt' => $this->attempt->id));
36e413e3 811 }
812
9f9eec1e 813 /**
814 * @return string the URL of this quiz's summary page.
815 */
816 public function processattempt_url() {
a1eb3a44 817 return new moodle_url('/mod/quiz/processattempt.php');
9f9eec1e 818 }
819
b10c38a3 820 /**
a1eb3a44 821 * @param integer $slot indicates which question to link to.
b10c38a3 822 * @param integer $page if specified, the URL of this particular page of the attempt, otherwise
a1eb3a44 823 * the URL will go to the first page. If -1, deduce $page from $slot.
b10c38a3 824 * @param boolean $showall if true, the URL will be to review the entire attempt on one page,
825 * and $page will be ignored.
d18675a8 826 * @param integer $thispage if not -1, the current page. Will cause links to other things on
827 * this page to be output as only a fragment.
b10c38a3 828 * @return string the URL to review this attempt.
829 */
56e82d99 830 public function review_url($slot = null, $page = -1, $showall = false, $thispage = -1) {
a1eb3a44 831 return $this->page_and_question_url('review', $slot, $page, $showall, $thispage);
78e7a3dd 832 }
833
834 // Bits of content =====================================================================
a1eb3a44 835
d18675a8 836 /**
837 * Initialise the JS etc. required all the questions on a page..
838 * @param mixed $page a page number, or 'all'.
839 */
a1eb3a44
TH
840 public function get_html_head_contributions($page = 'all', $showall = false) {
841 if ($showall) {
842 $page = 'all';
843 }
844 $result = '';
845 foreach ($this->get_slots($page) as $slot) {
846 $result .= $this->quba->render_question_head_html($slot);
847 }
848 $result .= question_engine::initialise_js();
849 return $result;
78e7a3dd 850 }
851
d18675a8 852 /**
853 * Initialise the JS etc. required by one question.
854 * @param integer $questionid the question id.
855 */
a1eb3a44
TH
856 public function get_question_html_head_contributions($slot) {
857 return $this->quba->render_question_head_html($slot) .
858 question_engine::initialise_js();
b826bcef 859 }
860
d18675a8 861 /**
862 * Print the HTML for the start new preview button.
863 */
78e7a3dd 864 public function print_restart_preview_button() {
39e37019 865 global $CFG, $OUTPUT;
866 echo $OUTPUT->container_start('controls');
c68287a9 867 $url = new moodle_url($this->start_attempt_url(), array('forcenew' => true));
5c2ed7e2 868 echo $OUTPUT->single_button($url, get_string('startagain', 'quiz'));
39e37019 869 echo $OUTPUT->container_end();
36e413e3 870 }
871
d18675a8 872 /**
873 * Return the HTML of the quiz timer.
874 * @return string HTML content.
875 */
692e0c33 876 public function get_timer_html() {
877 return '<div id="quiz-timer">' . get_string('timeleft', 'quiz') .
878 ' <span id="quiz-time-left"></span></div>';
879 }
880
aafdb447 881 /**
a1eb3a44
TH
882 * Generate the HTML that displayes the question in its current state, with
883 * the appropriate display options.
aafdb447 884 *
885 * @param integer $id the id of a question in this quiz attempt.
886 * @param boolean $reviewing is the being printed on an attempt or a review page.
887 * @param string $thispageurl the URL of the page this question is being printed on.
a1eb3a44 888 * @return string HTML for the question in its current state.
aafdb447 889 */
a1eb3a44
TH
890 public function render_question($slot, $reviewing, $thispageurl = '') {
891 return $this->quba->render_question($slot,
892 $this->get_display_options($reviewing),
893 $this->quba->get_question($slot)->_number);
894 }
fb6dcdab 895
a1eb3a44
TH
896 /**
897 * Like {@link render_question()} but displays the question at the past step
898 * indicated by $seq, rather than showing the latest step.
899 *
900 * @param integer $id the id of a question in this quiz attempt.
901 * @param integer $seq the seq number of the past state to display.
902 * @param boolean $reviewing is the being printed on an attempt or a review page.
903 * @param string $thispageurl the URL of the page this question is being printed on.
904 * @return string HTML for the question in its current state.
905 */
906 public function render_question_at_step($slot, $seq, $reviewing, $thispageurl = '') {
907 return $this->quba->render_question_at_step($slot, $seq,
908 $this->get_display_options($reviewing),
909 $this->quba->get_question($slot)->_number);
910 }
911
912 /**
913 * Wrapper round print_question from lib/questionlib.php.
914 *
915 * @param integer $id the id of a question in this quiz attempt.
916 * @param boolean $reviewing is the being printed on an attempt or a review page.
917 * @param string $thispageurl the URL of the page this question is being printed on.
918 */
919 public function render_question_for_commenting($slot) {
920 $options = $this->get_display_options(true);
921 $options->hide_all_feedback();
922 $options->manualcomment = question_display_options::EDITABLE;
923 return $this->quba->render_question($slot, $options, $this->quba->get_question($slot)->_number);
78e7a3dd 924 }
925
a1eb3a44
TH
926 /**
927 * Check wheter access should be allowed to a particular file.
928 *
929 * @param integer $id the id of a question in this quiz attempt.
930 * @param boolean $reviewing is the being printed on an attempt or a review page.
931 * @param string $thispageurl the URL of the page this question is being printed on.
932 * @return string HTML for the question in its current state.
933 */
56e82d99 934 public function check_file_access($slot, $reviewing, $contextid, $component,
fe6ce234 935 $filearea, $args, $forcedownload) {
56e82d99
TH
936 return $this->quba->check_file_access($slot, $this->get_display_options($reviewing),
937 $component, $filearea, $args, $forcedownload);
fe6ce234
DC
938 }
939
d18675a8 940 /**
941 * Triggers the sending of the notification emails at the end of this attempt.
942 */
78e7a3dd 943 public function quiz_send_notification_emails() {
a1eb3a44
TH
944 quiz_send_notification_emails($this->get_course(), $this->get_quiz(), $this->attempt,
945 $this->quizobj->get_context(), $this->get_cm());
78e7a3dd 946 }
3c168fbb 947
d18675a8 948 /**
949 * Get the navigation panel object for this attempt.
950 *
951 * @param $panelclass The type of panel, quiz_attempt_nav_panel or quiz_review_nav_panel
952 * @param $page the current page number.
953 * @param $showall whether we are showing the whole quiz on one page. (Used by review.php)
954 * @return quiz_nav_panel_base the requested object.
955 */
956 public function get_navigation_panel($panelclass, $page, $showall = false) {
a1eb3a44 957 $panel = new $panelclass($this, $this->get_display_options(true), $page, $showall);
d4a03c00 958 return $panel->get_contents();
3c168fbb 959 }
36e413e3 960
d18675a8 961 /**
83192608 962 * Given a URL containing attempt={this attempt id}, return an array of variant URLs
d18675a8 963 * @param $url a URL.
964 * @return string HTML fragment. Comma-separated list of links to the other
965 * attempts with the attempt number as the link text. The curent attempt is
966 * included but is not a link.
967 */
b55797b8 968 public function links_to_other_attempts($url) {
969 $search = '/\battempt=' . $this->attempt->id . '\b/';
a1eb3a44 970 $attempts = quiz_get_user_attempts($this->get_quiz()->id, $this->attempt->userid, 'all');
f88fb62c 971 if (count($attempts) <= 1) {
972 return false;
973 }
b55797b8 974 $attemptlist = array();
975 foreach ($attempts as $at) {
976 if ($at->id == $this->attempt->id) {
977 $attemptlist[] = '<strong>' . $at->attempt . '</strong>';
978 } else {
979 $changedurl = preg_replace($search, 'attempt=' . $at->id, $url);
d4ad9adf 980 $attemptlist[] = '<a href="' . s($changedurl) . '">' . $at->attempt . '</a>';
b55797b8 981 }
982 }
983 return implode(', ', $attemptlist);
984 }
985
a1eb3a44
TH
986 // Methods for processing ==================================================
987
d18675a8 988 /**
a1eb3a44
TH
989 * Process all the actions that were submitted as part of the current request.
990 *
991 * @param integer $timestamp the timestamp that should be stored as the modifed
992 * time in the database for these actions. If null, will use the current time.
d18675a8 993 */
a1eb3a44 994 public function process_all_actions($timestamp) {
8f37f7fb 995 global $DB;
a1eb3a44
TH
996 $this->quba->process_all_actions($timestamp);
997 question_engine::save_questions_usage_by_activity($this->quba);
998
999 $this->attempt->timemodified = $timestamp;
1000 if ($this->attempt->timefinish) {
1001 $this->attempt->sumgrades = $this->quba->get_total_mark();
1002 }
8f37f7fb 1003 if (!$DB->update_record('quiz_attempts', $this->attempt)) {
a1eb3a44
TH
1004 throw new moodle_quiz_exception($this->get_quizobj(), 'saveattemptfailed');
1005 }
1006 if (!$this->is_preview() && $this->attempt->timefinish) {
1007 quiz_save_best_grade($this->get_quiz(), $this->get_userid());
766df8f7 1008 }
766df8f7 1009 }
1010
d18675a8 1011 /**
a1eb3a44
TH
1012 * Update the flagged state for all question_attempts in this usage, if their
1013 * flagged state was changed in the request.
d18675a8 1014 */
a1eb3a44
TH
1015 public function save_question_flags() {
1016 $this->quba->update_question_flags();
1017 question_engine::save_questions_usage_by_activity($this->quba);
1018 }
e8f99abc 1019
a1eb3a44 1020 public function finish_attempt($timestamp) {
8f37f7fb 1021 global $DB;
a1eb3a44
TH
1022 $this->quba->process_all_actions($timestamp);
1023 $this->quba->finish_all_questions($timestamp);
e8f99abc 1024
a1eb3a44 1025 question_engine::save_questions_usage_by_activity($this->quba);
e8f99abc 1026
a1eb3a44
TH
1027 $this->attempt->timemodified = $timestamp;
1028 $this->attempt->timefinish = $timestamp;
1029 $this->attempt->sumgrades = $this->quba->get_total_mark();
8f37f7fb 1030 if (!$DB->update_record('quiz_attempts', $this->attempt)) {
a1eb3a44
TH
1031 throw new moodle_quiz_exception($this->get_quizobj(), 'saveattemptfailed');
1032 }
766df8f7 1033
a1eb3a44
TH
1034 if (!$this->is_preview()) {
1035 quiz_save_best_grade($this->get_quiz());
1036 $this->quiz_send_notification_emails();
36e413e3 1037 }
1038 }
78e7a3dd 1039
1040 /**
a1eb3a44
TH
1041 * Print the fields of the comment form for questions in this attempt.
1042 * @param $slot which question to output the fields for.
1043 * @param $prefix Prefix to add to all field names.
78e7a3dd 1044 */
a1eb3a44
TH
1045 public function question_print_comment_fields($slot, $prefix) {
1046 // Work out a nice title.
1047 $student = get_record('user', 'id', $this->get_userid());
1048 $a = new object();
1049 $a->fullname = fullname($student, true);
1050 $a->attempt = $this->get_attempt_number();
1051
1052 question_print_comment_fields($this->quba->get_question_attempt($slot),
1053 $prefix, $this->get_display_options(true)->markdp,
1054 get_string('gradingattempt', 'quiz_grading', $a));
78e7a3dd 1055 }
1056
a1eb3a44
TH
1057 // Private methods =====================================================================
1058
78e7a3dd 1059 /**
d18675a8 1060 * Get a URL for a particular question on a particular page of the quiz.
1061 * Used by {@link attempt_url()} and {@link review_url()}.
78e7a3dd 1062 *
d18675a8 1063 * @param string $script. Used in the URL like /mod/quiz/$script.php
a1eb3a44
TH
1064 * @param integer $slot identifies the specific question on the page to jump to. 0 to just use the $page parameter.
1065 * @param integer $page -1 to look up the page number from the slot, otherwise the page number to go to.
d18675a8 1066 * @param boolean $showall if true, return a URL with showall=1, and not page number
a1eb3a44 1067 * @param integer $thispage the page we are currently on. Links to questions on this
d18675a8 1068 * page will just be a fragment #q123. -1 to disable this.
1069 * @return The requested URL.
78e7a3dd 1070 */
a1eb3a44 1071 protected function page_and_question_url($script, $slot, $page, $showall, $thispage) {
d18675a8 1072 // Fix up $page
3c168fbb 1073 if ($page == -1) {
56e82d99 1074 if (!is_null($slot) && !$showall) {
a1eb3a44 1075 $page = $this->quba->get_question($slot)->_page;
78e7a3dd 1076 } else {
1077 $page = 0;
1078 }
1079 }
a1eb3a44 1080
78e7a3dd 1081 if ($showall) {
1082 $page = 0;
1083 }
d18675a8 1084
fb6dcdab 1085 // Add a fragment to scroll down to the question.
a1eb3a44 1086 $fragment = '';
56e82d99 1087 if (!is_null($slot)) {
a1eb3a44 1088 if ($slot == reset($this->pagelayout[$page])) {
d4a03c00 1089 // First question on page, go to top.
a1eb3a44 1090 $fragment = '#';
d4a03c00 1091 } else {
a1eb3a44 1092 $fragment = '#q' . $slot;
d4a03c00 1093 }
78e7a3dd 1094 }
d18675a8 1095
a1eb3a44
TH
1096 // Work out the correct start to the URL.
1097 if ($thispage == $page) {
1098 return new moodle_url($fragment);
36e413e3 1099
36e413e3 1100 } else {
a1eb3a44
TH
1101 $url = new moodle_url('/mod/quiz/' . $script . '.php' . $fragment,
1102 array('attempt' => $this->attempt->id));
1103 if ($showall) {
1104 $url->param('showall', 1);
1105 } else if ($page > 0) {
1106 $url->param('page', $page);
1107 }
1108 return $url;
36e413e3 1109 }
1110 }
36e413e3 1111}
3c168fbb 1112
d18675a8 1113/**
1114 * Represents the navigation panel, and builds a {@link block_contents} to allow
1115 * it to be output.
1116 *
1117 * @copyright 2008 Tim Hunt
1118 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1119 * @since Moodle 2.0
1120 */
3c168fbb 1121abstract class quiz_nav_panel_base {
38c9edd0 1122 /** @var quiz_attempt */
3c168fbb 1123 protected $attemptobj;
38c9edd0 1124 /** @var question_display_options */
3c168fbb 1125 protected $options;
38c9edd0 1126 /** @var integer */
3c168fbb 1127 protected $page;
38c9edd0 1128 /** @var boolean */
d18675a8 1129 protected $showall;
3c168fbb 1130
a1eb3a44
TH
1131 public function __construct(quiz_attempt $attemptobj,
1132 question_display_options $options, $page, $showall) {
38c9edd0
TH
1133 $this->attemptobj = $attemptobj;
1134 $this->options = $options;
1135 $this->page = $page;
1136 $this->showall = $showall;
3c168fbb 1137 }
1138
1139 protected function get_question_buttons() {
c752264f 1140 $html = '<div class="qn_buttons">' . "\n";
a1eb3a44
TH
1141 foreach ($this->attemptobj->get_slots() as $slot) {
1142 $qa = $this->attemptobj->get_question_attempt($slot);
1143 $showcorrectness = $this->options->correctness && $qa->has_marks();
1144 $html .= $this->get_question_button($qa, $qa->get_question()->_number,
1145 $showcorrectness) . "\n";
3c168fbb 1146 }
c752264f 1147 $html .= "</div>\n";
3c168fbb 1148 return $html;
1149 }
1150
a1eb3a44
TH
1151 protected function get_button_id(question_attempt $qa) {
1152 // The id to put on the button element in the HTML.
1153 return 'quiznavbutton' . $qa->get_slot();
1154 }
1155
1156 protected function get_question_button(question_attempt $qa, $number, $showcorrectness) {
1157 $attributes = $this->get_attributes($qa, $showcorrectness);
1158
1159 if (is_numeric($number)) {
1160 $qnostring = 'questionnonav';
1161 } else {
1162 $qnostring = 'questionnonavinfo';
1163 }
1164
0ff4bd08 1165 $a = new stdClass();
a1eb3a44
TH
1166 $a->number = $number;
1167 $a->attributes = implode(' ', $attributes);
1168
1169 return '<a href="' . $this->get_question_url($qa->get_slot()) .
1170 '" class="qnbutton ' . implode(' ', array_keys($attributes)) .
1171 '" id="' . $this->get_button_id($qa) . '" title="' .
1172 $qa->get_state_string($showcorrectness) . '">' .
1173 '<span class="thispageholder"></span><span class="trafficlight"></span>' .
1174 get_string($qnostring, 'quiz', $a) . '</a>';
1175 }
1176
1177 /**
1178 * @param question_attempt $qa
1179 * @param boolean $showcorrectness
1180 * @return array class name => descriptive string.
1181 */
1182 protected function get_attributes(question_attempt $qa, $showcorrectness) {
1183 // The current status of the question.
1184 $attributes = array();
1185
1186 // On the current page?
1187 if ($qa->get_question()->_page == $this->page) {
1188 $attributes['thispage'] = get_string('onthispage', 'quiz');
1189 }
1190
1191 // Question state.
1192 $stateclass = $qa->get_state()->get_state_class($showcorrectness);
1193 if (!$showcorrectness && $stateclass == 'notanswered') {
1194 $stateclass = 'complete';
a26246ea 1195 }
a1eb3a44
TH
1196 $attributes[$stateclass] = $qa->get_state_string($showcorrectness);
1197
1198 // Flagged?
1199 if ($qa->is_flagged()) {
1200 $attributes['flagged'] = '<span class="flagstate">' .
1201 get_string('flagged', 'question') . '</span>';
1202 } else {
1203 $attributes[''] = '<span class="flagstate"></span>';
1204 }
1205
1206 return $attributes;
c752264f 1207 }
1208
d18675a8 1209 protected function get_before_button_bits() {
1210 return '';
1211 }
3c168fbb 1212
1213 abstract protected function get_end_bits();
1214
a1eb3a44 1215 abstract protected function get_question_url($slot);
d18675a8 1216
a733c4b9 1217 protected function get_user_picture() {
39e37019 1218 global $DB, $OUTPUT;
a733c4b9 1219 $user = $DB->get_record('user', array('id' => $this->attemptobj->get_userid()));
1220 $output = '';
1221 $output .= '<div id="user-picture" class="clearfix">';
812dbaf7 1222 $output .= $OUTPUT->user_picture($user, array('courseid'=>$this->attemptobj->get_courseid()));
a733c4b9 1223 $output .= ' ' . fullname($user);
1224 $output .= '</div>';
1225 return $output;
1226 }
1227
d4a03c00 1228 public function get_contents() {
d18675a8 1229 global $PAGE;
ff065f96 1230 $PAGE->requires->js_init_call('M.mod_quiz.nav.init', null, false, quiz_get_js_module());
d18675a8 1231
a733c4b9 1232 $content = '';
a1eb3a44 1233 if (!empty($this->attemptobj->get_quiz()->showuserpicture)) {
a733c4b9 1234 $content .= $this->get_user_picture() . "\n";
1235 }
d18675a8 1236 $content .= $this->get_before_button_bits();
a733c4b9 1237 $content .= $this->get_question_buttons() . "\n";
1238 $content .= '<div class="othernav">' . "\n" . $this->get_end_bits() . "\n</div>\n";
d4a03c00 1239
1240 $bc = new block_contents();
1241 $bc->id = 'quiznavigation';
1242 $bc->title = get_string('quiznavigation', 'quiz');
1243 $bc->content = $content;
1244 return $bc;
3c168fbb 1245 }
1246}
1247
d18675a8 1248/**
1249 * Specialisation of {@link quiz_nav_panel_base} for the attempt quiz page.
1250 *
1251 * @copyright 2008 Tim Hunt
1252 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1253 * @since Moodle 2.0
1254 */
3c168fbb 1255class quiz_attempt_nav_panel extends quiz_nav_panel_base {
a1eb3a44
TH
1256 protected function get_question_url($slot) {
1257 return $this->attemptobj->attempt_url($slot, -1, $this->page);
3c168fbb 1258 }
1259
d18675a8 1260 protected function get_before_button_bits() {
1261 return '<div id="quiznojswarning">' . get_string('navnojswarning', 'quiz') . "</div>\n";
3c168fbb 1262 }
1263
1264 protected function get_end_bits() {
d18675a8 1265 global $PAGE;
5533791a 1266 $output = '';
a1eb3a44 1267 $output .= '<a href="' . s($this->attemptobj->summary_url()) . '" id="endtestlink">' . get_string('endtest', 'quiz') . '</a>';
692e0c33 1268 $output .= $this->attemptobj->get_timer_html();
5533791a 1269 return $output;
3c168fbb 1270 }
1271}
1272
d18675a8 1273/**
1274 * Specialisation of {@link quiz_nav_panel_base} for the review quiz page.
1275 *
1276 * @copyright 2008 Tim Hunt
1277 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1278 * @since Moodle 2.0
1279 */
3c168fbb 1280class quiz_review_nav_panel extends quiz_nav_panel_base {
a1eb3a44
TH
1281 protected function get_question_url($slot) {
1282 return $this->attemptobj->review_url($slot, -1, $this->showall, $this->page);
3c168fbb 1283 }
1284
1285 protected function get_end_bits() {
d18675a8 1286 $html = '';
1287 if ($this->attemptobj->get_num_pages() > 1) {
1288 if ($this->showall) {
2709ee45 1289 $html .= '<a href="' . $this->attemptobj->review_url(null, 0, false) . '">' . get_string('showeachpage', 'quiz') . '</a>';
d18675a8 1290 } else {
2709ee45 1291 $html .= '<a href="' . $this->attemptobj->review_url(null, 0, true) . '">' . get_string('showall', 'quiz') . '</a>';
d18675a8 1292 }
1293 }
baef998b 1294 $accessmanager = $this->attemptobj->get_access_manager(time());
baef998b 1295 $html .= $accessmanager->print_finish_review_link($this->attemptobj->is_preview_user(), true);
3c168fbb 1296 return $html;
1297 }
1298}