MDL-63622 core_grading: Add support for removal of context users.
[moodle.git] / grade / grading / 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 class for requesting user data.
19  *
20  * @package    core_grading
21  * @copyright  2018 Sara Arjona <sara@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace core_grading\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;
34 use \core_privacy\manager;
36 /**
37  * Privacy class for requesting user data.
38  *
39  * @copyright  2018 Sara Arjona <sara@moodle.com>
40  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41  */
42 class provider implements
43     \core_privacy\local\metadata\provider,
44     \core_privacy\local\request\plugin\provider,
45     \core_privacy\local\request\core_userlist_provider,
46     \core_privacy\local\request\subsystem\provider {
48     /**
49      * Returns meta data about this system.
50      *
51      * @param   collection     $collection The initialised collection to add items to.
52      * @return  collection     A listing of user data stored through this system.
53      */
54     public static function get_metadata(collection $collection) : collection {
55         $collection->add_database_table('grading_definitions', [
56                 'method' => 'privacy:metadata:grading_definitions:method',
57                 'areaid' => 'privacy:metadata:grading_definitions:areaid',
58                 'name' => 'privacy:metadata:grading_definitions:name',
59                 'description' => 'privacy:metadata:grading_definitions:description',
60                 'status' => 'privacy:metadata:grading_definitions:status',
61                 'copiedfromid' => 'privacy:metadata:grading_definitions:copiedfromid',
62                 'timecopied' => 'privacy:metadata:grading_definitions:timecopied',
63                 'timecreated' => 'privacy:metadata:grading_definitions:timecreated',
64                 'usercreated' => 'privacy:metadata:grading_definitions:usercreated',
65                 'timemodified' => 'privacy:metadata:grading_definitions:timemodified',
66                 'usermodified' => 'privacy:metadata:grading_definitions:usermodified',
67                 'options' => 'privacy:metadata:grading_definitions:options',
68             ], 'privacy:metadata:grading_definitions');
70         $collection->add_database_table('grading_instances', [
71                 'raterid' => 'privacy:metadata:grading_instances:raterid',
72                 'rawgrade' => 'privacy:metadata:grading_instances:rawgrade',
73                 'status' => 'privacy:metadata:grading_instances:status',
74                 'feedback' => 'privacy:metadata:grading_instances:feedback',
75                 'feedbackformat' => 'privacy:metadata:grading_instances:feedbackformat',
76                 'timemodified' => 'privacy:metadata:grading_instances:timemodified',
77             ], 'privacy:metadata:grading_instances');
79         // Link to subplugin.
80         $collection->add_plugintype_link('gradingform', [], 'privacy:metadata:gradingformpluginsummary');
82         return $collection;
83     }
85     /**
86      * Get the list of contexts that contain user information for the specified user.
87      *
88      * @param int $userid The user to search.
89      * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
90      */
91     public static function get_contexts_for_userid(int $userid) : contextlist {
92         $contextlist = new contextlist();
94         $sql = "SELECT c.id
95                   FROM {context} c
96                   JOIN {grading_areas} a ON a.contextid = c.id
97                   JOIN {grading_definitions} d ON d.areaid = a.id
98              LEFT JOIN {grading_instances} i ON i.definitionid = d.id AND i.raterid = :raterid
99                  WHERE c.contextlevel = :contextlevel
100                    AND (d.usercreated = :usercreated OR d.usermodified = :usermodified OR i.id IS NOT NULL)";
101         $params = [
102             'usercreated' => $userid,
103             'usermodified' => $userid,
104             'raterid' => $userid,
105             'contextlevel' => CONTEXT_MODULE
106         ];
107         $contextlist->add_from_sql($sql, $params);
109         return $contextlist;
110     }
112     /**
113      * Get the list of contexts that contain user information for the specified user.
114      *
115      * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
116      */
117     public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) {
118         $context = $userlist->get_context();
119         if ($context->contextlevel != CONTEXT_MODULE) {
120             return;
121         }
123         $params = ['contextid' => $context->id];
125         $sql = "SELECT d.usercreated, d.usermodified
126                   FROM {grading_definitions} d
127                   JOIN {grading_areas} a ON a.id = d.areaid
128                   WHERE a.contextid = :contextid";
129         $userlist->add_from_sql('usercreated', $sql, $params);
130         $userlist->add_from_sql('usermodified', $sql, $params);
132         $sql = "SELECT i.raterid
133                   FROM {grading_definitions} d
134                   JOIN {grading_areas} a ON a.id = d.areaid
135                   JOIN {grading_instances} i ON i.definitionid = d.id
136                   WHERE a.contextid = :contextid";
137         $userlist->add_from_sql('raterid', $sql, $params);
138     }
140     /**
141      * Export all user data for the specified user, in the specified contexts.
142      *
143      * @param approved_contextlist $contextlist The approved contexts to export information for.
144      */
145     public static function export_user_data(approved_contextlist $contextlist) {
146         // Remove contexts different from MODULE.
147         $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) {
148             if ($context->contextlevel == CONTEXT_MODULE) {
149                 $carry[] = $context;
150             }
151             return $carry;
152         }, []);
154         if (empty($contexts)) {
155             return;
156         }
158         $userid = $contextlist->get_user()->id;
159         $subcontext = [get_string('gradingmethod', 'grading')];
160         foreach ($contexts as $context) {
161             // Export grading definitions created or modified on this context.
162             self::export_definitions($context, $subcontext, $userid);
163         }
164     }
166     /**
167      * Export all user data related to a context and itemid.
168      *
169      * @param  \context $context    Context to export on.
170      * @param  int      $itemid     Item ID to export on.
171      * @param  array    $subcontext Directory location to export to.
172      */
173     public static function export_item_data(\context $context, int $itemid, array $subcontext) {
174         global $DB;
176         $sql = "SELECT gi.id AS instanceid, gd.id AS definitionid, gd.method
177                   FROM {grading_areas} ga
178                   JOIN {grading_definitions} gd ON gd.areaid = ga.id
179                   JOIN {grading_instances} gi ON gi.definitionid = gd.id AND gi.itemid = :itemid
180                  WHERE ga.contextid = :contextid";
181         $params = [
182             'itemid' => $itemid,
183             'contextid' => $context->id,
184         ];
185         $records = $DB->get_recordset_sql($sql, $params);
186         foreach ($records as $record) {
187             $instancedata = manager::component_class_callback(
188                 "gradingform_{$record->method}",
189                 gradingform_provider_v2::class,
190                 'export_gradingform_instance_data',
191                 [$context, $record->instanceid, $subcontext]
192             );
193         }
194         $records->close();
195     }
197     /**
198      * Deletes all user data related to a context and possibly an itemid.
199      *
200      * @param  \context $context The context to delete on.
201      * @param  int|null $itemid  An optional item ID to refine the deletion.
202      */
203     public static function delete_instance_data(\context $context, int $itemid = null) {
204         if (is_null($itemid)) {
205             self::delete_data_for_instances($context);
206         } else {
207             self::delete_data_for_instances($context, [$itemid]);
208         }
209     }
211     /**
212      * Deletes all user data related to a context and possibly itemids.
213      *
214      * @param  \context $context The context to delete on.
215      * @param  array $itemids  An optional list of item IDs to refine the deletion.
216      */
217     public static function delete_data_for_instances(\context $context, array $itemids = []) {
218         global $DB;
219         $itemsql = '';
220         $params = ['contextid' => $context->id];
221         if (!empty($itemids)) {
222             list($itemsql, $itemparams) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
223             $params = array_merge($params, $itemparams);
224             $itemsql = "AND itemid $itemsql";
225         }
226         $sql = "SELECT gi.id AS instanceid, gd.id, gd.method
227                   FROM {grading_definitions} gd
228                   JOIN {grading_instances} gi ON gi.definitionid = gd.id
229                   JOIN {grading_areas} ga ON ga.id = gd.areaid
230                  WHERE ga.contextid = :contextid $itemsql";
231         $records = $DB->get_records_sql($sql, $params);
232         if ($records) {
233             $firstrecord = current($records);
234             $method = $firstrecord->method;
235             $instanceids = array_map(function($record) {
236                 return $record->instanceid;
237             }, $records);
238             manager::component_class_callback(
239                 "gradingform_{$method}",
240                 gradingform_provider_v2::class,
241                 'delete_gradingform_for_instances',
242                 [$instanceids]);
243             // Delete grading_instances rows.
244             $DB->delete_records_list('grading_instances', 'id', $instanceids);
245         }
246     }
248     /**
249      * Exports the data related to grading definitions within the specified context/subcontext.
250      *
251      * @param  \context         $context Context owner of the data.
252      * @param  array            $subcontext Subcontext owner of the data.
253      * @param  int              $userid The user whose information is to be exported.
254      */
255     protected static function export_definitions(\context $context, array $subcontext, int $userid = 0) {
256         global $DB;
258         $join = "JOIN {grading_areas} a ON a.id = d.areaid
259                  JOIN {context} c ON a.contextid = c.id AND c.contextlevel = :contextlevel";
260         $select = 'a.contextid = :contextid';
261         $params = [
262             'contextlevel' => CONTEXT_MODULE,
263             'contextid'    => $context->id
264         ];
266         if (!empty($userid)) {
267             $join .= ' LEFT JOIN {grading_instances} i ON i.definitionid = d.id AND i.raterid = :raterid';
268             $select .= ' AND (usercreated = :usercreated
269                 OR usermodified = :usermodified OR i.id IS NOT NULL)';
270             $params['usercreated'] = $userid;
271             $params['usermodified'] = $userid;
272             $params['raterid'] = $userid;
273         }
275         $sql = "SELECT gd.id,
276                        gd.method,
277                        gd.name,
278                        gd.description,
279                        gd.timecopied,
280                        gd.timecreated,
281                        gd.usercreated,
282                        gd.timemodified,
283                        gd.usermodified
284                   FROM (
285                         SELECT DISTINCT d.id
286                                    FROM {grading_definitions} d
287                                   $join
288                                   WHERE $select
289                   ) ids
290                   JOIN {grading_definitions} gd ON gd.id = ids.id";
291         $definitions = $DB->get_recordset_sql($sql, $params);
292         $defdata = [];
293         foreach ($definitions as $definition) {
294             $tmpdata = [
295                 'method' => $definition->method,
296                 'name' => $definition->name,
297                 'description' => $definition->description,
298                 'timecreated' => transform::datetime($definition->timecreated),
299                 'usercreated' => transform::user($definition->usercreated),
300                 'timemodified' => transform::datetime($definition->timemodified),
301                 'usermodified' => transform::user($definition->usermodified),
302             ];
303             if (!empty($definition->timecopied)) {
304                 $tmpdata['timecopied'] = transform::datetime($definition->timecopied);
305             }
307             // MDL-63167 - This section is to be removed with the final deprecation of the gradingform_provider interface.
308             // Export gradingform information (if needed).
309             $instancedata = manager::component_class_callback(
310                 "gradingform_{$definition->method}",
311                 gradingform_provider::class,
312                 'get_gradingform_export_data',
313                 [$context, $definition, $userid]
314             );
315             if (null !== $instancedata) {
316                 $tmpdata = array_merge($tmpdata, $instancedata);
317             }
318             // End of section to be removed with deprecation.
320             $defdata[] = (object) $tmpdata;
322             // Export grading_instances information.
323             self::export_grading_instances($context, $subcontext, $definition->id, $userid);
324         }
325         $definitions->close();
327         if (!empty($defdata)) {
328             $data = (object) [
329                 'definitions' => $defdata,
330             ];
332             writer::with_context($context)->export_data($subcontext, $data);
333         }
334     }
336     /**
337      * Exports the data related to grading instances within the specified definition.
338      *
339      * @param  \context         $context Context owner of the data.
340      * @param  array            $subcontext Subcontext owner of the data.
341      * @param  int              $definitionid The definition ID whose grading instance information is to be exported.
342      * @param  int              $userid The user whose information is to be exported.
343      */
344     protected static function export_grading_instances(\context $context, array $subcontext, int $definitionid, int $userid = 0) {
345         global $DB;
347         $params = ['definitionid' => $definitionid];
348         if (!empty($userid)) {
349             $params['raterid'] = $userid;
350         }
351         $instances = $DB->get_recordset('grading_instances', $params);
352         $instancedata = [];
353         foreach ($instances as $instance) {
354             // TODO: Get the status name (instead of the ID).
355             $tmpdata = [
356                 'rawgrade' => $instance->rawgrade,
357                 'status' => $instance->status,
358                 'feedback' => $instance->feedback,
359                 'feedbackformat' => $instance->feedbackformat,
360                 'timemodified' => transform::datetime($instance->timemodified),
361             ];
362             $instancedata[] = (object) $tmpdata;
363         }
364         $instances->close();
366         if (!empty($instancedata)) {
367             $data = (object) [
368                 'instances' => $instancedata,
369             ];
371             writer::with_context($context)->export_related_data($subcontext, 'gradinginstances', $data);
372         }
373     }
375     /**
376      * No deletion of the advanced grading is done.
377      *
378      * @param \context $context the context to delete in.
379      */
380     public static function delete_data_for_all_users_in_context(\context $context) {
381         // MDL-63167 - This section is to be removed with the final deprecation of the gradingform_provider interface.
382         manager::plugintype_class_callback(
383             'gradingform',
384             gradingform_provider::class,
385             'delete_gradingform_for_context',
386             [$context]
387         );
388         // End of section to be removed for final deprecation.
389     }
391     /**
392      * Deletion of data in this provider is only related to grades and so can not be
393      * deleted for the creator of the advanced grade criteria.
394      *
395      * @param approved_contextlist $contextlist a list of contexts approved for deletion.
396      */
397     public static function delete_data_for_user(approved_contextlist $contextlist) {
398         // MDL-63167 - This section is to be removed with the final deprecation of the gradingform_provider interface.
399         manager::plugintype_class_callback(
400             'gradingform',
401             gradingform_provider::class,
402             'delete_gradingform_for_userid',
403             [$contextlist]
404         );
405         // End of section to be removed for final deprecation.
406     }
408     /**
409      * Delete multiple users within a single context.
410      *
411      * @param approved_userlist $userlist The approved context and user information to delete information for.
412      */
413     public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) {
414         // The only information left to be deleted here is the grading definitions. Currently we are not deleting these.
415     }