Skip to content

Instantly share code, notes, and snippets.

@timhunt

timhunt/rule.php Secret

Created March 31, 2022 08:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timhunt/f8c5ddc2a78d1226d249bed17ba4fdfc to your computer and use it in GitHub Desktop.
Save timhunt/f8c5ddc2a78d1226d249bed17ba4fdfc to your computer and use it in GitHub Desktop.
quizaccess_subtimes main class
<?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/>.
/**
* Implementation of the quizaccess_subtimes plugin.
*
* @package quizaccess_subtimes
* @copyright 2020 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use quizaccess_subtimes\setting_utils;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
require_once($CFG->dirroot . '/mod/quiz/accessmanager.php');
/**
* A rule representing the Time limit steps. It does not actually restrict access, but we use this
* class to encapsulate some of the relevant code.
*/
class quizaccess_subtimes extends quiz_access_rule_base {
/**
* @var array the subtimes for this quiz, in 'array form' as explained in
* the class comment of {@see setting_utils}.
*/
protected $subtimes;
/**
* @var int if the user has a time limit override, this is how much their time differs from standard.
*/
protected $timeadjust;
/**
* @var int quiz closes time.
*/
protected $timeclose;
public function __construct(quiz $quizobj, int $timenow, array $subtimes, int $timeadjust) {
parent::__construct($quizobj, $timenow);
$this->subtimes = $subtimes;
$this->timeadjust = $timeadjust;
$this->timeclose = $quizobj->get_quiz()->timeclose;
}
public static function make(quiz $quizobj, $timenow, $canignoretimelimits): ?quizaccess_subtimes {
if (empty($quizobj->get_quiz()->subtimes) || $canignoretimelimits) {
return null;
}
$subtimes = setting_utils::db_to_array($quizobj->get_quiz()->subtimes);
// Take account of any settings overrides in force.
[$subtimes, $timeadjust] = setting_utils::change_total_time(
$subtimes, $quizobj->get_quiz()->timelimit);
if (!$subtimes) {
return null;
}
return new self($quizobj, $timenow, $subtimes, $timeadjust);
}
public function get_superceded_rules(): array {
return ['timelimit'];
}
protected function get_description_bits(array $subtimes): array {
$stages = [];
foreach ($subtimes as [$time, $name]) {
if ($time == 0) {
$displaytime = get_string('unavailable', 'quizaccess_subtimes');
} else {
$displaytime = format_time($time);
}
$stages[] = get_string('timestage', 'quizaccess_subtimes',
['name' => s($name), 'duration' => $displaytime]);
}
$extratimemessage = '';
if ($this->timeadjust > 0) {
$extratimemessage = ' ' . get_string('timeextra', 'quizaccess_subtimes',
format_time($this->timeadjust));
}
return [$stages, $extratimemessage];
}
public function description(): string {
[$stages, $extratimemessage] = $this->get_description_bits($this->subtimes);
return html_writer::tag('b', get_string('timelimits', 'quizaccess_subtimes')) .
$extratimemessage . '<br>' . implode('<br>', $stages);
}
public function is_preflight_check_required($attemptid): bool {
// Warning only required if the attempt is not already started.
return $attemptid === null;
}
public function add_preflight_check_form_fields(mod_quiz_preflight_check_form $quizform,
MoodleQuickForm $mform, $attemptid): void {
[$stages, $extratimemessage] = $this->get_description_bits($this->subtimes);
if ($extratimemessage) {
$stages[0] .= $extratimemessage;
}
$mform->addElement('header', 'subtimeheader',
get_string('timelimits', 'quizaccess_subtimes'));
$mform->addElement('static', 'subtimemessage', '',
html_writer::tag('p', get_string('confirmstart', 'quizaccess_subtimes')) .
html_writer::alist($stages));
}
public function end_time($attempt): int {
$timedue = $attempt->timestart + setting_utils::get_total_time($this->subtimes);
if ($this->quiz->timeclose) {
$timedue = min($timedue, $this->quiz->timeclose);
}
return $timedue;
}
public function time_left_display($attempt, $timenow) {
// If this is a teacher preview after the time limit expires, don't show the time_left
$endtime = $this->end_time($attempt);
if ($attempt->preview && $timenow > $endtime) {
return false;
}
return $endtime - $timenow;
}
public function setup_attempt_page($page): void {
// This is a nasty global variable hack to get the attempt time start.
// There does not seem to be a way to do this using the API.
global $attemptobj;
$attempttimestart = null;
if (!empty($attemptobj) && $attemptobj->get_state() == quiz_attempt::IN_PROGRESS) {
$attempttimestart = $attemptobj->get_attempt()->timestart;
}
$subtimes = $this->subtimes;
$mustsubmit = '';
if ($attempttimestart &&
$this->timeclose > $this->timenow &&
$this->timeclose < $attempttimestart + setting_utils::get_total_time($subtimes)) {
$subtimes = setting_utils::change_total_time_at_end($subtimes,
$this->timeclose - $attempttimestart);
$mustsubmit = get_string('mustsubmitby', 'quizaccess_subtimes',
userdate($this->timeclose, get_string('strftimedatetime', 'langconfig')));
}
// Prepare the info about the time periods.
[$stages] = $this->get_description_bits($subtimes);
$timestages = [];
$timeleft = setting_utils::get_total_time($subtimes);
foreach ($subtimes as $key => [$time]) {
$timeleft -= $time;
$timestage = new stdClass();
$timestage->display = $stages[$key];
$timestage->endtime = $timeleft;
$timestages[] = $timestage;
}
// Render the time info into a fake block.
$bc = new block_contents();
$bc->attributes['id'] = 'quizaccess_subtimes-timer';
$bc->title = get_string('timelimits', 'quizaccess_subtimes');
$bc->content = $page->get_renderer('core')->render_from_template(
'quizaccess_subtimes/timer_display',
['timestages' => $timestages, 'mustsubmitby' => $mustsubmit]);
// Add the fake block to the page.
$regions = $page->blocks->get_regions();
$page->blocks->add_fake_block($bc, reset($regions));
// Initialise the JavaScript.
$page->requires->js_call_amd('quizaccess_subtimes/timer', 'init',
[get_string('timestring', 'quizaccess_subtimes',
['hours' => '%%HH%%', 'mins' => '%%MM%%'])]);
}
public static function add_settings_form_fields(mod_quiz_mod_form $quizform,
MoodleQuickForm $mform): void {
global $DB;
// There are 3 cases to consider:
// 1) If the user has 'quizaccess/subtimes:editsettings', the show our field editable,
// disabledIf Moodle's time limit if ours is set.
// 2) If the user does not have the capability, and setting not set, don't show it.
// 3) If the user does not have the capability, and setting set, hide standard, show ours frozen..
$context = $quizform->get_context();
$canedit = has_capability('quizaccess/subtimes:editsettings', $context);
// Get the current value. Slightly ugly to query the DB here.
$currentvalue = null;
$quizid = $quizform->get_instance();
if ($quizid) {
$currentvalue = $DB->get_field('quizaccess_subtimes', 'subtimes',
['quizid' => $quizid]);
}
if (!$canedit && !$currentvalue) {
return; // Case 2).
}
// Case 1) or 3).
$element = $mform->createElement('textarea', 'subtimessetting',
get_string('subtimes', 'quizaccess_subtimes'), ['cols' => '80', 'rows' => '4']);
$mform->insertElementBefore($element, 'overduehandling');
$mform->addHelpButton('subtimessetting', 'subtimes', 'quizaccess_subtimes');
if (!$currentvalue) {
$mform->setAdvanced('subtimessetting');
}
if ($canedit) {
// Case 1).
$mform->disabledIf('timelimit', 'subtimessetting', 'neq', '');
} else {
// Case 3).
$mform->removeElement('timelimit');
$mform->addElement('hidden', 'timelimit', 0);
$mform->setType('timelimit', PARAM_INT);
$mform->hardFreeze('subtimessetting');
}
}
public static function validate_settings_form_fields(array $errors, array $data, $files,
mod_quiz_mod_form $quizform): array {
if (!empty($data['subtimessetting'])) {
[, $error] = setting_utils::input_to_array_with_validation($data['subtimessetting']);
if ($error) {
$errors['subtimessetting'] = $error;
}
}
return $errors;
}
public static function save_settings($quiz): void {
global $DB;
if (!isset($quiz->subtimessetting)) {
// Data did not come from the form, so user does not have permissions, so don't update the db.
return;
}
$subtimes = setting_utils::input_to_array($quiz->subtimessetting);
if (!$subtimes) {
$DB->delete_records('quizaccess_subtimes', ['quizid' => $quiz->id]);
return;
}
$tosave = setting_utils::array_to_db($subtimes);
if ($DB->record_exists('quizaccess_subtimes', ['quizid' => $quiz->id])) {
$DB->set_field('quizaccess_subtimes', 'subtimes', $tosave, ['quizid' => $quiz->id]);
} else {
$DB->insert_record('quizaccess_subtimes',
(object) ['quizid' => $quiz->id, 'subtimes' => $tosave]);
}
// Having saved our settings, we also need to set the overall quiz time limit to the right total time.
$DB->set_field('quiz', 'timelimit', setting_utils::get_total_time($subtimes), ['id' => $quiz->id]);
}
public static function delete_settings($quiz): void {
global $DB;
$DB->delete_records('quizaccess_subtimes', ['quizid' => $quiz->id]);
}
/**
* We cannot use the standard get_settings_sql, because we need to
* convert the stored value to input form, for the cases when we are showing the form.
*
* @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) {
global $DB;
$dbvalue = $DB->get_field('quizaccess_subtimes', 'subtimes', ['quizid' => $quizid]);
if (!$dbvalue) {
return [];
}
return [
'subtimes' => $dbvalue,
'subtimessetting' => setting_utils::db_to_input($dbvalue),
];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment