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/>.
16
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 */
24
25namespace mod_assign\privacy;
26
27defined('MOODLE_INTERNAL') || die();
28
29require_once($CFG->dirroot . '/mod/assign/locallib.php');
30
31use \core_privacy\local\metadata\collection;
32use \core_privacy\local\request\contextlist;
33use \core_privacy\local\request\writer;
34use \core_privacy\local\request\approved_contextlist;
35use \core_privacy\local\request\transform;
36use \core_privacy\local\request\helper;
37use \core_privacy\local\request\userlist;
38use \core_privacy\local\request\approved_userlist;
39use \core_privacy\manager;
40
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 */
48class 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 {
53
54    /** Interface for all assign submission sub-plugins. */
55    const ASSIGNSUBMISSION_INTERFACE = 'mod_assign\privacy\assignsubmission_provider';
56
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';
59
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';
62
63    /** Interface for all assign feedback sub-plugins. */
64    const ASSIGNFEEDBACK_INTERFACE = 'mod_assign\privacy\assignfeedback_provider';
65
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');
121
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');
126
127        return $collection;
128    }
129
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];
145
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)";
152
153        $contextlist = new contextlist();
154        $contextlist->add_from_sql($sql, $params);
155
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";
163
164        $contextlist->add_from_sql($sql, $params);
165
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";
173
174        $contextlist->add_from_sql($sql, $params);
175
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";
183
184        $contextlist->add_from_sql($sql, $params);
185
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";
193
194        $contextlist->add_from_sql($sql, $params);
195
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]);
200
201        return $contextlist;
202    }
203
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) {
210
211        $context = $userlist->get_context();
212        if ($context->contextlevel != CONTEXT_MODULE) {
213            return;
214        }
215
216        $params = [
217            'modulename' => 'assign',
218            'contextid' => $context->id,
219            'contextlevel' => CONTEXT_MODULE
220        ];
221
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);
231
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);
240
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);
249
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);
258
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);
267
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    }
273
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);
288
289            writer::with_context($context)->export_data([], $assigndata);
290            $assign = new \assign($context, null, null);
291
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            }
301
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    }
316
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;
324
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]);
338
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                }
345
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    }
354
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;
362
363        $user = $contextlist->get_user();
364
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;
372
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            }
379
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            }
392
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    }
400
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;
408
409        $context = $userlist->get_context();
410        if ($context->contextlevel != CONTEXT_MODULE) {
411            return;
412        }
413
414        $userids = $userlist->get_userids();
415
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]);
425
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        }
436
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    }
445
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;
455
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    }
484
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];
494
495        $sql = "SELECT DISTINCT userid AS id
496                  FROM {assign_grades}
497                 WHERE grader = :grader AND assignment = :assignid";
498
499        $useridlist = new useridlist($userid, $assign->get_instance()->id);
500        $useridlist->add_from_sql($sql, $params);
501
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]);
506
507        $userids = $useridlist->get_userids();
508        return ($userids) ? $userids : false;
509    }
510
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);
525
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    }
538
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    }
557
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    }
577
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    }
605
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) {
614
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    }
633
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))]);
654
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]);
666
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    }
675}
676