86e441cc24ab19640f835143470dfd6307824f1b
[moodle.git] / group / classes / privacy / provider.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Privacy Subsystem implementation for core_group.
19  *
20  * @package    core_group
21  * @category   privacy
22  * @copyright  2018 Shamim Rezaie <shamim@moodle.com>
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 namespace core_group\privacy;
28 defined('MOODLE_INTERNAL') || die();
30 use core_privacy\local\metadata\collection;
31 use core_privacy\local\request\approved_contextlist;
32 use core_privacy\local\request\approved_userlist;
33 use core_privacy\local\request\contextlist;
34 use core_privacy\local\request\transform;
35 use core_privacy\local\request\userlist;
37 /**
38  * Privacy Subsystem implementation for core_group.
39  *
40  * @copyright  2018 Shamim Rezaie <shamim@moodle.com>
41  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42  */
43 class provider implements
44         // Groups store user data.
45         \core_privacy\local\metadata\provider,
47         // The group subsystem contains user's group memberships.
48         \core_privacy\local\request\subsystem\provider,
50         // The group subsystem can provide information to other plugins.
51         \core_privacy\local\request\subsystem\plugin_provider,
53         // This plugin is capable of determining which users have data within it.
54         \core_privacy\local\request\core_userlist_provider,
55         \core_privacy\local\request\shared_userlist_provider
56     {
58     /**
59      * Returns meta data about this system.
60      *
61      * @param   collection $collection The initialised collection to add items to.
62      * @return  collection A listing of user data stored through this system.
63      */
64     public static function get_metadata(collection $collection) : collection {
65         $collection->add_database_table('groups_members', [
66             'groupid' => 'privacy:metadata:groups:groupid',
67             'userid' => 'privacy:metadata:groups:userid',
68             'timeadded' => 'privacy:metadata:groups:timeadded',
69         ], 'privacy:metadata:groups');
71         return $collection;
72     }
74     /**
75      * Writes user data to the writer for the user to download.
76      *
77      * @param \context  $context    The context to export data for.
78      * @param string    $component  The component that is calling this function. Empty string means no component.
79      * @param array     $subcontext The sub-context in which to export this data.
80      * @param int       $itemid     Optional itemid associated with component.
81      */
82     public static function export_groups(\context $context, string $component, array $subcontext = [], int $itemid = 0) {
83         global $DB, $USER;
85         if (!$context instanceof \context_course) {
86             return;
87         }
89         $subcontext[] = get_string('groups', 'core_group');
91         $sql = "SELECT gm.id, gm.timeadded, gm.userid, g.name
92                   FROM {groups_members} gm
93                   JOIN {groups} g ON gm.groupid = g.id
94                  WHERE g.courseid = :courseid
95                        AND gm.component = :component
96                        AND gm.userid = :userid";
97         $params = [
98             'courseid'  => $context->instanceid,
99             'component' => $component,
100             'userid'    => $USER->id
101         ];
103         if ($itemid) {
104             $sql .= ' AND gm.itemid = :itemid';
105             $params['itemid'] = $itemid;
106         }
108         $groups = $DB->get_records_sql($sql, $params);
110         $groups = array_map(function($group) {
111             return (object) [
112                 'name' => format_string($group->name),
113                 'timeadded' => transform::datetime($group->timeadded),
114             ];
115         }, $groups);
117         if (!empty($groups)) {
118             \core_privacy\local\request\writer::with_context($context)
119                     ->export_data($subcontext, (object) [
120                         'groups' => $groups,
121                     ]);
122         }
123     }
125     /**
126      * Deletes all group memberships for a specified context and component.
127      *
128      * @param \context  $context    Details about which context to delete group memberships for.
129      * @param string    $component  Component to delete. Empty string means no component (manual group memberships).
130      * @param int       $itemid     Optional itemid associated with component.
131      */
132     public static function delete_groups_for_all_users(\context $context, string $component, int $itemid = 0) {
133         global $DB;
135         if (!$context instanceof \context_course) {
136             return;
137         }
139         if (!$DB->record_exists('groups', ['courseid' => $context->instanceid])) {
140             return;
141         }
143         $select = "component = :component AND groupid IN (SELECT g.id FROM {groups} g WHERE courseid = :courseid)";
144         $params = ['component' => $component, 'courseid' => $context->instanceid];
146         if ($itemid) {
147             $select .= ' AND itemid = :itemid';
148             $params['itemid'] = $itemid;
149         }
151         $DB->delete_records_select('groups_members', $select, $params);
153         // Purge the group and grouping cache for users.
154         \cache_helper::purge_by_definition('core', 'user_group_groupings');
155     }
157     /**
158      * Deletes all records for a user from a list of approved contexts.
159      *
160      * @param approved_contextlist  $contextlist    Contains the user ID and a list of contexts to be deleted from.
161      * @param string                $component      Component to delete from. Empty string means no component (manual memberships).
162      * @param int                   $itemid         Optional itemid associated with component.
163      */
164     public static function delete_groups_for_user(approved_contextlist $contextlist, string $component, int $itemid = 0) {
165         global $DB;
167         $userid = $contextlist->get_user()->id;
169         $contextids = $contextlist->get_contextids();
171         if (!$contextids) {
172             return;
173         }
175         list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
176         $contextparams += ['contextcourse' => CONTEXT_COURSE];
177         $groupselect = "SELECT g.id
178                           FROM {groups} g
179                           JOIN {context} ctx ON g.courseid = ctx.instanceid AND ctx.contextlevel = :contextcourse
180                          WHERE ctx.id $contextsql";
182         if (!$DB->record_exists_sql($groupselect, $contextparams)) {
183             return;
184         }
186         $select = "userid = :userid AND component = :component AND groupid IN ({$groupselect})";
187         $params = ['userid' => $userid, 'component' => $component] + $contextparams;
189         if ($itemid) {
190             $select .= ' AND itemid = :itemid';
191             $params['itemid'] = $itemid;
192         }
194         $DB->delete_records_select('groups_members', $select, $params);
196         // Invalidate the group and grouping cache for the user.
197         \cache_helper::invalidate_by_definition('core', 'user_group_groupings', array(), array($userid));
198     }
200     /**
201      * Add the list of users who are members of some groups in the specified constraints.
202      *
203      * @param   userlist    $userlist   The userlist to add the users to.
204      * @param   string      $component  The component to check.
205      * @param   int         $itemid     Optional itemid associated with component.
206      */
207     public static function get_group_members_in_context(userlist $userlist, string $component, int $itemid = 0) {
208         $context = $userlist->get_context();
210         if (!$context instanceof \context_course) {
211             return;
212         }
214         // Group members in the given context.
215         $sql = "SELECT gm.userid
216                   FROM {groups_members} gm
217                   JOIN {groups} g ON gm.groupid = g.id
218                  WHERE g.courseid = :courseid AND gm.component = :component";
219         $params = [
220             'courseid'      => $context->instanceid,
221             'component'     => $component
222         ];
224         if ($itemid) {
225             $sql .= ' AND gm.itemid = :itemid';
226             $params['itemid'] = $itemid;
227         }
229         $userlist->add_from_sql('userid', $sql, $params);
230     }
232     /**
233      * Deletes all records for multiple users within a single context.
234      *
235      * @param approved_userlist $userlist   The approved context and user information to delete information for.
236      * @param string            $component  Component to delete from. Empty string means no component (manual memberships).
237      * @param int               $itemid     Optional itemid associated with component.
238      */
239     public static function delete_groups_for_users(approved_userlist $userlist, string $component, int $itemid = 0) {
240         global $DB;
242         $context = $userlist->get_context();
243         $userids = $userlist->get_userids();
245         list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
247         $groupselect = "SELECT g.id
248                           FROM {groups} g
249                           JOIN {context} ctx ON g.courseid = ctx.instanceid AND ctx.contextlevel = :contextcourse
250                          WHERE ctx.id = :contextid";
251         $groupparams = ['contextid' => $context->id, 'contextcourse' => CONTEXT_COURSE];
253         $select = "component = :component AND userid {$usersql} AND groupid IN ({$groupselect})";
254         $params = ['component' => $component] + $groupparams + $userparams;
256         if ($itemid) {
257             $select .= ' AND itemid = :itemid';
258             $params['itemid'] = $itemid;
259         }
261         $DB->delete_records_select('groups_members', $select, $params);
263         // Invalidate the group and grouping cache for the user.
264         \cache_helper::invalidate_by_definition('core', 'user_group_groupings', array(), $userids);
265     }
267     /**
268      * Get the list of contexts that contain group membership for the specified user.
269      *
270      * @param   int     $userid     The user to search.
271      * @param   string  $component  The component to check.
272      * @param   int     $itemid     Optional itemid associated with component.
273      * @return  contextlist         The contextlist containing the list of contexts.
274      */
275     public static function get_contexts_for_group_member(int $userid, string $component, int $itemid = 0) {
276         $contextlist = new contextlist();
278         $sql = "SELECT ctx.id
279                   FROM {groups_members} gm
280                   JOIN {groups} g ON gm.groupid = g.id
281                   JOIN {context} ctx ON g.courseid = ctx.instanceid AND ctx.contextlevel = :contextcourse
282                  WHERE gm.userid = :userid AND gm.component = :component";
284         $params = [
285             'contextcourse' => CONTEXT_COURSE,
286             'userid'        => $userid,
287             'component'     => $component
288         ];
290         if ($itemid) {
291             $sql .= ' AND gm.itemid = :itemid';
292             $params['itemid'] = $itemid;
293         }
295         $contextlist->add_from_sql($sql, $params);
297         return $contextlist;
298     }
300     /**
301      * Get the list of users who have data within a context.
302      *
303      * @param   int $userid The user to search.
304      * @return  contextlist The contextlist containing the list of contexts used in this plugin.
305      */
306     public static function get_contexts_for_userid(int $userid) : contextlist {
307         return static::get_contexts_for_group_member($userid, '');
308     }
310     /**
311      * Get the list of users who have data within a context.
312      *
313      * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
314      */
315     public static function get_users_in_context(userlist $userlist) {
316         $context = $userlist->get_context();
318         if (!$context instanceof \context_course) {
319             return;
320         }
322         static::get_group_members_in_context($userlist, '');
323     }
325     /**
326      * Export all user data for the specified user, in the specified contexts.
327      *
328      * @param approved_contextlist $contextlist The approved contexts to export information for.
329      */
330     public static function export_user_data(approved_contextlist $contextlist) {
331         $contexts = $contextlist->get_contexts();
333         foreach ($contexts as $context) {
334             static::export_groups($context, '');
335         }
336     }
338     /**
339      * Delete all data for all users in the specified context.
340      *
341      * @param context $context The specific context to delete data for.
342      */
343     public static function delete_data_for_all_users_in_context(\context $context) {
344         static::delete_groups_for_all_users($context, '');
345     }
347     /**
348      * Delete all user data for the specified user, in the specified contexts.
349      *
350      * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
351      */
352     public static function delete_data_for_user(approved_contextlist $contextlist) {
353         static::delete_groups_for_user($contextlist, '');
354     }
356     /**
357      * Delete multiple users within a single context.
358      *
359      * @param   approved_userlist   $userlist   The approved context and user information to delete information for.
360      */
361     public static function delete_data_for_users(approved_userlist $userlist) {
362         static::delete_groups_for_users($userlist, '');
363     }