mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 08:56:36 +02:00
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:
parent
139a0ad5f0
commit
2499276c65
9 changed files with 117 additions and 3 deletions
8
.upgradenotes/MDL-83784-2025021310342487.yml
Normal file
8
.upgradenotes/MDL-83784-2025021310342487.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
issueNumber: MDL-83784
|
||||
notes:
|
||||
core_question:
|
||||
- message: >-
|
||||
Question bank Condition classes can now implement a function called
|
||||
"filter_invalid_values($filterconditions)" to remove anything from the
|
||||
filterconditions array which is invalid or should not be there.
|
||||
type: improved
|
|
@ -1096,6 +1096,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);
|
||||
|
|
|
@ -44,6 +44,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;
|
||||
|
||||
|
@ -1801,6 +1807,10 @@ 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 (!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);
|
||||
|
@ -1811,6 +1821,7 @@ class structure {
|
|||
}
|
||||
$this->randomslottags[$slotid] = implode(', ', $slottagnames);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -59,6 +60,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;
|
||||
|
@ -135,9 +137,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);
|
||||
|
@ -154,7 +157,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();
|
||||
|
|
|
@ -556,6 +556,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';
|
||||
|
|
|
@ -82,3 +82,22 @@ 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"
|
||||
Then I click on "Switch bank" "button"
|
||||
And I click on "Qbank 1" "link" in the "Select question bank" "dialogue"
|
||||
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 "Qbank 1" "core_question > question categories" page
|
||||
And I open the action menu in "Used category" "list_item"
|
||||
And I choose "Delete" in the open action menu
|
||||
And I click on "Delete" "button" in the "Delete" "dialogue"
|
||||
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"
|
||||
|
|
|
@ -342,4 +342,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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -205,6 +205,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.
|
||||
*
|
||||
|
|
|
@ -147,4 +147,24 @@ class filter_condition_manager {
|
|||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue