diff --git a/course/classes/category.php b/course/classes/category.php index a1e20c22a39..ba631fd4fcd 100644 --- a/course/classes/category.php +++ b/course/classes/category.php @@ -1600,7 +1600,10 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr } // Prepare the list of core_course_list_element objects. foreach ($ids as $id) { - $courses[$id] = new core_course_list_element($records[$id]); + // If a course is deleted after we got the cache entry it may not exist in the database anymore. + if (!empty($records[$id])) { + $courses[$id] = new core_course_list_element($records[$id]); + } } } return $courses; @@ -1810,7 +1813,10 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr } // Prepare the list of core_course_list_element objects. foreach ($ids as $id) { - $courses[$id] = new core_course_list_element($records[$id]); + // If a course is deleted after we got the cache entry it may not exist in the database anymore. + if (!empty($records[$id])) { + $courses[$id] = new core_course_list_element($records[$id]); + } } } return $courses; diff --git a/course/tests/category_test.php b/course/tests/category_test.php index 54899398c73..8d8b26be765 100644 --- a/course/tests/category_test.php +++ b/course/tests/category_test.php @@ -1080,4 +1080,26 @@ class core_course_category_testcase extends advanced_testcase { } return $draftid; } + + /** + * This test ensures that is the list of courses in a category can be retrieved while a course is being deleted. + */ + public function test_get_courses_during_delete() { + global $DB; + $category = self::getDataGenerator()->create_category(); + $course = self::getDataGenerator()->create_course(['category' => $category->id]); + $othercourse = self::getDataGenerator()->create_course(['category' => $category->id]); + $coursecategory = core_course_category::get($category->id); + // Get a list of courses before deletion to populate the cache. + $originalcourses = $coursecategory->get_courses(); + $this->assertCount(2, $originalcourses); + $this->assertArrayHasKey($course->id, $originalcourses); + $this->assertArrayHasKey($othercourse->id, $originalcourses); + // Simulate the course deletion process being part way though. + $DB->delete_records('course', ['id' => $course->id]); + // Get the list of courses while a deletion is in progress. + $courses = $coursecategory->get_courses(); + $this->assertCount(1, $courses); + $this->assertArrayHasKey($othercourse->id, $courses); + } } diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 6f4a6c08927..cb91da2f556 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -5245,6 +5245,9 @@ function remove_course_contents($courseid, $showfeedback = true, array $options echo $OUTPUT->notification($strdeleted.get_string('type_block_plural', 'plugin'), 'notifysuccess'); } + $DB->set_field('course_modules', 'deletioninprogress', '1', ['course' => $courseid]); + rebuild_course_cache($courseid, true); + // Get the list of all modules that are properly installed. $allmodules = $DB->get_records_menu('modules', array(), '', 'name, id'); @@ -5287,6 +5290,7 @@ function remove_course_contents($courseid, $showfeedback = true, array $options // Delete cm and its context - orphaned contexts are purged in cron in case of any race condition. context_helper::delete_instance(CONTEXT_MODULE, $cm->id); $DB->delete_records('course_modules', array('id' => $cm->id)); + rebuild_course_cache($cm->course, true); } } } @@ -5323,6 +5327,7 @@ function remove_course_contents($courseid, $showfeedback = true, array $options } context_helper::delete_instance(CONTEXT_MODULE, $cm->id); $DB->delete_records('course_modules', array('id' => $cm->id)); + rebuild_course_cache($cm->course, true); } if ($showfeedback) {