Merge branch 'MDL-62169-master' of git://github.com/zig-moodle/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Mon, 7 May 2018 06:12:06 +0000 (14:12 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Mon, 7 May 2018 06:12:06 +0000 (14:12 +0800)
mod/assignment/classes/privacy/provider.php [new file with mode: 0644]
mod/assignment/lang/en/assignment.php
mod/assignment/tests/privacy_test.php [new file with mode: 0644]

diff --git a/mod/assignment/classes/privacy/provider.php b/mod/assignment/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..bc4dea1
--- /dev/null
@@ -0,0 +1,475 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for mod_assignment.
+ *
+ * @package    mod_assignment
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_assignment\privacy;
+
+use core_privacy\local\metadata\collection;
+use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\contextlist;
+use core_privacy\local\request\transform;
+use core_privacy\local\request\writer;
+use core_privacy\local\request\helper;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/assignment/lib.php');
+
+/**
+ * Implementation of the privacy subsystem plugin provider for mod_assignment.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+    \core_privacy\local\metadata\provider,
+    \core_privacy\local\request\plugin\provider,
+    \core_privacy\local\request\user_preference_provider {
+
+    /**
+     * Return the fields which contain personal data.
+     *
+     * @param collection $collection a reference to the collection to use to store the metadata.
+     * @return collection the updated collection of metadata items.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        $collection->add_database_table(
+            'assignment_submissions',
+            [
+                'userid' => 'privacy:metadata:assignment_submissions:userid',
+                'timecreated' => 'privacy:metadata:assignment_submissions:timecreated',
+                'timemodified' => 'privacy:metadata:assignment_submissions:timemodified',
+                'numfiles' => 'privacy:metadata:assignment_submissions:numfiles',
+                'data1' => 'privacy:metadata:assignment_submissions:data1',
+                'data2' => 'privacy:metadata:assignment_submissions:data2',
+                'grade' => 'privacy:metadata:assignment_submissions:grade',
+                'submissioncomment' => 'privacy:metadata:assignment_submissions:submissioncomment',
+                'teacher' => 'privacy:metadata:assignment_submissions:teacher',
+                'timemarked' => 'privacy:metadata:assignment_submissions:timemarked',
+                'mailed' => 'privacy:metadata:assignment_submissions:mailed'
+            ],
+            'privacy:metadata:assignment_submissions'
+        );
+
+        // Legacy mod_assignment preferences from Moodle 2.X.
+        $collection->add_user_preference('assignment_filter', 'privacy:metadata:assignmentfilter');
+        $collection->add_user_preference('assignment_mailinfo', 'privacy:metadata:assignmentmailinfo');
+        $collection->add_user_preference('assignment_perpage', 'privacy:metadata:assignmentperpage');
+        $collection->add_user_preference('assignment_quickgrade', 'privacy:metadata:assignmentquickgrade');
+
+        return $collection;
+    }
+
+    /**
+     * Get the list of contexts that contain user information for the specified user.
+     *
+     * @param int $userid the userid.
+     * @return contextlist the list of contexts containing user info for the user.
+     */
+    public static function get_contexts_for_userid(int $userid) : contextlist {
+        $contextlist = new contextlist();
+
+        $sql = "SELECT DISTINCT
+                       ctx.id
+                  FROM {context} ctx
+                  JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmodule
+                  JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
+                  JOIN {assignment} a ON cm.instance = a.id
+                  JOIN {assignment_submissions} s ON s.assignment = a.id
+                 WHERE s.userid = :userid
+                    OR s.teacher = :teacher";
+
+        $params = [
+            'contextmodule'  => CONTEXT_MODULE,
+            'modulename'    => 'assignment',
+            'userid'        => $userid,
+            'teacher'       => $userid
+        ];
+
+        $contextlist->add_from_sql($sql, $params);
+
+        return $contextlist;
+    }
+
+    /**
+     * Export personal data for the given approved_contextlist.
+     * User and context information is contained within the contextlist.
+     *
+     * @param approved_contextlist $contextlist a list of contexts approved for export.
+     */
+    public static function export_user_data(approved_contextlist $contextlist) {
+        if (empty($contextlist->count())) {
+            return;
+        }
+
+        $user = $contextlist->get_user();
+
+        foreach ($contextlist->get_contexts() as $context) {
+            if ($context->contextlevel != CONTEXT_MODULE) {
+                continue;
+            }
+
+            // Cannot make use of helper::export_context_files(), need to manually export assignment details.
+            $assignmentdata = self::get_assignment_by_context($context);
+
+            // Get assignment details object for output.
+            $assignment = self::get_assignment_output($assignmentdata);
+            writer::with_context($context)->export_data([], $assignment);
+
+            // Check if the user has marked any assignment's submissions to determine assignment submissions to export.
+            $teacher = (self::has_marked_assignment_submissions($assignmentdata->id, $user->id) == true) ? true : false;
+
+            // Get the assignment submissions submitted by & marked by the user for an assignment.
+            $submissionsdata = self::get_assignment_submissions_by_assignment($assignmentdata->id, $user->id, $teacher);
+
+            foreach ($submissionsdata as $submissiondata) {
+                // Default subcontext path to export assignment submissions submitted by the user.
+                $subcontexts = [
+                    get_string('privacy:submissionpath', 'mod_assignment')
+                ];
+
+                if ($teacher == true) {
+                    if ($submissiondata->teacher == $user->id) {
+                        // Export assignment submissions that have been marked by the user.
+                        $subcontexts = [
+                            get_string('privacy:markedsubmissionspath', 'mod_assignment'),
+                            transform::user($submissiondata->userid)
+                        ];
+                    }
+                }
+
+                // Get assignment submission details object for output.
+                $submission = self::get_assignment_submission_output($submissiondata);
+                $itemid = $submissiondata->id;
+
+                writer::with_context($context)
+                    ->export_data($subcontexts, $submission)
+                    ->export_area_files($subcontexts, 'mod_assignment', 'submission', $itemid);
+            }
+        }
+    }
+
+    /**
+     * Stores the user preferences related to mod_assign.
+     *
+     * @param  int $userid The user ID that we want the preferences for.
+     */
+    public static function export_user_preferences(int $userid) {
+        $context = \context_system::instance();
+        $assignmentpreferences = [
+            'assignment_filter' => [
+                'string' => get_string('privacy:metadata:assignmentfilter', 'mod_assignment'),
+                'bool' => false
+            ],
+            'assignment_mailinfo' => [
+                'string' => get_string('privacy:metadata:assignmentmailinfo', 'mod_assignment'),
+                'bool' => false
+            ],
+            'assignment_perpage' => [
+                'string' => get_string('privacy:metadata:assignmentperpage', 'mod_assignment'),
+                'bool' => false
+            ],
+            'assignment_quickgrade' => [
+                'string' => get_string('privacy:metadata:assignmentquickgrade', 'mod_assignment'),
+                'bool' => false
+            ],
+        ];
+        foreach ($assignmentpreferences as $key => $preference) {
+            $value = get_user_preferences($key, null, $userid);
+            if ($preference['bool']) {
+                $value = transform::yesno($value);
+            }
+            if (isset($value)) {
+                writer::with_context($context)
+                    ->export_user_preference('mod_assignment', $key, $value, $preference['string']);
+            }
+        }
+    }
+
+    /**
+     * Delete all data for all users in the specified context.
+     *
+     * @param \context $context the context to delete in.
+     */
+    public static function delete_data_for_all_users_in_context(\context $context) {
+        global $DB;
+
+        if (empty($context)) {
+            return;
+        }
+
+        if ($context->contextlevel == CONTEXT_MODULE) {
+            // Delete all assignment submissions for the assignment associated with the context module.
+            $assignment = self::get_assignment_by_context($context);
+            if ($assignment != null) {
+                $DB->delete_records('assignment_submissions', ['assignment' => $assignment->id]);
+
+                // Delete all file uploads associated with the assignment submission for the specified context.
+                $fs = get_file_storage();
+                $fs->delete_area_files($context->id, 'mod_assignment', 'submission');
+            }
+        }
+    }
+
+    /**
+     * Delete all user data for the specified user, in the specified contexts.
+     *
+     * @param approved_contextlist $contextlist a list of contexts approved for deletion.
+     */
+    public static function delete_data_for_user(approved_contextlist $contextlist) {
+        global $DB;
+
+        if (empty($contextlist->count())) {
+            return;
+        }
+
+        $userid = $contextlist->get_user()->id;
+
+        // Only retrieve assignment submissions submitted by the user for deletion.
+        $assignmentsubmissionids = array_keys(self::get_assignment_submissions_by_contextlist($contextlist, $userid));
+        $DB->delete_records_list('assignment_submissions', 'id', $assignmentsubmissionids);
+
+        // Delete all file uploads associated with the assignment submission for the user's specified list of contexts.
+        $fs = get_file_storage();
+        foreach ($contextlist->get_contextids() as $contextid) {
+            foreach ($assignmentsubmissionids as $submissionid) {
+                $fs->delete_area_files($contextid, 'mod_assignment', 'submission', $submissionid);
+            }
+        }
+    }
+
+    // Start of helper functions.
+
+    /**
+     * Helper function to check if a user has marked assignment submissions for a given assignment.
+     *
+     * @param int $assignmentid The assignment ID to check if user has marked associated submissions.
+     * @param int $userid       The user ID to check if user has marked associated submissions.
+     * @return bool             If user has marked associated submissions returns true, otherwise false.
+     * @throws \dml_exception
+     */
+    protected static function has_marked_assignment_submissions($assignmentid, $userid) {
+        global $DB;
+
+        $params = [
+            'assignment' => $assignmentid,
+            'teacher'    => $userid
+        ];
+
+        $sql = "SELECT count(s.id) as nomarked
+                  FROM {assignment_submissions} s
+                 WHERE s.assignment = :assignment
+                   AND s.teacher = :teacher";
+
+        $results = $DB->get_record_sql($sql, $params);
+
+        return ($results->nomarked > 0) ? true : false;
+    }
+
+    /**
+     * Helper function to return assignment for a context module.
+     *
+     * @param object $context   The context module object to return the assignment record by.
+     * @return mixed            The assignment details or null record associated with the context module.
+     * @throws \dml_exception
+     */
+    protected static function get_assignment_by_context($context) {
+        global $DB;
+
+        $params = [
+            'modulename' => 'assignment',
+            'contextmodule' => CONTEXT_MODULE,
+            'contextid' => $context->id
+        ];
+
+        $sql = "SELECT a.id,
+                       a.name,
+                       a.intro,
+                       a.assignmenttype,
+                       a.grade,
+                       a.timedue,
+                       a.timeavailable,
+                       a.timemodified
+                  FROM {assignment} a
+                  JOIN {course_modules} cm ON a.id = cm.instance
+                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+                  JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextmodule
+                 WHERE ctx.id = :contextid";
+
+        return $DB->get_record_sql($sql, $params);
+    }
+
+    /**
+     * Helper function to return assignment submissions submitted by / marked by a user and their contextlist.
+     *
+     * @param object $contextlist   Object with the contexts related to a userid to retrieve assignment submissions by.
+     * @param int $userid           The user ID to find assignment submissions that were submitted by.
+     * @param bool $teacher         The teacher status to determine if marked assignment submissions should be returned.
+     * @return array                Array of assignment submission details.
+     * @throws \coding_exception
+     * @throws \dml_exception
+     */
+    protected static function get_assignment_submissions_by_contextlist($contextlist, $userid, $teacher = false) {
+        global $DB;
+
+        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
+
+        $params = [
+            'contextmodule' => CONTEXT_MODULE,
+            'modulename' => 'assignment',
+            'userid' => $userid
+        ];
+
+        $sql = "SELECT s.id as id,
+                       s.assignment as assignment,
+                       s.numfiles as numfiles,
+                       s.data1 as data1,
+                       s.data2 as data2,
+                       s.grade as grade,
+                       s.submissioncomment as submissioncomment,
+                       s.teacher as teacher,
+                       s.timemarked as timemarked,
+                       s.timecreated as timecreated,
+                       s.timemodified as timemodified
+                  FROM {context} ctx
+                  JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmodule
+                  JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
+                  JOIN {assignment} a ON cm.instance = a.id
+                  JOIN {assignment_submissions} s ON s.assignment = a.id
+                 WHERE (s.userid = :userid";
+
+        if ($teacher == true) {
+            $sql .= " OR s.teacher = :teacher";
+            $params['teacher'] = $userid;
+        }
+
+        $sql .= ")";
+
+        $sql .= " AND ctx.id {$contextsql}";
+        $params += $contextparams;
+
+        return $DB->get_records_sql($sql, $params);
+    }
+
+    /**
+     * Helper function to retrieve assignment submissions submitted by / marked by a user for a specific assignment.
+     *
+     * @param int $assignmentid     The assignment ID to retrieve assignment submissions by.
+     * @param int $userid           The user ID to retrieve assignment submissions submitted / marked by.
+     * @param bool $teacher         The teacher status to determine if marked assignment submissions should be returned.
+     * @return array                Array of assignment submissions details.
+     * @throws \dml_exception
+     */
+    protected static function get_assignment_submissions_by_assignment($assignmentid, $userid, $teacher = false) {
+        global $DB;
+
+        $params = [
+            'assignment' => $assignmentid,
+            'userid' => $userid
+        ];
+
+        $sql = "SELECT s.id as id,
+                       s.assignment as assignment,
+                       s.numfiles as numfiles,
+                       s.data1 as data1,
+                       s.data2 as data2,
+                       s.grade as grade,
+                       s.submissioncomment as submissioncomment,
+                       s.teacher as teacher,
+                       s.timemarked as timemarked,
+                       s.timecreated as timecreated,
+                       s.timemodified as timemodified,
+                       s.userid as userid
+                  FROM {assignment_submissions} s
+                 WHERE s.assignment = :assignment
+                   AND (s.userid = :userid";
+
+        if ($teacher == true) {
+            $sql .= " OR s.teacher = :teacher";
+            $params['teacher'] = $userid;
+        }
+
+        $sql .= ")";
+
+        return $DB->get_records_sql($sql, $params);
+    }
+
+    /**
+     * Helper function generate assignment output object for exporting.
+     *
+     * @param object $assignmentdata    Object containing assignment data.
+     * @return object                   Formatted assignment output object for exporting.
+     */
+    protected static function get_assignment_output($assignmentdata) {
+        $assignment = (object) [
+            'name' => $assignmentdata->name,
+            'intro' => $assignmentdata->intro,
+            'assignmenttype' => $assignmentdata->assignmenttype,
+            'grade' => $assignmentdata->grade,
+            'timemodified' => transform::datetime($assignmentdata->timemodified)
+        ];
+
+        if ($assignmentdata->timeavailable != 0) {
+            $assignment->timeavailable = transform::datetime($assignmentdata->timeavailable);
+        }
+
+        if ($assignmentdata->timedue != 0) {
+            $assignment->timedue = transform::datetime($assignmentdata->timedue);
+        }
+
+        return $assignment;
+    }
+
+    /**
+     * Helper function generate assignment submission output object for exporting.
+     *
+     * @param object $submissiondata    Object containing assignment submission data.
+     * @return object                   Formatted assignment submission output for exporting.
+     */
+    protected static function get_assignment_submission_output($submissiondata) {
+        $submission = (object) [
+            'assignment' => $submissiondata->assignment,
+            'numfiles' => $submissiondata->numfiles,
+            'data1' => $submissiondata->data1,
+            'data2' => $submissiondata->data2,
+            'grade' => $submissiondata->grade,
+            'submissioncomment' => $submissiondata->submissioncomment,
+            'teacher' => transform::user($submissiondata->teacher)
+        ];
+
+        if ($submissiondata->timecreated != 0) {
+            $submission->timecreated = transform::datetime($submissiondata->timecreated);
+        }
+
+        if ($submissiondata->timemarked != 0) {
+            $submission->timemarked = transform::datetime($submissiondata->timemarked);
+        }
+
+        if ($submissiondata->timemodified != 0) {
+            $submission->timemodified = transform::datetime($submissiondata->timemodified);
+        }
+
+        return $submission;
+    }
+}
index 969f22d..b13ba8e 100644 (file)
@@ -42,3 +42,21 @@ $string['pluginname'] = 'Assignment 2.2 (Disabled)';
 $string['upgradenotification'] = 'This activity is based on an older assignment module.';
 $string['viewassignmentupgradetool'] = 'View the assignment upgrade tool';
 $string['pluginadministration'] = 'Assignment 2.2 (Disabled) administration';
+$string['privacy:markedsubmissionspath'] = 'markedsubmissions';
+$string['privacy:submissionpath'] = 'submission';
+$string['privacy:metadata:assignmentfilter'] = 'Filter preference of assignment submissions.';
+$string['privacy:metadata:assignmentperpage'] = 'Number of assignment submissions shown per page preference.';
+$string['privacy:metadata:assignmentmailinfo'] = 'Mail info preference for assignment submissions.';
+$string['privacy:metadata:assignmentquickgrade'] = 'Quick grading preference for assignment submissions.';
+$string['privacy:metadata:assignment_submissions'] = 'Assignment submissions associated with an assignment.';
+$string['privacy:metadata:assignment_submissions:userid'] = 'The user ID submitting the assignment submission.';
+$string['privacy:metadata:assignment_submissions:timecreated'] = 'The creation date/time of the assignment submission.';
+$string['privacy:metadata:assignment_submissions:timemodified'] = 'The modification date/time of the assignment submission.';
+$string['privacy:metadata:assignment_submissions:numfiles'] = 'The maximum number of files allowed for the assignment submission.';
+$string['privacy:metadata:assignment_submissions:data1'] = 'The onlinetext submitted for the assignment submission.';
+$string['privacy:metadata:assignment_submissions:data2'] = '';
+$string['privacy:metadata:assignment_submissions:grade'] = 'The grade value awarded for the assignment submission.';
+$string['privacy:metadata:assignment_submissions:submissioncomment'] = 'The submission comment accompanying the assignment submission.';
+$string['privacy:metadata:assignment_submissions:teacher'] = 'The teacher user ID grading the assignment submission.';
+$string['privacy:metadata:assignment_submissions:timemarked'] = 'The marking date/time of the assignment submission.';
+$string['privacy:metadata:assignment_submissions:mailed'] = 'The mailed notification status of the assignment submission.';
diff --git a/mod/assignment/tests/privacy_test.php b/mod/assignment/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..5c53a64
--- /dev/null
@@ -0,0 +1,606 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy test for the event monitor
+ *
+ * @package    mod_assignment
+ * @category   test
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__ . '/../lib.php');
+
+use \mod_assignment\privacy\provider;
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\request\approved_contextlist;
+use \core_privacy\local\request\transform;
+use \core_privacy\local\request\writer;
+use \core_privacy\tests\provider_testcase;
+
+/**
+ * Privacy test for the event monitor
+ *
+ * @package    mod_assignment
+ * @category   test
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_assignment_privacy_testcase extends advanced_testcase {
+
+    /**
+     * @var int array   Array of test student ids associated for Course 1.
+     */
+    private $course1students = [];
+
+    /**
+     * @var int array   Array of test student ids associated for Course 2.
+     */
+    private $course2students = [];
+
+    /**
+     * Test for provider::get_contexts_for_userid().
+     *
+     * @throws coding_exception
+     */
+    public function test_get_contexts_for_userid() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+        $this->create_courses_and_assignments();
+
+        // Get Teacher 1 to test get_contexts_for_userid().
+        $teacher1 = $DB->get_record('user', ['username' => 'teacher1']);
+        $contextids = provider::get_contexts_for_userid($teacher1->id);
+        // Verify there should be 4 contexts, as Teacher 1 has submitted tests and marked Assignments in Course 1 and 2.
+        $this->assertEquals(4, count($contextids->get_contextids()));
+
+        // Get Teacher 2 to test get_contexts_for_userid().
+        $teacher2 = $DB->get_record('user', ['username' => 'teacher2']);
+        $contextids = provider::get_contexts_for_userid($teacher2->id);
+        // Verify there should be 0 contexts, as teacher 2 has not marked any Assignments.
+        $this->assertEquals(0, count($contextids->get_contextids()));
+
+        // Get Student 1 to test get_contexts_for_userid().
+        $student1 = $DB->get_record('user', ['username' => 'student1']);
+        $contextids = provider::get_contexts_for_userid($student1->id);
+        // Verify there should be 2 contexts, as student 1 added submissions for both Assignments in Course 1.
+        $this->assertEquals(2, count($contextids->get_contextids()));
+
+        // Get Student 2 to test get_contexts_for_userid().
+        $student2 = $DB->get_record('user', ['username' => 'student2']);
+        $contextids = provider::get_contexts_for_userid($student2->id);
+        // Verify there should be 2 context, as student 2 added submissions for both Assignments in Course 2.
+        $this->assertEquals(2, count($contextids->get_contextids()));
+    }
+
+    /**
+     * Test for provider::export_user_data().
+     *
+     * @throws coding_exception
+     */
+    public function test_export_user_data_teacher() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+        $this->create_courses_and_assignments();
+
+        // Test Teacher 1 export_data_for_user() - marking assignment submissions for both Course 1 and 2.
+        $teacher1 = $DB->get_record('user', ['username' => 'teacher1']);
+
+        $contextlist = provider::get_contexts_for_userid($teacher1->id);
+        $approvedcontextlist = new approved_contextlist($teacher1, 'mod_assignment', $contextlist->get_contextids());
+
+        // Verify Teacher 1 has four contexts.
+        $this->assertCount(4, $contextlist->get_contextids());
+
+        // Retrieve Assignment Submissions data for Teacher 1.
+        provider::export_user_data($approvedcontextlist);
+
+        $contexts = $contextlist->get_contexts();
+
+        // Context 1 - Course 1's Assignment 1 -- (onlinetext).
+        $writer = writer::with_context($contexts[0]);
+        $subcontexts = [
+            get_string('privacy:markedsubmissionspath', 'mod_assignment'),
+            transform::user($teacher1->id)
+        ];
+        // Verify the test assignment submission from Teacher 1 exists.
+        $submission = $writer->get_data($subcontexts);
+        $this->assertEquals('<p>Course 1 - Ass 1: Teacher Test Submission</p>', $submission->data1);
+
+        foreach ($this->course1students as $student) {
+            $subcontexts = [
+                get_string('privacy:markedsubmissionspath', 'mod_assignment'),
+                transform::user($student->id)
+            ];
+            // Verify the student assignment submissions exists.
+            $submission = $writer->get_data($subcontexts);
+            $this->assertEquals("<p>Course 1 - Ass 1: " . $student->id . "</p>", $submission->data1);
+        }
+
+        // Context 2 - Course 1's Assignment 2 -- (single file upload).
+        $writer = writer::with_context($contexts[1]);
+        foreach ($this->course1students as $student) {
+            $subcontexts = [
+                get_string('privacy:markedsubmissionspath', 'mod_assignment'),
+                transform::user($student->id)
+            ];
+            // Verify the student assignment submissions exists.
+            $submission = $writer->get_data($subcontexts);
+            $this->assertEquals("<p>Course 1 - Ass 2: " . $student->id . "</p>", $submission->data1);
+
+            // Verify the student assignment submission file upload exists.
+            $submissionfiles = $writer->get_files($subcontexts);
+            $this->assertTrue(array_key_exists('Student' . $student->id . '-Course1-Ass2-(File 1 of 1)', $submissionfiles));
+        }
+
+        // Context 3 - Course 2's Assignment 1 -- (offline).
+        $writer = writer::with_context($contexts[2]);
+        foreach ($this->course2students as $student) {
+            $subcontexts = [
+                get_string('privacy:markedsubmissionspath', 'mod_assignment'),
+                transform::user($student->id)
+            ];
+            // Verify the student assignment submissions exists.
+            $submission = $writer->get_data($subcontexts);
+            $this->assertEquals("<p>Course 2 - Ass 1: " . $student->id . "</p>", $submission->data1);
+        }
+
+        // Context 4 - Course 2's Assignment 2 -- (multiple file upload).
+        $writer = writer::with_context($contexts[3]);
+        foreach ($this->course2students as $student) {
+            $subcontexts = [
+                get_string('privacy:markedsubmissionspath', 'mod_assignment'),
+                transform::user($student->id)
+            ];
+            // Verify the student assignment submissions exists.
+            $submission = $writer->get_data($subcontexts);
+            $this->assertEquals("<p>Course 2 - Ass 2: " . $student->id . "</p>", $submission->data1);
+
+            // Verify the student assignment submission file upload exists.
+            $submissionfiles = $writer->get_files($subcontexts);
+            $this->assertTrue(array_key_exists('Student' . $student->id . '-Course2-Ass2-(File 1 of 2)', $submissionfiles));
+            $this->assertTrue(array_key_exists('Student' . $student->id . '-Course2-Ass2-(File 2 of 2)', $submissionfiles));
+        }
+    }
+
+    /**
+     * Test for provider::export_user_data().
+     *
+     * @throws dml_exception
+     */
+    public function test_export_user_data_student() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+        $this->create_courses_and_assignments();
+
+        // Test Student 1 export_data_for_user() - added assignment submissions for both assignments in Course 1.
+        $student1 = $DB->get_record('user', ['username' => 'student1']);
+
+        $contextlist = provider::get_contexts_for_userid($student1->id);
+        $approvedcontextlist = new approved_contextlist($student1, 'mod_assignment', $contextlist->get_contextids());
+
+        // Retrieve Assignment Submissions data for Student 1.
+        provider::export_user_data($approvedcontextlist);
+        $contexts = $contextlist->get_contexts();
+
+        // Context 1 - Course 1's Assignment 1 -- (onlinetext).
+        $writer = writer::with_context($contexts[0]);
+        $subcontexts = [
+            get_string('privacy:submissionpath', 'mod_assignment')
+        ];
+
+        // Verify the student assignment submissions exists.
+        $submission = $writer->get_data($subcontexts);
+        $this->assertEquals("<p>Course 1 - Ass 1: " . $student1->id . "</p>", $submission->data1);
+
+        // Context 2 - Course 1's Assignment 2 -- (single file upload).
+        $writer = writer::with_context($contexts[1]);
+        $subcontexts = [
+            get_string('privacy:submissionpath', 'mod_assignment')
+        ];
+
+        // Verify the student assignment submission exists.
+        $submission = $writer->get_data($subcontexts);
+        $this->assertEquals("<p>Course 1 - Ass 2: " . $student1->id . "</p>", $submission->data1);
+
+        // Verify the student assignment submission file upload exists.
+        $submissionfiles = $writer->get_files($subcontexts);
+        $this->assertTrue(array_key_exists('Student' . $student1->id . '-Course1-Ass2-(File 1 of 1)', $submissionfiles));
+
+        // Test Student 2 export_data_for_user() - added assignment submissions for both assignments in Course 2.
+        $student2 = $DB->get_record('user', ['username' => 'student2']);
+
+        $contextlist = provider::get_contexts_for_userid($student2->id);
+        $approvedcontextlist = new approved_contextlist($student2, 'mod_assignment', $contextlist->get_contextids());
+
+        // Retrieve Assignment Submissions data for Student 2.
+        provider::export_user_data($approvedcontextlist);
+        $contexts = $contextlist->get_contexts();
+
+        // Context 1 - Course 2's Assignment 1 -- (offline).
+        $writer = writer::with_context($contexts[0]);
+        $subcontexts = [
+            get_string('privacy:submissionpath', 'mod_assignment')
+        ];
+
+        // Verify the student assignment submissions exists.
+        $submission = $writer->get_data($subcontexts);
+        $this->assertEquals("<p>Course 2 - Ass 1: " . $student2->id . "</p>", $submission->data1);
+
+        // Context 2 - Course 2's Assignment 2 -- (multiple file upload).
+        $writer = writer::with_context($contexts[1]);
+        $subcontexts = [
+            get_string('privacy:submissionpath', 'mod_assignment')
+        ];
+
+        // Verify the student assignment submission exists.
+        $submission = $writer->get_data($subcontexts);
+        $this->assertEquals("<p>Course 2 - Ass 2: " . $student2->id . "</p>", $submission->data1);
+
+        // Verify the student assignment submission file upload exists.
+        $submissionfiles = $writer->get_files($subcontexts);
+        $this->assertTrue(array_key_exists('Student' . $student2->id . '-Course2-Ass2-(File 1 of 2)', $submissionfiles));
+        $this->assertTrue(array_key_exists('Student' . $student2->id . '-Course2-Ass2-(File 2 of 2)', $submissionfiles));
+    }
+
+    /**
+     * Test for provider::delete_data_for_all_users_in_context().
+     *
+     * @throws dml_exception
+     */
+    public function test_delete_data_for_all_users_in_context() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+        $this->create_courses_and_assignments();
+
+        // Test teacher1 delete_data_for_all_users_in_context().
+        $teacher1 = $DB->get_record('user', ['username' => 'teacher1']);
+        $contextlist = provider::get_contexts_for_userid($teacher1->id);
+
+        foreach ($contextlist as $context) {
+            provider::delete_data_for_all_users_in_context($context);
+
+            // Verify assignment submission(s) were deleted for the context.
+            $deleted = $this->get_assignment_submissions($context->id);
+            $this->assertCount(0, $deleted);
+
+            // Verify all the file submissions associated with the context for all users were deleted.
+            $files = $DB->get_records('files', ['component' => 'mod_assignment', 'filearea' => 'submission', 'contextid' => $context->id]);
+            $this->assertEquals(0, count($files));
+        }
+    }
+
+    /**
+     * Test for provider::delete_data_for_user().
+     *
+     * @throws dml_exception
+     */
+    public function test_delete_data_for_user() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+        $this->create_courses_and_assignments();
+
+        // Test Teacher 1 delete_data_for_user(), should only remove the 1 test submission added by Teacher 1.
+        // Should not remove any assignment submission records marked by the teacher.
+        $teacher1 = $DB->get_record('user', ['username' => 'teacher1']);
+        $contextlist = provider::get_contexts_for_userid($teacher1->id);
+        $approvedcontextlist = new approved_contextlist($teacher1, 'mod_assignment', $contextlist->get_contextids());
+        provider::delete_data_for_user($approvedcontextlist);
+
+        // Verify the submissions submitted by students still exists.
+        $markedsubmissions = $DB->get_records('assignment_submissions', ['teacher' => $teacher1->id]);
+        $this->assertCount(4, $markedsubmissions);
+
+        // Test student 1 delete_data_for_user().
+        $student1 = $DB->get_record('user', ['username' => 'student1']);
+        $contextlist = provider::get_contexts_for_userid($student1->id);
+        $approvedcontextlist = new approved_contextlist($student1, 'mod_assignment', $contextlist->get_contextids());
+        provider::delete_data_for_user($approvedcontextlist);
+
+        // Verify student 1's assignment submissions were deleted.
+        $assignmentsubmissions = $DB->get_records('assignment_submissions', ['userid' => $student1->id]);
+        $this->assertEquals(0, count($assignmentsubmissions));
+
+        // Verify student 1's file submissions were deleted.
+        foreach ($contextlist->get_contextids() as $contextid) {
+            $files = $DB->get_records('files', ['component' => 'mod_assignment', 'filearea' => 'submission', 'contextid' => $contextid]);
+            $this->assertEquals(0, count($files));
+        }
+    }
+
+    // Start of helper functions.
+
+    /**
+     * Helper function to setup Course, users, and assignments for testing.
+     */
+    protected function create_courses_and_assignments() {
+        // Create Courses, Users, and Assignments.
+        $course1 = $this->getDataGenerator()->create_course(['shortname' => 'course1']);
+        $course2 = $this->getDataGenerator()->create_course(['shortname' => 'course2']);
+
+        $teacher1 = $this->getDataGenerator()->create_user(['username' => 'teacher1']);
+        $teacher2 = $this->getDataGenerator()->create_user(['username' => 'teacher2']);
+
+        $student1 = $this->getDataGenerator()->create_user(['username' => 'student1']);
+        $student2 = $this->getDataGenerator()->create_user(['username' => 'student2']);
+
+        $this->course1students = [
+            $student1
+        ];
+
+        $this->course2students = [
+            $student2
+        ];
+
+        $course1assignment1 = $this->getDataGenerator()->create_module('assignment',
+            [
+                'course' => $course1->id,
+                'name' => 'Course 1 - Assignment 1 (onlinetext)',
+                'assignmenttype' => 'onlinetext',
+            ]
+        );
+        $course1assignment2 = $this->getDataGenerator()->create_module('assignment',
+            [
+                'course' => $course1->id,
+                'name' => 'Course 1 - Assignment 2 (single file upload)',
+                'assignmenttype' => 'uploadsingle',
+            ]
+        );
+        $course2assignment1 = $this->getDataGenerator()->create_module('assignment',
+            [
+                'course' => $course2->id,
+                'name' => 'Course 2 - Assignment 1 (offline)',
+                'assignmenttype' => 'offline',
+            ]
+        );
+        $course2assignment2 = $this->getDataGenerator()->create_module('assignment',
+            [
+                'course' => $course2->id,
+                'name' => 'Course 2 - Assignment 2 (multiple file upload)',
+                'assignmenttype' => 'upload',
+            ]
+        );
+
+        // Teacher 1 add test assignment submission for Course 1 - Assignment 1.
+        $this->add_assignment_submission(
+            $course1assignment1,
+            $teacher1,
+            "Course 1 - Ass 1: Teacher Test Submission"
+        );
+
+        // Student 1 add assignment submissions for Course 1 - Assignment 1 and 2.
+        $this->add_assignment_submission(
+            $course1assignment1,
+            $student1,
+            "Course 1 - Ass 1: " . $student1->id
+        );
+        $this->add_file_assignment_submission(
+            $course1assignment2,
+            $student1,
+            "Course 1 - Ass 2: " . $student1->id,
+            'Student' . $student1->id . '-Course1-Ass2'
+        );
+
+        // Student 2 add assignment submissions for Course 2 - Assignment 1 and 2.
+        $this->add_assignment_submission(
+            $course2assignment1,
+            $student2,
+            "Course 2 - Ass 1: " . $student2->id
+        );
+        $this->add_file_assignment_submission(
+            $course2assignment2,
+            $student2,
+            "Course 2 - Ass 2: " . $student2->id,
+            'Student' . $student2->id . '-Course2-Ass2',
+            2
+        );
+
+        // Teacher 1 to mark assignment submissions for Course 1's Assignment 1 and 2.
+        $course1submissions = $this->get_course_assignment_submissions($course1->id);
+        foreach ($course1submissions as $submission) {
+            $this->mark_assignment_submission($submission->assignment, $submission->id, $teacher1, 49);
+        }
+
+        // Teacher 1 to mark assignment submissions for Course 2's Assignment 1 and 2.
+        $course2submissions = $this->get_course_assignment_submissions($course2->id);
+        foreach ($course2submissions as $submission) {
+            $this->mark_assignment_submission($submission->assignment, $submission->id, $teacher1, 50);
+        }
+    }
+
+    /**
+     * Helper function to add an assignment submission for testing.
+     *
+     * @param object $assignment        Object containing assignment submission details to create for testing.
+     * @param object $user              Object of the user making the assignment submission.
+     * @param string $submissiondata    The onlintext string value of the assignment submission.
+     * @throws dml_exception
+     */
+    protected function add_assignment_submission($assignment, $user, $submissiondata) {
+        global $DB;
+
+        $submission = (object) [
+            'assignment' => $assignment->id,
+            'userid' => $user->id,
+            'timecreated' => date('U'),
+            'data1' => '<p>' . $submissiondata . '</p>',
+            'submissioncomment' => 'My submission by ' . $user->username
+        ];
+
+        return $DB->insert_record('assignment_submissions', $submission);
+    }
+
+    /**
+     * Helper function to add an assignment submission with file submissions for testing.
+     *
+     * @param object $assignment        Object containing assignment submission details to create for testing.
+     * @param object $user              Object of the user making the assignment submission.
+     * @param string $submissiondata    The onlintext string value of the assignment submission.
+     * @param string $filename          The filename of the file submission included with the assignment submission.
+     * @param int $numfiles             The number of files included with the assignment submission.
+     * @throws dml_exception
+     * @throws file_exception
+     * @throws stored_file_creation_exception
+     */
+    protected function add_file_assignment_submission($assignment, $user, $submissiondata, $filename, $numfiles = 1) {
+        global $CFG, $DB;
+
+        $submission = (object) [
+            'assignment' => $assignment->id,
+            'userid' => $user->id,
+            'timecreated' => date('U'),
+            'data1' => '<p>' . $submissiondata . '</p>',
+            'numfiles' => $numfiles,
+            'submissioncomment' => 'My submission by ' . $user->username
+        ];
+
+        $submissionid = $DB->insert_record('assignment_submissions', $submission);
+
+        // Create a file submission with the test pdf.
+        $this->setUser($user->id);
+        $context = context_module::instance($assignment->cmid);
+
+        $fs = get_file_storage();
+        $sourcefile = $CFG->dirroot . '/mod/assign/feedback/editpdf/tests/fixtures/submission.pdf';
+
+        for ($f = 1; $f <= $numfiles; $f++) {
+            $pdfsubmission = (object)array(
+                'contextid' => $context->id,
+                'component' => 'mod_assignment',
+                'filearea' => 'submission',
+                'itemid' => $submissionid,
+                'filepath' => '/',
+                'filename' => $filename . "-(File $f of $numfiles)"
+            );
+            $fs->create_file_from_pathname($pdfsubmission, $sourcefile);
+        }
+    }
+
+    /**
+     * Helper function to retrieve the assignment submission records for a given course.
+     *
+     * @param int $courseid     The course ID to get assignment submissions by.
+     * @return array            Array of assignment submission details.
+     * @throws dml_exception
+     */
+    protected function get_course_assignment_submissions($courseid) {
+        global $DB;
+
+        $sql = "SELECT s.id,
+                       s.assignment,
+                       s.userid,
+                       s.timecreated,
+                       s.timemodified,
+                       s.numfiles,
+                       s.data1,
+                       s.data2,
+                       s.grade,
+                       s.submissioncomment,
+                       s.format,
+                       s.teacher,
+                       s.timemarked,
+                       s.mailed
+                  FROM {assignment} a
+                  JOIN {assignment_submissions} s ON s.assignment = a.id
+                 WHERE a.course = :courseid";
+        $params = [
+            'courseid' => $courseid
+        ];
+
+        return $DB->get_records_sql($sql, $params);
+    }
+
+    /**
+     * Helper function to update an assignment submission with grading details for a teacher.
+     *
+     * @param int $assignmentid     The assignment ID to update assignment submissions with marking/graded details.
+     * @param int $submissionid     The assignment submission ID to update with marking/grading details.
+     * @param int $teacher          The teacher user ID to making the marking/grading details.
+     * @param int $gradedata        The grade value set for the marking/grading details.
+     */
+    protected function mark_assignment_submission($assignmentid, $submissionid, $teacher, $gradedata) {
+        global $DB;
+
+        $submission = (object) [
+            'id' => $submissionid,
+            'assignment' => $assignmentid,
+            'grade' => $gradedata,
+            'teacher' => $teacher->id,
+            'timemarked' => date('U')
+        ];
+
+        return $DB->update_record('assignment_submissions', $submission);
+    }
+
+    /**
+     * Helper function to retrieve the assignment records for a given context.
+     *
+     * @param int $contextid    The context module ID value to retrieve assignment IDs by.
+     * @return array            Array of assignment IDs.
+     * @throws dml_exception
+     */
+    protected function get_assignments($contextid) {
+        global $DB;
+
+        $sql = "SELECT a.id
+                  FROM {assignment} a
+                  JOIN {course_modules} cm ON a.id = cm.instance
+                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+                  JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextmodule
+                 WHERE ctx.id = :contextid";
+        $params = [
+            'modulename' => 'assignment',
+            'contextmodule' => CONTEXT_MODULE,
+            'contextid' => $contextid
+        ];
+
+        return $DB->get_records_sql($sql, $params);
+    }
+
+    /**
+     * Helper function to retrieve the assignment submission records for a given context.
+     *
+     * @param int $contextid    The context module ID value to retrieve assignment submission IDs by.
+     * @return array            Array of assignment submission IDs.
+     * @throws dml_exception
+     */
+    protected function get_assignment_submissions($contextid) {
+        global $DB;
+
+        $sql = "SELECT s.id
+                  FROM {assignment_submissions} s
+                  JOIN {assignment} a ON a.id = s.assignment
+                  JOIN {course_modules} cm ON a.id = cm.instance
+                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+                  JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextmodule
+                 WHERE ctx.id = :contextid";
+        $params = [
+            'modulename' => 'assignment',
+            'contextmodule' => CONTEXT_MODULE,
+            'contextid' => $contextid
+        ];
+
+        return $DB->get_records_sql($sql, $params);
+    }
+}