diff --git a/enrol/meta/lib.php b/enrol/meta/lib.php index 27f68b7762c..7558803b728 100644 --- a/enrol/meta/lib.php +++ b/enrol/meta/lib.php @@ -170,4 +170,82 @@ class enrol_meta_plugin extends enrol_plugin { $context = context_course::instance($instance->courseid); return has_capability('enrol/meta:config', $context); } + + /** + * Restore instance and map settings. + * + * @param restore_enrolments_structure_step $step + * @param stdClass $data + * @param stdClass $course + * @param int $oldid + */ + public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) { + global $DB, $CFG; + + if (!$step->get_task()->is_samesite()) { + // No meta restore from other sites. + $step->set_mapping('enrol', $oldid, 0); + return; + } + + if (!empty($data->customint2)) { + $data->customint2 = $step->get_mappingid('group', $data->customint2); + } + + if ($DB->record_exists('course', array('id' => $data->customint1))) { + $instance = $DB->get_record('enrol', array('roleid' => $data->roleid, 'customint1' => $data->customint1, + 'courseid' => $course->id, 'enrol' => $this->get_name())); + if ($instance) { + $instanceid = $instance->id; + } else { + $instanceid = $this->add_instance($course, (array)$data); + } + $step->set_mapping('enrol', $oldid, $instanceid); + + require_once("$CFG->dirroot/enrol/meta/locallib.php"); + enrol_meta_sync($data->customint1); + + } else { + $step->set_mapping('enrol', $oldid, 0); + } + } + + /** + * Restore user enrolment. + * + * @param restore_enrolments_structure_step $step + * @param stdClass $data + * @param stdClass $instance + * @param int $userid + * @param int $oldinstancestatus + */ + public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) { + global $DB; + + if ($this->get_config('unenrolaction') != ENROL_EXT_REMOVED_SUSPENDNOROLES) { + // Enrolments were already synchronised in restore_instance(), we do not want any suspended leftovers. + return; + } + + // ENROL_EXT_REMOVED_SUSPENDNOROLES means all previous enrolments are restored + // but without roles and suspended. + + if (!$DB->record_exists('user_enrolments', array('enrolid' => $instance->id, 'userid' => $userid))) { + $this->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, ENROL_USER_SUSPENDED); + if ($instance->customint2) { + groups_add_member($instance->customint2, $userid, 'enrol_meta', $instance->id); + } + } + } + + /** + * Restore user group membership. + * @param stdClass $instance + * @param int $groupid + * @param int $userid + */ + public function restore_group_member($instance, $groupid, $userid) { + // Nothing to do here, the group members are added in $this->restore_group_restored(). + return; + } } diff --git a/enrol/meta/locallib.php b/enrol/meta/locallib.php index 6c4c0ce3422..d79fed32600 100644 --- a/enrol/meta/locallib.php +++ b/enrol/meta/locallib.php @@ -150,13 +150,11 @@ class enrol_meta_handler { } } - // enrol user if not enrolled yet or fix status if ($ue) { if ($parentstatus != $ue->status) { $plugin->update_user_enrol($instance, $userid, $parentstatus); $ue->status = $parentstatus; - groups_add_member($instance->customint2, $userid, 'enrol_meta', $instance->courseid); } } else { $plugin->enrol_user($instance, $userid, NULL, 0, 0, $parentstatus); @@ -164,7 +162,9 @@ class enrol_meta_handler { $ue->userid = $userid; $ue->enrolid = $instance->id; $ue->status = $parentstatus; - groups_add_member($instance->customint2, $userid, 'enrol_meta', $instance->courseid); + if ($instance->customint2) { + groups_add_member($instance->customint2, $userid, 'enrol_meta', $instance->id); + } } $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES); @@ -174,7 +174,6 @@ class enrol_meta_handler { if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) { // Always keep the roles. } else if ($roles) { - groups_remove_member($instance->customint2, $userid); role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id)); } return; @@ -184,7 +183,6 @@ class enrol_meta_handler { foreach ($parentroles as $rid) { if (!isset($roles[$rid])) { role_assign($rid, $userid, $context->id, 'enrol_meta', $instance->id); - groups_add_member($instance->customint2, $userid, 'enrol_meta', $instance->courseid); } } @@ -232,8 +230,6 @@ class enrol_meta_handler { if ($ue->status != ENROL_USER_SUSPENDED) { $plugin->update_user_enrol($instance, $userid, ENROL_USER_SUSPENDED); } - // Remove from metagroup. - groups_remove_member($instance->customint2, $userid); role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id)); } else { @@ -318,6 +314,9 @@ function enrol_meta_sync($courseid = NULL, $verbose = false) { } $meta->enrol_user($instance, $ue->userid, $ue->status); + if ($instance->customint2) { + groups_add_member($instance->customint2, $ue->userid, 'enrol_meta', $instance->id); + } if ($verbose) { mtrace(" enrolling: $ue->userid ==> $instance->courseid"); } @@ -568,7 +567,9 @@ function enrol_meta_sync($courseid = NULL, $verbose = false) { $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $gm) { groups_remove_member($gm->groupid, $gm->userid); - mtrace("removing user from group: $gm->userid ==> $gm->courseid - $gm->groupname", 1); + if ($verbose) { + mtrace("removing user from group: $gm->userid ==> $gm->courseid - $gm->groupname", 1); + } } $rs->close(); @@ -586,7 +587,9 @@ function enrol_meta_sync($courseid = NULL, $verbose = false) { $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $ue) { groups_add_member($ue->groupid, $ue->userid, 'enrol_meta', $ue->enrolid); - mtrace("adding user to group: $ue->userid ==> $ue->courseid - $ue->groupname", 1); + if ($verbose) { + mtrace("adding user to group: $ue->userid ==> $ue->courseid - $ue->groupname", 1); + } } $rs->close(); diff --git a/enrol/meta/tests/behat/enrol_meta.feature b/enrol/meta/tests/behat/enrol_meta.feature new file mode 100644 index 00000000000..21582bef189 --- /dev/null +++ b/enrol/meta/tests/behat/enrol_meta.feature @@ -0,0 +1,107 @@ +@enrol @enrol_meta +Feature: Enrolments are synchronised with meta courses + In order to simplify enrolments in parent courses + As a teacher + I need to be able to set up meta enrolments + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | student1 | Student | 1 | student1@asd.com | + | student2 | Student | 2 | student2@asd.com | + | student3 | Student | 3 | student3@asd.com | + | student4 | Student | 4 | student4@asd.com | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + | Course 2 | C2 | + | Course 3 | C3 | + And the following "groups" exist: + | name | course | idnumber | + | Groupcourse 1 | C3 | G1 | + | Groupcourse 2 | C3 | G2 | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + | student2 | C1 | student | + | student3 | C1 | student | + | student4 | C1 | student | + | student1 | C2 | student | + | student2 | C2 | student | + And I log in as "admin" + And I navigate to "Manage enrol plugins" node in "Site administration > Plugins > Enrolments" + And I click on "Enable" "link" in the "Course meta link" "table_row" + And I am on homepage + And I follow "Courses" + + @javascript + Scenario: Add meta enrolment instance without groups + When I follow "Course 3" + And I add "Course meta link" enrolment method with: + | Link course | Course 1 | + And I navigate to "Enrolled users" node in "Course administration > Users" + Then I should see "Student 1" + And I should see "Student 4" + And I should not see "Groupcourse" in the "table.userenrolment" "css_element" + + @javascript + Scenario: Add meta enrolment instance with groups + When I follow "Course 3" + And I navigate to "Enrolment methods" node in "Course administration > Users" + And I set the field "Add method" to "Course meta link" + And I wait to be redirected + And I set the following fields to these values: + | Link course | Course 1 | + | Add to group | Groupcourse 1 | + And I press "Add method and create another" + And I set the following fields to these values: + | Link course | Course 2 | + | Add to group | Groupcourse 2 | + And I press "Add method" + And I navigate to "Enrolled users" node in "Course administration > Users" + Then I should see "Groupcourse 1" in the "Student 1" "table_row" + And I should see "Groupcourse 1" in the "Student 2" "table_row" + And I should see "Groupcourse 1" in the "Student 3" "table_row" + And I should see "Groupcourse 1" in the "Student 4" "table_row" + And I should see "Groupcourse 2" in the "Student 1" "table_row" + And I should see "Groupcourse 2" in the "Student 2" "table_row" + And I should not see "Groupcourse 2" in the "Student 3" "table_row" + And I should not see "Groupcourse 2" in the "Student 4" "table_row" + + @javascript + Scenario: Backup and restore of meta enrolment instance + When I follow "Course 3" + And I navigate to "Enrolment methods" node in "Course administration > Users" + And I set the field "Add method" to "Course meta link" + And I wait to be redirected + And I set the following fields to these values: + | Link course | Course 1 | + | Add to group | Groupcourse 1 | + And I press "Add method and create another" + And I set the following fields to these values: + | Link course | Course 2 | + And I press "Add method" + When I backup "Course 3" course using this options: + | Confirmation | Filename | test_backup.mbz | + And I click on "Restore" "link" in the "test_backup.mbz" "table_row" + And I press "Continue" + And I set the field "targetid" to "1" + And I click on "Continue" "button" in the ".bcs-new-course" "css_element" + And I press "Next" + And I set the field "Course name" to "Course 4" + And I press "Next" + And I press "Perform restore" + And I trigger cron + And I am on homepage + And I follow "Courses" + And I follow "Course 4" + And I navigate to "Enrolment methods" node in "Course administration > Users" + Then I should see "Course meta link (Course 1)" + And I should see "Course meta link (Course 2)" + And I navigate to "Enrolled users" node in "Course administration > Users" + And I should see "Groupcourse 1" in the "Student 1" "table_row" + And I should see "Groupcourse 1" in the "Student 2" "table_row" + And I should see "Groupcourse 1" in the "Student 3" "table_row" + And I should see "Groupcourse 1" in the "Student 4" "table_row" + And I should see "Course 2" in the "Student 1" "table_row" + And I should not see "Course 2" in the "Student 3" "table_row" diff --git a/enrol/meta/tests/plugin_test.php b/enrol/meta/tests/plugin_test.php index e4a0f2cb33f..77b5065b4e4 100644 --- a/enrol/meta/tests/plugin_test.php +++ b/enrol/meta/tests/plugin_test.php @@ -100,13 +100,6 @@ class enrol_meta_plugin_testcase extends advanced_testcase { $teacher = $DB->get_record('role', array('shortname'=>'teacher')); $manager = $DB->get_record('role', array('shortname'=>'manager')); - $id = groups_create_group((object)array('name'=>'Group 1', 'courseid'=>$course1->id)); - $group1 = $DB->get_record('groups', array('id'=>$id), '*', MUST_EXIST); - $id = groups_create_group((object)array('name'=>'Group 2', 'courseid'=>$course1->id)); - $group2 = $DB->get_record('groups', array('id'=>$id), '*', MUST_EXIST); - $id = groups_create_group((object)array('name'=>'Group 3', 'courseid'=>$course2->id)); - $group3 = $DB->get_record('groups', array('id'=>$id), '*', MUST_EXIST); - $this->disable_plugin(); $this->getDataGenerator()->enrol_user($user1->id, $course1->id, $student->id); @@ -135,14 +128,12 @@ class enrol_meta_plugin_testcase extends advanced_testcase { $this->assertEquals(7, $DB->count_records('user_enrolments')); $this->assertEquals(6, $DB->count_records('role_assignments')); - $e1 = $metalplugin->add_instance($course3, array('customint1'=>$course1->id, 'customint2'=>$group1->id)); - $e2 = $metalplugin->add_instance($course3, array('customint1'=>$course2->id, 'customint2'=>$group2->id)); + $e1 = $metalplugin->add_instance($course3, array('customint1'=>$course1->id)); + $e2 = $metalplugin->add_instance($course3, array('customint1'=>$course2->id)); $e3 = $metalplugin->add_instance($course4, array('customint1'=>$course2->id)); - $e4 = $metalplugin->add_instance($course4, array('customint1'=>$course2->id, 'customint2'=>$group3->id)); $enrol1 = $DB->get_record('enrol', array('id'=>$e1)); $enrol2 = $DB->get_record('enrol', array('id'=>$e2)); $enrol3 = $DB->get_record('enrol', array('id'=>$e3)); - $enrol4 = $DB->get_record('enrol', array('id'=>$e3)); enrol_meta_sync($course4->id, false); $this->assertEquals(9, $DB->count_records('user_enrolments')); @@ -185,34 +176,6 @@ class enrol_meta_plugin_testcase extends advanced_testcase { $this->enable_plugin(); - $this->assertTrue(is_enrolled(context_course::instance($course1->id), $user4)); - $this->assertTrue(groups_add_member($group1, $user4)); - $this->assertTrue(groups_add_member($group2, $user4)); - - $this->assertFalse(groups_is_member($group1->id, $user1->id)); - groups_add_member($group1->id, $user1->id); - $this->assertTrue(groups_is_member($group1->id, $user1->id)); - $this->assertTrue($DB->record_exists('groups_members', array('groupid'=>$group1->id, 'userid'=>$user1->id, 'component'=>'enrol_cohort', 'itemid'=>$cohortinstance1->id))); - - groups_add_member($group1->id, $user4->id); - $this->assertTrue(groups_is_member($group1->id, $user4->id)); - $this->assertFalse($DB->record_exists('groups_members', array('groupid'=>$group1->id, 'userid'=>$user4->id, 'component'=>'enrol_cohort', 'itemid'=>$cohortinstance1->id))); - - set_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL, 'enrol_meta'); - - groups_remove_member($group1->id, $user1->id); - $this->assertFalse(groups_is_member($group1->id, $user1->id)); - - groups_remove_member($group1->id, $user4->id); - $this->assertTrue(groups_is_member($group1->id, $user4->id)); - $this->assertTrue(groups_is_member($group2->id, $user4->id)); - - set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES, 'enrol_meta'); - groups_add_member($group1->id, $user1->id); - - groups_remove_member($group1->id, $user1->id); - $this->assertTrue(groups_is_member($group1->id, $user1->id)); - set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPEND, 'enrol_meta'); enrol_meta_sync($course4->id, false); $this->assertEquals(14, $DB->count_records('user_enrolments')); @@ -477,6 +440,98 @@ class enrol_meta_plugin_testcase extends advanced_testcase { } + public function test_add_to_group() { + global $CFG, $DB; + + require_once($CFG->dirroot.'/group/lib.php'); + + $this->resetAfterTest(true); + + $metalplugin = enrol_get_plugin('meta'); + + $user1 = $this->getDataGenerator()->create_user(); + $user4 = $this->getDataGenerator()->create_user(); + + $course1 = $this->getDataGenerator()->create_course(); + $course2 = $this->getDataGenerator()->create_course(); + $course3 = $this->getDataGenerator()->create_course(); + $manualenrol1 = $DB->get_record('enrol', array('courseid' => $course1->id, 'enrol' => 'manual'), '*', MUST_EXIST); + $manualenrol2 = $DB->get_record('enrol', array('courseid' => $course2->id, 'enrol' => 'manual'), '*', MUST_EXIST); + + $student = $DB->get_record('role', array('shortname' => 'student')); + $teacher = $DB->get_record('role', array('shortname' => 'teacher')); + + $id = groups_create_group((object)array('name' => 'Group 1 in course 3', 'courseid' => $course3->id)); + $group31 = $DB->get_record('groups', array('id' => $id), '*', MUST_EXIST); + $id = groups_create_group((object)array('name' => 'Group 2 in course 4', 'courseid' => $course3->id)); + $group32 = $DB->get_record('groups', array('id' => $id), '*', MUST_EXIST); + + $this->enable_plugin(); + + $e1 = $metalplugin->add_instance($course3, array('customint1' => $course1->id, 'customint2' => $group31->id)); + $e2 = $metalplugin->add_instance($course3, array('customint1' => $course2->id, 'customint2' => $group32->id)); + + $this->getDataGenerator()->enrol_user($user1->id, $course1->id, $student->id); + $this->getDataGenerator()->enrol_user($user4->id, $course1->id, $teacher->id); + + $this->getDataGenerator()->enrol_user($user1->id, $course2->id, $student->id); + + // Now make sure users are in the correct groups. + $this->assertTrue(groups_is_member($group31->id, $user1->id)); + $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group31->id, 'userid' => $user1->id, + 'component' => 'enrol_meta', 'itemid' => $e1))); + $this->assertTrue(groups_is_member($group32->id, $user1->id)); + $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group32->id, 'userid' => $user1->id, + 'component' => 'enrol_meta', 'itemid' => $e2))); + + $this->assertTrue(groups_is_member($group31->id, $user4->id)); + $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group31->id, 'userid' => $user4->id, + 'component' => 'enrol_meta', 'itemid' => $e1))); + + // Make sure everything is the same after sync. + enrol_meta_sync(null, false); + $this->assertTrue(groups_is_member($group31->id, $user1->id)); + $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group31->id, 'userid' => $user1->id, + 'component' => 'enrol_meta', 'itemid' => $e1))); + $this->assertTrue(groups_is_member($group32->id, $user1->id)); + $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group32->id, 'userid' => $user1->id, + 'component' => 'enrol_meta', 'itemid' => $e2))); + + $this->assertTrue(groups_is_member($group31->id, $user4->id)); + $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group31->id, 'userid' => $user4->id, + 'component' => 'enrol_meta', 'itemid' => $e1))); + + set_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL, 'enrol_meta'); + + // When user 1 is unenrolled from course1, he is removed from group31 but still present in group32. + enrol_get_plugin('manual')->unenrol_user($manualenrol1, $user1->id); + $this->assertFalse(groups_is_member($group31->id, $user1->id)); + $this->assertTrue(groups_is_member($group32->id, $user1->id)); + $this->assertTrue(is_enrolled(context_course::instance($course3->id), $user1, '', true)); // He still has active enrolment. + // And the same after sync. + enrol_meta_sync(null, false); + $this->assertFalse(groups_is_member($group31->id, $user1->id)); + $this->assertTrue(groups_is_member($group32->id, $user1->id)); + $this->assertTrue(is_enrolled(context_course::instance($course3->id), $user1, '', true)); + + // Unenroll user1 from course2 and make sure he is completely unenrolled from course3. + enrol_get_plugin('manual')->unenrol_user($manualenrol2, $user1->id); + $this->assertFalse(groups_is_member($group32->id, $user1->id)); + $this->assertFalse(is_enrolled(context_course::instance($course3->id), $user1)); + + set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES, 'enrol_meta'); + + // When user is unenrolled in this case, he is still a member of a group (but enrolment is suspended). + enrol_get_plugin('manual')->unenrol_user($manualenrol1, $user4->id); + $this->assertTrue(groups_is_member($group31->id, $user4->id)); + $this->assertTrue(is_enrolled(context_course::instance($course3->id), $user4)); + $this->assertFalse(is_enrolled(context_course::instance($course3->id), $user4, '', true)); + enrol_meta_sync(null, false); + $this->assertTrue(groups_is_member($group31->id, $user4->id)); + $this->assertTrue(is_enrolled(context_course::instance($course3->id), $user4)); + $this->assertFalse(is_enrolled(context_course::instance($course3->id), $user4, '', true)); + } + /** * Test user_enrolment_created event. */