MDL-61994 mod_glossary: Fixing wrong subsystem name
[moodle.git] / mod / glossary / 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 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  */
24 namespace mod_glossary\privacy;
25 use core_privacy\local\metadata\collection;
26 use core_privacy\local\request\approved_contextlist;
27 use core_privacy\local\request\contextlist;
28 use core_privacy\local\request\deletion_criteria;
29 use core_privacy\local\request\helper;
30 use core_privacy\local\request\writer;
32 defined('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  */
39 class 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 {
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         );
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');
68         $items->add_subsystem_link('core_rating', [], 'privacy:metadata:core_rating');
69         return $items;
70     }
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);
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;
98         $contextlist = new contextlist();
99         $contextlist->add_from_sql($sql, $params);
101         return $contextlist;
102     }
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;
114         if (empty($contextlist->count())) {
115             return;
116         }
118         $user = $contextlist->get_user();
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);
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;
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})"]);
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);
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);
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);
185                 // Get all files attached to the glossary attachment.
186                 writer::with_context($context)->export_area_files($path, 'mod_glossary', 'attachment', $record->entryid);
187             }
189             // Export associated comments.
190             \core_comment\privacy\provider::export_comments($context, 'mod_glossary', 'glossary_entry',
191                     $record->entryid, $path, $record->userid != $user->id);
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);
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);
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();
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     }
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     }
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         }
247         if ($context->contextlevel != CONTEXT_MODULE) {
248             return;
249         }
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]);
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]);
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]);
264                 // Delete related entry aliases.
265                 $DB->delete_records('glossary_alias', ['entryid' => $entry->id]);
266             }
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');
272             // Delete related ratings.
273             \core_rating\privacy\provider::delete_ratings($context, 'mod_glossary', 'entry');
275             // Delete comments.
276             \core_comment\privacy\provider::delete_comments_for_all_users($context, 'mod_glossary', 'glossary_entry');
278             // Delete tags.
279             \core_tag\privacy\provider::delete_item_tags($context, 'mod_glossary', 'glossary_entries');
281             // Now delete all user related entries.
282             $DB->delete_records('glossary_entries', ['glossaryid' => $instanceid]);
283         }
284     }
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;
294         if (empty($contextlist->count())) {
295             return;
296         }
298         $userid = $contextlist->get_user()->id;
299         foreach ($contextlist->get_contexts() as $context) {
300             if ($context->contextlevel == CONTEXT_MODULE) {
302                 $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
303                 $DB->record_exists('glossary', ['id' => $context->instanceid]);
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]);
310                     // Delete related entry aliases.
311                     $DB->delete_records('glossary_alias', ['entryid' => $entry->id]);
313                     // Delete tags.
314                     \core_tag\privacy\provider::delete_item_tags($context, 'mod_glossary', 'glossary_entries', $entry->id);
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);
320                     // Delete related ratings.
321                     \core_rating\privacy\provider::delete_ratings($context, 'mod_glossary', 'entry', $entry->id);
322                 }
324                 // Delete comments.
325                 \core_comment\privacy\provider::delete_comments_for_user($contextlist, 'mod_glossary', 'glossary_entry');
327                 // Now delete all user related entries.
328                 $DB->delete_records('glossary_entries', ['glossaryid' => $instanceid, 'userid' => $userid]);
329             }
330         }
331     }