diff --git a/mod/quiz/accessrule/seb/classes/seb_quiz_settings.php b/mod/quiz/accessrule/seb/classes/seb_quiz_settings.php
index 1e338b497869a..872bda64c9c9f 100644
--- a/mod/quiz/accessrule/seb/classes/seb_quiz_settings.php
+++ b/mod/quiz/accessrule/seb/classes/seb_quiz_settings.php
@@ -34,6 +34,7 @@
use lang_string;
use moodle_exception;
use moodle_url;
+use mod_quiz\quiz_settings;
defined('MOODLE_INTERNAL') || die();
@@ -196,11 +197,39 @@ protected static function define_properties(): array {
* @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)) {
- return new static(0, $data);
+ global $USER;
+ // Skip cache if quiz has overrides for user.
+ if (!($hasoverrides = quiz_has_user_overrides($quizid))) {
+ if ($data = self::get_quiz_settings_cache()->get($quizid)) {
+ return new static(0, $data);
+ }
}
- return self::get_record(['quizid' => $quizid]);
+ $quiz = self::get_record(['quizid' => $quizid]);
+
+ // Overwrite settings from override manager if available.
+ if ($hasoverrides) {
+ // Create blank seb_quiz_settings instance if none exists.
+ if (!$quiz) {
+ $record = new \stdClass();
+ $record->quizid = $quizid;
+ $record->cmid = get_coursemodule_from_instance('quiz', $quizid)->id;
+ $quiz = $quiz ?: new self(0, $record);
+ }
+
+ $settings = quiz_settings::create_for_cmid($quiz->get('cmid'), $USER->id)->get_quiz();
+ // If overriding enabled, overwrite seb settings.
+ if (isset($settings->enableseboverride) && !!$settings->enableseboverride) {
+ $prefix = 'seb_';
+ foreach (array_keys(self::properties_definition()) as $key) {
+ if (isset($settings->{$prefix.$key})) {
+ $quiz->set($key, $settings->{$prefix.$key});
+ }
+ }
+ }
+ }
+
+ return $quiz;
}
/**
@@ -210,10 +239,12 @@ public static function get_by_quiz_id(int $quizid) {
* @return string|null
*/
public static function get_config_by_quiz_id(int $quizid): ?string {
- $config = self::get_config_cache()->get($quizid);
-
- if ($config !== false) {
- return $config;
+ // Skip cache if quiz has overrides for user.
+ if (!quiz_has_user_overrides($quizid)) {
+ $config = self::get_config_cache()->get($quizid);
+ if ($config !== false) {
+ return $config;
+ }
}
$config = null;
diff --git a/mod/quiz/accessrule/seb/classes/settings_provider.php b/mod/quiz/accessrule/seb/classes/settings_provider.php
index f7a8ffdaa10f4..8983920b8d9ee 100644
--- a/mod/quiz/accessrule/seb/classes/settings_provider.php
+++ b/mod/quiz/accessrule/seb/classes/settings_provider.php
@@ -188,16 +188,17 @@ protected static function add_seb_header_element(\mod_quiz_mod_form $quizform, \
* @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
*/
protected static function add_seb_usage_options(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
+ $options = self::get_requiresafeexambrowser_options($quizform->get_context());
$element = $mform->createElement(
'select',
'seb_requiresafeexambrowser',
get_string('seb_requiresafeexambrowser', 'quizaccess_seb'),
- self::get_requiresafeexambrowser_options($quizform->get_context())
+ $options
);
self::insert_element($quizform, $mform, $element);
self::set_type($quizform, $mform, 'seb_requiresafeexambrowser', PARAM_INT);
- self::set_default($quizform, $mform, 'seb_requiresafeexambrowser', self::USE_SEB_NO);
+ self::set_default($quizform, $mform, 'seb_requiresafeexambrowser', array_key_first($options));
self::add_help_button($quizform, $mform, 'seb_requiresafeexambrowser');
if (self::is_conflicting_permissions($quizform->get_context())) {
@@ -549,6 +550,11 @@ public static function is_conflicting_permissions(\context $context) {
return true;
}
+ if (!self::can_unrequire($context) &&
+ $settings->get('requiresafeexambrowser') == self::USE_SEB_NO) {
+ return true;
+ }
+
return false;
}
@@ -559,7 +565,11 @@ public static function is_conflicting_permissions(\context $context) {
* @return array
*/
public static function get_requiresafeexambrowser_options(\context $context): array {
- $options[self::USE_SEB_NO] = get_string('no');
+ $options = [];
+
+ if (self::can_unrequire($context) || self::is_conflicting_permissions($context)) {
+ $options[self::USE_SEB_NO] = get_string('no');
+ }
if (self::can_configure_manually($context) || self::is_conflicting_permissions($context)) {
$options[self::USE_SEB_CONFIG_MANUALLY] = get_string('seb_use_manually', 'quizaccess_seb');
@@ -584,7 +594,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) {
@@ -785,6 +795,26 @@ public static function can_change_seb_allowedbrowserexamkeys(\context $context):
return has_capability('quizaccess/seb:manage_seb_allowedbrowserexamkeys', $context);
}
+ /**
+ * Check if the current user can unrequire SEB from quiz.
+ *
+ * @param \context $context Context to check access in.
+ * @return bool
+ */
+ public static function can_unrequire(\context $context): bool {
+ return has_capability('quizaccess/seb:manage_seb_unrequiresafeexambrowser', $context);
+ }
+
+ /**
+ * Check if the current user can unrequire SEB from quiz in the override menu.
+ *
+ * @param \context $context Context to check access in.
+ * @return bool
+ */
+ public static function can_override_unrequire(\context $context): bool {
+ return has_capability('quizaccess/seb:override_seb_unrequiresafeexambrowser', $context);
+ }
+
/**
* Check if the current user can config SEB manually.
*
diff --git a/mod/quiz/accessrule/seb/db/access.php b/mod/quiz/accessrule/seb/db/access.php
index d79df40695928..e8070c1a69e46 100644
--- a/mod/quiz/accessrule/seb/db/access.php
+++ b/mod/quiz/accessrule/seb/db/access.php
@@ -49,6 +49,22 @@
'editingteacher' => CAP_ALLOW
]
],
+ 'quizaccess/seb:manage_seb_unrequiresafeexambrowser' => [
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_MODULE,
+ 'archetypes' => [
+ 'manager' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ ],
+ ],
+ 'quizaccess/seb:override_seb_unrequiresafeexambrowser' => [
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_MODULE,
+ 'archetypes' => [
+ 'manager' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ ],
+ ],
'quizaccess/seb:manage_seb_templateid' => [
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
diff --git a/mod/quiz/accessrule/seb/lang/en/quizaccess_seb.php b/mod/quiz/accessrule/seb/lang/en/quizaccess_seb.php
index c340f295a2f2d..8e233fb979b4e 100644
--- a/mod/quiz/accessrule/seb/lang/en/quizaccess_seb.php
+++ b/mod/quiz/accessrule/seb/lang/en/quizaccess_seb.php
@@ -106,6 +106,8 @@
$string['seb:manage_seb_regexallowed'] = 'Change SEB quiz setting: Regex expressions allowed';
$string['seb:manage_seb_regexblocked'] = 'Change SEB quiz setting: Regex expressions blocked';
$string['seb:manage_seb_requiresafeexambrowser'] = 'Change SEB quiz setting: Require Safe Exam Browser';
+$string['seb:manage_seb_unrequiresafeexambrowser'] = 'Change SEB quiz setting: Do not require Safe Exam Browser';
+$string['seb:override_seb_unrequiresafeexambrowser'] = 'Override SEB quiz setting: Do not require Safe Exam Browser';
$string['seb:manage_seb_showkeyboardlayout'] = 'Change SEB quiz setting: Show keyboard layout';
$string['seb:manage_seb_showreloadbutton'] = 'Change SEB quiz setting: Show reload button';
$string['seb:manage_seb_showsebtaskbar'] = 'Change SEB quiz setting: Show task bar';
diff --git a/mod/quiz/accessrule/seb/tests/settings_provider_test.php b/mod/quiz/accessrule/seb/tests/settings_provider_test.php
index 821d1c9e36aad..0626fc7dbb5d1 100644
--- a/mod/quiz/accessrule/seb/tests/settings_provider_test.php
+++ b/mod/quiz/accessrule/seb/tests/settings_provider_test.php
@@ -656,6 +656,15 @@ public function test_get_requiresafeexambrowser_options($settingcapability): voi
$options = settings_provider::get_requiresafeexambrowser_options($this->context);
+ $this->assertCount(1, $options);
+ $this->assertFalse(array_key_exists(settings_provider::USE_SEB_CONFIG_MANUALLY, $options));
+ $this->assertFalse(array_key_exists(settings_provider::USE_SEB_TEMPLATE, $options));
+ $this->assertFalse(array_key_exists(settings_provider::USE_SEB_UPLOAD_CONFIG, $options));
+ $this->assertTrue(array_key_exists(settings_provider::USE_SEB_CLIENT_CONFIG, $options));
+ $this->assertFalse(array_key_exists(settings_provider::USE_SEB_NO, $options));
+
+ assign_capability('quizaccess/seb:manage_seb_unrequiresafeexambrowser', CAP_ALLOW, $this->roleid, $this->context->id);
+ $options = settings_provider::get_requiresafeexambrowser_options($this->context);
$this->assertCount(2, $options);
$this->assertFalse(array_key_exists(settings_provider::USE_SEB_CONFIG_MANUALLY, $options));
$this->assertFalse(array_key_exists(settings_provider::USE_SEB_TEMPLATE, $options));
diff --git a/mod/quiz/accessrule/seb/version.php b/mod/quiz/accessrule/seb/version.php
index 93d40beabaa19..312231bc2648e 100644
--- a/mod/quiz/accessrule/seb/version.php
+++ b/mod/quiz/accessrule/seb/version.php
@@ -25,7 +25,7 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2024042200;
+$plugin->version = 2024073101;
$plugin->requires = 2024041600;
$plugin->component = 'quizaccess_seb';
$plugin->maturity = MATURITY_STABLE;
diff --git a/mod/quiz/backup/moodle2/backup_quiz_stepslib.php b/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
index c3914d17d3905..89a246917a6c5 100644
--- a/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
+++ b/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
@@ -71,7 +71,7 @@ protected function define_structure() {
$override = new backup_nested_element('override', ['id'], [
'userid', 'groupid', 'timeopen', 'timeclose',
- 'timelimit', 'attempts', 'password']);
+ 'timelimit', 'attempts', 'password', 'sebdata']);
$grades = new backup_nested_element('grades');
diff --git a/mod/quiz/classes/external/save_overrides.php b/mod/quiz/classes/external/save_overrides.php
index 40c5029d7ba42..e4e36bdd18e0e 100644
--- a/mod/quiz/classes/external/save_overrides.php
+++ b/mod/quiz/classes/external/save_overrides.php
@@ -46,6 +46,7 @@ public static function execute_parameters(): external_function_parameters {
'timelimit' => new external_value(PARAM_INT, 'Quiz override time limit', VALUE_DEFAULT, null),
'attempts' => new external_value(PARAM_INT, 'Quiz override attempt count', VALUE_DEFAULT, null),
'password' => new external_value(PARAM_TEXT, 'Quiz override password', VALUE_DEFAULT, null),
+ 'sebdata' => new external_value(PARAM_TEXT, 'Quiz override SEB settings', VALUE_DEFAULT, null),
]);
return new external_function_parameters([
diff --git a/mod/quiz/classes/form/edit_override_form.php b/mod/quiz/classes/form/edit_override_form.php
index 4f5c7c6786074..5175bd875a98c 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,177 @@ 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', get_string('enabled', 'quizaccess_seb'));
+ $mform->setDefault('enableseboverride', $this->sebdata['enableseboverride'] ?? false);
+
+ // ... "Require the use of Safe Exam Browser"
+ if (settings_provider::can_override_unrequire($this->context)) {
+ $requireseboptions[settings_provider::USE_SEB_NO] = get_string('no');
+ }
+
+ if (settings_provider::can_configure_manually($this->context) || settings_provider::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) || settings_provider::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..7ebc910eaf077 100644
--- a/mod/quiz/classes/local/override_manager.php
+++ b/mod/quiz/classes/local/override_manager.php
@@ -32,7 +32,34 @@
*/
class override_manager {
/** @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', 'enableseboverride'];
+
+ /** @var array quiz SEB setting keys that can be overwritten **/
+ private const OVERRIDEABLE_QUIZ_SEB_SETTINGS = [
+ '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',
+ ];
/**
* Create override manager
@@ -56,6 +83,15 @@ public function __construct(
}
}
+ /**
+ * Retrieve list of overridable quiz settings.
+ *
+ * @return array of quiz settings
+ */
+ private static function get_overridable_quiz_settings() {
+ return [...self::OVERRIDEABLE_QUIZ_SETTINGS, ...self::OVERRIDEABLE_QUIZ_SEB_SETTINGS];
+ }
+
/**
* Returns all overrides for the linked quiz.
*
@@ -88,7 +124,7 @@ 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);
+ }, [...self::OVERRIDEABLE_QUIZ_SETTINGS]);
if (!in_array(true, $keysthatareset)) {
$errors['general'][] = new \lang_string('nooverridedata', 'quiz');
@@ -215,7 +251,7 @@ private static function validate_against_existing_record(int $existingid, \stdCl
*/
public function parse_formdata(array $formdata): array {
// Get the data from the form that we want to update.
- $settings = array_intersect_key($formdata, array_flip(self::OVERRIDEABLE_QUIZ_SETTINGS));
+ $settings = array_intersect_key($formdata, array_flip(self::get_overridable_quiz_settings()));
// Remove values that are the same as currently in the quiz.
$settings = $this->clear_unused_values($settings);
@@ -238,6 +274,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(['enableseboverride', ...self::OVERRIDEABLE_QUIZ_SEB_SETTINGS]));
+ $sebdata = serialize($sebdata);
+
+ $datatoset['sebdata'] = $sebdata;
$datatoset['quiz'] = $this->quiz->id;
// Validate the data is OK.
@@ -569,6 +611,7 @@ private function clear_unused_values(array $formdata): array {
return $formdata;
}
+
/**
* Deletes orphaned group overrides in a given course.
* Note - permissions are not checked and events are not logged for performance reasons.
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..efab7764ae6a4 100644
--- a/mod/quiz/lib.php
+++ b/mod/quiz/lib.php
@@ -219,6 +219,46 @@ function quiz_delete_instance($id) {
return true;
}
+/**
+ * Checks if the user has overrides for the quiz whether individually or in a group.
+ *
+ * @param int $quizid The quiz object.
+ * @return bool
+ */
+function quiz_has_user_overrides($quizid) {
+ global $DB, $USER;
+ $userid = $USER->id;
+
+ $quiz = $DB->get_record('quiz', ['id' => $quizid]);
+
+ // No quiz, no override.
+ if (!$quiz) {
+ return false;
+ }
+
+ // Check for user override.
+ $useroverride = $DB->record_exists('quiz_overrides', ['quiz' => $quiz->id, 'userid' => $userid]);
+ if ($useroverride) {
+ return true;
+ }
+
+ // Check for group overrides.
+ $groupings = groups_get_user_groups($quiz->course, $userid);
+ if (!empty($groupings[0])) {
+ list($extra, $params) = $DB->get_in_or_equal(array_values($groupings[0]));
+ $sql = "SELECT * FROM {quiz_overrides}
+ WHERE groupid $extra AND quiz = ?";
+ $params[] = $quiz->id;
+ $gpoverrides = $DB->record_exists_sql($sql, $params);
+
+ if ($gpoverrides) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
/**
* Updates a quiz object with override information for a user.
*
@@ -326,6 +366,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..f9d272067186c 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';