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