2008-06-27 18:04:48 +00:00
|
|
|
<?php
|
2009-07-23 09:20:33 +00:00
|
|
|
// This file is part of Moodle - http://moodle.org/
|
|
|
|
//
|
|
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2008-06-27 18:04:48 +00:00
|
|
|
/**
|
2009-07-23 09:20:33 +00:00
|
|
|
* Back-end code for handling data about quizzes and the current user's attempt.
|
|
|
|
*
|
|
|
|
* There are classes for loading all the information about a quiz and attempts,
|
|
|
|
* and for displaying the navigation panel.
|
|
|
|
*
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
* @package mod_quiz
|
|
|
|
* @copyright 2008 onwards Tim Hunt
|
|
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
2009-07-23 09:20:33 +00:00
|
|
|
*/
|
2008-06-27 18:04:48 +00:00
|
|
|
|
|
|
|
|
2011-02-23 15:03:35 +00:00
|
|
|
defined('MOODLE_INTERNAL') || die();
|
2011-02-08 14:19:23 +00:00
|
|
|
|
|
|
|
|
2008-06-27 18:04:48 +00:00
|
|
|
/**
|
2008-06-30 16:56:49 +00:00
|
|
|
* Class for quiz exceptions. Just saves a couple of arguments on the
|
|
|
|
* constructor for a moodle_exception.
|
2009-07-23 09:20:33 +00:00
|
|
|
*
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
* @copyright 2008 Tim Hunt
|
|
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
|
* @since Moodle 2.0
|
2008-06-27 18:04:48 +00:00
|
|
|
*/
|
|
|
|
class moodle_quiz_exception extends moodle_exception {
|
2011-05-12 00:30:25 +01:00
|
|
|
public function __construct($quizobj, $errorcode, $a = null, $link = '', $debuginfo = null) {
|
2008-06-27 18:04:48 +00:00
|
|
|
if (!$link) {
|
|
|
|
$link = $quizobj->view_url();
|
|
|
|
}
|
|
|
|
parent::__construct($errorcode, 'quiz', $link, $a, $debuginfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
2009-07-23 09:20:33 +00:00
|
|
|
* A class encapsulating a quiz and the questions it contains, and making the
|
|
|
|
* information available to scripts like view.php.
|
|
|
|
*
|
|
|
|
* Initially, it only loads a minimal amout of information about each question - loading
|
|
|
|
* extra information only when necessary or when asked. The class tracks which questions
|
|
|
|
* are loaded.
|
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @copyright 2008 Tim Hunt
|
|
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
|
* @since Moodle 2.0
|
2008-06-30 16:56:49 +00:00
|
|
|
*/
|
2008-06-27 18:04:48 +00:00
|
|
|
class quiz {
|
2015-04-02 15:23:04 +01:00
|
|
|
/** @var stdClass the course settings from the database. */
|
2008-06-27 18:04:48 +00:00
|
|
|
protected $course;
|
2015-04-02 15:23:04 +01:00
|
|
|
/** @var stdClass the course_module settings from the database. */
|
2008-06-27 18:04:48 +00:00
|
|
|
protected $cm;
|
2015-04-02 15:23:04 +01:00
|
|
|
/** @var stdClass the quiz settings from the database. */
|
2008-06-27 18:04:48 +00:00
|
|
|
protected $quiz;
|
2015-04-02 15:23:04 +01:00
|
|
|
/** @var context the quiz context. */
|
2008-06-27 18:04:48 +00:00
|
|
|
protected $context;
|
2008-08-29 07:29:35 +00:00
|
|
|
|
2015-04-02 15:23:04 +01:00
|
|
|
/** @var array of questions augmented with slot information. */
|
2008-07-08 16:33:47 +00:00
|
|
|
protected $questions = null;
|
2015-04-02 15:23:04 +01:00
|
|
|
/** @var array of quiz_section rows. */
|
|
|
|
protected $sections = null;
|
|
|
|
/** @var quiz_access_manager the access manager for this quiz. */
|
2008-06-27 18:04:48 +00:00
|
|
|
protected $accessmanager = null;
|
2015-04-02 15:23:04 +01:00
|
|
|
/** @var bool whether the current user has capability mod/quiz:preview. */
|
2008-06-30 16:56:49 +00:00
|
|
|
protected $ispreviewuser = null;
|
2008-06-27 18:04:48 +00:00
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Constructor =============================================================
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
|
|
|
* Constructor, assuming we already have the necessary data loaded.
|
|
|
|
*
|
|
|
|
* @param object $quiz the row from the quiz table.
|
|
|
|
* @param object $cm the course_module object for this quiz.
|
|
|
|
* @param object $course the row from the course table for the course we belong to.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param bool $getcontext intended for testing - stops the constructor getting the context.
|
2008-06-30 16:56:49 +00:00
|
|
|
*/
|
2011-03-23 16:22:25 +00:00
|
|
|
public function __construct($quiz, $cm, $course, $getcontext = true) {
|
2008-06-27 18:04:48 +00:00
|
|
|
$this->quiz = $quiz;
|
|
|
|
$this->cm = $cm;
|
2009-01-16 04:47:23 +00:00
|
|
|
$this->quiz->cmid = $this->cm->id;
|
2008-06-27 18:04:48 +00:00
|
|
|
$this->course = $course;
|
2008-09-23 07:18:15 +00:00
|
|
|
if ($getcontext && !empty($cm->id)) {
|
2012-07-24 16:55:49 +08:00
|
|
|
$this->context = context_module::instance($cm->id);
|
2008-09-23 07:18:15 +00:00
|
|
|
}
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
2010-03-08 16:01:38 +00:00
|
|
|
/**
|
|
|
|
* Static function to create a new quiz object for a specific user.
|
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $quizid the the quiz id.
|
|
|
|
* @param int $userid the the userid.
|
2010-07-28 15:06:28 +00:00
|
|
|
* @return quiz the new quiz object
|
2010-03-08 16:01:38 +00:00
|
|
|
*/
|
MDL-43089 quiz: improved interface for building quizzes
This commit is actually the joint work of Mahmoud Kassaei, Colin
Chambers and Tim Hunt from The Open University. We could only use one
persons name for the commit, and this time Colin gets the credit/blame.
The goal of this work was to increase usability, and also clean up
the page enough that it will be possible to add new features in future.
Display of mod/quiz/edit.php is now entirely generated by
mod_quiz\output\edit_renderer. This uses a helper class
mod_quiz\structure to provide details of the structure of the quiz, and
mod_quiz\repaginate to alter that structure. (Acutally, there are still
some modification methods on mod_quiz\structure. Expect that to be
cleaned up in future.)
The new code uses much more ajax, and there are new scripts
mod/quiz/edit_rest.php and mod/quiz/repaginate.php to handle this.
(Again, don't be surprised if those two scripts get merged in future.)
Also questionbank.ajax.php (which may, in future, be made more generic,
and moved into the core question bank code.)
Most of the new JavaScript code has intentionally copied the way things
are done when editing activities on the course page.
As a result of this, mod/quiz/editlib.php is now much shorter than it
was. (In future, expect the remaining code in here to move into
mod/quiz/classes.)
2013-12-05 16:18:24 +00:00
|
|
|
public static function create($quizid, $userid = null) {
|
2010-03-08 16:01:38 +00:00
|
|
|
global $DB;
|
|
|
|
|
2011-10-05 15:54:57 +01:00
|
|
|
$quiz = quiz_access_manager::load_quiz_and_settings($quizid);
|
2013-08-21 13:42:30 +08:00
|
|
|
$course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST);
|
2011-02-23 16:50:09 +00:00
|
|
|
$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id, false, MUST_EXIST);
|
2010-03-08 16:01:38 +00:00
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Update quiz with override information.
|
MDL-43089 quiz: improved interface for building quizzes
This commit is actually the joint work of Mahmoud Kassaei, Colin
Chambers and Tim Hunt from The Open University. We could only use one
persons name for the commit, and this time Colin gets the credit/blame.
The goal of this work was to increase usability, and also clean up
the page enough that it will be possible to add new features in future.
Display of mod/quiz/edit.php is now entirely generated by
mod_quiz\output\edit_renderer. This uses a helper class
mod_quiz\structure to provide details of the structure of the quiz, and
mod_quiz\repaginate to alter that structure. (Acutally, there are still
some modification methods on mod_quiz\structure. Expect that to be
cleaned up in future.)
The new code uses much more ajax, and there are new scripts
mod/quiz/edit_rest.php and mod/quiz/repaginate.php to handle this.
(Again, don't be surprised if those two scripts get merged in future.)
Also questionbank.ajax.php (which may, in future, be made more generic,
and moved into the core question bank code.)
Most of the new JavaScript code has intentionally copied the way things
are done when editing activities on the course page.
As a result of this, mod/quiz/editlib.php is now much shorter than it
was. (In future, expect the remaining code in here to move into
mod/quiz/classes.)
2013-12-05 16:18:24 +00:00
|
|
|
if ($userid) {
|
|
|
|
$quiz = quiz_update_effective_access($quiz, $userid);
|
|
|
|
}
|
2010-03-08 16:01:38 +00:00
|
|
|
|
|
|
|
return new quiz($quiz, $cm, $course);
|
|
|
|
}
|
|
|
|
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
/**
|
|
|
|
* Create a {@link quiz_attempt} for an attempt at this quiz.
|
|
|
|
* @param object $attemptdata row from the quiz_attempts table.
|
|
|
|
* @return quiz_attempt the new quiz_attempt object.
|
|
|
|
*/
|
|
|
|
public function create_attempt_object($attemptdata) {
|
|
|
|
return new quiz_attempt($attemptdata, $this->quiz, $this->cm, $this->course);
|
|
|
|
}
|
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Functions for loading more data =========================================
|
2008-07-08 16:33:47 +00:00
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
|
|
|
* Load just basic information about all the questions in this quiz.
|
|
|
|
*/
|
2008-07-08 16:33:47 +00:00
|
|
|
public function preload_questions() {
|
2014-01-22 18:19:31 +00:00
|
|
|
$this->questions = question_preload_questions(null,
|
|
|
|
'slot.maxmark, slot.id AS slotid, slot.slot, slot.page',
|
|
|
|
'{quiz_slots} slot ON slot.quizid = :quizid AND q.id = slot.questionid',
|
|
|
|
array('quizid' => $this->quiz->id), 'slot.slot');
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
2011-05-12 00:30:25 +01:00
|
|
|
/**
|
|
|
|
* Fully load some or all of the questions for this quiz. You must call
|
|
|
|
* {@link preload_questions()} first.
|
2008-06-27 18:04:48 +00:00
|
|
|
*
|
2008-07-08 16:33:47 +00:00
|
|
|
* @param array $questionids question ids of the questions to load. null for all.
|
2008-06-27 18:04:48 +00:00
|
|
|
*/
|
2008-07-08 16:33:47 +00:00
|
|
|
public function load_questions($questionids = null) {
|
2014-01-22 18:19:31 +00:00
|
|
|
if ($this->questions === null) {
|
|
|
|
throw new coding_exception('You must call preload_questions before calling load_questions.');
|
|
|
|
}
|
2008-07-08 16:33:47 +00:00
|
|
|
if (is_null($questionids)) {
|
2014-01-22 18:19:31 +00:00
|
|
|
$questionids = array_keys($this->questions);
|
2008-07-08 16:33:47 +00:00
|
|
|
}
|
|
|
|
$questionstoprocess = array();
|
|
|
|
foreach ($questionids as $id) {
|
2011-02-08 14:19:23 +00:00
|
|
|
if (array_key_exists($id, $this->questions)) {
|
|
|
|
$questionstoprocess[$id] = $this->questions[$id];
|
|
|
|
}
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
2011-02-12 00:40:43 +00:00
|
|
|
get_question_options($questionstoprocess);
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
MDL-43089 quiz: improved interface for building quizzes
This commit is actually the joint work of Mahmoud Kassaei, Colin
Chambers and Tim Hunt from The Open University. We could only use one
persons name for the commit, and this time Colin gets the credit/blame.
The goal of this work was to increase usability, and also clean up
the page enough that it will be possible to add new features in future.
Display of mod/quiz/edit.php is now entirely generated by
mod_quiz\output\edit_renderer. This uses a helper class
mod_quiz\structure to provide details of the structure of the quiz, and
mod_quiz\repaginate to alter that structure. (Acutally, there are still
some modification methods on mod_quiz\structure. Expect that to be
cleaned up in future.)
The new code uses much more ajax, and there are new scripts
mod/quiz/edit_rest.php and mod/quiz/repaginate.php to handle this.
(Again, don't be surprised if those two scripts get merged in future.)
Also questionbank.ajax.php (which may, in future, be made more generic,
and moved into the core question bank code.)
Most of the new JavaScript code has intentionally copied the way things
are done when editing activities on the course page.
As a result of this, mod/quiz/editlib.php is now much shorter than it
was. (In future, expect the remaining code in here to move into
mod/quiz/classes.)
2013-12-05 16:18:24 +00:00
|
|
|
/**
|
|
|
|
* Get an instance of the {@link \mod_quiz\structure} class for this quiz.
|
|
|
|
* @return \mod_quiz\structure describes the questions in the quiz.
|
|
|
|
*/
|
|
|
|
public function get_structure() {
|
|
|
|
return \mod_quiz\structure::create_for_quiz($this);
|
|
|
|
}
|
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Simple getters ==========================================================
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int the course id. */
|
2008-06-27 18:04:48 +00:00
|
|
|
public function get_courseid() {
|
|
|
|
return $this->course->id;
|
|
|
|
}
|
|
|
|
|
2008-07-08 16:33:47 +00:00
|
|
|
/** @return object the row of the course table. */
|
|
|
|
public function get_course() {
|
|
|
|
return $this->course;
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int the quiz id. */
|
2008-06-27 18:04:48 +00:00
|
|
|
public function get_quizid() {
|
|
|
|
return $this->quiz->id;
|
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/** @return object the row of the quiz table. */
|
2008-06-27 18:04:48 +00:00
|
|
|
public function get_quiz() {
|
|
|
|
return $this->quiz;
|
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/** @return string the name of this quiz. */
|
2008-06-27 18:04:48 +00:00
|
|
|
public function get_quiz_name() {
|
|
|
|
return $this->quiz->name;
|
|
|
|
}
|
|
|
|
|
2012-01-20 16:52:17 -08:00
|
|
|
/** @return int the quiz navigation method. */
|
|
|
|
public function get_navigation_method() {
|
|
|
|
return $this->quiz->navmethod;
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int the number of attempts allowed at this quiz (0 = infinite). */
|
2008-07-10 17:23:56 +00:00
|
|
|
public function get_num_attempts_allowed() {
|
|
|
|
return $this->quiz->attempts;
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int the course_module id. */
|
2008-06-27 18:04:48 +00:00
|
|
|
public function get_cmid() {
|
|
|
|
return $this->cm->id;
|
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/** @return object the course_module object. */
|
2008-06-27 18:04:48 +00:00
|
|
|
public function get_cm() {
|
|
|
|
return $this->cm;
|
|
|
|
}
|
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
/** @return object the module context for this quiz. */
|
|
|
|
public function get_context() {
|
|
|
|
return $this->context;
|
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
2011-02-23 16:25:25 +00:00
|
|
|
* @return bool wether the current user is someone who previews the quiz,
|
2008-06-30 16:56:49 +00:00
|
|
|
* rather than attempting it.
|
|
|
|
*/
|
|
|
|
public function is_preview_user() {
|
|
|
|
if (is_null($this->ispreviewuser)) {
|
|
|
|
$this->ispreviewuser = has_capability('mod/quiz:preview', $this->context);
|
|
|
|
}
|
|
|
|
return $this->ispreviewuser;
|
|
|
|
}
|
|
|
|
|
2008-07-08 16:33:47 +00:00
|
|
|
/**
|
2011-02-08 14:19:23 +00:00
|
|
|
* @return whether any questions have been added to this quiz.
|
2008-07-08 16:33:47 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function has_questions() {
|
2014-01-22 18:19:31 +00:00
|
|
|
if ($this->questions === null) {
|
|
|
|
$this->preload_questions();
|
|
|
|
}
|
|
|
|
return !empty($this->questions);
|
2008-07-08 16:33:47 +00:00
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $id the question id.
|
2008-06-30 16:56:49 +00:00
|
|
|
* @return object the question object with that id.
|
|
|
|
*/
|
2008-06-27 18:04:48 +00:00
|
|
|
public function get_question($id) {
|
|
|
|
return $this->questions[$id];
|
|
|
|
}
|
|
|
|
|
2008-07-08 16:33:47 +00:00
|
|
|
/**
|
|
|
|
* @param array $questionids question ids of the questions to load. null for all.
|
|
|
|
*/
|
|
|
|
public function get_questions($questionids = null) {
|
|
|
|
if (is_null($questionids)) {
|
2014-01-22 18:19:31 +00:00
|
|
|
$questionids = array_keys($this->questions);
|
2008-07-08 16:33:47 +00:00
|
|
|
}
|
|
|
|
$questions = array();
|
|
|
|
foreach ($questionids as $id) {
|
2011-02-08 14:19:23 +00:00
|
|
|
if (!array_key_exists($id, $this->questions)) {
|
|
|
|
throw new moodle_exception('cannotstartmissingquestion', 'quiz', $this->view_url());
|
|
|
|
}
|
2008-07-08 16:33:47 +00:00
|
|
|
$questions[$id] = $this->questions[$id];
|
|
|
|
$this->ensure_question_loaded($id);
|
|
|
|
}
|
|
|
|
return $questions;
|
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
2015-04-02 15:23:04 +01:00
|
|
|
* Get all the sections in this quiz.
|
|
|
|
* @return array 0, 1, 2, ... => quiz_sections row from the database.
|
|
|
|
*/
|
|
|
|
public function get_sections() {
|
|
|
|
global $DB;
|
|
|
|
if ($this->sections === null) {
|
|
|
|
$this->sections = array_values($DB->get_records('quiz_sections',
|
|
|
|
array('quizid' => $this->get_quizid()), 'firstslot'));
|
|
|
|
}
|
|
|
|
return $this->sections;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return quiz_access_manager and instance of the quiz_access_manager class
|
|
|
|
* for this quiz at this time.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $timenow the current time as a unix timestamp.
|
2011-05-12 00:30:25 +01:00
|
|
|
* @return quiz_access_manager and instance of the quiz_access_manager class
|
|
|
|
* for this quiz at this time.
|
2008-06-30 16:56:49 +00:00
|
|
|
*/
|
2008-06-27 18:04:48 +00:00
|
|
|
public function get_access_manager($timenow) {
|
|
|
|
if (is_null($this->accessmanager)) {
|
2008-07-08 16:33:47 +00:00
|
|
|
$this->accessmanager = new quiz_access_manager($this, $timenow,
|
2011-05-12 00:30:25 +01:00
|
|
|
has_capability('mod/quiz:ignoretimelimits', $this->context, null, false));
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
return $this->accessmanager;
|
|
|
|
}
|
|
|
|
|
2008-07-08 16:33:47 +00:00
|
|
|
/**
|
|
|
|
* Wrapper round the has_capability funciton that automatically passes in the quiz context.
|
|
|
|
*/
|
2011-05-12 00:30:25 +01:00
|
|
|
public function has_capability($capability, $userid = null, $doanything = true) {
|
2008-07-08 16:33:47 +00:00
|
|
|
return has_capability($capability, $this->context, $userid, $doanything);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wrapper round the require_capability funciton that automatically passes in the quiz context.
|
|
|
|
*/
|
2011-05-12 00:30:25 +01:00
|
|
|
public function require_capability($capability, $userid = null, $doanything = true) {
|
2008-07-08 16:33:47 +00:00
|
|
|
return require_capability($capability, $this->context, $userid, $doanything);
|
|
|
|
}
|
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// URLs related to this attempt ============================================
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
|
|
|
* @return string the URL of this quiz's view page.
|
|
|
|
*/
|
2008-06-27 18:04:48 +00:00
|
|
|
public function view_url() {
|
|
|
|
global $CFG;
|
|
|
|
return $CFG->wwwroot . '/mod/quiz/view.php?id=' . $this->cm->id;
|
|
|
|
}
|
|
|
|
|
2008-07-08 16:33:47 +00:00
|
|
|
/**
|
|
|
|
* @return string the URL of this quiz's edit page.
|
|
|
|
*/
|
|
|
|
public function edit_url() {
|
|
|
|
global $CFG;
|
|
|
|
return $CFG->wwwroot . '/mod/quiz/edit.php?cmid=' . $this->cm->id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $attemptid the id of an attempt.
|
2011-06-22 21:07:40 +01:00
|
|
|
* @param int $page optional page number to go to in the attempt.
|
2008-07-08 16:33:47 +00:00
|
|
|
* @return string the URL of that attempt.
|
|
|
|
*/
|
2011-06-22 21:07:40 +01:00
|
|
|
public function attempt_url($attemptid, $page = 0) {
|
2008-07-08 16:33:47 +00:00
|
|
|
global $CFG;
|
2011-06-22 21:07:40 +01:00
|
|
|
$url = $CFG->wwwroot . '/mod/quiz/attempt.php?attempt=' . $attemptid;
|
|
|
|
if ($page) {
|
|
|
|
$url .= '&page=' . $page;
|
|
|
|
}
|
|
|
|
return $url;
|
2008-07-08 16:33:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string the URL of this quiz's edit page. Needs to be POSTed to with a cmid parameter.
|
|
|
|
*/
|
2011-06-22 21:07:40 +01:00
|
|
|
public function start_attempt_url($page = 0) {
|
|
|
|
$params = array('cmid' => $this->cm->id, 'sesskey' => sesskey());
|
|
|
|
if ($page) {
|
|
|
|
$params['page'] = $page;
|
|
|
|
}
|
|
|
|
return new moodle_url('/mod/quiz/startattempt.php', $params);
|
2008-07-08 16:33:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $attemptid the id of an attempt.
|
2008-07-08 16:33:47 +00:00
|
|
|
* @return string the URL of the review of that attempt.
|
|
|
|
*/
|
|
|
|
public function review_url($attemptid) {
|
2010-08-25 15:58:26 +00:00
|
|
|
return new moodle_url('/mod/quiz/review.php', array('attempt' => $attemptid));
|
2008-07-08 16:33:47 +00:00
|
|
|
}
|
|
|
|
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
/**
|
|
|
|
* @param int $attemptid the id of an attempt.
|
|
|
|
* @return string the URL of the review of that attempt.
|
|
|
|
*/
|
|
|
|
public function summary_url($attemptid) {
|
|
|
|
return new moodle_url('/mod/quiz/summary.php', array('attempt' => $attemptid));
|
|
|
|
}
|
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Bits of content =========================================================
|
2008-06-27 18:04:48 +00:00
|
|
|
|
2011-10-05 20:58:38 +01:00
|
|
|
/**
|
2014-06-22 21:30:04 +01:00
|
|
|
* @param bool $notused not used.
|
|
|
|
* @return string an empty string.
|
|
|
|
* @deprecated since 3.1. This sort of functionality is now entirely handled by quiz access rules.
|
2011-10-05 20:58:38 +01:00
|
|
|
*/
|
2014-06-22 21:30:04 +01:00
|
|
|
public function confirm_start_attempt_message($notused) {
|
|
|
|
debugging('confirm_start_attempt_message is deprecated. ' .
|
|
|
|
'This sort of functionality is now entirely handled by quiz access rules.');
|
2011-10-05 20:58:38 +01:00
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If $reviewoptions->attempt is false, meaning that students can't review this
|
|
|
|
* attempt at the moment, return an appropriate string explaining why.
|
|
|
|
*
|
|
|
|
* @param int $when One of the mod_quiz_display_options::DURING,
|
|
|
|
* IMMEDIATELY_AFTER, LATER_WHILE_OPEN or AFTER_CLOSE constants.
|
|
|
|
* @param bool $short if true, return a shorter string.
|
|
|
|
* @return string an appropraite message.
|
|
|
|
*/
|
|
|
|
public function cannot_review_message($when, $short = false) {
|
|
|
|
|
|
|
|
if ($short) {
|
|
|
|
$langstrsuffix = 'short';
|
|
|
|
$dateformat = get_string('strftimedatetimeshort', 'langconfig');
|
|
|
|
} else {
|
|
|
|
$langstrsuffix = '';
|
|
|
|
$dateformat = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($when == mod_quiz_display_options::DURING ||
|
|
|
|
$when == mod_quiz_display_options::IMMEDIATELY_AFTER) {
|
|
|
|
return '';
|
|
|
|
} else if ($when == mod_quiz_display_options::LATER_WHILE_OPEN && $this->quiz->timeclose &&
|
|
|
|
$this->quiz->reviewattempt & mod_quiz_display_options::AFTER_CLOSE) {
|
|
|
|
return get_string('noreviewuntil' . $langstrsuffix, 'quiz',
|
|
|
|
userdate($this->quiz->timeclose, $dateformat));
|
|
|
|
} else {
|
|
|
|
return get_string('noreview' . $langstrsuffix, 'quiz');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
|
|
|
* @param string $title the name of this particular quiz page.
|
|
|
|
* @return array the data that needs to be sent to print_header_simple as the $navigation
|
|
|
|
* parameter.
|
|
|
|
*/
|
2008-06-27 18:04:48 +00:00
|
|
|
public function navigation($title) {
|
2009-09-07 02:11:54 +00:00
|
|
|
global $PAGE;
|
|
|
|
$PAGE->navbar->add($title);
|
|
|
|
return '';
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Private methods =========================================================
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
2011-05-12 00:30:25 +01:00
|
|
|
* Check that the definition of a particular question is loaded, and if not throw an exception.
|
|
|
|
* @param $id a questionid.
|
2009-07-23 09:20:33 +00:00
|
|
|
*/
|
2008-07-08 16:33:47 +00:00
|
|
|
protected function ensure_question_loaded($id) {
|
|
|
|
if (isset($this->questions[$id]->_partiallyloaded)) {
|
2008-06-27 18:04:48 +00:00
|
|
|
throw new moodle_quiz_exception($this, 'questionnotloaded', $id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
|
|
|
* This class extends the quiz class to hold data about the state of a particular attempt,
|
|
|
|
* in addition to the data about the quiz.
|
2009-07-23 09:20:33 +00:00
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @copyright 2008 Tim Hunt
|
|
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
|
* @since Moodle 2.0
|
2008-06-30 16:56:49 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
class quiz_attempt {
|
2011-11-14 15:35:46 +00:00
|
|
|
|
|
|
|
/** @var string to identify the in progress state. */
|
|
|
|
const IN_PROGRESS = 'inprogress';
|
|
|
|
/** @var string to identify the overdue state. */
|
|
|
|
const OVERDUE = 'overdue';
|
|
|
|
/** @var string to identify the finished state. */
|
|
|
|
const FINISHED = 'finished';
|
|
|
|
/** @var string to identify the abandoned state. */
|
|
|
|
const ABANDONED = 'abandoned';
|
|
|
|
|
2014-06-22 11:03:03 +01:00
|
|
|
/** @var int maximum number of slots in the quiz for the review page to default to show all. */
|
|
|
|
const MAX_SLOTS_FOR_DEFAULT_REVIEW_SHOW_ALL = 50;
|
|
|
|
|
2015-02-26 11:53:00 +00:00
|
|
|
/** @var quiz object containing the quiz settings. */
|
2011-02-08 14:19:23 +00:00
|
|
|
protected $quizobj;
|
2015-02-26 11:53:00 +00:00
|
|
|
|
|
|
|
/** @var stdClass the quiz_attempts row. */
|
2008-06-27 18:04:48 +00:00
|
|
|
protected $attempt;
|
|
|
|
|
2012-10-19 03:25:57 +08:00
|
|
|
/** @var question_usage_by_activity the question usage for this quiz attempt. */
|
2011-11-14 15:35:46 +00:00
|
|
|
protected $quba;
|
2012-10-19 03:25:57 +08:00
|
|
|
|
2015-04-02 15:23:04 +01:00
|
|
|
/**
|
|
|
|
* @var array of slot information. These objects contain ->slot (int),
|
|
|
|
* ->requireprevious (bool), ->questionids (int) the original question for random questions,
|
|
|
|
* ->firstinsection (bool), ->section (stdClass from $this->sections).
|
|
|
|
* This does not contain page - get that from {@link get_question_page()} -
|
|
|
|
* or maxmark - get that from $this->quba.
|
|
|
|
*/
|
2015-02-26 11:53:00 +00:00
|
|
|
protected $slots;
|
|
|
|
|
2015-04-02 15:23:04 +01:00
|
|
|
/** @var array of quiz_sections rows, with a ->lastslot field added. */
|
|
|
|
protected $sections;
|
|
|
|
|
2012-10-19 03:25:57 +08:00
|
|
|
/** @var array page no => array of slot numbers on the page in order. */
|
|
|
|
protected $pagelayout;
|
|
|
|
|
|
|
|
/** @var array slot => displayed question number for this slot. (E.g. 1, 2, 3 or 'i'.) */
|
|
|
|
protected $questionnumbers;
|
|
|
|
|
|
|
|
/** @var array slot => page number for this slot. */
|
|
|
|
protected $questionpages;
|
|
|
|
|
|
|
|
/** @var mod_quiz_display_options cache for the appropriate review options. */
|
2008-07-08 17:47:57 +00:00
|
|
|
protected $reviewoptions = null;
|
2008-06-27 18:04:48 +00:00
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Constructor =============================================================
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
2010-03-08 16:01:38 +00:00
|
|
|
* Constructor assuming we already have the necessary data loaded.
|
|
|
|
*
|
|
|
|
* @param object $attempt the row of the quiz_attempts table.
|
|
|
|
* @param object $quiz the quiz object for this attempt and user.
|
|
|
|
* @param object $cm the course_module object for this quiz.
|
|
|
|
* @param object $course the row from the course table for the course we belong to.
|
2011-11-09 13:56:03 +00:00
|
|
|
* @param bool $loadquestions (optional) if true, the default, load all the details
|
|
|
|
* of the state of each question. Else just set up the basic details of the attempt.
|
2010-03-08 16:01:38 +00:00
|
|
|
*/
|
2011-11-09 13:56:03 +00:00
|
|
|
public function __construct($attempt, $quiz, $cm, $course, $loadquestions = true) {
|
2015-02-26 11:53:00 +00:00
|
|
|
global $DB;
|
|
|
|
|
2010-03-08 16:01:38 +00:00
|
|
|
$this->attempt = $attempt;
|
2011-02-08 14:19:23 +00:00
|
|
|
$this->quizobj = new quiz($quiz, $cm, $course);
|
2011-11-09 13:56:03 +00:00
|
|
|
|
|
|
|
if (!$loadquestions) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
$this->quba = question_engine::load_questions_usage_by_activity($this->attempt->uniqueid);
|
2015-02-26 11:53:00 +00:00
|
|
|
$this->slots = $DB->get_records('quiz_slots',
|
|
|
|
array('quizid' => $this->get_quizid()), 'slot',
|
2015-04-02 15:23:04 +01:00
|
|
|
'slot, requireprevious, questionid');
|
|
|
|
$this->sections = array_values($DB->get_records('quiz_sections',
|
|
|
|
array('quizid' => $this->get_quizid()), 'firstslot'));
|
2015-02-26 11:53:00 +00:00
|
|
|
|
2015-04-02 15:23:04 +01:00
|
|
|
$this->link_sections_and_slots();
|
2011-02-08 14:19:23 +00:00
|
|
|
$this->determine_layout();
|
|
|
|
$this->number_questions();
|
2010-03-08 16:01:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-02-09 20:33:51 +00:00
|
|
|
* Used by {create()} and {create_from_usage_id()}.
|
|
|
|
* @param array $conditions passed to $DB->get_record('quiz_attempts', $conditions).
|
2008-06-30 16:56:49 +00:00
|
|
|
*/
|
2011-03-23 16:22:25 +00:00
|
|
|
protected static function create_helper($conditions) {
|
2008-06-27 18:04:48 +00:00
|
|
|
global $DB;
|
2010-03-08 16:01:38 +00:00
|
|
|
|
2011-02-23 16:50:09 +00:00
|
|
|
$attempt = $DB->get_record('quiz_attempts', $conditions, '*', MUST_EXIST);
|
2011-10-05 15:54:57 +01:00
|
|
|
$quiz = quiz_access_manager::load_quiz_and_settings($attempt->quiz);
|
2013-08-21 13:42:30 +08:00
|
|
|
$course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST);
|
2011-02-23 16:50:09 +00:00
|
|
|
$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id, false, MUST_EXIST);
|
2011-02-08 14:19:23 +00:00
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Update quiz with override information.
|
2010-03-08 16:01:38 +00:00
|
|
|
$quiz = quiz_update_effective_access($quiz, $attempt->userid);
|
|
|
|
|
|
|
|
return new quiz_attempt($attempt, $quiz, $cm, $course);
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
2011-02-09 20:33:51 +00:00
|
|
|
/**
|
|
|
|
* Static function to create a new quiz_attempt object given an attemptid.
|
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $attemptid the attempt id.
|
2011-02-09 20:33:51 +00:00
|
|
|
* @return quiz_attempt the new quiz_attempt object
|
|
|
|
*/
|
2011-03-23 16:22:25 +00:00
|
|
|
public static function create($attemptid) {
|
2011-02-09 20:33:51 +00:00
|
|
|
return self::create_helper(array('id' => $attemptid));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Static function to create a new quiz_attempt object given a usage id.
|
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $usageid the attempt usage id.
|
2011-02-09 20:33:51 +00:00
|
|
|
* @return quiz_attempt the new quiz_attempt object
|
|
|
|
*/
|
2011-03-23 16:22:25 +00:00
|
|
|
public static function create_from_usage_id($usageid) {
|
2011-02-09 20:33:51 +00:00
|
|
|
return self::create_helper(array('uniqueid' => $usageid));
|
|
|
|
}
|
|
|
|
|
2011-11-16 16:37:14 +00:00
|
|
|
/**
|
|
|
|
* @param string $state one of the state constants like IN_PROGRESS.
|
|
|
|
* @return string the human-readable state name.
|
|
|
|
*/
|
|
|
|
public static function state_name($state) {
|
2012-04-24 15:01:12 +01:00
|
|
|
return quiz_attempt_state_name($state);
|
2011-11-16 16:37:14 +00:00
|
|
|
}
|
|
|
|
|
2015-04-02 15:23:04 +01:00
|
|
|
/**
|
|
|
|
* Let each slot know which section it is part of.
|
|
|
|
*/
|
|
|
|
protected function link_sections_and_slots() {
|
|
|
|
foreach ($this->sections as $i => $section) {
|
|
|
|
if (isset($this->sections[$i + 1])) {
|
|
|
|
$section->lastslot = $this->sections[$i + 1]->firstslot - 1;
|
|
|
|
} else {
|
|
|
|
$section->lastslot = count($this->slots);
|
|
|
|
}
|
|
|
|
for ($slot = $section->firstslot; $slot <= $section->lastslot; $slot += 1) {
|
|
|
|
$this->slots[$slot]->section = $section;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-22 11:03:03 +01:00
|
|
|
/**
|
|
|
|
* Parse attempt->layout to populate the other arrays the represent the layout.
|
|
|
|
*/
|
|
|
|
protected function determine_layout() {
|
2011-02-08 14:19:23 +00:00
|
|
|
$this->pagelayout = array();
|
|
|
|
|
|
|
|
// Break up the layout string into pages.
|
2014-01-22 18:19:31 +00:00
|
|
|
$pagelayouts = explode(',0', $this->attempt->layout);
|
2011-02-08 14:19:23 +00:00
|
|
|
|
|
|
|
// Strip off any empty last page (normally there is one).
|
|
|
|
if (end($pagelayouts) == '') {
|
|
|
|
array_pop($pagelayouts);
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
2011-02-08 14:19:23 +00:00
|
|
|
|
|
|
|
// File the ids into the arrays.
|
2015-04-02 15:23:04 +01:00
|
|
|
// Tracking which is the first slot in each section in this attempt is
|
|
|
|
// trickier than you might guess, since the slots in this section
|
|
|
|
// may be shuffled, so $section->firstslot (the lowest numbered slot in
|
|
|
|
// the section) may not be the first one.
|
|
|
|
$unseensections = $this->sections;
|
2011-02-08 14:19:23 +00:00
|
|
|
$this->pagelayout = array();
|
|
|
|
foreach ($pagelayouts as $page => $pagelayout) {
|
|
|
|
$pagelayout = trim($pagelayout, ',');
|
|
|
|
if ($pagelayout == '') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$this->pagelayout[$page] = explode(',', $pagelayout);
|
2015-04-02 15:23:04 +01:00
|
|
|
foreach ($this->pagelayout[$page] as $slot) {
|
|
|
|
$sectionkey = array_search($this->slots[$slot]->section, $unseensections);
|
|
|
|
if ($sectionkey !== false) {
|
|
|
|
$this->slots[$slot]->firstinsection = true;
|
|
|
|
unset($unseensections[$sectionkey]);
|
|
|
|
} else {
|
|
|
|
$this->slots[$slot]->firstinsection = false;
|
|
|
|
}
|
|
|
|
}
|
2008-07-08 16:33:47 +00:00
|
|
|
}
|
2011-02-08 14:19:23 +00:00
|
|
|
}
|
|
|
|
|
2014-06-22 11:03:03 +01:00
|
|
|
/**
|
|
|
|
* Work out the number to display for each question/slot.
|
|
|
|
*/
|
|
|
|
protected function number_questions() {
|
2011-02-08 14:19:23 +00:00
|
|
|
$number = 1;
|
|
|
|
foreach ($this->pagelayout as $page => $slots) {
|
|
|
|
foreach ($slots as $slot) {
|
2014-06-22 11:03:03 +01:00
|
|
|
if ($length = $this->is_real_question($slot)) {
|
2012-10-19 03:25:57 +08:00
|
|
|
$this->questionnumbers[$slot] = $number;
|
2014-06-22 11:03:03 +01:00
|
|
|
$number += $length;
|
2011-02-08 14:19:23 +00:00
|
|
|
} else {
|
2012-10-19 03:25:57 +08:00
|
|
|
$this->questionnumbers[$slot] = get_string('infoshort', 'quiz');
|
2011-02-08 14:19:23 +00:00
|
|
|
}
|
2012-10-19 03:25:57 +08:00
|
|
|
$this->questionpages[$slot] = $page;
|
2011-02-08 14:19:23 +00:00
|
|
|
}
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
2008-09-04 06:37:43 +00:00
|
|
|
}
|
|
|
|
|
2012-07-27 11:42:03 +01:00
|
|
|
/**
|
|
|
|
* If the given page number is out of range (before the first page, or after
|
|
|
|
* the last page, chnage it to be within range).
|
|
|
|
* @param int $page the requested page number.
|
|
|
|
* @return int a safe page number to use.
|
|
|
|
*/
|
|
|
|
public function force_page_number_into_range($page) {
|
|
|
|
return min(max($page, 0), count($this->pagelayout) - 1);
|
|
|
|
}
|
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Simple getters ==========================================================
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_quiz() {
|
|
|
|
return $this->quizobj->get_quiz();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function get_quizobj() {
|
|
|
|
return $this->quizobj;
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int the course id. */
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_courseid() {
|
|
|
|
return $this->quizobj->get_courseid();
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int the course id. */
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_course() {
|
|
|
|
return $this->quizobj->get_course();
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int the quiz id. */
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_quizid() {
|
|
|
|
return $this->quizobj->get_quizid();
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @return string the name of this quiz. */
|
|
|
|
public function get_quiz_name() {
|
|
|
|
return $this->quizobj->get_quiz_name();
|
|
|
|
}
|
|
|
|
|
2012-01-20 16:52:17 -08:00
|
|
|
/** @return int the quiz navigation method. */
|
|
|
|
public function get_navigation_method() {
|
|
|
|
return $this->quizobj->get_navigation_method();
|
|
|
|
}
|
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
/** @return object the course_module object. */
|
|
|
|
public function get_cm() {
|
|
|
|
return $this->quizobj->get_cm();
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @return object the course_module object. */
|
|
|
|
public function get_cmid() {
|
|
|
|
return $this->quizobj->get_cmid();
|
|
|
|
}
|
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
2011-02-23 16:25:25 +00:00
|
|
|
* @return bool wether the current user is someone who previews the quiz,
|
2011-02-08 14:19:23 +00:00
|
|
|
* rather than attempting it.
|
2009-07-23 09:20:33 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function is_preview_user() {
|
|
|
|
return $this->quizobj->is_preview_user();
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int the number of attempts allowed at this quiz (0 = infinite). */
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_num_attempts_allowed() {
|
|
|
|
return $this->quizobj->get_num_attempts_allowed();
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int number fo pages in this quiz. */
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_num_pages() {
|
|
|
|
return count($this->pagelayout);
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $timenow the current time as a unix timestamp.
|
2011-05-12 00:30:25 +01:00
|
|
|
* @return quiz_access_manager and instance of the quiz_access_manager class
|
|
|
|
* for this quiz at this time.
|
2009-07-23 09:20:33 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_access_manager($timenow) {
|
|
|
|
return $this->quizobj->get_access_manager($timenow);
|
2008-07-15 15:30:39 +00:00
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int the attempt id. */
|
2008-06-27 18:04:48 +00:00
|
|
|
public function get_attemptid() {
|
|
|
|
return $this->attempt->id;
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int the attempt unique id. */
|
2008-09-18 07:39:10 +00:00
|
|
|
public function get_uniqueid() {
|
|
|
|
return $this->attempt->uniqueid;
|
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/** @return object the row from the quiz_attempts table. */
|
2008-06-27 18:04:48 +00:00
|
|
|
public function get_attempt() {
|
|
|
|
return $this->attempt;
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int the number of this attemp (is it this user's first, second, ... attempt). */
|
2008-07-08 16:33:47 +00:00
|
|
|
public function get_attempt_number() {
|
|
|
|
return $this->attempt->attempt;
|
|
|
|
}
|
|
|
|
|
2012-05-03 12:16:43 +01:00
|
|
|
/** @return string one of the quiz_attempt::IN_PROGRESS, FINISHED, OVERDUE or ABANDONED constants. */
|
2011-11-14 15:35:46 +00:00
|
|
|
public function get_state() {
|
|
|
|
return $this->attempt->state;
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return int the id of the user this attempt belongs to. */
|
2008-06-27 18:04:48 +00:00
|
|
|
public function get_userid() {
|
|
|
|
return $this->attempt->userid;
|
|
|
|
}
|
2012-01-16 13:41:12 +08:00
|
|
|
|
2012-01-08 14:14:09 -08:00
|
|
|
/** @return int the current page of the attempt. */
|
|
|
|
public function get_currentpage() {
|
|
|
|
return $this->attempt->currentpage;
|
|
|
|
}
|
2008-06-27 18:04:48 +00:00
|
|
|
|
2011-11-14 15:35:46 +00:00
|
|
|
public function get_sum_marks() {
|
|
|
|
return $this->attempt->sumgrades;
|
|
|
|
}
|
|
|
|
|
2011-05-12 00:30:25 +01:00
|
|
|
/**
|
|
|
|
* @return bool whether this attempt has been finished (true) or is still
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
* in progress (false). Be warned that this is not just state == self::FINISHED,
|
|
|
|
* it also includes self::ABANDONED.
|
2011-05-12 00:30:25 +01:00
|
|
|
*/
|
2008-06-27 18:04:48 +00:00
|
|
|
public function is_finished() {
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
return $this->attempt->state == self::FINISHED || $this->attempt->state == self::ABANDONED;
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
/** @return bool whether this attempt is a preview attempt. */
|
2008-07-08 17:47:57 +00:00
|
|
|
public function is_preview() {
|
|
|
|
return $this->attempt->preview;
|
|
|
|
}
|
|
|
|
|
2008-07-15 15:30:39 +00:00
|
|
|
/**
|
2015-08-04 17:26:12 +01:00
|
|
|
* Is this someone dealing with their own attempt or preview?
|
2008-08-29 07:29:35 +00:00
|
|
|
*
|
2015-08-04 17:26:12 +01:00
|
|
|
* @return bool true => own attempt/preview. false => reviewing someone elses.
|
2008-07-15 15:30:39 +00:00
|
|
|
*/
|
|
|
|
public function is_own_attempt() {
|
|
|
|
global $USER;
|
2015-08-04 17:26:12 +01:00
|
|
|
return $this->attempt->userid == $USER->id;
|
2008-07-15 15:30:39 +00:00
|
|
|
}
|
|
|
|
|
2011-04-15 18:26:35 +01:00
|
|
|
/**
|
|
|
|
* @return bool whether this attempt is a preview belonging to the current user.
|
|
|
|
*/
|
|
|
|
public function is_own_preview() {
|
|
|
|
global $USER;
|
2015-08-04 17:26:12 +01:00
|
|
|
return $this->is_own_attempt() &&
|
2011-04-15 18:26:35 +01:00
|
|
|
$this->is_preview_user() && $this->attempt->preview;
|
|
|
|
}
|
|
|
|
|
2011-02-18 16:32:49 +00:00
|
|
|
/**
|
|
|
|
* Is the current user allowed to review this attempt. This applies when
|
|
|
|
* {@link is_own_attempt()} returns false.
|
|
|
|
* @return bool whether the review should be allowed.
|
|
|
|
*/
|
|
|
|
public function is_review_allowed() {
|
|
|
|
if (!$this->has_capability('mod/quiz:viewreports')) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$cm = $this->get_cm();
|
|
|
|
if ($this->has_capability('moodle/site:accessallgroups') ||
|
|
|
|
groups_get_activity_groupmode($cm) != SEPARATEGROUPS) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the users have at least one group in common.
|
|
|
|
$teachersgroups = groups_get_activity_allowed_groups($cm);
|
2011-05-12 00:30:25 +01:00
|
|
|
$studentsgroups = groups_get_all_groups(
|
|
|
|
$cm->course, $this->attempt->userid, $cm->groupingid);
|
2011-02-18 16:32:49 +00:00
|
|
|
return $teachersgroups && $studentsgroups &&
|
|
|
|
array_intersect(array_keys($teachersgroups), array_keys($studentsgroups));
|
|
|
|
}
|
|
|
|
|
2014-10-15 16:31:25 +01:00
|
|
|
/**
|
|
|
|
* Has the student, in this attempt, engaged with the quiz in a non-trivial way?
|
|
|
|
* That is, is there any question worth a non-zero number of marks, where
|
|
|
|
* the student has made some response that we have saved?
|
|
|
|
* @return bool true if we have saved a response for at least one graded question.
|
|
|
|
*/
|
|
|
|
public function has_response_to_at_least_one_graded_question() {
|
|
|
|
foreach ($this->quba->get_attempt_iterator() as $qa) {
|
|
|
|
if ($qa->get_max_mark() == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ($qa->get_num_steps() > 1) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-08-16 14:18:58 +01:00
|
|
|
/**
|
|
|
|
* Get extra summary information about this attempt.
|
|
|
|
*
|
|
|
|
* Some behaviours may be able to provide interesting summary information
|
|
|
|
* about the attempt as a whole, and this method provides access to that data.
|
|
|
|
* To see how this works, try setting a quiz to one of the CBM behaviours,
|
|
|
|
* and then look at the extra information displayed at the top of the quiz
|
|
|
|
* review page once you have sumitted an attempt.
|
|
|
|
*
|
|
|
|
* In the return value, the array keys are identifiers of the form
|
|
|
|
* qbehaviour_behaviourname_meaningfullkey. For qbehaviour_deferredcbm_highsummary.
|
|
|
|
* The values are arrays with two items, title and content. Each of these
|
|
|
|
* will be either a string, or a renderable.
|
|
|
|
*
|
2014-10-15 16:31:25 +01:00
|
|
|
* @param question_display_options $options the display options for this quiz attempt at this time.
|
2012-08-16 14:18:58 +01:00
|
|
|
* @return array as described above.
|
|
|
|
*/
|
|
|
|
public function get_additional_summary_data(question_display_options $options) {
|
|
|
|
return $this->quba->get_summary_information($options);
|
|
|
|
}
|
|
|
|
|
2011-02-09 16:56:44 +00:00
|
|
|
/**
|
|
|
|
* Get the overall feedback corresponding to a particular mark.
|
|
|
|
* @param $grade a particular grade.
|
|
|
|
*/
|
|
|
|
public function get_overall_feedback($grade) {
|
|
|
|
return quiz_feedback_for_grade($grade, $this->get_quiz(),
|
2011-02-11 18:47:08 +00:00
|
|
|
$this->quizobj->get_context());
|
2011-02-09 16:56:44 +00:00
|
|
|
}
|
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
/**
|
|
|
|
* Wrapper round the has_capability funciton that automatically passes in the quiz context.
|
|
|
|
*/
|
2011-05-12 00:30:25 +01:00
|
|
|
public function has_capability($capability, $userid = null, $doanything = true) {
|
2011-02-08 14:19:23 +00:00
|
|
|
return $this->quizobj->has_capability($capability, $userid, $doanything);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wrapper round the require_capability funciton that automatically passes in the quiz context.
|
|
|
|
*/
|
2011-05-12 00:30:25 +01:00
|
|
|
public function require_capability($capability, $userid = null, $doanything = true) {
|
2011-02-08 14:19:23 +00:00
|
|
|
return $this->quizobj->require_capability($capability, $userid, $doanything);
|
|
|
|
}
|
|
|
|
|
2009-01-14 07:08:02 +00:00
|
|
|
/**
|
|
|
|
* Check the appropriate capability to see whether this user may review their own attempt.
|
|
|
|
* If not, prints an error.
|
|
|
|
*/
|
|
|
|
public function check_review_capability() {
|
2014-03-28 18:37:21 +00:00
|
|
|
if ($this->get_attempt_state() == mod_quiz_display_options::IMMEDIATELY_AFTER) {
|
|
|
|
$capability = 'mod/quiz:attempt';
|
|
|
|
} else {
|
|
|
|
$capability = 'mod/quiz:reviewmyattempts';
|
2009-01-14 07:08:02 +00:00
|
|
|
}
|
2014-03-28 18:37:21 +00:00
|
|
|
|
|
|
|
// These next tests are in a slighly funny order. The point is that the
|
|
|
|
// common and most performance-critical case is students attempting a quiz
|
|
|
|
// so we want to check that permisison first.
|
|
|
|
|
|
|
|
if ($this->has_capability($capability)) {
|
|
|
|
// User has the permission that lets you do the quiz as a student. Fine.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->has_capability('mod/quiz:viewreports') ||
|
|
|
|
$this->has_capability('mod/quiz:preview')) {
|
|
|
|
// User has the permission that lets teachers review. Fine.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// They should not be here. Trigger the standard no-permission error
|
|
|
|
// but using the name of the student capability.
|
|
|
|
// We know this will fail. We just want the stadard exception thown.
|
|
|
|
$this->require_capability($capability);
|
2009-01-14 07:08:02 +00:00
|
|
|
}
|
|
|
|
|
2012-01-20 16:52:17 -08:00
|
|
|
/**
|
|
|
|
* Checks whether a user may navigate to a particular slot
|
|
|
|
*/
|
|
|
|
public function can_navigate_to($slot) {
|
|
|
|
switch ($this->get_navigation_method()) {
|
|
|
|
case QUIZ_NAVMETHOD_FREE:
|
|
|
|
return true;
|
|
|
|
break;
|
|
|
|
case QUIZ_NAVMETHOD_SEQ:
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
2011-02-23 16:25:25 +00:00
|
|
|
* @return int one of the mod_quiz_display_options::DURING,
|
2011-02-08 14:19:23 +00:00
|
|
|
* IMMEDIATELY_AFTER, LATER_WHILE_OPEN or AFTER_CLOSE constants.
|
2009-07-23 09:20:33 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_attempt_state() {
|
|
|
|
return quiz_attempt_state($this->get_quiz(), $this->attempt);
|
2008-07-08 16:33:47 +00:00
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
2011-02-08 14:19:23 +00:00
|
|
|
* Wrapper that the correct mod_quiz_display_options for this quiz at the
|
|
|
|
* moment.
|
2008-06-30 16:56:49 +00:00
|
|
|
*
|
2011-02-08 14:19:23 +00:00
|
|
|
* @return question_display_options the render options for this user on this attempt.
|
2008-06-30 16:56:49 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_display_options($reviewing) {
|
|
|
|
if ($reviewing) {
|
|
|
|
if (is_null($this->reviewoptions)) {
|
2011-02-09 19:07:57 +00:00
|
|
|
$this->reviewoptions = quiz_get_review_options($this->get_quiz(),
|
2011-02-08 14:19:23 +00:00
|
|
|
$this->attempt, $this->quizobj->get_context());
|
2015-08-04 17:26:12 +01:00
|
|
|
if ($this->is_own_preview()) {
|
|
|
|
// It should always be possible for a teacher to review their
|
|
|
|
// own preview irrespective of the review options settings.
|
|
|
|
$this->reviewoptions->attempt = true;
|
|
|
|
}
|
2011-02-08 14:19:23 +00:00
|
|
|
}
|
|
|
|
return $this->reviewoptions;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
$options = mod_quiz_display_options::make_from_quiz($this->get_quiz(),
|
|
|
|
mod_quiz_display_options::DURING);
|
|
|
|
$options->flags = quiz_get_flag_option($this->attempt, $this->quizobj->get_context());
|
|
|
|
return $options;
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-22 21:07:40 +01:00
|
|
|
/**
|
|
|
|
* Wrapper that the correct mod_quiz_display_options for this quiz at the
|
|
|
|
* moment.
|
|
|
|
*
|
|
|
|
* @param bool $reviewing true for review page, else attempt page.
|
|
|
|
* @param int $slot which question is being displayed.
|
|
|
|
* @param moodle_url $thispageurl to return to after the editing form is
|
|
|
|
* submitted or cancelled. If null, no edit link will be generated.
|
|
|
|
*
|
|
|
|
* @return question_display_options the render options for this user on this
|
|
|
|
* attempt, with extra info to generate an edit link, if applicable.
|
|
|
|
*/
|
|
|
|
public function get_display_options_with_edit_link($reviewing, $slot, $thispageurl) {
|
|
|
|
$options = clone($this->get_display_options($reviewing));
|
|
|
|
|
|
|
|
if (!$thispageurl) {
|
|
|
|
return $options;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!($reviewing || $this->is_preview())) {
|
|
|
|
return $options;
|
|
|
|
}
|
|
|
|
|
|
|
|
$question = $this->quba->get_question($slot);
|
|
|
|
if (!question_has_capability_on($question, 'edit', $question->category)) {
|
|
|
|
return $options;
|
|
|
|
}
|
|
|
|
|
|
|
|
$options->editquestionparams['cmid'] = $this->get_cmid();
|
|
|
|
$options->editquestionparams['returnurl'] = $thispageurl;
|
|
|
|
|
|
|
|
return $options;
|
|
|
|
}
|
|
|
|
|
2008-07-08 17:47:57 +00:00
|
|
|
/**
|
2011-02-08 14:19:23 +00:00
|
|
|
* @param int $page page number
|
2011-02-23 16:25:25 +00:00
|
|
|
* @return bool true if this is the last page of the quiz.
|
2008-07-08 17:47:57 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function is_last_page($page) {
|
|
|
|
return $page == count($this->pagelayout) - 1;
|
2008-07-08 17:47:57 +00:00
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
2015-03-09 18:40:18 +00:00
|
|
|
* Return the list of slot numbers for either a given page of the quiz, or for the
|
2011-02-08 14:19:23 +00:00
|
|
|
* whole quiz.
|
2008-06-30 16:56:49 +00:00
|
|
|
*
|
2011-02-08 14:19:23 +00:00
|
|
|
* @param mixed $page string 'all' or integer page number.
|
2015-03-09 18:40:18 +00:00
|
|
|
* @return array the requested list of slot numbers.
|
2008-06-30 16:56:49 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_slots($page = 'all') {
|
|
|
|
if ($page === 'all') {
|
|
|
|
$numbers = array();
|
|
|
|
foreach ($this->pagelayout as $numbersonpage) {
|
|
|
|
$numbers = array_merge($numbers, $numbersonpage);
|
|
|
|
}
|
|
|
|
return $numbers;
|
|
|
|
} else {
|
|
|
|
return $this->pagelayout[$page];
|
|
|
|
}
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
2015-03-09 18:40:18 +00:00
|
|
|
/**
|
|
|
|
* Return the list of slot numbers for either a given page of the quiz, or for the
|
|
|
|
* whole quiz.
|
|
|
|
*
|
|
|
|
* @param mixed $page string 'all' or integer page number.
|
|
|
|
* @return array the requested list of slot numbers.
|
|
|
|
*/
|
|
|
|
public function get_active_slots($page = 'all') {
|
|
|
|
$activeslots = array();
|
|
|
|
foreach ($this->get_slots($page) as $slot) {
|
|
|
|
if (!$this->is_blocked_by_previous_question($slot)) {
|
|
|
|
$activeslots[] = $slot;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $activeslots;
|
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
2011-02-08 14:19:23 +00:00
|
|
|
* Get the question_attempt object for a particular question in this attempt.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
2011-02-08 14:19:23 +00:00
|
|
|
* @return question_attempt
|
2008-06-30 16:56:49 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_question_attempt($slot) {
|
|
|
|
return $this->quba->get_question_attempt($slot);
|
|
|
|
}
|
2008-06-30 16:56:49 +00:00
|
|
|
|
2015-03-09 18:40:18 +00:00
|
|
|
/**
|
|
|
|
* Get the question_attempt object for a particular question in this attempt.
|
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
|
|
|
* @return question_attempt
|
|
|
|
*/
|
|
|
|
public function all_question_attempts_originally_in_slot($slot) {
|
|
|
|
$qas = array();
|
|
|
|
foreach ($this->quba->get_attempt_iterator() as $qa) {
|
|
|
|
if ($qa->get_metadata('originalslot') == $slot) {
|
|
|
|
$qas[] = $qa;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$qas[] = $this->quba->get_question_attempt($slot);
|
|
|
|
return $qas;
|
|
|
|
}
|
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
/**
|
|
|
|
* Is a particular question in this attempt a real question, or something like a description.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
2014-06-22 11:03:03 +01:00
|
|
|
* @return int whether that question is a real question. Actually returns the
|
|
|
|
* question length, which could theoretically be greater than one.
|
2011-02-08 14:19:23 +00:00
|
|
|
*/
|
|
|
|
public function is_real_question($slot) {
|
2014-06-22 11:03:03 +01:00
|
|
|
return $this->quba->get_question($slot)->length;
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
2008-08-29 10:08:27 +00:00
|
|
|
/**
|
2011-02-08 14:19:23 +00:00
|
|
|
* Is a particular question in this attempt a real question, or something like a description.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
|
|
|
* @return bool whether that question is a real question.
|
2008-08-29 10:08:27 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function is_question_flagged($slot) {
|
|
|
|
return $this->quba->get_question_attempt($slot)->is_flagged();
|
2008-08-29 10:08:27 +00:00
|
|
|
}
|
|
|
|
|
2008-06-27 18:04:48 +00:00
|
|
|
/**
|
2015-02-26 11:53:00 +00:00
|
|
|
* Checks whether the question in this slot requires the previous question to have been completed.
|
|
|
|
*
|
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
|
|
|
* @return bool whether the previous question must have been completed before this one can be seen.
|
|
|
|
*/
|
|
|
|
public function is_blocked_by_previous_question($slot) {
|
2015-03-09 18:40:18 +00:00
|
|
|
return $slot > 1 && isset($this->slots[$slot]) && $this->slots[$slot]->requireprevious &&
|
2015-04-02 15:23:04 +01:00
|
|
|
!$this->slots[$slot]->section->shufflequestions &&
|
|
|
|
!$this->slots[$slot - 1]->section->shufflequestions &&
|
2015-02-26 11:53:00 +00:00
|
|
|
$this->get_navigation_method() != QUIZ_NAVMETHOD_SEQ &&
|
2015-03-09 18:40:18 +00:00
|
|
|
!$this->get_question_state($slot - 1)->is_finished() &&
|
2015-02-26 11:53:00 +00:00
|
|
|
$this->quba->can_question_finish_during_attempt($slot - 1);
|
|
|
|
}
|
|
|
|
|
2015-03-09 18:40:18 +00:00
|
|
|
/**
|
|
|
|
* Is it possible for this question to be re-started within this attempt?
|
|
|
|
*
|
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
|
|
|
* @return whether the student should be given the option to restart this question now.
|
|
|
|
*/
|
|
|
|
public function can_question_be_redone_now($slot) {
|
|
|
|
return $this->get_quiz()->canredoquestions && !$this->is_finished() &&
|
|
|
|
$this->get_question_state($slot)->is_finished();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a slot in this attempt, which may or not be a redone question, return the original slot.
|
|
|
|
*
|
|
|
|
* @param int $slot identifies a particular question in this attempt.
|
|
|
|
* @return int the slot where this question was originally.
|
|
|
|
*/
|
|
|
|
public function get_original_slot($slot) {
|
|
|
|
$originalslot = $this->quba->get_question_attempt_metadata($slot, 'originalslot');
|
|
|
|
if ($originalslot) {
|
|
|
|
return $originalslot;
|
|
|
|
} else {
|
|
|
|
return $slot;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-26 11:53:00 +00:00
|
|
|
/**
|
|
|
|
* Get the displayed question number for a slot.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
2012-10-19 03:25:57 +08:00
|
|
|
* @return string the displayed question number for the question in this slot.
|
|
|
|
* For example '1', '2', '3' or 'i'.
|
2008-06-27 18:04:48 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_question_number($slot) {
|
2012-10-19 03:25:57 +08:00
|
|
|
return $this->questionnumbers[$slot];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-04-02 15:23:04 +01:00
|
|
|
* If the section heading, if any, that should come just before this slot.
|
|
|
|
* @param int $slot identifies a particular question in this attempt.
|
|
|
|
* @return string the required heading, or null if there is not one here.
|
|
|
|
*/
|
|
|
|
public function get_heading_before_slot($slot) {
|
|
|
|
if ($this->slots[$slot]->firstinsection) {
|
|
|
|
return $this->slots[$slot]->section->heading;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the page of the quiz where this question appears.
|
2012-10-19 03:25:57 +08:00
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
|
|
|
* @return int the page of the quiz this question appears on.
|
|
|
|
*/
|
|
|
|
public function get_question_page($slot) {
|
|
|
|
return $this->questionpages[$slot];
|
2011-02-08 14:19:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-05-12 00:30:25 +01:00
|
|
|
* Return the grade obtained on a particular question, if the user is permitted
|
|
|
|
* to see it. You must previously have called load_question_states to load the
|
|
|
|
* state data about this question.
|
2011-02-08 14:19:23 +00:00
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
2011-05-12 00:30:25 +01:00
|
|
|
* @return string the formatted grade, to the number of decimal places specified
|
|
|
|
* by the quiz.
|
2011-02-08 14:19:23 +00:00
|
|
|
*/
|
|
|
|
public function get_question_name($slot) {
|
|
|
|
return $this->quba->get_question($slot)->name;
|
|
|
|
}
|
|
|
|
|
2015-03-09 18:40:18 +00:00
|
|
|
/**
|
|
|
|
* Return the {@link question_state} that this question is in.
|
|
|
|
*
|
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
|
|
|
* @return question_state the state this question is in.
|
|
|
|
*/
|
|
|
|
public function get_question_state($slot) {
|
|
|
|
return $this->quba->get_question_state($slot);
|
|
|
|
}
|
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
/**
|
2011-05-12 00:30:25 +01:00
|
|
|
* Return the grade obtained on a particular question, if the user is permitted
|
|
|
|
* to see it. You must previously have called load_question_states to load the
|
|
|
|
* state data about this question.
|
2011-02-08 14:19:23 +00:00
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
|
|
|
* @param bool $showcorrectness Whether right/partial/wrong states should
|
2011-02-08 14:19:23 +00:00
|
|
|
* be distinguised.
|
2011-05-12 00:30:25 +01:00
|
|
|
* @return string the formatted grade, to the number of decimal places specified
|
|
|
|
* by the quiz.
|
2011-02-08 14:19:23 +00:00
|
|
|
*/
|
|
|
|
public function get_question_status($slot, $showcorrectness) {
|
|
|
|
return $this->quba->get_question_state_string($slot, $showcorrectness);
|
|
|
|
}
|
|
|
|
|
2011-06-07 15:10:59 +01:00
|
|
|
/**
|
|
|
|
* Return the grade obtained on a particular question, if the user is permitted
|
|
|
|
* to see it. You must previously have called load_question_states to load the
|
|
|
|
* state data about this question.
|
|
|
|
*
|
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
|
|
|
* @param bool $showcorrectness Whether right/partial/wrong states should
|
|
|
|
* be distinguised.
|
|
|
|
* @return string class name for this state.
|
|
|
|
*/
|
|
|
|
public function get_question_state_class($slot, $showcorrectness) {
|
|
|
|
return $this->quba->get_question_state_class($slot, $showcorrectness);
|
|
|
|
}
|
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
/**
|
|
|
|
* Return the grade obtained on a particular question.
|
|
|
|
* You must previously have called load_question_states to load the state
|
|
|
|
* data about this question.
|
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
2011-02-08 14:19:23 +00:00
|
|
|
* @return string the formatted grade, to the number of decimal places specified by the quiz.
|
|
|
|
*/
|
2011-02-09 16:56:44 +00:00
|
|
|
public function get_question_mark($slot) {
|
2011-02-08 14:19:23 +00:00
|
|
|
return quiz_format_question_grade($this->get_quiz(), $this->quba->get_question_mark($slot));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the time of the most recent action performed on a question.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $slot the number used to identify this question within this usage.
|
|
|
|
* @return int timestamp.
|
2011-02-08 14:19:23 +00:00
|
|
|
*/
|
|
|
|
public function get_question_action_time($slot) {
|
|
|
|
return $this->quba->get_question_action_time($slot);
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
2016-01-24 23:20:52 +01:00
|
|
|
/**
|
|
|
|
* Return the question type name for a given slot within the current attempt.
|
|
|
|
*
|
|
|
|
* @param int $slot the number used to identify this question within this attempt.
|
|
|
|
* @return string the question type name
|
|
|
|
* @since Moodle 3.1
|
|
|
|
*/
|
|
|
|
public function get_question_type_name($slot) {
|
|
|
|
return $this->quba->get_question($slot)->get_type_name();
|
|
|
|
}
|
|
|
|
|
2011-11-15 17:25:26 +00:00
|
|
|
/**
|
|
|
|
* Get the time remaining for an in-progress attempt, if the time is short
|
|
|
|
* enought that it would be worth showing a timer.
|
|
|
|
* @param int $timenow the time to consider as 'now'.
|
|
|
|
* @return int|false the number of seconds remaining for this attempt.
|
|
|
|
* False if there is no limit.
|
|
|
|
*/
|
2012-10-09 13:48:30 -04:00
|
|
|
public function get_time_left_display($timenow) {
|
2011-11-15 17:25:26 +00:00
|
|
|
if ($this->attempt->state != self::IN_PROGRESS) {
|
|
|
|
return false;
|
|
|
|
}
|
2012-10-09 13:48:30 -04:00
|
|
|
return $this->get_access_manager($timenow)->get_time_left_display($this->attempt, $timenow);
|
2011-11-15 17:25:26 +00:00
|
|
|
}
|
2011-11-14 15:35:46 +00:00
|
|
|
|
2012-10-09 13:48:30 -04:00
|
|
|
|
2011-11-14 15:35:46 +00:00
|
|
|
/**
|
|
|
|
* @return int the time when this attempt was submitted. 0 if it has not been
|
|
|
|
* submitted yet.
|
|
|
|
*/
|
|
|
|
public function get_submitted_date() {
|
|
|
|
return $this->attempt->timefinish;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If the attempt is in an applicable state, work out the time by which the
|
|
|
|
* student should next do something.
|
|
|
|
* @return int timestamp by which the student needs to do something.
|
|
|
|
*/
|
2012-05-04 12:40:21 +01:00
|
|
|
public function get_due_date() {
|
2011-11-15 17:25:26 +00:00
|
|
|
$deadlines = array();
|
|
|
|
if ($this->quizobj->get_quiz()->timelimit) {
|
|
|
|
$deadlines[] = $this->attempt->timestart + $this->quizobj->get_quiz()->timelimit;
|
|
|
|
}
|
|
|
|
if ($this->quizobj->get_quiz()->timeclose) {
|
|
|
|
$deadlines[] = $this->quizobj->get_quiz()->timeclose;
|
|
|
|
}
|
|
|
|
if ($deadlines) {
|
|
|
|
$duedate = min($deadlines);
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
2011-11-14 15:35:46 +00:00
|
|
|
|
2011-11-15 17:25:26 +00:00
|
|
|
switch ($this->attempt->state) {
|
2011-11-14 15:35:46 +00:00
|
|
|
case self::IN_PROGRESS:
|
2011-11-15 17:25:26 +00:00
|
|
|
return $duedate;
|
2011-11-14 15:35:46 +00:00
|
|
|
|
|
|
|
case self::OVERDUE:
|
2011-11-15 17:25:26 +00:00
|
|
|
return $duedate + $this->quizobj->get_quiz()->graceperiod;
|
2011-11-14 15:35:46 +00:00
|
|
|
|
|
|
|
default:
|
2011-11-15 17:25:26 +00:00
|
|
|
throw new coding_exception('Unexpected state: ' . $this->attempt->state);
|
2011-11-14 15:35:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// URLs related to this attempt ============================================
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
2011-02-08 14:19:23 +00:00
|
|
|
* @return string quiz view url.
|
|
|
|
*/
|
|
|
|
public function view_url() {
|
|
|
|
return $this->quizobj->view_url();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string the URL of this quiz's edit page. Needs to be POSTed to with a cmid parameter.
|
|
|
|
*/
|
2011-06-22 21:07:40 +01:00
|
|
|
public function start_attempt_url($slot = null, $page = -1) {
|
|
|
|
if ($page == -1 && !is_null($slot)) {
|
2012-10-19 03:25:57 +08:00
|
|
|
$page = $this->get_question_page($slot);
|
2011-06-22 21:07:40 +01:00
|
|
|
} else {
|
|
|
|
$page = 0;
|
|
|
|
}
|
|
|
|
return $this->quizobj->start_attempt_url($page);
|
2011-02-08 14:19:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $slot if speified, the slot number of a specific question to link to.
|
|
|
|
* @param int $page if specified, a particular page to link to. If not givem deduced
|
2011-02-08 14:19:23 +00:00
|
|
|
* from $slot, or goes to the first page.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $questionid a question id. If set, will add a fragment to the URL
|
2008-06-30 16:56:49 +00:00
|
|
|
* to jump to a particuar question on the page.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $thispage if not -1, the current page. Will cause links to other things on
|
2009-07-23 09:20:33 +00:00
|
|
|
* this page to be output as only a fragment.
|
2008-06-30 16:56:49 +00:00
|
|
|
* @return string the URL to continue this attempt.
|
|
|
|
*/
|
2011-02-09 20:33:51 +00:00
|
|
|
public function attempt_url($slot = null, $page = -1, $thispage = -1) {
|
2011-02-08 14:19:23 +00:00
|
|
|
return $this->page_and_question_url('attempt', $slot, $page, false, $thispage);
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
|
|
|
* @return string the URL of this quiz's summary page.
|
|
|
|
*/
|
2008-06-27 18:04:48 +00:00
|
|
|
public function summary_url() {
|
2011-02-08 14:19:23 +00:00
|
|
|
return new moodle_url('/mod/quiz/summary.php', array('attempt' => $this->attempt->id));
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
2008-07-15 16:46:24 +00:00
|
|
|
/**
|
|
|
|
* @return string the URL of this quiz's summary page.
|
|
|
|
*/
|
|
|
|
public function processattempt_url() {
|
2011-02-08 14:19:23 +00:00
|
|
|
return new moodle_url('/mod/quiz/processattempt.php');
|
2008-07-15 16:46:24 +00:00
|
|
|
}
|
|
|
|
|
2008-06-30 16:56:49 +00:00
|
|
|
/**
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $slot indicates which question to link to.
|
|
|
|
* @param int $page if specified, the URL of this particular page of the attempt, otherwise
|
2011-02-08 14:19:23 +00:00
|
|
|
* the URL will go to the first page. If -1, deduce $page from $slot.
|
2014-06-22 11:03:03 +01:00
|
|
|
* @param bool|null $showall if true, the URL will be to review the entire attempt on one page,
|
|
|
|
* and $page will be ignored. If null, a sensible default will be chosen.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $thispage if not -1, the current page. Will cause links to other things on
|
2009-07-23 09:20:33 +00:00
|
|
|
* this page to be output as only a fragment.
|
2008-06-30 16:56:49 +00:00
|
|
|
* @return string the URL to review this attempt.
|
|
|
|
*/
|
2014-06-22 11:03:03 +01:00
|
|
|
public function review_url($slot = null, $page = -1, $showall = null, $thispage = -1) {
|
2011-02-08 14:19:23 +00:00
|
|
|
return $this->page_and_question_url('review', $slot, $page, $showall, $thispage);
|
2008-07-08 16:33:47 +00:00
|
|
|
}
|
|
|
|
|
2014-06-22 11:03:03 +01:00
|
|
|
/**
|
|
|
|
* By default, should this script show all questions on one page for this attempt?
|
|
|
|
* @param string $script the script name, e.g. 'attempt', 'summary', 'review'.
|
|
|
|
* @return whether show all on one page should be on by default.
|
|
|
|
*/
|
|
|
|
public function get_default_show_all($script) {
|
|
|
|
return $script == 'review' && count($this->questionpages) < self::MAX_SLOTS_FOR_DEFAULT_REVIEW_SHOW_ALL;
|
|
|
|
}
|
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Bits of content =========================================================
|
2011-02-08 14:19:23 +00:00
|
|
|
|
2011-10-05 20:58:38 +01:00
|
|
|
/**
|
|
|
|
* If $reviewoptions->attempt is false, meaning that students can't review this
|
|
|
|
* attempt at the moment, return an appropriate string explaining why.
|
|
|
|
*
|
|
|
|
* @param bool $short if true, return a shorter string.
|
|
|
|
* @return string an appropraite message.
|
|
|
|
*/
|
|
|
|
public function cannot_review_message($short = false) {
|
|
|
|
return $this->quizobj->cannot_review_message(
|
|
|
|
$this->get_attempt_state(), $short);
|
|
|
|
}
|
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
2012-07-27 11:42:03 +01:00
|
|
|
* Initialise the JS etc. required all the questions on a page.
|
2009-07-23 09:20:33 +00:00
|
|
|
* @param mixed $page a page number, or 'all'.
|
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_html_head_contributions($page = 'all', $showall = false) {
|
|
|
|
if ($showall) {
|
|
|
|
$page = 'all';
|
|
|
|
}
|
|
|
|
$result = '';
|
|
|
|
foreach ($this->get_slots($page) as $slot) {
|
|
|
|
$result .= $this->quba->render_question_head_html($slot);
|
|
|
|
}
|
|
|
|
$result .= question_engine::initialise_js();
|
|
|
|
return $result;
|
2008-07-08 16:33:47 +00:00
|
|
|
}
|
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
|
|
|
* Initialise the JS etc. required by one question.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $questionid the question id.
|
2009-07-23 09:20:33 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function get_question_html_head_contributions($slot) {
|
|
|
|
return $this->quba->render_question_head_html($slot) .
|
|
|
|
question_engine::initialise_js();
|
2008-08-28 02:15:19 +00:00
|
|
|
}
|
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
2011-02-25 12:12:09 +00:00
|
|
|
* Print the HTML for the start new preview button, if the current user
|
|
|
|
* is allowed to see one.
|
|
|
|
*/
|
|
|
|
public function restart_preview_button() {
|
|
|
|
global $OUTPUT;
|
|
|
|
if ($this->is_preview() && $this->is_preview_user()) {
|
|
|
|
return $OUTPUT->single_button(new moodle_url(
|
|
|
|
$this->start_attempt_url(), array('forcenew' => true)),
|
|
|
|
get_string('startnewpreview', 'quiz'));
|
|
|
|
} else {
|
|
|
|
return '';
|
|
|
|
}
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
|
2008-12-10 06:26:47 +00:00
|
|
|
/**
|
2011-02-08 14:19:23 +00:00
|
|
|
* Generate the HTML that displayes the question in its current state, with
|
|
|
|
* the appropriate display options.
|
2008-12-10 06:26:47 +00:00
|
|
|
*
|
2015-03-09 18:40:18 +00:00
|
|
|
* @param int $slot identifies the question in the attempt.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param bool $reviewing is the being printed on an attempt or a review page.
|
2015-03-09 18:40:18 +00:00
|
|
|
* @param mod_quiz_renderer $renderer the quiz renderer.
|
2011-06-22 21:07:40 +01:00
|
|
|
* @param moodle_url $thispageurl the URL of the page this question is being printed on.
|
2011-02-08 14:19:23 +00:00
|
|
|
* @return string HTML for the question in its current state.
|
2008-12-10 06:26:47 +00:00
|
|
|
*/
|
2015-03-09 18:40:18 +00:00
|
|
|
public function render_question($slot, $reviewing, mod_quiz_renderer $renderer, $thispageurl = null) {
|
2015-02-26 11:53:00 +00:00
|
|
|
if ($this->is_blocked_by_previous_question($slot)) {
|
|
|
|
$placeholderqa = $this->make_blocked_question_placeholder($slot);
|
|
|
|
|
|
|
|
$displayoptions = $this->get_display_options($reviewing);
|
|
|
|
$displayoptions->manualcomment = question_display_options::HIDDEN;
|
|
|
|
$displayoptions->history = question_display_options::HIDDEN;
|
|
|
|
$displayoptions->readonly = true;
|
|
|
|
|
|
|
|
return html_writer::div($placeholderqa->render($displayoptions,
|
2015-03-09 18:40:18 +00:00
|
|
|
$this->get_question_number($this->get_original_slot($slot))),
|
2015-02-26 11:53:00 +00:00
|
|
|
'mod_quiz-blocked_question_warning');
|
|
|
|
}
|
|
|
|
|
2015-03-09 18:40:18 +00:00
|
|
|
return $this->render_question_helper($slot, $reviewing, $thispageurl, $renderer, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper used by {@link render_question()} and {@link render_question_at_step()}.
|
|
|
|
*
|
|
|
|
* @param int $slot identifies the question in the attempt.
|
|
|
|
* @param bool $reviewing is the being printed on an attempt or a review page.
|
|
|
|
* @param moodle_url $thispageurl the URL of the page this question is being printed on.
|
|
|
|
* @param mod_quiz_renderer $renderer the quiz renderer.
|
|
|
|
* @param int|null $seq the seq number of the past state to display.
|
|
|
|
* @return string HTML fragment.
|
|
|
|
*/
|
|
|
|
protected function render_question_helper($slot, $reviewing, $thispageurl, mod_quiz_renderer $renderer, $seq) {
|
|
|
|
$originalslot = $this->get_original_slot($slot);
|
|
|
|
$number = $this->get_question_number($originalslot);
|
|
|
|
$displayoptions = $this->get_display_options_with_edit_link($reviewing, $slot, $thispageurl);
|
|
|
|
|
|
|
|
if ($slot != $originalslot) {
|
|
|
|
$originalmaxmark = $this->get_question_attempt($slot)->get_max_mark();
|
|
|
|
$this->get_question_attempt($slot)->set_max_mark($this->get_question_attempt($originalslot)->get_max_mark());
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->can_question_be_redone_now($slot)) {
|
|
|
|
$displayoptions->extrainfocontent = $renderer->redo_question_button(
|
|
|
|
$slot, $displayoptions->readonly);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($displayoptions->history && $displayoptions->questionreviewlink) {
|
|
|
|
$links = $this->links_to_other_redos($slot, $displayoptions->questionreviewlink);
|
|
|
|
if ($links) {
|
|
|
|
$displayoptions->extrahistorycontent = html_writer::tag('p',
|
|
|
|
get_string('redoesofthisquestion', 'quiz', $renderer->render($links)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($seq === null) {
|
|
|
|
$output = $this->quba->render_question($slot, $displayoptions, $number);
|
|
|
|
} else {
|
|
|
|
$output = $this->quba->render_question_at_step($slot, $seq, $displayoptions, $number);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($slot != $originalslot) {
|
|
|
|
$this->get_question_attempt($slot)->set_max_mark($originalmaxmark);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $output;
|
2011-02-08 14:19:23 +00:00
|
|
|
}
|
2010-10-18 15:28:59 +00:00
|
|
|
|
2015-02-26 11:53:00 +00:00
|
|
|
/**
|
|
|
|
* Create a fake question to be displayed in place of a question that is blocked
|
|
|
|
* until the previous question has been answered.
|
|
|
|
*
|
2015-03-09 18:40:18 +00:00
|
|
|
* @param int $slot int slot number of the question to replace.
|
2015-02-26 11:53:00 +00:00
|
|
|
* @return question_definition the placeholde question.
|
|
|
|
*/
|
|
|
|
protected function make_blocked_question_placeholder($slot) {
|
|
|
|
$replacedquestion = $this->get_question_attempt($slot)->get_question();
|
|
|
|
|
|
|
|
question_bank::load_question_definition_classes('description');
|
|
|
|
$question = new qtype_description_question();
|
|
|
|
$question->id = $replacedquestion->id;
|
|
|
|
$question->category = null;
|
|
|
|
$question->parent = 0;
|
|
|
|
$question->qtype = question_bank::get_qtype('description');
|
|
|
|
$question->name = '';
|
|
|
|
$question->questiontext = get_string('questiondependsonprevious', 'quiz');
|
|
|
|
$question->questiontextformat = FORMAT_HTML;
|
|
|
|
$question->generalfeedback = '';
|
|
|
|
$question->defaultmark = $this->quba->get_question_max_mark($slot);
|
|
|
|
$question->length = $replacedquestion->length;
|
|
|
|
$question->penalty = 0;
|
|
|
|
$question->stamp = '';
|
|
|
|
$question->version = 0;
|
|
|
|
$question->hidden = 0;
|
|
|
|
$question->timecreated = null;
|
|
|
|
$question->timemodified = null;
|
|
|
|
$question->createdby = null;
|
|
|
|
$question->modifiedby = null;
|
|
|
|
|
|
|
|
$placeholderqa = new question_attempt($question, $this->quba->get_id(),
|
|
|
|
null, $this->quba->get_question_max_mark($slot));
|
|
|
|
$placeholderqa->set_slot($slot);
|
|
|
|
$placeholderqa->start($this->get_quiz()->preferredbehaviour, 1);
|
|
|
|
$placeholderqa->set_flagged($this->is_question_flagged($slot));
|
|
|
|
return $placeholderqa;
|
|
|
|
}
|
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
/**
|
|
|
|
* Like {@link render_question()} but displays the question at the past step
|
|
|
|
* indicated by $seq, rather than showing the latest step.
|
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $id the id of a question in this quiz attempt.
|
|
|
|
* @param int $seq the seq number of the past state to display.
|
|
|
|
* @param bool $reviewing is the being printed on an attempt or a review page.
|
2015-03-09 18:40:18 +00:00
|
|
|
* @param mod_quiz_renderer $renderer the quiz renderer.
|
2011-02-08 14:19:23 +00:00
|
|
|
* @param string $thispageurl the URL of the page this question is being printed on.
|
|
|
|
* @return string HTML for the question in its current state.
|
|
|
|
*/
|
2015-03-09 18:40:18 +00:00
|
|
|
public function render_question_at_step($slot, $seq, $reviewing, mod_quiz_renderer $renderer, $thispageurl = '') {
|
|
|
|
return $this->render_question_helper($slot, $reviewing, $thispageurl, $renderer, $seq);
|
2011-02-08 14:19:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wrapper round print_question from lib/questionlib.php.
|
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $id the id of a question in this quiz attempt.
|
2011-02-08 14:19:23 +00:00
|
|
|
*/
|
|
|
|
public function render_question_for_commenting($slot) {
|
|
|
|
$options = $this->get_display_options(true);
|
|
|
|
$options->hide_all_feedback();
|
|
|
|
$options->manualcomment = question_display_options::EDITABLE;
|
2011-05-12 00:30:25 +01:00
|
|
|
return $this->quba->render_question($slot, $options,
|
2012-10-19 03:25:57 +08:00
|
|
|
$this->get_question_number($slot));
|
2008-07-08 16:33:47 +00:00
|
|
|
}
|
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
/**
|
|
|
|
* Check wheter access should be allowed to a particular file.
|
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $id the id of a question in this quiz attempt.
|
|
|
|
* @param bool $reviewing is the being printed on an attempt or a review page.
|
2011-02-08 14:19:23 +00:00
|
|
|
* @param string $thispageurl the URL of the page this question is being printed on.
|
|
|
|
* @return string HTML for the question in its current state.
|
|
|
|
*/
|
2011-02-09 20:33:51 +00:00
|
|
|
public function check_file_access($slot, $reviewing, $contextid, $component,
|
2010-08-10 09:56:48 +00:00
|
|
|
$filearea, $args, $forcedownload) {
|
2015-08-04 17:26:12 +01:00
|
|
|
$options = $this->get_display_options($reviewing);
|
|
|
|
|
|
|
|
// Check permissions - warning there is similar code in review.php and
|
|
|
|
// reviewquestion.php. If you change on, change them all.
|
|
|
|
if ($reviewing && $this->is_own_attempt() && !$options->attempt) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($reviewing && !$this->is_own_attempt() && !$this->is_review_allowed()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->quba->check_file_access($slot, $options,
|
2011-02-09 20:33:51 +00:00
|
|
|
$component, $filearea, $args, $forcedownload);
|
2010-08-10 09:56:48 +00:00
|
|
|
}
|
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
|
|
|
* Get the navigation panel object for this attempt.
|
|
|
|
*
|
|
|
|
* @param $panelclass The type of panel, quiz_attempt_nav_panel or quiz_review_nav_panel
|
|
|
|
* @param $page the current page number.
|
|
|
|
* @param $showall whether we are showing the whole quiz on one page. (Used by review.php)
|
|
|
|
* @return quiz_nav_panel_base the requested object.
|
|
|
|
*/
|
2011-04-15 18:26:35 +01:00
|
|
|
public function get_navigation_panel(mod_quiz_renderer $output,
|
|
|
|
$panelclass, $page, $showall = false) {
|
2011-02-08 14:19:23 +00:00
|
|
|
$panel = new $panelclass($this, $this->get_display_options(true), $page, $showall);
|
2011-04-15 18:26:35 +01:00
|
|
|
|
|
|
|
$bc = new block_contents();
|
2011-06-17 17:05:35 +01:00
|
|
|
$bc->attributes['id'] = 'mod_quiz_navblock';
|
2011-04-15 18:26:35 +01:00
|
|
|
$bc->title = get_string('quiznavigation', 'quiz');
|
|
|
|
$bc->content = $output->navigation_panel($panel);
|
|
|
|
return $bc;
|
2008-07-11 17:03:43 +00:00
|
|
|
}
|
2008-06-27 18:04:48 +00:00
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
2015-03-09 18:40:18 +00:00
|
|
|
* Return an array of variant URLs to other attempts at this quiz.
|
|
|
|
*
|
|
|
|
* The $url passed in must contain an attempt parameter.
|
|
|
|
*
|
|
|
|
* The {@link mod_quiz_links_to_other_attempts} object returned contains an
|
|
|
|
* array with keys that are the attempt number, 1, 2, 3.
|
|
|
|
* The array values are either a {@link moodle_url} with the attmept parameter
|
|
|
|
* updated to point to the attempt id of the other attempt, or null corresponding
|
|
|
|
* to the current attempt number.
|
|
|
|
*
|
2011-04-15 18:26:35 +01:00
|
|
|
* @param moodle_url $url a URL.
|
2015-03-09 18:40:18 +00:00
|
|
|
* @return mod_quiz_links_to_other_attempts containing array int => null|moodle_url.
|
2009-07-23 09:20:33 +00:00
|
|
|
*/
|
2011-04-15 18:26:35 +01:00
|
|
|
public function links_to_other_attempts(moodle_url $url) {
|
2011-02-08 14:19:23 +00:00
|
|
|
$attempts = quiz_get_user_attempts($this->get_quiz()->id, $this->attempt->userid, 'all');
|
2008-08-15 06:42:38 +00:00
|
|
|
if (count($attempts) <= 1) {
|
|
|
|
return false;
|
|
|
|
}
|
2011-04-15 18:26:35 +01:00
|
|
|
|
|
|
|
$links = new mod_quiz_links_to_other_attempts();
|
2008-07-15 15:30:39 +00:00
|
|
|
foreach ($attempts as $at) {
|
|
|
|
if ($at->id == $this->attempt->id) {
|
2011-04-15 18:26:35 +01:00
|
|
|
$links->links[$at->attempt] = null;
|
2008-07-15 15:30:39 +00:00
|
|
|
} else {
|
2011-04-15 18:26:35 +01:00
|
|
|
$links->links[$at->attempt] = new moodle_url($url, array('attempt' => $at->id));
|
2008-07-15 15:30:39 +00:00
|
|
|
}
|
|
|
|
}
|
2011-04-15 18:26:35 +01:00
|
|
|
return $links;
|
2008-07-15 15:30:39 +00:00
|
|
|
}
|
|
|
|
|
2015-03-09 18:40:18 +00:00
|
|
|
/**
|
|
|
|
* Return an array of variant URLs to other redos of the question in a particular slot.
|
|
|
|
*
|
|
|
|
* The $url passed in must contain a slot parameter.
|
|
|
|
*
|
|
|
|
* The {@link mod_quiz_links_to_other_attempts} object returned contains an
|
|
|
|
* array with keys that are the redo number, 1, 2, 3.
|
|
|
|
* The array values are either a {@link moodle_url} with the slot parameter
|
|
|
|
* updated to point to the slot that has that redo of this question; or null
|
|
|
|
* corresponding to the redo identified by $slot.
|
|
|
|
*
|
|
|
|
* @param int $slot identifies a question in this attempt.
|
|
|
|
* @param moodle_url $baseurl the base URL to modify to generate each link.
|
|
|
|
* @return mod_quiz_links_to_other_attempts|null containing array int => null|moodle_url,
|
|
|
|
* or null if the question in this slot has not been redone.
|
|
|
|
*/
|
|
|
|
public function links_to_other_redos($slot, moodle_url $baseurl) {
|
|
|
|
$originalslot = $this->get_original_slot($slot);
|
|
|
|
|
|
|
|
$qas = $this->all_question_attempts_originally_in_slot($originalslot);
|
|
|
|
if (count($qas) <= 1) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$links = new mod_quiz_links_to_other_attempts();
|
|
|
|
$index = 1;
|
|
|
|
foreach ($qas as $qa) {
|
|
|
|
if ($qa->get_slot() == $slot) {
|
|
|
|
$links->links[$index] = null;
|
|
|
|
} else {
|
|
|
|
$url = new moodle_url($baseurl, array('slot' => $qa->get_slot()));
|
|
|
|
$links->links[$index] = new action_link($url, $index,
|
|
|
|
new popup_action('click', $url, 'reviewquestion',
|
|
|
|
array('width' => 450, 'height' => 650)),
|
|
|
|
array('title' => get_string('reviewresponse', 'question')));
|
|
|
|
}
|
|
|
|
$index++;
|
|
|
|
}
|
|
|
|
return $links;
|
|
|
|
}
|
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
// Methods for processing ==================================================
|
|
|
|
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
/**
|
|
|
|
* Check this attempt, to see if there are any state transitions that should
|
2012-10-09 13:48:30 -04:00
|
|
|
* happen automatically. This function will update the attempt checkstatetime.
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
* @param int $timestamp the timestamp that should be stored as the modifed
|
2012-04-20 14:51:15 +01:00
|
|
|
* @param bool $studentisonline is the student currently interacting with Moodle?
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
*/
|
2012-04-20 14:51:15 +01:00
|
|
|
public function handle_if_time_expired($timestamp, $studentisonline) {
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
global $DB;
|
|
|
|
|
2012-10-09 13:48:30 -04:00
|
|
|
$timeclose = $this->get_access_manager($timestamp)->get_end_time($this->attempt);
|
2012-04-20 14:51:15 +01:00
|
|
|
|
2012-10-09 13:48:30 -04:00
|
|
|
if ($timeclose === false || $this->is_preview()) {
|
|
|
|
$this->update_timecheckstate(null);
|
|
|
|
return; // No time limit
|
|
|
|
}
|
|
|
|
if ($timestamp < $timeclose) {
|
|
|
|
$this->update_timecheckstate($timeclose);
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
return; // Time has not yet expired.
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the attempt is already overdue, look to see if it should be abandoned ...
|
2012-05-04 12:40:21 +01:00
|
|
|
if ($this->attempt->state == self::OVERDUE) {
|
2012-10-09 13:48:30 -04:00
|
|
|
$timeoverdue = $timestamp - $timeclose;
|
|
|
|
$graceperiod = $this->quizobj->get_quiz()->graceperiod;
|
|
|
|
if ($timeoverdue >= $graceperiod) {
|
2012-04-20 14:51:15 +01:00
|
|
|
$this->process_abandon($timestamp, $studentisonline);
|
2012-10-09 13:48:30 -04:00
|
|
|
} else {
|
|
|
|
// Overdue time has not yet expired
|
|
|
|
$this->update_timecheckstate($timeclose + $graceperiod);
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
}
|
|
|
|
return; // ... and we are done.
|
|
|
|
}
|
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
if ($this->attempt->state != self::IN_PROGRESS) {
|
2012-10-09 13:48:30 -04:00
|
|
|
$this->update_timecheckstate(null);
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
return; // Attempt is already in a final state.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we were in quiz_attempt::IN_PROGRESS, and time has now expired.
|
|
|
|
// Transition to the appropriate state.
|
|
|
|
switch ($this->quizobj->get_quiz()->overduehandling) {
|
|
|
|
case 'autosubmit':
|
|
|
|
$this->process_finish($timestamp, false);
|
|
|
|
return;
|
|
|
|
|
|
|
|
case 'graceperiod':
|
2012-04-20 14:51:15 +01:00
|
|
|
$this->process_going_overdue($timestamp, $studentisonline);
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
case 'autoabandon':
|
2012-04-20 14:51:15 +01:00
|
|
|
$this->process_abandon($timestamp, $studentisonline);
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
return;
|
|
|
|
}
|
2012-10-09 13:48:30 -04:00
|
|
|
|
|
|
|
// This is an overdue attempt with no overdue handling defined, so just abandon.
|
|
|
|
$this->process_abandon($timestamp, $studentisonline);
|
|
|
|
return;
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
}
|
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
2011-02-08 14:19:23 +00:00
|
|
|
* Process all the actions that were submitted as part of the current request.
|
|
|
|
*
|
2013-07-14 18:23:48 +07:00
|
|
|
* @param int $timestamp the timestamp that should be stored as the modifed
|
|
|
|
* time in the database for these actions. If null, will use the current time.
|
|
|
|
* @param bool $becomingoverdue
|
|
|
|
* @param array|null $simulatedresponses If not null, then we are testing, and this is an array of simulated data, keys are slot
|
|
|
|
* nos and values are arrays representing student responses which will be passed to
|
|
|
|
* question_definition::prepare_simulated_post_data method and then have the
|
|
|
|
* appropriate prefix added.
|
|
|
|
*/
|
|
|
|
public function process_submitted_actions($timestamp, $becomingoverdue = false, $simulatedresponses = null) {
|
2011-02-08 15:02:23 +00:00
|
|
|
global $DB;
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
|
|
|
|
$transaction = $DB->start_delegated_transaction();
|
|
|
|
|
2013-07-14 18:23:48 +07:00
|
|
|
if ($simulatedresponses !== null) {
|
|
|
|
$simulatedpostdata = $this->quba->prepare_simulated_post_data($simulatedresponses);
|
|
|
|
} else {
|
|
|
|
$simulatedpostdata = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->quba->process_all_actions($timestamp, $simulatedpostdata);
|
2011-02-08 14:19:23 +00:00
|
|
|
question_engine::save_questions_usage_by_activity($this->quba);
|
|
|
|
|
|
|
|
$this->attempt->timemodified = $timestamp;
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
if ($this->attempt->state == self::FINISHED) {
|
2011-02-08 14:19:23 +00:00
|
|
|
$this->attempt->sumgrades = $this->quba->get_total_mark();
|
|
|
|
}
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
if ($becomingoverdue) {
|
2012-04-20 14:51:15 +01:00
|
|
|
$this->process_going_overdue($timestamp, true);
|
|
|
|
} else {
|
|
|
|
$DB->update_record('quiz_attempts', $this->attempt);
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
}
|
2011-02-23 16:50:09 +00:00
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
if (!$this->is_preview() && $this->attempt->state == self::FINISHED) {
|
2011-02-08 14:19:23 +00:00
|
|
|
quiz_save_best_grade($this->get_quiz(), $this->get_userid());
|
2008-09-18 07:39:10 +00:00
|
|
|
}
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
|
|
|
|
$transaction->allow_commit();
|
2008-09-18 07:39:10 +00:00
|
|
|
}
|
|
|
|
|
2014-12-03 11:24:46 +00:00
|
|
|
/**
|
2015-03-09 18:40:18 +00:00
|
|
|
* Replace a question in an attempt with a new attempt at the same qestion.
|
|
|
|
* @param int $slot the questoin to restart.
|
|
|
|
* @param int $timestamp the timestamp to record for this action.
|
2014-12-03 11:24:46 +00:00
|
|
|
*/
|
2015-03-09 18:40:18 +00:00
|
|
|
public function process_redo_question($slot, $timestamp) {
|
2014-12-03 11:24:46 +00:00
|
|
|
global $DB;
|
|
|
|
|
2015-03-09 18:40:18 +00:00
|
|
|
if (!$this->can_question_be_redone_now($slot)) {
|
|
|
|
throw new coding_exception('Attempt to restart the question in slot ' . $slot .
|
|
|
|
' when it is not in a state to be restarted.');
|
|
|
|
}
|
2014-12-03 11:24:46 +00:00
|
|
|
|
2015-03-09 18:40:18 +00:00
|
|
|
$qubaids = new \mod_quiz\question\qubaids_for_users_attempts(
|
|
|
|
$this->get_quizid(), $this->get_userid());
|
2014-12-03 11:24:46 +00:00
|
|
|
|
2015-03-09 18:40:18 +00:00
|
|
|
$transaction = $DB->start_delegated_transaction();
|
2014-12-03 11:24:46 +00:00
|
|
|
|
2016-02-10 19:22:17 +00:00
|
|
|
// Choose the replacement question.
|
2015-03-09 18:40:18 +00:00
|
|
|
$questiondata = $DB->get_record('question',
|
|
|
|
array('id' => $this->slots[$slot]->questionid));
|
|
|
|
if ($questiondata->qtype != 'random') {
|
|
|
|
$newqusetionid = $questiondata->id;
|
|
|
|
} else {
|
|
|
|
$randomloader = new \core_question\bank\random_question_loader($qubaids, array());
|
|
|
|
$newqusetionid = $randomloader->get_next_question_id($questiondata->category,
|
|
|
|
(bool) $questiondata->questiontext);
|
|
|
|
if ($newqusetionid === null) {
|
|
|
|
throw new moodle_exception('notenoughrandomquestions', 'quiz',
|
|
|
|
$quizobj->view_url(), $questiondata);
|
|
|
|
}
|
2014-12-03 11:24:46 +00:00
|
|
|
}
|
|
|
|
|
2016-02-10 19:22:17 +00:00
|
|
|
// Add the question to the usage. It is important we do this before we choose a variant.
|
2015-03-09 18:40:18 +00:00
|
|
|
$newquestion = question_bank::load_question($newqusetionid);
|
2016-02-10 19:22:17 +00:00
|
|
|
$newslot = $this->quba->add_question_in_place_of_other($slot, $newquestion);
|
|
|
|
|
|
|
|
// Choose the variant.
|
2015-03-09 18:40:18 +00:00
|
|
|
if ($newquestion->get_num_variants() == 1) {
|
|
|
|
$variant = 1;
|
|
|
|
} else {
|
|
|
|
$variantstrategy = new core_question\engine\variants\least_used_strategy(
|
|
|
|
$this->quba, $qubaids);
|
|
|
|
$variant = $variantstrategy->choose_variant($newquestion->get_num_variants(),
|
|
|
|
$newquestion->get_variants_selection_seed());
|
2014-12-03 11:24:46 +00:00
|
|
|
}
|
2015-03-09 18:40:18 +00:00
|
|
|
|
2016-02-10 19:22:17 +00:00
|
|
|
// Start the question.
|
|
|
|
$this->quba->start_question($slot, $variant);
|
2015-03-09 18:40:18 +00:00
|
|
|
$this->quba->set_max_mark($newslot, 0);
|
|
|
|
$this->quba->set_question_attempt_metadata($newslot, 'originalslot', $slot);
|
|
|
|
question_engine::save_questions_usage_by_activity($this->quba);
|
|
|
|
|
|
|
|
$transaction->allow_commit();
|
2014-12-03 11:24:46 +00:00
|
|
|
}
|
|
|
|
|
2013-03-28 16:57:24 +00:00
|
|
|
/**
|
|
|
|
* Process all the autosaved data that was part of the current request.
|
|
|
|
*
|
|
|
|
* @param int $timestamp the timestamp that should be stored as the modifed
|
|
|
|
* time in the database for these actions. If null, will use the current time.
|
|
|
|
*/
|
|
|
|
public function process_auto_save($timestamp) {
|
|
|
|
global $DB;
|
|
|
|
|
|
|
|
$transaction = $DB->start_delegated_transaction();
|
|
|
|
|
|
|
|
$this->quba->process_all_autosaves($timestamp);
|
|
|
|
question_engine::save_questions_usage_by_activity($this->quba);
|
|
|
|
|
|
|
|
$transaction->allow_commit();
|
|
|
|
}
|
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
2011-02-08 14:19:23 +00:00
|
|
|
* Update the flagged state for all question_attempts in this usage, if their
|
|
|
|
* flagged state was changed in the request.
|
2009-07-23 09:20:33 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
public function save_question_flags() {
|
2012-08-15 10:25:25 +01:00
|
|
|
global $DB;
|
|
|
|
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
$transaction = $DB->start_delegated_transaction();
|
2011-02-08 14:19:23 +00:00
|
|
|
$this->quba->update_question_flags();
|
|
|
|
question_engine::save_questions_usage_by_activity($this->quba);
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
$transaction->allow_commit();
|
2011-02-08 14:19:23 +00:00
|
|
|
}
|
2008-12-10 09:11:30 +00:00
|
|
|
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
public function process_finish($timestamp, $processsubmitted) {
|
2012-04-20 14:51:15 +01:00
|
|
|
global $DB;
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
|
|
|
|
$transaction = $DB->start_delegated_transaction();
|
|
|
|
|
|
|
|
if ($processsubmitted) {
|
|
|
|
$this->quba->process_all_actions($timestamp);
|
|
|
|
}
|
2011-02-08 14:19:23 +00:00
|
|
|
$this->quba->finish_all_questions($timestamp);
|
2008-12-10 09:11:30 +00:00
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
question_engine::save_questions_usage_by_activity($this->quba);
|
2008-12-10 09:11:30 +00:00
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
$this->attempt->timemodified = $timestamp;
|
|
|
|
$this->attempt->timefinish = $timestamp;
|
|
|
|
$this->attempt->sumgrades = $this->quba->get_total_mark();
|
2012-04-20 14:51:15 +01:00
|
|
|
$this->attempt->state = self::FINISHED;
|
2012-10-09 13:48:30 -04:00
|
|
|
$this->attempt->timecheckstate = null;
|
2011-02-23 16:50:09 +00:00
|
|
|
$DB->update_record('quiz_attempts', $this->attempt);
|
2008-09-18 07:39:10 +00:00
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
if (!$this->is_preview()) {
|
2012-01-30 17:45:49 +00:00
|
|
|
quiz_save_best_grade($this->get_quiz(), $this->attempt->userid);
|
2011-07-04 18:58:34 +01:00
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Trigger event.
|
2013-09-03 13:38:44 +08:00
|
|
|
$this->fire_state_transition_event('\mod_quiz\event\attempt_submitted', $timestamp);
|
2011-07-04 18:58:34 +01:00
|
|
|
|
2011-10-07 17:43:15 +01:00
|
|
|
// Tell any access rules that care that the attempt is over.
|
|
|
|
$this->get_access_manager($timestamp)->current_attempt_finished();
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
|
|
|
|
$transaction->allow_commit();
|
|
|
|
}
|
|
|
|
|
2012-10-09 13:48:30 -04:00
|
|
|
/**
|
|
|
|
* Update this attempt timecheckstate if necessary.
|
|
|
|
* @param int|null the timecheckstate
|
|
|
|
*/
|
|
|
|
public function update_timecheckstate($time) {
|
|
|
|
global $DB;
|
|
|
|
if ($this->attempt->timecheckstate !== $time) {
|
|
|
|
$this->attempt->timecheckstate = $time;
|
2015-04-02 15:23:04 +01:00
|
|
|
$DB->set_field('quiz_attempts', 'timecheckstate', $time, array('id' => $this->attempt->id));
|
2012-10-09 13:48:30 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
/**
|
|
|
|
* Mark this attempt as now overdue.
|
|
|
|
* @param int $timestamp the time to deem as now.
|
2012-04-20 14:51:15 +01:00
|
|
|
* @param bool $studentisonline is the student currently interacting with Moodle?
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
*/
|
2012-04-20 14:51:15 +01:00
|
|
|
public function process_going_overdue($timestamp, $studentisonline) {
|
|
|
|
global $DB;
|
|
|
|
|
|
|
|
$transaction = $DB->start_delegated_transaction();
|
|
|
|
$this->attempt->timemodified = $timestamp;
|
|
|
|
$this->attempt->state = self::OVERDUE;
|
2012-10-09 13:48:30 -04:00
|
|
|
// If we knew the attempt close time, we could compute when the graceperiod ends.
|
|
|
|
// Instead we'll just fix it up through cron.
|
|
|
|
$this->attempt->timecheckstate = $timestamp;
|
2012-04-20 14:51:15 +01:00
|
|
|
$DB->update_record('quiz_attempts', $this->attempt);
|
|
|
|
|
2013-09-18 12:03:48 +08:00
|
|
|
$this->fire_state_transition_event('\mod_quiz\event\attempt_becameoverdue', $timestamp);
|
2012-04-20 14:51:15 +01:00
|
|
|
|
|
|
|
$transaction->allow_commit();
|
2014-10-15 16:31:25 +01:00
|
|
|
|
|
|
|
quiz_send_overdue_message($this);
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mark this attempt as abandoned.
|
|
|
|
* @param int $timestamp the time to deem as now.
|
2012-04-20 14:51:15 +01:00
|
|
|
* @param bool $studentisonline is the student currently interacting with Moodle?
|
MDL-3030 quiz overdue handling: trigger automatic state transitions.
Here, we catch all the places where a student might be accessing their
own attempts, and make sure any automatic state transitions that
should happen, do happen, before the student sees the attempt.
The places where we need to check this are view.php, startattempt.php
and processattempt.php.
We do not really need to check attempt.php or summary.php, because if
the student is on one of those pages, the JavaScript timer will
auto-submit when time expires, taking them to processattempt.php,
which will do the acutal work.
We intentionally do not trigger state transition when a teacher is
looking at a student's quiz attemp. We will trigger state transitions
on cron, but that is still to do.
Also, the body of the process_... methods still needs to be written.
2012-04-18 19:18:55 +01:00
|
|
|
*/
|
2012-04-20 14:51:15 +01:00
|
|
|
public function process_abandon($timestamp, $studentisonline) {
|
|
|
|
global $DB;
|
|
|
|
|
|
|
|
$transaction = $DB->start_delegated_transaction();
|
|
|
|
$this->attempt->timemodified = $timestamp;
|
|
|
|
$this->attempt->state = self::ABANDONED;
|
2012-10-09 13:48:30 -04:00
|
|
|
$this->attempt->timecheckstate = null;
|
2012-04-20 14:51:15 +01:00
|
|
|
$DB->update_record('quiz_attempts', $this->attempt);
|
|
|
|
|
2013-09-03 13:38:44 +08:00
|
|
|
$this->fire_state_transition_event('\mod_quiz\event\attempt_abandoned', $timestamp);
|
2012-04-20 14:51:15 +01:00
|
|
|
|
|
|
|
$transaction->allow_commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fire a state transition event.
|
2013-09-03 13:38:44 +08:00
|
|
|
* the same event information.
|
|
|
|
* @param string $eventclass the event class name.
|
2012-04-20 14:51:15 +01:00
|
|
|
* @param int $timestamp the timestamp to include in the event.
|
2013-09-03 13:38:44 +08:00
|
|
|
* @return void
|
2012-04-20 14:51:15 +01:00
|
|
|
*/
|
2013-09-03 13:38:44 +08:00
|
|
|
protected function fire_state_transition_event($eventclass, $timestamp) {
|
2012-04-20 14:51:15 +01:00
|
|
|
global $USER;
|
2014-01-17 12:52:34 +08:00
|
|
|
$quizrecord = $this->get_quiz();
|
2013-09-03 13:38:44 +08:00
|
|
|
$params = array(
|
|
|
|
'context' => $this->get_quizobj()->get_context(),
|
|
|
|
'courseid' => $this->get_courseid(),
|
|
|
|
'objectid' => $this->attempt->id,
|
|
|
|
'relateduserid' => $this->attempt->userid,
|
|
|
|
'other' => array(
|
2014-01-17 12:52:34 +08:00
|
|
|
'submitterid' => CLI_SCRIPT ? null : $USER->id,
|
|
|
|
'quizid' => $quizrecord->id
|
2013-09-03 13:38:44 +08:00
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
$event = $eventclass::create($params);
|
|
|
|
$event->add_record_snapshot('quiz', $this->get_quiz());
|
|
|
|
$event->add_record_snapshot('quiz_attempts', $this->get_attempt());
|
|
|
|
$event->trigger();
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
2008-07-08 16:33:47 +00:00
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Private methods =========================================================
|
2011-02-08 14:19:23 +00:00
|
|
|
|
2008-07-08 16:33:47 +00:00
|
|
|
/**
|
2009-07-23 09:20:33 +00:00
|
|
|
* Get a URL for a particular question on a particular page of the quiz.
|
|
|
|
* Used by {@link attempt_url()} and {@link review_url()}.
|
2008-07-08 16:33:47 +00:00
|
|
|
*
|
2009-07-23 09:20:33 +00:00
|
|
|
* @param string $script. Used in the URL like /mod/quiz/$script.php
|
2011-05-12 00:30:25 +01:00
|
|
|
* @param int $slot identifies the specific question on the page to jump to.
|
|
|
|
* 0 to just use the $page parameter.
|
|
|
|
* @param int $page -1 to look up the page number from the slot, otherwise
|
|
|
|
* the page number to go to.
|
2014-06-22 11:03:03 +01:00
|
|
|
* @param bool|null $showall if true, return a URL with showall=1, and not page number.
|
|
|
|
* if null, then an intelligent default will be chosen.
|
2011-02-23 16:25:25 +00:00
|
|
|
* @param int $thispage the page we are currently on. Links to questions on this
|
2009-07-23 09:20:33 +00:00
|
|
|
* page will just be a fragment #q123. -1 to disable this.
|
|
|
|
* @return The requested URL.
|
2008-07-08 16:33:47 +00:00
|
|
|
*/
|
2011-02-08 14:19:23 +00:00
|
|
|
protected function page_and_question_url($script, $slot, $page, $showall, $thispage) {
|
2014-06-22 11:03:03 +01:00
|
|
|
|
|
|
|
$defaultshowall = $this->get_default_show_all($script);
|
|
|
|
if ($showall === null && ($page == 0 || $page == -1)) {
|
|
|
|
$showall = $defaultshowall;
|
|
|
|
}
|
|
|
|
|
2012-05-04 12:40:21 +01:00
|
|
|
// Fix up $page.
|
2008-07-11 17:03:43 +00:00
|
|
|
if ($page == -1) {
|
2014-06-22 11:03:03 +01:00
|
|
|
if ($slot !== null && !$showall) {
|
2012-10-19 03:25:57 +08:00
|
|
|
$page = $this->get_question_page($slot);
|
2008-07-08 16:33:47 +00:00
|
|
|
} else {
|
|
|
|
$page = 0;
|
|
|
|
}
|
|
|
|
}
|
2011-02-08 14:19:23 +00:00
|
|
|
|
2008-07-08 16:33:47 +00:00
|
|
|
if ($showall) {
|
|
|
|
$page = 0;
|
|
|
|
}
|
2009-07-23 09:20:33 +00:00
|
|
|
|
2010-10-18 15:28:59 +00:00
|
|
|
// Add a fragment to scroll down to the question.
|
2011-02-08 14:19:23 +00:00
|
|
|
$fragment = '';
|
2014-06-22 11:03:03 +01:00
|
|
|
if ($slot !== null) {
|
2011-02-08 14:19:23 +00:00
|
|
|
if ($slot == reset($this->pagelayout[$page])) {
|
2009-07-09 07:35:03 +00:00
|
|
|
// First question on page, go to top.
|
2011-02-08 14:19:23 +00:00
|
|
|
$fragment = '#';
|
2009-07-09 07:35:03 +00:00
|
|
|
} else {
|
2011-02-08 14:19:23 +00:00
|
|
|
$fragment = '#q' . $slot;
|
2009-07-09 07:35:03 +00:00
|
|
|
}
|
2008-07-08 16:33:47 +00:00
|
|
|
}
|
2009-07-23 09:20:33 +00:00
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
// Work out the correct start to the URL.
|
|
|
|
if ($thispage == $page) {
|
|
|
|
return new moodle_url($fragment);
|
2008-06-27 18:04:48 +00:00
|
|
|
|
|
|
|
} else {
|
2011-02-08 14:19:23 +00:00
|
|
|
$url = new moodle_url('/mod/quiz/' . $script . '.php' . $fragment,
|
|
|
|
array('attempt' => $this->attempt->id));
|
2014-06-22 11:03:03 +01:00
|
|
|
if ($page == 0 && $showall != $defaultshowall) {
|
|
|
|
$url->param('showall', (int) $showall);
|
2011-02-08 14:19:23 +00:00
|
|
|
} else if ($page > 0) {
|
|
|
|
$url->param('page', $page);
|
|
|
|
}
|
|
|
|
return $url;
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
|
|
|
}
|
2016-01-21 17:21:45 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Process responses during an attempt at a quiz.
|
|
|
|
*
|
|
|
|
* @param int $timenow time when the processing started
|
|
|
|
* @param bool $finishattempt whether to finish the attempt or not
|
|
|
|
* @param bool $timeup true if form was submitted by timer
|
|
|
|
* @param int $thispage current page number
|
|
|
|
* @return string the attempt state once the data has been processed
|
|
|
|
* @since Moodle 3.1
|
|
|
|
* @throws moodle_exception
|
|
|
|
*/
|
|
|
|
public function process_attempt($timenow, $finishattempt, $timeup, $thispage) {
|
|
|
|
global $DB;
|
|
|
|
|
|
|
|
$transaction = $DB->start_delegated_transaction();
|
|
|
|
|
|
|
|
// If there is only a very small amount of time left, there is no point trying
|
|
|
|
// to show the student another page of the quiz. Just finish now.
|
|
|
|
$graceperiodmin = null;
|
|
|
|
$accessmanager = $this->get_access_manager($timenow);
|
|
|
|
$timeclose = $accessmanager->get_end_time($this->get_attempt());
|
|
|
|
|
|
|
|
// Don't enforce timeclose for previews.
|
|
|
|
if ($this->is_preview()) {
|
|
|
|
$timeclose = false;
|
|
|
|
}
|
|
|
|
$toolate = false;
|
|
|
|
if ($timeclose !== false && $timenow > $timeclose - QUIZ_MIN_TIME_TO_CONTINUE) {
|
|
|
|
$timeup = true;
|
|
|
|
$graceperiodmin = get_config('quiz', 'graceperiodmin');
|
|
|
|
if ($timenow > $timeclose + $graceperiodmin) {
|
|
|
|
$toolate = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If time is running out, trigger the appropriate action.
|
|
|
|
$becomingoverdue = false;
|
|
|
|
$becomingabandoned = false;
|
|
|
|
if ($timeup) {
|
|
|
|
if ($this->get_quiz()->overduehandling == 'graceperiod') {
|
|
|
|
if (is_null($graceperiodmin)) {
|
|
|
|
$graceperiodmin = get_config('quiz', 'graceperiodmin');
|
|
|
|
}
|
|
|
|
if ($timenow > $timeclose + $this->get_quiz()->graceperiod + $graceperiodmin) {
|
|
|
|
// Grace period has run out.
|
|
|
|
$finishattempt = true;
|
|
|
|
$becomingabandoned = true;
|
|
|
|
} else {
|
|
|
|
$becomingoverdue = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$finishattempt = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't log - we will end with a redirect to a page that is logged.
|
|
|
|
|
|
|
|
if (!$finishattempt) {
|
|
|
|
// Just process the responses for this page and go to the next page.
|
|
|
|
if (!$toolate) {
|
|
|
|
try {
|
|
|
|
$this->process_submitted_actions($timenow, $becomingoverdue);
|
|
|
|
|
|
|
|
} catch (question_out_of_sequence_exception $e) {
|
|
|
|
throw new moodle_exception('submissionoutofsequencefriendlymessage', 'question',
|
|
|
|
$this->attempt_url(null, $thispage));
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
// This sucks, if we display our own custom error message, there is no way
|
|
|
|
// to display the original stack trace.
|
|
|
|
$debuginfo = '';
|
|
|
|
if (!empty($e->debuginfo)) {
|
|
|
|
$debuginfo = $e->debuginfo;
|
|
|
|
}
|
|
|
|
throw new moodle_exception('errorprocessingresponses', 'question',
|
|
|
|
$this->attempt_url(null, $thispage), $e->getMessage(), $debuginfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$becomingoverdue) {
|
|
|
|
foreach ($this->get_slots() as $slot) {
|
|
|
|
if (optional_param('redoslot' . $slot, false, PARAM_BOOL)) {
|
|
|
|
$this->process_redo_question($slot, $timenow);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// The student is too late.
|
|
|
|
$this->process_going_overdue($timenow, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
$transaction->allow_commit();
|
|
|
|
|
|
|
|
return $becomingoverdue ? self::OVERDUE : self::IN_PROGRESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the quiz attempt record.
|
|
|
|
try {
|
|
|
|
if ($becomingabandoned) {
|
|
|
|
$this->process_abandon($timenow, true);
|
|
|
|
} else {
|
|
|
|
$this->process_finish($timenow, !$toolate);
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (question_out_of_sequence_exception $e) {
|
|
|
|
throw new moodle_exception('submissionoutofsequencefriendlymessage', 'question',
|
|
|
|
$this->attempt_url(null, $thispage));
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
// This sucks, if we display our own custom error message, there is no way
|
|
|
|
// to display the original stack trace.
|
|
|
|
$debuginfo = '';
|
|
|
|
if (!empty($e->debuginfo)) {
|
|
|
|
$debuginfo = $e->debuginfo;
|
|
|
|
}
|
|
|
|
throw new moodle_exception('errorprocessingresponses', 'question',
|
|
|
|
$this->attempt_url(null, $thispage), $e->getMessage(), $debuginfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send the user to the review page.
|
|
|
|
$transaction->allow_commit();
|
|
|
|
|
|
|
|
return $becomingabandoned ? self::ABANDONED : self::FINISHED;
|
|
|
|
}
|
|
|
|
|
2008-06-27 18:04:48 +00:00
|
|
|
}
|
2008-07-11 17:03:43 +00:00
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
|
2015-04-02 15:23:04 +01:00
|
|
|
/**
|
|
|
|
* Represents a heading in the navigation panel.
|
|
|
|
*
|
|
|
|
* @copyright 2015 The Open University
|
|
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
|
* @since Moodle 2.9
|
|
|
|
*/
|
|
|
|
class quiz_nav_section_heading implements renderable {
|
|
|
|
/** @var string the heading text. */
|
|
|
|
public $heading;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
* @param string $heading the heading text
|
|
|
|
*/
|
|
|
|
public function __construct($heading) {
|
|
|
|
$this->heading = $heading;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-04-15 18:26:35 +01:00
|
|
|
/**
|
|
|
|
* Represents a single link in the navigation panel.
|
|
|
|
*
|
|
|
|
* @copyright 2011 The Open University
|
|
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
|
* @since Moodle 2.1
|
|
|
|
*/
|
|
|
|
class quiz_nav_question_button implements renderable {
|
2015-01-13 13:55:33 +00:00
|
|
|
/** @var string id="..." to add to the HTML for this button. */
|
2011-04-15 18:26:35 +01:00
|
|
|
public $id;
|
2015-01-13 13:55:33 +00:00
|
|
|
/** @var string number to display in this button. Either the question number of 'i'. */
|
2011-04-15 18:26:35 +01:00
|
|
|
public $number;
|
2015-01-13 13:55:33 +00:00
|
|
|
/** @var string class to add to the class="" attribute to represnt the question state. */
|
2011-04-15 18:26:35 +01:00
|
|
|
public $stateclass;
|
2015-01-13 13:55:33 +00:00
|
|
|
/** @var string Textual description of the question state, e.g. to use as a tool tip. */
|
2011-04-15 18:26:35 +01:00
|
|
|
public $statestring;
|
2015-01-13 13:55:33 +00:00
|
|
|
/** @var int the page number this question is on. */
|
|
|
|
public $page;
|
|
|
|
/** @var bool true if this question is on the current page. */
|
2011-04-15 18:26:35 +01:00
|
|
|
public $currentpage;
|
2015-01-13 13:55:33 +00:00
|
|
|
/** @var bool true if this question has been flagged. */
|
2011-04-15 18:26:35 +01:00
|
|
|
public $flagged;
|
2015-01-13 13:55:33 +00:00
|
|
|
/** @var moodle_url the link this button goes to, or null if there should not be a link. */
|
2011-04-15 18:26:35 +01:00
|
|
|
public $url;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
|
|
|
* Represents the navigation panel, and builds a {@link block_contents} to allow
|
|
|
|
* it to be output.
|
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @copyright 2008 Tim Hunt
|
|
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
|
* @since Moodle 2.0
|
2009-07-23 09:20:33 +00:00
|
|
|
*/
|
2008-07-11 17:03:43 +00:00
|
|
|
abstract class quiz_nav_panel_base {
|
2010-08-10 13:30:32 +00:00
|
|
|
/** @var quiz_attempt */
|
2008-07-11 17:03:43 +00:00
|
|
|
protected $attemptobj;
|
2010-08-10 13:30:32 +00:00
|
|
|
/** @var question_display_options */
|
2008-07-11 17:03:43 +00:00
|
|
|
protected $options;
|
2010-08-10 13:30:32 +00:00
|
|
|
/** @var integer */
|
2008-07-11 17:03:43 +00:00
|
|
|
protected $page;
|
2010-08-10 13:30:32 +00:00
|
|
|
/** @var boolean */
|
2009-07-23 09:20:33 +00:00
|
|
|
protected $showall;
|
2008-07-11 17:03:43 +00:00
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
public function __construct(quiz_attempt $attemptobj,
|
|
|
|
question_display_options $options, $page, $showall) {
|
2010-08-10 13:30:32 +00:00
|
|
|
$this->attemptobj = $attemptobj;
|
|
|
|
$this->options = $options;
|
|
|
|
$this->page = $page;
|
|
|
|
$this->showall = $showall;
|
2008-07-11 17:03:43 +00:00
|
|
|
}
|
|
|
|
|
2015-04-02 15:23:04 +01:00
|
|
|
/**
|
|
|
|
* Get the buttons and section headings to go in the quiz navigation block.
|
|
|
|
* @return renderable[] the buttons, possibly interleaved with section headings.
|
|
|
|
*/
|
2011-04-15 18:26:35 +01:00
|
|
|
public function get_question_buttons() {
|
|
|
|
$buttons = array();
|
2011-02-08 14:19:23 +00:00
|
|
|
foreach ($this->attemptobj->get_slots() as $slot) {
|
2015-04-02 15:23:04 +01:00
|
|
|
if ($heading = $this->attemptobj->get_heading_before_slot($slot)) {
|
|
|
|
$buttons[] = new quiz_nav_section_heading(format_string($heading));
|
|
|
|
}
|
|
|
|
|
2011-02-08 14:19:23 +00:00
|
|
|
$qa = $this->attemptobj->get_question_attempt($slot);
|
|
|
|
$showcorrectness = $this->options->correctness && $qa->has_marks();
|
|
|
|
|
2011-04-15 18:26:35 +01:00
|
|
|
$button = new quiz_nav_question_button();
|
|
|
|
$button->id = 'quiznavbutton' . $slot;
|
2012-10-19 03:25:57 +08:00
|
|
|
$button->number = $this->attemptobj->get_question_number($slot);
|
2011-06-07 15:10:59 +01:00
|
|
|
$button->stateclass = $qa->get_state_class($showcorrectness);
|
2012-01-20 16:52:17 -08:00
|
|
|
$button->navmethod = $this->attemptobj->get_navigation_method();
|
2011-04-15 18:26:35 +01:00
|
|
|
if (!$showcorrectness && $button->stateclass == 'notanswered') {
|
|
|
|
$button->stateclass = 'complete';
|
|
|
|
}
|
2011-09-22 18:12:43 +01:00
|
|
|
$button->statestring = $this->get_state_string($qa, $showcorrectness);
|
2015-01-13 13:55:33 +00:00
|
|
|
$button->page = $this->attemptobj->get_question_page($slot);
|
|
|
|
$button->currentpage = $this->showall || $button->page == $this->page;
|
2011-04-15 18:26:35 +01:00
|
|
|
$button->flagged = $qa->is_flagged();
|
|
|
|
$button->url = $this->get_question_url($slot);
|
2015-04-23 15:57:29 +01:00
|
|
|
if ($this->attemptobj->is_blocked_by_previous_question($slot)) {
|
|
|
|
$button->url = null;
|
|
|
|
$button->stateclass = 'blocked';
|
|
|
|
$button->statestring = get_string('questiondependsonprevious', 'quiz');
|
|
|
|
}
|
2011-04-15 18:26:35 +01:00
|
|
|
$buttons[] = $button;
|
2011-02-08 14:19:23 +00:00
|
|
|
}
|
|
|
|
|
2011-04-15 18:26:35 +01:00
|
|
|
return $buttons;
|
2008-09-03 02:35:56 +00:00
|
|
|
}
|
|
|
|
|
2011-09-22 18:12:43 +01:00
|
|
|
protected function get_state_string(question_attempt $qa, $showcorrectness) {
|
2011-10-06 19:12:05 +01:00
|
|
|
if ($qa->get_question()->length > 0) {
|
2011-09-22 18:12:43 +01:00
|
|
|
return $qa->get_state_string($showcorrectness);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Special case handling for 'information' items.
|
|
|
|
if ($qa->get_state() == question_state::$todo) {
|
|
|
|
return get_string('notyetviewed', 'quiz');
|
|
|
|
} else {
|
|
|
|
return get_string('viewed', 'quiz');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-15 18:26:35 +01:00
|
|
|
public function render_before_button_bits(mod_quiz_renderer $output) {
|
2009-07-23 09:20:33 +00:00
|
|
|
return '';
|
|
|
|
}
|
2008-07-11 17:03:43 +00:00
|
|
|
|
2011-05-19 19:05:25 +01:00
|
|
|
abstract public function render_end_bits(mod_quiz_renderer $output);
|
|
|
|
|
|
|
|
protected function render_restart_preview_link($output) {
|
2011-04-15 18:26:35 +01:00
|
|
|
if (!$this->attemptobj->is_own_preview()) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
return $output->restart_preview_button(new moodle_url(
|
|
|
|
$this->attemptobj->start_attempt_url(), array('forcenew' => true)));
|
|
|
|
}
|
2008-07-11 17:03:43 +00:00
|
|
|
|
2011-03-23 16:22:25 +00:00
|
|
|
protected abstract function get_question_url($slot);
|
2009-07-23 09:20:33 +00:00
|
|
|
|
2011-04-15 18:26:35 +01:00
|
|
|
public function user_picture() {
|
|
|
|
global $DB;
|
2013-05-09 19:55:26 +05:30
|
|
|
if ($this->attemptobj->get_quiz()->showuserpicture == QUIZ_SHOWIMAGE_NONE) {
|
2011-06-20 10:26:01 +01:00
|
|
|
return null;
|
|
|
|
}
|
2009-01-07 06:32:13 +00:00
|
|
|
$user = $DB->get_record('user', array('id' => $this->attemptobj->get_userid()));
|
2011-04-15 18:26:35 +01:00
|
|
|
$userpicture = new user_picture($user);
|
|
|
|
$userpicture->courseid = $this->attemptobj->get_courseid();
|
2013-05-09 19:55:26 +05:30
|
|
|
if ($this->attemptobj->get_quiz()->showuserpicture == QUIZ_SHOWIMAGE_LARGE) {
|
|
|
|
$userpicture->size = true;
|
|
|
|
}
|
2011-04-15 18:26:35 +01:00
|
|
|
return $userpicture;
|
2008-07-11 17:03:43 +00:00
|
|
|
}
|
2013-10-17 14:46:58 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Return 'allquestionsononepage' as CSS class name when $showall is set,
|
|
|
|
* otherwise, return 'multipages' as CSS class name.
|
|
|
|
* @return string, CSS class name
|
|
|
|
*/
|
|
|
|
public function get_button_container_class() {
|
|
|
|
// Quiz navigation is set on 'Show all questions on one page'.
|
|
|
|
if ($this->showall) {
|
|
|
|
return 'allquestionsononepage';
|
|
|
|
}
|
|
|
|
// Quiz navigation is set on 'Show one page at a time'.
|
|
|
|
return 'multipages';
|
|
|
|
}
|
2008-07-11 17:03:43 +00:00
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
|
|
|
* Specialisation of {@link quiz_nav_panel_base} for the attempt quiz page.
|
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @copyright 2008 Tim Hunt
|
|
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
|
* @since Moodle 2.0
|
2009-07-23 09:20:33 +00:00
|
|
|
*/
|
2008-07-11 17:03:43 +00:00
|
|
|
class quiz_attempt_nav_panel extends quiz_nav_panel_base {
|
2011-04-15 18:26:35 +01:00
|
|
|
public function get_question_url($slot) {
|
2012-01-20 16:52:17 -08:00
|
|
|
if ($this->attemptobj->can_navigate_to($slot)) {
|
|
|
|
return $this->attemptobj->attempt_url($slot, -1, $this->page);
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
2008-07-11 17:03:43 +00:00
|
|
|
}
|
|
|
|
|
2011-04-15 18:26:35 +01:00
|
|
|
public function render_before_button_bits(mod_quiz_renderer $output) {
|
|
|
|
return html_writer::tag('div', get_string('navnojswarning', 'quiz'),
|
|
|
|
array('id' => 'quiznojswarning'));
|
2008-07-11 17:03:43 +00:00
|
|
|
}
|
|
|
|
|
2011-04-15 18:26:35 +01:00
|
|
|
public function render_end_bits(mod_quiz_renderer $output) {
|
|
|
|
return html_writer::link($this->attemptobj->summary_url(),
|
2011-06-07 14:29:11 +01:00
|
|
|
get_string('endtest', 'quiz'), array('class' => 'endtestlink')) .
|
2011-11-15 17:25:26 +00:00
|
|
|
$output->countdown_timer($this->attemptobj, time()) .
|
2011-05-19 19:05:25 +01:00
|
|
|
$this->render_restart_preview_link($output);
|
2008-07-11 17:03:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-23 16:25:25 +00:00
|
|
|
|
2009-07-23 09:20:33 +00:00
|
|
|
/**
|
|
|
|
* Specialisation of {@link quiz_nav_panel_base} for the review quiz page.
|
|
|
|
*
|
2011-02-23 16:25:25 +00:00
|
|
|
* @copyright 2008 Tim Hunt
|
|
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
|
* @since Moodle 2.0
|
2009-07-23 09:20:33 +00:00
|
|
|
*/
|
2008-07-11 17:03:43 +00:00
|
|
|
class quiz_review_nav_panel extends quiz_nav_panel_base {
|
2011-04-15 18:26:35 +01:00
|
|
|
public function get_question_url($slot) {
|
2011-02-08 14:19:23 +00:00
|
|
|
return $this->attemptobj->review_url($slot, -1, $this->showall, $this->page);
|
2008-07-11 17:03:43 +00:00
|
|
|
}
|
|
|
|
|
2011-04-15 18:26:35 +01:00
|
|
|
public function render_end_bits(mod_quiz_renderer $output) {
|
2009-07-23 09:20:33 +00:00
|
|
|
$html = '';
|
|
|
|
if ($this->attemptobj->get_num_pages() > 1) {
|
|
|
|
if ($this->showall) {
|
2011-04-15 18:26:35 +01:00
|
|
|
$html .= html_writer::link($this->attemptobj->review_url(null, 0, false),
|
|
|
|
get_string('showeachpage', 'quiz'));
|
2009-07-23 09:20:33 +00:00
|
|
|
} else {
|
2011-04-15 18:26:35 +01:00
|
|
|
$html .= html_writer::link($this->attemptobj->review_url(null, 0, true),
|
|
|
|
get_string('showall', 'quiz'));
|
2009-07-23 09:20:33 +00:00
|
|
|
}
|
|
|
|
}
|
2012-08-13 13:48:22 +01:00
|
|
|
$html .= $output->finish_review_link($this->attemptobj);
|
2011-05-19 19:05:25 +01:00
|
|
|
$html .= $this->render_restart_preview_link($output);
|
2008-07-11 17:03:43 +00:00
|
|
|
return $html;
|
|
|
|
}
|
|
|
|
}
|