MDL-63688 core_tag: Add method that returns users in context
[moodle.git] / tag / classes / privacy / provider.php
CommitLineData
bd913946
AN
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/>.
16
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 */
24
25namespace core_tag\privacy;
26
27defined('MOODLE_INTERNAL') || die();
28
29use \core_privacy\local\metadata\collection;
2207b0fa
MG
30use core_privacy\local\request\approved_contextlist;
31use core_privacy\local\request\contextlist;
32use core_privacy\local\request\transform;
33use core_privacy\local\request\writer;
1d3d4c66
MG
34use core_privacy\local\request\userlist;
35use core_privacy\local\request\approved_userlist;
bd913946
AN
36
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 */
43class provider implements
44 // Tags store user data.
45 \core_privacy\local\metadata\provider,
46
47 // The tag subsystem provides data to other components.
2207b0fa
MG
48 \core_privacy\local\request\subsystem\plugin_provider,
49
1d3d4c66
MG
50 // This plugin is capable of determining which users have data within it.
51 \core_privacy\local\request\core_userlist_provider,
52
2207b0fa
MG
53 // The tag subsystem may have data that belongs to this user.
54 \core_privacy\local\request\plugin\provider {
bd913946
AN
55
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');
74
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');
85
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.
88
89 // The table 'tag_coll' does not contain any specific user data.
90 // It describes a list of tag collections configured by the administrator.
91
92 // The table 'tag_correlation' does not contain any user data.
93 // It is a cache for other data already stored.
94
95 return $collection;
96 }
97
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;
124
2207b0fa 125 // Ignore mdl_tag.userid here because it only reflects the user who originally created the tag.
bd913946 126 $sql = "SELECT
2207b0fa 127 t.rawname
bd913946
AN
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 ";
134
135 if ($onlyuser) {
136 $sql .= "AND ti.tiuserid = :userid";
137 } else {
138 $sql .= "AND (ti.tiuserid = 0 OR ti.tiuserid = :userid)";
139 }
140
141 $params = [
142 'component' => $component,
143 'itemtype' => $itemtype,
144 'itemid' => $itemid,
145 'userid' => $userid,
146 ];
147
2207b0fa 148 if ($tags = $DB->get_fieldset_sql($sql, $params)) {
bd913946
AN
149 $writer = \core_privacy\local\request\writer::with_context($context)
150 ->export_related_data($subcontext, 'tags', $tags);
151 }
152 }
1c4b87cb
MG
153
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) {
af2e8ed9 174 $params['tiuserid'] = $userid;
1c4b87cb
MG
175 }
176 $DB->delete_records('tag_instance', $params);
177 }
178
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 }
2207b0fa
MG
201
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 }
217
1d3d4c66
MG
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();
225
226 if (!$context instanceof \context_system) {
227 return;
228 }
229
230 $sql = "SELECT userid
231 FROM {tag}";
232
233 $userlist->add_from_sql('userid', $sql, []);
234 }
235
2207b0fa
MG
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 }
247
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),
265
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 }
272
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 }
289
1d3d4c66
MG
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;
297
298 $context = $userlist->get_context();
299
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 }
308
2207b0fa
MG
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 }
320
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 }
bd913946 325}