diff --git a/mod/quiz/accessrule/seb/classes/seb_access_manager.php b/mod/quiz/accessrule/seb/classes/seb_access_manager.php
index 6664419fa1e43..ecbbc798bedfd 100644
--- a/mod/quiz/accessrule/seb/classes/seb_access_manager.php
+++ b/mod/quiz/accessrule/seb/classes/seb_access_manager.php
@@ -30,6 +30,7 @@
use context_module;
use mod_quiz\quiz_settings;
+use mod_quiz\local\override_manager;
defined('MOODLE_INTERNAL') || die();
@@ -69,6 +70,25 @@ public function __construct(quiz_settings $quiz) {
$this->context = context_module::instance($quiz->get_cmid());
$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());
+
+ $settings = $quiz->get_quiz();
+ if (isset($settings->enableseboverride) && !!$settings->enableseboverride) {
+ $prefix = 'seb_';
+ $prelen = strlen($prefix);
+ if (!$this->quizsettings) {
+ $this->quizsettings = new seb_quiz_settings();
+ }
+ foreach (override_manager::get_seb_override_settings() as $key) {
+ if (str_starts_with($key, $prefix)) {
+ $key = substr($key, $prelen);
+ } else {
+ continue;
+ }
+ if ($settings->{$prefix.$key} !== null && $settings->{$prefix.$key} != $this->quizsettings->get($key)) {
+ $this->quizsettings->set($key, $settings->{$prefix.$key});
+ }
+ }
+ }
}
/**
diff --git a/mod/quiz/accessrule/seb/classes/settings_provider.php b/mod/quiz/accessrule/seb/classes/settings_provider.php
index f7a8ffdaa10f4..e265835d15a6c 100644
--- a/mod/quiz/accessrule/seb/classes/settings_provider.php
+++ b/mod/quiz/accessrule/seb/classes/settings_provider.php
@@ -584,7 +584,7 @@ public static function get_requiresafeexambrowser_options(\context $context): ar
* Returns a list of templates.
* @return array
*/
- protected static function get_template_options(): array {
+ public static function get_template_options(): array {
$templates = [];
$records = template::get_records(['enabled' => 1], 'name');
if ($records) {
diff --git a/mod/quiz/classes/form/edit_override_form.php b/mod/quiz/classes/form/edit_override_form.php
index 4f5c7c6786074..d00ab4d1c956f 100644
--- a/mod/quiz/classes/form/edit_override_form.php
+++ b/mod/quiz/classes/form/edit_override_form.php
@@ -23,6 +23,7 @@
use moodle_url;
use moodleform;
use stdClass;
+use quizaccess_seb\{seb_quiz_settings,settings_provider};
defined('MOODLE_INTERNAL') || die();
@@ -59,6 +60,9 @@ class edit_override_form extends moodleform {
/** @var int overrideid, if provided. */
protected int $overrideid;
+ /** @var array array of seb settings to override. */
+ protected array $sebdata;
+
/**
* Constructor.
*
@@ -80,6 +84,7 @@ public function __construct(moodle_url $submiturl,
$this->groupid = empty($override->groupid) ? 0 : $override->groupid;
$this->userid = empty($override->userid) ? 0 : $override->userid;
$this->overrideid = $override->id ?? 0;
+ $this->sebdata = empty($override->sebdata) ? [] : unserialize($override->sebdata);
parent::__construct($submiturl);
}
@@ -224,6 +229,9 @@ protected function definition() {
$mform->addHelpButton('attempts', 'attempts', 'quiz');
$mform->setDefault('attempts', $this->quiz->attempts);
+ // SEB override settings.
+ $this->display_seb_settings($mform);
+
// Submit buttons.
$mform->addElement('submit', 'resetbutton',
get_string('reverttodefaults', 'quiz'));
@@ -239,6 +247,162 @@ protected function definition() {
$mform->closeHeaderBefore('buttonbar');
}
+ /**
+ * Add SEB settings to the form.
+ *
+ * @param \MoodleQuickForm $mform
+ * @return void
+ */
+ protected function display_seb_settings($mform) {
+ $mform->addElement('header', 'seb', get_string('seb', 'quizaccess_seb'));
+
+ $mform->addElement('checkbox', 'enableseboverride', 'Enable SEB override');
+ $mform->setDefault('enableseboverride', $this->sebdata['enableseboverride'] ?? false);
+
+ // "Require the use of Safe Exam Browser".
+ $requireseboptions[settings_provider::USE_SEB_NO] = get_string('no');
+
+ if (settings_provider::can_configure_manually($this->context) || self::is_conflicting_permissions($this->context)) {
+ $requireseboptions[settings_provider::USE_SEB_CONFIG_MANUALLY] = get_string('seb_use_manually', 'quizaccess_seb');
+ }
+
+ if (settings_provider::can_use_seb_template($this->context) || self::is_conflicting_permissions($this->context)) {
+ if (!empty(settings_provider::get_template_options())) {
+ $requireseboptions[settings_provider::USE_SEB_TEMPLATE] = get_string('seb_use_template', 'quizaccess_seb');
+ }
+ }
+
+ $requireseboptions[settings_provider::USE_SEB_CLIENT_CONFIG] = get_string('seb_use_client', 'quizaccess_seb');
+
+ $mform->addElement(
+ 'select',
+ 'seb_requiresafeexambrowser',
+ get_string('seb_requiresafeexambrowser', 'quizaccess_seb'),
+ $requireseboptions
+ );
+
+ $mform->setType('seb_requiresafeexambrowser', PARAM_INT);
+ $mform->setDefault('seb_requiresafeexambrowser', $this->sebdata['seb_requiresafeexambrowser'] ?? $this->quiz->seb_requiresafeexambrowser ?? 0);
+ $mform->addHelpButton('seb_requiresafeexambrowser', 'seb_requiresafeexambrowser', 'quizaccess_seb');
+ $mform->disabledIf('seb_requiresafeexambrowser', 'enableseboverride');
+
+ if (settings_provider::is_conflicting_permissions($this->context)) {
+ $mform->freeze('seb_requiresafeexambrowser');
+ }
+
+ // "Safe Exam Browser config template".
+ if (settings_provider::can_use_seb_template($this->context) || settings_provider::is_conflicting_permissions($this->context)) {
+ $element = $mform->addElement(
+ 'select',
+ 'seb_templateid',
+ get_string('seb_templateid', 'quizaccess_seb'),
+ settings_provider::get_template_options()
+ );
+ } else {
+ $element = $mform->addElement('hidden', 'seb_templateid');
+ }
+
+ $mform->setType('seb_templateid', PARAM_INT);
+ $mform->setDefault('seb_templateid', $this->sebdata['seb_templateid'] ?? $this->quiz->seb_templateid ?? 0);
+ $mform->addHelpButton('seb_templateid', 'seb_templateid', 'quizaccess_seb');
+ $mform->disabledIf('seb_templateid', 'enableseboverride');
+
+ if (settings_provider::is_conflicting_permissions($this->context)) {
+ $mform->freeze('seb_templateid');
+ }
+
+ // "Show Safe Exam browser download button".
+ if (settings_provider::can_change_seb_showsebdownloadlink($this->context)) {
+ $mform->addElement('selectyesno',
+ 'seb_showsebdownloadlink',
+ get_string('seb_showsebdownloadlink', 'quizaccess_seb')
+ );
+
+ $mform->setType('seb_showsebdownloadlink', PARAM_BOOL);
+ $mform->setDefault('seb_showsebdownloadlink', $this->sebdata['seb_showsebdownloadlink'] ?? $this->quiz->seb_showsebdownloadlink ?? 1);
+ $mform->addHelpButton('seb_showsebdownloadlink', 'seb_showsebdownloadlink', 'quizaccess_seb');
+ $mform->disabledIf('seb_showsebdownloadlink', 'enableseboverride');
+ }
+
+ // Manual config elements.
+ $defaults = settings_provider::get_seb_config_element_defaults();
+ $types = settings_provider::get_seb_config_element_types();
+
+ foreach (settings_provider::get_seb_config_elements() as $name => $type) {
+ if (!settings_provider::can_manage_seb_config_setting($name, $this->context)) {
+ $type = 'hidden';
+ }
+
+ $mform->addElement($type, $name, get_string($name, 'quizaccess_seb'));
+
+ $mform->addHelpButton($name, $name, 'quizaccess_seb');
+ $mform->setType('seb_showsebdownloadlink', PARAM_BOOL);
+ $mform->setDefault('seb_showsebdownloadlink', $this->sebdata['seb_showsebdownloadlink'] ?? $this->quiz->seb_showsebdownloadlink ?? 1);
+ $mform->disabledIf($name, 'enableseboverride');
+
+ if (isset($defaults[$name])) {
+ $mform->setDefault($name, $this->sebdata[$name] ?? $this->quiz->{$name} ?? $defaults[$name]);
+ }
+
+ if (isset($types[$name])) {
+ $mform->setType($name, $types[$name]);
+ }
+ }
+
+ if (settings_provider::can_change_seb_allowedbrowserexamkeys($this->context)) {
+ $mform->addElement('textarea',
+ 'seb_allowedbrowserexamkeys',
+ get_string('seb_allowedbrowserexamkeys', 'quizaccess_seb')
+ );
+
+ $mform->setType('seb_allowedbrowserexamkeys', PARAM_RAW);
+ $mform->setDefault('seb_allowedbrowserexamkeys', $this->sebdata['seb_allowedbrowserexamkeys'] ?? $this->quiz->seb_allowedbrowserexamkeys ?? '');
+ $mform->addHelpButton('seb_allowedbrowserexamkeys', 'seb_allowedbrowserexamkeys', 'quizaccess_seb');
+ $mform->disabledIf('seb_allowedbrowserexamkeys', 'enableseboverride');
+ }
+
+ // Hideifs.
+ foreach (settings_provider::get_quiz_hideifs() as $elname => $rules) {
+ if ($mform->elementExists($elname)) {
+ foreach ($rules as $hideif) {
+ $mform->hideIf(
+ $hideif->get_element(),
+ $hideif->get_dependantname(),
+ $hideif->get_condition(),
+ $hideif->get_dependantvalue()
+ );
+ }
+ }
+ }
+
+ // Lock elements.
+ if (settings_provider::is_conflicting_permissions($this->context)) {
+ // Freeze common quiz settings.
+ $mform->addElement('enableseboverride');
+ $mform->freeze('seb_requiresafeexambrowser');
+ $mform->freeze('seb_templateid');
+ $mform->freeze('seb_showsebdownloadlink');
+ $mform->freeze('seb_allowedbrowserexamkeys');
+
+ $quizsettings = seb_quiz_settings::get_by_quiz_id((int) $this->quiz->id);
+
+ // Remove template ID if not using template for this quiz.
+ if (empty($quizsettings) || $quizsettings->get('requiresafeexambrowser') != settings_provider::USE_SEB_TEMPLATE) {
+ $mform->removeElement('seb_templateid');
+ }
+
+ // Freeze all SEB specific settings.
+ foreach (settings_provider::get_seb_config_elements() as $element => $type) {
+ if ($mform->elementExists($element)) {
+ $mform->freeze($element);
+ }
+ }
+ }
+
+ // Close header before next field.
+ $mform->closeHeaderBefore('resetbutton');
+ }
+
/**
* Get a user's name and identity ready to display.
*
diff --git a/mod/quiz/classes/local/override_manager.php b/mod/quiz/classes/local/override_manager.php
index a79c739b1a56f..d1721aed4fa0c 100644
--- a/mod/quiz/classes/local/override_manager.php
+++ b/mod/quiz/classes/local/override_manager.php
@@ -31,8 +31,43 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class override_manager {
+ /** @var array quiz SEB setting keys that can be overwritten **/
+ private const OVERRIDEABLE_QUIZ_SEB_SETTINGS = [
+ 'enableseboverride',
+ 'seb_activateurlfiltering',
+ 'seb_allowedbrowserexamkeys',
+ 'seb_allowreloadinexam',
+ 'seb_allowspellchecking',
+ 'seb_allowuserquitseb',
+ 'seb_enableaudiocontrol',
+ 'seb_expressionsallowed',
+ 'seb_expressionsblocked',
+ 'seb_filterembeddedcontent',
+ 'seb_linkquitseb',
+ 'seb_muteonstartup',
+ 'seb_quitpassword',
+ 'seb_regexallowed',
+ 'seb_regexblocked',
+ 'seb_requiresafeexambrowser',
+ 'seb_showkeyboardlayout',
+ 'seb_showreloadbutton',
+ 'seb_showsebdownloadlink',
+ 'seb_showsebtaskbar',
+ 'seb_showtime',
+ 'seb_showwificontrol',
+ 'seb_templateid',
+ 'seb_userconfirmquit',
+ ];
+
/** @var array quiz setting keys that can be overwritten **/
- private const OVERRIDEABLE_QUIZ_SETTINGS = ['timeopen', 'timeclose', 'timelimit', 'attempts', 'password'];
+ private const OVERRIDEABLE_QUIZ_SETTINGS = [
+ 'timeopen',
+ 'timeclose',
+ 'timelimit',
+ 'attempts',
+ 'password',
+ ...self::OVERRIDEABLE_QUIZ_SEB_SETTINGS
+ ];
/**
* Create override manager
@@ -88,10 +123,12 @@ public function validate_data(array $formdata): array {
// Ensure at least one of the overrideable settings is set.
$keysthatareset = array_map(function ($key) use ($formdata) {
return isset($formdata->$key) && !is_null($formdata->$key);
- }, self::OVERRIDEABLE_QUIZ_SETTINGS);
+ }, array_diff(self::OVERRIDEABLE_QUIZ_SETTINGS, self::OVERRIDEABLE_QUIZ_SEB_SETTINGS));
if (!in_array(true, $keysthatareset)) {
- $errors['general'][] = new \lang_string('nooverridedata', 'quiz');
+ if (!(isset($formdata->enableseboverride) && !is_null($formdata->enableseboverride))) {
+ $errors['general'][] = new \lang_string('nooverridedata', 'quiz');
+ }
}
// Ensure quiz is a valid quiz.
@@ -238,6 +275,12 @@ public function save_override(array $formdata): int {
// Extract only the necessary data.
$datatoset = $this->parse_formdata($formdata);
+
+ // Create sebdata field value.
+ $sebdata = array_intersect_key($datatoset, array_flip(self::OVERRIDEABLE_QUIZ_SEB_SETTINGS));
+ $sebdata = serialize($sebdata);
+
+ $datatoset['sebdata'] = $sebdata;
$datatoset['quiz'] = $this->quiz->id;
// Validate the data is OK.
@@ -557,12 +600,9 @@ private function clear_unused_values(array $formdata): array {
// If the formdata is empty, set it to null.
// This avoids putting 0, false, or '' into the DB since the override logic expects null.
// Attempts is the exception, it can have a integer value of '0', so we use is_numeric instead.
- if ($key != 'attempts' && empty($formdata[$key])) {
- $formdata[$key] = null;
- }
-
- if ($key == 'attempts' && !is_numeric($formdata[$key])) {
- $formdata[$key] = null;
+ if (!in_array($key, ['attempts', ...self::OVERRIDEABLE_QUIZ_SEB_SETTINGS])
+ && (empty($formdata[$key]) || !is_numeric($formdata[$key]))) {
+ $formdata[$key] = null;
}
}
@@ -600,4 +640,8 @@ public static function delete_orphaned_group_overrides_in_course(int $courseid):
}
return array_unique(array_column($records, 'quiz'));
}
+
+ public static function get_seb_override_settings() {
+ return self::OVERRIDEABLE_QUIZ_SEB_SETTINGS;
+ }
}
diff --git a/mod/quiz/db/install.xml b/mod/quiz/db/install.xml
index 8a7d8ee9f304f..8420cf3d6c5bc 100644
--- a/mod/quiz/db/install.xml
+++ b/mod/quiz/db/install.xml
@@ -132,6 +132,7 @@
+
diff --git a/mod/quiz/db/upgrade.php b/mod/quiz/db/upgrade.php
index 4748c3ad20011..feafe30e900b5 100644
--- a/mod/quiz/db/upgrade.php
+++ b/mod/quiz/db/upgrade.php
@@ -132,6 +132,21 @@ function xmldb_quiz_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2023112402, 'quiz');
}
+ if ($oldversion < 2024072400) {
+
+ // Define field sebdata to be added to quiz_overrides.
+ $table = new xmldb_table('quiz_overrides');
+ $field = new xmldb_field('sebdata', XMLDB_TYPE_TEXT, null, null, null, null, null, 'password');
+
+ // Conditionally launch add field quizgradeitemid.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Quiz savepoint reached.
+ upgrade_mod_savepoint(true, 2024072400, 'quiz');
+ }
+
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php
index 375f3cc9e40b4..8ecc3f8810f40 100644
--- a/mod/quiz/lib.php
+++ b/mod/quiz/lib.php
@@ -326,6 +326,14 @@ function quiz_update_effective_access($quiz, $userid) {
}
}
+ // Merge SEB override settings if available.
+ $seboverride = isset($override->sebdata) ? unserialize($override->sebdata) : null;
+ if (!empty($seboverride) && !!$seboverride['enableseboverride']) {
+ foreach ($seboverride as $key => $value) {
+ $quiz->{$key} = $value;
+ }
+ }
+
return $quiz;
}
diff --git a/mod/quiz/overrideedit.php b/mod/quiz/overrideedit.php
index 878e263770562..a9f7379d5dc81 100644
--- a/mod/quiz/overrideedit.php
+++ b/mod/quiz/overrideedit.php
@@ -72,6 +72,15 @@
// Editing an override.
$data = clone $override;
+ // Unpack SEB settings into data object.
+ $data->sebdata = unserialize($data->sebdata);
+ if ($data->sebdata->enableseboverride) {
+ foreach($data->sebdata as $sebkey => $sebval) {
+ $data->{$sebkey} = $sebval;
+ }
+ }
+ unset($data->sebdata);
+
if ($override->groupid) {
if (!groups_group_visible($override->groupid, $course, $cm)) {
throw new \moodle_exception('invalidoverrideid', 'quiz');
diff --git a/mod/quiz/overrides.php b/mod/quiz/overrides.php
index be467be62435f..6392d72286e30 100644
--- a/mod/quiz/overrides.php
+++ b/mod/quiz/overrides.php
@@ -23,6 +23,7 @@
*/
use mod_quiz\quiz_settings;
+use quizaccess_seb\settings_provider;
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot.'/mod/quiz/lib.php');
@@ -229,6 +230,15 @@
get_string('enabled', 'quiz') : get_string('none', 'quiz');
}
+ // Safe exam browser.
+ if (isset($override->sebdata)) {
+ $sebdata = unserialize($override->sebdata);
+ if (!!$sebdata['enableseboverride']) {
+ $fields[] = get_string('seb_requiresafeexambrowser', 'quizaccess_seb');
+ $values[] = settings_provider::get_requiresafeexambrowser_options($context)[$sebdata['seb_requiresafeexambrowser']];
+ }
+ }
+
// Prepare the information about who this override applies to.
$extranamebit = $active ? '' : '*';
$usercells = [];
diff --git a/mod/quiz/version.php b/mod/quiz/version.php
index fa22a93ba4087..8d648feba1090 100644
--- a/mod/quiz/version.php
+++ b/mod/quiz/version.php
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2024051700;
+$plugin->version = 2024072400;
$plugin->requires = 2024041600;
$plugin->component = 'mod_quiz';