-
-
Save timhunt/f8c5ddc2a78d1226d249bed17ba4fdfc to your computer and use it in GitHub Desktop.
quizaccess_subtimes main class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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