From 30f3ea7fe170578f001653ae71e88ece17145abe Mon Sep 17 00:00:00 2001 From: cescobedo Date: Mon, 14 May 2018 10:49:16 +0200 Subject: [PATCH] MDL-62357 enrol_cohort: Add privacy implementation for enrol_cohort --- enrol/cohort/classes/privacy/provider.php | 92 +++++++- enrol/cohort/lang/en/enrol_cohort.php | 2 +- enrol/cohort/tests/privacy_test.php | 252 ++++++++++++++++++++++ 3 files changed, 338 insertions(+), 8 deletions(-) create mode 100644 enrol/cohort/tests/privacy_test.php diff --git a/enrol/cohort/classes/privacy/provider.php b/enrol/cohort/classes/privacy/provider.php index 7f0b93246b1..9380cb89b31 100644 --- a/enrol/cohort/classes/privacy/provider.php +++ b/enrol/cohort/classes/privacy/provider.php @@ -22,20 +22,98 @@ */ namespace enrol_cohort\privacy; defined('MOODLE_INTERNAL') || die(); +use \core_privacy\local\metadata\collection; +use \core_privacy\local\request\contextlist; +use \core_privacy\local\request\approved_contextlist; + /** - * Privacy Subsystem for enrol_cohort implementing null_provider. + * Privacy provider for enrol_cohort. * * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements + \core_privacy\local\metadata\provider, + \core_privacy\local\request\plugin\provider { /** - * Get the language string identifier with the component's language - * file to explain why this plugin stores no data. + * Returns meta data about this system. * - * @return string + * @param collection $collection The initialised item collection to add items to. + * @return collection A listing of user data stored through this system. */ - public static function get_reason() : string { - return 'privacy:metadata'; + public static function get_metadata(collection $collection) : collection { + + $collection->add_subsystem_link('core_group', [], 'privacy:metadata:core_group'); + return $collection; + } + /** + * Get the list of contexts that contain user information for the specified user. + * + * @param int $userid The user to search. + * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. + */ + public static function get_contexts_for_userid(int $userid) : contextlist { + $contextlist = new contextlist(); + + $sql = "SELECT ctx.id + FROM {groups_members} gm + JOIN {groups} g ON gm.groupid = g.id + JOIN {context} ctx ON g.courseid = ctx.instanceid AND ctx.contextlevel = :contextlevel + WHERE gm.userid = :userid + AND gm.component = 'enrol_cohort'"; + + $params = [ + 'contextlevel' => CONTEXT_COURSE, + 'userid' => $userid + ]; + + $contextlist->add_from_sql($sql, $params); + + return $contextlist; + } + /** + * Export all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + */ + public static function export_user_data(approved_contextlist $contextlist) { + if (empty($contextlist)) { + return; + } + foreach ($contextlist as $context) { + if ($context->contextlevel == CONTEXT_COURSE) { + \core_group\privacy\provider::export_groups( + $context, + 'enrol_cohort', + [get_string('pluginname', 'enrol_cohort')] + ); + } + } + } + + /** + * Delete all use data which matches the specified deletion_criteria. + * + * @param context $context A user context. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + if (empty($context)) { + return; + } + if ($context->contextlevel == CONTEXT_COURSE) { + // Delete all the associated groups. + \core_group\privacy\provider::delete_groups_for_all_users($context, 'enrol_cohort'); + } + } + /** + * Delete all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. + */ + public static function delete_data_for_user(approved_contextlist $contextlist) { + if (empty($contextlist->count())) { + return; + } + \core_group\privacy\provider::delete_groups_for_user($contextlist, 'enrol_cohort'); } } \ No newline at end of file diff --git a/enrol/cohort/lang/en/enrol_cohort.php b/enrol/cohort/lang/en/enrol_cohort.php index ffc090589d8..86e04261bd2 100644 --- a/enrol/cohort/lang/en/enrol_cohort.php +++ b/enrol/cohort/lang/en/enrol_cohort.php @@ -33,4 +33,4 @@ $string['pluginname'] = 'Cohort sync'; $string['pluginname_desc'] = 'Cohort enrolment plugin synchronises cohort members with course participants.'; $string['status'] = 'Active'; $string['creategroup'] = 'Create new group'; -$string['privacy:metadata'] = 'The Cohort sync enrolment plugin does not store any personal data.'; +$string['privacy:metadata:core_group'] = 'Enrol cohort plugin can create a new group or use an existing group to add all the members of the cohort.'; diff --git a/enrol/cohort/tests/privacy_test.php b/enrol/cohort/tests/privacy_test.php new file mode 100644 index 00000000000..abb38b00ec1 --- /dev/null +++ b/enrol/cohort/tests/privacy_test.php @@ -0,0 +1,252 @@ +. +/** + * Base class for unit tests for enrol_cohort. + * + * @package enrol_cohort + * @category test + * @copyright 2018 Carlos Escobedo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +defined('MOODLE_INTERNAL') || die(); +use \core_privacy\local\request\writer; +use \core_privacy\local\request\approved_contextlist; +use \enrol_cohort\privacy\provider; +/** + * Unit tests for the enrol_cohort implementation of the privacy API. + * + * @copyright 2018 Carlos Escobedo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class enrol_cohort_privacy_testcase extends \core_privacy\tests\provider_testcase { + /** + * Test getting the context for the user ID related to this plugin. + */ + public function test_get_contexts_for_userid() { + global $DB; + + $this->resetAfterTest(); + $trace = new null_progress_trace(); + + $cohortplugin = enrol_get_plugin('cohort'); + $user1 = $this->getDataGenerator()->create_user(); + $cat1 = $this->getDataGenerator()->create_category(); + $course1 = $this->getDataGenerator()->create_course(array('category' => $cat1->id)); + $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course1->id)); + $studentrole = $DB->get_record('role', array('shortname' => 'student')); + $cohort1 = $this->getDataGenerator()->create_cohort( + array('contextid' => context_coursecat::instance($cat1->id)->id)); + $cohortplugin->add_instance($course1, array( + 'customint1' => $cohort1->id, + 'roleid' => $studentrole->id, + 'customint2' => $group1->id) + ); + + cohort_add_member($cohort1->id, $user1->id); + enrol_cohort_sync($trace, $course1->id); + // Check if user1 is enrolled into course1 in group 1. + $this->assertEquals(1, $DB->count_records('role_assignments', array())); + $this->assertTrue($DB->record_exists('groups_members', array( + 'groupid' => $group1->id, + 'userid' => $user1->id, + 'component' => 'enrol_cohort') + )); + // Check context course fro provider to user1. + $context = \context_course::instance($course1->id); + $contextlist = provider::get_contexts_for_userid($user1->id); + $this->assertEquals($context->id, $contextlist->current()->id); + } + + /** + * Test that user data is exported correctly. + */ + public function test_export_user_data() { + global $DB; + + $this->resetAfterTest(); + $trace = new null_progress_trace(); + + $cohortplugin = enrol_get_plugin('cohort'); + $user1 = $this->getDataGenerator()->create_user(); + $cat1 = $this->getDataGenerator()->create_category(); + $course1 = $this->getDataGenerator()->create_course(array('category' => $cat1->id)); + $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course1->id)); + $studentrole = $DB->get_record('role', array('shortname' => 'student')); + $cohort1 = $this->getDataGenerator()->create_cohort( + array('contextid' => context_coursecat::instance($cat1->id)->id)); + $cohortplugin->add_instance($course1, array( + 'customint1' => $cohort1->id, + 'roleid' => $studentrole->id, + 'customint2' => $group1->id) + ); + + cohort_add_member($cohort1->id, $user1->id); + enrol_cohort_sync($trace, $course1->id); + // Check if user1 is enrolled into course1 in group 1. + $this->assertEquals(1, $DB->count_records('role_assignments', array())); + $this->assertTrue($DB->record_exists('groups_members', array( + 'groupid' => $group1->id, + 'userid' => $user1->id, + 'component' => 'enrol_cohort') + )); + + $this->setUser($user1); + $contextlist = provider::get_contexts_for_userid($user1->id); + $approvedcontextlist = new approved_contextlist($user1, 'enrol_cohort', $contextlist->get_contextids()); + provider::export_user_data($approvedcontextlist); + foreach ($contextlist as $context) { + $writer = writer::with_context($context); + $data = $writer->get_data([ + get_string('pluginname', 'enrol_cohort'), + get_string('groups', 'core_group') + ]); + $this->assertTrue($writer->has_any_data()); + if ($context->contextlevel == CONTEXT_COURSE) { + $exportedgroups = $data->groups; + // User1 only belongs to group1 via enrol_cohort. + $this->assertCount(1, $exportedgroups); + $exportedgroup = reset($exportedgroups); + $this->assertEquals($group1->name, $exportedgroup->name); + } + } + } + /** + * Test for provider::delete_data_for_all_users_in_context(). + */ + public function test_delete_data_for_all_users_in_context() { + global $DB; + + $this->resetAfterTest(); + $trace = new null_progress_trace(); + + $cohortplugin = enrol_get_plugin('cohort'); + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(); + $cat1 = $this->getDataGenerator()->create_category(); + $course1 = $this->getDataGenerator()->create_course(array('category' => $cat1->id)); + $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course1->id)); + $studentrole = $DB->get_record('role', array('shortname' => 'student')); + $cohort1 = $this->getDataGenerator()->create_cohort( + array('contextid' => context_coursecat::instance($cat1->id)->id)); + $cohortplugin->add_instance($course1, array( + 'customint1' => $cohort1->id, + 'roleid' => $studentrole->id, + 'customint2' => $group1->id) + ); + + cohort_add_member($cohort1->id, $user1->id); + cohort_add_member($cohort1->id, $user2->id); + cohort_add_member($cohort1->id, $user3->id); + enrol_cohort_sync($trace, $course1->id); + $this->assertEquals( + 3, + $DB->count_records_sql("SELECT COUNT(gm.id) + FROM {groups_members} gm + JOIN {groups} g ON gm.groupid = g.id + WHERE g.courseid = ?", [$course1->id]) + ); + + $coursecontext1 = context_course::instance($course1->id); + provider::delete_data_for_all_users_in_context($coursecontext1); + $this->assertEquals( + 0, + $DB->count_records_sql("SELECT COUNT(gm.id) + FROM {groups_members} gm + JOIN {groups} g ON gm.groupid = g.id + WHERE g.courseid = ?", [$course1->id]) + ); + } + /** + * Test for provider::delete_data_for_user(). + */ + public function test_delete_data_for_user() { + global $DB; + + $this->resetAfterTest(); + $trace = new null_progress_trace(); + + $cohortplugin = enrol_get_plugin('cohort'); + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(); + $cat1 = $this->getDataGenerator()->create_category(); + $course1 = $this->getDataGenerator()->create_course(array('category' => $cat1->id)); + $course2 = $this->getDataGenerator()->create_course(array('category' => $cat1->id)); + $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course1->id)); + $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course2->id)); + $studentrole = $DB->get_record('role', array('shortname' => 'student')); + $cohort1 = $this->getDataGenerator()->create_cohort( + array('contextid' => context_coursecat::instance($cat1->id)->id)); + $cohortplugin->add_instance($course1, array( + 'customint1' => $cohort1->id, + 'roleid' => $studentrole->id, + 'customint2' => $group1->id) + ); + $cohortplugin->add_instance($course2, array( + 'customint1' => $cohort1->id, + 'roleid' => $studentrole->id, + 'customint2' => $group2->id) + ); + + $this->getDataGenerator()->enrol_user($user2->id, $course1->id); + $this->getDataGenerator()->enrol_user($user3->id, $course1->id); + $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user2->id)); + $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user3->id)); + + cohort_add_member($cohort1->id, $user1->id); + enrol_cohort_sync($trace, $course1->id); + + $this->assertEquals( + 3, + $DB->count_records_sql("SELECT COUNT(gm.id) + FROM {groups_members} gm + JOIN {groups} g ON gm.groupid = g.id + WHERE g.courseid = ?", [$course1->id]) + ); + + $this->assertEquals( + 1, + $DB->count_records_sql("SELECT COUNT(gm.id) + FROM {groups_members} gm + JOIN {groups} g ON gm.groupid = g.id + WHERE g.courseid = ?", [$course2->id]) + ); + + $this->setUser($user1); + $coursecontext1 = context_course::instance($course1->id); + $coursecontext2 = context_course::instance($course2->id); + $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist($user1, 'enrol_cohort', + [$coursecontext1->id, $coursecontext2->id]); + provider::delete_data_for_user($approvedcontextlist); + // Check we have 2 users in groups because we are deleted user1. + $this->assertEquals( + 2, + $DB->count_records_sql("SELECT COUNT(gm.id) + FROM {groups_members} gm + JOIN {groups} g ON gm.groupid = g.id + WHERE g.courseid = ?", [$course1->id]) + ); + // Check we have not users in groups. + $this->assertEquals( + 0, + $DB->count_records_sql("SELECT COUNT(gm.id) + FROM {groups_members} gm + JOIN {groups} g ON gm.groupid = g.id + WHERE g.courseid = ?", [$course2->id]) + ); + } +} \ No newline at end of file -- 2.43.0