MDL-61994 mod_glossary: Fixing wrong subsystem name
[moodle.git] / mod / glossary / classes / privacy / provider.php
CommitLineData
a92bbd86
SL
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 mod_glossary.
19 *
20 * @package mod_glossary
21 * @copyright 2018 Simey Lameze <simey@moodle.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24namespace mod_glossary\privacy;
25use core_privacy\local\metadata\collection;
26use core_privacy\local\request\approved_contextlist;
27use core_privacy\local\request\contextlist;
28use core_privacy\local\request\deletion_criteria;
29use core_privacy\local\request\helper;
30use core_privacy\local\request\writer;
31
32defined('MOODLE_INTERNAL') || die();
33/**
34 * Implementation of the privacy subsystem plugin provider for the glossary activity module.
35 *
36 * @copyright 2018 Simey Lameze <simey@moodle.com>
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38 */
39class provider implements
40 // This plugin stores personal data.
41 \core_privacy\local\metadata\provider,
42 // This plugin is a core_user_data_provider.
43 \core_privacy\local\request\plugin\provider {
44
45 /**
46 * Return the fields which contain personal data.
47 *
48 * @param collection $items a reference to the collection to use to store the metadata.
49 * @return collection the updated collection of metadata items.
50 */
51 public static function get_metadata(collection $items) : collection {
52 $items->add_database_table(
53 'glossary_entries',
54 [
55 'glossaryid' => 'privacy:metadata:glossary_entries:glossaryid',
56 'userid' => 'privacy:metadata:glossary_entries:userid',
57 'concept' => 'privacy:metadata:glossary_entries:concept',
58 'definition' => 'privacy:metadata:glossary_entries:definition',
59 'attachment' => 'privacy:metadata:glossary_entries:attachment',
60 'timemodified' => 'privacy:metadata:glossary_entries:timemodified',
61 ],
62 'privacy:metadata:glossary_entries'
63 );
64
65 $items->add_subsystem_link('core_files', [], 'privacy:metadata:core_files');
66 $items->add_subsystem_link('core_comment', [], 'privacy:metadata:core_comments');
67 $items->add_subsystem_link('core_tag', [], 'privacy:metadata:core_tag');
8a4b725f 68 $items->add_subsystem_link('core_rating', [], 'privacy:metadata:core_rating');
a92bbd86
SL
69 return $items;
70 }
71
72 /**
73 * Get the list of contexts that contain user information for the specified user.
74 *
75 * @param int $userid the userid.
76 * @return contextlist the list of contexts containing user info for the user.
77 */
78 public static function get_contexts_for_userid(int $userid) : contextlist {
79 $ratingquery = \core_rating\privacy\provider::get_sql_join('r', 'mod_glossary', 'entry', 'ge.id', $userid);
80
81 $sql = "SELECT c.id
82 FROM {context} c
83 INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
84 INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname
85 INNER JOIN {glossary} g ON g.id = cm.instance
86 INNER JOIN {glossary_entries} ge ON ge.glossaryid = g.id
87 LEFT JOIN {comments} com ON com.commentarea =:commentarea AND com.itemid = ge.id
88 {$ratingquery->join}
89 WHERE ge.userid = :glossaryentryuserid OR com.userid = :commentuserid OR {$ratingquery->userwhere}";
90 $params = [
91 'contextlevel' => CONTEXT_MODULE,
92 'modname' => 'glossary',
93 'commentarea' => 'glossary_entry',
94 'glossaryentryuserid' => $userid,
95 'commentuserid' => $userid,
96 ] + $ratingquery->params;
97
98 $contextlist = new contextlist();
99 $contextlist->add_from_sql($sql, $params);
100
101 return $contextlist;
102 }
103
104 /**
105 * Export personal data for the given approved_contextlist.
106 *
107 * User and context information is contained within the contextlist.
108 *
109 * @param approved_contextlist $contextlist a list of contexts approved for export.
110 */
111 public static function export_user_data(approved_contextlist $contextlist) {
112 global $DB;
113
114 if (empty($contextlist->count())) {
115 return;
116 }
117
118 $user = $contextlist->get_user();
119
120 list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
121 $sql = "SELECT ge.id as entryid,
122 cm.id AS cmid,
123 ge.userid,
124 ge.concept,
125 ge.definition,
126 ge.definitionformat,
127 ge.attachment,
128 ge.timecreated,
129 ge.timemodified
130 FROM {glossary_entries} ge
131 JOIN {glossary} g ON ge.glossaryid = g.id
132 JOIN {course_modules} cm ON g.id = cm.instance
133 JOIN {context} c ON cm.id = c.instanceid
134 WHERE c.id {$contextsql}
135 AND ge.userid = :userid
136 OR EXISTS (SELECT 1 FROM {comments} com WHERE com.commentarea = :commentarea AND com.itemid = ge.id
137 AND com.userid = :commentuserid)
138 OR EXISTS (SELECT 1 FROM {rating} r WHERE r.contextid = c.id AND r.itemid = ge.id
139 AND r.component = :ratingcomponent
140 AND r.ratingarea = :ratingarea
141 AND r.userid = :ratinguserid)
142 ORDER BY ge.id, cm.id";
143 $params = [
144 'userid' => $user->id,
145 'commentarea' => 'glossary_entry',
146 'commentuserid' => $user->id,
147 'ratingcomponent' => 'mod_glossary',
148 'ratingarea' => 'entry',
149 'ratinguserid' => $user->id
150 ] + $contextparams;
151 $glossaryentries = $DB->get_recordset_sql($sql, $params);
152
153 // Reference to the glossary activity seen in the last iteration of the loop. By comparing this with the
154 // current record, and because we know the results are ordered, we know when we've moved to the entries
155 // for a new glossary activity and therefore when we can export the complete data for the last activity.
156 $lastcmid = null;
157
158 $glossarydata = [];
159 foreach ($glossaryentries as $record) {
160 $concept = format_string($record->concept);
161 $path = array_merge([get_string('entries', 'mod_glossary'), $concept . " ({$record->entryid})"]);
162
163 // If we've moved to a new glossary, then write the last glossary data and reinit the glossary data array.
164 if (!is_null($lastcmid)) {
165 if ($lastcmid != $record->cmid) {
166 if (!empty($glossarydata)) {
167 $context = \context_module::instance($lastcmid);
168 self::export_glossary_data_for_user($glossarydata, $context, [], $user);
169 $glossarydata = [];
170 }
171 }
172 }
173 $lastcmid = $record->cmid;
174 $context = \context_module::instance($lastcmid);
175
176 // Export files added on the glossary entry definition field.
177 $definition = format_text(writer::with_context($context)->rewrite_pluginfile_urls($path, 'mod_glossary',
178 'entry', $record->entryid, $record->definition), $record->definitionformat);
179
180 // Export just the files attached to this user entry.
181 if ($record->userid == $user->id) {
182 // Get all files attached to the glossary attachment.
183 writer::with_context($context)->export_area_files($path, 'mod_glossary', 'entry', $record->entryid);
184
185 // Get all files attached to the glossary attachment.
186 writer::with_context($context)->export_area_files($path, 'mod_glossary', 'attachment', $record->entryid);
187 }
188
189 // Export associated comments.
190 \core_comment\privacy\provider::export_comments($context, 'mod_glossary', 'glossary_entry',
191 $record->entryid, $path, $record->userid != $user->id);
192
193 // Export associated tags.
194 \core_tag\privacy\provider::export_item_tags($user->id, $context, $path, 'mod_glossary', 'glossary_entries',
195 $record->entryid, $record->userid != $user->id);
196
197 // Export associated ratings.
198 \core_rating\privacy\provider::export_area_ratings($user->id, $context, $path, 'mod_glossary', 'entry',
199 $record->entryid, $record->userid != $user->id);
200
201 $glossarydata['entries'][] = [
202 'concept' => $record->concept,
203 'definition' => $definition,
204 'timecreated' => \core_privacy\local\request\transform::datetime($record->timecreated),
205 'timemodified' => \core_privacy\local\request\transform::datetime($record->timemodified)
206 ];
207 }
208 $glossaryentries->close();
209
210 // The data for the last activity won't have been written yet, so make sure to write it now!
211 if (!empty($glossarydata)) {
212 $context = \context_module::instance($lastcmid);
213 self::export_glossary_data_for_user($glossarydata, $context, [], $user);
214 }
215 }
216
217 /**
218 * Export the supplied personal data for a single glossary activity, along with any generic data or area files.
219 *
220 * @param array $glossarydata The personal data to export for the glossary.
221 * @param \context_module $context The context of the glossary.
222 * @param array $subcontext The location within the current context that this data belongs.
223 * @param \stdClass $user the user record
224 */
225 protected static function export_glossary_data_for_user(array $glossarydata, \context_module $context,
226 array $subcontext, \stdClass $user) {
227 // Fetch the generic module data for the glossary.
228 $contextdata = helper::get_context_data($context, $user);
229 // Merge with glossary data and write it.
230 $contextdata = (object)array_merge((array)$contextdata, $glossarydata);
231 writer::with_context($context)->export_data($subcontext, $contextdata);
232 // Write generic module intro files.
233 helper::export_context_files($context, $user);
234 }
235
236 /**
237 * Delete all data for all users in the specified context.
238 *
239 * @param \context $context the context to delete in.
240 */
241 public static function delete_data_for_all_users_in_context(\context $context) {
242 global $DB;
243 if (empty($context)) {
244 return;
245 }
246
247 if ($context->contextlevel != CONTEXT_MODULE) {
248 return;
249 }
250
251 $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
252 $DB->record_exists('glossary', ['id' => $context->instanceid]);
253 $DB->delete_records('glossary_entries', ['glossaryid' => $instanceid]);
254
255 if ($context->contextlevel == CONTEXT_MODULE) {
256 $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
257 $DB->record_exists('glossary', ['id' => $context->instanceid]);
258
259 $entries = $DB->get_records('glossary_entries', ['glossaryid' => $instanceid]);
260 foreach ($entries as $entry) {
261 // Delete related entry categories.
262 $DB->delete_records('glossary_entries_categories', ['entryid' => $entry->id]);
263
264 // Delete related entry aliases.
265 $DB->delete_records('glossary_alias', ['entryid' => $entry->id]);
266 }
267
268 // Delete entry and attachment files.
269 get_file_storage()->delete_area_files($context->id, 'mod_glossary', 'entry');
270 get_file_storage()->delete_area_files($context->id, 'mod_glossary', 'attachment');
271
272 // Delete related ratings.
273 \core_rating\privacy\provider::delete_ratings($context, 'mod_glossary', 'entry');
274
275 // Delete comments.
276 \core_comment\privacy\provider::delete_comments_for_all_users($context, 'mod_glossary', 'glossary_entry');
277
278 // Delete tags.
279 \core_tag\privacy\provider::delete_item_tags($context, 'mod_glossary', 'glossary_entries');
280
281 // Now delete all user related entries.
282 $DB->delete_records('glossary_entries', ['glossaryid' => $instanceid]);
283 }
284 }
285
286 /**
287 * Delete all user data for the specified user, in the specified contexts.
288 *
289 * @param approved_contextlist $contextlist a list of contexts approved for deletion.
290 */
291 public static function delete_data_for_user(approved_contextlist $contextlist) {
292 global $DB;
293
294 if (empty($contextlist->count())) {
295 return;
296 }
297
298 $userid = $contextlist->get_user()->id;
299 foreach ($contextlist->get_contexts() as $context) {
300 if ($context->contextlevel == CONTEXT_MODULE) {
301
302 $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
303 $DB->record_exists('glossary', ['id' => $context->instanceid]);
304
305 $entries = $DB->get_records('glossary_entries', ['glossaryid' => $instanceid, 'userid' => $userid]);
306 foreach ($entries as $entry) {
307 // Delete related entry categories.
308 $DB->delete_records('glossary_entries_categories', ['entryid' => $entry->id]);
309
310 // Delete related entry aliases.
311 $DB->delete_records('glossary_alias', ['entryid' => $entry->id]);
312
313 // Delete tags.
314 \core_tag\privacy\provider::delete_item_tags($context, 'mod_glossary', 'glossary_entries', $entry->id);
315
316 // Delete entry and attachment files.
317 get_file_storage()->delete_area_files($context->id, 'mod_glossary', 'entry', $entry->id);
318 get_file_storage()->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
319
320 // Delete related ratings.
321 \core_rating\privacy\provider::delete_ratings($context, 'mod_glossary', 'entry', $entry->id);
322 }
323
324 // Delete comments.
325 \core_comment\privacy\provider::delete_comments_for_user($contextlist, 'mod_glossary', 'glossary_entry');
326
327 // Now delete all user related entries.
328 $DB->delete_records('glossary_entries', ['glossaryid' => $instanceid, 'userid' => $userid]);
329 }
330 }
331 }
332}