MDL-63816 privacy: Corrected provider get_users_in_context descriptions
[moodle.git] / mod / assign / 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    mod_assign
21  * @copyright  2018 Adrian Greeve <adrian@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace mod_assign\privacy;
27 defined('MOODLE_INTERNAL') || die();
29 require_once($CFG->dirroot . '/mod/assign/locallib.php');
31 use \core_privacy\local\metadata\collection;
32 use \core_privacy\local\request\contextlist;
33 use \core_privacy\local\request\writer;
34 use \core_privacy\local\request\approved_contextlist;
35 use \core_privacy\local\request\transform;
36 use \core_privacy\local\request\helper;
37 use \core_privacy\local\request\userlist;
38 use \core_privacy\local\request\approved_userlist;
39 use \core_privacy\manager;
41 /**
42  * Privacy class for requesting user data.
43  *
44  * @package    mod_assign
45  * @copyright  2018 Adrian Greeve <adrian@moodle.com>
46  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
47  */
48 class provider implements
49         \core_privacy\local\metadata\provider,
50         \core_privacy\local\request\plugin\provider,
51         \core_privacy\local\request\user_preference_provider,
52         \core_privacy\local\request\core_userlist_provider {
54     /** Interface for all assign submission sub-plugins. */
55     const ASSIGNSUBMISSION_INTERFACE = 'mod_assign\privacy\assignsubmission_provider';
57     /** Interface for all assign submission sub-plugins. This allows for deletion of users with a context. */
58     const ASSIGNSUBMISSION_USER_INTERFACE = 'mod_assign\privacy\assignsubmission_user_provider';
60     /** Interface for all assign feedback sub-plugins. This allows for deletion of users with a context. */
61     const ASSIGNFEEDBACK_USER_INTERFACE = 'mod_assign\privacy\assignfeedback_user_provider';
63     /** Interface for all assign feedback sub-plugins. */
64     const ASSIGNFEEDBACK_INTERFACE = 'mod_assign\privacy\assignfeedback_provider';
66     /**
67      * Provides meta data that is stored about a user with mod_assign
68      *
69      * @param  collection $collection A collection of meta data items to be added to.
70      * @return  collection Returns the collection of metadata.
71      */
72     public static function get_metadata(collection $collection) : collection {
73         $assigngrades = [
74                 'userid' => 'privacy:metadata:userid',
75                 'timecreated' => 'privacy:metadata:timecreated',
76                 'timemodified' => 'timemodified',
77                 'grader' => 'privacy:metadata:grader',
78                 'grade' => 'privacy:metadata:grade',
79                 'attemptnumber' => 'attemptnumber'
80         ];
81         $assignoverrides = [
82                 'groupid' => 'privacy:metadata:groupid',
83                 'userid' => 'privacy:metadata:userid',
84                 'allowsubmissionsfromdate' => 'allowsubmissionsfromdate',
85                 'duedate' => 'duedate',
86                 'cutoffdate' => 'cutoffdate'
87         ];
88         $assignsubmission = [
89                 'userid' => 'privacy:metadata:userid',
90                 'timecreated' => 'privacy:metadata:timecreated',
91                 'timemodified' => 'timemodified',
92                 'status' => 'gradingstatus',
93                 'groupid' => 'privacy:metadata:groupid',
94                 'attemptnumber' => 'attemptnumber',
95                 'latest' => 'privacy:metadata:latest'
96         ];
97         $assignuserflags = [
98                 'userid' => 'privacy:metadata:userid',
99                 'assignment' => 'privacy:metadata:assignmentid',
100                 'locked' => 'locksubmissions',
101                 'mailed' => 'privacy:metadata:mailed',
102                 'extensionduedate' => 'extensionduedate',
103                 'workflowstate' => 'markingworkflowstate',
104                 'allocatedmarker' => 'allocatedmarker'
105         ];
106         $assignusermapping = [
107                 'assignment' => 'privacy:metadata:assignmentid',
108                 'userid' => 'privacy:metadata:userid'
109         ];
110         $collection->add_database_table('assign_grades', $assigngrades, 'privacy:metadata:assigngrades');
111         $collection->add_database_table('assign_overrides', $assignoverrides, 'privacy:metadata:assignoverrides');
112         $collection->add_database_table('assign_submission', $assignsubmission, 'privacy:metadata:assignsubmissiondetail');
113         $collection->add_database_table('assign_user_flags', $assignuserflags, 'privacy:metadata:assignuserflags');
114         $collection->add_database_table('assign_user_mapping', $assignusermapping, 'privacy:metadata:assignusermapping');
115         $collection->add_user_preference('assign_perpage', 'privacy:metadata:assignperpage');
116         $collection->add_user_preference('assign_filter', 'privacy:metadata:assignfilter');
117         $collection->add_user_preference('assign_markerfilter', 'privacy:metadata:assignmarkerfilter');
118         $collection->add_user_preference('assign_workflowfilter', 'privacy:metadata:assignworkflowfilter');
119         $collection->add_user_preference('assign_quickgrading', 'privacy:metadata:assignquickgrading');
120         $collection->add_user_preference('assign_downloadasfolders', 'privacy:metadata:assigndownloadasfolders');
122         // Link to subplugins.
123         $collection->add_plugintype_link('assignsubmission', [],'privacy:metadata:assignsubmissionpluginsummary');
124         $collection->add_plugintype_link('assignfeedback', [], 'privacy:metadata:assignfeedbackpluginsummary');
125         $collection->add_subsystem_link('core_message', [], 'privacy:metadata:assignmessageexplanation');
127         return $collection;
128     }
130     /**
131      * Returns all of the contexts that has information relating to the userid.
132      *
133      * @param  int $userid The user ID.
134      * @return contextlist an object with the contexts related to a userid.
135      */
136     public static function get_contexts_for_userid(int $userid) : contextlist {
137         $params = ['modulename' => 'assign',
138                    'contextlevel' => CONTEXT_MODULE,
139                    'userid' => $userid,
140                    'graderid' => $userid,
141                    'aouserid' => $userid,
142                    'asnuserid' => $userid,
143                    'aufuserid' => $userid,
144                    'aumuserid' => $userid];
146         $sql = "SELECT ctx.id
147                   FROM {course_modules} cm
148                   JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
149                   JOIN {assign} a ON cm.instance = a.id
150                   JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
151                   JOIN {assign_grades} ag ON a.id = ag.assignment AND (ag.userid = :userid OR ag.grader = :graderid)";
153         $contextlist = new contextlist();
154         $contextlist->add_from_sql($sql, $params);
156         $sql = "SELECT ctx.id
157                   FROM {course_modules} cm
158                   JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
159                   JOIN {assign} a ON cm.instance = a.id
160                   JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
161                   JOIN {assign_overrides} ao ON a.id = ao.assignid
162                  WHERE ao.userid = :aouserid";
164         $contextlist->add_from_sql($sql, $params);
166         $sql = "SELECT ctx.id
167                   FROM {course_modules} cm
168                   JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
169                   JOIN {assign} a ON cm.instance = a.id
170                   JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
171                   JOIN {assign_submission} asn ON a.id = asn.assignment
172                  WHERE asn.userid = :asnuserid";
174         $contextlist->add_from_sql($sql, $params);
176         $sql = "SELECT ctx.id
177                   FROM {course_modules} cm
178                   JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
179                   JOIN {assign} a ON cm.instance = a.id
180                   JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
181                   JOIN {assign_user_flags} auf ON a.id = auf.assignment
182                  WHERE auf.userid = :aufuserid";
184         $contextlist->add_from_sql($sql, $params);
186         $sql = "SELECT ctx.id
187                   FROM {course_modules} cm
188                   JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
189                   JOIN {assign} a ON cm.instance = a.id
190                   JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
191                   JOIN {assign_user_mapping} aum ON a.id = aum.assignment
192                  WHERE aum.userid = :aumuserid";
194         $contextlist->add_from_sql($sql, $params);
196         manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_INTERFACE,
197                 'get_context_for_userid_within_feedback', [$userid, $contextlist]);
198         manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_INTERFACE,
199                 'get_context_for_userid_within_submission', [$userid, $contextlist]);
201         return $contextlist;
202     }
204     /**
205      * Get the list of users who have data within a context.
206      *
207      * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
208      */
209     public static function get_users_in_context(userlist $userlist) {
211         $context = $userlist->get_context();
212         if ($context->contextlevel != CONTEXT_MODULE) {
213             return;
214         }
216         $params = [
217             'modulename' => 'assign',
218             'contextid' => $context->id,
219             'contextlevel' => CONTEXT_MODULE
220         ];
222         $sql = "SELECT g.userid, g.grader
223                   FROM {context} ctx
224                   JOIN {course_modules} cm ON cm.id = ctx.instanceid
225                   JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
226                   JOIN {assign} a ON a.id = cm.instance
227                   JOIN {assign_grades} g ON a.id = g.assignment
228                  WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
229         $userlist->add_from_sql('userid', $sql, $params);
230         $userlist->add_from_sql('grader', $sql, $params);
232         $sql = "SELECT o.userid
233                   FROM {context} ctx
234                   JOIN {course_modules} cm ON cm.id = ctx.instanceid
235                   JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
236                   JOIN {assign} a ON a.id = cm.instance
237                   JOIN {assign_overrides} o ON a.id = o.assignid
238                  WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
239         $userlist->add_from_sql('userid', $sql, $params);
241         $sql = "SELECT s.userid
242                   FROM {context} ctx
243                   JOIN {course_modules} cm ON cm.id = ctx.instanceid
244                   JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
245                   JOIN {assign} a ON a.id = cm.instance
246                   JOIN {assign_submission} s ON a.id = s.assignment
247                  WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
248         $userlist->add_from_sql('userid', $sql, $params);
250         $sql = "SELECT uf.userid
251                   FROM {context} ctx
252                   JOIN {course_modules} cm ON cm.id = ctx.instanceid
253                   JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
254                   JOIN {assign} a ON a.id = cm.instance
255                   JOIN {assign_user_flags} uf ON a.id = uf.assignment
256                  WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
257         $userlist->add_from_sql('userid', $sql, $params);
259         $sql = "SELECT um.userid
260                   FROM {context} ctx
261                   JOIN {course_modules} cm ON cm.id = ctx.instanceid
262                   JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
263                   JOIN {assign} a ON a.id = cm.instance
264                   JOIN {assign_user_mapping} um ON a.id = um.assignment
265                  WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
266         $userlist->add_from_sql('userid', $sql, $params);
268         manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_USER_INTERFACE,
269                 'get_userids_from_context', [$userlist]);
270         manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_USER_INTERFACE,
271                 'get_userids_from_context', [$userlist]);
272     }
274     /**
275      * Write out the user data filtered by contexts.
276      *
277      * @param approved_contextlist $contextlist contexts that we are writing data out from.
278      */
279     public static function export_user_data(approved_contextlist $contextlist) {
280         foreach ($contextlist->get_contexts() as $context) {
281             // Check that the context is a module context.
282             if ($context->contextlevel != CONTEXT_MODULE) {
283                 continue;
284             }
285             $user = $contextlist->get_user();
286             $assigndata = helper::get_context_data($context, $user);
287             helper::export_context_files($context, $user);
289             writer::with_context($context)->export_data([], $assigndata);
290             $assign = new \assign($context, null, null);
292             // I need to find out if I'm a student or a teacher.
293             if ($userids = self::get_graded_users($user->id, $assign)) {
294                 // Return teacher info.
295                 $currentpath = [get_string('privacy:studentpath', 'mod_assign')];
296                 foreach ($userids as $studentuserid) {
297                     $studentpath = array_merge($currentpath, [$studentuserid->id]);
298                     static::export_submission($assign, $studentuserid, $context, $studentpath, true);
299                 }
300             }
302             static::export_overrides($context, $assign, $user);
303             static::export_submission($assign, $user, $context, []);
304             // Meta data.
305             self::store_assign_user_flags($context, $assign, $user->id);
306             if ($assign->is_blind_marking()) {
307                 $uniqueid = $assign->get_uniqueid_for_user_static($assign->get_instance()->id, $contextlist->get_user()->id);
308                 if ($uniqueid) {
309                     writer::with_context($context)
310                             ->export_metadata([get_string('blindmarking', 'mod_assign')], 'blindmarkingid', $uniqueid,
311                                     get_string('privacy:blindmarkingidentifier', 'mod_assign'));
312                 }
313             }
314         }
315     }
317     /**
318      * Delete all use data which matches the specified context.
319      *
320      * @param \context $context The module context.
321      */
322     public static function delete_data_for_all_users_in_context(\context $context) {
323         global $DB;
325         if ($context->contextlevel == CONTEXT_MODULE) {
326             $cm = get_coursemodule_from_id('assign', $context->instanceid);
327             if ($cm) {
328                 // Get the assignment related to this context.
329                 $assign = new \assign($context, null, null);
330                 // What to do first... Get sub plugins to delete their stuff.
331                 $requestdata = new assign_plugin_request_data($context, $assign);
332                 manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_INTERFACE,
333                     'delete_submission_for_context', [$requestdata]);
334                 $requestdata = new assign_plugin_request_data($context, $assign);
335                 manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_INTERFACE,
336                     'delete_feedback_for_context', [$requestdata]);
337                 $DB->delete_records('assign_grades', ['assignment' => $assign->get_instance()->id]);
339                 // Delete advanced grading information.
340                 $gradingmanager = get_grading_manager($context, 'mod_assign', 'submissions');
341                 $controller = $gradingmanager->get_active_controller();
342                 if (isset($controller)) {
343                     \core_grading\privacy\provider::delete_instance_data($context);
344                 }
346                 // Time to roll my own method for deleting overrides.
347                 static::delete_overrides_for_users($assign);
348                 $DB->delete_records('assign_submission', ['assignment' => $assign->get_instance()->id]);
349                 $DB->delete_records('assign_user_flags', ['assignment' => $assign->get_instance()->id]);
350                 $DB->delete_records('assign_user_mapping', ['assignment' => $assign->get_instance()->id]);
351             }
352         }
353     }
355     /**
356      * Delete all user data for the specified user, in the specified contexts.
357      *
358      * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
359      */
360     public static function delete_data_for_user(approved_contextlist $contextlist) {
361         global $DB;
363         $user = $contextlist->get_user();
365         foreach ($contextlist as $context) {
366             if ($context->contextlevel != CONTEXT_MODULE) {
367                 continue;
368             }
369             // Get the assign object.
370             $assign = new \assign($context, null, null);
371             $assignid = $assign->get_instance()->id;
373             $submissions = $DB->get_records('assign_submission', ['assignment' => $assignid, 'userid' => $user->id]);
374             foreach ($submissions as $submission) {
375                 $requestdata = new assign_plugin_request_data($context, $assign, $submission, [], $user);
376                 manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_INTERFACE,
377                         'delete_submission_for_userid', [$requestdata]);
378             }
380             $grades = $DB->get_records('assign_grades', ['assignment' => $assignid, 'userid' => $user->id]);
381             $gradingmanager = get_grading_manager($context, 'mod_assign', 'submissions');
382             $controller = $gradingmanager->get_active_controller();
383             foreach ($grades as $grade) {
384                 $requestdata = new assign_plugin_request_data($context, $assign, $grade, [], $user);
385                 manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_INTERFACE,
386                         'delete_feedback_for_grade', [$requestdata]);
387                 // Delete advanced grading information.
388                 if (isset($controller)) {
389                     \core_grading\privacy\provider::delete_instance_data($context, $grade->id);
390                 }
391             }
393             static::delete_overrides_for_users($assign, [$user->id]);
394             $DB->delete_records('assign_user_flags', ['assignment' => $assignid, 'userid' => $user->id]);
395             $DB->delete_records('assign_user_mapping', ['assignment' => $assignid, 'userid' => $user->id]);
396             $DB->delete_records('assign_grades', ['assignment' => $assignid, 'userid' => $user->id]);
397             $DB->delete_records('assign_submission', ['assignment' => $assignid, 'userid' => $user->id]);
398         }
399     }
401     /**
402      * Delete multiple users within a single context.
403      *
404      * @param  approved_userlist $userlist The approved context and user information to delete information for.
405      */
406     public static function delete_data_for_users(approved_userlist $userlist) {
407         global $DB;
409         $context = $userlist->get_context();
410         if ($context->contextlevel != CONTEXT_MODULE) {
411             return;
412         }
414         $userids = $userlist->get_userids();
416         $assign = new \assign($context, null, null);
417         $assignid = $assign->get_instance()->id;
418         $requestdata = new assign_plugin_request_data($context, $assign);
419         $requestdata->set_userids($userids);
420         $requestdata->populate_submissions_and_grades();
421         manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_USER_INTERFACE, 'delete_submissions',
422                 [$requestdata]);
423         manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_USER_INTERFACE, 'delete_feedback_for_grades',
424                 [$requestdata]);
426         // Update this function to delete advanced grading information.
427         $gradingmanager = get_grading_manager($context, 'mod_assign', 'submissions');
428         $controller = $gradingmanager->get_active_controller();
429         if (isset($controller)) {
430             $gradeids = $requestdata->get_gradeids();
431             // Careful here, if no gradeids are provided then all data is deleted for the context.
432             if (!empty($gradeids)) {
433                 \core_grading\privacy\provider::delete_data_for_instances($context, $gradeids);
434             }
435         }
437         static::delete_overrides_for_users($assign, $userids);
438         list($sql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
439         $params['assignment'] = $assignid;
440         $DB->delete_records_select('assign_user_flags', "assignment = :assignment AND userid $sql", $params);
441         $DB->delete_records_select('assign_user_mapping', "assignment = :assignment AND userid $sql", $params);
442         $DB->delete_records_select('assign_grades', "assignment = :assignment AND userid $sql", $params);
443         $DB->delete_records_select('assign_submission', "assignment = :assignment AND userid $sql", $params);
444     }
446     /**
447      * Deletes assignment overrides in bulk
448      *
449      * @param  \assign $assign  The assignment object
450      * @param  array   $userids An array of user IDs
451      */
452     protected static function delete_overrides_for_users(\assign $assign, array $userids = []) {
453         global $DB;
454         $assignid = $assign->get_instance()->id;
456         $usersql = '';
457         $params = ['assignid' => $assignid];
458         if (!empty($userids)) {
459             list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
460             $params = array_merge($params, $userparams);
461             $overrides = $DB->get_records_select('assign_overrides', "assignid = :assignid AND userid $usersql", $params);
462         } else {
463             $overrides = $DB->get_records('assign_overrides', $params);
464         }
465         if (!empty($overrides)) {
466             $params = ['modulename' => 'assign', 'instance' => $assignid];
467             if (!empty($userids)) {
468                 $params = array_merge($params, $userparams);
469                 $DB->delete_records_select('event', "modulename = :modulename AND instance = :instance AND userid $usersql",
470                         $params);
471                 // Setting up for the next query.
472                 $params = $userparams;
473                 $usersql = "AND userid $usersql";
474             } else {
475                 $DB->delete_records('event', $params);
476                 // Setting up for the next query.
477                 $params = [];
478             }
479             list($overridesql, $overrideparams) = $DB->get_in_or_equal(array_keys($overrides), SQL_PARAMS_NAMED);
480             $params = array_merge($params, $overrideparams);
481             $DB->delete_records_select('assign_overrides', "id $overridesql $usersql", $params);
482         }
483     }
485     /**
486      * Find out if this user has graded any users.
487      *
488      * @param  int $userid The user ID (potential teacher).
489      * @param  assign $assign The assignment object.
490      * @return array If successful an array of objects with userids that this user graded, otherwise false.
491      */
492     protected static function get_graded_users(int $userid, \assign $assign) {
493         $params = ['grader' => $userid, 'assignid' => $assign->get_instance()->id];
495         $sql = "SELECT DISTINCT userid AS id
496                   FROM {assign_grades}
497                  WHERE grader = :grader AND assignment = :assignid";
499         $useridlist = new useridlist($userid, $assign->get_instance()->id);
500         $useridlist->add_from_sql($sql, $params);
502         // Call sub-plugins to see if they have information not already collected.
503         manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_INTERFACE, 'get_student_user_ids',
504                 [$useridlist]);
505         manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_INTERFACE, 'get_student_user_ids', [$useridlist]);
507         $userids = $useridlist->get_userids();
508         return ($userids) ? $userids : false;
509     }
511     /**
512      * Writes out various user meta data about the assignment.
513      *
514      * @param  \context $context The context of this assignment.
515      * @param  \assign $assign The assignment object.
516      * @param  int $userid The user ID
517      */
518     protected static function store_assign_user_flags(\context $context, \assign $assign, int $userid) {
519         $datatypes = ['locked' => get_string('locksubmissions', 'mod_assign'),
520                       'mailed' => get_string('privacy:metadata:mailed', 'mod_assign'),
521                       'extensionduedate' => get_string('extensionduedate', 'mod_assign'),
522                       'workflowstate' => get_string('markingworkflowstate', 'mod_assign'),
523                       'allocatedmarker' => get_string('allocatedmarker_help', 'mod_assign')];
524         $userflags = (array)$assign->get_user_flags($userid, false);
526         foreach ($datatypes as $key => $description) {
527             if (isset($userflags[$key]) && !empty($userflags[$key])) {
528                 $value = $userflags[$key];
529                 if ($key == 'locked' || $key == 'mailed') {
530                     $value = transform::yesno($value);
531                 } else if ($key == 'extensionduedate') {
532                     $value = transform::datetime($value);
533                 }
534                 writer::with_context($context)->export_metadata([], $key, $value, $description);
535             }
536         }
537     }
539     /**
540      * Formats and then exports the user's grade data.
541      *
542      * @param  \stdClass $grade The assign grade object
543      * @param  \context $context The context object
544      * @param  array $currentpath Current directory path that we are exporting to.
545      */
546     protected static function export_grade_data(\stdClass $grade, \context $context, array $currentpath) {
547         $gradedata = (object)[
548             'timecreated' => transform::datetime($grade->timecreated),
549             'timemodified' => transform::datetime($grade->timemodified),
550             'grader' => transform::user($grade->grader),
551             'grade' => $grade->grade,
552             'attemptnumber' => ($grade->attemptnumber + 1)
553         ];
554         writer::with_context($context)
555                 ->export_data(array_merge($currentpath, [get_string('privacy:gradepath', 'mod_assign')]), $gradedata);
556     }
558     /**
559      * Formats and then exports the user's submission data.
560      *
561      * @param  \stdClass $submission The assign submission object
562      * @param  \context $context The context object
563      * @param  array $currentpath Current directory path that we are exporting to.
564      */
565     protected static function export_submission_data(\stdClass $submission, \context $context, array $currentpath) {
566         $submissiondata = (object)[
567             'timecreated' => transform::datetime($submission->timecreated),
568             'timemodified' => transform::datetime($submission->timemodified),
569             'status' => get_string('submissionstatus_' . $submission->status, 'mod_assign'),
570             'groupid' => $submission->groupid,
571             'attemptnumber' => ($submission->attemptnumber + 1),
572             'latest' => transform::yesno($submission->latest)
573         ];
574         writer::with_context($context)
575                 ->export_data(array_merge($currentpath, [get_string('privacy:submissionpath', 'mod_assign')]), $submissiondata);
576     }
578     /**
579      * Stores the user preferences related to mod_assign.
580      *
581      * @param  int $userid The user ID that we want the preferences for.
582      */
583     public static function export_user_preferences(int $userid) {
584         $context = \context_system::instance();
585         $assignpreferences = [
586             'assign_perpage' => ['string' => get_string('privacy:metadata:assignperpage', 'mod_assign'), 'bool' => false],
587             'assign_filter' => ['string' => get_string('privacy:metadata:assignfilter', 'mod_assign'), 'bool' => false],
588             'assign_markerfilter' => ['string' => get_string('privacy:metadata:assignmarkerfilter', 'mod_assign'), 'bool' => true],
589             'assign_workflowfilter' => ['string' => get_string('privacy:metadata:assignworkflowfilter', 'mod_assign'),
590                     'bool' => true],
591             'assign_quickgrading' => ['string' => get_string('privacy:metadata:assignquickgrading', 'mod_assign'), 'bool' => true],
592             'assign_downloadasfolders' => ['string' => get_string('privacy:metadata:assigndownloadasfolders', 'mod_assign'),
593                     'bool' => true]
594         ];
595         foreach ($assignpreferences as $key => $preference) {
596             $value = get_user_preferences($key, null, $userid);
597             if ($preference['bool']) {
598                 $value = transform::yesno($value);
599             }
600             if (isset($value)) {
601                 writer::with_context($context)->export_user_preference('mod_assign', $key, $value, $preference['string']);
602             }
603         }
604     }
606     /**
607      * Export overrides for this assignment.
608      *
609      * @param  \context $context Context
610      * @param  \assign $assign The assign object.
611      * @param  \stdClass $user The user object.
612      */
613     public static function export_overrides(\context $context, \assign $assign, \stdClass $user) {
615         $overrides = $assign->override_exists($user->id);
616         // Overrides returns an array with data in it, but an override with actual data will have the assign ID set.
617         if (isset($overrides->assignid)) {
618             $data = new \stdClass();
619             if (!empty($overrides->duedate)) {
620                 $data->duedate = transform::datetime($overrides->duedate);
621             }
622             if (!empty($overrides->cutoffdate)) {
623                 $data->cutoffdate = transform::datetime($overrides->cutoffdate);
624             }
625             if (!empty($overrides->allowsubmissionsfromdate)) {
626                 $data->allowsubmissionsfromdate = transform::datetime($overrides->allowsubmissionsfromdate);
627             }
628             if (!empty($data)) {
629                 writer::with_context($context)->export_data([get_string('overrides', 'mod_assign')], $data);
630             }
631         }
632     }
634     /**
635      * Exports assignment submission data for a user.
636      *
637      * @param  \assign         $assign           The assignment object
638      * @param  \stdClass        $user             The user object
639      * @param  \context_module $context          The context
640      * @param  array           $path             The path for exporting data
641      * @param  bool|boolean    $exportforteacher A flag for if this is exporting data as a teacher.
642      */
643     protected static function export_submission(\assign $assign, \stdClass $user, \context_module $context, array $path,
644             bool $exportforteacher = false) {
645         $submissions = $assign->get_all_submissions($user->id);
646         $teacher = ($exportforteacher) ? $user : null;
647         $gradingmanager = get_grading_manager($context, 'mod_assign', 'submissions');
648         $controller = $gradingmanager->get_active_controller();
649         foreach ($submissions as $submission) {
650             // Attempt numbers start at zero, which is fine for programming, but doesn't make as much sense
651             // for users.
652             $submissionpath = array_merge($path,
653                     [get_string('privacy:attemptpath', 'mod_assign', ($submission->attemptnumber + 1))]);
655             $params = new assign_plugin_request_data($context, $assign, $submission, $submissionpath ,$teacher);
656             manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_INTERFACE,
657                     'export_submission_user_data', [$params]);
658             if (!isset($teacher)) {
659                 self::export_submission_data($submission, $context, $submissionpath);
660             }
661             $grade = $assign->get_user_grade($user->id, false, $submission->attemptnumber);
662             if ($grade) {
663                 $params = new assign_plugin_request_data($context, $assign, $grade, $submissionpath, $teacher);
664                 manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_INTERFACE, 'export_feedback_user_data',
665                         [$params]);
667                 self::export_grade_data($grade, $context, $submissionpath);
668                 // Check for advanced grading and retrieve that information.
669                 if (isset($controller)) {
670                     \core_grading\privacy\provider::export_item_data($context, $grade->id, $submissionpath);
671                 }
672             }
673         }
674     }