Skip to content

Commit

Permalink
MDL-80945 quiz: Add SEB options to override settings.
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Kotlyar committed Jul 30, 2024
1 parent 7d7a871 commit e77f0a1
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 11 deletions.
20 changes: 20 additions & 0 deletions mod/quiz/accessrule/seb/classes/seb_access_manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

use context_module;
use mod_quiz\quiz_settings;
use mod_quiz\local\override_manager;

defined('MOODLE_INTERNAL') || die();

Expand Down Expand Up @@ -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 ($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});
}
}
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion mod/quiz/accessrule/seb/classes/settings_provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
163 changes: 163 additions & 0 deletions mod/quiz/classes/form/edit_override_form.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use moodle_url;
use moodleform;
use stdClass;
use quizaccess_seb\{seb_quiz_settings,settings_provider};

defined('MOODLE_INTERNAL') || die();

Expand Down Expand Up @@ -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.
*
Expand All @@ -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);
}
Expand Down Expand Up @@ -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'));
Expand All @@ -239,6 +247,161 @@ 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_seb_settings_locked($this->quiz->id) || settings_provider::is_conflicting_permissions($this->context)) {
// Freeze common quiz settings.
$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.
*
Expand Down
61 changes: 52 additions & 9 deletions mod/quiz/classes/local/override_manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,42 @@
* @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_requiresafeexambrowser',
'seb_showsebdownloadlink',
'seb_linkquitseb',
'seb_userconfirmquit',
'seb_allowuserquitseb',
'seb_quitpassword',
'seb_allowreloadinexam',
'seb_showsebtaskbar',
'seb_showreloadbutton',
'seb_showtime',
'seb_showkeyboardlayout',
'seb_showwificontrol',
'seb_enableaudiocontrol',
'seb_muteonstartup',
'seb_allowspellchecking',
'seb_activateurlfiltering',
'seb_filterembeddedcontent',
'seb_expressionsallowed',
'seb_regexallowed',
'seb_expressionsblocked',
'seb_regexblocked',
'seb_allowedbrowserexamkeys',
];

/** @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
Expand Down Expand Up @@ -88,10 +122,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.
Expand Down Expand Up @@ -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(self::OVERRIDEABLE_QUIZ_SEB_SETTINGS));
$sebdata = serialize($sebdata);

$datatoset['sebdata'] = $sebdata;
$datatoset['quiz'] = $this->quiz->id;

// Validate the data is OK.
Expand Down Expand Up @@ -557,12 +599,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;
}
}

Expand Down Expand Up @@ -600,4 +639,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;
}
}
1 change: 1 addition & 0 deletions mod/quiz/db/install.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
<FIELD NAME="timelimit" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Time limit in seconds. Can be null, in which case the quiz default is used."/>
<FIELD NAME="attempts" TYPE="int" LENGTH="6" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Quiz password. Can be null, in which case the quiz default is used."/>
<FIELD NAME="sebdata" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Serialized array of SEB settings"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
Expand Down
15 changes: 15 additions & 0 deletions mod/quiz/db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
8 changes: 8 additions & 0 deletions mod/quiz/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,14 @@ function quiz_update_effective_access($quiz, $userid) {
}
}

// Merge SEB override settings if available.
$seboverride = $override->sebdata ? unserialize($override->sebdata) : null;
if (!empty($seboverride) && !!$seboverride['enableseboverride']) {
foreach ($seboverride as $key => $value) {
$quiz->{$key} = $value;
}
}

return $quiz;
}

Expand Down
9 changes: 9 additions & 0 deletions mod/quiz/overrideedit.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
Loading

0 comments on commit e77f0a1

Please sign in to comment.