diff --git a/course/externallib.php b/course/externallib.php index 3476dccb72f..62c7b93b118 100644 --- a/course/externallib.php +++ b/course/externallib.php @@ -1715,6 +1715,70 @@ class core_course_external extends external_api { return null; } + /** + * Describes the parameters for delete_modules. + * + * @return external_external_function_parameters + * @since Moodle 2.5 + */ + public static function delete_modules_parameters() { + return new external_function_parameters ( + array( + 'cmids' => new external_multiple_structure(new external_value(PARAM_INT, 'course module ID', + VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of course module IDs'), + ) + ); + } + + /** + * Deletes a list of provided module instances. + * + * @param array $cmids the course module ids + * @since Moodle 2.5 + */ + public static function delete_modules($cmids) { + global $CFG, $DB; + + // Require course file containing the course delete module function. + require_once($CFG->dirroot . "/course/lib.php"); + + // Clean the parameters. + $params = self::validate_parameters(self::delete_modules_parameters(), array('cmids' => $cmids)); + + // Keep track of the course ids we have performed a capability check on to avoid repeating. + $arrcourseschecked = array(); + + foreach ($params['cmids'] as $cmid) { + // Get the course module. + $cm = $DB->get_record('course_modules', array('id' => $cmid), '*', MUST_EXIST); + + // Check if we have not yet confirmed they have permission in this course. + if (!in_array($cm->course, $arrcourseschecked)) { + // Ensure the current user has required permission in this course. + $context = context_course::instance($cm->course); + self::validate_context($context); + // Add to the array. + $arrcourseschecked[] = $cm->course; + } + + // Ensure they can delete this module. + $modcontext = context_module::instance($cm->id); + require_capability('moodle/course:manageactivities', $modcontext); + + // Delete the module. + course_delete_module($cm->id); + } + } + + /** + * Describes the delete_modules return value. + * + * @return external_single_structure + * @since Moodle 2.5 + */ + public static function delete_modules_returns() { + return null; + } } /** diff --git a/course/tests/externallib_test.php b/course/tests/externallib_test.php index d6f8fa96524..1cec6c60069 100644 --- a/course/tests/externallib_test.php +++ b/course/tests/externallib_test.php @@ -637,4 +637,104 @@ class core_course_external_testcase extends externallib_advanced_testcase { // Check that the course has been duplicated. $this->assertEquals($newcourse['shortname'], $duplicate['shortname']); } + + /** + * Test delete course_module. + */ + public function test_delete_modules() { + global $DB; + + // Ensure we reset the data after this test. + $this->resetAfterTest(true); + + // Create a user. + $user = self::getDataGenerator()->create_user(); + + // Set the tests to run as the user. + self::setUser($user); + + // Create a course to add the modules. + $course = self::getDataGenerator()->create_course(); + + // Create two test modules. + $record = new stdClass(); + $record->course = $course->id; + $module1 = self::getDataGenerator()->create_module('forum', $record); + $module2 = self::getDataGenerator()->create_module('assignment', $record); + + // Check the forum was correctly created. + $this->assertEquals(1, $DB->count_records('forum', array('id' => $module1->id))); + + // Check the assignment was correctly created. + $this->assertEquals(1, $DB->count_records('assignment', array('id' => $module2->id))); + + // Check data exists in the course modules table. + $this->assertEquals(2, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2', + array('module1' => $module1->cmid, 'module2' => $module2->cmid))); + + // Enrol the user in the course. + $enrol = enrol_get_plugin('manual'); + $enrolinstances = enrol_get_instances($course->id, true); + foreach ($enrolinstances as $courseenrolinstance) { + if ($courseenrolinstance->enrol == "manual") { + $instance = $courseenrolinstance; + break; + } + } + $enrol->enrol_user($instance, $user->id); + + // Assign capabilities to delete module 1. + $modcontext = context_module::instance($module1->cmid); + $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id); + + // Assign capabilities to delete module 2. + $modcontext = context_module::instance($module2->cmid); + $newrole = create_role('Role 2', 'role2', 'Role 2 description'); + $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id, $newrole); + + // Deleting these module instances. + core_course_external::delete_modules(array($module1->cmid, $module2->cmid)); + + // Check the forum was deleted. + $this->assertEquals(0, $DB->count_records('forum', array('id' => $module1->id))); + + // Check the assignment was deleted. + $this->assertEquals(0, $DB->count_records('assignment', array('id' => $module2->id))); + + // Check we retrieve no data in the course modules table. + $this->assertEquals(0, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2', + array('module1' => $module1->cmid, 'module2' => $module2->cmid))); + + // Call with non-existent course module id and ensure exception thrown. + try { + core_course_external::delete_modules(array('1337')); + $this->fail('Exception expected due to missing course module.'); + } catch (dml_missing_record_exception $e) { + $this->assertEquals('invalidrecord', $e->errorcode); + } + + // Create two modules. + $module1 = self::getDataGenerator()->create_module('forum', $record); + $module2 = self::getDataGenerator()->create_module('assignment', $record); + + // Since these modules were recreated the user will not have capabilities + // to delete them, ensure exception is thrown if they try. + try { + core_course_external::delete_modules(array($module1->cmid, $module2->cmid)); + $this->fail('Exception expected due to missing capability.'); + } catch (moodle_exception $e) { + $this->assertEquals('nopermissions', $e->errorcode); + } + + // Unenrol user from the course. + $enrol->unenrol_user($instance, $user->id); + + // Try and delete modules from the course the user was unenrolled in, make sure exception thrown. + try { + core_course_external::delete_modules(array($module1->cmid, $module2->cmid)); + $this->fail('Exception expected due to being unenrolled from the course.'); + } catch (moodle_exception $e) { + $this->assertEquals('requireloginerror', $e->errorcode); + } + } } diff --git a/lib/db/services.php b/lib/db/services.php index 1532b2b0ce4..cfd26ef0c6c 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -505,6 +505,15 @@ $functions = array( 'capabilities'=> 'moodle/course:delete', ), + 'core_course_delete_modules' => array( + 'classname' => 'core_course_external', + 'methodname' => 'delete_modules', + 'classpath' => 'course/externallib.php', + 'description' => 'Deletes all specified module instances', + 'type' => 'write', + 'capabilities' => 'moodle/course:manageactivities' + ), + 'core_course_duplicate_course' => array( 'classname' => 'core_course_external', 'methodname' => 'duplicate_course', diff --git a/lib/dmllib.php b/lib/dmllib.php index 005f30da4f0..a2229b9f439 100644 --- a/lib/dmllib.php +++ b/lib/dmllib.php @@ -24,10 +24,10 @@ * used to interact with the DB. All the dunctions in this library must be * generic and work against the major number of RDBMS possible. This is the * list of currently supported and tested DBs: mysql, postresql, mssql, oracle - + * * This library is automatically included by Moodle core so you never need to * include it yourself. - + * * For more info about the functions available in this library, please visit: * http://docs.moodle.org/en/DML_functions * (feel free to modify, improve and document such page, thanks!)