24ce67af9d64b89317520a9928b2c437f0925d46
[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\subsystem\provider {
46     /**
47      * Returns meta data about this system.
48      *
49      * @param   collection     $collection The initialised collection to add items to.
50      * @return  collection     A listing of user data stored through this system.
51      */
52     public static function get_metadata(collection $collection) : collection {
53         $collection->add_database_table('grading_definitions', [
54                 'method' => 'privacy:metadata:grading_definitions:method',
55                 'areaid' => 'privacy:metadata:grading_definitions:areaid',
56                 'name' => 'privacy:metadata:grading_definitions:name',
57                 'description' => 'privacy:metadata:grading_definitions:description',
58                 'status' => 'privacy:metadata:grading_definitions:status',
59                 'copiedfromid' => 'privacy:metadata:grading_definitions:copiedfromid',
60                 'timecopied' => 'privacy:metadata:grading_definitions:timecopied',
61                 'timecreated' => 'privacy:metadata:grading_definitions:timecreated',
62                 'usercreated' => 'privacy:metadata:grading_definitions:usercreated',
63                 'timemodified' => 'privacy:metadata:grading_definitions:timemodified',
64                 'usermodified' => 'privacy:metadata:grading_definitions:usermodified',
65                 'options' => 'privacy:metadata:grading_definitions:options',
66             ], 'privacy:metadata:grading_definitions');
68         $collection->add_database_table('grading_instances', [
69                 'raterid' => 'privacy:metadata:grading_instances:raterid',
70                 'rawgrade' => 'privacy:metadata:grading_instances:rawgrade',
71                 'status' => 'privacy:metadata:grading_instances:status',
72                 'feedback' => 'privacy:metadata:grading_instances:feedback',
73                 'feedbackformat' => 'privacy:metadata:grading_instances:feedbackformat',
74                 'timemodified' => 'privacy:metadata:grading_instances:timemodified',
75             ], 'privacy:metadata:grading_instances');
77         // Link to subplugin.
78         $collection->add_plugintype_link('gradingform', [], 'privacy:metadata:gradingformpluginsummary');
80         return $collection;
81     }
83     /**
84      * Get the list of contexts that contain user information for the specified user.
85      *
86      * @param int $userid The user to search.
87      * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
88      */
89     public static function get_contexts_for_userid(int $userid) : contextlist {
90         $contextlist = new contextlist();
92         $sql = "SELECT c.id
93                   FROM {context} c
94                   JOIN {grading_areas} a ON a.contextid = c.id
95                   JOIN {grading_definitions} d ON d.areaid = a.id
96              LEFT JOIN {grading_instances} i ON i.definitionid = d.id AND i.raterid = :raterid
97                  WHERE c.contextlevel = :contextlevel
98                    AND (d.usercreated = :usercreated OR d.usermodified = :usermodified OR i.id IS NOT NULL)";
99         $params = [
100             'usercreated' => $userid,
101             'usermodified' => $userid,
102             'raterid' => $userid,
103             'contextlevel' => CONTEXT_MODULE
104         ];
105         $contextlist->add_from_sql($sql, $params);
107         return $contextlist;
108     }
110     /**
111      * Export all user data for the specified user, in the specified contexts.
112      *
113      * @param approved_contextlist $contextlist The approved contexts to export information for.
114      */
115     public static function export_user_data(approved_contextlist $contextlist) {
116         // Remove contexts different from MODULE.
117         $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) {
118             if ($context->contextlevel == CONTEXT_MODULE) {
119                 $carry[] = $context;
120             }
121             return $carry;
122         }, []);
124         if (empty($contexts)) {
125             return;
126         }
128         $userid = $contextlist->get_user()->id;
129         $subcontext = [get_string('gradingmethod', 'grading')];
130         foreach ($contexts as $context) {
131             // Export grading definitions created or modified on this context.
132             self::export_definitions($context, $subcontext, $userid);
133         }
134     }
136     /**
137      * Export all user data related to a context and itemid.
138      *
139      * @param  \context $context    Context to export on.
140      * @param  int      $itemid     Item ID to export on.
141      * @param  array    $subcontext Directory location to export to.
142      */
143     public static function export_item_data(\context $context, int $itemid, array $subcontext) {
144         global $DB;
146         $sql = "SELECT gi.id AS instanceid, gd.id AS definitionid, gd.method
147                   FROM {grading_areas} ga
148                   JOIN {grading_definitions} gd ON gd.areaid = ga.id
149                   JOIN {grading_instances} gi ON gi.definitionid = gd.id AND gi.itemid = :itemid
150                  WHERE ga.contextid = :contextid";
151         $params = [
152             'itemid' => $itemid,
153             'contextid' => $context->id,
154         ];
155         $records = $DB->get_recordset_sql($sql, $params);
156         foreach ($records as $record) {
157             $instancedata = manager::component_class_callback(
158                 "gradingform_{$record->method}",
159                 gradingform_provider_v2::class,
160                 'export_gradingform_instance_data',
161                 [$context, $record->instanceid, $subcontext]
162             );
163         }
164         $records->close();
165     }
167     /**
168      * Deletes all user data related to a context and possibly an itemid.
169      *
170      * @param  \context $context The context to delete on.
171      * @param  int|null $itemid  An optional item ID to refine the deletion.
172      */
173     public static function delete_instance_data(\context $context, int $itemid = null) {
174         global $DB;
175         $itemsql = '';
176         $params = ['contextid' => $context->id];
177         if (isset($itemid)) {
178             $params['itemid'] = $itemid;
179             $itemsql = 'AND gi.itemid = :itemid';
180         }
181         $sql = "SELECT gi.id AS instanceid, gd.id, gd.method
182                   FROM {grading_definitions} gd
183                   JOIN {grading_instances} gi ON gi.definitionid = gd.id
184                   JOIN {grading_areas} ga ON ga.id = gd.areaid
185                  WHERE ga.contextid = :contextid $itemsql";
186         $records = $DB->get_records_sql($sql, $params);
187         if ($records) {
188             $firstrecord = current($records);
189             $method = $firstrecord->method;
190             $instanceids = array_map(function($record) {
191                 return $record->instanceid;
192             }, $records);
193             manager::component_class_callback(
194                 "gradingform_{$method}",
195                 gradingform_provider_v2::class,
196                 'delete_gradingform_for_instances',
197                 [$instanceids]);
198             // Delete grading_instances rows.
199             $DB->delete_records_list('grading_instances', 'id', $instanceids);
200         }
201     }
203     /**
204      * Exports the data related to grading definitions within the specified context/subcontext.
205      *
206      * @param  \context         $context Context owner of the data.
207      * @param  array            $subcontext Subcontext owner of the data.
208      * @param  int              $userid The user whose information is to be exported.
209      */
210     protected static function export_definitions(\context $context, array $subcontext, int $userid = 0) {
211         global $DB;
213         $join = "JOIN {grading_areas} a ON a.id = d.areaid
214                  JOIN {context} c ON a.contextid = c.id AND c.contextlevel = :contextlevel";
215         $select = 'a.contextid = :contextid';
216         $params = [
217             'contextlevel' => CONTEXT_MODULE,
218             'contextid'    => $context->id
219         ];
221         if (!empty($userid)) {
222             $join .= ' LEFT JOIN {grading_instances} i ON i.definitionid = d.id AND i.raterid = :raterid';
223             $select .= ' AND (usercreated = :usercreated
224                 OR usermodified = :usermodified OR i.id IS NOT NULL)';
225             $params['usercreated'] = $userid;
226             $params['usermodified'] = $userid;
227             $params['raterid'] = $userid;
228         }
230         $sql = "SELECT gd.id,
231                        gd.method,
232                        gd.name,
233                        gd.description,
234                        gd.timecopied,
235                        gd.timecreated,
236                        gd.usercreated,
237                        gd.timemodified,
238                        gd.usermodified
239                   FROM (
240                         SELECT DISTINCT d.id
241                                    FROM {grading_definitions} d
242                                   $join
243                                   WHERE $select
244                   ) ids
245                   JOIN {grading_definitions} gd ON gd.id = ids.id";
246         $definitions = $DB->get_recordset_sql($sql, $params);
247         $defdata = [];
248         foreach ($definitions as $definition) {
249             $tmpdata = [
250                 'method' => $definition->method,
251                 'name' => $definition->name,
252                 'description' => $definition->description,
253                 'timecreated' => transform::datetime($definition->timecreated),
254                 'usercreated' => transform::user($definition->usercreated),
255                 'timemodified' => transform::datetime($definition->timemodified),
256                 'usermodified' => transform::user($definition->usermodified),
257             ];
258             if (!empty($definition->timecopied)) {
259                 $tmpdata['timecopied'] = transform::datetime($definition->timecopied);
260             }
262             // MDL-63167 - This section is to be removed with the final deprecation of the gradingform_provider interface.
263             // Export gradingform information (if needed).
264             $instancedata = manager::component_class_callback(
265                 "gradingform_{$definition->method}",
266                 gradingform_provider::class,
267                 'get_gradingform_export_data',
268                 [$context, $definition, $userid]
269             );
270             if (null !== $instancedata) {
271                 $tmpdata = array_merge($tmpdata, $instancedata);
272             }
273             // End of section to be removed with deprecation.
275             $defdata[] = (object) $tmpdata;
277             // Export grading_instances information.
278             self::export_grading_instances($context, $subcontext, $definition->id, $userid);
279         }
280         $definitions->close();
282         if (!empty($defdata)) {
283             $data = (object) [
284                 'definitions' => $defdata,
285             ];
287             writer::with_context($context)->export_data($subcontext, $data);
288         }
289     }
291     /**
292      * Exports the data related to grading instances within the specified definition.
293      *
294      * @param  \context         $context Context owner of the data.
295      * @param  array            $subcontext Subcontext owner of the data.
296      * @param  int              $definitionid The definition ID whose grading instance information is to be exported.
297      * @param  int              $userid The user whose information is to be exported.
298      */
299     protected static function export_grading_instances(\context $context, array $subcontext, int $definitionid, int $userid = 0) {
300         global $DB;
302         $params = ['definitionid' => $definitionid];
303         if (!empty($userid)) {
304             $params['raterid'] = $userid;
305         }
306         $instances = $DB->get_recordset('grading_instances', $params);
307         $instancedata = [];
308         foreach ($instances as $instance) {
309             // TODO: Get the status name (instead of the ID).
310             $tmpdata = [
311                 'rawgrade' => $instance->rawgrade,
312                 'status' => $instance->status,
313                 'feedback' => $instance->feedback,
314                 'feedbackformat' => $instance->feedbackformat,
315                 'timemodified' => transform::datetime($instance->timemodified),
316             ];
317             $instancedata[] = (object) $tmpdata;
318         }
319         $instances->close();
321         if (!empty($instancedata)) {
322             $data = (object) [
323                 'instances' => $instancedata,
324             ];
326             writer::with_context($context)->export_related_data($subcontext, 'gradinginstances', $data);
327         }
328     }
330     /**
331      * No deletion of the advanced grading is done.
332      *
333      * @param \context $context the context to delete in.
334      */
335     public static function delete_data_for_all_users_in_context(\context $context) {
336         // MDL-63167 - This section is to be removed with the final deprecation of the gradingform_provider interface.
337         manager::plugintype_class_callback(
338             'gradingform',
339             gradingform_provider::class,
340             'delete_gradingform_for_context',
341             [$context]
342         );
343         // End of section to be removed for final deprecation.
344     }
346     /**
347      * Deletion of data in this provider is only related to grades and so can not be
348      * deleted for the creator of the advanced grade criteria.
349      *
350      * @param approved_contextlist $contextlist a list of contexts approved for deletion.
351      */
352     public static function delete_data_for_user(approved_contextlist $contextlist) {
353         // MDL-63167 - This section is to be removed with the final deprecation of the gradingform_provider interface.
354         manager::plugintype_class_callback(
355             'gradingform',
356             gradingform_provider::class,
357             'delete_gradingform_for_userid',
358             [$contextlist]
359         );
360         // End of section to be removed for final deprecation.
361     }