Skip to content

Commit

Permalink
MDL-80945 quiz: Add hooks to override access settings.
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Kotlyar committed Dec 16, 2024
1 parent 15546be commit 4c05e31
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 22 deletions.
144 changes: 144 additions & 0 deletions mod/quiz/classes/access_manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
namespace mod_quiz;

use core_component;
use mod_quiz\form\edit_override_form;
use mod_quiz\form\preflight_check_form;
use mod_quiz\local\access_rule_base;
use mod_quiz\output\renderer;
Expand Down Expand Up @@ -103,6 +104,17 @@ protected static function get_rule_classes(): array {
return core_component::get_plugin_list_with_class('quizaccess', '', 'rule.php');
}

/**
* Get the names of overridable rule classes.
*
* @return array of class names.
*/
protected static function get_overridable_rule_classes(): array {
return array_filter(self::get_rule_classes(), function($rule) {
return in_array(\mod_quiz\local\rule_overridable::class, class_implements($rule));
});
}

/**
* Add any form fields that the access rules require to the settings form.
*
Expand Down Expand Up @@ -279,6 +291,138 @@ public static function load_quiz_and_settings(int $quizid): stdClass {
return $quiz;
}

/**
* Add any form fields that the access rules require to the override form.
*
* Note that the standard plugins do not use this mechanism, becuase all their
* settings are stored in the quiz table.
*
* @param edit_override_form $quizform the quiz override settings form being built.
* @param MoodleQuickForm $mform the wrapped MoodleQuickForm.
* @return boolean return true if fields have been added.
*/
public static function add_override_form_fields(
edit_override_form $quizform, MoodleQuickForm $mform): bool {
$fieldsadded = false;
foreach (self::get_overridable_rule_classes() as $rule) {
$element = $rule::get_override_form_section_header();
$mform->addElement('header', $element['name'], $element['title']);
$mform->setExpanded($element['name'], $rule::get_override_form_section_expand($quizform));
$rule::add_override_form_fields($quizform, $mform);
$fieldsadded = true;
}
return $fieldsadded;
}

/**
* Validate the data from any form fields added using {@see add_override_form_fields()}.
*
* @param array $errors the errors found so far.
* @param array $data the submitted form data.
* @param array $files information about any uploaded files.
* @param edit_override_form $quizform the quiz override form object.
* @return array $errors the updated $errors array.
*/
public static function validate_override_form_fields(array $errors,
array $data, array $files, edit_override_form $quizform): array {
foreach (self::get_overridable_rule_classes() as $rule) {
$errors = $rule::validate_override_form_fields($errors, $data, $files, $quizform);
}
return $errors;
}

/**
* Save any submitted settings when the quiz override settings form is submitted.
*
* @param array $override data from the override form.
* @return void
*/
public static function save_override_settings(array $override): void {
foreach (self::get_overridable_rule_classes() as $rule) {
$rule::save_override_settings($override);
}
}

/**
* Delete any rule-specific override settings when the quiz override is deleted.
*
* @param int $quizid all overrides being deleted should belong to the same quiz.
* @param array $overrides an array of override objects to be deleted.
* @return void
*/
public static function delete_override_settings($quizid, $overrides): void {
foreach (self::get_overridable_rule_classes() as $rule) {
$rule::delete_override_settings($quizid, $overrides);
}
}

/**
* Get components of the SQL query to fetch the access rule components' override
* settings. To be used as part of a quiz_override query to reference.
*
* @param string $overridetablename Name of the table to reference for joins.
* @return array 'selects', 'joins' and 'params'.
*/
public static function get_override_settings_sql($overridetablename = 'quiz_overrides'): array {
$allfields = [];
$alljoins = [];
$allparams = [];

foreach (self::get_overridable_rule_classes() as $rule) {
[$fields, $joins, $params] = $rule::get_override_settings_sql($overridetablename);
$fields && $allfields[] = $fields;
$joins && $alljoins[] = $joins;
$params && $allparams += $params;
}

$allfields = implode(', ', $allfields);
$alljoins = implode(' ', $alljoins);

return [$allfields, $alljoins, $allparams];
}

/**
* Retrieve all keys of fields to be used in the override form.
*
* @return array
*/
public static function get_override_setting_keys(): array {
$keys = [];
foreach (self::get_overridable_rule_classes() as $rule) {
$keys += $rule::get_override_setting_keys();
}
return $keys;
}

/**
* Retrieve keys of fields that are required to be filled in.
*
* @return array
*/
public static function get_override_required_setting_keys(): array {
$keys = [];
foreach (self::get_overridable_rule_classes() as $rule) {
$keys += $rule::get_override_required_setting_keys();
}
return $keys;
}

/**
* Update fields and values of the override table using the override settings.
*
* @param object $override the override data to use to update the $fields and $values.
* @param array $fields the fields to populate.
* @param array $values the fields to populate.
* @param context $context the context of which the override is being applied to.
* @return array
*/
public static function add_override_table_fields($override, $fields, $values, $context): array {
foreach (self::get_overridable_rule_classes() as $rule) {
[$fields, $values] = $rule::add_override_table_fields($override, $fields, $values, $context);
}
return [$fields, $values];
}

/**
* Get an array of the class names of all the active rules.
*
Expand Down
33 changes: 33 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 mod_quiz\access_manager;

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

Expand Down Expand Up @@ -84,6 +85,30 @@ public function __construct(moodle_url $submiturl,
parent::__construct($submiturl);
}

/**
* Return the course context for new modules, or the module context for existing modules.
* @return context_module
*/
public function get_context(): context_module {
return $this->context;
}

/**
* Get the quiz override ID.
* @return int
*/
public function get_overrideid(): int {
return $this->overrideid;
}

/**
* Get the quiz object.
* @return \stdClass
*/
public function get_quiz(): \stdClass {
return $this->quiz;
}

protected function definition() {
global $DB;

Expand Down Expand Up @@ -224,6 +249,11 @@ protected function definition() {
$mform->addHelpButton('attempts', 'attempts', 'quiz');
$mform->setDefault('attempts', $this->quiz->attempts);

// Access-rule fields.
if (access_manager::add_override_form_fields($this, $mform)) {
$mform->closeHeaderBefore('resetbutton');
}

// Submit buttons.
$mform->addElement('submit', 'resetbutton',
get_string('reverttodefaults', 'quiz'));
Expand Down Expand Up @@ -289,6 +319,9 @@ public function validation($data, $files): array {
}
}

// Apply access-rule validation.
$errors = access_manager::validate_override_form_fields($errors, $data, $files, $this);

return $errors;
}
}
62 changes: 58 additions & 4 deletions mod/quiz/classes/local/override_manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

namespace mod_quiz\local;

use mod_quiz\access_manager;
use mod_quiz\event\group_override_created;
use mod_quiz\event\group_override_deleted;
use mod_quiz\event\group_override_updated;
Expand Down Expand Up @@ -87,9 +88,11 @@ public function validate_data(array $formdata): array {
$errors = [];

// Ensure at least one of the overrideable settings is set.
$accessrulerequiredkeys = access_manager::get_override_required_setting_keys();
$requiredkeys = array_merge(self::OVERRIDEABLE_QUIZ_SETTINGS, $accessrulerequiredkeys);
$keysthatareset = array_map(function ($key) use ($formdata) {
return isset($formdata->$key) && !is_null($formdata->$key);
}, self::OVERRIDEABLE_QUIZ_SETTINGS);
}, $requiredkeys);

if (!in_array(true, $keysthatareset)) {
$errors['general'][] = new \lang_string('nooverridedata', 'quiz');
Expand Down Expand Up @@ -207,7 +210,7 @@ private static function validate_against_existing_record(int $existingid, \stdCl
}

/**
* Parses the formdata by finding only the OVERRIDEABLE_QUIZ_SETTINGS,
* Parses the formdata by finding the OVERRIDEABLE_QUIZ_SETTINGS and from overridable access-rule components,
* clearing any values that match the existing quiz, and re-adds the user or group id.
*
* @param array $formdata data usually from moodleform or webservice call.
Expand All @@ -216,7 +219,9 @@ 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));
$accessrulekeys = access_manager::get_override_setting_keys();
$keys = array_merge(self::OVERRIDEABLE_QUIZ_SETTINGS, $accessrulekeys);
$settings = array_intersect_key($formdata, array_flip($keys));

// Remove values that are the same as currently in the quiz.
$settings = $this->clear_unused_values($settings);
Expand Down Expand Up @@ -255,6 +260,7 @@ public function save_override(array $formdata): int {
} else {
$id = $DB->insert_record('quiz_overrides', $datatoset);
}
$datatoset['overrideid'] = $id;

$userid = $datatoset['userid'] ?? null;
$groupid = $datatoset['groupid'] ?? null;
Expand Down Expand Up @@ -283,6 +289,9 @@ public function save_override(array $formdata): int {
quiz_update_events($this->quiz, (object) $datatoset);
}

// Update access-rule override data.
access_manager::save_override_settings($datatoset);

return $id;
}

Expand Down Expand Up @@ -330,7 +339,6 @@ public function delete_overrides_by_id(array $ids, bool $shouldlog = true): void
$this->delete_overrides($records, $shouldlog);
}


/**
* Builds sql and parameters to find overrides in quiz with the given ids
*
Expand Down Expand Up @@ -390,6 +398,8 @@ public function delete_overrides(array $overrides, bool $shouldlog = true): void
$this->fire_deleted_event($override->id, $userid, $groupid);
}
}

access_manager::delete_override_settings($this->quiz->id, $overrides);
}

/**
Expand Down Expand Up @@ -627,4 +637,48 @@ public static function delete_orphaned_group_overrides_in_course(int $courseid):
}
return array_unique(array_column($records, 'quiz'));
}

/**
* Checks if the user has overrides for the quiz whether individually or in a group.
*
* @param int $quizid The quiz object.
* @return stdClass|false
*/
public static function get_quiz_override($quizid) {
global $DB, $USER;

// No quiz, no override.
if (!($quiz = $DB->get_record('quiz', ['id' => $quizid]))) {
return false;
}

// SQL components to include quiz_access subplugin override fields.
[$selects, $joins, $params] = access_manager::get_override_settings_sql('o');
$selects = $selects ? ", {$selects}" : '';

// Check for user override.
$sql = "SELECT o.*{$selects}
FROM {quiz_overrides} o {$joins}
WHERE o.quiz = ? AND o.userid = ?";
$userparams = array_merge($params, [$quiz->id, $USER->id]);
$useroverride = $DB->get_record_sql($sql, $userparams);
if ($useroverride) {
return $useroverride;
}

// Check for group overrides.
$groupings = groups_get_user_groups($quiz->course, $USER->id);
if (!empty($groupings[0])) {
list($insql, $inparams) = $DB->get_in_or_equal(array_values($groupings[0]));
$sql = "SELECT o.*{$selects}
FROM {quiz_overrides} o
{$joins}
WHERE groupid {$insql} AND quiz = ?";
$groupparams = array_merge($params, $inparams);
$groupparams[] = $quiz->id;
return $DB->get_record_sql($sql, $groupparams);
}

return false;
}
}
Loading

0 comments on commit 4c05e31

Please sign in to comment.