This commit is contained in:
Andrew Nicols 2023-01-11 10:32:42 +08:00
commit 6f1c9d3b53
141 changed files with 7213 additions and 6579 deletions

View File

@ -16,6 +16,9 @@
namespace tool_recyclebin;
use mod_quiz\quiz_attempt;
use stdClass;
/**
* Recycle bin course tests.
*
@ -237,7 +240,7 @@ class course_bin_test extends \advanced_testcase {
$attempts = quiz_get_user_attempts($cm->instance, $student->id);
$this->assertEquals(1, count($attempts));
$attempt = array_pop($attempts);
$attemptobj = \quiz_attempt::create($attempt->id);
$attemptobj = quiz_attempt::create($attempt->id);
$this->assertEquals($student->id, $attemptobj->get_userid());
$this->assertEquals(true, $attemptobj->is_finished());
}
@ -300,17 +303,17 @@ class course_bin_test extends \advanced_testcase {
quiz_add_quiz_question($numq->id, $quiz);
// Create quiz attempt.
$quizobj = \quiz::create($quiz->id, $student->id);
$quizobj = \mod_quiz\quiz_settings::create($quiz->id, $student->id);
$quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
$timenow = time();
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $student->id);
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
quiz_attempt_save_started($quizobj, $quba, $attempt);
$attemptobj = \quiz_attempt::create($attempt->id);
$attemptobj = quiz_attempt::create($attempt->id);
$tosubmit = array(1 => array('answer' => '0'));
$attemptobj->process_submitted_actions($timenow, false, $tosubmit);
$attemptobj = \quiz_attempt::create($attempt->id);
$attemptobj = quiz_attempt::create($attempt->id);
$attemptobj->process_finish($timenow, false);
}
}

View File

@ -16,6 +16,8 @@
namespace core_backup;
use mod_quiz\quiz_attempt;
defined('MOODLE_INTERNAL') || die();
global $CFG;
@ -379,7 +381,7 @@ class restore_stepslib_date_test extends \restore_date_testcase {
// Make a user to do the quiz.
$user1 = $this->getDataGenerator()->create_user();
$quizobj = \quiz::create($quiz->id, $user1->id);
$quizobj = \mod_quiz\quiz_settings::create($quiz->id, $user1->id);
// Start the attempt.
$quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
@ -393,7 +395,7 @@ class restore_stepslib_date_test extends \restore_date_testcase {
quiz_attempt_save_started($quizobj, $quba, $attempt);
// Process some responses from the student.
$attemptobj = \quiz_attempt::create($attempt->id);
$attemptobj = quiz_attempt::create($attempt->id);
$prefix1 = $quba->get_field_prefix(1);
$prefix2 = $quba->get_field_prefix(2);
@ -404,7 +406,7 @@ class restore_stepslib_date_test extends \restore_date_testcase {
$attemptobj->process_submitted_actions($timenow, false, $tosubmit);
// Finish the attempt.
$attemptobj = \quiz_attempt::create($attempt->id);
$attemptobj = quiz_attempt::create($attempt->id);
$attemptobj->process_finish($timenow, false);
$questionattemptstepdates = [];
@ -419,7 +421,7 @@ class restore_stepslib_date_test extends \restore_date_testcase {
// Get the quiz for this new restored course.
$quizdata = $DB->get_record('quiz', ['course' => $newcourseid]);
$quizobj = \quiz::create($quizdata->id, $user1->id);
$quizobj = \mod_quiz\quiz_settings::create($quizdata->id, $user1->id);
$questionusage = $DB->get_record('question_usages', [
'component' => 'mod_quiz',

View File

@ -841,7 +841,7 @@ function question_move_category_to_context($categoryid, $oldcontextid, $newconte
/**
* Given a list of ids, load the basic information about a set of questions from
* the questions table. The $join and $extrafields arguments can be used together
* to pull in extra data. See, for example, the usage in mod/quiz/attemptlib.php, and
* to pull in extra data. See, for example, the usage in {@see \mod_quiz\quiz_attempt}, and
* read the code below to see how the SQL is assembled. Throws exceptions on error.
*
* @param array $questionids array of question ids to load. If null, then all

View File

@ -15,549 +15,11 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Classes to enforce the various access rules that can apply to a quiz.
* File only retained to prevent fatal errors in code that tries to require/include this.
*
* @package mod_quiz
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-76612 delete this file as part of Moodle 4.6 development.
* @deprecated This file is no longer required in Moodle 4.2+.
*/
defined('MOODLE_INTERNAL') || die();
use mod_quiz\question\display_options;
/**
* This class keeps track of the various access rules that apply to a particular
* quiz, with convinient methods for seeing whether access is allowed.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.2
*/
class quiz_access_manager {
/** @var quiz the quiz settings object. */
protected $quizobj;
/** @var int the time to be considered as 'now'. */
protected $timenow;
/** @var array of quiz_access_rule_base. */
protected $rules = array();
/**
* Create an instance for a particular quiz.
* @param object $quizobj An instance of the class quiz from attemptlib.php.
* The quiz we will be controlling access to.
* @param int $timenow The time to use as 'now'.
* @param bool $canignoretimelimits Whether this user is exempt from time
* limits (has_capability('mod/quiz:ignoretimelimits', ...)).
*/
public function __construct($quizobj, $timenow, $canignoretimelimits) {
$this->quizobj = $quizobj;
$this->timenow = $timenow;
$this->rules = $this->make_rules($quizobj, $timenow, $canignoretimelimits);
}
/**
* Make all the rules relevant to a particular quiz.
* @param quiz $quizobj information about the quiz in question.
* @param int $timenow the time that should be considered as 'now'.
* @param bool $canignoretimelimits whether the current user is exempt from
* time limits by the mod/quiz:ignoretimelimits capability.
* @return array of {@link quiz_access_rule_base}s.
*/
protected function make_rules($quizobj, $timenow, $canignoretimelimits) {
$rules = array();
foreach (self::get_rule_classes() as $ruleclass) {
$rule = $ruleclass::make($quizobj, $timenow, $canignoretimelimits);
if ($rule) {
$rules[$ruleclass] = $rule;
}
}
$superceededrules = array();
foreach ($rules as $rule) {
$superceededrules += $rule->get_superceded_rules();
}
foreach ($superceededrules as $superceededrule) {
unset($rules['quizaccess_' . $superceededrule]);
}
return $rules;
}
/**
* @return array of all the installed rule class names.
*/
protected static function get_rule_classes() {
return core_component::get_plugin_list_with_class('quizaccess', '', 'rule.php');
}
/**
* Add any form fields that the access rules require to the settings form.
*
* Note that the standard plugins do not use this mechanism, becuase all their
* settings are stored in the quiz table.
*
* @param mod_quiz_mod_form $quizform the quiz settings form that is being built.
* @param MoodleQuickForm $mform the wrapped MoodleQuickForm.
*/
public static function add_settings_form_fields(
mod_quiz_mod_form $quizform, MoodleQuickForm $mform) {
foreach (self::get_rule_classes() as $rule) {
$rule::add_settings_form_fields($quizform, $mform);
}
}
/**
* The the options for the Browser security settings menu.
*
* @return array key => lang string.
*/
public static function get_browser_security_choices() {
$options = array('-' => get_string('none', 'quiz'));
foreach (self::get_rule_classes() as $rule) {
$options += $rule::get_browser_security_choices();
}
return $options;
}
/**
* Validate the data from any form fields added using {@link add_settings_form_fields()}.
* @param array $errors the errors found so far.
* @param array $data the submitted form data.
* @param array $files information about any uploaded files.
* @param mod_quiz_mod_form $quizform the quiz form object.
* @return array $errors the updated $errors array.
*/
public static function validate_settings_form_fields(array $errors,
array $data, $files, mod_quiz_mod_form $quizform) {
foreach (self::get_rule_classes() as $rule) {
$errors = $rule::validate_settings_form_fields($errors, $data, $files, $quizform);
}
return $errors;
}
/**
* Save any submitted settings when the quiz settings form is submitted.
*
* Note that the standard plugins do not use this mechanism because their
* settings are stored in the quiz table.
*
* @param object $quiz the data from the quiz form, including $quiz->id
* which is the id of the quiz being saved.
*/
public static function save_settings($quiz) {
foreach (self::get_rule_classes() as $rule) {
$rule::save_settings($quiz);
}
}
/**
* Delete any rule-specific settings when the quiz is deleted.
*
* Note that the standard plugins do not use this mechanism because their
* settings are stored in the quiz table.
*
* @param object $quiz the data from the database, including $quiz->id
* which is the id of the quiz being deleted.
* @since Moodle 2.7.1, 2.6.4, 2.5.7
*/
public static function delete_settings($quiz) {
foreach (self::get_rule_classes() as $rule) {
$rule::delete_settings($quiz);
}
}
/**
* Build the SQL for loading all the access settings in one go.
* @param int $quizid the quiz id.
* @param string $basefields initial part of the select list.
* @return array with two elements, the sql and the placeholder values.
* If $basefields is '' then you must allow for the possibility that
* there is no data to load, in which case this method returns $sql = ''.
*/
protected static function get_load_sql($quizid, $rules, $basefields) {
$allfields = $basefields;
$alljoins = '{quiz} quiz';
$allparams = array('quizid' => $quizid);
foreach ($rules as $rule) {
list($fields, $joins, $params) = $rule::get_settings_sql($quizid);
if ($fields) {
if ($allfields) {
$allfields .= ', ';
}
$allfields .= $fields;
}
if ($joins) {
$alljoins .= ' ' . $joins;
}
if ($params) {
$allparams += $params;
}
}
if ($allfields === '') {
return array('', array());
}
return array("SELECT $allfields FROM $alljoins WHERE quiz.id = :quizid", $allparams);
}
/**
* Load any settings required by the access rules. We try to do this with
* a single DB query.
*
* Note that the standard plugins do not use this mechanism, becuase all their
* settings are stored in the quiz table.
*
* @param int $quizid the quiz id.
* @return array setting value name => value. The value names should all
* start with the name of the corresponding plugin to avoid collisions.
*/
public static function load_settings($quizid) {
global $DB;
$rules = self::get_rule_classes();
list($sql, $params) = self::get_load_sql($quizid, $rules, '');
if ($sql) {
$data = (array) $DB->get_record_sql($sql, $params);
} else {
$data = array();
}
foreach ($rules as $rule) {
$data += $rule::get_extra_settings($quizid);
}
return $data;
}
/**
* Load the quiz settings and any settings required by the access rules.
* We try to do this with a single DB query.
*
* Note that the standard plugins do not use this mechanism, becuase all their
* settings are stored in the quiz table.
*
* @param int $quizid the quiz id.
* @return object mdl_quiz row with extra fields.
*/
public static function load_quiz_and_settings($quizid) {
global $DB;
$rules = self::get_rule_classes();
list($sql, $params) = self::get_load_sql($quizid, $rules, 'quiz.*');
$quiz = $DB->get_record_sql($sql, $params, MUST_EXIST);
foreach ($rules as $rule) {
foreach ($rule::get_extra_settings($quizid) as $name => $value) {
$quiz->$name = $value;
}
}
return $quiz;
}
/**
* @return array the class names of all the active rules. Mainly useful for
* debugging.
*/
public function get_active_rule_names() {
$classnames = array();
foreach ($this->rules as $rule) {
$classnames[] = get_class($rule);
}
return $classnames;
}
/**
* Accumulates an array of messages.
* @param array $messages the current list of messages.
* @param string|array $new the new messages or messages.
* @return array the updated array of messages.
*/
protected function accumulate_messages($messages, $new) {
if (is_array($new)) {
$messages = array_merge($messages, $new);
} else if (is_string($new) && $new) {
$messages[] = $new;
}
return $messages;
}
/**
* Provide a description of the rules that apply to this quiz, such
* as is shown at the top of the quiz view page. Note that not all
* rules consider themselves important enough to output a description.
*
* @return array an array of description messages which may be empty. It
* would be sensible to output each one surrounded by &lt;p> tags.
*/
public function describe_rules() {
$result = array();
foreach ($this->rules as $rule) {
$result = $this->accumulate_messages($result, $rule->description());
}
return $result;
}
/**
* Whether or not a user should be allowed to start a new attempt at this quiz now.
* If there are any restrictions in force now, return an array of reasons why access
* should be blocked. If access is OK, return false.
*
* @param int $numattempts the number of previous attempts this user has made.
* @param object|false $lastattempt information about the user's last completed attempt.
* if there is not a previous attempt, the false is passed.
* @return mixed An array of reason why access is not allowed, or an empty array
* (== false) if access should be allowed.
*/
public function prevent_new_attempt($numprevattempts, $lastattempt) {
$reasons = array();
foreach ($this->rules as $rule) {
$reasons = $this->accumulate_messages($reasons,
$rule->prevent_new_attempt($numprevattempts, $lastattempt));
}
return $reasons;
}
/**
* Whether the user should be blocked from starting a new attempt or continuing
* an attempt now. If there are any restrictions in force now, return an array
* of reasons why access should be blocked. If access is OK, return false.
*
* @return mixed An array of reason why access is not allowed, or an empty array
* (== false) if access should be allowed.
*/
public function prevent_access() {
$reasons = array();
foreach ($this->rules as $rule) {
$reasons = $this->accumulate_messages($reasons, $rule->prevent_access());
}
return $reasons;
}
/**
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
* @return bool whether a check is required before the user starts/continues
* their attempt.
*/
public function is_preflight_check_required($attemptid) {
foreach ($this->rules as $rule) {
if ($rule->is_preflight_check_required($attemptid)) {
return true;
}
}
return false;
}
/**
* Build the form required to do the pre-flight checks.
* @param moodle_url $url the form action URL.
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
* @return mod_quiz_preflight_check_form the form.
*/
public function get_preflight_check_form(moodle_url $url, $attemptid) {
// This form normally wants POST submissins. However, it also needs to
// accept GET submissions. Since formslib is strict, we have to detect
// which case we are in, and set the form property appropriately.
$method = 'post';
if (!empty($_GET['_qf__mod_quiz_preflight_check_form'])) {
$method = 'get';
}
return new mod_quiz_preflight_check_form($url->out_omit_querystring(),
array('rules' => $this->rules, 'quizobj' => $this->quizobj,
'attemptid' => $attemptid, 'hidden' => $url->params()), $method);
}
/**
* The pre-flight check has passed. This is a chance to record that fact in
* some way.
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
*/
public function notify_preflight_check_passed($attemptid) {
foreach ($this->rules as $rule) {
$rule->notify_preflight_check_passed($attemptid);
}
}
/**
* Inform the rules that the current attempt is finished. This is use, for example
* by the password rule, to clear the flag in the session.
*/
public function current_attempt_finished() {
foreach ($this->rules as $rule) {
$rule->current_attempt_finished();
}
}
/**
* Do any of the rules mean that this student will no be allowed any further attempts at this
* quiz. Used, for example, to change the label by the grade displayed on the view page from
* 'your current grade is' to 'your final grade is'.
*
* @param int $numattempts the number of previous attempts this user has made.
* @param object $lastattempt information about the user's last completed attempt.
* @return bool true if there is no way the user will ever be allowed to attempt
* this quiz again.
*/
public function is_finished($numprevattempts, $lastattempt) {
foreach ($this->rules as $rule) {
if ($rule->is_finished($numprevattempts, $lastattempt)) {
return true;
}
}
return false;
}
/**
* Sets up the attempt (review or summary) page with any properties required
* by the access rules.
*
* @param moodle_page $page the page object to initialise.
*/
public function setup_attempt_page($page) {
foreach ($this->rules as $rule) {
$rule->setup_attempt_page($page);
}
}
/**
* Compute when the attempt must be submitted.
*
* @param object $attempt the data from the relevant quiz_attempts row.
* @return int|false the attempt close time.
* False if there is no limit.
*/
public function get_end_time($attempt) {
$timeclose = false;
foreach ($this->rules as $rule) {
$ruletimeclose = $rule->end_time($attempt);
if ($ruletimeclose !== false && ($timeclose === false || $ruletimeclose < $timeclose)) {
$timeclose = $ruletimeclose;
}
}
return $timeclose;
}
/**
* Compute what should be displayed to the user for time remaining in this attempt.
*
* @param object $attempt the data from the relevant quiz_attempts row.
* @param int $timenow the time to consider as 'now'.
* @return int|false the number of seconds remaining for this attempt.
* False if no limit should be displayed.
*/
public function get_time_left_display($attempt, $timenow) {
$timeleft = false;
foreach ($this->rules as $rule) {
$ruletimeleft = $rule->time_left_display($attempt, $timenow);
if ($ruletimeleft !== false && ($timeleft === false || $ruletimeleft < $timeleft)) {
$timeleft = $ruletimeleft;
}
}
return $timeleft;
}
/**
* @return bolean if this quiz should only be shown to students in a popup window.
*/
public function attempt_must_be_in_popup() {
foreach ($this->rules as $rule) {
if ($rule->attempt_must_be_in_popup()) {
return true;
}
}
return false;
}
/**
* @return array any options that are required for showing the attempt page
* in a popup window.
*/
public function get_popup_options() {
$options = array();
foreach ($this->rules as $rule) {
$options += $rule->get_popup_options();
}
return $options;
}
/**
* Send the user back to the quiz view page. Normally this is just a redirect, but
* If we were in a secure window, we close this window, and reload the view window we came from.
*
* This method does not return;
*
* @param mod_quiz_renderer $output the quiz renderer.
* @param string $message optional message to output while redirecting.
*/
public function back_to_view_page($output, $message = '') {
if ($this->attempt_must_be_in_popup()) {
echo $output->close_attempt_popup($this->quizobj->view_url(), $message);
die();
} else {
redirect($this->quizobj->view_url(), $message);
}
}
/**
* Make some text into a link to review the quiz, if that is appropriate.
*
* @param string $linktext some text.
* @param object $attempt the attempt object
* @return string some HTML, the $linktext either unmodified or wrapped in a
* link to the review page.
*/
public function make_review_link($attempt, $reviewoptions, $output) {
// If the attempt is still open, don't link.
if (in_array($attempt->state, array(quiz_attempt::IN_PROGRESS, quiz_attempt::OVERDUE))) {
return $output->no_review_message('');
}
$when = quiz_attempt_state($this->quizobj->get_quiz(), $attempt);
$reviewoptions = display_options::make_from_quiz(
$this->quizobj->get_quiz(), $when);
if (!$reviewoptions->attempt) {
return $output->no_review_message($this->quizobj->cannot_review_message($when, true));
} else {
return $output->review_link($this->quizobj->review_url($attempt->id),
$this->attempt_must_be_in_popup(), $this->get_popup_options());
}
}
/**
* Run the preflight checks using the given data in all the rules supporting them.
*
* @param array $data passed data for validation
* @param array $files un-used, Moodle seems to not support it anymore
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
* @return array of errors, empty array means no erros
* @since Moodle 3.1
*/
public function validate_preflight_check($data, $files, $attemptid) {
$errors = array();
foreach ($this->rules as $rule) {
if ($rule->is_preflight_check_required($attemptid)) {
$errors = $rule->validate_preflight_check($data, $files, $errors, $attemptid);
}
}
return $errors;
}
}
debugging('This file is no longer required in Moodle 4.2+. Please do not include/require it.', DEBUG_DEVELOPER);

View File

@ -15,58 +15,11 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines the form that limits student's access to attempt a quiz.
* File only retained to prevent fatal errors in code that tries to require/include this.
*
* @package mod_quiz
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-76612 delete this file as part of Moodle 4.6 development.
* @deprecated This file is no longer required in Moodle 4.2+.
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* A form that limits student's access to attempt a quiz.
*
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_preflight_check_form extends moodleform {
protected function definition() {
$mform = $this->_form;
$this->_form->updateAttributes(array('id' => 'mod_quiz_preflight_form'));
foreach ($this->_customdata['hidden'] as $name => $value) {
if ($name === 'sesskey') {
continue;
}
$mform->addElement('hidden', $name, $value);
$mform->setType($name, PARAM_INT);
}
foreach ($this->_customdata['rules'] as $rule) {
if ($rule->is_preflight_check_required($this->_customdata['attemptid'])) {
$rule->add_preflight_check_form_fields($this, $mform,
$this->_customdata['attemptid']);
}
}
$this->add_action_buttons(true, get_string('startattempt', 'quiz'));
$this->set_display_vertical();
$mform->setDisableShortforms();
}
public function validation($data, $files) {
$errors = parent::validation($data, $files);
$timenow = time();
$accessmanager = $this->_customdata['quizobj']->get_access_manager($timenow);
$errors = array_merge($errors, $accessmanager->validate_preflight_check($data, $files, $this->_customdata['attemptid']));
return $errors;
}
}
debugging('This file is no longer required in Moodle 4.2+. Please do not include/require it.', DEBUG_DEVELOPER);

View File

@ -15,322 +15,13 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Base class for rules that restrict the ability to attempt a quiz.
* File only retained to prevent fatal errors in code that tries to require/include this.
*
* @package mod_quiz
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-76612 delete this file as part of Moodle 4.6 development.
* @deprecated This file is no longer required in Moodle 4.2+.
*/
defined('MOODLE_INTERNAL') || die();
debugging('This file is no longer required in Moodle 4.2+. Please do not include/require it.', DEBUG_DEVELOPER);
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
/**
* A base class that defines the interface for the various quiz access rules.
* Most of the methods are defined in a slightly unnatural way because we either
* want to say that access is allowed, or explain the reason why it is block.
* Therefore instead of is_access_allowed(...) we have prevent_access(...) that
* return false if access is permitted, or a string explanation (which is treated
* as true) if access should be blocked. Slighly unnatural, but actually the easiest
* way to implement this.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.2
*/
abstract class quiz_access_rule_base {
/** @var stdClass the quiz settings. */
protected $quiz;
/** @var quiz the quiz object. */
protected $quizobj;
/** @var int the time to use as 'now'. */
protected $timenow;
/**
* Create an instance of this rule for a particular quiz.
* @param quiz $quizobj information about the quiz in question.
* @param int $timenow the time that should be considered as 'now'.
*/
public function __construct($quizobj, $timenow) {
$this->quizobj = $quizobj;
$this->quiz = $quizobj->get_quiz();
$this->timenow = $timenow;
}
/**
* Return an appropriately configured instance of this rule, if it is applicable
* to the given quiz, otherwise return null.
* @param quiz $quizobj information about the quiz in question.
* @param int $timenow the time that should be considered as 'now'.
* @param bool $canignoretimelimits whether the current user is exempt from
* time limits by the mod/quiz:ignoretimelimits capability.
* @return quiz_access_rule_base|null the rule, if applicable, else null.
*/
public static function make(quiz $quizobj, $timenow, $canignoretimelimits) {
return null;
}
/**
* Whether or not a user should be allowed to start a new attempt at this quiz now.
* @param int $numattempts the number of previous attempts this user has made.
* @param object $lastattempt information about the user's last completed attempt.
* @return string false if access should be allowed, a message explaining the
* reason if access should be prevented.
*/
public function prevent_new_attempt($numprevattempts, $lastattempt) {
return false;
}
/**
* Whether the user should be blocked from starting a new attempt or continuing
* an attempt now.
* @return string false if access should be allowed, a message explaining the
* reason if access should be prevented.
*/
public function prevent_access() {
return false;
}
/**
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
* @return bool whether a check is required before the user starts/continues
* their attempt.
*/
public function is_preflight_check_required($attemptid) {
return false;
}
/**
* Add any field you want to pre-flight check form. You should only do
* something here if {@link is_preflight_check_required()} returned true.
*
* @param mod_quiz_preflight_check_form $quizform the form being built.
* @param MoodleQuickForm $mform The wrapped MoodleQuickForm.
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
*/
public function add_preflight_check_form_fields(mod_quiz_preflight_check_form $quizform,
MoodleQuickForm $mform, $attemptid) {
// Do nothing by default.
}
/**
* Validate the pre-flight check form submission. You should only do
* something here if {@link is_preflight_check_required()} returned true.
*
* If the form validates, the user will be allowed to continue.
*
* @param array $data the submitted form data.
* @param array $files any files in the submission.
* @param array $errors the list of validation errors that is being built up.
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
* @return array the update $errors array;
*/
public function validate_preflight_check($data, $files, $errors, $attemptid) {
return $errors;
}
/**
* The pre-flight check has passed. This is a chance to record that fact in
* some way.
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
*/
public function notify_preflight_check_passed($attemptid) {
// Do nothing by default.
}
/**
* This is called when the current attempt at the quiz is finished. This is
* used, for example by the password rule, to clear the flag in the session.
*/
public function current_attempt_finished() {
// Do nothing by default.
}
/**
* Information, such as might be shown on the quiz view page, relating to this restriction.
* There is no obligation to return anything. If it is not appropriate to tell students
* about this rule, then just return ''.
* @return mixed a message, or array of messages, explaining the restriction
* (may be '' if no message is appropriate).
*/
public function description() {
return '';
}
/**
* If this rule can determine that this user will never be allowed another attempt at
* this quiz, then return true. This is used so we can know whether to display a
* final grade on the view page. This will only be called if there is not a currently
* active attempt for this user.
* @param int $numattempts the number of previous attempts this user has made.
* @param object $lastattempt information about the user's last completed attempt.
* @return bool true if this rule means that this user will never be allowed another
* attempt at this quiz.
*/
public function is_finished($numprevattempts, $lastattempt) {
return false;
}
/**
* If, because of this rule, the user has to finish their attempt by a certain time,
* you should override this method to return the attempt end time.
* @param object $attempt the current attempt
* @return mixed the attempt close time, or false if there is no close time.
*/
public function end_time($attempt) {
return false;
}
/**
* If the user should be shown a different amount of time than $timenow - $this->end_time(), then
* override this method. This is useful if the time remaining is large enough to be omitted.
* @param object $attempt the current attempt
* @param int $timenow the time now. We don't use $this->timenow, so we can
* give the user a more accurate indication of how much time is left.
* @return mixed the time left in seconds (can be negative) or false if there is no limit.
*/
public function time_left_display($attempt, $timenow) {
$endtime = $this->end_time($attempt);
if ($endtime === false) {
return false;
}
return $endtime - $timenow;
}
/**
* @return boolean whether this rule requires that the attemp (and review)
* pages must be displayed in a pop-up window.
*/
public function attempt_must_be_in_popup() {
return false;
}
/**
* @return array any options that are required for showing the attempt page
* in a popup window.
*/
public function get_popup_options() {
return array();
}
/**
* Sets up the attempt (review or summary) page with any special extra
* properties required by this rule. securewindow rule is an example of where
* this is used.
*
* @param moodle_page $page the page object to initialise.
*/
public function setup_attempt_page($page) {
// Do nothing by default.
}
/**
* It is possible for one rule to override other rules.
*
* The aim is that third-party rules should be able to replace sandard rules
* if they want. See, for example MDL-13592.
*
* @return array plugin names of other rules that this one replaces.
* For example array('ipaddress', 'password').
*/
public function get_superceded_rules() {
return array();
}
/**
* Add any fields that this rule requires to the quiz settings form. This
* method is called from {@link mod_quiz_mod_form::definition()}, while the
* security seciton is being built.
* @param mod_quiz_mod_form $quizform the quiz settings form that is being built.
* @param MoodleQuickForm $mform the wrapped MoodleQuickForm.
*/
public static function add_settings_form_fields(
mod_quiz_mod_form $quizform, MoodleQuickForm $mform) {
// By default do nothing.
}
/**
* Validate the data from any form fields added using {@link add_settings_form_fields()}.
* @param array $errors the errors found so far.
* @param array $data the submitted form data.
* @param array $files information about any uploaded files.
* @param mod_quiz_mod_form $quizform the quiz form object.
* @return array $errors the updated $errors array.
*/
public static function validate_settings_form_fields(array $errors,
array $data, $files, mod_quiz_mod_form $quizform) {
return $errors;
}
/**
* @return array key => lang string any choices to add to the quiz Browser
* security settings menu.
*/
public static function get_browser_security_choices() {
return array();
}
/**
* Save any submitted settings when the quiz settings form is submitted. This
* is called from {@link quiz_after_add_or_update()} in lib.php.
* @param object $quiz the data from the quiz form, including $quiz->id
* which is the id of the quiz being saved.
*/
public static function save_settings($quiz) {
// By default do nothing.
}
/**
* Delete any rule-specific settings when the quiz is deleted. This is called
* from {@link quiz_delete_instance()} in lib.php.
* @param object $quiz the data from the database, including $quiz->id
* which is the id of the quiz being deleted.
* @since Moodle 2.7.1, 2.6.4, 2.5.7
*/
public static function delete_settings($quiz) {
// By default do nothing.
}
/**
* Return the bits of SQL needed to load all the settings from all the access
* plugins in one DB query. The easiest way to understand what you need to do
* here is probalby to read the code of {@link quiz_access_manager::load_settings()}.
*
* If you have some settings that cannot be loaded in this way, then you can
* use the {@link get_extra_settings()} method instead, but that has
* performance implications.
*
* @param int $quizid the id of the quiz we are loading settings for. This
* can also be accessed as quiz.id in the SQL. (quiz is a table alisas for {quiz}.)
* @return array with three elements:
* 1. fields: any fields to add to the select list. These should be alised
* if neccessary so that the field name starts the name of the plugin.
* 2. joins: any joins (should probably be LEFT JOINS) with other tables that
* are needed.
* 3. params: array of placeholder values that are needed by the SQL. You must
* used named placeholders, and the placeholder names should start with the
* plugin name, to avoid collisions.
*/
public static function get_settings_sql($quizid) {
return array('', '', array());
}
/**
* You can use this method to load any extra settings your plugin has that
* cannot be loaded efficiently with get_settings_sql().
* @param int $quizid the quiz id.
* @return array setting value name => value. The value names should all
* start with the name of your plugin to avoid collisions.
*/
public static function get_extra_settings($quizid) {
return array();
}
}

View File

@ -14,30 +14,19 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Implementaton of the quizaccess_delaybetweenattempts plugin.
*
* @package quizaccess
* @subpackage delaybetweenattempts
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
use mod_quiz\local\access_rule_base;
use mod_quiz\quiz_settings;
/**
* A rule imposing the delay between attempts settings.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package quizaccess_delaybetweenattempts
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quizaccess_delaybetweenattempts extends quiz_access_rule_base {
class quizaccess_delaybetweenattempts extends access_rule_base {
public static function make(quiz $quizobj, $timenow, $canignoretimelimits) {
public static function make(quiz_settings $quizobj, $timenow, $canignoretimelimits) {
if (empty($quizobj->get_quiz()->delay1) && empty($quizobj->get_quiz()->delay2)) {
return null;
}

View File

@ -16,7 +16,7 @@
namespace quizaccess_delaybetweenattempts;
use quiz;
use mod_quiz\quiz_settings;
use quizaccess_delaybetweenattempts;
defined('MOODLE_INTERNAL') || die();
@ -43,7 +43,7 @@ class rule_test extends \basic_testcase {
$quiz->timeclose = 0;
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$attempt = new \stdClass();
$attempt->timefinish = 10000;
@ -77,7 +77,7 @@ class rule_test extends \basic_testcase {
$quiz->timeclose = 0;
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$attempt = new \stdClass();
$attempt->timefinish = 10000;
@ -116,7 +116,7 @@ class rule_test extends \basic_testcase {
$quiz->timeclose = 0;
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$attempt = new \stdClass();
$attempt->timefinish = 10000;
@ -167,7 +167,7 @@ class rule_test extends \basic_testcase {
$quiz->timeclose = 15000;
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$attempt = new \stdClass();
$attempt->timefinish = 13000;
@ -223,7 +223,7 @@ class rule_test extends \basic_testcase {
$quiz->timeclose = 0;
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$attempt = new \stdClass();
$attempt->timestart = 9900;
$attempt->timefinish = 10100;

View File

@ -14,30 +14,19 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Implementaton of the quizaccess_ipaddress plugin.
*
* @package quizaccess
* @subpackage ipaddress
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
use mod_quiz\local\access_rule_base;
use mod_quiz\quiz_settings;
/**
* A rule implementing the ipaddress check against the ->subnet setting.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package quizaccess_ipaddress
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quizaccess_ipaddress extends quiz_access_rule_base {
class quizaccess_ipaddress extends access_rule_base {
public static function make(quiz $quizobj, $timenow, $canignoretimelimits) {
public static function make(quiz_settings $quizobj, $timenow, $canignoretimelimits) {
if (empty($quizobj->get_quiz()->subnet)) {
return null;
}

View File

@ -16,7 +16,7 @@
namespace quizaccess_ipaddress;
use quiz;
use mod_quiz\quiz_settings;
use quizaccess_ipaddress;
defined('MOODLE_INTERNAL') || die();
@ -44,7 +44,7 @@ class rule_test extends \basic_testcase {
// does not always work, for example using the mac install package on my laptop.
$quiz->subnet = getremoteaddr(null);
if (!empty($quiz->subnet)) {
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$rule = new quizaccess_ipaddress($quizobj, 0);
$this->assertFalse($rule->prevent_access());
@ -56,7 +56,7 @@ class rule_test extends \basic_testcase {
}
$quiz->subnet = '0.0.0.0';
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$rule = new quizaccess_ipaddress($quizobj, 0);
$this->assertNotEmpty($rule->prevent_access());

View File

@ -14,30 +14,19 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Implementaton of the quizaccess_numattempts plugin.
*
* @package quizaccess
* @subpackage numattempts
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
use mod_quiz\local\access_rule_base;
use mod_quiz\quiz_settings;
/**
* A rule controlling the number of attempts allowed.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package quizaccess_numattempts
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quizaccess_numattempts extends quiz_access_rule_base {
class quizaccess_numattempts extends access_rule_base {
public static function make(quiz $quizobj, $timenow, $canignoretimelimits) {
public static function make(quiz_settings $quizobj, $timenow, $canignoretimelimits) {
if ($quizobj->get_num_attempts_allowed() == 0) {
return null;

View File

@ -16,7 +16,7 @@
namespace quizaccess_numattempts;
use quiz;
use mod_quiz\quiz_settings;
use quizaccess_numattempts;
defined('MOODLE_INTERNAL') || die();
@ -39,7 +39,7 @@ class rule_test extends \basic_testcase {
$quiz->attempts = 3;
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$rule = new quizaccess_numattempts($quizobj, 0);
$attempt = new \stdClass();

View File

@ -14,29 +14,21 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Implementaton of the quizaccess_offlineattempts plugin.
*
* @package quizaccess_offlineattempts
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
use mod_quiz\form\preflight_check_form;
use mod_quiz\local\access_rule_base;
use mod_quiz\quiz_settings;
/**
* A rule implementing the offlineattempts check.
*
* @package quizaccess_offlineattempts
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.2
*/
class quizaccess_offlineattempts extends quiz_access_rule_base {
class quizaccess_offlineattempts extends access_rule_base {
public static function make(quiz $quizobj, $timenow, $canignoretimelimits) {
public static function make(quiz_settings $quizobj, $timenow, $canignoretimelimits) {
global $CFG;
// If mobile services are off, the user won't be able to use any external app.
@ -63,7 +55,7 @@ class quizaccess_offlineattempts extends quiz_access_rule_base {
}
}
public function add_preflight_check_form_fields(mod_quiz_preflight_check_form $quizform,
public function add_preflight_check_form_fields(preflight_check_form $quizform,
MoodleQuickForm $mform, $attemptid) {
global $DB;

View File

@ -16,7 +16,7 @@
namespace quizaccess_offlineattempts;
use quiz;
use mod_quiz\quiz_settings;
use quizaccess_offlineattempts;
defined('MOODLE_INTERNAL') || die();
@ -38,7 +38,7 @@ class rule_test extends \basic_testcase {
$quiz->allowofflineattempts = 1;
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$rule = new quizaccess_offlineattempts($quizobj, 0);
$attempt = new \stdClass();

View File

@ -14,30 +14,19 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Implementaton of the quizaccess_openclosedate plugin.
*
* @package quizaccess
* @subpackage openclosedate
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
use mod_quiz\local\access_rule_base;
use mod_quiz\quiz_settings;
/**
* A rule enforcing open and close dates.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package quizaccess_openclosedate
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quizaccess_openclosedate extends quiz_access_rule_base {
class quizaccess_openclosedate extends access_rule_base {
public static function make(quiz $quizobj, $timenow, $canignoretimelimits) {
public static function make(quiz_settings $quizobj, $timenow, $canignoretimelimits) {
// This rule is always used, even if the quiz has no open or close date.
return new self($quizobj, $timenow);
}

View File

@ -16,7 +16,7 @@
namespace quizaccess_openclosedate;
use quiz;
use mod_quiz\quiz_settings;
use quizaccess_openclosedate;
defined('MOODLE_INTERNAL') || die();
@ -41,7 +41,7 @@ class rule_test extends \basic_testcase {
$quiz->overduehandling = 'autosubmit';
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$attempt = new \stdClass();
$attempt->preview = 0;
@ -68,7 +68,7 @@ class rule_test extends \basic_testcase {
$quiz->overduehandling = 'autosubmit';
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$attempt = new \stdClass();
$attempt->preview = 0;
@ -95,7 +95,7 @@ class rule_test extends \basic_testcase {
$quiz->overduehandling = 'autosubmit';
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$attempt = new \stdClass();
$attempt->preview = 0;
@ -129,7 +129,7 @@ class rule_test extends \basic_testcase {
$quiz->overduehandling = 'autosubmit';
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$attempt = new \stdClass();
$attempt->preview = 0;
@ -170,7 +170,7 @@ class rule_test extends \basic_testcase {
$quiz->graceperiod = 1000;
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$attempt = new \stdClass();
$attempt->preview = 0;

View File

@ -14,30 +14,20 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Implementaton of the quizaccess_password plugin.
*
* @package quizaccess
* @subpackage password
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
use mod_quiz\form\preflight_check_form;
use mod_quiz\local\access_rule_base;
use mod_quiz\quiz_settings;
/**
* A rule implementing the password check.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package quizaccess_password
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quizaccess_password extends quiz_access_rule_base {
class quizaccess_password extends access_rule_base {
public static function make(quiz $quizobj, $timenow, $canignoretimelimits) {
public static function make(quiz_settings $quizobj, $timenow, $canignoretimelimits) {
if (empty($quizobj->get_quiz()->password)) {
return null;
}
@ -54,7 +44,7 @@ class quizaccess_password extends quiz_access_rule_base {
return empty($SESSION->passwordcheckedquizzes[$this->quiz->id]);
}
public function add_preflight_check_form_fields(mod_quiz_preflight_check_form $quizform,
public function add_preflight_check_form_fields(preflight_check_form $quizform,
MoodleQuickForm $mform, $attemptid) {
$mform->addElement('header', 'passwordheader', get_string('password'));

View File

@ -16,7 +16,7 @@
namespace quizaccess_password;
use quiz;
use mod_quiz\quiz_settings;
use quizaccess_password;
defined('MOODLE_INTERNAL') || die();
@ -39,7 +39,7 @@ class rule_test extends \basic_testcase {
$quiz->password = 'frog';
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$rule = new quizaccess_password($quizobj, 0);
$attempt = new \stdClass();

View File

@ -58,7 +58,7 @@ class backup_quizaccess_seb_subplugin extends backup_mod_quiz_access_subplugin {
$subplugintemplatesettings = new backup_nested_element('quizaccess_seb_template', null, $templatekeys);
// Get quiz settings keys to save.
$settings = new \quizaccess_seb\quiz_settings();
$settings = new \quizaccess_seb\seb_quiz_settings();
$blanksettingsarray = (array) $settings->to_record();
unset($blanksettingsarray['id']); // We don't need to save reference to settings record in current instance.
// We don't need to save the data about who last modified the settings as they will be overwritten on restore. Also
@ -77,7 +77,7 @@ class backup_quizaccess_seb_subplugin extends backup_mod_quiz_access_subplugin {
$subpluginquizsettings->add_child($subplugintemplatesettings);
// Set source to populate the settings data by referencing the ID of quiz being backed up.
$subpluginquizsettings->set_source_table(quizaccess_seb\quiz_settings::TABLE, ['quizid' => $quizid]);
$subpluginquizsettings->set_source_table(quizaccess_seb\seb_quiz_settings::TABLE, ['quizid' => $quizid]);
$subpluginquizsettings->annotate_files('quizaccess_seb', 'filemanager_sebconfigfile', null);
@ -86,4 +86,4 @@ class backup_quizaccess_seb_subplugin extends backup_mod_quiz_access_subplugin {
return $subplugin;
}
}
}

View File

@ -24,7 +24,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use quizaccess_seb\quiz_settings;
use quizaccess_seb\seb_quiz_settings;
defined('MOODLE_INTERNAL') || die();
@ -73,7 +73,7 @@ class restore_quizaccess_seb_subplugin extends restore_mod_quiz_access_subplugin
unset($data->id);
$data->timecreated = $data->timemodified = time();
$data->usermodified = $USER->id;
$DB->insert_record(quizaccess_seb\quiz_settings::TABLE, $data);
$DB->insert_record(quizaccess_seb\seb_quiz_settings::TABLE, $data);
// Process attached files.
$this->add_related_files('quizaccess_seb', 'filemanager_sebconfigfile', null);
@ -112,7 +112,7 @@ class restore_quizaccess_seb_subplugin extends restore_mod_quiz_access_subplugin
}
// Update the restored quiz settings to use restored template.
$DB->set_field(\quizaccess_seb\quiz_settings::TABLE, 'templateid', $template->get('id'), ['quizid' => $quizid]);
$DB->set_field(\quizaccess_seb\seb_quiz_settings::TABLE, 'templateid', $template->get('id'), ['quizid' => $quizid]);
}
}

View File

@ -26,7 +26,7 @@
namespace quizaccess_seb\event;
use core\event\base;
use quizaccess_seb\access_manager;
use quizaccess_seb\seb_access_manager;
defined('MOODLE_INTERNAL') || die();
@ -44,13 +44,13 @@ class access_prevented extends base {
* Define strict parameters to create event with instead of relying on internal validation of array. Better code practice.
* Easier for consumers of this class to know what data must be supplied and observers can have more trust in event data.
*
* @param access_manager $accessmanager Access manager.
* @param seb_access_manager $accessmanager Access manager.
* @param string $reason Reason that access was prevented.
* @param string|null $configkey A Safe Exam Browser config key.
* @param string|null $browserexamkey A Safe Exam Browser browser exam key.
* @return base
*/
public static function create_strict(access_manager $accessmanager, string $reason,
public static function create_strict(seb_access_manager $accessmanager, string $reason,
?string $configkey = null, ?string $browserexamkey = null) : base {
global $USER;

View File

@ -25,12 +25,10 @@ use external_function_parameters;
use external_single_structure;
use external_value;
use invalid_parameter_exception;
use quiz;
use mod_quiz\quiz_settings;
use quizaccess_seb\event\access_prevented;
use quizaccess_seb\access_manager;
use quizaccess_seb\seb_access_manager;
require_once($CFG->dirroot . '/mod/quiz/accessmanager.php');
require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
require_once($CFG->libdir . '/externallib.php');
/**
@ -97,7 +95,7 @@ class validate_quiz_keys extends external_api {
$result = ['configkey' => true, 'browserexamkey' => true];
$accessmanager = new access_manager(quiz::create($quizid));
$accessmanager = new seb_access_manager(quiz_settings::create($quizid));
// Check if there is a valid config key.
if (!$accessmanager->validate_config_key($configkey, $url)) {

View File

@ -121,7 +121,7 @@ class helper {
require_login($cm->course, false, $cm);
// Retrieve the config for quiz.
$config = quiz_settings::get_config_by_quiz_id($cm->instance);
$config = seb_quiz_settings::get_config_by_quiz_id($cm->instance);
if (empty($config)) {
throw new \moodle_exception('noconfigfound', 'quizaccess_seb', '', $cm->id);
}

View File

@ -33,7 +33,7 @@ use core_privacy\local\request\contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
use quizaccess_seb\quiz_settings;
use quizaccess_seb\seb_quiz_settings;
use quizaccess_seb\template;
defined('MOODLE_INTERNAL') || die();
@ -162,7 +162,7 @@ class provider implements
$index++;
$subcontext = [
get_string('pluginname', 'quizaccess_seb'),
quiz_settings::TABLE,
seb_quiz_settings::TABLE,
$index
];

View File

@ -29,7 +29,7 @@
namespace quizaccess_seb;
use context_module;
use quiz;
use mod_quiz\quiz_settings;
defined('MOODLE_INTERNAL') || die();
@ -39,7 +39,7 @@ defined('MOODLE_INTERNAL') || die();
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class access_manager {
class seb_access_manager {
/** Header sent by Safe Exam Browser containing the Config Key hash. */
private const CONFIG_KEY_HEADER = 'HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH';
@ -47,10 +47,10 @@ class access_manager {
/** Header sent by Safe Exam Browser containing the Browser Exam Key hash. */
private const BROWSER_EXAM_KEY_HEADER = 'HTTP_X_SAFEEXAMBROWSER_REQUESTHASH';
/** @var quiz $quiz A quiz object containing all information pertaining to current quiz. */
/** @var quiz_settings $quiz A quiz object containing all information pertaining to current quiz. */
private $quiz;
/** @var quiz_settings $quizsettings A quiz settings persistent object containing plugin settings */
/** @var seb_quiz_settings $quizsettings A quiz settings persistent object containing plugin settings */
private $quizsettings;
/** @var context_module $context Context of this quiz activity. */
@ -62,13 +62,13 @@ class access_manager {
/**
* The access_manager constructor.
*
* @param quiz $quiz The details of the quiz.
* @param quiz_settings $quiz The details of the quiz.
*/
public function __construct(quiz $quiz) {
public function __construct(quiz_settings $quiz) {
$this->quiz = $quiz;
$this->context = context_module::instance($quiz->get_cmid());
$this->quizsettings = quiz_settings::get_by_quiz_id($quiz->get_quizid());
$this->validconfigkey = quiz_settings::get_config_key_by_quiz_id($quiz->get_quizid());
$this->quizsettings = seb_quiz_settings::get_by_quiz_id($quiz->get_quizid());
$this->validconfigkey = seb_quiz_settings::get_config_key_by_quiz_id($quiz->get_quizid());
}
/**
@ -219,9 +219,9 @@ class access_manager {
/**
* Getter for the quiz object.
*
* @return quiz
* @return \mod_quiz\quiz_settings
*/
public function get_quiz() : quiz {
public function get_quiz() : quiz_settings {
return $this->quiz;
}

View File

@ -43,7 +43,7 @@ defined('MOODLE_INTERNAL') || die();
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_settings extends persistent {
class seb_quiz_settings extends persistent {
/** Table name for the persistent. */
const TABLE = 'quizaccess_seb_quizsettings';
@ -193,7 +193,7 @@ class quiz_settings extends persistent {
* This method gets data from cache before doing any DB calls.
*
* @param int $quizid Quiz id.
* @return false|\quizaccess_seb\quiz_settings
* @return false|\quizaccess_seb\seb_quiz_settings
*/
public static function get_by_quiz_id(int $quizid) {
if ($data = self::get_quiz_settings_cache()->get($quizid)) {
@ -567,7 +567,7 @@ class quiz_settings extends persistent {
}
/**
* Sets the quitURL if found in the quiz_settings.
* Sets the quitURL if found in the seb_quiz_settings.
*/
private function process_quit_url_from_settings() {
$settings = $this->to_record();

View File

@ -395,7 +395,7 @@ class settings_provider {
self::freeze_element($quizform, $mform, 'seb_showsebdownloadlink');
self::freeze_element($quizform, $mform, 'seb_allowedbrowserexamkeys');
$quizsettings = quiz_settings::get_by_quiz_id((int) $quizform->get_instance());
$quizsettings = seb_quiz_settings::get_by_quiz_id((int) $quizform->get_instance());
// If the file has been uploaded, then replace it with the link to download the file.
if (!empty($quizsettings) && $quizsettings->get('requiresafeexambrowser') == self::USE_SEB_UPLOAD_CONFIG) {
@ -528,7 +528,7 @@ class settings_provider {
return false;
}
$settings = quiz_settings::get_record(['cmid' => (int) $context->instanceid]);
$settings = seb_quiz_settings::get_record(['cmid' => (int) $context->instanceid]);
if (empty($settings)) {
return false;

View File

@ -125,7 +125,7 @@ class template extends persistent {
$result = true;
if ($this->get('id')) {
$settings = quiz_settings::get_records(['templateid' => $this->get('id')]);
$settings = seb_quiz_settings::get_records(['templateid' => $this->get('id')]);
$result = empty($settings);
}

View File

@ -0,0 +1,31 @@
<?php
// 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/>.
/**
* This file contains mappings for classes that have been renamed.
*
* @package quizaccess_seb
* @copyright 2022 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$renamedclasses = [
// Since Moodle 4.2.
'quizaccess_seb\quiz_settings' => 'quizaccess_seb\seb_quiz_settings',
'quizaccess_seb\access_manager' => 'quizaccess_seb\seb_access_manager',
];

View File

@ -14,6 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
use mod_quiz\local\access_rule_base;
use mod_quiz\quiz_attempt;
use quizaccess_seb\seb_access_manager;
use quizaccess_seb\seb_quiz_settings;
use quizaccess_seb\settings_provider;
use quizaccess_seb\event\access_prevented;
/**
* Implementation of the quizaccess_seb plugin.
*
@ -23,36 +30,19 @@
* @copyright 2019 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quizaccess_seb extends access_rule_base {
use quizaccess_seb\access_manager;
use quizaccess_seb\quiz_settings;
use quizaccess_seb\settings_provider;
use \quizaccess_seb\event\access_prevented;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
/**
* Implementation of the quizaccess_seb plugin.
*
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quizaccess_seb extends quiz_access_rule_base {
/** @var access_manager $accessmanager Instance to manage the access to the quiz for this plugin. */
/** @var seb_access_manager $accessmanager Instance to manage the access to the quiz for this plugin. */
private $accessmanager;
/**
* Create an instance of this rule for a particular quiz.
*
* @param quiz $quizobj information about the quiz in question.
* @param \mod_quiz\quiz_settings $quizobj information about the quiz in question.
* @param int $timenow the time that should be considered as 'now'.
* @param access_manager $accessmanager the quiz accessmanager.
* @param seb_access_manager $accessmanager the quiz accessmanager.
*/
public function __construct(quiz $quizobj, int $timenow, access_manager $accessmanager) {
public function __construct(\mod_quiz\quiz_settings $quizobj, int $timenow, seb_access_manager $accessmanager) {
parent::__construct($quizobj, $timenow);
$this->accessmanager = $accessmanager;
}
@ -61,14 +51,14 @@ class quizaccess_seb extends quiz_access_rule_base {
* Return an appropriately configured instance of this rule, if it is applicable
* to the given quiz, otherwise return null.
*
* @param quiz $quizobj information about the quiz in question.
* @param \mod_quiz\quiz_settings $quizobj information about the quiz in question.
* @param int $timenow the time that should be considered as 'now'.
* @param bool $canignoretimelimits whether the current user is exempt from
* time limits by the mod/quiz:ignoretimelimits capability.
* @return quiz_access_rule_base|null the rule, if applicable, else null.
* @return access_rule_base|null the rule, if applicable, else null.
*/
public static function make (quiz $quizobj, $timenow, $canignoretimelimits) {
$accessmanager = new access_manager($quizobj);
public static function make(\mod_quiz\quiz_settings $quizobj, $timenow, $canignoretimelimits) {
$accessmanager = new seb_access_manager($quizobj);
// If Safe Exam Browser is not required, this access rule is not applicable.
if (!$accessmanager->seb_required()) {
return null;
@ -120,7 +110,7 @@ class quizaccess_seb extends quiz_access_rule_base {
$settings = settings_provider::filter_plugin_settings((object) $data);
// Validate basic settings using persistent class.
$quizsettings = (new quiz_settings())->from_record($settings);
$quizsettings = (new seb_quiz_settings())->from_record($settings);
// Set non-form fields.
$quizsettings->set('quizid', $quizid);
$quizsettings->set('cmid', $cmid);
@ -186,9 +176,9 @@ class quizaccess_seb extends quiz_access_rule_base {
$settings->cmid = $cm->id;
// Get existing settings or create new settings if none exist.
$quizsettings = quiz_settings::get_by_quiz_id($quiz->id);
$quizsettings = seb_quiz_settings::get_by_quiz_id($quiz->id);
if (empty($quizsettings)) {
$quizsettings = new quiz_settings(0, $settings);
$quizsettings = new seb_quiz_settings(0, $settings);
} else {
$settings->id = $quizsettings->get('id');
$quizsettings->from_record($settings);
@ -218,7 +208,7 @@ class quizaccess_seb extends quiz_access_rule_base {
* which is the id of the quiz being deleted.
*/
public static function delete_settings($quiz) {
$quizsettings = quiz_settings::get_by_quiz_id($quiz->id);
$quizsettings = seb_quiz_settings::get_by_quiz_id($quiz->id);
// Check that there are existing settings.
if ($quizsettings !== false) {
$quizsettings->delete();
@ -228,7 +218,7 @@ class quizaccess_seb extends quiz_access_rule_base {
/**
* Return the bits of SQL needed to load all the settings from all the access
* plugins in one DB query. The easiest way to understand what you need to do
* here is probalby to read the code of {@link quiz_access_manager::load_settings()}.
* here is probably to read the code of {@see \mod_quiz\access_manager::load_settings()}.
*
* If you have some settings that cannot be loaded in this way, then you can
* use the {@link get_extra_settings()} method instead, but that has

View File

@ -27,7 +27,7 @@ require_once(__DIR__ . '/test_helper_trait.php');
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \quizaccess_seb\access_manager
* @covers \quizaccess_seb\seb_access_manager
*/
class access_manager_test extends \advanced_testcase {
use \quizaccess_seb_test_helper_trait;
@ -53,7 +53,7 @@ class access_manager_test extends \advanced_testcase {
$this->assertFalse($accessmanager->seb_required());
$reflection = new \ReflectionClass('\quizaccess_seb\access_manager');
$reflection = new \ReflectionClass('\quizaccess_seb\seb_access_manager');
$property = $reflection->getProperty('quizsettings');
$property->setAccessible(true);
@ -153,7 +153,7 @@ class access_manager_test extends \advanced_testcase {
$accessmanager = $this->get_access_manager();
$configkey = quiz_settings::get_record(['quizid' => $this->quiz->id])->get_config_key();
$configkey = seb_quiz_settings::get_record(['quizid' => $this->quiz->id])->get_config_key();
// Set up dummy request.
$FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4';
@ -171,7 +171,7 @@ class access_manager_test extends \advanced_testcase {
$url = 'https://www.example.com/moodle';
$accessmanager = $this->get_access_manager();
$configkey = quiz_settings::get_record(['quizid' => $this->quiz->id])->get_config_key();
$configkey = seb_quiz_settings::get_record(['quizid' => $this->quiz->id])->get_config_key();
$fullconfigkey = hash('sha256', $url . $configkey);
$this->assertTrue($accessmanager->validate_config_key($fullconfigkey, $url));
@ -202,7 +202,7 @@ class access_manager_test extends \advanced_testcase {
public function test_no_browser_exam_keys_cause_check_to_be_successful() {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$settings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings->set('allowedbrowserexamkeys', '');
$settings->save();
$accessmanager = $this->get_access_manager();
@ -216,7 +216,7 @@ class access_manager_test extends \advanced_testcase {
public function test_access_keys_fail_if_browser_exam_key_header_does_not_exist() {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$settings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings->set('allowedbrowserexamkeys', hash('sha256', 'one') . "\n" . hash('sha256', 'two'));
$settings->save();
$accessmanager = $this->get_access_manager();
@ -229,7 +229,7 @@ class access_manager_test extends \advanced_testcase {
public function test_access_keys_fail_if_browser_exam_key_header_does_not_match_provided_hash() {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$settings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings->set('allowedbrowserexamkeys', hash('sha256', 'one') . "\n" . hash('sha256', 'two'));
$settings->save();
$accessmanager = $this->get_access_manager();
@ -244,7 +244,7 @@ class access_manager_test extends \advanced_testcase {
global $FULLME;
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$settings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$browserexamkey = hash('sha256', 'browserexamkey');
$settings->set('allowedbrowserexamkeys', $browserexamkey); // Add a hashed BEK.
$settings->save();
@ -263,7 +263,7 @@ class access_manager_test extends \advanced_testcase {
public function test_browser_exam_keys_match_provided_browser_exam_key() {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$url = 'https://www.example.com/moodle';
$settings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$browserexamkey = hash('sha256', 'browserexamkey');
$fullbrowserexamkey = hash('sha256', $url . $browserexamkey);
$settings->set('allowedbrowserexamkeys', $browserexamkey); // Add a hashed BEK.
@ -315,7 +315,7 @@ class access_manager_test extends \advanced_testcase {
// Use template.
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $this->create_template()->get('id'));
$quizsettings->save();
@ -324,7 +324,7 @@ class access_manager_test extends \advanced_testcase {
// Use uploaded config.
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG); // Doesn't check basic header.
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
$this->create_module_test_file($xml, $this->quiz->cmid);
@ -362,7 +362,7 @@ class access_manager_test extends \advanced_testcase {
* @dataProvider should_validate_basic_header_data_provider
*/
public function test_should_validate_basic_header($type, $expected) {
$accessmanager = $this->getMockBuilder(access_manager::class)
$accessmanager = $this->getMockBuilder(seb_access_manager::class)
->disableOriginalConstructor()
->onlyMethods(['get_seb_use_type'])
->getMock();
@ -396,7 +396,7 @@ class access_manager_test extends \advanced_testcase {
* @dataProvider should_validate_config_key_data_provider
*/
public function test_should_validate_config_key($type, $expected) {
$accessmanager = $this->getMockBuilder(access_manager::class)
$accessmanager = $this->getMockBuilder(seb_access_manager::class)
->disableOriginalConstructor()
->onlyMethods(['get_seb_use_type'])
->getMock();
@ -429,7 +429,7 @@ class access_manager_test extends \advanced_testcase {
* @dataProvider should_validate_browser_exam_key_data_provider
*/
public function test_should_validate_browser_exam_key($type, $expected) {
$accessmanager = $this->getMockBuilder(access_manager::class)
$accessmanager = $this->getMockBuilder(seb_access_manager::class)
->disableOriginalConstructor()
->onlyMethods(['get_seb_use_type'])
->getMock();
@ -457,7 +457,7 @@ class access_manager_test extends \advanced_testcase {
$this->assertTrue($accessmanager->validate_config_key());
// Change settings (but don't save) and check that still can validate config key.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('showsebtaskbar', 0);
$this->assertNotEquals($quizsettings->get_config_key(), $configkey);
$this->assertTrue($accessmanager->validate_config_key());
@ -479,7 +479,7 @@ class access_manager_test extends \advanced_testcase {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_NO);
$accessmanager = $this->get_access_manager();
$this->assertEmpty(quiz_settings::get_record(['quizid' => $this->quiz->id]));
$this->assertEmpty(seb_quiz_settings::get_record(['quizid' => $this->quiz->id]));
$this->assertNull($accessmanager->get_valid_config_key());
}

View File

@ -54,11 +54,11 @@ class backup_restore_test extends \advanced_testcase {
/**
* A helper method to create a quiz with template usage of SEB.
*
* @return quiz_settings
* @return seb_quiz_settings
*/
protected function create_quiz_with_template() {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $this->template->get('id'));
$quizsettings->save();
@ -129,10 +129,10 @@ class backup_restore_test extends \advanced_testcase {
* @param cm_info $newcm Restored course_module object.
*/
protected function validate_backup_restore(\cm_info $newcm) {
$this->assertEquals(2, quiz_settings::count_records());
$actual = quiz_settings::get_record(['quizid' => $newcm->instance]);
$this->assertEquals(2, seb_quiz_settings::count_records());
$actual = seb_quiz_settings::get_record(['quizid' => $newcm->instance]);
$expected = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals($expected->get('templateid'), $actual->get('templateid'));
$this->assertEquals($expected->get('requiresafeexambrowser'), $actual->get('requiresafeexambrowser'));
$this->assertEquals($expected->get('showsebdownloadlink'), $actual->get('showsebdownloadlink'));
@ -152,10 +152,10 @@ class backup_restore_test extends \advanced_testcase {
*/
public function test_backup_restore_no_seb() {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_NO);
$this->assertEquals(0, quiz_settings::count_records());
$this->assertEquals(0, seb_quiz_settings::count_records());
$this->backup_and_restore_quiz();
$this->assertEquals(0, quiz_settings::count_records());
$this->assertEquals(0, seb_quiz_settings::count_records());
}
/**
@ -164,12 +164,12 @@ class backup_restore_test extends \advanced_testcase {
public function test_backup_restore_manual_config() {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$expected = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected->set('showsebdownloadlink', 0);
$expected->set('quitpassword', '123');
$expected->save();
$this->assertEquals(1, quiz_settings::count_records());
$this->assertEquals(1, seb_quiz_settings::count_records());
$newcm = $this->backup_and_restore_quiz();
$this->validate_backup_restore($newcm);
@ -181,13 +181,13 @@ class backup_restore_test extends \advanced_testcase {
public function test_backup_restore_template_config() {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$expected = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$template = $this->create_template();
$expected->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$expected->set('templateid', $template->get('id'));
$expected->save();
$this->assertEquals(1, quiz_settings::count_records());
$this->assertEquals(1, seb_quiz_settings::count_records());
$newcm = $this->backup_and_restore_quiz();
$this->validate_backup_restore($newcm);
@ -199,13 +199,13 @@ class backup_restore_test extends \advanced_testcase {
public function test_backup_restore_uploaded_config() {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$expected = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
$this->create_module_test_file($xml, $this->quiz->cmid);
$expected->save();
$this->assertEquals(1, quiz_settings::count_records());
$this->assertEquals(1, seb_quiz_settings::count_records());
$newcm = $this->backup_and_restore_quiz();
$this->validate_backup_restore($newcm);
@ -224,14 +224,14 @@ class backup_restore_test extends \advanced_testcase {
$this->create_quiz_with_template();
$backupid = $this->backup_quiz();
$this->assertEquals(1, quiz_settings::count_records());
$this->assertEquals(1, seb_quiz_settings::count_records());
$this->assertEquals(1, template::count_records());
$this->change_site();
$this->restore_quiz($backupid);
// Should see additional setting record, but no new template record.
$this->assertEquals(2, quiz_settings::count_records());
$this->assertEquals(2, seb_quiz_settings::count_records());
$this->assertEquals(1, template::count_records());
}
@ -243,7 +243,7 @@ class backup_restore_test extends \advanced_testcase {
$this->create_quiz_with_template();
$backupid = $this->backup_quiz();
$this->assertEquals(1, quiz_settings::count_records());
$this->assertEquals(1, seb_quiz_settings::count_records());
$this->assertEquals(1, template::count_records());
$this->template->set('name', 'New name for template');
@ -253,7 +253,7 @@ class backup_restore_test extends \advanced_testcase {
$this->restore_quiz($backupid);
// Should see additional setting record, and new template record.
$this->assertEquals(2, quiz_settings::count_records());
$this->assertEquals(2, seb_quiz_settings::count_records());
$this->assertEquals(2, template::count_records());
}
@ -267,7 +267,7 @@ class backup_restore_test extends \advanced_testcase {
$this->create_quiz_with_template();
$backupid = $this->backup_quiz();
$this->assertEquals(1, quiz_settings::count_records());
$this->assertEquals(1, seb_quiz_settings::count_records());
$this->assertEquals(1, template::count_records());
$newxml = file_get_contents($CFG->dirroot . '/mod/quiz/accessrule/seb/tests/fixtures/simpleunencrypted.seb');
@ -278,7 +278,7 @@ class backup_restore_test extends \advanced_testcase {
$this->restore_quiz($backupid);
// Should see additional setting record, and new template record.
$this->assertEquals(2, quiz_settings::count_records());
$this->assertEquals(2, seb_quiz_settings::count_records());
$this->assertEquals(2, template::count_records());
}

View File

@ -16,7 +16,7 @@
namespace quizaccess_seb\event;
use quiz;
use mod_quiz\quiz_settings;
defined('MOODLE_INTERNAL') || die();
@ -53,7 +53,7 @@ class events_test extends \advanced_testcase {
$this->setAdminUser();
$quiz = $this->create_test_quiz($this->course, \quizaccess_seb\settings_provider::USE_SEB_CONFIG_MANUALLY);
$accessmanager = new \quizaccess_seb\access_manager(new quiz($quiz,
$accessmanager = new \quizaccess_seb\seb_access_manager(new quiz_settings($quiz,
get_coursemodule_from_id('quiz', $quiz->cmid), $this->course));
// Set up event with data.
@ -103,7 +103,7 @@ class events_test extends \advanced_testcase {
$this->setAdminUser();
$quiz = $this->create_test_quiz($this->course, \quizaccess_seb\settings_provider::USE_SEB_CONFIG_MANUALLY);
$accessmanager = new \quizaccess_seb\access_manager(new quiz($quiz,
$accessmanager = new \quizaccess_seb\seb_access_manager(new quiz_settings($quiz,
get_coursemodule_from_id('quiz', $quiz->cmid), $this->course));
// Set up event with data.

View File

@ -20,7 +20,7 @@ defined('MOODLE_INTERNAL') || die();
global $CFG;
use quizaccess_seb\quiz_settings;
use quizaccess_seb\seb_quiz_settings;
require_once($CFG->libdir . '/externallib.php');
@ -164,7 +164,7 @@ class validate_quiz_access_test extends \advanced_testcase {
$url = 'https://www.example.com/moodle';
// Create the quiz settings.
$quizsettings = new quiz_settings(0, $settings);
$quizsettings = new seb_quiz_settings(0, $settings);
$quizsettings->save();
$fullconfigkey = hash('sha256', $url . $quizsettings->get_config_key());
@ -188,7 +188,7 @@ class validate_quiz_access_test extends \advanced_testcase {
]);
// Create the quiz settings.
$quizsettings = new quiz_settings(0, $settings);
$quizsettings = new seb_quiz_settings(0, $settings);
$quizsettings->save();
$result = validate_quiz_keys::execute($this->quiz->cmid, 'https://www.example.com/moodle', 'badconfigkey');
@ -217,7 +217,7 @@ class validate_quiz_access_test extends \advanced_testcase {
]);
// Create the quiz settings.
$quizsettings = new quiz_settings(0, $settings);
$quizsettings = new seb_quiz_settings(0, $settings);
$quizsettings->save();
$fullbrowserexamkey = hash('sha256', $url . $validbrowserexamkey);
@ -243,7 +243,7 @@ class validate_quiz_access_test extends \advanced_testcase {
]);
// Create the quiz settings.
$quizsettings = new quiz_settings(0, $settings);
$quizsettings = new seb_quiz_settings(0, $settings);
$quizsettings->save();
$result = validate_quiz_keys::execute($this->quiz->cmid, 'https://www.example.com/moodle', null,

View File

@ -31,7 +31,7 @@ use core_privacy\local\request\writer;
use core_privacy\tests\request\approved_contextlist;
use core_privacy\tests\provider_testcase;
use quizaccess_seb\privacy\provider;
use quizaccess_seb\quiz_settings;
use quizaccess_seb\seb_quiz_settings;
defined('MOODLE_INTERNAL') || die();
@ -62,7 +62,7 @@ class provider_test extends provider_testcase {
$template = $this->create_template();
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
// Modify settings so usermodified is updated. This is the user data we are testing for.
$quizsettings->set('requiresafeexambrowser', \quizaccess_seb\settings_provider::USE_SEB_TEMPLATE);
@ -126,7 +126,7 @@ class provider_test extends provider_testcase {
$index = '1'; // Get first data returned from the quizsettings table metadata.
$data = $writer->get_data([
get_string('pluginname', 'quizaccess_seb'),
quiz_settings::TABLE,
seb_quiz_settings::TABLE,
$index,
]);
$this->assertNotEmpty($data);
@ -142,7 +142,7 @@ class provider_test extends provider_testcase {
$index = '2'; // There should not be more than one instance with data.
$data = $writer->get_data([
get_string('pluginname', 'quizaccess_seb'),
quiz_settings::TABLE,
seb_quiz_settings::TABLE,
$index,
]);
$this->assertEmpty($data);
@ -180,11 +180,11 @@ class provider_test extends provider_testcase {
'quizaccess_seb', [$this->user->id]);
// Test data exists.
$this->assertNotEmpty(quiz_settings::get_record(['quizid' => $this->quiz->id]));
$this->assertNotEmpty(seb_quiz_settings::get_record(['quizid' => $this->quiz->id]));
// Test data is deleted.
provider::delete_data_for_users($approveduserlist);
$record = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$record = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEmpty($record->get('usermodified'));
$template = \quizaccess_seb\template::get_record(['id' => $record->get('templateid')]);
@ -202,11 +202,11 @@ class provider_test extends provider_testcase {
'quizaccess_seb', [$context->id]);
// Test data exists.
$this->assertNotEmpty(quiz_settings::get_record(['quizid' => $this->quiz->id]));
$this->assertNotEmpty(seb_quiz_settings::get_record(['quizid' => $this->quiz->id]));
// Test data is deleted.
provider::delete_data_for_user($approvedcontextlist);
$record = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$record = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEmpty($record->get('usermodified'));
$template = \quizaccess_seb\template::get_record(['id' => $record->get('templateid')]);
@ -222,7 +222,7 @@ class provider_test extends provider_testcase {
$context = \context_module::instance($this->quiz->cmid);
// Test data exists.
$record = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$record = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$template = \quizaccess_seb\template::get_record(['id' => $record->get('templateid')]);
$this->assertNotEmpty($record->get('usermodified'));
$this->assertNotEmpty($template->get('usermodified'));
@ -230,7 +230,7 @@ class provider_test extends provider_testcase {
// Test data is deleted.
provider::delete_data_for_all_users_in_context($context);
$record = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$record = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$template = \quizaccess_seb\template::get_record(['id' => $record->get('templateid')]);
$this->assertEmpty($record->get('usermodified'));
$this->assertEmpty($template->get('usermodified'));

View File

@ -16,12 +16,15 @@
namespace quizaccess_seb;
use context_module;
use moodle_url;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/test_helper_trait.php');
/**
* PHPUnit tests for quiz_settings class.
* PHPUnit tests for seb_quiz_settings class.
*
* @package quizaccess_seb
* @author Andrew Madden <andrewmadden@catalyst-au.net>
@ -66,7 +69,7 @@ class quiz_settings_test extends \advanced_testcase {
]);
// Obtain the existing record that is created when using a generator.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
// Update the settings with values from the test function.
$quizsettings->from_record($settings);
@ -100,7 +103,7 @@ class quiz_settings_test extends \advanced_testcase {
]);
// Obtain the existing record that is created when using a generator.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
// Update the settings with values from the test function.
$quizsettings->from_record($settings);
@ -145,7 +148,7 @@ class quiz_settings_test extends \advanced_testcase {
public function test_config_key_is_created_from_quiz_settings() {
$settings = $this->get_test_settings();
$quizsettings = new quiz_settings(0, $settings);
$quizsettings = new seb_quiz_settings(0, $settings);
$configkey = $quizsettings->get_config_key();
$this->assertEquals("65ff7a3b8aec80e58fbe2e7968826c33cbf0ac444a748055ebe665829cbf4201",
$configkey
@ -158,7 +161,7 @@ class quiz_settings_test extends \advanced_testcase {
public function test_config_key_is_updated_from_quiz_settings() {
$settings = $this->get_test_settings();
$quizsettings = new quiz_settings(0, $settings);
$quizsettings = new seb_quiz_settings(0, $settings);
$configkey = $quizsettings->get_config_key();
$this->assertEquals("65ff7a3b8aec80e58fbe2e7968826c33cbf0ac444a748055ebe665829cbf4201",
$configkey);
@ -178,7 +181,7 @@ class quiz_settings_test extends \advanced_testcase {
* @dataProvider filter_rules_provider
*/
public function test_filter_rules_added_to_config(\stdClass $settings, string $expectedxml) {
$quizsettings = new quiz_settings(0, $settings);
$quizsettings = new seb_quiz_settings(0, $settings);
$config = $quizsettings->get_config();
$this->assertEquals($expectedxml, $config);
}
@ -187,7 +190,7 @@ class quiz_settings_test extends \advanced_testcase {
* Test that browser keys are validated and retrieved as an array instead of string.
*/
public function test_browser_exam_keys_are_retrieved_as_array() {
$quizsettings = new quiz_settings();
$quizsettings = new seb_quiz_settings();
$quizsettings->set('allowedbrowserexamkeys', "one two,three\nfour");
$retrievedkeys = $quizsettings->get('allowedbrowserexamkeys');
$this->assertEquals(['one', 'two', 'three', 'four'], $retrievedkeys);
@ -202,7 +205,7 @@ class quiz_settings_test extends \advanced_testcase {
* @dataProvider bad_browser_exam_key_provider
*/
public function test_browser_exam_keys_validation_errors($bek, $expectederrorstring) {
$quizsettings = new quiz_settings();
$quizsettings = new seb_quiz_settings();
$quizsettings->set('allowedbrowserexamkeys', $bek);
$quizsettings->validate();
$errors = $quizsettings->get_errors();
@ -220,7 +223,7 @@ class quiz_settings_test extends \advanced_testcase {
. "<key>allowWlan</key><false/><key>startURL</key><string>$url</string>"
. "<key>sendBrowserExamKey</key><true/><key>browserWindowWebView</key><integer>3</integer></dict></plist>\n";
$itemid = $this->create_module_test_file($xml, $this->quiz->cmid);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$quizsettings->save();
$config = $quizsettings->get_config();
@ -231,7 +234,7 @@ class quiz_settings_test extends \advanced_testcase {
* Test test_no_config_file_uploaded
*/
public function test_no_config_file_uploaded() {
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$cmid = $quizsettings->get('cmid');
$this->expectException(\moodle_exception::class);
@ -279,7 +282,7 @@ class quiz_settings_test extends \advanced_testcase {
$this->assertStringContainsString("<key>allowQuit</key><true/>", $template->get('content'));
$this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $template->get('content'));
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $template->get('id'));
$quizsettings->set('allowuserquitseb', 1);
@ -318,7 +321,7 @@ class quiz_settings_test extends \advanced_testcase {
$this->assertStringNotContainsString("<key>allowQuit</key><true/>", $template->get('content'));
$this->assertStringNotContainsString("<key>hashedQuitPassword</key><string>password</string>", $template->get('content'));
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $template->get('id'));
$quizsettings->set('allowuserquitseb', 1);
@ -347,7 +350,7 @@ class quiz_settings_test extends \advanced_testcase {
$xml = $this->get_config_xml(true, 'password');
$this->create_module_test_file($xml, $this->quiz->cmid);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$quizsettings->set('allowuserquitseb', 0);
$quizsettings->set('quitpassword', '');
@ -384,7 +387,7 @@ class quiz_settings_test extends \advanced_testcase {
$xml = $this->get_config_xml();
$this->create_module_test_file($xml, $this->quiz->cmid);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$quizsettings->set('allowuserquitseb', 1);
$quizsettings->set('quitpassword', '');
@ -424,7 +427,7 @@ class quiz_settings_test extends \advanced_testcase {
$template = $this->create_template($xml);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $template->get('id'));
@ -446,7 +449,7 @@ class quiz_settings_test extends \advanced_testcase {
. "<key>sendBrowserExamKey</key><true/></dict></plist>\n";
$itemid = $this->create_module_test_file($xml, $this->quiz->cmid);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$this->assertEmpty($quizsettings->get('linkquitseb'));
@ -460,7 +463,7 @@ class quiz_settings_test extends \advanced_testcase {
* Test template id set correctly.
*/
public function test_templateid_set_correctly_when_save_settings() {
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals(0, $quizsettings->get('templateid'));
$template = $this->create_template();
@ -468,12 +471,12 @@ class quiz_settings_test extends \advanced_testcase {
// Initially set to USE_SEB_TEMPLATE with a template id.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals($templateid, $quizsettings->get('templateid'));
// Case for USE_SEB_NO, ensure template id reverts to 0.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_NO);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals(0, $quizsettings->get('templateid'));
// Reverting back to USE_SEB_TEMPLATE.
@ -481,7 +484,7 @@ class quiz_settings_test extends \advanced_testcase {
// Case for USE_SEB_CONFIG_MANUALLY, ensure template id reverts to 0.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_CONFIG_MANUALLY);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals(0, $quizsettings->get('templateid'));
// Reverting back to USE_SEB_TEMPLATE.
@ -489,7 +492,7 @@ class quiz_settings_test extends \advanced_testcase {
// Case for USE_SEB_CLIENT_CONFIG, ensure template id reverts to 0.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_CLIENT_CONFIG);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals(0, $quizsettings->get('templateid'));
// Reverting back to USE_SEB_TEMPLATE.
@ -499,19 +502,19 @@ class quiz_settings_test extends \advanced_testcase {
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
$this->create_module_test_file($xml, $this->quiz->cmid);
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_UPLOAD_CONFIG);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals(0, $quizsettings->get('templateid'));
// Case for USE_SEB_TEMPLATE, ensure template id is correct.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals($templateid, $quizsettings->get('templateid'));
}
/**
* Helper function in tests to set USE_SEB_TEMPLATE and a template id on the quiz settings.
*
* @param quiz_settings $quizsettings Given quiz settings instance.
* @param seb_quiz_settings $quizsettings Given quiz settings instance.
* @param int $savetype Type of SEB usage.
* @param int $templateid Template ID.
*/
@ -695,13 +698,13 @@ class quiz_settings_test extends \advanced_testcase {
* Test that config and config key are null when expected.
*/
public function test_generates_config_values_as_null_when_expected() {
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertNotNull($quizsettings->get_config());
$this->assertNotNull($quizsettings->get_config_key());
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_NO);
$quizsettings->save();
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertNull($quizsettings->get_config());
$this->assertNull($quizsettings->get_config());
@ -709,20 +712,20 @@ class quiz_settings_test extends \advanced_testcase {
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
$this->create_module_test_file($xml, $this->quiz->cmid);
$quizsettings->save();
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertNotNull($quizsettings->get_config());
$this->assertNotNull($quizsettings->get_config_key());
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG);
$quizsettings->save();
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertNull($quizsettings->get_config());
$this->assertNull($quizsettings->get_config_key());
$template = $this->create_template();
$templateid = $template->get('id');
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertNotNull($quizsettings->get_config());
$this->assertNotNull($quizsettings->get_config_key());
}
@ -731,7 +734,7 @@ class quiz_settings_test extends \advanced_testcase {
* Test that quizsettings cache exists after creation.
*/
public function test_quizsettings_cache_exists_after_creation() {
$expected = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals($expected->to_record(), \cache::make('quizaccess_seb', 'quizsettings')->get($this->quiz->id));
}
@ -741,30 +744,30 @@ class quiz_settings_test extends \advanced_testcase {
public function test_quizsettings_cache_purged_after_deletion() {
$this->assertNotEmpty(\cache::make('quizaccess_seb', 'quizsettings')->get($this->quiz->id));
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->delete();
$this->assertFalse(\cache::make('quizaccess_seb', 'quizsettings')->get($this->quiz->id));
}
/**
* Test that we can get quiz_settings by quiz id.
* Test that we can get seb_quiz_settings by quiz id.
*/
public function test_get_quiz_settings_by_quiz_id() {
$expected = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals($expected->to_record(), quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
$this->assertEquals($expected->to_record(), seb_quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
// Check that data is getting from cache.
$expected->set('showsebtaskbar', 0);
$this->assertNotEquals($expected->to_record(), quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
$this->assertNotEquals($expected->to_record(), seb_quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
// Now save and check that cached as been updated.
$expected->save();
$this->assertEquals($expected->to_record(), quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
$this->assertEquals($expected->to_record(), seb_quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
// Returns false for non existing quiz.
$this->assertFalse(quiz_settings::get_by_quiz_id(7777777));
$this->assertFalse(seb_quiz_settings::get_by_quiz_id(7777777));
}
/**
@ -780,7 +783,7 @@ class quiz_settings_test extends \advanced_testcase {
public function test_config_cache_purged_after_deletion() {
$this->assertNotEmpty(\cache::make('quizaccess_seb', 'config')->get($this->quiz->id));
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->delete();
$this->assertFalse(\cache::make('quizaccess_seb', 'config')->get($this->quiz->id));
@ -790,21 +793,21 @@ class quiz_settings_test extends \advanced_testcase {
* Test that we can get SEB config by quiz id.
*/
public function test_get_config_by_quiz_id() {
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected = $quizsettings->get_config();
$this->assertEquals($expected, quiz_settings::get_config_by_quiz_id($this->quiz->id));
$this->assertEquals($expected, seb_quiz_settings::get_config_by_quiz_id($this->quiz->id));
// Check that data is getting from cache.
$quizsettings->set('showsebtaskbar', 0);
$this->assertNotEquals($quizsettings->get_config(), quiz_settings::get_config_by_quiz_id($this->quiz->id));
$this->assertNotEquals($quizsettings->get_config(), seb_quiz_settings::get_config_by_quiz_id($this->quiz->id));
// Now save and check that cached as been updated.
$quizsettings->save();
$this->assertEquals($quizsettings->get_config(), quiz_settings::get_config_by_quiz_id($this->quiz->id));
$this->assertEquals($quizsettings->get_config(), seb_quiz_settings::get_config_by_quiz_id($this->quiz->id));
// Returns null for non existing quiz.
$this->assertNull(quiz_settings::get_config_by_quiz_id(7777777));
$this->assertNull(seb_quiz_settings::get_config_by_quiz_id(7777777));
}
/**
@ -820,7 +823,7 @@ class quiz_settings_test extends \advanced_testcase {
public function test_config_key_cache_purged_after_deletion() {
$this->assertNotEmpty(\cache::make('quizaccess_seb', 'configkey')->get($this->quiz->id));
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->delete();
$this->assertFalse(\cache::make('quizaccess_seb', 'configkey')->get($this->quiz->id));
@ -830,21 +833,21 @@ class quiz_settings_test extends \advanced_testcase {
* Test that we can get SEB config key by quiz id.
*/
public function test_get_config_key_by_quiz_id() {
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected = $quizsettings->get_config_key();
$this->assertEquals($expected, quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
$this->assertEquals($expected, seb_quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
// Check that data is getting from cache.
$quizsettings->set('showsebtaskbar', 0);
$this->assertNotEquals($quizsettings->get_config_key(), quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
$this->assertNotEquals($quizsettings->get_config_key(), seb_quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
// Now save and check that cached as been updated.
$quizsettings->save();
$this->assertEquals($quizsettings->get_config_key(), quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
$this->assertEquals($quizsettings->get_config_key(), seb_quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
// Returns null for non existing quiz.
$this->assertNull(quiz_settings::get_config_key_by_quiz_id(7777777));
$this->assertNull(seb_quiz_settings::get_config_key_by_quiz_id(7777777));
}
}

View File

@ -285,7 +285,7 @@ class rule_test extends \advanced_testcase {
$this->quiz->seb_requiresafeexambrowser = settings_provider::USE_SEB_NO;
quizaccess_seb::save_settings($this->quiz);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals(settings_provider::USE_SEB_CONFIG_MANUALLY, $quizsettings->get('requiresafeexambrowser'));
}
@ -388,7 +388,7 @@ class rule_test extends \advanced_testcase {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
// Set quiz setting to require seb and save BEK.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
$this->create_module_test_file($xml, $this->quiz->cmid);
@ -414,7 +414,7 @@ class rule_test extends \advanced_testcase {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
// Set quiz setting to require seb and save BEK.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $this->create_template()->get('id'));
$quizsettings->save();
@ -442,7 +442,7 @@ class rule_test extends \advanced_testcase {
$this->setUser($user);
// Set quiz setting to require seb.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
// Set up dummy request.
$FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4';
@ -463,7 +463,7 @@ class rule_test extends \advanced_testcase {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
// Set quiz setting to require seb and save BEK.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $this->create_template()->get('id'));
$quizsettings->save();
@ -490,7 +490,7 @@ class rule_test extends \advanced_testcase {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
// Set quiz setting to require seb and save BEK.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
$this->create_module_test_file($xml, $this->quiz->cmid);
@ -522,7 +522,7 @@ class rule_test extends \advanced_testcase {
// Set quiz setting to require seb and save BEK.
$browserexamkey = hash('sha256', 'testkey');
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG); // Doesn't check config key.
$quizsettings->set('allowedbrowserexamkeys', $browserexamkey);
$quizsettings->save();
@ -548,7 +548,7 @@ class rule_test extends \advanced_testcase {
// Set quiz setting to require seb and save BEK.
$browserexamkey = hash('sha256', 'testkey');
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$quizsettings->set('allowedbrowserexamkeys', $browserexamkey);
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
@ -643,7 +643,7 @@ class rule_test extends \advanced_testcase {
// Set quiz setting to require seb and save BEK.
$browserexamkey = hash('sha256', 'testkey');
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG); // Doesn't check config key.
$quizsettings->set('allowedbrowserexamkeys', $browserexamkey);
$quizsettings->save();
@ -666,7 +666,7 @@ class rule_test extends \advanced_testcase {
// Set quiz setting to require seb and save BEK.
$browserexamkey = hash('sha256', 'testkey');
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$quizsettings->set('allowedbrowserexamkeys', $browserexamkey);
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
@ -698,7 +698,7 @@ class rule_test extends \advanced_testcase {
// Set quiz setting to require seb and save BEK.
$browserexamkey = hash('sha256', 'testkey');
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('allowedbrowserexamkeys', $browserexamkey);
$quizsettings->set('templateid', $this->create_template()->get('id'));
@ -730,7 +730,7 @@ class rule_test extends \advanced_testcase {
$this->setUser($user);
// Set quiz setting to require seb.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG); // Doesn't check config key.
$quizsettings->save();
@ -752,7 +752,7 @@ class rule_test extends \advanced_testcase {
$this->setUser($user);
// Set quiz setting to require seb.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG); // Doesn't check config key.
$quizsettings->save();
@ -795,7 +795,7 @@ class rule_test extends \advanced_testcase {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
// Set quiz setting to require seb.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG); // Doesn't check basic header.
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
$this->create_module_test_file($xml, $this->quiz->cmid);
@ -824,7 +824,7 @@ class rule_test extends \advanced_testcase {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
// Set quiz setting to require seb.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $this->create_template()->get('id'));
$quizsettings->save();
@ -853,7 +853,7 @@ class rule_test extends \advanced_testcase {
$this->setUser($user);
// Set quiz setting to not require seb.
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_NO);
$quizsettings->save();
@ -1044,7 +1044,7 @@ class rule_test extends \advanced_testcase {
$method = $reflection->getMethod('get_action_buttons');
$method->setAccessible(true);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
// Should see link when using manually.
$this->assertStringContainsString($this->get_seb_launch_link(), $method->invoke($this->make_rule()));
@ -1161,7 +1161,7 @@ class rule_test extends \advanced_testcase {
$this->setAdminUser();
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$user = $this->getDataGenerator()->create_user();
$roleid = $this->getDataGenerator()->create_role();
@ -1268,7 +1268,7 @@ class rule_test extends \advanced_testcase {
$this->setAdminUser();
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->save();
// Set access for Moodle session.
$SESSION->quizaccess_seb_access = [$this->quiz->cmid => true];

View File

@ -111,7 +111,7 @@ class settings_provider_test extends \advanced_testcase {
* Test that settings types to be added to quiz settings, are part of quiz_settings persistent class.
*/
public function test_setting_elements_are_part_of_quiz_settings_table() {
$dbsettings = (array) (new quiz_settings())->to_record();
$dbsettings = (array) (new seb_quiz_settings())->to_record();
$settingelements = settings_provider::get_seb_config_elements();
$settingelements = (array) $this->strip_all_prefixes((object) $settingelements);
@ -704,7 +704,7 @@ class settings_provider_test extends \advanced_testcase {
$template = $this->create_template();
$settings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings->set('templateid', $template->get('id'));
$settings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$settings->save();
@ -731,7 +731,7 @@ class settings_provider_test extends \advanced_testcase {
// Setup conflicting permissions.
$template = $this->create_template();
$settings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings->set('templateid', $template->get('id'));
$settings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$settings->save();
@ -794,7 +794,7 @@ class settings_provider_test extends \advanced_testcase {
$template = $this->create_template();
$settings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings->set('templateid', $template->get('id'));
$settings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$settings->save();
@ -989,7 +989,7 @@ class settings_provider_test extends \advanced_testcase {
settings_provider::save_filemanager_sebconfigfile_draftarea($draftitemid, $this->quiz->cmid);
$settings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$settings->save();
@ -1167,7 +1167,7 @@ class settings_provider_test extends \advanced_testcase {
// Create a template.
$template = $this->create_template();
$settings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings->set('templateid', $template->get('id'));
$settings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$settings->save();
@ -1197,7 +1197,7 @@ class settings_provider_test extends \advanced_testcase {
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
$draftitemid = $this->create_test_draftarea_file($xml);
settings_provider::save_filemanager_sebconfigfile_draftarea($draftitemid, $this->quiz->cmid);
$settings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$settings->save();

View File

@ -108,7 +108,7 @@ class template_test extends \advanced_testcase {
$template->save();
$this->assertTrue($template->can_delete());
$DB->insert_record(quiz_settings::TABLE, (object) [
$DB->insert_record(seb_quiz_settings::TABLE, (object) [
'quizid' => 1,
'cmid' => 1,
'templateid' => $template->get('id'),

View File

@ -23,7 +23,9 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use quizaccess_seb\access_manager;
use mod_quiz\local\access_rule_base;
use mod_quiz\quiz_attempt;
use quizaccess_seb\seb_access_manager;
use quizaccess_seb\settings_provider;
defined('MOODLE_INTERNAL') || die();
@ -189,7 +191,7 @@ trait quizaccess_seb_test_helper_trait {
$this->setUser($user);
$starttime = time();
$quizobj = \quiz::create($quiz->id, $user->id);
$quizobj = mod_quiz\quiz_settings::create($quiz->id, $user->id);
$quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
@ -200,7 +202,7 @@ trait quizaccess_seb_test_helper_trait {
quiz_attempt_save_started($quizobj, $quba, $attempt);
// Answer the questions.
$attemptobj = \quiz_attempt::create($attempt->id);
$attemptobj = quiz_attempt::create($attempt->id);
$tosubmit = [
1 => ['answer' => 'frog'],
@ -210,7 +212,7 @@ trait quizaccess_seb_test_helper_trait {
$attemptobj->process_submitted_actions($starttime, false, $tosubmit);
// Finish the attempt.
$attemptobj = \quiz_attempt::create($attempt->id);
$attemptobj = quiz_attempt::create($attempt->id);
$attemptobj->process_finish($starttime, false);
$this->setUser();
@ -237,21 +239,21 @@ trait quizaccess_seb_test_helper_trait {
/**
* Get access manager for testing.
*
* @return \quizaccess_seb\access_manager
* @return \quizaccess_seb\seb_access_manager
*/
protected function get_access_manager() {
return new access_manager(new \quiz($this->quiz,
return new seb_access_manager(new mod_quiz\quiz_settings($this->quiz,
get_coursemodule_from_id('quiz', $this->quiz->cmid), $this->course));
}
/**
* A helper method to make the rule form the currently created quiz and course.
*
* @return \quiz_access_rule_base|null
* @return access_rule_base|null
*/
protected function make_rule() {
return \quizaccess_seb::make(
new \quiz($this->quiz, get_coursemodule_from_id('quiz', $this->quiz->cmid), $this->course),
new mod_quiz\quiz_settings($this->quiz, get_coursemodule_from_id('quiz', $this->quiz->cmid), $this->course),
0,
true
);

View File

@ -14,29 +14,18 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Implementaton of the quizaccess_securewindow plugin.
*
* @package quizaccess
* @subpackage securewindow
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
use mod_quiz\local\access_rule_base;
use mod_quiz\quiz_settings;
/**
* A rule for ensuring that the quiz is opened in a popup, with some JavaScript
* to prevent copying and pasting, etc.
*
* @package quizaccess_securewindow
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quizaccess_securewindow extends quiz_access_rule_base {
class quizaccess_securewindow extends access_rule_base {
/** @var array options that should be used for opening the secure popup. */
protected static $popupoptions = array(
'left' => 0,
@ -52,7 +41,7 @@ class quizaccess_securewindow extends quiz_access_rule_base {
'menubar' => false,
);
public static function make(quiz $quizobj, $timenow, $canignoretimelimits) {
public static function make(quiz_settings $quizobj, $timenow, $canignoretimelimits) {
if ($quizobj->get_quiz()->browsersecurity !== 'securewindow') {
return null;

View File

@ -16,7 +16,7 @@
namespace quizaccess_securewindow;
use quiz;
use mod_quiz\quiz_settings;
use quizaccess_securewindow;
defined('MOODLE_INTERNAL') || die();
@ -32,7 +32,7 @@ require_once($CFG->dirroot . '/mod/quiz/accessrule/securewindow/rule.php');
* @copyright 2008 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @covers \quiz_access_rule_base
* @covers \mod_quiz\local\access_rule_base
* @covers \quizaccess_securewindow
*/
class rule_test extends \basic_testcase {
@ -42,7 +42,7 @@ class rule_test extends \basic_testcase {
$quiz->browsersecurity = 'securewindow';
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$rule = new quizaccess_securewindow($quizobj, 0);
$attempt = new \stdClass();

View File

@ -14,31 +14,23 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
use mod_quiz\form\preflight_check_form;
use mod_quiz\local\access_rule_base;
use mod_quiz\quiz_settings;
/**
* Implementaton of the quizaccess_timelimit plugin.
* A rule representing the time limit.
*
* @package quizaccess
* @subpackage timelimit
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
/**
* A rule representing the time limit. It does not actually restrict access, but we use this
* It does not actually restrict access, but we use this
* class to encapsulate some of the relevant code.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package quizaccess_timelimit
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quizaccess_timelimit extends quiz_access_rule_base {
class quizaccess_timelimit extends access_rule_base {
public static function make(quiz $quizobj, $timenow, $canignoretimelimits) {
public static function make(quiz_settings $quizobj, $timenow, $canignoretimelimits) {
if (empty($quizobj->get_quiz()->timelimit) || $canignoretimelimits) {
return null;
@ -74,7 +66,7 @@ class quizaccess_timelimit extends quiz_access_rule_base {
return $attemptid === null;
}
public function add_preflight_check_form_fields(mod_quiz_preflight_check_form $quizform,
public function add_preflight_check_form_fields(preflight_check_form $quizform,
MoodleQuickForm $mform, $attemptid) {
$mform->addElement('header', 'honestycheckheader',
get_string('confirmstartheader', 'quizaccess_timelimit'));

View File

@ -16,7 +16,7 @@
namespace quizaccess_timelimit;
use quiz;
use mod_quiz\quiz_settings;
use quizaccess_timelimit;
defined('MOODLE_INTERNAL') || die();
@ -39,7 +39,7 @@ class rule_test extends \basic_testcase {
$quiz->timelimit = 3600;
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$rule = new quizaccess_timelimit($quizobj, 10000);
$attempt = new \stdClass();
@ -88,7 +88,7 @@ class rule_test extends \basic_testcase {
$quiz->timelimit = $timelimit;
$cm = new \stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
$quizobj = new quiz_settings($quiz, $cm, null);
$rule = new quizaccess_timelimit($quizobj, $timenow);
$attempt = new \stdClass();

View File

@ -2,6 +2,17 @@ This files describes API changes for quiz access rule plugins.
Overview of this plugin type at http://docs.moodle.org/dev/Quiz_access_rules
=== 4.2 ===
* Note that class mod_quiz_preflight_check_form has been renamed to
mod_quiz\form\preflight_check_form.
* The base class quiz_access_rule_base has been moved to mod_quiz\local\access_rule_base.
Please:
1. update your class declaration to ... extends access_rule_base {
2. Add use mod_quiz\local\access_rule_base;
3. Remove require_once($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
=== 2.8, 2.7.1, 2.6.4 and 2.5.7 ===
* New static method delete_settings for access rules, which is called when a

View File

@ -26,9 +26,9 @@
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
require_once($CFG->dirroot . '/mod/quiz/addrandomform.php');
require_once($CFG->dirroot . '/question/editlib.php');
use mod_quiz\form\add_random_form;
use qbank_managecategories\question_category_object;
list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) =
@ -75,7 +75,7 @@ $qcobject = new question_category_object(
null,
$contexts->having_cap('moodle/question:add'));
$mform = new quiz_add_random_form(new moodle_url('/mod/quiz/addrandom.php'),
$mform = new add_random_form(new moodle_url('/mod/quiz/addrandom.php'),
array('contexts' => $contexts, 'cat' => $pagevars['cat']));
if ($mform->is_cancelled()) {

View File

@ -15,134 +15,11 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines the Moodle forum used to add random questions to the quiz.
* File only retained to prevent fatal errors in code that tries to require/include this.
*
* @package mod_quiz
* @copyright 2008 Olli Savolainen
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-76612 delete this file as part of Moodle 4.6 development.
* @deprecated This file is no longer required in Moodle 4.2+.
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* The add random questions form.
*
* @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_add_random_form extends moodleform {
protected function definition() {
global $OUTPUT, $PAGE, $CFG;
$mform = $this->_form;
$mform->setDisableShortforms();
$contexts = $this->_customdata['contexts'];
$usablecontexts = $contexts->having_cap('moodle/question:useall');
// Random from existing category section.
$mform->addElement('header', 'existingcategoryheader',
get_string('randomfromexistingcategory', 'quiz'));
$mform->addElement('questioncategory', 'category', get_string('category'),
array('contexts' => $usablecontexts, 'top' => true));
$mform->setDefault('category', $this->_customdata['cat']);
$mform->addElement('checkbox', 'includesubcategories', '', get_string('recurse', 'quiz'));
$tops = question_get_top_categories_for_contexts(array_column($contexts->all(), 'id'));
$mform->hideIf('includesubcategories', 'category', 'in', $tops);
if ($CFG->usetags) {
$tagstrings = array();
$tags = core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $usablecontexts);
foreach ($tags as $tag) {
$tagstrings["{$tag->id},{$tag->name}"] = $tag->name;
}
$options = array(
'multiple' => true,
'noselectionstring' => get_string('anytags', 'quiz'),
);
$mform->addElement('autocomplete', 'fromtags', get_string('randomquestiontags', 'mod_quiz'), $tagstrings, $options);
$mform->addHelpButton('fromtags', 'randomquestiontags', 'mod_quiz');
}
// TODO: in the past, the drop-down used to only show sensible choices for
// number of questions to add. That is, if the currently selected filter
// only matched 9 questions (not already in the quiz), then the drop-down would
// only offer choices 1..9. This nice UI hint got lost when the UI became Ajax-y.
// We should add it back.
$mform->addElement('select', 'numbertoadd', get_string('randomnumber', 'quiz'),
$this->get_number_of_questions_to_add_choices());
$previewhtml = $OUTPUT->render_from_template('mod_quiz/random_question_form_preview', []);
$mform->addElement('html', $previewhtml);
$mform->addElement('submit', 'existingcategory', get_string('addrandomquestion', 'quiz'));
// If the manage categories plugins is enabled, add the elements to create a new category in the form.
if (\core\plugininfo\qbank::is_plugin_enabled(\qbank_managecategories\helper::PLUGINNAME)) {
// Random from a new category section.
$mform->addElement('header', 'newcategoryheader',
get_string('randomquestionusinganewcategory', 'quiz'));
$mform->addElement('text', 'name', get_string('name'), 'maxlength="254" size="50"');
$mform->setType('name', PARAM_TEXT);
$mform->addElement('questioncategory', 'parent', get_string('parentcategory', 'question'),
array('contexts' => $usablecontexts, 'top' => true));
$mform->addHelpButton('parent', 'parentcategory', 'question');
$mform->addElement('submit', 'newcategory',
get_string('createcategoryandaddrandomquestion', 'quiz'));
}
// Cancel button.
$mform->addElement('cancel');
$mform->closeHeaderBefore('cancel');
$mform->addElement('hidden', 'addonpage', 0, 'id="rform_qpage"');
$mform->setType('addonpage', PARAM_SEQUENCE);
$mform->addElement('hidden', 'cmid', 0);
$mform->setType('cmid', PARAM_INT);
$mform->addElement('hidden', 'returnurl', 0);
$mform->setType('returnurl', PARAM_LOCALURL);
// Add the javascript required to enhance this mform.
$PAGE->requires->js_call_amd('mod_quiz/add_random_form', 'init', [
$mform->getAttribute('id'),
$contexts->lowest()->id,
$tops,
$CFG->usetags
]);
}
public function validation($fromform, $files) {
$errors = parent::validation($fromform, $files);
if (!empty($fromform['newcategory']) && trim($fromform['name']) == '') {
$errors['name'] = get_string('categorynamecantbeblank', 'question');
}
return $errors;
}
/**
* Return an arbitrary array for the dropdown menu
*
* @param int $maxrand
* @return array of integers [1, 2, ..., 100] (or to the smaller of $maxrand and 100.)
*/
private function get_number_of_questions_to_add_choices($maxrand = 100) {
$randomcount = array();
for ($i = 1; $i <= min(100, $maxrand); $i++) {
$randomcount[$i] = $i;
}
return $randomcount;
}
}
debugging('This file is no longer required in Moodle 4.2+. Please do not include/require it.', DEBUG_DEVELOPER);

View File

@ -22,6 +22,10 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_quiz\output\navigation_panel_attempt;
use mod_quiz\output\renderer;
use mod_quiz\quiz_attempt;
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
@ -57,7 +61,7 @@ if ($attemptobj->get_userid() != $USER->id) {
if ($attemptobj->has_capability('mod/quiz:viewreports')) {
redirect($attemptobj->review_url(null, $page));
} else {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt');
throw new moodle_exception('notyourattempt', 'quiz', $quizobj->view_url());
}
}
@ -82,6 +86,7 @@ if ($attemptobj->is_finished()) {
// Check the access rules.
$accessmanager = $attemptobj->get_access_manager(time());
$accessmanager->setup_attempt_page($PAGE);
/** @var renderer $output */
$output = $PAGE->get_renderer('mod_quiz');
$messages = $accessmanager->prevent_access();
if (!$attemptobj->is_preview_user() && $messages) {
@ -107,7 +112,7 @@ $slots = $attemptobj->get_slots($page);
// Check.
if (empty($slots)) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noquestionsfound');
throw new moodle_exception('noquestionsfound', 'quiz', $quizobj->view_url());
}
// Update attempt page, redirecting the user if $page is not valid.
@ -121,7 +126,7 @@ $PAGE->requires->js_init_call('M.mod_quiz.init_attempt_form', null, false, quiz_
\core\session\manager::keepalive(); // Try to prevent sessions expiring during quiz attempts.
// Arrange for the navigation to be displayed in the first region on the page.
$navbc = $attemptobj->get_navigation_panel($output, 'quiz_attempt_nav_panel', $page);
$navbc = $attemptobj->get_navigation_panel($output, navigation_panel_attempt::class, $page);
$regions = $PAGE->blocks->get_regions();
$PAGE->blocks->add_fake_block($navbc, reset($regions));

File diff suppressed because it is too large Load Diff

View File

@ -45,7 +45,7 @@ require_login($attemptobj->get_course(), false, $attemptobj->get_cm());
// Check that this attempt belongs to this user.
if ($attemptobj->get_userid() != $USER->id) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt');
throw new moodle_exception('notyourattempt', 'quiz', $attemptobj->view_url());
}
// Check capabilities.
@ -55,8 +55,7 @@ if (!$attemptobj->is_preview_user()) {
// If the attempt is already closed, send them to the review page.
if ($attemptobj->is_finished()) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(),
'attemptalreadyclosed', null, $attemptobj->review_url());
throw new moodle_exception('attemptalreadyclosed', 'quiz', $attemptobj->review_url());
}
$attemptobj->process_auto_save($timenow);

View File

@ -0,0 +1,586 @@
<?php
// 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/>.
namespace mod_quiz;
use core_component;
use mod_quiz\form\preflight_check_form;
use mod_quiz\local\access_rule_base;
use mod_quiz\question\display_options;
use mod_quiz_mod_form;
use mod_quiz\output\renderer;
use moodle_page;
use moodle_url;
use MoodleQuickForm;
use stdClass;
/**
* This class aggregates the access rules that apply to a particular quiz.
*
* This provides a convenient API which other parts of the quiz code can use
* to interact with the access rules.
*
* @package mod_quiz
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.2
*/
class access_manager {
/** @var quiz_settings the quiz settings object. */
protected $quizobj;
/** @var int the time to be considered as 'now'. */
protected $timenow;
/** @var access_rule_base instances of the active rules for this quiz. */
protected $rules = [];
/**
* Create an instance for a particular quiz.
*
* @param quiz_settings $quizobj the quiz settings.
* The quiz we will be controlling access to.
* @param int $timenow The time to use as 'now'.
* @param bool $canignoretimelimits Whether this user is exempt from time
* limits (has_capability('mod/quiz:ignoretimelimits', ...)).
*/
public function __construct(quiz_settings $quizobj, int $timenow, bool $canignoretimelimits) {
$this->quizobj = $quizobj;
$this->timenow = $timenow;
$this->rules = $this->make_rules($quizobj, $timenow, $canignoretimelimits);
}
/**
* Make all the rules relevant to a particular quiz.
*
* @param quiz_settings $quizobj information about the quiz in question.
* @param int $timenow the time that should be considered as 'now'.
* @param bool $canignoretimelimits whether the current user is exempt from
* time limits by the mod/quiz:ignoretimelimits capability.
* @return access_rule_base[] rules that apply to this quiz.
*/
protected function make_rules(quiz_settings $quizobj, int $timenow, bool $canignoretimelimits): array {
$rules = [];
foreach (self::get_rule_classes() as $ruleclass) {
$rule = $ruleclass::make($quizobj, $timenow, $canignoretimelimits);
if ($rule) {
$rules[$ruleclass] = $rule;
}
}
$superceededrules = [];
foreach ($rules as $rule) {
$superceededrules += $rule->get_superceded_rules();
}
foreach ($superceededrules as $superceededrule) {
unset($rules['quizaccess_' . $superceededrule]);
}
return $rules;
}
/**
* Get that names of all the installed rule classes.
*
* @return array of class names.
*/
protected static function get_rule_classes(): array {
return core_component::get_plugin_list_with_class('quizaccess', '', 'rule.php');
}
/**
* Add any form fields that the access rules require to the settings form.
*
* Note that the standard plugins do not use this mechanism, becuase all their
* settings are stored in the quiz table.
*
* @param mod_quiz_mod_form $quizform the quiz settings form that is being built.
* @param MoodleQuickForm $mform the wrapped MoodleQuickForm.
*/
public static function add_settings_form_fields(
mod_quiz_mod_form $quizform, MoodleQuickForm $mform): void {
foreach (self::get_rule_classes() as $rule) {
$rule::add_settings_form_fields($quizform, $mform);
}
}
/**
* The the options for the Browser security settings menu.
*
* @return array key => lang string.
*/
public static function get_browser_security_choices(): array {
$options = ['-' => get_string('none', 'quiz')];
foreach (self::get_rule_classes() as $rule) {
$options += $rule::get_browser_security_choices();
}
return $options;
}
/**
* Validate the data from any form fields added using {@see add_settings_form_fields()}.
*
* @param array $errors the errors found so far.
* @param array $data the submitted form data.
* @param array $files information about any uploaded files.
* @param mod_quiz_mod_form $quizform the quiz form object.
* @return array $errors the updated $errors array.
*/
public static function validate_settings_form_fields(array $errors,
array $data, array $files, mod_quiz_mod_form $quizform): array {
foreach (self::get_rule_classes() as $rule) {
$errors = $rule::validate_settings_form_fields($errors, $data, $files, $quizform);
}
return $errors;
}
/**
* Save any submitted settings when the quiz settings form is submitted.
*
* Note that the standard plugins do not use this mechanism because their
* settings are stored in the quiz table.
*
* @param stdClass $quiz the data from the quiz form, including $quiz->id
* which is the id of the quiz being saved.
*/
public static function save_settings(stdClass $quiz): void {
foreach (self::get_rule_classes() as $rule) {
$rule::save_settings($quiz);
}
}
/**
* Delete any rule-specific settings when the quiz is deleted.
*
* Note that the standard plugins do not use this mechanism because their
* settings are stored in the quiz table.
*
* @param stdClass $quiz the data from the database, including $quiz->id
* which is the id of the quiz being deleted.
* @since Moodle 2.7.1, 2.6.4, 2.5.7
*/
public static function delete_settings(stdClass $quiz): void {
foreach (self::get_rule_classes() as $rule) {
$rule::delete_settings($quiz);
}
}
/**
* Build the SQL for loading all the access settings in one go.
*
* @param int $quizid the quiz id.
* @param array $rules list of rule plugins, from {@see get_rule_classes()}.
* @param string $basefields initial part of the select list.
* @return array with two elements, the sql and the placeholder values.
* If $basefields is '' then you must allow for the possibility that
* there is no data to load, in which case this method returns $sql = ''.
*/
protected static function get_load_sql(int $quizid, array $rules, string $basefields): array {
$allfields = $basefields;
$alljoins = '{quiz} quiz';
$allparams = ['quizid' => $quizid];
foreach ($rules as $rule) {
[$fields, $joins, $params] = $rule::get_settings_sql($quizid);
if ($fields) {
if ($allfields) {
$allfields .= ', ';
}
$allfields .= $fields;
}
if ($joins) {
$alljoins .= ' ' . $joins;
}
if ($params) {
$allparams += $params;
}
}
if ($allfields === '') {
return ['', []];
}
return ["SELECT $allfields FROM $alljoins WHERE quiz.id = :quizid", $allparams];
}
/**
* Load any settings required by the access rules. We try to do this with
* a single DB query.
*
* Note that the standard plugins do not use this mechanism, becuase all their
* settings are stored in the quiz table.
*
* @param int $quizid the quiz id.
* @return array setting value name => value. The value names should all
* start with the name of the corresponding plugin to avoid collisions.
*/
public static function load_settings(int $quizid): array {
global $DB;
$rules = self::get_rule_classes();
[$sql, $params] = self::get_load_sql($quizid, $rules, '');
if ($sql) {
$data = (array) $DB->get_record_sql($sql, $params);
} else {
$data = [];
}
foreach ($rules as $rule) {
$data += $rule::get_extra_settings($quizid);
}
return $data;
}
/**
* Load the quiz settings and any settings required by the access rules.
* We try to do this with a single DB query.
*
* Note that the standard plugins do not use this mechanism, becuase all their
* settings are stored in the quiz table.
*
* @param int $quizid the quiz id.
* @return stdClass mdl_quiz row with extra fields.
*/
public static function load_quiz_and_settings(int $quizid): stdClass {
global $DB;
$rules = self::get_rule_classes();
[$sql, $params] = self::get_load_sql($quizid, $rules, 'quiz.*');
$quiz = $DB->get_record_sql($sql, $params, MUST_EXIST);
foreach ($rules as $rule) {
foreach ($rule::get_extra_settings($quizid) as $name => $value) {
$quiz->$name = $value;
}
}
return $quiz;
}
/**
* Get an array of the class names of all the active rules.
*
* Mainly useful for debugging.
*
* @return array
*/
public function get_active_rule_names(): array {
$classnames = [];
foreach ($this->rules as $rule) {
$classnames[] = get_class($rule);
}
return $classnames;
}
/**
* Accumulates an array of messages.
*
* @param array $messages the current list of messages.
* @param string|array $new the new messages or messages.
* @return array the updated array of messages.
*/
protected function accumulate_messages(array $messages, $new): array {
if (is_array($new)) {
$messages = array_merge($messages, $new);
} else if (is_string($new) && $new) {
$messages[] = $new;
}
return $messages;
}
/**
* Provide a description of the rules that apply to this quiz, such
* as is shown at the top of the quiz view page. Note that not all
* rules consider themselves important enough to output a description.
*
* @return array an array of description messages which may be empty. It
* would be sensible to output each one surrounded by &lt;p> tags.
*/
public function describe_rules(): array {
$result = [];
foreach ($this->rules as $rule) {
$result = $this->accumulate_messages($result, $rule->description());
}
return $result;
}
/**
* Whether a user should be allowed to start a new attempt at this quiz now.
* If there are any restrictions in force now, return an array of reasons why access
* should be blocked. If access is OK, return false.
*
* @param int $numprevattempts the number of previous attempts this user has made.
* @param stdClass|false $lastattempt information about the user's last completed attempt.
* if there is not a previous attempt, the false is passed.
* @return array an array of reason why access is not allowed. An empty array
* (== false) if access should be allowed.
*/
public function prevent_new_attempt(int $numprevattempts, $lastattempt): array {
$reasons = [];
foreach ($this->rules as $rule) {
$reasons = $this->accumulate_messages($reasons,
$rule->prevent_new_attempt($numprevattempts, $lastattempt));
}
return $reasons;
}
/**
* Whether the user should be blocked from starting a new attempt or continuing
* an attempt now. If there are any restrictions in force now, return an array
* of reasons why access should be blocked. If access is OK, return false.
*
* @return array An array of reason why access is not allowed, or an empty array
* (== false) if access should be allowed.
*/
public function prevent_access(): array {
$reasons = [];
foreach ($this->rules as $rule) {
$reasons = $this->accumulate_messages($reasons, $rule->prevent_access());
}
return $reasons;
}
/**
* Is a UI check is required before the user starts/continues their attempt.
*
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
* @return bool whether a check is required.
*/
public function is_preflight_check_required(?int $attemptid): bool {
foreach ($this->rules as $rule) {
if ($rule->is_preflight_check_required($attemptid)) {
return true;
}
}
return false;
}
/**
* Build the form required to do the pre-flight checks.
* @param moodle_url $url the form action URL.
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
* @return preflight_check_form the form.
*/
public function get_preflight_check_form(moodle_url $url, ?int $attemptid): preflight_check_form {
// This form normally wants POST submissions. However, it also needs to
// accept GET submissions. Since formslib is strict, we have to detect
// which case we are in, and set the form property appropriately.
$method = 'post';
if (!empty($_GET['_qf__preflight_check_form'])) {
$method = 'get';
}
return new preflight_check_form($url->out_omit_querystring(),
['rules' => $this->rules, 'quizobj' => $this->quizobj,
'attemptid' => $attemptid, 'hidden' => $url->params()], $method);
}
/**
* The pre-flight check has passed. This is a chance to record that fact in some way.
*
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
*/
public function notify_preflight_check_passed(?int $attemptid): void {
foreach ($this->rules as $rule) {
$rule->notify_preflight_check_passed($attemptid);
}
}
/**
* Inform the rules that the current attempt is finished.
*
* This is use, for example by the password rule, to clear the flag in the session.
*/
public function current_attempt_finished(): void {
foreach ($this->rules as $rule) {
$rule->current_attempt_finished();
}
}
/**
* Do any of the rules mean that this student will no be allowed any further attempts at this
* quiz. Used, for example, to change the label by the grade displayed on the view page from
* 'your current grade is' to 'your final grade is'.
*
* @param int $numprevattempts the number of previous attempts this user has made.
* @param stdClass|false $lastattempt information about the user's last completed attempt.
* @return bool true if there is no way the user will ever be allowed to attempt
* this quiz again.
*/
public function is_finished(int $numprevattempts, $lastattempt): bool {
foreach ($this->rules as $rule) {
if ($rule->is_finished($numprevattempts, $lastattempt)) {
return true;
}
}
return false;
}
/**
* Sets up the attempt (review or summary) page with any properties required
* by the access rules.
*
* @param moodle_page $page the page object to initialise.
*/
public function setup_attempt_page(moodle_page $page): void {
foreach ($this->rules as $rule) {
$rule->setup_attempt_page($page);
}
}
/**
* Compute when the attempt must be submitted.
*
* @param stdClass $attempt the data from the relevant quiz_attempts row.
* @return int|false the attempt close time. False if there is no limit.
*/
public function get_end_time(stdClass $attempt) {
$timeclose = false;
foreach ($this->rules as $rule) {
$ruletimeclose = $rule->end_time($attempt);
if ($ruletimeclose !== false && ($timeclose === false || $ruletimeclose < $timeclose)) {
$timeclose = $ruletimeclose;
}
}
return $timeclose;
}
/**
* Compute what should be displayed to the user for time remaining in this attempt.
*
* @param stdClass $attempt the data from the relevant quiz_attempts row.
* @param int $timenow the time to consider as 'now'.
* @return int|false the number of seconds remaining for this attempt.
* False if no limit should be displayed.
*/
public function get_time_left_display(stdClass $attempt, int $timenow) {
$timeleft = false;
foreach ($this->rules as $rule) {
$ruletimeleft = $rule->time_left_display($attempt, $timenow);
if ($ruletimeleft !== false && ($timeleft === false || $ruletimeleft < $timeleft)) {
$timeleft = $ruletimeleft;
}
}
return $timeleft;
}
/**
* Is this quiz required to be shown in a popup window?
*
* @return bool true if a popup is required.
*/
public function attempt_must_be_in_popup(): bool {
foreach ($this->rules as $rule) {
if ($rule->attempt_must_be_in_popup()) {
return true;
}
}
return false;
}
/**
* Get options required for opening the attempt in a popup window.
*
* @return array any options that are required for showing the attempt page
* in a popup window.
*/
public function get_popup_options(): array {
$options = [];
foreach ($this->rules as $rule) {
$options += $rule->get_popup_options();
}
return $options;
}
/**
* Send the user back to the quiz view page. Normally this is just a redirect, but
* If we were in a secure window, we close this window, and reload the view window we came from.
*
* This method does not return;
*
* @param renderer $output the quiz renderer.
* @param string $message optional message to output while redirecting.
*/
public function back_to_view_page(renderer $output, string $message = ''): void {
// Actually return type 'never' on the previous line, once 8.1 is our minimum PHP version.
if ($this->attempt_must_be_in_popup()) {
echo $output->close_attempt_popup(new moodle_url($this->quizobj->view_url()), $message);
die();
} else {
redirect($this->quizobj->view_url(), $message);
}
}
/**
* Make some text into a link to review the quiz, if that is appropriate.
*
* @param stdClass $attempt the attempt object
* @param mixed $nolongerused not used any more.
* @param renderer $output quiz renderer instance.
* @return string some HTML, the $linktext either unmodified or wrapped in a
* link to the review page.
*/
public function make_review_link(stdClass $attempt, $nolongerused, renderer $output): string {
// If the attempt is still open, don't link.
if (in_array($attempt->state, [quiz_attempt::IN_PROGRESS, quiz_attempt::OVERDUE])) {
return $output->no_review_message('');
}
$when = quiz_attempt_state($this->quizobj->get_quiz(), $attempt);
$reviewoptions = display_options::make_from_quiz(
$this->quizobj->get_quiz(), $when);
if (!$reviewoptions->attempt) {
return $output->no_review_message($this->quizobj->cannot_review_message($when, true));
} else {
return $output->review_link($this->quizobj->review_url($attempt->id),
$this->attempt_must_be_in_popup(), $this->get_popup_options());
}
}
/**
* Run the preflight checks using the given data in all the rules supporting them.
*
* @param array $data passed data for validation
* @param array $files un-used, Moodle seems to not support it anymore
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
* @return array of errors, empty array means no errors
* @since Moodle 3.1
*/
public function validate_preflight_check(array $data, array $files, ?int $attemptid): array {
$errors = [];
foreach ($this->rules as $rule) {
if ($rule->is_preflight_check_required($attemptid)) {
$errors = $rule->validate_preflight_check($data, $files, $errors, $attemptid);
}
}
return $errors;
}
}

View File

@ -16,6 +16,8 @@
namespace mod_quiz\admin;
use mod_quiz\access_manager;
/**
* Admin settings class for the quiz browser security option.
*
@ -35,7 +37,7 @@ class browser_security_setting extends \admin_setting_configselect_with_advanced
}
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
$this->choices = \quiz_access_manager::get_browser_security_choices();
$this->choices = access_manager::get_browser_security_choices();
return true;
}

View File

@ -20,10 +20,8 @@ namespace mod_quiz\completion;
use context_module;
use core_completion\activity_custom_completion;
use grade_grade;
use grade_item;
use quiz;
use quiz_access_manager;
use mod_quiz\quiz_settings;
use mod_quiz\access_manager;
/**
* Activity custom completion subclass for the quiz activity.
@ -71,8 +69,8 @@ class custom_completion extends activity_custom_completion {
}
$lastfinishedattempt = end($attempts);
$context = context_module::instance($this->cm->id);
$quizobj = quiz::create($this->cm->instance, $this->userid);
$accessmanager = new quiz_access_manager(
$quizobj = quiz_settings::create($this->cm->instance, $this->userid);
$accessmanager = new access_manager(
$quizobj,
time(),
has_capability('mod/quiz:ignoretimelimits', $context, $this->userid, false)

View File

@ -25,6 +25,9 @@
*/
use core_course\external\helper_for_get_mods_by_courses;
use mod_quiz\access_manager;
use mod_quiz\quiz_attempt;
use mod_quiz\quiz_settings;
defined('MOODLE_INTERNAL') || die;
@ -111,8 +114,8 @@ class mod_quiz_external extends external_api {
$quizdetails['hasfeedback'] = (!empty($hasfeedback)) ? 1 : 0;
$timenow = time();
$quizobj = quiz::create($quiz->id, $USER->id);
$accessmanager = new quiz_access_manager($quizobj, $timenow, has_capability('mod/quiz:ignoretimelimits',
$quizobj = quiz_settings::create($quiz->id, $USER->id);
$accessmanager = new access_manager($quizobj, $timenow, has_capability('mod/quiz:ignoretimelimits',
$context, null, false));
// Fields the user could see if have access to the quiz.
@ -304,7 +307,6 @@ class mod_quiz_external extends external_api {
* @param int $quizid quiz instance id
* @return array of warnings and status result
* @since Moodle 3.1
* @throws moodle_exception
*/
public static function view_quiz($quizid) {
global $DB;
@ -365,10 +367,9 @@ class mod_quiz_external extends external_api {
* @param bool $includepreviews whether to include previews or not
* @return array of warnings and the list of attempts
* @since Moodle 3.1
* @throws invalid_parameter_exception
*/
public static function get_user_attempts($quizid, $userid = 0, $status = 'finished', $includepreviews = false) {
global $DB, $USER;
global $USER;
$warnings = array();
@ -710,7 +711,6 @@ class mod_quiz_external extends external_api {
* @param bool $forcenew Whether to force a new attempt or not.
* @return array of warnings and the attempt basic data
* @since Moodle 3.1
* @throws moodle_quiz_exception
*/
public static function start_attempt($quizid, $preflightdata = array(), $forcenew = false) {
global $DB, $USER;
@ -728,11 +728,11 @@ class mod_quiz_external extends external_api {
list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']);
$quizobj = quiz::create($cm->instance, $USER->id);
$quizobj = quiz_settings::create($cm->instance, $USER->id);
// Check questions.
if (!$quizobj->has_questions()) {
throw new moodle_quiz_exception($quizobj, 'noquestionsfound');
throw new moodle_exception('noquestionsfound', 'quiz', $quizobj->view_url());
}
// Create an object to manage all the other (non-roles) access rules.
@ -766,7 +766,7 @@ class mod_quiz_external extends external_api {
$errors = $accessmanager->validate_preflight_check($provideddata, [], $currentattemptid);
if (!empty($errors)) {
throw new moodle_quiz_exception($quizobj, array_shift($errors));
throw new moodle_exception(array_shift($errors), 'quiz', $quizobj->view_url());
}
// Pre-flight check passed.
@ -775,9 +775,9 @@ class mod_quiz_external extends external_api {
if ($currentattemptid) {
if ($lastattempt->state == quiz_attempt::OVERDUE) {
throw new moodle_quiz_exception($quizobj, 'stateoverdue');
throw new moodle_exception('stateoverdue', 'quiz', $quizobj->view_url());
} else {
throw new moodle_quiz_exception($quizobj, 'attemptstillinprogress');
throw new moodle_exception('attemptstillinprogress', 'quiz', $quizobj->view_url());
}
}
$offlineattempt = WS_SERVER ? true : false;
@ -812,7 +812,6 @@ class mod_quiz_external extends external_api {
* @param bool $checkaccessrules whether to check the quiz access rules or not
* @param bool $failifoverdue whether to return error if the attempt is overdue
* @return array containing the attempt object and access messages
* @throws moodle_quiz_exception
* @since Moodle 3.1
*/
protected static function validate_attempt($params, $checkaccessrules = true, $failifoverdue = true) {
@ -825,7 +824,7 @@ class mod_quiz_external extends external_api {
// Check that this attempt belongs to this user.
if ($attemptobj->get_userid() != $USER->id) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt');
throw new moodle_exception('notyourattempt', 'quiz', $attemptobj->view_url());
}
// General capabilities check.
@ -843,15 +842,15 @@ class mod_quiz_external extends external_api {
$messages = $accessmanager->prevent_access();
if (!$ispreviewuser && $messages) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attempterror');
throw new moodle_exception('attempterror', 'quiz', $attemptobj->view_url());
}
}
// Attempt closed?.
if ($attemptobj->is_finished()) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attemptalreadyclosed');
throw new moodle_exception('attemptalreadyclosed', 'quiz', $attemptobj->view_url());
} else if ($failifoverdue && $attemptobj->get_state() == quiz_attempt::OVERDUE) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'stateoverdue');
throw new moodle_exception('stateoverdue', 'quiz', $attemptobj->view_url());
}
// User submitted data (like the quiz password).
@ -863,7 +862,7 @@ class mod_quiz_external extends external_api {
$errors = $accessmanager->validate_preflight_check($provideddata, [], $params['attemptid']);
if (!empty($errors)) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), array_shift($errors));
throw new moodle_exception(array_shift($errors), 'quiz', $attemptobj->view_url());
}
// Pre-flight check passed.
$accessmanager->notify_preflight_check_passed($params['attemptid']);
@ -872,19 +871,19 @@ class mod_quiz_external extends external_api {
if (isset($params['page'])) {
// Check if the page is out of range.
if ($params['page'] != $attemptobj->force_page_number_into_range($params['page'])) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Invalid page number');
throw new moodle_exception('Invalid page number', 'quiz', $attemptobj->view_url());
}
// Prevent out of sequence access.
if (!$attemptobj->check_page_access($params['page'])) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Out of sequence access');
throw new moodle_exception('Out of sequence access', 'quiz', $attemptobj->view_url());
}
// Check slots.
$slots = $attemptobj->get_slots($params['page']);
if (empty($slots)) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noquestionsfound');
throw new moodle_exception('noquestionsfound', 'quiz', $attemptobj->view_url());
}
}
@ -1047,7 +1046,6 @@ class mod_quiz_external extends external_api {
* @param array $preflightdata preflight required data (like passwords)
* @return array of warnings and the attempt data, next page, message and questions
* @since Moodle 3.1
* @throws moodle_quiz_exceptions
*/
public static function get_attempt_data($attemptid, $page, $preflightdata = array()) {
global $PAGE;
@ -1371,8 +1369,6 @@ class mod_quiz_external extends external_api {
* @param array $params Array of parameters including the attemptid
* @return array containing the attempt object and display options
* @since Moodle 3.1
* @throws moodle_exception
* @throws moodle_quiz_exception
*/
protected static function validate_attempt_review($params) {
@ -1382,13 +1378,13 @@ class mod_quiz_external extends external_api {
$displayoptions = $attemptobj->get_display_options(true);
if ($attemptobj->is_own_attempt()) {
if (!$attemptobj->is_finished()) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attemptclosed');
throw new moodle_exception('attemptclosed', 'quiz', $attemptobj->view_url());
} else if (!$displayoptions->attempt) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noreview', null, '',
throw new moodle_exception('noreview', 'quiz', $attemptobj->view_url(), null,
$attemptobj->cannot_review_message());
}
} else if (!$attemptobj->is_review_allowed()) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noreviewattempt');
throw new moodle_exception('noreviewattempt', 'quiz', $attemptobj->view_url());
}
return array($attemptobj, $displayoptions);
}
@ -1416,8 +1412,6 @@ class mod_quiz_external extends external_api {
* @param int $page page number, empty for all the questions in all the pages
* @return array of warnings and the attempt data, feedback and questions
* @since Moodle 3.1
* @throws moodle_exception
* @throws moodle_quiz_exception
*/
public static function get_attempt_review($attemptid, $page = -1) {
global $PAGE;
@ -1547,7 +1541,7 @@ class mod_quiz_external extends external_api {
// Update attempt page, throwing an exception if $page is not valid.
if (!$attemptobj->set_currentpage($params['page'])) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Out of sequence access');
throw new moodle_exception('Out of sequence access', 'quiz', $attemptobj->view_url());
}
$result = array();
@ -1713,7 +1707,6 @@ class mod_quiz_external extends external_api {
* @param float $grade the grade to check
* @return array of warnings and status result
* @since Moodle 3.1
* @throws moodle_exception
*/
public static function get_quiz_feedback_for_grade($quizid, $grade) {
global $DB;
@ -1784,7 +1777,6 @@ class mod_quiz_external extends external_api {
* @param int $quizid quiz instance id
* @return array of warnings and the access information
* @since Moodle 3.1
* @throws moodle_quiz_exception
*/
public static function get_quiz_access_information($quizid) {
global $DB, $USER;
@ -1807,10 +1799,10 @@ class mod_quiz_external extends external_api {
$result['canviewreports'] = has_capability('mod/quiz:viewreports', $context);;
// Access manager now.
$quizobj = quiz::create($cm->instance, $USER->id);
$quizobj = quiz_settings::create($cm->instance, $USER->id);
$ignoretimelimits = has_capability('mod/quiz:ignoretimelimits', $context, null, false);
$timenow = time();
$accessmanager = new quiz_access_manager($quizobj, $timenow, $ignoretimelimits);
$accessmanager = new access_manager($quizobj, $timenow, $ignoretimelimits);
$result['accessrules'] = $accessmanager->describe_rules();
$result['activerulenames'] = $accessmanager->get_active_rule_names();
@ -1868,7 +1860,6 @@ class mod_quiz_external extends external_api {
* @param int $attemptid attempt id, 0 for the user last attempt if exists
* @return array of warnings and the access information
* @since Moodle 3.1
* @throws moodle_quiz_exception
*/
public static function get_attempt_access_information($quizid, $attemptid = 0) {
global $DB, $USER;
@ -1883,20 +1874,20 @@ class mod_quiz_external extends external_api {
list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']);
$attempttocheck = 0;
$attempttocheck = null;
if (!empty($params['attemptid'])) {
$attemptobj = quiz_attempt::create($params['attemptid']);
if ($attemptobj->get_userid() != $USER->id) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt');
throw new moodle_exception('notyourattempt', 'quiz', $attemptobj->view_url());
}
$attempttocheck = $attemptobj->get_attempt();
}
// Access manager now.
$quizobj = quiz::create($cm->instance, $USER->id);
$quizobj = quiz_settings::create($cm->instance, $USER->id);
$ignoretimelimits = has_capability('mod/quiz:ignoretimelimits', $context, null, false);
$timenow = time();
$accessmanager = new quiz_access_manager($quizobj, $timenow, $ignoretimelimits);
$accessmanager = new access_manager($quizobj, $timenow, $ignoretimelimits);
$attempts = quiz_get_user_attempts($quiz->id, $USER->id, 'finished', true);
$lastfinishedattempt = end($attempts);
@ -1913,7 +1904,7 @@ class mod_quiz_external extends external_api {
$numattempts = count($attempts);
if (!$attempttocheck) {
$attempttocheck = $unfinishedattempt ? $unfinishedattempt : $lastfinishedattempt;
$attempttocheck = $unfinishedattempt ?: $lastfinishedattempt;
}
$result = array();
@ -1974,7 +1965,6 @@ class mod_quiz_external extends external_api {
* @param int $quizid quiz instance id
* @return array of warnings and the access information
* @since Moodle 3.1
* @throws moodle_quiz_exception
*/
public static function get_quiz_required_qtypes($quizid) {
global $DB, $USER;
@ -1988,7 +1978,7 @@ class mod_quiz_external extends external_api {
list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']);
$quizobj = quiz::create($cm->instance, $USER->id);
$quizobj = quiz_settings::create($cm->instance, $USER->id);
$quizobj->preload_questions();
$quizobj->load_questions();

View File

@ -0,0 +1,145 @@
<?php
// 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/>.
namespace mod_quiz\form;
use core_tag_tag;
use moodleform;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* The add random questions form.
*
* @package mod_quiz
* @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class add_random_form extends moodleform {
protected function definition() {
global $OUTPUT, $PAGE, $CFG;
$mform = $this->_form;
$mform->setDisableShortforms();
$contexts = $this->_customdata['contexts'];
$usablecontexts = $contexts->having_cap('moodle/question:useall');
// Random from existing category section.
$mform->addElement('header', 'existingcategoryheader',
get_string('randomfromexistingcategory', 'quiz'));
$mform->addElement('questioncategory', 'category', get_string('category'),
array('contexts' => $usablecontexts, 'top' => true));
$mform->setDefault('category', $this->_customdata['cat']);
$mform->addElement('checkbox', 'includesubcategories', '', get_string('recurse', 'quiz'));
$tops = question_get_top_categories_for_contexts(array_column($contexts->all(), 'id'));
$mform->hideIf('includesubcategories', 'category', 'in', $tops);
if ($CFG->usetags) {
$tagstrings = array();
$tags = core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $usablecontexts);
foreach ($tags as $tag) {
$tagstrings["{$tag->id},{$tag->name}"] = $tag->name;
}
$options = array(
'multiple' => true,
'noselectionstring' => get_string('anytags', 'quiz'),
);
$mform->addElement('autocomplete', 'fromtags', get_string('randomquestiontags', 'mod_quiz'), $tagstrings, $options);
$mform->addHelpButton('fromtags', 'randomquestiontags', 'mod_quiz');
}
// TODO: in the past, the drop-down used to only show sensible choices for
// number of questions to add. That is, if the currently selected filter
// only matched 9 questions (not already in the quiz), then the drop-down would
// only offer choices 1..9. This nice UI hint got lost when the UI became Ajax-y.
// We should add it back.
$mform->addElement('select', 'numbertoadd', get_string('randomnumber', 'quiz'),
$this->get_number_of_questions_to_add_choices());
$previewhtml = $OUTPUT->render_from_template('mod_quiz/random_question_form_preview', []);
$mform->addElement('html', $previewhtml);
$mform->addElement('submit', 'existingcategory', get_string('addrandomquestion', 'quiz'));
// If the manage categories plugins is enabled, add the elements to create a new category in the form.
if (\core\plugininfo\qbank::is_plugin_enabled(\qbank_managecategories\helper::PLUGINNAME)) {
// Random from a new category section.
$mform->addElement('header', 'newcategoryheader',
get_string('randomquestionusinganewcategory', 'quiz'));
$mform->addElement('text', 'name', get_string('name'), 'maxlength="254" size="50"');
$mform->setType('name', PARAM_TEXT);
$mform->addElement('questioncategory', 'parent', get_string('parentcategory', 'question'),
array('contexts' => $usablecontexts, 'top' => true));
$mform->addHelpButton('parent', 'parentcategory', 'question');
$mform->addElement('submit', 'newcategory',
get_string('createcategoryandaddrandomquestion', 'quiz'));
}
// Cancel button.
$mform->addElement('cancel');
$mform->closeHeaderBefore('cancel');
$mform->addElement('hidden', 'addonpage', 0, 'id="rform_qpage"');
$mform->setType('addonpage', PARAM_SEQUENCE);
$mform->addElement('hidden', 'cmid', 0);
$mform->setType('cmid', PARAM_INT);
$mform->addElement('hidden', 'returnurl', 0);
$mform->setType('returnurl', PARAM_LOCALURL);
// Add the javascript required to enhance this mform.
$PAGE->requires->js_call_amd('mod_quiz/add_random_form', 'init', [
$mform->getAttribute('id'),
$contexts->lowest()->id,
$tops,
$CFG->usetags
]);
}
public function validation($fromform, $files) {
$errors = parent::validation($fromform, $files);
if (!empty($fromform['newcategory']) && trim($fromform['name']) == '') {
$errors['name'] = get_string('categorynamecantbeblank', 'question');
}
return $errors;
}
/**
* Return an arbitrary array for the dropdown menu
*
* @param int $maxrand
* @return array of integers [1, 2, ..., 100] (or to the smaller of $maxrand and 100.)
*/
private function get_number_of_questions_to_add_choices($maxrand = 100) {
$randomcount = array();
for ($i = 1; $i <= min(100, $maxrand); $i++) {
$randomcount[$i] = $i;
}
return $randomcount;
}
}

View File

@ -0,0 +1,301 @@
<?php
// 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/>.
namespace mod_quiz\form;
use cm_info;
use context;
use context_module;
use mod_quiz_mod_form;
use moodle_url;
use moodleform;
use stdClass;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
require_once($CFG->dirroot . '/mod/quiz/mod_form.php');
/**
* Form for editing quiz settings overrides.
*
* @package mod_quiz
* @copyright 2010 Matt Petro
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class edit_override_form extends moodleform {
/** @var cm_info course module object. */
protected $cm;
/** @var stdClass the quiz settings object. */
protected $quiz;
/** @var context_module the quiz context. */
protected $context;
/** @var bool editing group override (true) or user override (false). */
protected $groupmode;
/** @var int groupid, if provided. */
protected $groupid;
/** @var int userid, if provided. */
protected $userid;
/**
* Constructor.
*
* @param moodle_url $submiturl the form action URL.
* @param cm_info $cm course module object.
* @param stdClass $quiz the quiz settings object.
* @param context_module $context the quiz context.
* @param bool $groupmode editing group override (true) or user override (false).
* @param stdClass|null $override the override being edited, if it already exists.
*/
public function __construct(moodle_url $submiturl,
cm_info $cm, stdClass $quiz, context_module $context,
bool $groupmode, ?stdClass $override) {
$this->cm = $cm;
$this->quiz = $quiz;
$this->context = $context;
$this->groupmode = $groupmode;
$this->groupid = empty($override->groupid) ? 0 : $override->groupid;
$this->userid = empty($override->userid) ? 0 : $override->userid;
parent::__construct($submiturl);
}
protected function definition() {
global $DB;
$cm = $this->cm;
$mform = $this->_form;
$mform->addElement('header', 'override', get_string('override', 'quiz'));
$quizgroupmode = groups_get_activity_groupmode($cm);
$accessallgroups = ($quizgroupmode == NOGROUPS) || has_capability('moodle/site:accessallgroups', $this->context);
if ($this->groupmode) {
// Group override.
if ($this->groupid) {
// There is already a groupid, so freeze the selector.
$groupchoices = [];
$groupchoices[$this->groupid] = groups_get_group_name($this->groupid);
$mform->addElement('select', 'groupid',
get_string('overridegroup', 'quiz'), $groupchoices);
$mform->freeze('groupid');
} else {
// Prepare the list of groups.
// Only include the groups the current can access.
$groups = $accessallgroups ? groups_get_all_groups($cm->course) : groups_get_activity_allowed_groups($cm);
if (empty($groups)) {
// Generate an error.
$link = new moodle_url('/mod/quiz/overrides.php', ['cmid' => $cm->id]);
throw new \moodle_exception('groupsnone', 'quiz', $link);
}
$groupchoices = [];
foreach ($groups as $group) {
$groupchoices[$group->id] = $group->name;
}
unset($groups);
if (count($groupchoices) == 0) {
$groupchoices[0] = get_string('none');
}
$mform->addElement('select', 'groupid',
get_string('overridegroup', 'quiz'), $groupchoices);
$mform->addRule('groupid', get_string('required'), 'required', null, 'client');
}
} else {
// User override.
$userfieldsapi = \core_user\fields::for_identity($this->context)->with_userpic()->with_name();
$extrauserfields = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]);
if ($this->userid) {
// There is already a userid, so freeze the selector.
$user = $DB->get_record('user', ['id' => $this->userid]);
profile_load_custom_fields($user);
$userchoices = [];
$userchoices[$this->userid] = self::display_user_name($user, $extrauserfields);
$mform->addElement('select', 'userid',
get_string('overrideuser', 'quiz'), $userchoices);
$mform->freeze('userid');
} else {
// Prepare the list of users.
$groupids = 0;
if (!$accessallgroups) {
$groups = groups_get_activity_allowed_groups($cm);
$groupids = array_keys($groups);
}
$enrolledjoin = get_enrolled_with_capabilities_join(
$this->context, '', 'mod/quiz:attempt', $groupids, true);
$userfieldsql = $userfieldsapi->get_sql('u', true, '', '', false);
list($sort, $sortparams) = users_order_by_sql('u', null,
$this->context, $userfieldsql->mappings);
$users = $DB->get_records_sql("
SELECT $userfieldsql->selects
FROM {user} u
$enrolledjoin->joins
$userfieldsql->joins
LEFT JOIN {quiz_overrides} existingoverride ON
existingoverride.userid = u.id AND existingoverride.quiz = :quizid
WHERE existingoverride.id IS NULL
AND $enrolledjoin->wheres
ORDER BY $sort
", array_merge(['quizid' => $this->quiz->id], $userfieldsql->params, $enrolledjoin->params, $sortparams));
// Filter users based on any fixed restrictions (groups, profile).
$info = new \core_availability\info_module($cm);
$users = $info->filter_user_list($users);
if (empty($users)) {
// Generate an error.
$link = new moodle_url('/mod/quiz/overrides.php', ['cmid' => $cm->id]);
throw new \moodle_exception('usersnone', 'quiz', $link);
}
$userchoices = [];
foreach ($users as $id => $user) {
$userchoices[$id] = self::display_user_name($user, $extrauserfields);
}
unset($users);
$mform->addElement('searchableselector', 'userid',
get_string('overrideuser', 'quiz'), $userchoices);
$mform->addRule('userid', get_string('required'), 'required', null, 'client');
}
}
// Password.
// This field has to be above the date and timelimit fields,
// otherwise browsers will clear it when those fields are changed.
$mform->addElement('passwordunmask', 'password', get_string('requirepassword', 'quiz'));
$mform->setType('password', PARAM_TEXT);
$mform->addHelpButton('password', 'requirepassword', 'quiz');
$mform->setDefault('password', $this->quiz->password);
// Open and close dates.
$mform->addElement('date_time_selector', 'timeopen',
get_string('quizopen', 'quiz'), mod_quiz_mod_form::$datefieldoptions);
$mform->setDefault('timeopen', $this->quiz->timeopen);
$mform->addElement('date_time_selector', 'timeclose',
get_string('quizclose', 'quiz'), mod_quiz_mod_form::$datefieldoptions);
$mform->setDefault('timeclose', $this->quiz->timeclose);
// Time limit.
$mform->addElement('duration', 'timelimit',
get_string('timelimit', 'quiz'), ['optional' => true]);
$mform->addHelpButton('timelimit', 'timelimit', 'quiz');
$mform->setDefault('timelimit', $this->quiz->timelimit);
// Number of attempts.
$attemptoptions = ['0' => get_string('unlimited')];
for ($i = 1; $i <= QUIZ_MAX_ATTEMPT_OPTION; $i++) {
$attemptoptions[$i] = $i;
}
$mform->addElement('select', 'attempts',
get_string('attemptsallowed', 'quiz'), $attemptoptions);
$mform->addHelpButton('attempts', 'attempts', 'quiz');
$mform->setDefault('attempts', $this->quiz->attempts);
// Submit buttons.
$mform->addElement('submit', 'resetbutton',
get_string('reverttodefaults', 'quiz'));
$buttonarray = [];
$buttonarray[] = $mform->createElement('submit', 'submitbutton',
get_string('save', 'quiz'));
$buttonarray[] = $mform->createElement('submit', 'againbutton',
get_string('saveoverrideandstay', 'quiz'));
$buttonarray[] = $mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonbar', '', [' '], false);
$mform->closeHeaderBefore('buttonbar');
}
/**
* Get a user's name and identity ready to display.
*
* @param stdClass $user a user object.
* @param array $extrauserfields (identity fields in user table only from the user_fields API)
* @return string User's name, with extra info, for display.
*/
public static function display_user_name(stdClass $user, array $extrauserfields): string {
$username = fullname($user);
$namefields = [];
foreach ($extrauserfields as $field) {
if (isset($user->$field) && $user->$field !== '') {
$namefields[] = s($user->$field);
} else if (strpos($field, 'profile_field_') === 0) {
$field = substr($field, 14);
if (isset($user->profile[$field]) && $user->profile[$field] !== '') {
$namefields[] = s($user->profile[$field]);
}
}
}
if ($namefields) {
$username .= ' (' . implode(', ', $namefields) . ')';
}
return $username;
}
public function validation($data, $files): array {
$errors = parent::validation($data, $files);
$mform =& $this->_form;
$quiz = $this->quiz;
if ($mform->elementExists('userid')) {
if (empty($data['userid'])) {
$errors['userid'] = get_string('required');
}
}
if ($mform->elementExists('groupid')) {
if (empty($data['groupid'])) {
$errors['groupid'] = get_string('required');
}
}
// Ensure that the dates make sense.
if (!empty($data['timeopen']) && !empty($data['timeclose'])) {
if ($data['timeclose'] < $data['timeopen'] ) {
$errors['timeclose'] = get_string('closebeforeopen', 'quiz');
}
}
// Ensure that at least one quiz setting was changed.
$changed = false;
$keys = ['timeopen', 'timeclose', 'timelimit', 'attempts', 'password'];
foreach ($keys as $key) {
if ($data[$key] != $quiz->{$key}) {
$changed = true;
break;
}
}
if (!$changed) {
$errors['timeopen'] = get_string('nooverridedata', 'quiz');
}
return $errors;
}
}

View File

@ -0,0 +1,64 @@
<?php
// 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/>.
namespace mod_quiz\form;
use moodleform;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* A form that limits student's access to attempt a quiz.
*
* @package mod_quiz
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class preflight_check_form extends moodleform {
protected function definition() {
$mform = $this->_form;
$this->_form->updateAttributes(array('id' => 'mod_quiz_preflight_form'));
foreach ($this->_customdata['hidden'] as $name => $value) {
if ($name === 'sesskey') {
continue;
}
$mform->addElement('hidden', $name, $value);
$mform->setType($name, PARAM_INT);
}
foreach ($this->_customdata['rules'] as $rule) {
if ($rule->is_preflight_check_required($this->_customdata['attemptid'])) {
$rule->add_preflight_check_form_fields($this, $mform,
$this->_customdata['attemptid']);
}
}
$this->add_action_buttons(true, get_string('startattempt', 'quiz'));
$this->set_display_vertical();
$mform->setDisableShortforms();
}
public function validation($data, $files): array {
$errors = parent::validation($data, $files);
$accessmanager = $this->_customdata['quizobj']->get_access_manager(time());
return array_merge($errors, $accessmanager->validate_preflight_check(
$data, $files, $this->_customdata['attemptid']));
}
}

View File

@ -0,0 +1,356 @@
<?php
// 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/>.
namespace mod_quiz\local;
use mod_quiz\form\preflight_check_form;
use mod_quiz_mod_form;
use moodle_page;
use MoodleQuickForm;
use mod_quiz\quiz_settings;
use stdClass;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
/**
* Base class for rules that restrict the ability to attempt a quiz.
*
* Quiz access rule plugins must sublclass this one to form their main 'rule' class.
* Most of the methods are defined in a slightly unnatural way because we either
* want to say that access is allowed, or explain the reason why it is block.
* Therefore instead of is_access_allowed(...) we have prevent_access(...) that
* return false if access is permitted, or a string explanation (which is treated
* as true) if access should be blocked. Slighly unnatural, but actually the easiest
* way to implement this.
*
* @package mod_quiz
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.2
*/
abstract class access_rule_base {
/** @var stdClass the quiz settings. */
protected $quiz;
/** @var quiz_settings the quiz object. */
protected $quizobj;
/** @var int the time to use as 'now'. */
protected $timenow;
/**
* Create an instance of this rule for a particular quiz.
*
* @param quiz_settings $quizobj information about the quiz in question.
* @param int $timenow the time that should be considered as 'now'.
*/
public function __construct($quizobj, $timenow) {
$this->quizobj = $quizobj;
$this->quiz = $quizobj->get_quiz();
$this->timenow = $timenow;
}
/**
* Return an appropriately configured instance of this rule, if it is applicable
* to the given quiz, otherwise return null.
*
* @param quiz_settings $quizobj information about the quiz in question.
* @param int $timenow the time that should be considered as 'now'.
* @param bool $canignoretimelimits whether the current user is exempt from
* time limits by the mod/quiz:ignoretimelimits capability.
* @return self|null the rule, if applicable, else null.
*/
public static function make(quiz_settings $quizobj, $timenow, $canignoretimelimits) {
return null;
}
/**
* Whether a user should be allowed to start a new attempt at this quiz now.
*
* @param int $numprevattempts the number of previous attempts this user has made.
* @param object $lastattempt information about the user's last completed attempt.
* @return string false if access should be allowed, a message explaining the
* reason if access should be prevented.
*/
public function prevent_new_attempt($numprevattempts, $lastattempt) {
return false;
}
/**
* Whether the user should be blocked from starting a new attempt or continuing
* an attempt now.
* @return string false if access should be allowed, a message explaining the
* reason if access should be prevented.
*/
public function prevent_access() {
return false;
}
/**
* Does this rule require a UI check with the user before an attempt is started?
*
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
* @return bool whether a check is required before the user starts/continues
* their attempt.
*/
public function is_preflight_check_required($attemptid) {
return false;
}
/**
* Add any field you want to pre-flight check form. You should only do
* something here if {@see is_preflight_check_required()} returned true.
*
* @param preflight_check_form $quizform the form being built.
* @param MoodleQuickForm $mform The wrapped MoodleQuickForm.
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
*/
public function add_preflight_check_form_fields(preflight_check_form $quizform,
MoodleQuickForm $mform, $attemptid) {
// Do nothing by default.
}
/**
* Validate the pre-flight check form submission. You should only do
* something here if {@see is_preflight_check_required()} returned true.
*
* If the form validates, the user will be allowed to continue.
*
* @param array $data the submitted form data.
* @param array $files any files in the submission.
* @param array $errors the list of validation errors that is being built up.
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
* @return array the update $errors array;
*/
public function validate_preflight_check($data, $files, $errors, $attemptid) {
return $errors;
}
/**
* The pre-flight check has passed. This is a chance to record that fact in
* some way.
* @param int|null $attemptid the id of the current attempt, if there is one,
* otherwise null.
*/
public function notify_preflight_check_passed($attemptid) {
// Do nothing by default.
}
/**
* This is called when the current attempt at the quiz is finished. This is
* used, for example by the password rule, to clear the flag in the session.
*/
public function current_attempt_finished() {
// Do nothing by default.
}
/**
* Return a brief summary of this rule, to show to users, if required.
*
* This information is show shown, for example, on the quiz view page, to explain this
* restriction. There is no obligation to return anything. If it is not appropriate to
* tell students about this rule, then just return ''.
*
* @return string a message, or array of messages, explaining the restriction
* (may be '' if no message is appropriate).
*/
public function description() {
return '';
}
/**
* Is the current user unable to start any more attempts in future, because of this rule?
*
* If this rule can determine that this user will never be allowed another attempt at
* this quiz, for example because the last possible start time is past, or all attempts
* have been used up, then return true. This is used to know whether to display a
* final grade on the view page. This will only be called if there is not a currently
* active attempt for this user.
*
* @param int $numprevattempts the number of previous attempts this user has made.
* @param object $lastattempt information about the user's last completed attempt.
* @return bool true if this rule means that this user will never be allowed another
* attempt at this quiz.
*/
public function is_finished($numprevattempts, $lastattempt) {
return false;
}
/**
* Time by which, according to this rule, the user has to finish their attempt.
*
* @param stdClass $attempt the current attempt
* @return int|false the attempt close time, or false if there is no close time.
*/
public function end_time($attempt) {
return false;
}
/**
* If the user should be shown a different amount of time than $timenow - $this->end_time(), then
* override this method. This is useful if the time remaining is large enough to be omitted.
* @param object $attempt the current attempt
* @param int $timenow the time now. We don't use $this->timenow, so we can
* give the user a more accurate indication of how much time is left.
* @return mixed the time left in seconds (can be negative) or false if there is no limit.
*/
public function time_left_display($attempt, $timenow) {
$endtime = $this->end_time($attempt);
if ($endtime === false) {
return false;
}
return $endtime - $timenow;
}
/**
* Does this rule requires the attempt (and review) to be displayed in a pop-up window?
*
* @return bool true if it does.
*/
public function attempt_must_be_in_popup() {
return false;
}
/**
* Any options required when showing the attempt in a pop-up.
*
* @return array any options that are required for showing the attempt page
* in a popup window.
*/
public function get_popup_options() {
return [];
}
/**
* Sets up the attempt (review or summary) page with any special extra
* properties required by this rule. securewindow rule is an example of where
* this is used.
*
* @param moodle_page $page the page object to initialise.
*/
public function setup_attempt_page($page) {
// Do nothing by default.
}
/**
* It is possible for one rule to override other rules.
*
* The aim is that third-party rules should be able to replace sandard rules
* if they want. See, for example MDL-13592.
*
* @return array plugin names of other rules that this one replaces.
* For example ['ipaddress', 'password'].
*/
public function get_superceded_rules() {
return [];
}
/**
* Add any fields that this rule requires to the quiz settings form. This
* method is called from {@see mod_quiz_mod_form::definition()}, while the
* security seciton is being built.
* @param mod_quiz_mod_form $quizform the quiz settings form that is being built.
* @param MoodleQuickForm $mform the wrapped MoodleQuickForm.
*/
public static function add_settings_form_fields(
mod_quiz_mod_form $quizform, MoodleQuickForm $mform) {
// By default do nothing.
}
/**
* Validate the data from any form fields added using {@see add_settings_form_fields()}.
* @param array $errors the errors found so far.
* @param array $data the submitted form data.
* @param array $files information about any uploaded files.
* @param mod_quiz_mod_form $quizform the quiz form object.
* @return array $errors the updated $errors array.
*/
public static function validate_settings_form_fields(array $errors,
array $data, $files, mod_quiz_mod_form $quizform) {
return $errors;
}
/**
* Get any options this rule adds to the 'Browser security' quiz setting.
*
* @return array key => lang string any choices to add to the quiz Browser
* security settings menu.
*/
public static function get_browser_security_choices() {
return [];
}
/**
* Save any submitted settings when the quiz settings form is submitted. This
* is called from {@see quiz_after_add_or_update()} in lib.php.
* @param object $quiz the data from the quiz form, including $quiz->id
* which is the id of the quiz being saved.
*/
public static function save_settings($quiz) {
// By default do nothing.
}
/**
* Delete any rule-specific settings when the quiz is deleted. This is called
* from {@see quiz_delete_instance()} in lib.php.
* @param object $quiz the data from the database, including $quiz->id
* which is the id of the quiz being deleted.
* @since Moodle 2.7.1, 2.6.4, 2.5.7
*/
public static function delete_settings($quiz) {
// By default do nothing.
}
/**
* Return the bits of SQL needed to load all the settings from all the access
* plugins in one DB query. The easiest way to understand what you need to do
* here is probably to read the code of {@see access_manager::load_settings()}.
*
* If you have some settings that cannot be loaded in this way, then you can
* use the {@see get_extra_settings()} method instead, but that has
* performance implications.
*
* @param int $quizid the id of the quiz we are loading settings for. This
* can also be accessed as quiz.id in the SQL. (quiz is a table alisas for {quiz}.)
* @return array with three elements:
* 1. fields: any fields to add to the select list. These should be alised
* if neccessary so that the field name starts the name of the plugin.
* 2. joins: any joins (should probably be LEFT JOINS) with other tables that
* are needed.
* 3. params: array of placeholder values that are needed by the SQL. You must
* used named placeholders, and the placeholder names should start with the
* plugin name, to avoid collisions.
*/
public static function get_settings_sql($quizid) {
return ['', '', []];
}
/**
* You can use this method to load any extra settings your plugin has that
* cannot be loaded efficiently with get_settings_sql().
* @param int $quizid the quiz id.
* @return array setting value name => value. The value names should all
* start with the name of your plugin to avoid collisions.
*/
public static function get_extra_settings($quizid) {
return [];
}
}

View File

@ -17,8 +17,8 @@
namespace mod_quiz\local\reports;
use context_module;
use mod_quiz\quiz_attempt;
use moodle_url;
use quiz_attempt;
use stdClass;
/**

View File

@ -23,6 +23,7 @@ require_once($CFG->libdir.'/tablelib.php');
use coding_exception;
use context_module;
use html_writer;
use mod_quiz\quiz_attempt;
use moodle_url;
use popup_action;
use question_state;
@ -30,7 +31,6 @@ use qubaid_condition;
use qubaid_join;
use qubaid_list;
use question_engine_data_mapper;
use quiz_attempt;
use stdClass;
/**

View File

@ -44,14 +44,14 @@ class edit_renderer extends \plugin_renderer_base {
/**
* Render the edit page
*
* @param \quiz $quizobj object containing all the quiz settings information.
* @param \mod_quiz\quiz_settings $quizobj object containing all the quiz settings information.
* @param structure $structure object containing the structure of the quiz.
* @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
* @param \moodle_url $pageurl the canonical URL of this page.
* @param array $pagevars the variables from {@link question_edit_setup()}.
* @return string HTML to output.
*/
public function edit_page(\quiz $quizobj, structure $structure,
public function edit_page(\mod_quiz\quiz_settings $quizobj, structure $structure,
\core_question\local\bank\question_edit_contexts $contexts, \moodle_url $pageurl, array $pagevars) {
$output = '';

View File

@ -0,0 +1,38 @@
<?php
// 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/>.
namespace mod_quiz\output;
use renderable;
/**
* Represents the list of links to other attempts
*
* @package mod_quiz
* @category output
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class links_to_other_attempts implements renderable {
/**
* @var array The list of links. string attempt number => one of three things:
* - null if this is the current attempt, and so should not be linked. (Just the number is output.)
* - moodle_url if this is a different attempt. (Output as a link to the URL with the number as link text.)
* - a renderable, in which case the results of rendering the renderable is output.
* (The third option is used by {@see quiz_attempt::links_to_other_redos()}.)
*/
public $links = [];
}

View File

@ -0,0 +1,55 @@
<?php
// 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/>.
namespace mod_quiz\output;
use html_writer;
/**
* Specialisation of {@see navigation_panel_base} for the attempt quiz page.
*
* This class is not currently renderable or templatable, but it probably should be in the future,
* which is why it is already in the output namespace.
*
* @package mod_quiz
* @category output
* @copyright 2008 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class navigation_panel_attempt extends navigation_panel_base {
public function get_question_url($slot) {
if ($this->attemptobj->can_navigate_to($slot)) {
return $this->attemptobj->attempt_url($slot, -1, $this->page);
} else {
return null;
}
}
public function render_before_button_bits(renderer $output) {
return html_writer::tag('div', get_string('navnojswarning', 'quiz'),
array('id' => 'quiznojswarning'));
}
public function render_end_bits(renderer $output) {
if ($this->page == -1) {
// Don't link from the summary page to itself.
return '';
}
return html_writer::link($this->attemptobj->summary_url(),
get_string('endtest', 'quiz'), array('class' => 'endtestlink aalink')) .
$this->render_restart_preview_link($output);
}
}

View File

@ -0,0 +1,200 @@
<?php
// 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/>.
namespace mod_quiz\output;
use mod_quiz\quiz_attempt;
use moodle_url;
use question_attempt;
use question_display_options;
use question_state;
use renderable;
use user_picture;
/**
* Represents the navigation panel, and builds a {@see block_contents} to allow it to be output.
*
* This class is not currently renderable or templatable, but it probably should be in the future,
* which is why it is already in the output namespace.
*
* @package mod_quiz
* @category output
* @copyright 2008 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class navigation_panel_base {
/** @var quiz_attempt */
protected $attemptobj;
/** @var question_display_options */
protected $options;
/** @var integer */
protected $page;
/** @var boolean */
protected $showall;
/**
* Constructor.
*
* @param quiz_attempt $attemptobj construct the panel for this attempt.
* @param question_display_options $options display options in force.
* @param int $page which page of the quiz attempt is being shown, -1 if all.
* @param bool $showall whether all pages are being shown at once.
*/
public function __construct(quiz_attempt $attemptobj,
question_display_options $options, $page, $showall) {
$this->attemptobj = $attemptobj;
$this->options = $options;
$this->page = $page;
$this->showall = $showall;
}
/**
* Get the buttons and section headings to go in the quiz navigation block.
*
* @return renderable[] the buttons, possibly interleaved with section headings.
*/
public function get_question_buttons() {
$buttons = array();
foreach ($this->attemptobj->get_slots() as $slot) {
$heading = $this->attemptobj->get_heading_before_slot($slot);
if (!is_null($heading)) {
$sections = $this->attemptobj->get_quizobj()->get_sections();
if (!(empty($heading) && count($sections) == 1)) {
$buttons[] = new navigation_section_heading(format_string($heading));
}
}
$qa = $this->attemptobj->get_question_attempt($slot);
$showcorrectness = $this->options->correctness && $qa->has_marks();
$button = new navigation_question_button();
$button->id = 'quiznavbutton' . $slot;
$button->number = $this->attemptobj->get_question_number($slot);
$button->stateclass = $qa->get_state_class($showcorrectness);
$button->navmethod = $this->attemptobj->get_navigation_method();
if (!$showcorrectness && $button->stateclass === 'notanswered') {
$button->stateclass = 'complete';
}
$button->statestring = $this->get_state_string($qa, $showcorrectness);
$button->page = $this->attemptobj->get_question_page($slot);
$button->currentpage = $this->showall || $button->page == $this->page;
$button->flagged = $qa->is_flagged();
$button->url = $this->get_question_url($slot);
if ($this->attemptobj->is_blocked_by_previous_question($slot)) {
$button->url = null;
$button->stateclass = 'blocked';
$button->statestring = get_string('questiondependsonprevious', 'quiz');
}
$buttons[] = $button;
}
return $buttons;
}
/**
* Get the human-readable description of the current state of a particular question.
*
* @param question_attempt $qa the attempt at the question of interest.
* @param bool $showcorrectness whether the current use is allowed to see if they have got the question right.
* @return string Human-readable description of the state.
*/
protected function get_state_string(question_attempt $qa, $showcorrectness) {
if ($qa->get_question(false)->length > 0) {
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');
}
}
/**
* Hook for subclasses to override to do output above the question buttons.
*
* @param renderer $output the quiz renderer to use.
* @return string HTML to output.
*/
public function render_before_button_bits(renderer $output) {
return '';
}
/**
* Hook that subclasses must override to do output after the question buttons.
*
* @param renderer $output the quiz renderer to use.
* @return string HTML to output.
*/
abstract public function render_end_bits(renderer $output);
/**
* Render the restart preview button.
*
* @param renderer $output the quiz renderer to use.
* @return string HTML to output.
*/
protected function render_restart_preview_link($output) {
if (!$this->attemptobj->is_own_preview()) {
return '';
}
return $output->restart_preview_button(new moodle_url(
$this->attemptobj->start_attempt_url(), array('forcenew' => true)));
}
/**
* Get the URL to navigate to a particular question.
*
* @param int $slot slot number, to identify the question.
* @return moodle_url|null URL if the user can navigate there, or null if they cannot.
*/
abstract protected function get_question_url($slot);
/**
* Get the user picture which should be displayed, if required.
*
* @return user_picture|null
*/
public function user_picture() {
global $DB;
if ($this->attemptobj->get_quiz()->showuserpicture == QUIZ_SHOWIMAGE_NONE) {
return null;
}
$user = $DB->get_record('user', array('id' => $this->attemptobj->get_userid()));
$userpicture = new user_picture($user);
$userpicture->courseid = $this->attemptobj->get_courseid();
if ($this->attemptobj->get_quiz()->showuserpicture == QUIZ_SHOWIMAGE_LARGE) {
$userpicture->size = true;
}
return $userpicture;
}
/**
* 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';
}
}

View File

@ -0,0 +1,52 @@
<?php
// 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/>.
namespace mod_quiz\output;
use html_writer;
/**
* Specialisation of {@see navigation_panel_base} for the review quiz page.
*
* This class is not currently renderable or templatable, but it probably should be in the future,
* which is why it is already in the output namespace.
*
* @package mod_quiz
* @category output
* @copyright 2008 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class navigation_panel_review extends navigation_panel_base {
public function get_question_url($slot) {
return $this->attemptobj->review_url($slot, -1, $this->showall, $this->page);
}
public function render_end_bits(renderer $output) {
$html = '';
if ($this->attemptobj->get_num_pages() > 1) {
if ($this->showall) {
$html .= html_writer::link($this->attemptobj->review_url(null, 0, false),
get_string('showeachpage', 'quiz'));
} else {
$html .= html_writer::link($this->attemptobj->review_url(null, 0, true),
get_string('showall', 'quiz'));
}
}
$html .= $output->finish_review_link($this->attemptobj);
$html .= $this->render_restart_preview_link($output);
return $html;
}
}

View File

@ -0,0 +1,49 @@
<?php
// 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/>.
namespace mod_quiz\output;
use moodle_url;
use renderable;
/**
* Represents a single link in the navigation panel.
*
* @package mod_quiz
* @category output
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class navigation_question_button implements renderable {
/** @var string id="..." to add to the HTML for this button. */
public $id;
/** @var string number to display in this button. Either the question number of 'i'. */
public $number;
/** @var string class to add to the class="" attribute to represnt the question state. */
public $stateclass;
/** @var string Textual description of the question state, e.g. to use as a tool tip. */
public $statestring;
/** @var int the page number this question is on. */
public $page;
/** @var bool true if this question is on the current page. */
public $currentpage;
/** @var bool true if this question has been flagged. */
public $flagged;
/** @var moodle_url the link this button goes to, or null if there should not be a link. */
public $url;
/** @var int QUIZ_NAVMETHOD_FREE or QUIZ_NAVMETHOD_SEQ. */
public $navmethod;
}

View File

@ -0,0 +1,40 @@
<?php
// 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/>.
namespace mod_quiz\output;
use renderable;
/**
* Represents a heading in the navigation panel.
*
* @package mod_quiz
* @category output
* @copyright 2015 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class navigation_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;
}
}

View File

@ -23,7 +23,6 @@
*/
namespace mod_quiz\output;
defined('MOODLE_INTERNAL') || die();
/**
* The question_chooser renderable class.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,98 @@
<?php
// 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/>.
namespace mod_quiz\output;
use mod_quiz\access_manager;
use mod_quiz\form\preflight_check_form;
use mod_quiz\quiz_attempt;
use moodle_url;
/**
* This class captures all the various information to render the front page of the quiz activity.
*
* This class is not currently renderable or templatable, but it very nearly could be,
* which is why it is in the output namespace. It is used to send data to the renderer.
*
* @package mod_quiz
* @category output
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class view_page {
/** @var array $infomessages of messages with information to display about the quiz. */
public $infomessages;
/** @var array $attempts contains all the user's attempts at this quiz. */
public $attempts;
/** @var quiz_attempt[] $attemptobjs objects corresponding to $attempts. */
public $attemptobjs;
/** @var access_manager $accessmanager contains various access rules. */
public $accessmanager;
/** @var bool $canreviewmine whether the current user has the capability to
* review their own attempts. */
public $canreviewmine;
/** @var bool $canedit whether the current user has the capability to edit the quiz. */
public $canedit;
/** @var moodle_url $editurl the URL for editing this quiz. */
public $editurl;
/** @var int $attemptcolumn contains the number of attempts done. */
public $attemptcolumn;
/** @var int $gradecolumn contains the grades of any attempts. */
public $gradecolumn;
/** @var int $markcolumn contains the marks of any attempt. */
public $markcolumn;
/** @var int $overallstats contains all marks for any attempt. */
public $overallstats;
/** @var string $feedbackcolumn contains any feedback for and attempt. */
public $feedbackcolumn;
/** @var string $timenow contains a timestamp in string format. */
public $timenow;
/** @var int $numattempts contains the total number of attempts. */
public $numattempts;
/** @var float $mygrade contains the user's final grade for a quiz. */
public $mygrade;
/** @var bool $moreattempts whether this user is allowed more attempts. */
public $moreattempts;
/** @var int $mygradeoverridden contains an overriden grade. */
public $mygradeoverridden;
/** @var string $gradebookfeedback contains any feedback for a gradebook. */
public $gradebookfeedback;
/** @var bool $unfinished contains 1 if an attempt is unfinished. */
public $unfinished;
/** @var object $lastfinishedattempt the last attempt from the attempts array. */
public $lastfinishedattempt;
/** @var array $preventmessages of messages telling the user why they can't
* attempt the quiz now. */
public $preventmessages;
/** @var string $buttontext caption for the start attempt button. If this is null, show no
* button, or if it is '' show a back to the course button. */
public $buttontext;
/** @var moodle_url $startattempturl URL to start an attempt. */
public $startattempturl;
/** @var preflight_check_form|null $preflightcheckform confirmation form that must be
* submitted before an attempt is started, if required. */
public $preflightcheckform;
/** @var moodle_url $startattempturl URL for any Back to the course button. */
public $backtocourseurl;
/** @var bool $showbacktocourse should we show a back to the course button? */
public $showbacktocourse;
/** @var bool whether the attempt must take place in a popup window. */
public $popuprequired;
/** @var array options to use for the popup window, if required. */
public $popupoptions;
/** @var bool $quizhasquestions whether the quiz has any questions. */
public $quizhasquestions;
}

View File

@ -39,30 +39,30 @@ trait legacy_quizaccess_polyfill {
/**
* Export all user data for the specified user, for the specified quiz.
*
* @param \quiz $quiz The quiz being exported
* @param \mod_quiz\quiz_settings $quiz The quiz being exported
* @param \stdClass $user The user to export data for
* @return \stdClass The data to be exported for this access rule.
*/
public static function export_quizaccess_user_data(\quiz $quiz, \stdClass $user) : \stdClass {
public static function export_quizaccess_user_data(\mod_quiz\quiz_settings $quiz, \stdClass $user) : \stdClass {
return static::_export_quizaccess_user_data($quiz, $user);
}
/**
* Delete all data for all users in the specified quiz.
*
* @param \quiz $quiz The quiz being deleted
* @param \mod_quiz\quiz_settings $quiz The quiz being deleted
*/
public static function delete_quizaccess_data_for_all_users_in_context(\quiz $quiz) {
public static function delete_quizaccess_data_for_all_users_in_context(\mod_quiz\quiz_settings $quiz) {
static::_delete_quizaccess_data_for_all_users_in_context($quiz);
}
/**
* Delete all user data for the specified user, in the specified quiz.
*
* @param \quiz $quiz The quiz being deleted
* @param \mod_quiz\quiz_settings $quiz The quiz being deleted
* @param \stdClass $user The user to export data for
*/
public static function delete_quizaccess_data_for_user(\quiz $quiz, \stdClass $user) {
public static function delete_quizaccess_data_for_user(\mod_quiz\quiz_settings $quiz, \stdClass $user) {
static::_delete_quizaccess_data_for_user($quiz, $user);
}

View File

@ -34,6 +34,7 @@ use core_privacy\local\metadata\collection;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
use core_privacy\manager;
use mod_quiz\quiz_attempt;
defined('MOODLE_INTERNAL') || die();
@ -276,7 +277,7 @@ class provider implements
$quizzes = $DB->get_recordset_sql($sql, $params);
foreach ($quizzes as $quiz) {
list($course, $cm) = get_course_and_cm_from_cmid($quiz->cmid, 'quiz');
$quizobj = new \quiz($quiz, $cm, $course);
$quizobj = new \mod_quiz\quiz_settings($quiz, $cm, $course);
$context = $quizobj->get_context();
$quizdata = \core_privacy\local\request\helper::get_context_data($context, $contextlist->get_user());
@ -353,7 +354,7 @@ class provider implements
return;
}
$quizobj = \quiz::create($cm->instance);
$quizobj = \mod_quiz\quiz_settings::create($cm->instance);
$quiz = $quizobj->get_quiz();
// Handle the 'quizaccess' subplugin.
@ -392,7 +393,7 @@ class provider implements
}
// Fetch the details of the data to be removed.
$quizobj = \quiz::create($cm->instance);
$quizobj = \mod_quiz\quiz_settings::create($cm->instance);
$quiz = $quizobj->get_quiz();
$user = $contextlist->get_user();
@ -440,7 +441,7 @@ class provider implements
return;
}
$quizobj = \quiz::create($cm->instance);
$quizobj = \mod_quiz\quiz_settings::create($cm->instance);
$quiz = $quizobj->get_quiz();
$userids = $userlist->get_userids();
@ -526,7 +527,7 @@ class provider implements
// Store the quiz attempt data.
$data = (object) [
'state' => \quiz_attempt::state_name($attempt->state),
'state' => quiz_attempt::state_name($attempt->state),
];
if (!empty($attempt->timestart)) {

View File

@ -40,24 +40,24 @@ interface quizaccess_provider extends \core_privacy\local\request\plugin\subplug
/**
* Export all user data for the specified user, for the specified quiz.
*
* @param \quiz $quiz The quiz being exported
* @param \mod_quiz\quiz_settings $quiz The quiz being exported
* @param \stdClass $user The user to export data for
* @return \stdClass The data to be exported for this access rule.
*/
public static function export_quizaccess_user_data(\quiz $quiz, \stdClass $user) : \stdClass;
public static function export_quizaccess_user_data(\mod_quiz\quiz_settings $quiz, \stdClass $user) : \stdClass;
/**
* Delete all data for all users in the specified quiz.
*
* @param \quiz $quiz The quiz being deleted
* @param \mod_quiz\quiz_settings $quiz The quiz being deleted
*/
public static function delete_quizaccess_data_for_all_users_in_context(\quiz $quiz);
public static function delete_quizaccess_data_for_all_users_in_context(\mod_quiz\quiz_settings $quiz);
/**
* Delete all user data for the specified user, in the specified quiz.
*
* @param \quiz $quiz The quiz being deleted
* @param \mod_quiz\quiz_settings $quiz The quiz being deleted
* @param \stdClass $user The user to export data for
*/
public static function delete_quizaccess_data_for_user(\quiz $quiz, \stdClass $user);
public static function delete_quizaccess_data_for_user(\mod_quiz\quiz_settings $quiz, \stdClass $user);
}

View File

@ -23,8 +23,6 @@ use qubaid_condition;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/engine/bank.php');
require_once($CFG->dirroot . '/mod/quiz/accessmanager.php');
require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
/**
* Helper class for question bank and its associated data.

View File

@ -16,6 +16,8 @@
namespace mod_quiz\question;
use mod_quiz\quiz_attempt;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/engine/datalib.php');
@ -47,7 +49,7 @@ class qubaids_for_quiz extends \qubaid_join {
if ($onlyfinished) {
$where .= ' AND state = :statefinished';
$params['statefinished'] = \quiz_attempt::FINISHED;
$params['statefinished'] = quiz_attempt::FINISHED;
}
parent::__construct('{quiz_attempts} quiza', 'quiza.uniqueid', $where, $params);

View File

@ -16,10 +16,11 @@
namespace mod_quiz\question;
use mod_quiz\quiz_attempt;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/engine/datalib.php');
require_once($CFG->dirroot.'/mod/quiz/attemptlib.php');
/**
* A {@see qubaid_condition} representing all the attempts by one user at a given quiz.
@ -54,14 +55,14 @@ class qubaids_for_users_attempts extends \qubaid_join {
case 'finished':
$where .= ' AND state IN (:state1, :state2)';
$params['state1'] = \quiz_attempt::FINISHED;
$params['state2'] = \quiz_attempt::ABANDONED;
$params['state1'] = quiz_attempt::FINISHED;
$params['state2'] = quiz_attempt::ABANDONED;
break;
case 'unfinished':
$where .= ' AND state IN (:state1, :state2)';
$params['state1'] = \quiz_attempt::IN_PROGRESS;
$params['state2'] = \quiz_attempt::OVERDUE;
$params['state1'] = quiz_attempt::IN_PROGRESS;
$params['state2'] = quiz_attempt::OVERDUE;
break;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,562 @@
<?php
// 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/>.
namespace mod_quiz;
use coding_exception;
use context;
use context_module;
use mod_quiz\question\bank\qbank_helper;
use mod_quiz\question\display_options;
use moodle_exception;
use moodle_url;
use question_bank;
use stdClass;
/**
* A class encapsulating the settings for a quiz.
*
* When this class is initialised, it may have the settings adjusted to account
* for the overrides for a particular user. See the create methods.
*
* Initially, it only loads a minimal amount of information about each question - loading
* extra information only when necessary or when asked. The class tracks which questions
* are loaded.
*
* @package mod_quiz
* @copyright 2008 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_settings {
/** @var stdClass the course settings from the database. */
protected $course;
/** @var stdClass the course_module settings from the database. */
protected $cm;
/** @var stdClass the quiz settings from the database. */
protected $quiz;
/** @var context the quiz context. */
protected $context;
/**
* @var stdClass[] of questions augmented with slot information. For non-random
* questions, the array key is question id. For random quesions it is 's' . $slotid.
* probalby best to use ->questionid field of the object instead.
*/
protected $questions = null;
/** @var stdClass[] of quiz_section rows. */
protected $sections = null;
/** @var access_manager the access manager for this quiz. */
protected $accessmanager = null;
/** @var bool whether the current user has capability mod/quiz:preview. */
protected $ispreviewuser = null;
// Constructor =============================================================.
/**
* 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.
* @param bool $getcontext intended for testing - stops the constructor getting the context.
*/
public function __construct($quiz, $cm, $course, $getcontext = true) {
$this->quiz = $quiz;
$this->cm = $cm;
$this->quiz->cmid = $this->cm->id;
$this->course = $course;
if ($getcontext && !empty($cm->id)) {
$this->context = context_module::instance($cm->id);
}
}
/**
* Static function to create a new quiz object for a specific user.
*
* @param int $quizid the the quiz id.
* @param int|null $userid the the userid (optional). If passed, relevant overrides are applied.
* @return quiz_settings the new quiz object.
*/
public static function create($quizid, $userid = null) {
global $DB;
$quiz = access_manager::load_quiz_and_settings($quizid);
$course = $DB->get_record('course', ['id' => $quiz->course], '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id, false, MUST_EXIST);
// Update quiz with override information.
if ($userid) {
$quiz = quiz_update_effective_access($quiz, $userid);
}
return new quiz_settings($quiz, $cm, $course);
}
/**
* Create a {@see 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);
}
// Functions for loading more data =========================================.
/**
* Load just basic information about all the questions in this quiz.
*/
public function preload_questions() {
$slots = qbank_helper::get_question_structure($this->quiz->id, $this->context);
$this->questions = [];
foreach ($slots as $slot) {
$this->questions[$slot->questionid] = $slot;
}
}
/**
* Fully load some or all of the questions for this quiz. You must call
* {@see preload_questions()} first.
*
* @param array|null $deprecated no longer supported (it was not used).
*/
public function load_questions($deprecated = null) {
if ($deprecated !== null) {
debugging('The argument to quiz::load_questions is no longer supported. ' .
'All questions are always loaded.', DEBUG_DEVELOPER);
}
if ($this->questions === null) {
throw new coding_exception('You must call preload_questions before calling load_questions.');
}
$questionstoprocess = [];
foreach ($this->questions as $question) {
if (is_number($question->questionid)) {
$question->id = $question->questionid;
$questionstoprocess[$question->questionid] = $question;
}
}
get_question_options($questionstoprocess);
}
/**
* Get an instance of the {@see \mod_quiz\structure} class for this quiz.
*
* @return structure describes the questions in the quiz.
*/
public function get_structure() {
return structure::create_for_quiz($this);
}
// Simple getters ==========================================================.
/**
* Get the id of the course this quiz belongs to.
*
* @return int the course id.
*/
public function get_courseid() {
return $this->course->id;
}
/**
* Get the course settings object that this quiz belongs to.
*
* @return object the row of the course table.
*/
public function get_course() {
return $this->course;
}
/**
* Get this quiz's id (in the quiz table).
*
* @return int the quiz id.
*/
public function get_quizid() {
return $this->quiz->id;
}
/**
* Get the quiz settings object.
*
* @return stdClass the row of the quiz table.
*/
public function get_quiz() {
return $this->quiz;
}
/**
* Get the quiz name.
*
* @return string the name of this quiz.
*/
public function get_quiz_name() {
return $this->quiz->name;
}
/**
* Get the navigation method in use.
*
* @return int QUIZ_NAVMETHOD_FREE or QUIZ_NAVMETHOD_SEQ.
*/
public function get_navigation_method() {
return $this->quiz->navmethod;
}
/**
* How many attepts is the user allowed at this quiz?
*
* @return int the number of attempts allowed at this quiz (0 = infinite).
*/
public function get_num_attempts_allowed() {
return $this->quiz->attempts;
}
/**
* Get the course-module id for this quiz.
*
* @return int the course_module id.
*/
public function get_cmid() {
return $this->cm->id;
}
/**
* Get the course-module object for this quiz.
*
* @return object the course_module object.
*/
public function get_cm() {
return $this->cm;
}
/**
* Get the quiz context.
*
* @return context_module the module context for this quiz.
*/
public function get_context() {
return $this->context;
}
/**
* Is the current user is someone who previews the quiz, rather than attempting it?
*
* @return bool true user is a preview user. False, if they can do real attempts.
*/
public function is_preview_user() {
if (is_null($this->ispreviewuser)) {
$this->ispreviewuser = has_capability('mod/quiz:preview', $this->context);
}
return $this->ispreviewuser;
}
/**
* Checks user enrollment in the current course.
*
* @param int $userid the id of the user to check.
* @return bool whether the user is enrolled.
*/
public function is_participant($userid) {
return is_enrolled($this->get_context(), $userid, 'mod/quiz:attempt', $this->show_only_active_users());
}
/**
* Check is only active users in course should be shown.
*
* @return bool true if only active users should be shown.
*/
public function show_only_active_users() {
return !has_capability('moodle/course:viewsuspendedusers', $this->get_context());
}
/**
* Have any questions been added to this quiz yet?
*
* @return bool whether any questions have been added to this quiz.
*/
public function has_questions() {
if ($this->questions === null) {
$this->preload_questions();
}
return !empty($this->questions);
}
/**
* Get a particular question in this quiz, by its id.
*
* @param int $id the question id.
* @return stdClass the question object with that id.
*/
public function get_question($id) {
return $this->questions[$id];
}
/**
* Get some of the question in this quiz.
*
* @param array|null $questionids question ids of the questions to load. null for all.
* @return stdClass[] the question data objects.
*/
public function get_questions($questionids = null) {
if (is_null($questionids)) {
$questionids = array_keys($this->questions);
}
$questions = [];
foreach ($questionids as $id) {
if (!array_key_exists($id, $this->questions)) {
throw new moodle_exception('cannotstartmissingquestion', 'quiz', $this->view_url());
}
$questions[$id] = $this->questions[$id];
$this->ensure_question_loaded($id);
}
return $questions;
}
/**
* 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',
['quizid' => $this->get_quizid()], 'firstslot'));
}
return $this->sections;
}
/**
* Return access_manager and instance of the access_manager class
* for this quiz at this time.
*
* @param int $timenow the current time as a unix timestamp.
* @return access_manager and instance of the access_manager class
* for this quiz at this time.
*/
public function get_access_manager($timenow) {
if (is_null($this->accessmanager)) {
$this->accessmanager = new access_manager($this, $timenow,
has_capability('mod/quiz:ignoretimelimits', $this->context, null, false));
}
return $this->accessmanager;
}
/**
* Wrapper round the has_capability funciton that automatically passes in the quiz context.
*
* @param string $capability the name of the capability to check. For example mod/quiz:view.
* @param int|null $userid A user id. By default (null) checks the permissions of the current user.
* @param bool $doanything If false, ignore effect of admin role assignment.
* @return boolean true if the user has this capability. Otherwise false.
*/
public function has_capability($capability, $userid = null, $doanything = true) {
return has_capability($capability, $this->context, $userid, $doanything);
}
/**
* Wrapper round the require_capability function that automatically passes in the quiz context.
*
* @param string $capability the name of the capability to check. For example mod/quiz:view.
* @param int|null $userid A user id. By default (null) checks the permissions of the current user.
* @param bool $doanything If false, ignore effect of admin role assignment.
*/
public function require_capability($capability, $userid = null, $doanything = true) {
require_capability($capability, $this->context, $userid, $doanything);
}
// URLs related to this attempt ============================================.
/**
* Get the URL of this quiz's view.php page.
*
* @return moodle_url the URL of this quiz's view page.
*/
public function view_url() {
return new moodle_url('/mod/quiz/view.php', ['id' => $this->cm->id]);
}
/**
* Get the URL of this quiz's edit questions page.
*
* @return moodle_url the URL of this quiz's edit page.
*/
public function edit_url() {
return new moodle_url('/mod/quiz/edit.php', ['cmid' => $this->cm->id]);
}
/**
* Get the URL of a particular page within an attempt.
*
* @param int $attemptid the id of an attempt.
* @param int $page optional page number to go to in the attempt.
* @return moodle_url the URL of that attempt.
*/
public function attempt_url($attemptid, $page = 0) {
$params = ['attempt' => $attemptid, 'cmid' => $this->get_cmid()];
if ($page) {
$params['page'] = $page;
}
return new moodle_url('/mod/quiz/attempt.php', $params);
}
/**
* Get the URL to start/continue an attempt.
*
* @param int $page page in the attempt to start on (optional).
* @return moodle_url the URL of this quiz's edit page. Needs to be POSTed to with a cmid parameter.
*/
public function start_attempt_url($page = 0) {
$params = ['cmid' => $this->cm->id, 'sesskey' => sesskey()];
if ($page) {
$params['page'] = $page;
}
return new moodle_url('/mod/quiz/startattempt.php', $params);
}
/**
* Get the URL to review a particular quiz attempt.
*
* @param int $attemptid the id of an attempt.
* @return string the URL of the review of that attempt.
*/
public function review_url($attemptid) {
return new moodle_url('/mod/quiz/review.php', ['attempt' => $attemptid, 'cmid' => $this->get_cmid()]);
}
/**
* Get the URL for the summary page for a particular attempt.
*
* @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', ['attempt' => $attemptid, 'cmid' => $this->get_cmid()]);
}
// Bits of content =========================================================.
/**
* 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 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 == display_options::DURING ||
$when == display_options::IMMEDIATELY_AFTER) {
return '';
} else {
if ($when == display_options::LATER_WHILE_OPEN && $this->quiz->timeclose &&
$this->quiz->reviewattempt & display_options::AFTER_CLOSE) {
return get_string('noreviewuntil' . $langstrsuffix, 'quiz',
userdate($this->quiz->timeclose, $dateformat));
} else {
return get_string('noreview' . $langstrsuffix, 'quiz');
}
}
}
/**
* Probably not used any more, but left for backwards compatibility.
*
* @param string $title the name of this particular quiz page.
* @return string always returns ''.
*/
public function navigation($title) {
global $PAGE;
$PAGE->navbar->add($title);
return '';
}
// Private methods =========================================================.
/**
* Check that the definition of a particular question is loaded, and if not throw an exception.
*
* @param int $id a question id.
*/
protected function ensure_question_loaded($id) {
if (isset($this->questions[$id]->_partiallyloaded)) {
throw new moodle_exception('questionnotloaded', 'quiz', $this->view_url(), $id);
}
}
/**
* Return all the question types used in this quiz.
*
* @param boolean $includepotential if the quiz include random questions,
* setting this flag to true will make the function to return all the
* possible question types in the random questions category.
* @return array a sorted array including the different question types.
* @since Moodle 3.1
*/
public function get_all_question_types_used($includepotential = false) {
$questiontypes = [];
// To control if we need to look in categories for questions.
$qcategories = [];
foreach ($this->get_questions() as $questiondata) {
if ($questiondata->qtype === 'random' && $includepotential) {
if (!isset($qcategories[$questiondata->category])) {
$qcategories[$questiondata->category] = false;
}
if (!empty($questiondata->filtercondition)) {
$filtercondition = json_decode($questiondata->filtercondition);
$qcategories[$questiondata->category] = !empty($filtercondition->includingsubcategories);
}
} else {
if (!in_array($questiondata->qtype, $questiontypes)) {
$questiontypes[] = $questiondata->qtype;
}
}
}
if (!empty($qcategories)) {
// We have to look for all the question types in these categories.
$categoriestolook = [];
foreach ($qcategories as $cat => $includesubcats) {
if ($includesubcats) {
$categoriestolook = array_merge($categoriestolook, question_categorylist($cat));
} else {
$categoriestolook[] = $cat;
}
}
$questiontypesincategories = question_bank::get_all_question_types_in_categories($categoriestolook);
$questiontypes = array_merge($questiontypes, $questiontypesincategories);
}
$questiontypes = array_unique($questiontypes);
sort($questiontypes);
return $questiontypes;
}
}

View File

@ -39,7 +39,7 @@ use mod_quiz\question\qubaids_for_quiz;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class structure {
/** @var \quiz the quiz this is the structure of. */
/** @var \mod_quiz\quiz_settings the quiz this is the structure of. */
protected $quizobj = null;
/**
@ -83,7 +83,7 @@ class structure {
/**
* Create an instance of this class representing the structure of a given quiz.
*
* @param \quiz $quizobj the quiz.
* @param \mod_quiz\quiz_settings $quizobj the quiz.
* @return structure
*/
public static function create_for_quiz($quizobj) {

View File

@ -20,10 +20,10 @@ defined('MOODLE_INTERNAL') || die();
use context_course;
use core_user;
use mod_quiz\quiz_attempt;
use moodle_recordset;
use question_display_options;
use mod_quiz\question\display_options;
use quiz_attempt;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');

View File

@ -24,8 +24,13 @@
*/
namespace mod_quiz\task;
use mod_quiz\quiz_attempt;
use moodle_exception;
use moodle_recordset;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
/**
@ -39,27 +44,112 @@ require_once($CFG->dirroot . '/mod/quiz/locallib.php');
*/
class update_overdue_attempts extends \core\task\scheduled_task {
public function get_name() {
public function get_name(): string {
return get_string('updateoverdueattemptstask', 'mod_quiz');
}
/**
*
* Close off any overdue attempts.
*/
public function execute() {
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/cronlib.php');
$timenow = time();
$overduehander = new \mod_quiz_overdue_attempt_updater();
$processto = $timenow - get_config('quiz', 'graceperiodmin');
mtrace(' Looking for quiz overdue quiz attempts...');
list($count, $quizcount) = $overduehander->update_overdue_attempts($timenow, $processto);
list($count, $quizcount) = $this->update_all_overdue_attempts($timenow, $processto);
mtrace(' Considered ' . $count . ' attempts in ' . $quizcount . ' quizzes.');
}
/**
* Do the processing required.
*
* @param int $timenow the time to consider as 'now' during the processing.
* @param int $processto only process attempt with timecheckstate longer ago than this.
* @return array with two elements, the number of attempt considered, and how many different quizzes that was.
*/
public function update_all_overdue_attempts(int $timenow, int $processto): array {
global $DB;
$attemptstoprocess = $this->get_list_of_overdue_attempts($processto);
$course = null;
$quiz = null;
$cm = null;
$count = 0;
$quizcount = 0;
foreach ($attemptstoprocess as $attempt) {
try {
// If we have moved on to a different quiz, fetch the new data.
if (!$quiz || $attempt->quiz != $quiz->id) {
$quiz = $DB->get_record('quiz', array('id' => $attempt->quiz), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('quiz', $attempt->quiz);
$quizcount += 1;
}
// If we have moved on to a different course, fetch the new data.
if (!$course || $course->id != $quiz->course) {
$course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST);
}
// Make a specialised version of the quiz settings, with the relevant overrides.
$quizforuser = clone($quiz);
$quizforuser->timeclose = $attempt->usertimeclose;
$quizforuser->timelimit = $attempt->usertimelimit;
// Trigger any transitions that are required.
$attemptobj = new quiz_attempt($attempt, $quizforuser, $cm, $course);
$attemptobj->handle_if_time_expired($timenow, false);
$count += 1;
} catch (moodle_exception $e) {
// If an error occurs while processing one attempt, don't let that kill cron.
mtrace("Error while processing attempt $attempt->id at $attempt->quiz quiz:");
mtrace($e->getMessage());
mtrace($e->getTraceAsString());
// Close down any currently open transactions, otherwise one error
// will stop following DB changes from being committed.
$DB->force_transaction_rollback();
}
}
$attemptstoprocess->close();
return array($count, $quizcount);
}
/**
* Get a recordset of all the attempts that need to be processed now.
*
* (Only public to allow unit testing. Do not use!)
*
* @param int $processto timestamp to process up to.
* @return moodle_recordset of quiz_attempts that need to be processed because time has
* passed, sorted by courseid then quizid.
*/
public function get_list_of_overdue_attempts(int $processto): moodle_recordset {
global $DB;
// SQL to compute timeclose and timelimit for each attempt.
$quizausersql = quiz_get_attempt_usertime_sql(
"iquiza.state IN ('inprogress', 'overdue') AND iquiza.timecheckstate <= :iprocessto");
// This query should have all the quiz_attempts columns.
return $DB->get_recordset_sql("
SELECT quiza.*,
quizauser.usertimeclose,
quizauser.usertimelimit
FROM {quiz_attempts} quiza
JOIN {quiz} quiz ON quiz.id = quiza.quiz
JOIN ( $quizausersql ) quizauser ON quizauser.id = quiza.id
WHERE quiza.state IN ('inprogress', 'overdue')
AND quiza.timecheckstate <= :processto
ORDER BY quiz.course, quiza.quiz",
array('processto' => $processto, 'iprocessto' => $processto));
}
}

View File

@ -20,106 +20,13 @@
* @package mod_quiz
* @copyright 2012 the Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-76612 delete this file as part of Moodle 4.6 development.
* @deprecated This file is no longer required in Moodle 4.2+.
*/
defined('MOODLE_INTERNAL') || die();
debugging('This file is no longer required in Moodle 4.2+. Please do not include/require it.', DEBUG_DEVELOPER);
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
/**
* This class holds all the code for automatically updating all attempts that have
* gone over their time limit.
*
* @copyright 2012 the Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_overdue_attempt_updater {
/**
* Do the processing required.
* @param int $timenow the time to consider as 'now' during the processing.
* @param int $processto only process attempt with timecheckstate longer ago than this.
* @return array with two elements, the number of attempt considered, and how many different quizzes that was.
*/
public function update_overdue_attempts($timenow, $processto) {
global $DB;
$attemptstoprocess = $this->get_list_of_overdue_attempts($processto);
$course = null;
$quiz = null;
$cm = null;
$count = 0;
$quizcount = 0;
foreach ($attemptstoprocess as $attempt) {
try {
// If we have moved on to a different quiz, fetch the new data.
if (!$quiz || $attempt->quiz != $quiz->id) {
$quiz = $DB->get_record('quiz', array('id' => $attempt->quiz), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('quiz', $attempt->quiz);
$quizcount += 1;
}
// If we have moved on to a different course, fetch the new data.
if (!$course || $course->id != $quiz->course) {
$course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST);
}
// Make a specialised version of the quiz settings, with the relevant overrides.
$quizforuser = clone($quiz);
$quizforuser->timeclose = $attempt->usertimeclose;
$quizforuser->timelimit = $attempt->usertimelimit;
// Trigger any transitions that are required.
$attemptobj = new quiz_attempt($attempt, $quizforuser, $cm, $course);
$attemptobj->handle_if_time_expired($timenow, false);
$count += 1;
} catch (moodle_exception $e) {
// If an error occurs while processing one attempt, don't let that kill cron.
mtrace("Error while processing attempt {$attempt->id} at {$attempt->quiz} quiz:");
mtrace($e->getMessage());
mtrace($e->getTraceAsString());
// Close down any currently open transactions, otherwise one error
// will stop following DB changes from being committed.
$DB->force_transaction_rollback();
}
}
$attemptstoprocess->close();
return array($count, $quizcount);
}
/**
* @return moodle_recordset of quiz_attempts that need to be processed because time has
* passed. The array is sorted by courseid then quizid.
*/
public function get_list_of_overdue_attempts($processto) {
global $DB;
// SQL to compute timeclose and timelimit for each attempt:
$quizausersql = quiz_get_attempt_usertime_sql(
"iquiza.state IN ('inprogress', 'overdue') AND iquiza.timecheckstate <= :iprocessto");
// This query should have all the quiz_attempts columns.
return $DB->get_recordset_sql("
SELECT quiza.*,
quizauser.usertimeclose,
quizauser.usertimelimit
FROM {quiz_attempts} quiza
JOIN {quiz} quiz ON quiz.id = quiza.quiz
JOIN ( $quizausersql ) quizauser ON quizauser.id = quiza.id
WHERE quiza.state IN ('inprogress', 'overdue')
AND quiza.timecheckstate <= :processto
ORDER BY quiz.course, quiza.quiz",
array('processto' => $processto, 'iprocessto' => $processto));
}
}
require_once($CFG->dirroot . '/mod/quiz/deprecatedlib.php');

View File

@ -51,4 +51,19 @@ $renamedclasses = [
'mod_quiz_attempts_report_form' => 'mod_quiz\local\reports\attempts_report_options_form',
'mod_quiz_attempts_report_options' => 'mod_quiz\local\reports\attempts_report_options',
'quiz_attempts_report_table' => 'mod_quiz\local\reports\attempts_report_table',
'quiz_access_manager' => 'mod_quiz\access_manager',
'mod_quiz_preflight_check_form' => 'mod_quiz\form\preflight_check_form',
'quiz_override_form' => 'mod_quiz\form\edit_override_form',
'quiz_access_rule_base' => 'mod_quiz\local\access_rule_base',
'quiz_add_random_form' => 'mod_quiz\form\add_random_form',
'mod_quiz_links_to_other_attempts' => 'mod_quiz\output\links_to_other_attempts',
'mod_quiz_view_object' => 'mod_quiz\output\view_page',
'mod_quiz_renderer' => 'mod_quiz\output\renderer',
'quiz_nav_question_button' => 'mod_quiz\output\navigation_question_button',
'quiz_nav_section_heading' => 'mod_quiz\output\navigation_section_heading',
'quiz_nav_panel_base' => 'mod_quiz\output\navigation_panel_base',
'quiz_attempt_nav_panel' => 'mod_quiz\output\navigation_panel_attempt',
'quiz_review_nav_panel' => 'mod_quiz\output\navigation_panel_review',
'quiz_attempt' => 'mod_quiz\quiz_attempt',
'quiz' => 'mod_quiz\quiz_settings',
];

View File

@ -22,6 +22,10 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_quiz\access_manager;
use mod_quiz\quiz_settings;
use mod_quiz\task\update_overdue_attempts;
/**
* Internal function used in quiz_get_completion_state. Check passing grade (or no attempts left) requirement for completion.
*
@ -68,8 +72,8 @@ function quiz_completion_check_passing_grade_or_all_attempts($course, $cm, $user
}
$lastfinishedattempt = end($attempts);
$context = context_module::instance($cm->id);
$quizobj = quiz::create($quiz->id, $userid);
$accessmanager = new quiz_access_manager($quizobj, time(),
$quizobj = quiz_settings::create($quiz->id, $userid);
$accessmanager = new access_manager($quizobj, time(),
has_capability('mod/quiz:ignoretimelimits', $context, $userid, false));
return $accessmanager->is_finished(count($attempts), $lastfinishedattempt);
@ -132,3 +136,62 @@ function quiz_get_completion_state($course, $cm, $userid, $type) {
return true;
}
/**
* @copyright 2012 the Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 4.2. Code moved to mod_quiz\task\update_overdue_attempts.
* @todo MDL-76612 Final deprecation in Moodle 4.6
*/
class mod_quiz_overdue_attempt_updater {
/**
* @deprecated since Moodle 4.2. Code moved to mod_quiz\task\update_overdue_attempts. that was.
*/
public function update_overdue_attempts($timenow, $processto) {
debugging('mod_quiz_overdue_attempt_updater has been deprecated. The code wsa moved to ' .
'mod_quiz\task\update_overdue_attempts.');
return (new update_overdue_attempts())->update_all_overdue_attempts((int) $timenow, (int) $processto);
}
/**
* @deprecated since Moodle 4.2. Code moved to mod_quiz\task\update_overdue_attempts.
*/
public function get_list_of_overdue_attempts($processto) {
debugging('mod_quiz_overdue_attempt_updater has been deprecated. The code wsa moved to ' .
'mod_quiz\task\update_overdue_attempts.');
return (new update_overdue_attempts())->get_list_of_overdue_attempts((int) $processto);
}
}
/**
* Class for quiz exceptions. Just saves a couple of arguments on the
* constructor for a moodle_exception.
*
* @copyright 2008 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.0
* @deprecated since Moodle 4.2. Please just use moodle_exception.
* @todo MDL-76612 Final deprecation in Moodle 4.6
*/
class moodle_quiz_exception extends moodle_exception {
/**
* Constructor.
*
* @param quiz_settings $quizobj the quiz the error relates to.
* @param string $errorcode The name of the string from error.php to print.
* @param mixed $a Extra words and phrases that might be required in the error string.
* @param string $link The url where the user will be prompted to continue.
* If no url is provided the user will be directed to the site index page.
* @param string|null $debuginfo optional debugging information.
* @deprecated since Moodle 4.2. Please just use moodle_exception.
*/
public function __construct($quizobj, $errorcode, $a = null, $link = '', $debuginfo = null) {
debugging('Class moodle_quiz_exception is deprecated. ' .
'Please use a standard moodle_exception instead.', DEBUG_DEVELOPER);
if (!$link) {
$link = $quizobj->view_url();
}
parent::__construct($errorcode, 'quiz', $link, $a, $debuginfo);
}
}

View File

@ -40,10 +40,10 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_quiz\quiz_settings;
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
require_once($CFG->dirroot . '/mod/quiz/addrandomform.php');
require_once($CFG->dirroot . '/question/editlib.php');
// These params are only passed from page request to request while we stay on
@ -63,7 +63,7 @@ $PAGE->set_secondary_active_tab("mod_quiz_edit");
// Get the course object and related bits.
$course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST);
$quizobj = new quiz($quiz, $cm, $course);
$quizobj = new quiz_settings($quiz, $cm, $course);
$structure = $quizobj->get_structure();
// You need mod/quiz:manage in addition to question capabilities to access this page.

View File

@ -22,6 +22,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_quiz\quiz_settings;
if (!defined('AJAX_SCRIPT')) {
define('AJAX_SCRIPT', true);
}
@ -57,7 +59,7 @@ $cm = get_coursemodule_from_instance('quiz', $quiz->id, $quiz->course);
$course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST);
require_login($course, false, $cm);
$quizobj = new quiz($quiz, $cm, $course);
$quizobj = new quiz_settings($quiz, $cm, $course);
$structure = $quizobj->get_structure();
$modcontext = context_module::instance($cm->id);

View File

@ -24,6 +24,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_quiz\quiz_attempt;
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');

View File

@ -28,14 +28,17 @@
defined('MOODLE_INTERNAL') || die();
use mod_quiz\access_manager;
use mod_quiz\form\add_random_form;
use mod_quiz\question\bank\custom_view;
use mod_quiz\question\display_options;
use mod_quiz\question\qubaids_for_quiz;
use mod_quiz\question\qubaids_for_users_attempts;
use core_question\statistics\questions\all_calculated_for_qubaid_condition;
use mod_quiz\quiz_attempt;
use mod_quiz\quiz_settings;
require_once($CFG->dirroot . '/calendar/lib.php');
require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
/**#@+
* Option controlling what options are offered on the quiz settings form.
@ -190,7 +193,7 @@ function quiz_delete_instance($id) {
$DB->delete_records('quiz_feedback', array('quizid' => $quiz->id));
quiz_access_manager::delete_settings($quiz);
access_manager::delete_settings($quiz);
$events = $DB->get_records('event', array('modulename' => 'quiz', 'instance' => $quiz->id));
foreach ($events as $event) {
@ -581,12 +584,7 @@ function quiz_user_complete($course, $user, $mod, $quiz) {
* array if there are none.
*/
function quiz_get_user_attempts($quizids, $userid, $status = 'finished', $includepreviews = false) {
global $DB, $CFG;
// TODO MDL-33071 it is very annoying to have to included all of locallib.php
// just to get the quiz_attempt::FINISHED constants, but I will try to sort
// that out properly for Moodle 2.4. For now, I will just do a quick fix for
// MDL-33048.
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
global $DB;
$params = array();
switch ($status) {
@ -659,7 +657,7 @@ function quiz_get_user_grades($quiz, $userid = 0) {
/**
* Round a grade to to the correct number of decimal places, and format it for display.
*
* @param object $quiz The quiz table row, only $quiz->decimalpoints is used.
* @param stdClass $quiz The quiz table row, only $quiz->decimalpoints is used.
* @param float $grade The grade to round.
* @return float
*/
@ -691,9 +689,9 @@ function quiz_get_grade_format($quiz) {
/**
* Round a grade to the correct number of decimal places, and format it for display.
*
* @param object $quiz The quiz table row, only $quiz->decimalpoints is used.
* @param stdClass $quiz The quiz table row, only $quiz->decimalpoints is used.
* @param float $grade The grade to round.
* @return float
* @return string
*/
function quiz_format_question_grade($quiz, $grade) {
return format_float($grade, quiz_get_grade_format($quiz));
@ -1185,7 +1183,7 @@ function mod_quiz_inplace_editable(string $itemtype, int $itemid, string $newval
// Check permission of the user to update this item (customise question number).
require_capability('mod/quiz:manage', $context);
$quizobj = new quiz($quiz, $cm, $course);
$quizobj = new quiz_settings($quiz, $cm, $course);
$structure = $quizobj->get_structure();
$warning = false;
// Clean input and update the record.
@ -1236,7 +1234,7 @@ function quiz_after_add_or_update($quiz) {
}
// Store any settings belonging to the access rules.
quiz_access_manager::save_settings($quiz);
access_manager::save_settings($quiz);
// Update the events relating to this quiz.
quiz_update_events($quiz);
@ -1965,7 +1963,7 @@ function quiz_check_updates_since(cm_info $cm, $from, $filter = array()) {
// Check if questions were updated.
$updates->questions = (object) array('updated' => false);
$quizobj = quiz::create($cm->instance, $USER->id);
$quizobj = quiz_settings::create($cm->instance, $USER->id);
$quizobj->preload_questions();
$quizobj->load_questions();
$questionids = array_keys($quizobj->get_questions());
@ -2062,7 +2060,7 @@ function mod_quiz_core_calendar_provide_event_action(calendar_event $event,
}
$cm = get_fast_modinfo($event->courseid, $userid)->instances['quiz'][$event->instance];
$quizobj = quiz::create($cm->instance, $userid);
$quizobj = quiz_settings::create($cm->instance, $userid);
$quiz = $quizobj->get_quiz();
// Check they have capabilities allowing them to view the quiz.
@ -2459,7 +2457,6 @@ function mod_quiz_output_fragment_quiz_question_bank($args) {
*/
function mod_quiz_output_fragment_add_random_question_form($args) {
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/addrandomform.php');
$contexts = new \core_question\local\bank\question_edit_contexts($args['context']);
$formoptions = [
@ -2473,7 +2470,7 @@ function mod_quiz_output_fragment_add_random_question_form($args) {
'cmid' => $args['cmid']
];
$form = new quiz_add_random_form(
$form = new add_random_form(
new \moodle_url('/mod/quiz/addrandom.php'),
$formoptions,
'post',

View File

@ -31,16 +31,15 @@
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/lib.php');
require_once($CFG->dirroot . '/mod/quiz/accessmanager.php');
require_once($CFG->dirroot . '/mod/quiz/accessmanager_form.php');
require_once($CFG->dirroot . '/mod/quiz/renderer.php');
require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
require_once($CFG->libdir . '/completionlib.php');
require_once($CFG->libdir . '/filelib.php');
require_once($CFG->libdir . '/questionlib.php');
use mod_quiz\access_manager;
use mod_quiz\question\bank\qbank_helper;
use mod_quiz\question\display_options;
use mod_quiz\quiz_attempt;
use mod_quiz\quiz_settings;
/**
* @var int We show the countdown timer if there is less than this amount of time left before the
@ -82,7 +81,7 @@ define('QUIZ_SHOWIMAGE_LARGE', 2);
*
* @param object $quizobj the quiz object to create an attempt for.
* @param int $attemptnumber the sequence number for the attempt.
* @param stdClass|null $lastattempt the previous attempt by this user, if any. Only needed
* @param stdClass|false $lastattempt the previous attempt by this user, if any. Only needed
* if $attemptnumber > 1 and $quiz->attemptonlast is true.
* @param int $timenow the time the attempt was started at.
* @param bool $ispreview whether this new attempt is a preview.
@ -90,7 +89,7 @@ define('QUIZ_SHOWIMAGE_LARGE', 2);
*
* @return object the newly created attempt object.
*/
function quiz_create_attempt(quiz $quizobj, $attemptnumber, $lastattempt, $timenow, $ispreview = false, $userid = null) {
function quiz_create_attempt(quiz_settings $quizobj, $attemptnumber, $lastattempt, $timenow, $ispreview = false, $userid = null) {
global $USER;
if ($userid === null) {
@ -146,17 +145,16 @@ function quiz_create_attempt(quiz $quizobj, $attemptnumber, $lastattempt, $timen
/**
* Start a normal, new, quiz attempt.
*
* @param quiz $quizobj the quiz object to start an attempt for.
* @param quiz_settings $quizobj the quiz object to start an attempt for.
* @param question_usage_by_activity $quba
* @param object $attempt
* @param integer $attemptnumber starting from 1
* @param integer $timenow the attempt start time
* @param array $questionids slot number => question id. Used for random questions, to force the choice
* of a particular actual question. Intended for testing purposes only.
* of a particular actual question. Intended for testing purposes only.
* @param array $forcedvariantsbyslot slot number => variant. Used for questions with variants,
* to force the choice of a particular variant. Intended for testing
* purposes only.
* @throws moodle_exception
* to force the choice of a particular variant. Intended for testing
* purposes only.
* @return object modified attempt object
*/
function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $timenow,
@ -341,7 +339,7 @@ function quiz_start_attempt_built_on_last($quba, $attempt, $lastattempt) {
/**
* The save started question usage and quiz attempt in db and log the started attempt.
*
* @param quiz $quizobj
* @param quiz_settings $quizobj
* @param question_usage_by_activity $quba
* @param object $attempt
* @return object attempt object with uniqueid and id set.
@ -586,8 +584,8 @@ function quiz_feedback_record_for_grade($grade, $quiz) {
* got this grade on this quiz. The feedback is processed ready for diplay.
*
* @param float $grade a grade on this quiz.
* @param object $quiz the quiz settings.
* @param object $context the quiz context.
* @param stdClass $quiz the quiz settings.
* @param context_module $context the quiz context.
* @return string the comment that corresponds to this grade (empty string if there is not one.
*/
function quiz_feedback_for_grade($grade, $quiz, $context) {
@ -1355,7 +1353,7 @@ function quiz_questions_per_page_options() {
/**
* Get the human-readable name for a quiz attempt state.
* @param string $state one of the state constants like {@link quiz_attempt::IN_PROGRESS}.
* @param string $state one of the state constants like {@see quiz_attempt::IN_PROGRESS}.
* @return string The lang string to describe that state.
*/
function quiz_attempt_state_name($state) {
@ -2437,16 +2435,15 @@ function quiz_view($quiz, $course, $cm, $context) {
/**
* Validate permissions for creating a new attempt and start a new preview attempt if required.
*
* @param quiz $quizobj quiz object
* @param quiz_access_manager $accessmanager quiz access manager
* @param quiz_settings $quizobj quiz object
* @param access_manager $accessmanager quiz access manager
* @param bool $forcenew whether was required to start a new preview attempt
* @param int $page page to jump to in the attempt
* @param bool $redirect whether to redirect or throw exceptions (for web or ws usage)
* @return array an array containing the attempt information, access error messages and the page to jump to in the attempt
* @throws moodle_quiz_exception
* @since Moodle 3.1
*/
function quiz_validate_new_attempt(quiz $quizobj, quiz_access_manager $accessmanager, $forcenew, $page, $redirect) {
function quiz_validate_new_attempt(quiz_settings $quizobj, access_manager $accessmanager, $forcenew, $page, $redirect) {
global $DB, $USER;
$timenow = time();
@ -2486,7 +2483,7 @@ function quiz_validate_new_attempt(quiz $quizobj, quiz_access_manager $accessman
if ($redirect) {
redirect($quizobj->review_url($lastattempt->id));
} else {
throw new moodle_quiz_exception($quizobj, 'attemptalreadyclosed');
throw new moodle_exception('attemptalreadyclosed', 'quiz', $quizobj->view_url());
}
}
@ -2522,7 +2519,7 @@ function quiz_validate_new_attempt(quiz $quizobj, quiz_access_manager $accessman
/**
* Prepare and start a new attempt deleting the previous preview attempts.
*
* @param quiz $quizobj quiz object
* @param quiz_settings $quizobj quiz object
* @param int $attemptnumber the attempt number
* @param object $lastattempt last attempt object
* @param bool $offlineattempt whether is an offline attempt or not
@ -2534,7 +2531,7 @@ function quiz_validate_new_attempt(quiz $quizobj, quiz_access_manager $accessman
* @return object the new attempt
* @since Moodle 3.1
*/
function quiz_prepare_and_start_new_attempt(quiz $quizobj, $attemptnumber, $lastattempt,
function quiz_prepare_and_start_new_attempt(quiz_settings $quizobj, $attemptnumber, $lastattempt,
$offlineattempt = false, $forcedrandomquestions = [], $forcedvariants = [], $userid = null) {
global $DB, $USER;
@ -2695,8 +2692,7 @@ function quiz_retrieve_tags_for_slot_ids($slotids) {
*
* @param int $attemptid the id of the current attempt.
* @param int|null $cmid the course_module id for this quiz.
* @return quiz_attempt $attemptobj all the data about the quiz attempt.
* @throws moodle_exception
* @return quiz_attempt all the data about the quiz attempt.
*/
function quiz_create_attempt_handling_errors($attemptid, $cmid = null) {
try {

View File

@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/course/moodleform_mod.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
use mod_quiz\access_manager;
use mod_quiz\question\display_options;
/**
@ -289,11 +290,11 @@ class mod_quiz_mod_form extends moodleform_mod {
// Browser security choices.
$mform->addElement('select', 'browsersecurity', get_string('browsersecurity', 'quiz'),
quiz_access_manager::get_browser_security_choices());
access_manager::get_browser_security_choices());
$mform->addHelpButton('browsersecurity', 'browsersecurity', 'quiz');
// Any other rule plugins.
quiz_access_manager::add_settings_form_fields($this, $mform);
access_manager::add_settings_form_fields($this, $mform);
// -------------------------------------------------------------------------------
$mform->addElement('header', 'overallfeedbackhdr', get_string('overallfeedback', 'quiz'));
@ -479,7 +480,7 @@ class mod_quiz_mod_form extends moodleform_mod {
// Load any settings belonging to the access rules.
if (!empty($toform['instance'])) {
$accesssettings = quiz_access_manager::load_settings($toform['instance']);
$accesssettings = access_manager::load_settings($toform['instance']);
foreach ($accesssettings as $name => $value) {
$toform[$name] = $value;
}
@ -589,7 +590,7 @@ class mod_quiz_mod_form extends moodleform_mod {
unset($errors['gradepass']);
}
// Any other rule plugins.
$errors = quiz_access_manager::validate_settings_form_fields($errors, $data, $files, $this);
$errors = access_manager::validate_settings_form_fields($errors, $data, $files, $this);
return $errors;
}

View File

@ -20,278 +20,10 @@
* @package mod_quiz
* @copyright 2010 Matt Petro
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-76612 delete this file as part of Moodle 4.6 development.
* @deprecated This file is no longer required in Moodle 4.2+.
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
require_once($CFG->dirroot . '/mod/quiz/mod_form.php');
/**
* Form for editing settings overrides.
*
* @copyright 2010 Matt Petro
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_override_form extends moodleform {
/** @var cm_info course module object. */
protected $cm;
/** @var stdClass the quiz settings object. */
protected $quiz;
/** @var context the quiz context. */
protected $context;
/** @var bool editing group override (true) or user override (false). */
protected $groupmode;
/** @var int groupid, if provided. */
protected $groupid;
/** @var int userid, if provided. */
protected $userid;
/**
* Constructor.
* @param moodle_url $submiturl the form action URL.
* @param object course module object.
* @param object the quiz settings object.
* @param context the quiz context.
* @param bool editing group override (true) or user override (false).
* @param object $override the override being edited, if it already exists.
*/
public function __construct($submiturl, $cm, $quiz, $context, $groupmode, $override) {
$this->cm = $cm;
$this->quiz = $quiz;
$this->context = $context;
$this->groupmode = $groupmode;
$this->groupid = empty($override->groupid) ? 0 : $override->groupid;
$this->userid = empty($override->userid) ? 0 : $override->userid;
parent::__construct($submiturl);
}
protected function definition() {
global $DB;
$cm = $this->cm;
$mform = $this->_form;
$mform->addElement('header', 'override', get_string('override', 'quiz'));
$quizgroupmode = groups_get_activity_groupmode($cm);
$accessallgroups = ($quizgroupmode == NOGROUPS) || has_capability('moodle/site:accessallgroups', $this->context);
if ($this->groupmode) {
// Group override.
if ($this->groupid) {
// There is already a groupid, so freeze the selector.
$groupchoices = array();
$groupchoices[$this->groupid] = groups_get_group_name($this->groupid);
$mform->addElement('select', 'groupid',
get_string('overridegroup', 'quiz'), $groupchoices);
$mform->freeze('groupid');
} else {
// Prepare the list of groups.
// Only include the groups the current can access.
$groups = $accessallgroups ? groups_get_all_groups($cm->course) : groups_get_activity_allowed_groups($cm);
if (empty($groups)) {
// Generate an error.
$link = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$cm->id));
throw new \moodle_exception('groupsnone', 'quiz', $link);
}
$groupchoices = array();
foreach ($groups as $group) {
$groupchoices[$group->id] = $group->name;
}
unset($groups);
if (count($groupchoices) == 0) {
$groupchoices[0] = get_string('none');
}
$mform->addElement('select', 'groupid',
get_string('overridegroup', 'quiz'), $groupchoices);
$mform->addRule('groupid', get_string('required'), 'required', null, 'client');
}
} else {
// User override.
$userfieldsapi = \core_user\fields::for_identity($this->context)->with_userpic()->with_name();
$extrauserfields = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]);
if ($this->userid) {
// There is already a userid, so freeze the selector.
$user = $DB->get_record('user', ['id' => $this->userid]);
profile_load_custom_fields($user);
$userchoices = array();
$userchoices[$this->userid] = self::display_user_name($user, $extrauserfields);
$mform->addElement('select', 'userid',
get_string('overrideuser', 'quiz'), $userchoices);
$mform->freeze('userid');
} else {
// Prepare the list of users.
$groupids = 0;
if (!$accessallgroups) {
$groups = groups_get_activity_allowed_groups($cm);
$groupids = array_keys($groups);
}
$enrolledjoin = get_enrolled_with_capabilities_join(
$this->context, '', 'mod/quiz:attempt', $groupids, true);
$userfieldsql = $userfieldsapi->get_sql('u', true, '', '', false);
list($sort, $sortparams) = users_order_by_sql('u', null,
$this->context, $userfieldsql->mappings);
$users = $DB->get_records_sql("
SELECT $userfieldsql->selects
FROM {user} u
$enrolledjoin->joins
$userfieldsql->joins
LEFT JOIN {quiz_overrides} existingoverride ON
existingoverride.userid = u.id AND existingoverride.quiz = :quizid
WHERE existingoverride.id IS NULL
AND $enrolledjoin->wheres
ORDER BY $sort
", array_merge(['quizid' => $this->quiz->id], $userfieldsql->params, $enrolledjoin->params, $sortparams));
// Filter users based on any fixed restrictions (groups, profile).
$info = new \core_availability\info_module($cm);
$users = $info->filter_user_list($users);
if (empty($users)) {
// Generate an error.
$link = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$cm->id));
throw new \moodle_exception('usersnone', 'quiz', $link);
}
$userchoices = [];
foreach ($users as $id => $user) {
$userchoices[$id] = self::display_user_name($user, $extrauserfields);
}
unset($users);
$mform->addElement('searchableselector', 'userid',
get_string('overrideuser', 'quiz'), $userchoices);
$mform->addRule('userid', get_string('required'), 'required', null, 'client');
}
}
// Password.
// This field has to be above the date and timelimit fields,
// otherwise browsers will clear it when those fields are changed.
$mform->addElement('passwordunmask', 'password', get_string('requirepassword', 'quiz'));
$mform->setType('password', PARAM_TEXT);
$mform->addHelpButton('password', 'requirepassword', 'quiz');
$mform->setDefault('password', $this->quiz->password);
// Open and close dates.
$mform->addElement('date_time_selector', 'timeopen',
get_string('quizopen', 'quiz'), mod_quiz_mod_form::$datefieldoptions);
$mform->setDefault('timeopen', $this->quiz->timeopen);
$mform->addElement('date_time_selector', 'timeclose',
get_string('quizclose', 'quiz'), mod_quiz_mod_form::$datefieldoptions);
$mform->setDefault('timeclose', $this->quiz->timeclose);
// Time limit.
$mform->addElement('duration', 'timelimit',
get_string('timelimit', 'quiz'), array('optional' => true));
$mform->addHelpButton('timelimit', 'timelimit', 'quiz');
$mform->setDefault('timelimit', $this->quiz->timelimit);
// Number of attempts.
$attemptoptions = array('0' => get_string('unlimited'));
for ($i = 1; $i <= QUIZ_MAX_ATTEMPT_OPTION; $i++) {
$attemptoptions[$i] = $i;
}
$mform->addElement('select', 'attempts',
get_string('attemptsallowed', 'quiz'), $attemptoptions);
$mform->addHelpButton('attempts', 'attempts', 'quiz');
$mform->setDefault('attempts', $this->quiz->attempts);
// Submit buttons.
$mform->addElement('submit', 'resetbutton',
get_string('reverttodefaults', 'quiz'));
$buttonarray = array();
$buttonarray[] = $mform->createElement('submit', 'submitbutton',
get_string('save', 'quiz'));
$buttonarray[] = $mform->createElement('submit', 'againbutton',
get_string('saveoverrideandstay', 'quiz'));
$buttonarray[] = $mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonbar', '', array(' '), false);
$mform->closeHeaderBefore('buttonbar');
}
/**
* Get a user's name and identity ready to display.
*
* @param stdClass $user a user object.
* @param array $extrauserfields (identity fields in user table only from the user_fields API)
* @return string User's name, with extra info, for display.
*/
public static function display_user_name(stdClass $user, array $extrauserfields): string {
$username = fullname($user);
$namefields = [];
foreach ($extrauserfields as $field) {
if (isset($user->$field) && $user->$field !== '') {
$namefields[] = s($user->$field);
} else if (strpos($field, 'profile_field_') === 0) {
$field = substr($field, 14);
if (isset($user->profile[$field]) && $user->profile[$field] !== '') {
$namefields[] = s($user->profile[$field]);
}
}
}
if ($namefields) {
$username .= ' (' . implode(', ', $namefields) . ')';
}
return $username;
}
public function validation($data, $files): array {
$errors = parent::validation($data, $files);
$mform =& $this->_form;
$quiz = $this->quiz;
if ($mform->elementExists('userid')) {
if (empty($data['userid'])) {
$errors['userid'] = get_string('required');
}
}
if ($mform->elementExists('groupid')) {
if (empty($data['groupid'])) {
$errors['groupid'] = get_string('required');
}
}
// Ensure that the dates make sense.
if (!empty($data['timeopen']) && !empty($data['timeclose'])) {
if ($data['timeclose'] < $data['timeopen'] ) {
$errors['timeclose'] = get_string('closebeforeopen', 'quiz');
}
}
// Ensure that at least one quiz setting was changed.
$changed = false;
$keys = array('timeopen', 'timeclose', 'timelimit', 'attempts', 'password');
foreach ($keys as $key) {
if ($data[$key] != $quiz->{$key}) {
$changed = true;
break;
}
}
if (!$changed) {
$errors['timeopen'] = get_string('nooverridedata', 'quiz');
}
return $errors;
}
}
debugging('This file is no longer required in Moodle 4.2+. Please do not include/require it.', DEBUG_DEVELOPER);

View File

@ -22,11 +22,11 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_quiz\form\edit_override_form;
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot.'/mod/quiz/lib.php');
require_once($CFG->dirroot.'/mod/quiz/locallib.php');
require_once($CFG->dirroot.'/mod/quiz/override_form.php');
$overrideid = required_param('id', PARAM_INT);
$confirm = optional_param('confirm', false, PARAM_BOOL);
@ -103,7 +103,7 @@ if ($override->groupid) {
profile_load_custom_fields($user);
$confirmstr = get_string('overridedeleteusersure', 'quiz',
quiz_override_form::display_user_name($user,
edit_override_form::display_user_name($user,
\core_user\fields::get_identity_fields($context)));
}

View File

@ -22,12 +22,11 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_quiz\form\edit_override_form;
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot.'/mod/quiz/lib.php');
require_once($CFG->dirroot.'/mod/quiz/locallib.php');
require_once($CFG->dirroot.'/mod/quiz/override_form.php');
$cmid = optional_param('cmid', 0, PARAM_INT);
$overrideid = optional_param('id', 0, PARAM_INT);
@ -119,7 +118,7 @@ if (!$groupmode) {
}
// Setup the form.
$mform = new quiz_override_form($url, $cm, $quiz, $context, $groupmode, $override);
$mform = new edit_override_form($url, $cm, $quiz, $context, $groupmode, $override);
$mform->set_data($data);
if ($mform->is_cancelled()) {

View File

@ -25,8 +25,6 @@
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot.'/mod/quiz/lib.php');
require_once($CFG->dirroot.'/mod/quiz/locallib.php');
require_once($CFG->dirroot.'/mod/quiz/override_form.php');
$cmid = required_param('cmid', PARAM_INT);
$mode = optional_param('mode', '', PARAM_ALPHA); // One of 'user' or 'group', default is 'group'.

View File

@ -28,6 +28,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_quiz\quiz_attempt;
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
@ -71,7 +73,7 @@ require_sesskey();
// Check that this attempt belongs to this user.
if ($attemptobj->get_userid() != $USER->id) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt');
throw new moodle_exception('notyourattempt', 'quiz', $attemptobj->view_url());
}
// Check capabilities.
@ -81,8 +83,7 @@ if (!$attemptobj->is_preview_user()) {
// If the attempt is already closed, send them to the review page.
if ($attemptobj->is_finished()) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(),
'attemptalreadyclosed', null, $attemptobj->review_url());
throw new moodle_exception('attemptalreadyclosed', 'quiz', $attemptobj->view_url());
}
// Process the attempt, getting the new status for the attempt.

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_quiz\quiz_settings;
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
@ -30,7 +32,7 @@ $slotnumber = required_param('slot', PARAM_INT);
$repagtype = required_param('repag', PARAM_INT);
require_sesskey();
$quizobj = quiz::create($quizid);
$quizobj = quiz_settings::create($quizid);
require_login($quizobj->get_course(), false, $quizobj->get_cm());
require_capability('mod/quiz:manage', $quizobj->get_context());
if (quiz_has_attempts($quizid)) {

View File

@ -15,6 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
use mod_quiz\local\reports\report_base;
use mod_quiz\quiz_attempt;
defined('MOODLE_INTERNAL') || die();

View File

@ -15,6 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
use mod_quiz\local\reports\attempts_report_table;
use mod_quiz\quiz_attempt;
/**
* This is a table subclass for displaying the quiz grades report.

View File

@ -24,6 +24,7 @@
use mod_quiz\local\reports\attempts_report;
use mod_quiz\question\bank\qbank_helper;
use mod_quiz\quiz_attempt;
defined('MOODLE_INTERNAL') || die();

Some files were not shown because too many files have changed in this diff Show More