/** * Automatically clean-up all plugin data and remove the plugin DB tables * * NOTE: do not call directly, use new /admin/plugins.php?uninstall=component instead! * * @param string $type The plugin type, eg. 'mod', 'qtype', 'workshopgrading' etc. * @param string $name The plugin name, eg. 'forum', 'multichoice', 'accumulative' etc. * @uses global $OUTPUT to produce notices and other messages * @return void */ function uninstall_plugin($type, $name) { global $CFG, $DB, $OUTPUT; // This may take a long time. core_php_time_limit::raise(); // Recursively uninstall all subplugins first. $subplugintypes = core_component::get_plugin_types_with_subplugins(); if (isset($subplugintypes[$type])) { $base = core_component::get_plugin_directory($type, $name); if (file_exists("{$base}/db/subplugins.php")) { $subplugins = array(); include "{$base}/db/subplugins.php"; foreach ($subplugins as $subplugintype => $dir) { $instances = core_component::get_plugin_list($subplugintype); foreach ($instances as $subpluginname => $notusedpluginpath) { uninstall_plugin($subplugintype, $subpluginname); } } } } $component = $type . '_' . $name; // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum' if ($type === 'mod') { $pluginname = $name; // eg. 'forum' if (get_string_manager()->string_exists('modulename', $component)) { $strpluginname = get_string('modulename', $component); } else { $strpluginname = $component; } } else { $pluginname = $component; if (get_string_manager()->string_exists('pluginname', $component)) { $strpluginname = get_string('pluginname', $component); } else { $strpluginname = $component; } } echo $OUTPUT->heading($pluginname); // Delete all tag instances associated with this plugin. require_once $CFG->dirroot . '/tag/lib.php'; tag_delete_instances($component); // Custom plugin uninstall. $plugindirectory = core_component::get_plugin_directory($type, $name); $uninstalllib = $plugindirectory . '/db/uninstall.php'; if (file_exists($uninstalllib)) { require_once $uninstalllib; $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall'; // eg. 'xmldb_workshop_uninstall()' if (function_exists($uninstallfunction)) { // Do not verify result, let plugin complain if necessary. $uninstallfunction(); } } // Specific plugin type cleanup. $plugininfo = core_plugin_manager::instance()->get_plugin_info($component); if ($plugininfo) { $plugininfo->uninstall_cleanup(); core_plugin_manager::reset_caches(); } $plugininfo = null; // perform clean-up task common for all the plugin/subplugin types //delete the web service functions and pre-built services require_once $CFG->dirroot . '/lib/externallib.php'; external_delete_descriptions($component); // delete calendar events $DB->delete_records('event', array('modulename' => $pluginname)); // Delete scheduled tasks. $DB->delete_records('task_scheduled', array('component' => $pluginname)); // Delete Inbound Message datakeys. $DB->delete_records_select('messageinbound_datakeys', 'handler IN (SELECT id FROM {messageinbound_handlers} WHERE component = ?)', array($pluginname)); // Delete Inbound Message handlers. $DB->delete_records('messageinbound_handlers', array('component' => $pluginname)); // delete all the logs $DB->delete_records('log', array('module' => $pluginname)); // delete log_display information $DB->delete_records('log_display', array('component' => $component)); // delete the module configuration records unset_all_config_for_plugin($component); if ($type === 'mod') { unset_all_config_for_plugin($pluginname); } // delete message provider message_provider_uninstall($component); // delete the plugin tables $xmldbfilepath = $plugindirectory . '/db/install.xml'; drop_plugin_tables($component, $xmldbfilepath, false); if ($type === 'mod' or $type === 'block') { // non-frankenstyle table prefixes drop_plugin_tables($name, $xmldbfilepath, false); } // delete the capabilities that were defined by this module capabilities_cleanup($component); // remove event handlers and dequeue pending events events_uninstall($component); // Delete all remaining files in the filepool owned by the component. $fs = get_file_storage(); $fs->delete_component_files($component); // Finally purge all caches. purge_all_caches(); // Invalidate the hash used for upgrade detections. set_config('allversionshash', ''); echo $OUTPUT->notification(get_string('success'), 'notifysuccess'); }
/** * This function will handle 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 * @since Moodle 2.5 */ function course_delete_module($cmid) { 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'; require_once $CFG->dirroot . '/tag/lib.php'; // Get the course module. if (!($cm = $DB->get_record('course_modules', array('id' => $cmid)))) { return true; } // Get the module context. $modcontext = context_module::instance($cm->id); // Get the course module name. $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module), MUST_EXIST); // 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('cannotdeletemodulemissinglib', '', '', null, "Cannot delete this module as the file mod/{$modulename}/lib.php is missing."); } $deleteinstancefunction = $modulename . '_delete_instance'; // Ensure the delete_instance function exists for this module. if (!function_exists($deleteinstancefunction)) { throw new moodle_exception('cannotdeletemodulemissingfunc', '', '', null, "Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/{$modulename}/lib.php."); } // Delete activity context questions and question categories. question_delete_activity($cm); // Call the delete_instance function, if it returns false throw an exception. if (!$deleteinstancefunction($cm->instance)) { throw new moodle_exception('cannotdeletemoduleinstance', '', '', null, "Cannot delete the module {$modulename} (instance)."); } // Remove all module files in case modules forget to do that. $fs = get_file_storage(); $fs->delete_area_files($modcontext->id); // Delete events from calendar. if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename))) { foreach ($events as $event) { $calendarevent = calendar_event::load($event->id); $calendarevent->delete(); } } // Delete grade items, outcome items and grades attached to modules. if ($grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $modulename, 'iteminstance' => $cm->instance, 'courseid' => $cm->course))) { foreach ($grade_items as $grade_item) { $grade_item->delete('moddelete'); } } // 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', array('coursemoduleid' => $cm->id)); $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id, 'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY)); // Delete all tag instances associated with the instance of this module. tag_delete_instances('mod_' . $modulename, $modcontext->id); // Delete the context. context_helper::delete_instance(CONTEXT_MODULE, $cm->id); // Delete the module from the course_modules table. $DB->delete_records('course_modules', array('id' => $cm->id)); // Delete module from that section. if (!delete_mod_from_section($cm->id, $cm->section)) { throw new moodle_exception('cannotdeletemodulefromsection', '', '', null, "Cannot delete the module {$modulename} (instance) from section."); } // Trigger event for course module delete action. $event = \core\event\course_module_deleted::create(array('courseid' => $cm->course, 'context' => $modcontext, 'objectid' => $cm->id, 'other' => array('modulename' => $modulename, 'instanceid' => $cm->instance))); $event->add_record_snapshot('course_modules', $cm); $event->trigger(); rebuild_course_cache($cm->course, true); }
/** * Test the tag removed event. */ public function test_tag_removed() { global $DB; $this->setAdminUser(); // Create a course to tag. $course = $this->getDataGenerator()->create_course(); // Create a wiki page to tag. $wikigenerator = $this->getDataGenerator()->get_plugin_generator('mod_wiki'); $wiki = $wikigenerator->create_instance(array('course' => $course->id)); $subwikiid = wiki_add_subwiki($wiki->id, 0); $wikipageid = wiki_create_page($subwikiid, 'Title', FORMAT_HTML, '2'); // Create the tag. $tag = $this->getDataGenerator()->create_tag(); // Assign a tag to a course. tag_assign('course', $course->id, $tag->id, 1, 2, 'core', context_course::instance($course->id)->id); // Trigger and capture the event for untagging a course. $sink = $this->redirectEvents(); coursetag_delete_keyword($tag->id, 2, $course->id); $events = $sink->get_events(); $event = reset($events); // Check that the tag was removed from the course and the event data is valid. $this->assertEquals(0, $DB->count_records('tag_instance')); $this->assertInstanceOf('\\core\\event\\tag_removed', $event); $this->assertEquals(context_course::instance($course->id), $event->get_context()); // Create the tag. $tag = $this->getDataGenerator()->create_tag(); // Assign a tag to a wiki this time. tag_assign('wiki_pages', $wikipageid, $tag->id, 1, 2, 'mod_wiki', context_module::instance($wiki->cmid)->id); // Trigger and capture the event for deleting this tag instance. $sink = $this->redirectEvents(); tag_delete_instance('wiki_pages', $wikipageid, $tag->id); $events = $sink->get_events(); $event = reset($events); // Check that tag was removed from the wiki page and the event data is valid. $this->assertEquals(0, $DB->count_records('tag_instance')); $this->assertInstanceOf('\\core\\event\\tag_removed', $event); $this->assertEquals(context_module::instance($wiki->cmid), $event->get_context()); // Create a tag again - the other would have been deleted since there were no more instances associated with it. $tag = $this->getDataGenerator()->create_tag(); // Assign a tag to the wiki again. tag_assign('wiki_pages', $wikipageid, $tag->id, 1, 2, 'mod_wiki', context_module::instance($wiki->cmid)->id); // Now we want to delete this tag, and because there is only one tag instance // associated with it, it should get deleted as well. $sink = $this->redirectEvents(); tag_delete($tag->id); $events = $sink->get_events(); $event = reset($events); // Check that tag was removed from the wiki page and the event data is valid. $this->assertEquals(0, $DB->count_records('tag_instance')); $this->assertInstanceOf('\\core\\event\\tag_removed', $event); $this->assertEquals(context_module::instance($wiki->cmid), $event->get_context()); // Create a tag again - the other would have been deleted since there were no more instances associated with it. $tag = $this->getDataGenerator()->create_tag(); // Assign a tag to the wiki again. tag_assign('wiki_pages', $wikipageid, $tag->id, 1, 2, 'mod_wiki', context_module::instance($wiki->cmid)->id); // Delete all tag instances for this wiki instance. $sink = $this->redirectEvents(); tag_delete_instances('mod_wiki', context_module::instance($wiki->cmid)->id); $events = $sink->get_events(); $event = reset($events); // Check that tag was removed from the wiki page and the event data is valid. $this->assertEquals(0, $DB->count_records('tag_instance')); $this->assertInstanceOf('\\core\\event\\tag_removed', $event); $this->assertEquals(context_module::instance($wiki->cmid), $event->get_context()); // Create another wiki. $wiki2 = $wikigenerator->create_instance(array('course' => $course->id)); $subwikiid2 = wiki_add_subwiki($wiki2->id, 0); $wikipageid2 = wiki_create_page($subwikiid2, 'Title', FORMAT_HTML, '2'); // Assign a tag to both wiki pages. tag_assign('wiki_pages', $wikipageid, $tag->id, 1, 2, 'mod_wiki', context_module::instance($wiki->cmid)->id); tag_assign('wiki_pages', $wikipageid2, $tag->id, 1, 2, 'mod_wiki', context_module::instance($wiki2->cmid)->id); // Now remove all tag_instances associated with all wikis. $sink = $this->redirectEvents(); tag_delete_instances('mod_wiki'); $events = $sink->get_events(); // There will be two events - one for each wiki instance removed. $event1 = reset($events); $event2 = $events[1]; // Check that the tags were removed from the wiki pages. $this->assertEquals(0, $DB->count_records('tag_instance')); // Check the first event data is valid. $this->assertInstanceOf('\\core\\event\\tag_removed', $event1); $this->assertEquals(context_module::instance($wiki->cmid), $event1->get_context()); // Check that the second event data is valid. $this->assertInstanceOf('\\core\\event\\tag_removed', $event2); $this->assertEquals(context_module::instance($wiki2->cmid), $event2->get_context()); }
/** * Called by delete_instance. Deletes all the forum's data (but * not the actual forum record, delete_instance handles that). */ public function delete_all_data() { global $DB, $CFG; require_once $CFG->dirroot . '/tag/lib.php'; // Delete per-post data $postquery = "\nSELECT\n fp.id\nFROM\n {forumng_discussions} fd\n INNER JOIN {forumng_posts} fp on fp.discussionid = fd.id\nWHERE\n fd.forumngid = ?"; $postparams = array($this->forumfields->id); $DB->execute("DELETE FROM {forumng_ratings}\n WHERE postid IN ({$postquery})", $postparams); $DB->execute("DELETE FROM {forumng_read_posts}\n WHERE postid IN ({$postquery})", $postparams); // Delete per-discussion data $discussionquery = "SELECT id FROM {forumng_discussions}\n WHERE forumngid = ?"; $discussionparams = array($this->forumfields->id); $DB->execute("DELETE FROM {forumng_read}\n WHERE discussionid IN ({$discussionquery})", $discussionparams); $DB->execute("DELETE FROM {forumng_posts}\n WHERE discussionid IN ({$discussionquery})", $discussionparams); // Delete standard rating data. if ($this->get_enableratings() == mod_forumng::FORUMNG_STANDARD_RATING && !$this->is_clone()) { require_once $CFG->dirroot . '/rating/lib.php'; $delopt = new stdClass(); $delopt->contextid = $this->get_context(true)->id; $delopt->component = 'mod_forumng'; $delopt->ratingarea = 'post'; $rm = new rating_manager(); $rm->delete_ratings($delopt); } // Delete per-forum data if ($this->is_clone()) { $DB->delete_records('forumng_subscriptions', array('clonecmid' => $this->get_course_module_id())); } else { $DB->delete_records('forumng_subscriptions', array('forumngid' => $this->forumfields->id)); } $DB->delete_records('forumng_discussions', array('forumngid' => $this->forumfields->id)); // Delete tag instances. tag_delete_instances('mod_forumng', $this->context->id); }