MDL-84591 questions: Save question in use when a course is deleted
question_category_delete_safe checks if questions in the category to be deleted are in use, and if so, saves them to a new qbank instance to avoid breaking existing quizzes. However, if the whole course is being deleted, the qbank is created in the current course as question_category_delete_safe does not know is is about to be deleted, and the questions are lost when the deletion occurs. This change passes down a flag to question_category_delete_safe so it knows it was called as part of a whole course deletion, and moves saved questions to the site course instead of the current course.
This commit is contained in:
+3
-2
@@ -4735,11 +4735,12 @@ function delete_course($courseorid, $showfeedback = true) {
|
||||
* @param int $courseid The id of the course that is being deleted
|
||||
* @param bool $showfeedback Whether to display notifications of each action the function performs.
|
||||
* @param array $options extra options
|
||||
* @param bool $coursedeletion Are we calling this as part of deleting the course?
|
||||
* @return bool true if all the removals succeeded. false if there were any failures. If this
|
||||
* method returns false, some of the removals will probably have succeeded, and others
|
||||
* failed, but you have no way of knowing which.
|
||||
*/
|
||||
function remove_course_contents($courseid, $showfeedback = true, ?array $options = null) {
|
||||
function remove_course_contents($courseid, $showfeedback = true, ?array $options = null, bool $coursedeletion = true) {
|
||||
global $CFG, $DB, $OUTPUT;
|
||||
|
||||
require_once($CFG->libdir.'/badgeslib.php');
|
||||
@@ -4822,7 +4823,7 @@ function remove_course_contents($courseid, $showfeedback = true, ?array $options
|
||||
foreach ($instances as $cm) {
|
||||
if ($cm->id) {
|
||||
// Delete activity context questions and question categories.
|
||||
question_delete_activity($cm);
|
||||
question_delete_activity($cm, coursedeletion: $coursedeletion);
|
||||
// Notify the competency subsystem.
|
||||
\core_competency\api::hook_course_module_deleted($cm);
|
||||
|
||||
|
||||
+9
-6
@@ -233,8 +233,9 @@ function match_grade_options($gradeoptionsfull, $grade, $matchgrades = 'error')
|
||||
* NOTE: this function is called from lib/db/upgrade.php
|
||||
*
|
||||
* @param object|core_course_category $category course category object
|
||||
* @param bool $coursedeletion Is the course this category is under being deleted? If so, move saved questions to the site course.
|
||||
*/
|
||||
function question_category_delete_safe($category): void {
|
||||
function question_category_delete_safe($category, bool $coursedeletion = false): void {
|
||||
global $DB;
|
||||
$criteria = ['questioncategoryid' => $category->id];
|
||||
$context = context::instance_by_id($category->contextid, IGNORE_MISSING);
|
||||
@@ -270,7 +271,7 @@ function question_category_delete_safe($category): void {
|
||||
if ($context !== false) {
|
||||
$name = $context->get_context_name();
|
||||
$parentcontext = $context->get_course_context(false);
|
||||
$course = $parentcontext ? get_course($parentcontext->instanceid) : get_site();
|
||||
$course = ($parentcontext && !$coursedeletion) ? get_course($parentcontext->instanceid) : get_site();
|
||||
}
|
||||
$qbank = core_question\local\bank\question_bank_helper::get_default_open_instance_system_type($course, true);
|
||||
question_save_from_deletion(array_keys($questionids), $qbank->context->id, $name, $rescue);
|
||||
@@ -422,9 +423,10 @@ function question_delete_question($questionid): void {
|
||||
* All question categories and their questions are deleted for this context id.
|
||||
*
|
||||
* @param int $contextid The contextid to delete question categories from
|
||||
* @param bool $coursedeletion Are we calling this as part of deleting the course the context is under?
|
||||
* @return array only returns an empty array for backwards compatibility.
|
||||
*/
|
||||
function question_delete_context($contextid): array {
|
||||
function question_delete_context($contextid, bool $coursedeletion = false): array {
|
||||
global $DB;
|
||||
|
||||
$fields = 'id, parent, name, contextid';
|
||||
@@ -432,7 +434,7 @@ function question_delete_context($contextid): array {
|
||||
// Sort categories following their tree (parent-child) relationships this will make the feedback more readable.
|
||||
$categories = sort_categories_by_tree($categories);
|
||||
foreach ($categories as $category) {
|
||||
question_category_delete_safe($category);
|
||||
question_category_delete_safe($category, $coursedeletion);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
@@ -481,11 +483,12 @@ function question_save_from_deletion($questionids, $newcontextid, $oldplace, $ne
|
||||
*
|
||||
* @param object $cm the course module object representing the activity
|
||||
* @param bool $notused the argument is not used any more. Kept for backwards compatibility.
|
||||
* @param bool $coursedeletion Are we calling this as part of deleting the course the activity belongs to?
|
||||
* @return boolean
|
||||
*/
|
||||
function question_delete_activity($cm, $notused = false): bool {
|
||||
function question_delete_activity($cm, $notused = false, bool $coursedeletion = false): bool {
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
question_delete_context($modcontext->id);
|
||||
question_delete_context($modcontext->id, $coursedeletion);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -342,27 +342,66 @@ final class questionlib_test extends \advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* This function tests the question_category_delete_safe function.
|
||||
* Test parameters for calling question_category_delete_safe
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function test_question_category_delete_safe(): void {
|
||||
public static function delete_category_parameters(): array {
|
||||
return [
|
||||
'Delete category' => [
|
||||
'coursedeletion' => false,
|
||||
],
|
||||
'Delete category with course' => [
|
||||
'coursedeletion' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function tests the question_category_delete_safe function.
|
||||
*
|
||||
* @param bool $coursedeletion If true, simulate calling question_category_delete_safe as part of deletion of the whole course.
|
||||
* @dataProvider delete_category_parameters
|
||||
* @covers ::question_category_delete_safe
|
||||
*/
|
||||
public function test_question_category_delete_safe(bool $coursedeletion): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions();
|
||||
[, $course, , $qcat, $questions] = $this->setup_quiz_and_questions();
|
||||
|
||||
question_category_delete_safe($qcat);
|
||||
$targetcourseid = $coursedeletion ? SITEID : $course->id;
|
||||
|
||||
question_category_delete_safe($qcat, $coursedeletion);
|
||||
|
||||
// Verify category deleted.
|
||||
$criteria = array('id' => $qcat->id);
|
||||
$criteria = ['id' => $qcat->id];
|
||||
$this->assertEquals(0, $DB->count_records('question_categories', $criteria));
|
||||
|
||||
// Verify questions deleted or moved.
|
||||
$this->assert_category_contains_questions($qcat->id, 0);
|
||||
|
||||
// Verify question not deleted.
|
||||
$criteria = array('id' => $questions[0]->id);
|
||||
$this->assertEquals(1, $DB->count_records('question', $criteria));
|
||||
$criteria = ['id' => $questions[0]->id];
|
||||
$savedquestion = $DB->get_record_sql(
|
||||
"SELECT q.*, qbe.questioncategoryid
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qv.questionbankentryid = qbe.id",
|
||||
$criteria
|
||||
);
|
||||
$this->assertNotEmpty($savedquestion);
|
||||
|
||||
// Verify question now sits in a system qbank in the target course.
|
||||
$this->assertNotEquals($qcat->id, $savedquestion->id);
|
||||
$newcategory = $DB->get_record('question_categories', ['id' => $savedquestion->questioncategoryid], strictness: MUST_EXIST);
|
||||
$newcategorycontext = context::instance_by_id($newcategory->contextid);
|
||||
$this->assertEquals(\context_module::LEVEL, $newcategorycontext->contextlevel);
|
||||
[$newcourse, $newcm] = get_course_and_cm_from_cmid($newcategorycontext->instanceid);
|
||||
$this->assertEquals($newcm->modname, 'qbank');
|
||||
$this->assertEquals(question_bank_helper::TYPE_SYSTEM, $DB->get_field('qbank', 'type', ['id' => $newcm->instance]));
|
||||
$this->assertEquals($targetcourseid, $newcourse->id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user