Skip to content

Commit

Permalink
MDL-80945 quizaccess_seb: Implement hooks to override SEB settings.
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Kotlyar committed Dec 18, 2024
1 parent 77fb322 commit 60d727d
Show file tree
Hide file tree
Showing 16 changed files with 1,347 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,43 @@ protected function define_quiz_subplugin_structure() {
// Save the settings.
$subpluginquizsettings = new backup_nested_element('quizaccess_seb_quizsettings', null, $settingskeys);

// Save the overrides.
$overridekeys = [
'id',
'overrideid',
'templateid',
'enabled',
'requiresafeexambrowser',
'showsebtaskbar',
'showwificontrol',
'showreloadbutton',
'showtime',
'showkeyboardlayout',
'allowuserquitseb',
'quitpassword',
'linkquitseb',
'userconfirmquit',
'enableaudiocontrol',
'muteonstartup',
'allowspellchecking',
'allowreloadinexam',
'activateurlfiltering',
'filterembeddedcontent',
'expressionsallowed',
'regexallowed',
'expressionsblocked',
'regexblocked',
'allowedbrowserexamkeys',
'showsebdownloadlink',
'timecreated',
];
$subpluginoverrides = new backup_nested_element('quizaccess_seb_override', null, $overridekeys);

// Connect XML elements into the tree.
$subplugin->add_child($subpluginwrapper);
$subpluginwrapper->add_child($subpluginquizsettings);
$subpluginquizsettings->add_child($subplugintemplatesettings);
$subpluginwrapper->add_child($subpluginoverrides);

// Set source to populate the settings data by referencing the ID of quiz being backed up.
$subpluginquizsettings->set_source_table(quizaccess_seb\seb_quiz_settings::TABLE, ['quizid' => $quizid]);
Expand All @@ -84,6 +117,15 @@ protected function define_quiz_subplugin_structure() {
$params = ['id' => '../templateid'];
$subplugintemplatesettings->set_source_table(\quizaccess_seb\template::TABLE, $params);

// Set source to populate the override data by referencing the ID of quiz being backed up.
$sql = "SELECT qso.*
FROM {quizaccess_seb_override} qso
JOIN {quiz_overrides} qo
ON qo.id = qso.overrideid
AND qo.quiz = ?";
$params = [$quizid];
$subpluginoverrides->set_source_sql($sql, $params);

return $subplugin;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_quizaccess_seb_subplugin extends restore_mod_quiz_access_subplugin {
/**
* A list of quizaccess_seb_override records to be restored.
* @var array
*/
protected $overrides = [];

/**
* Provides path structure required to restore data for seb quiz access plugin.
Expand All @@ -56,6 +61,10 @@ protected function define_quiz_subplugin_structure() {
$path = $this->get_pathfor('/quizaccess_seb_quizsettings/quizaccess_seb_template');
$paths[] = new restore_path_element('quizaccess_seb_template', $path);

// Overrides.
$path = $this->get_pathfor('/quizaccess_seb_override');
$paths[] = new restore_path_element('quizaccess_seb_override', $path);

return $paths;
}

Expand Down Expand Up @@ -128,5 +137,43 @@ public function process_quizaccess_seb_template($data) {
}
}

}
/**
* Process the restored data for the quizaccess_seb_override table.
*
* @param stdClass $data Data for quizaccess_seb_override retrieved from backup xml.
*/
public function process_quizaccess_seb_override($data) {
global $DB, $USER;

// Process quizsettings.
$data = (object) $data;
unset($data->id);
$data->timecreated = $data->timemodified = time();
$data->usermodified = $USER->id;

// Do not use template if it is no longer enabled.
if ($data->requiresafeexambrowser == settings_provider::USE_SEB_TEMPLATE &&
!$DB->record_exists(template::TABLE, ['id' => $data->templateid, 'enabled' => '1'])) {
$data->templateid = 0;
$data->requiresafeexambrowser = settings_provider::USE_SEB_NO;
}

// We wait until the quiz is complete before we restore as we need to get the new quiz_override IDs.
$this->overrides[] = $data;
}

/**
* Maps the new override IDs to the quizaccess_seb_override entries.
*
* @return void
*/
public function after_restore_quiz() {
global $DB;
foreach ($this->overrides as $data) {
$newoverrideid = $this->get_mappingid('quiz_override', $data->overrideid);
$data->overrideid = $newoverrideid;
$DB->insert_record('quizaccess_seb_override', $data);
}
}

}
11 changes: 11 additions & 0 deletions mod/quiz/accessrule/seb/classes/privacy/provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ class provider implements
* @return collection Collection of metadata.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_database_table(
'quizaccess_seb_override',
[
'overrideid' => 'privacy:metadata:quizaccess_seb_override:overrideid',
'usermodified' => 'privacy:metadata:quizaccess_seb_override:usermodified',
'timecreated' => 'privacy:metadata:quizaccess_seb_override:timecreated',
'timemodified' => 'privacy:metadata:quizaccess_seb_override:timemodified',
],
'privacy:metadata:quizaccess_seb_override'
);

$collection->add_database_table(
'quizaccess_seb_quizsettings',
[
Expand Down
94 changes: 80 additions & 14 deletions mod/quiz/accessrule/seb/classes/seb_quiz_settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use lang_string;
use moodle_exception;
use moodle_url;
use mod_quiz\local\override_manager;

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

Expand Down Expand Up @@ -193,14 +194,52 @@ protected static function define_properties(): array {
* This method gets data from cache before doing any DB calls.
*
* @param int $quizid Quiz id.
* @return false|\quizaccess_seb\seb_quiz_settings
* @return false|seb_quiz_settings
*/
public static function get_by_quiz_id(int $quizid) {
if ($data = self::get_quiz_settings_cache()->get($quizid)) {
if ($data = self::get_quiz_settings_cache()->get(self::get_cache_key($quizid))) {
return new static(0, $data);
}

return self::get_record(['quizid' => $quizid]);
return self::get_settings(['quizid' => $quizid]) ?: false;
}

/**
* Return an instance by quiz id and apply an override if available. Similar to self::get_records.
*
* @param array $filters conditions used to query seb_quiz_settings.
* @return ?seb_quiz_settings
*/
public static function get_settings(array $filters = []) {
$sebquizsetting = self::get_record($filters) ?: null;

if ($sebquizsetting || $filters['quizid']) {
$quizid = $filters['quizid'] ?? $sebquizsetting->get('quizid');

// Overwrite settings if available.
if ($override = override_manager::get_quiz_override($quizid)) {
// Create blank seb_quiz_settings instance if none exists.
if (!$sebquizsetting) {
$record = (object)[
'quizid' => $quizid,
'cmid' => get_coursemodule_from_instance('quiz', $quizid)->id,
];
$sebquizsetting = new self(0, $record);
}

// Overwrite settings if enabled.
if (isset($override->seb_enabled) && (bool) $override->seb_enabled) {
$prefix = 'seb_';
foreach (array_keys(self::properties_definition()) as $key) {
if (isset($override->{$prefix.$key})) {
$sebquizsetting->set($key, $override->{$prefix.$key});
}
}
}
}
}

return $sebquizsetting;
}

/**
Expand All @@ -210,7 +249,8 @@ 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);
$cachekey = self::get_cache_key($quizid);
$config = self::get_config_cache()->get($cachekey);

if ($config !== false) {
return $config;
Expand All @@ -219,7 +259,7 @@ public static function get_config_by_quiz_id(int $quizid): ?string {
$config = null;
if ($settings = self::get_by_quiz_id($quizid)) {
$config = $settings->get_config();
self::get_config_cache()->set($quizid, $config);
self::get_config_cache()->set($cachekey, $config);
}

return $config;
Expand All @@ -232,7 +272,8 @@ public static function get_config_by_quiz_id(int $quizid): ?string {
* @return string|null
*/
public static function get_config_key_by_quiz_id(int $quizid): ?string {
$configkey = self::get_config_key_cache()->get($quizid);
$cachekey = self::get_cache_key($quizid);
$configkey = self::get_config_key_cache()->get($cachekey);

if ($configkey !== false) {
return $configkey;
Expand All @@ -241,7 +282,7 @@ public static function get_config_key_by_quiz_id(int $quizid): ?string {
$configkey = null;
if ($settings = self::get_by_quiz_id($quizid)) {
$configkey = $settings->get_config_key();
self::get_config_key_cache()->set($quizid, $configkey);
self::get_config_key_cache()->set($cachekey, $configkey);
}

return $configkey;
Expand Down Expand Up @@ -294,19 +335,31 @@ protected function after_update($result) {
* Helper method to execute common stuff after create and update.
*/
private function after_save() {
self::get_quiz_settings_cache()->set($this->get('quizid'), $this->to_record());
self::get_config_cache()->set($this->get('quizid'), $this->config);
self::get_config_key_cache()->set($this->get('quizid'), $this->configkey);
$quizid = $this->get('quizid');
$cachekey = self::get_cache_key($quizid);
self::get_quiz_settings_cache()->set($cachekey, $this->to_record());
self::get_config_cache()->set($cachekey, $this->config);
self::get_config_key_cache()->set($cachekey, $this->configkey);
}

/**
* Removes unnecessary stuff from db.
*/
protected function before_delete() {
$key = $this->get('quizid');
self::get_quiz_settings_cache()->delete($key);
self::get_config_cache()->delete($key);
self::get_config_key_cache()->delete($key);
$cachekey = self::get_cache_key($this->get('quizid'));
self::delete_cache($cachekey);
}

/**
* Removes cached SEB data.
*
* @param string $cachekey The ID of of a quiz, or combined with the ID of an override e.g. '12' or '12-1'.
* @return void
*/
public static function delete_cache($cachekey): void {
self::get_quiz_settings_cache()->delete($cachekey);
self::get_config_cache()->delete($cachekey);
self::get_config_key_cache()->delete($cachekey);
}

/**
Expand Down Expand Up @@ -437,6 +490,19 @@ public function get_config(): ?string {
return $this->config;
}

/**
* Return key to index cache. Takes override into account.
*
* @param int $quizid Quiz id.
* @return string
*/
private static function get_cache_key($quizid) {
if ($override = override_manager::get_quiz_override($quizid)) {
return "$quizid-{$override->id}";
}
return $quizid;
}

/**
* Case for USE_SEB_NO.
*/
Expand Down
39 changes: 35 additions & 4 deletions mod/quiz/accessrule/seb/classes/settings_provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,16 +188,18 @@ 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())
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())) {
Expand Down Expand Up @@ -556,6 +558,11 @@ public static function is_conflicting_permissions(\context $context) {
return true;
}

if (!self::can_donotrequire($context) &&
$settings->get('requiresafeexambrowser') == self::USE_SEB_NO) {
return true;
}

return false;
}

Expand All @@ -566,7 +573,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_donotrequire($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');
Expand All @@ -593,7 +604,7 @@ public static function get_requiresafeexambrowser_options(\context $context): ar
* Returns a list of templates.
* @return array
*/
protected static function get_template_options($cmid): array {
public static function get_template_options($cmid): array {
$templates = [];
$templatetable = template::TABLE;
$sebquizsettingstable = seb_quiz_settings::TABLE;
Expand Down Expand Up @@ -832,6 +843,26 @@ public static function can_configure_manually(\context $context): bool {
return false;
}

/**
* Check if the current user can not require SEB for a quiz.
*
* @param \context $context Context to check access in.
* @return bool
*/
public static function can_donotrequire(\context $context): bool {
return has_capability('quizaccess/seb:manage_seb_donotrequiresafeexambrowser', $context);
}

/**
* Check if the current user can not require SEB for a quiz in the override menu.
*
* @param \context $context Context to check access in.
* @return bool
*/
public static function can_override_donotrequire(\context $context): bool {
return has_capability('quizaccess/seb:manage_seb_donotrequiresafeexambrowser_override', $context);
}

/**
* Check if the current user can manage provided SEB setting.
*
Expand Down
Loading

0 comments on commit 60d727d

Please sign in to comment.