MDL-63688 core_tag: Add method that returns users in context
[moodle.git] / tag / 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_tag.
19  *
20  * @package    core_tag
21  * @copyright  2018 Zig Tan <zig@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace core_tag\privacy;
27 defined('MOODLE_INTERNAL') || die();
29 use \core_privacy\local\metadata\collection;
30 use core_privacy\local\request\approved_contextlist;
31 use core_privacy\local\request\contextlist;
32 use core_privacy\local\request\transform;
33 use core_privacy\local\request\writer;
34 use core_privacy\local\request\userlist;
35 use core_privacy\local\request\approved_userlist;
37 /**
38  * Privacy Subsystem implementation for core_tag.
39  *
40  * @copyright  2018 Zig Tan <zig@moodle.com>
41  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42  */
43 class provider implements
44         // Tags store user data.
45         \core_privacy\local\metadata\provider,
47         // The tag subsystem provides data to other components.
48         \core_privacy\local\request\subsystem\plugin_provider,
50         // This plugin is capable of determining which users have data within it.
51         \core_privacy\local\request\core_userlist_provider,
53         // The tag subsystem may have data that belongs to this user.
54         \core_privacy\local\request\plugin\provider {
56     /**
57      * Returns meta data about this system.
58      *
59      * @param   collection     $collection The initialised collection to add items to.
60      * @return  collection     A listing of user data stored through this system.
61      */
62     public static function get_metadata(collection $collection) : collection {
63         // The table 'tag' contains data that a user has entered.
64         // It is currently linked with a userid, but this field will hopefulyl go away.
65         // Note: The userid is not necessarily 100% accurate. See MDL-61555.
66         $collection->add_database_table('tag', [
67                 'name' => 'privacy:metadata:tag:name',
68                 'rawname' => 'privacy:metadata:tag:rawname',
69                 'description' => 'privacy:metadata:tag:description',
70                 'flag' => 'privacy:metadata:tag:flag',
71                 'timemodified' => 'privacy:metadata:tag:timemodified',
72                 'userid' => 'privacy:metadata:tag:userid',
73             ], 'privacy:metadata:tag');
75         // The table 'tag_instance' contains user data.
76         // It links the user of a specific tag, to the item which is tagged.
77         // In some cases the userid who 'owns' the tag is also stored.
78         $collection->add_database_table('tag_instance', [
79                 'tagid' => 'privacy:metadata:taginstance:tagid',
80                 'ordering' => 'privacy:metadata:taginstance:ordering',
81                 'timecreated' => 'privacy:metadata:taginstance:timecreated',
82                 'timemodified' => 'privacy:metadata:taginstance:timemodified',
83                 'tiuserid' => 'privacy:metadata:taginstance:tiuserid',
84             ], 'privacy:metadata:taginstance');
86         // The table 'tag_area' does not contain any specific user data.
87         // It links components and item types to collections and describes how they can be associated.
89         // The table 'tag_coll' does not contain any specific user data.
90         // It describes a list of tag collections configured by the administrator.
92         // The table 'tag_correlation' does not contain any user data.
93         // It is a cache for other data already stored.
95         return $collection;
96     }
98     /**
99      * Store all tags which match the specified component, itemtype, and itemid.
100      *
101      * In most situations you will want to specify $onlyuser as false.
102      * This will fetch only tags where the user themselves set the tag, or where tags are a shared resource.
103      *
104      * If you specify $onlyuser as true, only the tags created by that user will be included.
105      *
106      * @param   int         $userid The user whose information is to be exported
107      * @param   \context    $context The context to export for
108      * @param   array       $subcontext The subcontext within the context to export this information
109      * @param   string      $component The component to fetch data from
110      * @param   string      $itemtype The itemtype that the data was exported in within the component
111      * @param   int         $itemid The itemid within that tag
112      * @param   bool        $onlyuser Whether to only export ratings that the current user has made, or all tags
113      */
114     public static function export_item_tags(
115         int $userid,
116         \context $context,
117         array $subcontext,
118         string $component,
119         string $itemtype,
120         int $itemid,
121         bool $onlyuser = false
122     ) {
123         global $DB;
125         // Ignore mdl_tag.userid here because it only reflects the user who originally created the tag.
126         $sql = "SELECT
127                     t.rawname
128                   FROM {tag} t
129             INNER JOIN {tag_instance} ti ON ti.tagid = t.id
130                  WHERE ti.component = :component
131                    AND ti.itemtype = :itemtype
132                    AND ti.itemid = :itemid
133                    ";
135         if ($onlyuser) {
136             $sql .= "AND ti.tiuserid = :userid";
137         } else {
138             $sql .= "AND (ti.tiuserid = 0 OR ti.tiuserid = :userid)";
139         }
141         $params = [
142             'component' => $component,
143             'itemtype' => $itemtype,
144             'itemid' => $itemid,
145             'userid' => $userid,
146         ];
148         if ($tags = $DB->get_fieldset_sql($sql, $params)) {
149             $writer = \core_privacy\local\request\writer::with_context($context)
150                 ->export_related_data($subcontext, 'tags', $tags);
151         }
152     }
154     /**
155      * Deletes all tag instances for given context, component, itemtype, itemid
156      *
157      * In most situations you will want to specify $userid as null. Per-user tag instances
158      * are possible in Tags API, however there are no components or standard plugins that actually use them.
159      *
160      * @param   \context    $context The context to export for
161      * @param   string      $component Tagarea component
162      * @param   string      $itemtype Tagarea item type
163      * @param   int         $itemid The itemid within that component and itemtype (optional)
164      * @param   int         $userid Only delete tag instances made by this user, per-user tags must be enabled for the tagarea
165      */
166     public static function delete_item_tags(\context $context, $component, $itemtype,
167             $itemid = null, $userid = null) {
168         global $DB;
169         $params = ['contextid' => $context->id, 'component' => $component, 'itemtype' => $itemtype];
170         if ($itemid) {
171             $params['itemid'] = $itemid;
172         }
173         if ($userid) {
174             $params['tiuserid'] = $userid;
175         }
176         $DB->delete_records('tag_instance', $params);
177     }
179     /**
180      * Deletes all tag instances for given context, component, itemtype using subquery for itemids
181      *
182      * In most situations you will want to specify $userid as null. Per-user tag instances
183      * are possible in Tags API, however there are no components or standard plugins that actually use them.
184      *
185      * @param   \context    $context The context to export for
186      * @param   string      $component Tagarea component
187      * @param   string      $itemtype Tagarea item type
188      * @param   string      $itemidstest an SQL fragment that the itemid must match. Used
189      *      in the query like WHERE itemid $itemidstest. Must use named parameters,
190      *      and may not use named parameters called contextid, component or itemtype.
191      * @param array $params any query params used by $itemidstest.
192      */
193     public static function delete_item_tags_select(\context $context, $component, $itemtype,
194                                             $itemidstest, $params = []) {
195         global $DB;
196         $params += ['contextid' => $context->id, 'component' => $component, 'itemtype' => $itemtype];
197         $DB->delete_records_select('tag_instance',
198             'contextid = :contextid AND component = :component AND itemtype = :itemtype AND itemid ' . $itemidstest,
199             $params);
200     }
202     /**
203      * Get the list of contexts that contain user information for the specified user.
204      *
205      * @param   int         $userid     The user to search.
206      * @return  contextlist   $contextlist  The contextlist containing the list of contexts used in this plugin.
207      */
208     public static function get_contexts_for_userid(int $userid) : contextlist {
209         $contextlist = new contextlist();
210         $contextlist->add_from_sql("SELECT c.id
211                   FROM {context} c
212                   JOIN {tag} t ON t.userid = :userid
213                  WHERE contextlevel = :contextlevel",
214             ['userid' => $userid, 'contextlevel' => CONTEXT_SYSTEM]);
215         return $contextlist;
216     }
218     /**
219      * Get the list of users within a specific context.
220      *
221      * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
222      */
223     public static function get_users_in_context(userlist $userlist) {
224         $context = $userlist->get_context();
226         if (!$context instanceof \context_system) {
227             return;
228         }
230         $sql = "SELECT userid
231                   FROM {tag}";
233         $userlist->add_from_sql('userid', $sql, []);
234     }
236     /**
237      * Export all user data for the specified user, in the specified contexts.
238      *
239      * @param   approved_contextlist    $contextlist    The approved contexts to export information for.
240      */
241     public static function export_user_data(approved_contextlist $contextlist) {
242         global $DB;
243         $context = \context_system::instance();
244         if (!$contextlist->count() || !in_array($context->id, $contextlist->get_contextids())) {
245             return;
246         }
248         $user = $contextlist->get_user();
249         $sql = "SELECT id, userid, tagcollid, name, rawname, isstandard, description, descriptionformat, flag, timemodified
250             FROM {tag} WHERE userid = ?";
251         $rs = $DB->get_recordset_sql($sql, [$user->id]);
252         foreach ($rs as $record) {
253             $subcontext = [get_string('tags', 'tag'), $record->id];
254             $tag = (object)[
255                 'id' => $record->id,
256                 'userid' => transform::user($record->userid),
257                 'name' => $record->name,
258                 'rawname' => $record->rawname,
259                 'isstandard' => transform::yesno($record->isstandard),
260                 'description' => writer::with_context($context)->rewrite_pluginfile_urls($subcontext,
261                     'tag', 'description', $record->id, strval($record->description)),
262                 'descriptionformat' => $record->descriptionformat,
263                 'flag' => $record->flag,
264                 'timemodified' => transform::datetime($record->timemodified),
266             ];
267             writer::with_context($context)->export_data($subcontext, $tag);
268             writer::with_context($context)->export_area_files($subcontext, 'tag', 'description', $record->id);
269         }
270         $rs->close();
271     }
273     /**
274      * Delete all data for all users in the specified context.
275      *
276      * We do not delete tag instances in this method - this should be done by the components that define tagareas.
277      * We only delete tags themselves in case of system context.
278      *
279      * @param context $context   The specific context to delete data for.
280      */
281     public static function delete_data_for_all_users_in_context(\context $context) {
282         global $DB;
283         // Tags can only be defined in system context.
284         if ($context->id == \context_system::instance()->id) {
285             $DB->delete_records('tag_instance');
286             $DB->delete_records('tag', []);
287         }
288     }
290     /**
291      * Delete multiple users within a single context.
292      *
293      * @param approved_userlist $userlist The approved context and user information to delete information for.
294      */
295     public static function delete_data_for_users(approved_userlist $userlist) {
296         global $DB;
298         $context = $userlist->get_context();
300         if ($context instanceof \context_system) {
301             // Do not delete tags themselves in case they are used by somebody else.
302             // If the user is the only one using the tag, it will be automatically deleted anyway during the
303             // next cron cleanup.
304             list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
305             $DB->set_field_select('tag', 'userid', 0, "userid {$usersql}", $userparams);
306         }
307     }
309     /**
310      * Delete all user data for the specified user, in the specified contexts.
311      *
312      * @param   approved_contextlist    $contextlist    The approved contexts and user information to delete information for.
313      */
314     public static function delete_data_for_user(approved_contextlist $contextlist) {
315         global $DB;
316         $context = \context_system::instance();
317         if (!$contextlist->count() || !in_array($context->id, $contextlist->get_contextids())) {
318             return;
319         }
321         // Do not delete tags themselves in case they are used by somebody else.
322         // If the user is the only one using the tag, it will be automatically deleted anyway during the next cron cleanup.
323         $DB->set_field_select('tag', 'userid', 0, 'userid = ?', [$contextlist->get_user()->id]);
324     }