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