MDL-62318 core_group: Add implementation of privacy API
authorShamim Rezaie <shamim@moodle.com>
Fri, 4 May 2018 13:59:04 +0000 (23:59 +1000)
committerShamim Rezaie <shamim@moodle.com>
Wed, 9 May 2018 03:29:26 +0000 (13:29 +1000)
group/classes/privacy/provider.php [new file with mode: 0644]
group/tests/privacy_provider_test.php [new file with mode: 0644]
lang/en/group.php

diff --git a/group/classes/privacy/provider.php b/group/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..8c93dc6
--- /dev/null
@@ -0,0 +1,242 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for core_group.
+ *
+ * @package    core_group
+ * @category   privacy
+ * @copyright  2018 Shamim Rezaie <shamim@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_group\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_privacy\local\metadata\collection;
+use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\contextlist;
+use core_privacy\local\request\transform;
+
+/**
+ * Privacy Subsystem implementation for core_group.
+ *
+ * @copyright  2018 Shamim Rezaie <shamim@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+        // Groups store user data.
+        \core_privacy\local\metadata\provider,
+
+        // The group subsystem contains user's group memberships.
+        \core_privacy\local\request\subsystem\provider,
+
+        // The group subsystem can provide information to other plugins.
+        \core_privacy\local\request\subsystem\plugin_provider {
+
+    /**
+     * Returns meta data about this system.
+     *
+     * @param   collection $collection The initialised collection to add items to.
+     * @return  collection A listing of user data stored through this system.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        $collection->add_database_table('groups_members', [
+            'groupid' => 'privacy:metadata:groups:groupid',
+            'userid' => 'privacy:metadata:groups:userid',
+            'timeadded' => 'privacy:metadata:groups:timeadded',
+        ], 'privacy:metadata:groups');
+
+        return $collection;
+    }
+
+    /**
+     * Writes user data to the writer for the user to download.
+     *
+     * @param \context  $context    The context to export data for.
+     * @param string    $component  The component that is calling this function. Empty string means no component.
+     * @param array     $subcontext The sub-context in which to export this data.
+     * @param int       $itemid     Optional itemid associated with component.
+     */
+    public static function export_groups(\context $context, string $component, array $subcontext = [], int $itemid = 0) {
+        global $DB, $USER;
+
+        if (!$context instanceof \context_course) {
+            return;
+        }
+
+        $subcontext[] = get_string('groups', 'core_group');
+
+        $sql = "SELECT gm.id, gm.timeadded, gm.userid, g.name
+                  FROM {groups_members} gm
+                  JOIN {groups} g ON gm.groupid = g.id
+                 WHERE g.courseid = :courseid
+                       AND gm.component = :component
+                       AND gm.userid = :userid";
+        $params = [
+            'courseid'  => $context->instanceid,
+            'component' => $component,
+            'userid'    => $USER->id
+        ];
+
+        if ($itemid) {
+            $sql .= ' AND gm.itemid = :itemid';
+            $params['itemid'] = $itemid;
+        }
+
+        $groups = $DB->get_records_sql($sql, $params);
+
+        $groups = array_map(function($group) {
+            return (object) [
+                'name' => format_string($group->name),
+                'timeadded' => transform::datetime($group->timeadded),
+            ];
+        }, $groups);
+
+        if (!empty($groups)) {
+            \core_privacy\local\request\writer::with_context($context)
+                    ->export_data($subcontext, (object) [
+                        'groups' => $groups,
+                    ]);
+        }
+    }
+
+    /**
+     * Deletes all group memberships for a specified context and component.
+     *
+     * @param \context  $context    Details about which context to delete group memberships for.
+     * @param string    $component  Component to delete. Empty string means no component (manual group memberships).
+     * @param int       $itemid     Optional itemid associated with component.
+     */
+    public static function delete_groups_for_all_users(\context $context, string $component, int $itemid = 0) {
+        global $DB;
+
+        if (!$context instanceof \context_course) {
+            return;
+        }
+
+        if (!$DB->record_exists('groups', ['courseid' => $context->instanceid])) {
+            return;
+        }
+
+        $select = "component = :component AND groupid IN (SELECT g.id FROM {groups} g WHERE courseid = :courseid)";
+        $params = ['component' => $component, 'courseid' => $context->instanceid];
+
+        if ($itemid) {
+            $select .= ' AND itemid = :itemid';
+            $params['itemid'] = $itemid;
+        }
+
+        $DB->delete_records_select('groups_members', $select, $params);
+    }
+
+    /**
+     * Deletes all records for a user from a list of approved contexts.
+     *
+     * @param approved_contextlist  $contextlist    Contains the user ID and a list of contexts to be deleted from.
+     * @param string                $component      Component to delete from. Empty string means no component (manual memberships).
+     * @param int                   $itemid         Optional itemid associated with component.
+     */
+    public static function delete_groups_for_user(approved_contextlist $contextlist, string $component, int $itemid = 0) {
+        global $DB;
+
+        $userid = $contextlist->get_user()->id;
+
+        $contextids = $contextlist->get_contextids();
+
+        if (!$contextids) {
+            return;
+        }
+
+        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
+        $contextparams += ['contextcourse' => CONTEXT_COURSE];
+        $groupselect = "SELECT g.id
+                          FROM {groups} g
+                          JOIN {context} ctx ON g.courseid = ctx.instanceid AND ctx.contextlevel = :contextcourse
+                         WHERE ctx.id $contextsql";
+
+        if (!$DB->record_exists_sql($groupselect, $contextparams)) {
+            return;
+        }
+
+        $select = "userid = :userid AND component = :component AND groupid IN ({$groupselect})";
+        $params = ['userid' => $userid, 'component' => $component] + $contextparams;
+
+        if ($itemid) {
+            $select .= ' AND itemid = :itemid';
+            $params['itemid'] = $itemid;
+        }
+
+        $DB->delete_records_select('groups_members', $select, $params);
+    }
+
+    /**
+     * Get the list of contexts that contain user information for the specified user.
+     *
+     * @param   int $userid The user to search.
+     * @return  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 = :contextcourse
+                 WHERE gm.userid = :userid";
+
+        $params = [
+            'contextcourse' => 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) {
+        $contexts = $contextlist->get_contexts();
+
+        foreach ($contexts as $context) {
+            static::export_groups($context, '');
+        }
+    }
+
+    /**
+     * Delete all data for all users in the specified context.
+     *
+     * @param context $context The specific context to delete data for.
+     */
+    public static function delete_data_for_all_users_in_context(\context $context) {
+        static::delete_groups_for_all_users($context, '');
+    }
+
+    /**
+     * 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) {
+        static::delete_groups_for_user($contextlist, '');
+    }
+}
diff --git a/group/tests/privacy_provider_test.php b/group/tests/privacy_provider_test.php
new file mode 100644 (file)
index 0000000..207b562
--- /dev/null
@@ -0,0 +1,733 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy provider tests.
+ *
+ * @package    core_group
+ * @category   test
+ * @copyright  2018 Shamim Rezaie <shamim@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_privacy\tests\provider_testcase;
+use core_privacy\local\metadata\collection;
+use core_group\privacy\provider;
+use core_privacy\local\request\writer;
+
+/**
+ * Class core_group_privacy_provider_testcase.
+ *
+ * @copyright  2018 Shamim Rezaie <shamim@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_group_privacy_provider_testcase extends provider_testcase {
+
+    /**
+     * Test for provider::get_metadata().
+     */
+    public function test_get_metadata() {
+        $collection = new collection('core_group');
+        $newcollection = provider::get_metadata($collection);
+        $itemcollection = $newcollection->get_collection();
+        $this->assertCount(1, $itemcollection);
+
+        $table = reset($itemcollection);
+
+        $this->assertEquals('groups_members', $table->get_name());
+        $this->assertEquals('privacy:metadata:groups', $table->get_summary());
+
+        $privacyfields = $table->get_privacy_fields();
+        $this->assertArrayHasKey('groupid', $privacyfields);
+        $this->assertArrayHasKey('userid', $privacyfields);
+        $this->assertArrayHasKey('timeadded', $privacyfields);
+    }
+
+    /**
+     * Test for provider::export_groups() to export manual group memberships.
+     */
+    public function test_export_groups() {
+        $this->resetAfterTest();
+
+        $course = $this->getDataGenerator()->create_course();
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group3 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group4 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
+
+        // Add user1 to group1 and group2.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user1->id));
+
+        // Add user2 to group2 and group3.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user2->id));
+
+        $context = context_course::instance($course->id);
+
+        // Retrieve groups for user1.
+        $this->setUser($user1);
+        $writer = writer::with_context($context);
+        provider::export_groups($context, '');
+
+        $data = $writer->get_data([get_string('groups', 'core_group')]);
+        $exportedgroups = $data->groups;
+
+        // User1 belongs to group1 and group2.
+        $this->assertEquals(
+                [$group1->name, $group2->name],
+                array_column($exportedgroups, 'name'),
+                '', 0.0, 10, true);
+    }
+
+    /**
+     * Test for provider::export_groups() to export group memberships of a component.
+     */
+    public function test_export_groups_for_component() {
+        $this->resetAfterTest();
+
+        $course = $this->getDataGenerator()->create_course();
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group3 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group4 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group5 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, null, 'self');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, null, 'self');
+
+        // Add user1 to group1 (via enrol_self) and group2 and group3.
+        $this->getDataGenerator()->create_group_member(
+                array('groupid' => $group1->id, 'userid' => $user1->id, 'component' => 'enrol_self'));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user1->id));
+
+        // Add user2 to group3 (via enrol_self) and group4.
+        $this->getDataGenerator()->create_group_member(
+                array('groupid' => $group3->id, 'userid' => $user2->id, 'component' => 'enrol_self'));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group4->id, 'userid' => $user2->id));
+
+        $context = context_course::instance($course->id);
+
+        // Retrieve groups for user1.
+        $this->setUser($user1);
+        $writer = writer::with_context($context);
+        provider::export_groups($context, 'enrol_self');
+
+        $data = $writer->get_data([get_string('groups', 'core_group')]);
+        $exportedgroups = $data->groups;
+
+        // User1 only belongs to group1 via enrol_self.
+        $this->assertCount(1, $exportedgroups);
+        $exportedgroup = reset($exportedgroups);
+        $this->assertEquals($group1->name, $exportedgroup->name);
+    }
+
+    /**
+     * Test for provider::delete_groups_for_all_users() to delete manual group memberships.
+     */
+    public function test_delete_groups_for_all_users() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group1b = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+        $group2b = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id);
+
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1b->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2b->id, 'userid' => $user2->id));
+
+        $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])
+        );
+        $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 = ?", [$course2->id])
+        );
+
+        $coursecontext1 = context_course::instance($course1->id);
+        provider::delete_groups_for_all_users($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])
+        );
+        $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 = ?", [$course2->id])
+        );
+    }
+
+    /**
+     * Test for provider::delete_groups_for_all_users() to delete group memberships of a component.
+     */
+    public function test_delete_groups_for_all_users_for_component() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group1b = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+        $group2b = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id, null, 'self');
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id, null, 'self');
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id, null, 'self');
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id, null, 'self');
+
+        $this->getDataGenerator()->create_group_member(
+                array('groupid' => $group1a->id, 'userid' => $user1->id, 'component' => 'enrol_self'));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1b->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(
+                array('groupid' => $group2a->id, 'userid' => $user1->id, 'component' => 'enrol_self'));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2b->id, 'userid' => $user2->id));
+
+        $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])
+        );
+        $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 = ?", [$course2->id])
+        );
+
+        $coursecontext1 = context_course::instance($course1->id);
+        provider::delete_groups_for_all_users($coursecontext1, 'enrol_self');
+
+        $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 = ?", [$course1->id])
+        );
+        $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 = ?", [$course2->id])
+        );
+    }
+
+    /**
+     * Test for provider::delete_groups_for_user() to delete manual group memberships.
+     */
+    public function test_delete_groups_for_user() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $course3 = $this->getDataGenerator()->create_course();
+
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group1b = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+        $group2b = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+        $group3a = $this->getDataGenerator()->create_group(array('courseid' => $course3->id));
+        $group3b = $this->getDataGenerator()->create_group(array('courseid' => $course3->id));
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course3->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course3->id);
+
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1b->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2b->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group3a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group3b->id, 'userid' => $user2->id));
+
+        $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])
+        );
+        $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 = ?", [$course2->id])
+        );
+        $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 = ?", [$course2->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 gm.userid = ?", [$user1->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, 'core_group',
+                [$coursecontext1->id, $coursecontext2->id]);
+        provider::delete_groups_for_user($approvedcontextlist, '');
+
+        $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 = ?", [$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->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 = ?", [$course3->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 gm.userid = ?", [$user1->id])
+        );
+    }
+
+    /**
+     * Test for provider::delete_groups_for_user() to delete group memberships of a component.
+     */
+    public function test_delete_groups_for_user_for_component() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $course3 = $this->getDataGenerator()->create_course();
+
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group1b = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+        $group2b = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+        $group3a = $this->getDataGenerator()->create_group(array('courseid' => $course3->id));
+        $group3b = $this->getDataGenerator()->create_group(array('courseid' => $course3->id));
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id, null, 'self');
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id, null, 'self');
+        $this->getDataGenerator()->enrol_user($user1->id, $course3->id, null, 'self');
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id, null, 'self');
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id, null, 'self');
+        $this->getDataGenerator()->enrol_user($user2->id, $course3->id, null, 'self');
+
+        $this->getDataGenerator()->create_group_member(
+                array('groupid' => $group1a->id, 'userid' => $user1->id, 'component' => 'enrol_self'));
+        $this->getDataGenerator()->create_group_member(
+                array('groupid' => $group1b->id, 'userid' => $user2->id, 'component' => 'enrol_self'));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2b->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group3a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group3b->id, 'userid' => $user2->id));
+
+        $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])
+        );
+        $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 = ?", [$course2->id])
+        );
+        $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 = ?", [$course2->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 gm.userid = ?", [$user1->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, 'core_group',
+                [$coursecontext1->id, $coursecontext2->id]);
+        provider::delete_groups_for_user($approvedcontextlist, 'enrol_self');
+
+        $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 = ?", [$course1->id])
+        );
+        $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 = ?", [$course2->id])
+        );
+        $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 = ?", [$course3->id])
+        );
+        $this->assertEquals(
+                2,
+                $DB->count_records_sql("SELECT COUNT(gm.id)
+                                          FROM {groups_members} gm
+                                          JOIN {groups} g ON gm.groupid = g.id
+                                         WHERE gm.userid = ?", [$user1->id])
+        );
+    }
+
+    /**
+     * Test for provider::get_contexts_for_userid().
+     */
+    public function test_get_contexts_for_userid() {
+        $this->resetAfterTest();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $course3 = $this->getDataGenerator()->create_course();
+
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group1b = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+        $group2b = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+        $group3a = $this->getDataGenerator()->create_group(array('courseid' => $course3->id));
+        $group3b = $this->getDataGenerator()->create_group(array('courseid' => $course3->id));
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course3->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course3->id);
+
+        $this->getDataGenerator()->create_group_member(array('userid' => $user1->id, 'groupid' => $group1a->id));
+        $this->getDataGenerator()->create_group_member(array('userid' => $user1->id, 'groupid' => $group2a->id));
+        $this->getDataGenerator()->create_group_member(array('userid' => $user2->id, 'groupid' => $group1b->id));
+        $this->getDataGenerator()->create_group_member(array('userid' => $user2->id, 'groupid' => $group2b->id));
+        $this->getDataGenerator()->create_group_member(array('userid' => $user2->id, 'groupid' => $group3b->id));
+
+        $coursecontext1 = context_course::instance($course1->id);
+        $coursecontext2 = context_course::instance($course2->id);
+
+        // User1 is member of some groups in course1 and course2.
+        $contextlist = provider::get_contexts_for_userid($user1->id);
+        $this->assertCount(2, $contextlist);
+        $this->assertEquals(
+                [$coursecontext1->id, $coursecontext2->id],
+                $contextlist->get_contextids(),
+                '', 0.0, 10, true);
+    }
+
+    /**
+     * Test for provider::export_user_data().
+     */
+    public function test_export_user_data() {
+        $this->resetAfterTest();
+
+        $course = $this->getDataGenerator()->create_course();
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group3 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group4 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
+
+        // Add user1 to group1 and group2.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user1->id));
+
+        // Add user2 to group2 and group3.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user2->id));
+
+        $context = context_course::instance($course->id);
+
+        $this->setUser($user1);
+
+        // Export all of the data for the context.
+        $this->export_context_data_for_user($user1->id, $context, 'core_group');
+
+        $writer = writer::with_context($context);
+        $this->assertTrue($writer->has_any_data());
+
+        $data = $writer->get_data([get_string('groups', 'core_group')]);
+        $exportedgroups = $data->groups;
+
+        // User1 belongs to group1 and group2.
+        $this->assertEquals(
+                [$group1->name, $group2->name],
+                array_column($exportedgroups, 'name'),
+                '', 0.0, 10, true);
+    }
+
+    /**
+     * Test for provider::delete_data_for_all_users_in_context().
+     */
+    public function test_delete_data_for_all_users_in_context() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group1b = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+        $group2b = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id);
+
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1b->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2b->id, 'userid' => $user2->id));
+
+        $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])
+        );
+        $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 = ?", [$course2->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])
+        );
+        $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 = ?", [$course2->id])
+        );
+    }
+
+    /**
+     * Test for provider::delete_data_for_user().
+     */
+    public function test_delete_data_for_user() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $course3 = $this->getDataGenerator()->create_course();
+
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group1b = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+        $group2b = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
+        $group3a = $this->getDataGenerator()->create_group(array('courseid' => $course3->id));
+        $group3b = $this->getDataGenerator()->create_group(array('courseid' => $course3->id));
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course3->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course3->id);
+
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1b->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2b->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group3a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group3b->id, 'userid' => $user2->id));
+
+        $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])
+        );
+        $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 = ?", [$course2->id])
+        );
+        $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 = ?", [$course2->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 gm.userid = ?", [$user1->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, 'core_group',
+                [$coursecontext1->id, $coursecontext2->id]);
+        provider::delete_data_for_user($approvedcontextlist);
+
+        $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 = ?", [$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->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 = ?", [$course3->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 gm.userid = ?", [$user1->id])
+        );
+    }
+}
index ead53e5..b349065 100644 (file)
@@ -173,6 +173,10 @@ $string['overview'] = 'Overview';
 $string['potentialmembers'] = 'Potential members: {$a}';
 $string['potentialmembs'] = 'Potential members';
 $string['printerfriendly'] = 'Printer-friendly display';
+$string['privacy:metadata:groups'] = 'A record of group membership.';
+$string['privacy:metadata:groups:groupid'] = 'The ID of the group.';
+$string['privacy:metadata:groups:timeadded'] = 'The timestamp indicating when the user was added to the group.';
+$string['privacy:metadata:groups:userid'] = 'The ID of the user which is associated to the group.';
 $string['random'] = 'Randomly';
 $string['removegroupfromselectedgrouping'] = 'Remove group from grouping';
 $string['removefromgroup'] = 'Remove user from group {$a}';