9fbfa1e5b9be43653b33a4a50eae0817cd1bb720
[moodle.git] / mod / assignment / 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_assignment.
19  *
20  * @package    mod_assignment
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 mod_assignment\privacy;
27 use core_privacy\local\metadata\collection;
28 use core_privacy\local\request\approved_contextlist;
29 use core_privacy\local\request\contextlist;
30 use core_privacy\local\request\transform;
31 use core_privacy\local\request\writer;
32 use core_privacy\local\request\helper;
33 use core_privacy\local\request\approved_userlist;
34 use core_privacy\local\request\userlist;
36 defined('MOODLE_INTERNAL') || die();
38 global $CFG;
39 require_once($CFG->dirroot . '/mod/assignment/lib.php');
41 /**
42  * Implementation of the privacy subsystem plugin provider for mod_assignment.
43  *
44  * @copyright  2018 Zig Tan <zig@moodle.com>
45  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46  */
47 class provider implements
48     \core_privacy\local\metadata\provider,
49     \core_privacy\local\request\plugin\provider,
50     \core_privacy\local\request\user_preference_provider,
51     \core_privacy\local\request\core_userlist_provider {
53     /**
54      * Return the fields which contain personal data.
55      *
56      * @param collection $collection a reference to the collection to use to store the metadata.
57      * @return collection the updated collection of metadata items.
58      */
59     public static function get_metadata(collection $collection) : collection {
60         $collection->add_database_table(
61             'assignment_submissions',
62             [
63                 'userid' => 'privacy:metadata:assignment_submissions:userid',
64                 'timecreated' => 'privacy:metadata:assignment_submissions:timecreated',
65                 'timemodified' => 'privacy:metadata:assignment_submissions:timemodified',
66                 'numfiles' => 'privacy:metadata:assignment_submissions:numfiles',
67                 'data1' => 'privacy:metadata:assignment_submissions:data1',
68                 'data2' => 'privacy:metadata:assignment_submissions:data2',
69                 'grade' => 'privacy:metadata:assignment_submissions:grade',
70                 'submissioncomment' => 'privacy:metadata:assignment_submissions:submissioncomment',
71                 'teacher' => 'privacy:metadata:assignment_submissions:teacher',
72                 'timemarked' => 'privacy:metadata:assignment_submissions:timemarked',
73                 'mailed' => 'privacy:metadata:assignment_submissions:mailed'
74             ],
75             'privacy:metadata:assignment_submissions'
76         );
78         // Legacy mod_assignment preferences from Moodle 2.X.
79         $collection->add_user_preference('assignment_filter', 'privacy:metadata:assignmentfilter');
80         $collection->add_user_preference('assignment_mailinfo', 'privacy:metadata:assignmentmailinfo');
81         $collection->add_user_preference('assignment_perpage', 'privacy:metadata:assignmentperpage');
82         $collection->add_user_preference('assignment_quickgrade', 'privacy:metadata:assignmentquickgrade');
84         return $collection;
85     }
87     /**
88      * Get the list of contexts that contain user information for the specified user.
89      *
90      * @param int $userid the userid.
91      * @return contextlist the list of contexts containing user info for the user.
92      */
93     public static function get_contexts_for_userid(int $userid) : contextlist {
94         $contextlist = new contextlist();
96         $sql = "SELECT DISTINCT
97                        ctx.id
98                   FROM {context} ctx
99                   JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmodule
100                   JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
101                   JOIN {assignment} a ON cm.instance = a.id
102                   JOIN {assignment_submissions} s ON s.assignment = a.id
103                  WHERE s.userid = :userid
104                     OR s.teacher = :teacher";
106         $params = [
107             'contextmodule'  => CONTEXT_MODULE,
108             'modulename'    => 'assignment',
109             'userid'        => $userid,
110             'teacher'       => $userid
111         ];
113         $contextlist->add_from_sql($sql, $params);
115         return $contextlist;
116     }
118     /**
119      * Get the list of contexts that contain user information for the specified user.
120      *
121      * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
122      */
123     public static function get_users_in_context(userlist $userlist) {
124         $context = $userlist->get_context();
125         if ($context->contextlevel != CONTEXT_MODULE) {
126             return;
127         }
129         $params = [
130             'modulename' => 'assignment',
131             'contextlevel' => CONTEXT_MODULE,
132             'contextid' => $context->id
133         ];
134         $sql = "SELECT s.userid
135                   FROM {assignment_submissions} s
136                   JOIN {assignment} a ON s.assignment = a.id
137                   JOIN {modules} m ON m.name = :modulename
138                   JOIN {course_modules} cm ON a.id = cm.instance AND cm.module = m.id
139                   JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel
140                  WHERE ctx.id = :contextid
141         ";
142         $userlist->add_from_sql('userid', $sql, $params);
144         $sql = "SELECT s.teacher
145                   FROM {assignment_submissions} s
146                   JOIN {assignment} a ON s.assignment = a.id
147                   JOIN {modules} m ON m.name = :modulename
148                   JOIN {course_modules} cm ON a.id = cm.instance AND cm.module = m.id
149                   JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel
150                  WHERE ctx.id = :contextid
151         ";
152         $userlist->add_from_sql('teacher', $sql, $params);
153     }
155     /**
156      * Export personal data for the given approved_contextlist.
157      * User and context information is contained within the contextlist.
158      *
159      * @param approved_contextlist $contextlist a list of contexts approved for export.
160      */
161     public static function export_user_data(approved_contextlist $contextlist) {
162         if (empty($contextlist->count())) {
163             return;
164         }
166         $user = $contextlist->get_user();
168         foreach ($contextlist->get_contexts() as $context) {
169             if ($context->contextlevel != CONTEXT_MODULE) {
170                 continue;
171             }
173             // Cannot make use of helper::export_context_files(), need to manually export assignment details.
174             $assignmentdata = self::get_assignment_by_context($context);
176             // Get assignment details object for output.
177             $assignment = self::get_assignment_output($assignmentdata);
178             writer::with_context($context)->export_data([], $assignment);
180             // Check if the user has marked any assignment's submissions to determine assignment submissions to export.
181             $teacher = (self::has_marked_assignment_submissions($assignmentdata->id, $user->id) == true) ? true : false;
183             // Get the assignment submissions submitted by & marked by the user for an assignment.
184             $submissionsdata = self::get_assignment_submissions_by_assignment($assignmentdata->id, $user->id, $teacher);
186             foreach ($submissionsdata as $submissiondata) {
187                 // Default subcontext path to export assignment submissions submitted by the user.
188                 $subcontexts = [
189                     get_string('privacy:submissionpath', 'mod_assignment')
190                 ];
192                 if ($teacher == true) {
193                     if ($submissiondata->teacher == $user->id) {
194                         // Export assignment submissions that have been marked by the user.
195                         $subcontexts = [
196                             get_string('privacy:markedsubmissionspath', 'mod_assignment'),
197                             transform::user($submissiondata->userid)
198                         ];
199                     }
200                 }
202                 // Get assignment submission details object for output.
203                 $submission = self::get_assignment_submission_output($submissiondata);
204                 $itemid = $submissiondata->id;
206                 writer::with_context($context)
207                     ->export_data($subcontexts, $submission)
208                     ->export_area_files($subcontexts, 'mod_assignment', 'submission', $itemid);
209             }
210         }
211     }
213     /**
214      * Stores the user preferences related to mod_assign.
215      *
216      * @param  int $userid The user ID that we want the preferences for.
217      */
218     public static function export_user_preferences(int $userid) {
219         $context = \context_system::instance();
220         $assignmentpreferences = [
221             'assignment_filter' => [
222                 'string' => get_string('privacy:metadata:assignmentfilter', 'mod_assignment'),
223                 'bool' => false
224             ],
225             'assignment_mailinfo' => [
226                 'string' => get_string('privacy:metadata:assignmentmailinfo', 'mod_assignment'),
227                 'bool' => false
228             ],
229             'assignment_perpage' => [
230                 'string' => get_string('privacy:metadata:assignmentperpage', 'mod_assignment'),
231                 'bool' => false
232             ],
233             'assignment_quickgrade' => [
234                 'string' => get_string('privacy:metadata:assignmentquickgrade', 'mod_assignment'),
235                 'bool' => false
236             ],
237         ];
238         foreach ($assignmentpreferences as $key => $preference) {
239             $value = get_user_preferences($key, null, $userid);
240             if ($preference['bool']) {
241                 $value = transform::yesno($value);
242             }
243             if (isset($value)) {
244                 writer::with_context($context)
245                     ->export_user_preference('mod_assignment', $key, $value, $preference['string']);
246             }
247         }
248     }
250     /**
251      * Delete all data for all users in the specified context.
252      *
253      * @param \context $context the context to delete in.
254      */
255     public static function delete_data_for_all_users_in_context(\context $context) {
256         global $DB;
258         if ($context->contextlevel == CONTEXT_MODULE) {
259             // Delete all assignment submissions for the assignment associated with the context module.
260             $assignment = self::get_assignment_by_context($context);
261             if ($assignment != null) {
262                 $DB->delete_records('assignment_submissions', ['assignment' => $assignment->id]);
264                 // Delete all file uploads associated with the assignment submission for the specified context.
265                 $fs = get_file_storage();
266                 $fs->delete_area_files($context->id, 'mod_assignment', 'submission');
267             }
268         }
269     }
271     /**
272      * Delete all user data for the specified user, in the specified contexts.
273      *
274      * @param approved_contextlist $contextlist a list of contexts approved for deletion.
275      */
276     public static function delete_data_for_user(approved_contextlist $contextlist) {
277         global $DB;
279         if (empty($contextlist->count())) {
280             return;
281         }
283         $userid = $contextlist->get_user()->id;
285         // Only retrieve assignment submissions submitted by the user for deletion.
286         $assignmentsubmissionids = array_keys(self::get_assignment_submissions_by_contextlist($contextlist, $userid));
287         $DB->delete_records_list('assignment_submissions', 'id', $assignmentsubmissionids);
289         // Delete all file uploads associated with the assignment submission for the user's specified list of contexts.
290         $fs = get_file_storage();
291         foreach ($contextlist->get_contextids() as $contextid) {
292             foreach ($assignmentsubmissionids as $submissionid) {
293                 $fs->delete_area_files($contextid, 'mod_assignment', 'submission', $submissionid);
294             }
295         }
296     }
298     /**
299      * Delete multiple users within a single context.
300      *
301      * @param   approved_userlist       $userlist The approved context and user information to delete information for.
302      */
303     public static function delete_data_for_users(approved_userlist $userlist) {
304         global $DB;
306         $context = $userlist->get_context();
307         // If the context isn't for a module then return early.
308         if ($context->contextlevel != CONTEXT_MODULE) {
309             return;
310         }
311         // Fetch the assignment.
312         $assignment = self::get_assignment_by_context($context);
313         $userids = $userlist->get_userids();
315         list($inorequalsql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
316         $params['assignmentid'] = $assignment->id;
318         // Get submission ids.
319         $sql = "
320             SELECT s.id
321             FROM {assignment_submissions} s
322             JOIN {assignment} a ON s.assignment = a.id
323             WHERE a.id = :assignmentid
324             AND s.userid $inorequalsql
325         ";
327         $submissionids = $DB->get_records_sql($sql, $params);
328         list($submissionidsql, $submissionparams) = $DB->get_in_or_equal(array_keys($submissionids), SQL_PARAMS_NAMED);
329         $fs = get_file_storage();
330         $fs->delete_area_files_select($context->id, 'mod_assignment', 'submission', $submissionidsql, $submissionparams);
331         // Delete related tables.
332         $DB->delete_records_list('assignment_submissions', 'id', array_keys($submissionids));
333     }
335     // Start of helper functions.
337     /**
338      * Helper function to check if a user has marked assignment submissions for a given assignment.
339      *
340      * @param int $assignmentid The assignment ID to check if user has marked associated submissions.
341      * @param int $userid       The user ID to check if user has marked associated submissions.
342      * @return bool             If user has marked associated submissions returns true, otherwise false.
343      * @throws \dml_exception
344      */
345     protected static function has_marked_assignment_submissions($assignmentid, $userid) {
346         global $DB;
348         $params = [
349             'assignment' => $assignmentid,
350             'teacher'    => $userid
351         ];
353         $sql = "SELECT count(s.id) as nomarked
354                   FROM {assignment_submissions} s
355                  WHERE s.assignment = :assignment
356                    AND s.teacher = :teacher";
358         $results = $DB->get_record_sql($sql, $params);
360         return ($results->nomarked > 0) ? true : false;
361     }
363     /**
364      * Helper function to return assignment for a context module.
365      *
366      * @param object $context   The context module object to return the assignment record by.
367      * @return mixed            The assignment details or null record associated with the context module.
368      * @throws \dml_exception
369      */
370     protected static function get_assignment_by_context($context) {
371         global $DB;
373         $params = [
374             'modulename' => 'assignment',
375             'contextmodule' => CONTEXT_MODULE,
376             'contextid' => $context->id
377         ];
379         $sql = "SELECT a.id,
380                        a.name,
381                        a.intro,
382                        a.assignmenttype,
383                        a.grade,
384                        a.timedue,
385                        a.timeavailable,
386                        a.timemodified
387                   FROM {assignment} a
388                   JOIN {course_modules} cm ON a.id = cm.instance
389                   JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
390                   JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextmodule
391                  WHERE ctx.id = :contextid";
393         return $DB->get_record_sql($sql, $params);
394     }
396     /**
397      * Helper function to return assignment submissions submitted by / marked by a user and their contextlist.
398      *
399      * @param object $contextlist   Object with the contexts related to a userid to retrieve assignment submissions by.
400      * @param int $userid           The user ID to find assignment submissions that were submitted by.
401      * @param bool $teacher         The teacher status to determine if marked assignment submissions should be returned.
402      * @return array                Array of assignment submission details.
403      * @throws \coding_exception
404      * @throws \dml_exception
405      */
406     protected static function get_assignment_submissions_by_contextlist($contextlist, $userid, $teacher = false) {
407         global $DB;
409         list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
411         $params = [
412             'contextmodule' => CONTEXT_MODULE,
413             'modulename' => 'assignment',
414             'userid' => $userid
415         ];
417         $sql = "SELECT s.id as id,
418                        s.assignment as assignment,
419                        s.numfiles as numfiles,
420                        s.data1 as data1,
421                        s.data2 as data2,
422                        s.grade as grade,
423                        s.submissioncomment as submissioncomment,
424                        s.teacher as teacher,
425                        s.timemarked as timemarked,
426                        s.timecreated as timecreated,
427                        s.timemodified as timemodified
428                   FROM {context} ctx
429                   JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmodule
430                   JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
431                   JOIN {assignment} a ON cm.instance = a.id
432                   JOIN {assignment_submissions} s ON s.assignment = a.id
433                  WHERE (s.userid = :userid";
435         if ($teacher == true) {
436             $sql .= " OR s.teacher = :teacher";
437             $params['teacher'] = $userid;
438         }
440         $sql .= ")";
442         $sql .= " AND ctx.id {$contextsql}";
443         $params += $contextparams;
445         return $DB->get_records_sql($sql, $params);
446     }
448     /**
449      * Helper function to retrieve assignment submissions submitted by / marked by a user for a specific assignment.
450      *
451      * @param int $assignmentid     The assignment ID to retrieve assignment submissions by.
452      * @param int $userid           The user ID to retrieve assignment submissions submitted / marked by.
453      * @param bool $teacher         The teacher status to determine if marked assignment submissions should be returned.
454      * @return array                Array of assignment submissions details.
455      * @throws \dml_exception
456      */
457     protected static function get_assignment_submissions_by_assignment($assignmentid, $userid, $teacher = false) {
458         global $DB;
460         $params = [
461             'assignment' => $assignmentid,
462             'userid' => $userid
463         ];
465         $sql = "SELECT s.id as id,
466                        s.assignment as assignment,
467                        s.numfiles as numfiles,
468                        s.data1 as data1,
469                        s.data2 as data2,
470                        s.grade as grade,
471                        s.submissioncomment as submissioncomment,
472                        s.teacher as teacher,
473                        s.timemarked as timemarked,
474                        s.timecreated as timecreated,
475                        s.timemodified as timemodified,
476                        s.userid as userid
477                   FROM {assignment_submissions} s
478                  WHERE s.assignment = :assignment
479                    AND (s.userid = :userid";
481         if ($teacher == true) {
482             $sql .= " OR s.teacher = :teacher";
483             $params['teacher'] = $userid;
484         }
486         $sql .= ")";
488         return $DB->get_records_sql($sql, $params);
489     }
491     /**
492      * Helper function generate assignment output object for exporting.
493      *
494      * @param object $assignmentdata    Object containing assignment data.
495      * @return object                   Formatted assignment output object for exporting.
496      */
497     protected static function get_assignment_output($assignmentdata) {
498         $assignment = (object) [
499             'name' => $assignmentdata->name,
500             'intro' => $assignmentdata->intro,
501             'assignmenttype' => $assignmentdata->assignmenttype,
502             'grade' => $assignmentdata->grade,
503             'timemodified' => transform::datetime($assignmentdata->timemodified)
504         ];
506         if ($assignmentdata->timeavailable != 0) {
507             $assignment->timeavailable = transform::datetime($assignmentdata->timeavailable);
508         }
510         if ($assignmentdata->timedue != 0) {
511             $assignment->timedue = transform::datetime($assignmentdata->timedue);
512         }
514         return $assignment;
515     }
517     /**
518      * Helper function generate assignment submission output object for exporting.
519      *
520      * @param object $submissiondata    Object containing assignment submission data.
521      * @return object                   Formatted assignment submission output for exporting.
522      */
523     protected static function get_assignment_submission_output($submissiondata) {
524         $submission = (object) [
525             'assignment' => $submissiondata->assignment,
526             'numfiles' => $submissiondata->numfiles,
527             'data1' => $submissiondata->data1,
528             'data2' => $submissiondata->data2,
529             'grade' => $submissiondata->grade,
530             'submissioncomment' => $submissiondata->submissioncomment,
531             'teacher' => transform::user($submissiondata->teacher)
532         ];
534         if ($submissiondata->timecreated != 0) {
535             $submission->timecreated = transform::datetime($submissiondata->timecreated);
536         }
538         if ($submissiondata->timemarked != 0) {
539             $submission->timemarked = transform::datetime($submissiondata->timemarked);
540         }
542         if ($submissiondata->timemodified != 0) {
543             $submission->timemodified = transform::datetime($submissiondata->timemodified);
544         }
546         return $submission;
547     }