diff --git a/course/lib.php b/course/lib.php index 37a28627d1b..3eca82d2fa2 100644 --- a/course/lib.php +++ b/course/lib.php @@ -3244,6 +3244,23 @@ function duplicate_module($course, $cm, int $sectionid = null, bool $changename // The following line is to be removed in MDL-58906. course_module_update_calendar_events($newcm->modname, null, $newcm); + // Copy permission overrides to new course module. + $newcmcontext = context_module::instance($newcm->id); + $overrides = $DB->get_records('role_capabilities', ['contextid' => $cmcontext->id]); + foreach ($overrides as $override) { + $override->contextid = $newcmcontext->id; + unset($override->id); + $DB->insert_record('role_capabilities', $override); + } + + // Copy locally assigned roles to new course module. + $overrides = $DB->get_records('role_assignments', ['contextid' => $cmcontext->id]); + foreach ($overrides as $override) { + $override->contextid = $newcmcontext->id; + unset($override->id); + $DB->insert_record('role_assignments', $override); + } + // Trigger course module created event. We can trigger the event only if we know the newcmid. $newcm = get_fast_modinfo($cm->course)->get_cm($newcmid); $event = \core\event\course_module_created::create_from_cm($newcm); diff --git a/course/tests/courselib_test.php b/course/tests/courselib_test.php index f811ec9ac6b..cbdbb136e13 100644 --- a/course/tests/courselib_test.php +++ b/course/tests/courselib_test.php @@ -3058,6 +3058,81 @@ class courselib_test extends advanced_testcase { } } + /** + * Test that permissions are duplicated correctly after duplicate_module(). + * @covers ::duplicate_module + * @return void + */ + public function test_duplicate_module_permissions(): void { + global $DB; + $this->setAdminUser(); + $this->resetAfterTest(); + + // Create course and course module. + $course = self::getDataGenerator()->create_course(); + $res = self::getDataGenerator()->create_module('assign', ['course' => $course]); + $cm = get_coursemodule_from_id('assign', $res->cmid, 0, false, MUST_EXIST); + $cmcontext = \context_module::instance($cm->id); + + // Enrol student user. + $user = self::getDataGenerator()->create_user(); + $roleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST); + self::getDataGenerator()->enrol_user($user->id, $course->id, $roleid); + + // Add capability to original course module. + assign_capability('gradereport/grader:view', CAP_ALLOW, $roleid, $cmcontext->id); + + // Duplicate module. + $newcm = duplicate_module($course, $cm); + $newcmcontext = \context_module::instance($newcm->id); + + // Assert that user still has capability. + $this->assertTrue(has_capability('gradereport/grader:view', $newcmcontext, $user)); + + // Assert that both modules contain the same count of overrides. + $overrides = $DB->get_records('role_capabilities', ['contextid' => $cmcontext->id]); + $newoverrides = $DB->get_records('role_capabilities', ['contextid' => $newcmcontext->id]); + $this->assertEquals(count($overrides), count($newoverrides)); + } + + /** + * Test that locally assigned roles are duplicated correctly after duplicate_module(). + * @covers ::duplicate_module + * @return void + */ + public function test_duplicate_module_role_assignments(): void { + global $DB; + $this->setAdminUser(); + $this->resetAfterTest(); + + // Create course and course module. + $course = self::getDataGenerator()->create_course(); + $res = self::getDataGenerator()->create_module('assign', ['course' => $course]); + $cm = get_coursemodule_from_id('assign', $res->cmid, 0, false, MUST_EXIST); + $cmcontext = \context_module::instance($cm->id); + + // Enrol student user. + $user = self::getDataGenerator()->create_user(); + $roleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST); + self::getDataGenerator()->enrol_user($user->id, $course->id, $roleid); + + // Assign user a new local role. + $newroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher'], MUST_EXIST); + role_assign($newroleid, $user->id, $cmcontext->id); + + // Duplicate module. + $newcm = duplicate_module($course, $cm); + $newcmcontext = \context_module::instance($newcm->id); + + // Assert that user still has role assigned. + $this->assertTrue(user_has_role_assignment($user->id, $newroleid, $newcmcontext->id)); + + // Assert that both modules contain the same count of overrides. + $overrides = $DB->get_records('role_assignments', ['contextid' => $cmcontext->id]); + $newoverrides = $DB->get_records('role_assignments', ['contextid' => $newcmcontext->id]); + $this->assertEquals(count($overrides), count($newoverrides)); + } + /** * Tests that when creating or updating a module, if the availability settings * are present but set to an empty tree, availability is set to null in diff --git a/mod/assign/tests/behat/duplicate_permissions.feature b/mod/assign/tests/behat/duplicate_permissions.feature new file mode 100644 index 00000000000..5d560386727 --- /dev/null +++ b/mod/assign/tests/behat/duplicate_permissions.feature @@ -0,0 +1,62 @@ +@mod @mod_assign +Feature: Duplicate assign activity module with permissions + In order to ensure that locally assigned roles and permissions are correctly duplicated + As a teacher + I need to add the roles and permissions and ensure they are correctly duplicated + + Background: + Given the following "courses" exist: + | fullname | shortname | category | groupmode | + | Course 1 | C1 | 0 | 1 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student10@example.com | + | student2 | Student | 2 | student20@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + | student2 | C1 | student | + And the following "activity" exists: + | activity | assign | + | course | C1 | + | name | Test assignment name | + | intro | Test assignment description | + | markingworkflow | 1 | + | submissiondrafts | 0 | + + @javascript + Scenario: Add a locally assigned role and duplicate activity + Given I log in as "teacher 1" + And I am on "Course 1" course homepage with editing mode on + + When I open "Test assignment name" actions menu + And I click on "Assign roles" "link" in the "Test assignment name" activity + And I click on "Non-editing teacher" "link" + And I click on "Student 2" "option" + And I click on "Add" "button" + And I am on "Course 1" course homepage with editing mode on + And I duplicate "Test assignment name" activity + Then I should see "Test assignment name (copy)" + + And I open "Test assignment name (copy)" actions menu + And I click on "Assign roles" "link" in the "Test assignment name (copy)" activity + Then "Non-editing teacher" row "Users with role" column of "generaltable" table should contain "1" + + @javascript + Scenario: Add a permission override to activity and duplicate + Given the following "permission overrides" exist: + | capability | permission | role | contextlevel | reference | + | mod/assign:grade | Allow | student | 70 | Test assignment name | + And I log in as "admin" + + When I am on "Course 1" course homepage with editing mode on + And I duplicate "Test assignment name" activity + Then I should see "Test assignment name (copy)" + + And I open "Test assignment name (copy)" actions menu + And I click on "Edit settings" "link" in the "Test assignment name (copy)" activity + And I navigate to "Permissions" in current page administration + And I set the field "permissionscapabilitysearch" to "mod/assign:grade" + Then "mod/assign:grade" row "Roles with permission" column of "permissions" table should contain "Student"