MDL-62614 Privacy: Core tag provider delete_item_tags function not work
[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;
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  */
41 class provider implements
42         // Tags store user data.
43         \core_privacy\local\metadata\provider,
45         // The tag subsystem provides data to other components.
46         \core_privacy\local\request\subsystem\plugin_provider,
48         // The tag subsystem may have data that belongs to this user.
49         \core_privacy\local\request\plugin\provider {
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');
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');
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.
84         // The table 'tag_coll' does not contain any specific user data.
85         // It describes a list of tag collections configured by the administrator.
87         // The table 'tag_correlation' does not contain any user data.
88         // It is a cache for other data already stored.
90         return $collection;
91     }
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;
120         // Ignore mdl_tag.userid here because it only reflects the user who originally created the tag.
121         $sql = "SELECT
122                     t.rawname
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                    ";
130         if ($onlyuser) {
131             $sql .= "AND ti.tiuserid = :userid";
132         } else {
133             $sql .= "AND (ti.tiuserid = 0 OR ti.tiuserid = :userid)";
134         }
136         $params = [
137             'component' => $component,
138             'itemtype' => $itemtype,
139             'itemid' => $itemid,
140             'userid' => $userid,
141         ];
143         if ($tags = $DB->get_fieldset_sql($sql, $params)) {
144             $writer = \core_privacy\local\request\writer::with_context($context)
145                 ->export_related_data($subcontext, 'tags', $tags);
146         }
147     }
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) {
169             $params['tiuserid'] = $userid;
170         }
171         $DB->delete_records('tag_instance', $params);
172     }
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     }
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     }
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         }
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),
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     }
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     }
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         }
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     }