MDL-86856 courseformat: Add delete and delete_async to cmactions
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
issueNumber: MDL-86856
|
||||
notes:
|
||||
core_courseformat:
|
||||
- message: Add delete method to the core_courseformat\cmactions
|
||||
type: improved
|
||||
@@ -20,6 +20,7 @@ use core\exception\moodle_exception;
|
||||
use core_courseformat\sectiondelegatemodule;
|
||||
use core_text;
|
||||
use course_modinfo;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Course module course format actions.
|
||||
@@ -227,4 +228,223 @@ class cmactions extends baseactions {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the whole deletion process of a module.
|
||||
* This includes calling the modules delete_instance function, deleting files, events, grades, conditional data,
|
||||
* the data in the course_module and course_sections table and adding a module deletion event to the DB.
|
||||
*
|
||||
* @param int $cmid The course module id
|
||||
* @param bool $async Whether or not to try to delete the module using an adhoc task. Async also depends on a plugin hook.
|
||||
*/
|
||||
public function delete(int $cmid, bool $async = false): void {
|
||||
// Check the 'course_module_background_deletion_recommended' hook first.
|
||||
// Only use asynchronous deletion if at least one plugin returns true and if async deletion has been requested.
|
||||
// Both are checked because plugins should not be allowed to dictate the deletion behaviour, only support/decline it.
|
||||
// It's up to plugins to handle things like whether or not they are enabled.
|
||||
if ($async && $pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) {
|
||||
foreach ($pluginsfunction as $plugintype => $plugins) {
|
||||
foreach ($plugins as $pluginfunction) {
|
||||
if ($pluginfunction()) {
|
||||
$this->delete_async($cmid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global $CFG, $DB;
|
||||
|
||||
require_once($CFG->libdir . '/gradelib.php');
|
||||
require_once($CFG->libdir . '/questionlib.php');
|
||||
require_once($CFG->dirroot . '/blog/lib.php');
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
if (!$cm = $DB->get_record('course_modules', ['id' => $cmid])) {
|
||||
return;
|
||||
}
|
||||
$modulename = $DB->get_field('modules', 'name', ['id' => $cm->module], MUST_EXIST);
|
||||
|
||||
$this->check_deletion($cm, $modulename);
|
||||
|
||||
// Allow plugins to use this course module before we completely delete it.
|
||||
if ($pluginsfunction = get_plugins_with_function('pre_course_module_delete')) {
|
||||
foreach ($pluginsfunction as $plugintype => $plugins) {
|
||||
foreach ($plugins as $pluginfunction) {
|
||||
$pluginfunction($cm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($cm->instance)) {
|
||||
throw new moodle_exception(
|
||||
errorcode: 'cannotdeletemodulemissinginstance',
|
||||
debuginfo: "Cannot delete module with ID $cm->id because it does not have a valid activity instance.",
|
||||
);
|
||||
}
|
||||
|
||||
// Call the delete_instance function, if it returns false throw an exception.
|
||||
$deleteinstancefunction = $modulename . '_delete_instance';
|
||||
if (!$deleteinstancefunction($cm->instance)) {
|
||||
throw new moodle_exception(
|
||||
errorcode: 'cannotdeletemoduleinstance',
|
||||
debuginfo: "Cannot delete module $modulename (instance).",
|
||||
);
|
||||
}
|
||||
|
||||
// We delete the questions after the activity database is removed,
|
||||
// because questions are referenced via question reference tables
|
||||
// and cannot be deleted while the activities that use them still exist.
|
||||
question_delete_activity($cm);
|
||||
|
||||
// Remove all module files in case modules forget to do that.
|
||||
$modcontext = \context_module::instance($cm->id);
|
||||
$fs = get_file_storage();
|
||||
$fs->delete_area_files($modcontext->id);
|
||||
|
||||
// Delete events from calendar.
|
||||
if ($events = $DB->get_records('event', ['instance' => $cm->instance, 'modulename' => $modulename])) {
|
||||
$coursecontext = \context_course::instance($cm->course);
|
||||
foreach ($events as $event) {
|
||||
$event->context = $coursecontext;
|
||||
$calendarevent = \calendar_event::load($event);
|
||||
$calendarevent->delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete grade items, outcome items and grades attached to modules.
|
||||
$gradeitems = \grade_item::fetch_all(['itemtype' => 'mod',
|
||||
'itemmodule' => $modulename,
|
||||
'iteminstance' => $cm->instance,
|
||||
'courseid' => $cm->course,
|
||||
]);
|
||||
if ($gradeitems) {
|
||||
foreach ($gradeitems as $gradeitem) {
|
||||
$gradeitem->delete('moddelete');
|
||||
}
|
||||
}
|
||||
|
||||
// Delete associated blogs and blog tag instances.
|
||||
blog_remove_associations_for_module($modcontext->id);
|
||||
|
||||
// Delete completion and availability data; it is better to do this even if the
|
||||
// features are not turned on, in case they were turned on previously (these will be
|
||||
// very quick on an empty table).
|
||||
$DB->delete_records('course_modules_completion', ['coursemoduleid' => $cm->id]);
|
||||
$DB->delete_records('course_modules_viewed', ['coursemoduleid' => $cm->id]);
|
||||
$DB->delete_records('course_completion_criteria', [
|
||||
'moduleinstance' => $cm->id,
|
||||
'course' => $cm->course,
|
||||
'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY,
|
||||
]);
|
||||
|
||||
// Delete all tag instances associated with the instance of this module.
|
||||
\core_tag_tag::delete_instances('mod_' . $modulename, null, $modcontext->id);
|
||||
\core_tag_tag::remove_all_item_tags('core', 'course_modules', $cm->id);
|
||||
|
||||
// Notify the competency subsystem.
|
||||
\core_competency\api::hook_course_module_deleted($cm);
|
||||
|
||||
// Delete the context.
|
||||
\context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
|
||||
|
||||
// Delete the module from the course_modules table.
|
||||
$DB->delete_records('course_modules', ['id' => $cm->id]);
|
||||
|
||||
// Delete module from that section.
|
||||
if (!delete_mod_from_section($cm->id, $cm->section)) {
|
||||
throw new moodle_exception(
|
||||
errorcode: 'cannotdeletemodulefromsection',
|
||||
debuginfo: "Cannot delete the module $modulename (instance) from section.",
|
||||
);
|
||||
}
|
||||
|
||||
// Trigger event for course module delete action.
|
||||
$event = \core\event\course_module_deleted::create([
|
||||
'courseid' => $cm->course,
|
||||
'context' => $modcontext,
|
||||
'objectid' => $cm->id,
|
||||
'other' => [
|
||||
'modulename' => $modulename,
|
||||
'instanceid' => $cm->instance,
|
||||
],
|
||||
]);
|
||||
$event->add_record_snapshot('course_modules', $cm);
|
||||
$event->trigger();
|
||||
course_modinfo::purge_course_module_cache($cm->course, $cm->id);
|
||||
rebuild_course_cache($cm->course, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a course module for deletion in the background using an adhoc task.
|
||||
*
|
||||
* @param int $cmid the course module id.
|
||||
*/
|
||||
protected function delete_async(int $cmid): void {
|
||||
global $DB, $USER;
|
||||
|
||||
if (!$cm = $DB->get_record('course_modules', ['id' => $cmid])) {
|
||||
return;
|
||||
}
|
||||
$modulename = $DB->get_field('modules', 'name', ['id' => $cm->module], MUST_EXIST);
|
||||
|
||||
// We need to be reasonably certain the deletion is going to succeed before we background the process.
|
||||
// Make the necessary delete_instance checks, etc. before proceeding further. Throw exceptions if required.
|
||||
$this->check_deletion($cm, $modulename);
|
||||
|
||||
// Defer the deletion as we can't be sure how long the module's pre_delete code will run for.
|
||||
$DB->set_field(
|
||||
'course_modules',
|
||||
'deletioninprogress',
|
||||
'1',
|
||||
['id' => $cmid],
|
||||
);
|
||||
|
||||
// Create an adhoc task for the deletion of the course module.
|
||||
$removaltask = new \core_course\task\course_delete_modules();
|
||||
$removaltask->set_custom_data([
|
||||
'cms' => [$cm],
|
||||
'userid' => $USER->id,
|
||||
'realuserid' => \core\session\manager::get_realuser()->id,
|
||||
]);
|
||||
|
||||
// Queue the task for the next run.
|
||||
\core\task\manager::queue_adhoc_task($removaltask);
|
||||
|
||||
// Reset the course cache to hide the module.
|
||||
rebuild_course_cache($cm->course, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the necessary delete_instance checks. Throw exceptions if required.
|
||||
*
|
||||
* @param stdClass $cm The course module object.
|
||||
* @param string $modulename The module name.
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
private function check_deletion(stdClass $cm, string $modulename) {
|
||||
global $CFG;
|
||||
|
||||
// Get the file location of the delete_instance function for this module.
|
||||
$modlib = "$CFG->dirroot/mod/$modulename/lib.php";
|
||||
|
||||
// Include the file required to call the delete_instance function for this module.
|
||||
if (file_exists($modlib)) {
|
||||
require_once($modlib);
|
||||
} else {
|
||||
throw new \moodle_exception(
|
||||
errorcode: 'cannotdeletemodulemissinglib',
|
||||
debuginfo: "Cannot delete module: Missing file mod/$modulename/lib.php.",
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure the delete_instance function exists for this module.
|
||||
$deleteinstancefunction = $modulename . '_delete_instance';
|
||||
if (!function_exists($deleteinstancefunction)) {
|
||||
throw new \moodle_exception(
|
||||
errorcode: 'cannotdeletemodulemissingfunc',
|
||||
debuginfo: "Cannot delete module: Missing function {$modulename}_delete_instance in mod/$modulename/lib.php.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ use core_courseformat\hook\after_cm_name_edited;
|
||||
* @package core_courseformat
|
||||
* @copyright 2024 Ferran Recio <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \core_courseformat\local\cmactions
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CoversClass(\core_courseformat\local\cmactions::class)]
|
||||
final class cmactions_test extends \advanced_testcase {
|
||||
/**
|
||||
* Setup to ensure that fixtures are loaded.
|
||||
@@ -227,4 +227,325 @@ final class cmactions_test extends \advanced_testcase {
|
||||
$this->assertEquals($activity->cmid, $executedhook->get_cm()->id);
|
||||
$this->assertEquals('New name', $executedhook->get_newname());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests the function that deletes a course module.
|
||||
*/
|
||||
public function test_delete(): void {
|
||||
global $DB, $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
// Generate an assignment with due date (will generate a course event).
|
||||
$course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
|
||||
$module = $this->getDataGenerator()->create_module(
|
||||
'assign',
|
||||
['course' => $course->id, 'duedate' => time()],
|
||||
['completion' => COMPLETION_TRACKING_MANUAL],
|
||||
);
|
||||
$modcontext = \context_module::instance($module->cmid);
|
||||
$cm = $DB->get_record('course_modules', ['id' => $module->cmid]);
|
||||
$this->assertInstanceOf('context_module', $modcontext);
|
||||
$this->assertEquals(1, $DB->count_records('event', ['instance' => $module->id, 'modulename' => 'assign']));
|
||||
|
||||
// Create blog entry associated to the module.
|
||||
/** @var \core_blog_generator $blogsgenerator */
|
||||
$blogsgenerator = $this->getDataGenerator()->get_plugin_generator('core_blog');
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
$blogentry = $blogsgenerator->create_entry([
|
||||
'publishstate' => 'site',
|
||||
'userid' => $user->id,
|
||||
'subject' => 'My blog',
|
||||
'summary' => 'Animals',
|
||||
'tags' => ['cat', 'fish'],
|
||||
'courseid' => $course->id,
|
||||
]);
|
||||
$blogentry->add_association($modcontext->id);
|
||||
$this->assertEquals(1, $DB->count_records('post', ['id' => $blogentry->id]));
|
||||
$this->assertEquals(1, $DB->count_records('blog_association', ['contextid' => $modcontext->id]));
|
||||
$this->assertEquals(2, $DB->count_records('tag_instance', ['itemid' => $blogentry->id]));
|
||||
|
||||
// Completion data.
|
||||
$completion = new \completion_info($course);
|
||||
$completion->update_state($cm, COMPLETION_COMPLETE);
|
||||
$this->assertEquals(1, $DB->count_records('course_modules_completion', ['coursemoduleid' => $cm->id]));
|
||||
|
||||
// Add some tags to this module.
|
||||
\core_tag_tag::set_item_tags('mod_assign', 'assign', $module->id, $modcontext, ['Tag 1', 'Tag 2', 'Tag 3']);
|
||||
\core_tag_tag::set_item_tags('core', 'course_modules', $module->cmid, $modcontext, ['Tag 3', 'Tag 4', 'Tag 5']);
|
||||
$criteria = ['component' => 'mod_assign', 'itemtype' => 'assign', 'contextid' => $modcontext->id];
|
||||
$this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
|
||||
$criteria = ['component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id];
|
||||
$this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
|
||||
|
||||
// To capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
|
||||
// Run delete.
|
||||
$cmactions = new cmactions($course);
|
||||
$cmactions->delete($module->cmid);
|
||||
|
||||
// Verify the context has been removed.
|
||||
$this->assertFalse(\context_module::instance($module->cmid, IGNORE_MISSING));
|
||||
|
||||
// Verify the course_module record has been deleted.
|
||||
$this->assertEmpty($DB->count_records('course_modules', ['id' => $module->cmid]));
|
||||
|
||||
// Verify the course_modules_completion record has been deleted.
|
||||
$this->assertEmpty($DB->count_records('course_modules_completion', ['coursemoduleid' => $module->cmid]));
|
||||
|
||||
// Verify the blog_association record has been deleted.
|
||||
$this->assertEmpty($DB->count_records('blog_association', ['contextid' => $modcontext->id]));
|
||||
|
||||
// Verify the blog post record has been deleted.
|
||||
$this->assertEmpty($DB->count_records('post', ['id' => $blogentry->id]));
|
||||
|
||||
// Verify the tag instance record has been deleted.
|
||||
$this->assertEmpty($DB->count_records('tag_instance', ['itemid' => $blogentry->id]));
|
||||
|
||||
// Verify events have been removed.
|
||||
$this->assertEmpty($DB->count_records('event', ['instance' => $module->id, 'modulename' => 'assign']));
|
||||
|
||||
// Verify the tag instances were deleted.
|
||||
$criteria = ['component' => 'mod_assign', 'contextid' => $modcontext->id];
|
||||
$this->assertEmpty($DB->count_records('tag_instance', $criteria));
|
||||
$criteria = ['component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id];
|
||||
$this->assertEmpty($DB->count_records('tag_instance', $criteria));
|
||||
|
||||
// Check the event is triggered.
|
||||
$events = $sink->get_events();
|
||||
$sink->close();
|
||||
$count = 0;
|
||||
while (!empty($events)) {
|
||||
$event = array_pop($events);
|
||||
if ($event instanceof \core\event\course_module_deleted) {
|
||||
$count++;
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\course_module_deleted', $event);
|
||||
$this->assertEquals($module->cmid, $event->objectid);
|
||||
$this->assertEquals($USER->id, $event->userid);
|
||||
$this->assertEquals('course_modules', $event->objecttable);
|
||||
$this->assertEquals(null, $event->get_url());
|
||||
$this->assertEquals($cm, $event->get_record_snapshot('course_modules', $module->cmid));
|
||||
}
|
||||
}
|
||||
$this->assertEquals(1, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the function that deletes a course module.
|
||||
*/
|
||||
public function test_delete_module_with_questions(): void {
|
||||
global $DB, $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
// Generate a quiz.
|
||||
$course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
|
||||
$module = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
|
||||
$modcontext = \context_module::instance($module->cmid);
|
||||
|
||||
// Add some questions to this module.
|
||||
/** @var \core_question_generator $qgen */
|
||||
$qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$qcat = $qgen->create_question_category(['contextid' => $modcontext->id]);
|
||||
$qgen->create_question('shortanswer', null, ['category' => $qcat->id]);
|
||||
$qgen->create_question('shortanswer', null, ['category' => $qcat->id]);
|
||||
$this->assertEquals(2, $DB->count_records('question'));
|
||||
|
||||
// Run delete.
|
||||
$cmactions = new cmactions($course);
|
||||
$cmactions->delete($module->cmid);
|
||||
|
||||
// Verify the context has been removed.
|
||||
$this->assertFalse(\context_module::instance($module->cmid, IGNORE_MISSING));
|
||||
|
||||
// Verify the course_module record has been deleted.
|
||||
$this->assertEmpty($DB->count_records('course_modules', ['id' => $module->cmid]));
|
||||
|
||||
// Verify events have been removed.
|
||||
$this->assertEmpty($DB->count_records('event', ['instance' => $module->id, 'modulename' => 'quiz']));
|
||||
|
||||
// Verify the category and questions were deleted.
|
||||
$this->assertEquals(0, $DB->count_records('question_categories', ['contextid' => $modcontext->id]));
|
||||
$this->assertEquals(0, $DB->count_records('question'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the function that deletes a course module with wrong cmid.
|
||||
*/
|
||||
public function test_delete_wrong_cmid(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'duedate' => time()]);
|
||||
$this->assertEquals(1, $DB->count_records('course_modules'));
|
||||
|
||||
$cmactions = new cmactions($course);
|
||||
$cmactions->delete(99999); // Non existing cmid.
|
||||
|
||||
// Verify the course_module record has not been deleted.
|
||||
$this->assertEquals(1, $DB->count_records('course_modules'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the function that deletes a course module with missing lib.php file.
|
||||
*/
|
||||
public function test_delete_missinglib(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Generate test data.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$module = $this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'duedate' => time()]);
|
||||
$cm = $DB->get_record('course_modules', ['id' => $module->cmid]);
|
||||
$this->assertEquals(1, $DB->count_records('course_modules'));
|
||||
|
||||
// Modify module name to make an exception when deleting.
|
||||
$module = $DB->get_record('modules', ['id' => $cm->module], 'id, name', MUST_EXIST);
|
||||
$module->name = 'TestModuleToDelete';
|
||||
$DB->update_record('modules', $module);
|
||||
|
||||
// Delete the module.
|
||||
$cmactions = new cmactions($course);
|
||||
$this->expectException(\moodle_exception::class);
|
||||
$this->expectExceptionMessage('Missing file mod/TestModuleToDelete/lib.php');
|
||||
$cmactions->delete($cm->id);
|
||||
|
||||
// Verify the course_module record has not been deleted.
|
||||
$this->assertEquals(1, $DB->count_records('course_modules'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests the function that deletes a course module async way.
|
||||
*/
|
||||
public function test_async_module_deletion_hook_implemented(): void {
|
||||
// Async module deletion depends on the 'true' being returned by at least one plugin implementing the hook,
|
||||
// 'course_module_adhoc_deletion_recommended'. In core, is implemented by the course recyclebin, which will only return
|
||||
// true if the recyclebin plugin is enabled. To make sure async deletion occurs, this test force-enables the recyclebin.
|
||||
global $DB, $USER;
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
// Ensure recyclebin is enabled.
|
||||
set_config('coursebinenable', true, 'tool_recyclebin');
|
||||
|
||||
// Create course, module and context.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$module = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
|
||||
$modcontext = \context_module::instance($module->cmid);
|
||||
|
||||
// Check events generated when deleting module.
|
||||
$sink = $this->redirectEvents();
|
||||
|
||||
// Try to delete the module asynchronously.
|
||||
$cmactions = new cmactions($course);
|
||||
$cmactions->delete($module->cmid, true);
|
||||
|
||||
// Verify that no event has been generated yet.
|
||||
$events = $sink->get_events();
|
||||
$event = array_pop($events);
|
||||
$sink->close();
|
||||
$this->assertEmpty($event);
|
||||
|
||||
// Verify the course_module hasn't been deleted yet.
|
||||
$this->assertEquals(1, $DB->count_records('course_modules', ['id' => $module->cmid]));
|
||||
|
||||
// Grab the record, in it's final state before hard deletion, for comparison with the event snapshot.
|
||||
// We need to do this because the 'deletioninprogress' flag has changed from '0' to '1'.
|
||||
$cm = $DB->get_record('course_modules', ['id' => $module->cmid], '*', MUST_EXIST);
|
||||
|
||||
// Verify the course_module is marked as 'deletioninprogress'.
|
||||
$this->assertNotEquals($cm, false);
|
||||
$this->assertEquals($cm->deletioninprogress, '1');
|
||||
|
||||
// Verify the context has not yet been removed.
|
||||
$this->assertEquals($modcontext, \context_module::instance($module->cmid, IGNORE_MISSING));
|
||||
|
||||
// Set up a sink to catch the 'course_module_deleted' event.
|
||||
$sink = $this->redirectEvents();
|
||||
|
||||
// Now, run the adhoc task which performs the hard deletion.
|
||||
\phpunit_util::run_all_adhoc_tasks();
|
||||
|
||||
// Fetch and validate the event data.
|
||||
$events = $sink->get_events();
|
||||
$event = array_pop($events);
|
||||
$sink->close();
|
||||
$this->assertInstanceOf('\core\event\course_module_deleted', $event);
|
||||
$this->assertEquals($module->cmid, $event->objectid);
|
||||
$this->assertEquals($USER->id, $event->userid);
|
||||
$this->assertEquals('course_modules', $event->objecttable);
|
||||
$this->assertEquals(null, $event->get_url());
|
||||
$this->assertEquals($cm, $event->get_record_snapshot('course_modules', $module->cmid));
|
||||
|
||||
// Verify the context has been removed.
|
||||
$this->assertFalse(\context_module::instance($module->cmid, IGNORE_MISSING));
|
||||
|
||||
// Verify the course_module record has been deleted.
|
||||
$this->assertEquals(0, $DB->count_records('course_modules', ['id' => $module->cmid]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the function that deletes a course module async way when no plugin implements the hook.
|
||||
*/
|
||||
public function test_async_module_deletion_hook_not_implemented(): void {
|
||||
// Only proceed if we are sure that no plugin is going to advocate async removal of a module. I.e. no plugin returns
|
||||
// 'true' from the 'course_module_adhoc_deletion_recommended' hook.
|
||||
// In the case of core, only recyclebin implements this hook, and it will only return true if enabled, so disable it.
|
||||
global $DB, $USER;
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
// Ensure recyclebin is disabled.
|
||||
set_config('coursebinenable', false, 'tool_recyclebin');
|
||||
|
||||
// Non-core plugins might implement the 'course_module_adhoc_deletion_recommended' hook and spoil this test.
|
||||
// If at least one plugin still returns true, then skip this test.
|
||||
if ($pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) {
|
||||
foreach ($pluginsfunction as $plugintype => $plugins) {
|
||||
foreach ($plugins as $pluginfunction) {
|
||||
if ($pluginfunction()) {
|
||||
$this->markTestSkipped();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create course, module and context.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$module = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
|
||||
$cm = $DB->get_record('course_modules', ['id' => $module->cmid], '*', MUST_EXIST);
|
||||
|
||||
// Check events generated when deleting module.
|
||||
$sink = $this->redirectEvents();
|
||||
|
||||
// Try to delete the module asynchronously.
|
||||
$cmactions = new cmactions($course);
|
||||
$cmactions->delete($module->cmid, true);
|
||||
|
||||
// Fetch and validate the event data.
|
||||
$events = $sink->get_events();
|
||||
$event = array_pop($events);
|
||||
$sink->close();
|
||||
$this->assertInstanceOf('\core\event\course_module_deleted', $event);
|
||||
$this->assertEquals($module->cmid, $event->objectid);
|
||||
$this->assertEquals($USER->id, $event->userid);
|
||||
$this->assertEquals('course_modules', $event->objecttable);
|
||||
$this->assertEquals(null, $event->get_url());
|
||||
$this->assertEquals($cm, $event->get_record_snapshot('course_modules', $module->cmid));
|
||||
|
||||
// Verify the context has been removed.
|
||||
$this->assertFalse(\context_module::instance($module->cmid, IGNORE_MISSING));
|
||||
|
||||
// Verify the course_module record has been deleted.
|
||||
$this->assertEquals(0, $DB->count_records('course_modules', ['id' => $module->cmid]));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user