MDL-83784 mod_quiz: Fix quiz unusable if question category missing.

Random questions set to question categories which no longer exist
was throwing an error making it impossible to use or edit the quiz
to fix it. This will now allow the user to view the questions and
edit the quiz in order to fix the problem of the missing category.
This commit is contained in:
Conn Warwicker
2025-02-13 15:48:22 +00:00
parent 1c7ac7057b
commit 17c3e8ecc8
8 changed files with 107 additions and 3 deletions
@@ -1067,6 +1067,13 @@ class edit_renderer extends \plugin_renderer_base {
$temp->questiontext = '';
$temp->name = $structure->describe_random_slot($slot->id);
$instancename = quiz_question_tostring($temp);
if (strpos($instancename, structure::MISSING_QUESTION_CATEGORY_PLACEHOLDER) !== false) {
$label = html_writer::span(
get_string('missingcategory', 'mod_quiz'),
'badge bg-danger text-white h-50'
);
$instancename = str_replace(structure::MISSING_QUESTION_CATEGORY_PLACEHOLDER, $label, $instancename);
}
$configuretitle = get_string('configurerandomquestion', 'quiz');
$qtype = \question_bank::get_qtype($question->qtype, false);
+13
View File
@@ -42,6 +42,12 @@ use stdClass;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class structure {
/**
* Placeholder string used when a question category is missing.
*/
const MISSING_QUESTION_CATEGORY_PLACEHOLDER = 'missing_question_category';
/** @var quiz_settings the quiz this is the structure of. */
protected $quizobj = null;
@@ -1759,6 +1765,13 @@ class structure {
// Now, put the data required for each slot into $this->randomslotcategories and $this->randomslottags.
foreach ($randomcategoriesandtags as $slotid => $catandtags) {
$qcategoryid = $catandtags['cat']['values'];
// If the category does not exist, replace with a temporary placeholder.
if (!array_key_exists($qcategoryid, $categories)) {
$this->randomslotcategories[$slotid] = self::MISSING_QUESTION_CATEGORY_PLACEHOLDER;
continue;
}
$qcategory = $categories[$qcategoryid];
$includesubcategories = $catandtags['cat']['includesubcategories'];
$this->randomslotcategories[$slotid] = $this->get_used_category_description($qcategory, $includesubcategories);
+5 -3
View File
@@ -23,6 +23,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_question\local\bank\filter_condition_manager;
use core_question\question_reference_manager;
use mod_quiz\quiz_settings;
use mod_quiz\question\bank\random_question_view;
@@ -58,6 +59,7 @@ $setreference = $DB->get_record('question_set_references',
['itemid' => $slot->id, 'component' => 'mod_quiz', 'questionarea' => 'slot']);
$filterconditions = json_decode($setreference->filtercondition, true);
$filterconditions = question_reference_manager::convert_legacy_set_reference_filter_condition($filterconditions);
$filterconditions = filter_condition_manager::filter_invalid_values($filterconditions);
$params = $filterconditions;
$params['cmid'] = $cm->id;
@@ -132,9 +134,10 @@ if ($mform->is_cancelled()) {
redirect($returnurl);
}
$PAGE->set_title('Random question');
$heading = get_string('randomediting', 'mod_quiz');
$PAGE->set_title($heading);
$PAGE->set_heading($COURSE->fullname);
$PAGE->navbar->add('Random question');
$PAGE->navbar->add($heading);
// Custom View.
$questionbank = new random_question_view($contexts, $thispageurl, $course, $cm, $params, $extraparams);
@@ -151,7 +154,6 @@ $PAGE->requires->js_call_amd('mod_quiz/update_random_question_filter_condition',
// Display a heading, question editing form.
echo $OUTPUT->header();
$heading = get_string('randomediting', 'mod_quiz');
echo $OUTPUT->heading_with_help($heading, 'randomquestion', 'mod_quiz');
echo $updateform;
echo $OUTPUT->footer();
+1
View File
@@ -555,6 +555,7 @@ $string['maxmarks_help'] = 'The maximum mark available for each question.';
$string['min'] = 'Min';
$string['minutes'] = 'Minutes';
$string['missingcategory'] = 'Missing question category';
$string['missingcorrectanswer'] = 'Correct answer must be specified';
$string['missingitemtypename'] = 'Missing name';
$string['missingquestion'] = 'This question no longer seems to exist';
@@ -71,3 +71,18 @@ Feature: Moving a question to another category should not affect random question
And I should see "I was edited" in the "Used category new" "list_item"
And I am on the "Quiz 1" "mod_quiz > Edit" page
And I should see "Random (Used category new) based on filter condition" on quiz page "1"
@javascript
Scenario: A random question with an invalid category should still be editable
Given I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1"
When I open the "last" add to quiz menu
And I follow "a random question"
And I apply question bank filter "Category" with value "Used category"
And I press "Add random question"
And I should see "Random (Used category) based on filter condition" on quiz page "1"
And I am on the "Course 1" "core_question > course question categories" page
And I click on "Delete" "link" in the "Used category" "list_item"
And I press "Save in category"
And I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1"
Then I should not see "Random (Used category) based on filter condition" on quiz page "1"
And I should see "Missing question category" on quiz page "1"
@@ -331,4 +331,35 @@ class category_condition extends condition {
public function is_required(): bool {
return true;
}
#[\Override]
public function filter_invalid_values(array $filterconditions): array {
global $DB;
$defaultcatid = explode(',', $filterconditions['cat'])[0];
[$insql, $inparams] = $DB->get_in_or_equal($filterconditions['filter']['category']['values']);
$categories = $DB->get_records_select('question_categories', "id {$insql}",
$inparams, null, 'id');
$categoryids = array_keys($categories);
foreach ($filterconditions['filter']['category']['values'] as $key => $catid) {
// Check that the category still exists, and if not, remove it from the conditions.
if (!in_array($catid, $categoryids)) {
unset($filterconditions['filter']['category']['values'][$key]);
}
}
// If we now don't have any valid categories, use the default loaded from the page.
if (count($filterconditions['filter']['category']['values']) === 0) {
$filterconditions['filter']['category']['values'] = [$defaultcatid];
}
return $filterconditions;
}
}
+15
View File
@@ -200,6 +200,21 @@ abstract class condition {
];
}
/**
* Method to be overridden in condition classes to filter out anything invalid from the filterconditions array.
*
* This can be applied anywhere where the $filterconditions array exists, to let condition plugins remove elements
* from the array, based on their own internal logic/validation. For example, this is used on the
* /mod/quiz/editrandom.php page to filter out question categories which no longer exist, which previously
* broke the editrandom page.
*
* @param array $filterconditions
* @return array
*/
public function filter_invalid_values(array $filterconditions): array {
return $filterconditions;
}
/**
* Given an array of filters, pick the entry that matches the condition key and return it.
*
@@ -117,4 +117,24 @@ class filter_condition_manager {
}
return $filters;
}
/**
* Filter out invalid values from the filterconditions array,
*
* @param array $filterconditions
* @return array
* @throws \dml_exception
*/
public static function filter_invalid_values(array $filterconditions): array {
$classes = self::get_condition_classes();
foreach ($classes as $class) {
$condition = new $class();
$filterconditions = $condition->filter_invalid_values($filterconditions);
}
return $filterconditions;
}
}