MDL-61308 assign_submission: Privacy code for user rights.
authorAdrian Greeve <adrian@moodle.com>
Wed, 31 Jan 2018 03:49:53 +0000 (11:49 +0800)
committerAdrian Greeve <adrian@moodle.com>
Thu, 26 Apr 2018 06:17:55 +0000 (14:17 +0800)
12 files changed:
mod/assign/classes/privacy/assignsubmission_provider.php [new file with mode: 0644]
mod/assign/classes/privacy/submission_legacy_polyfill.php [new file with mode: 0644]
mod/assign/submission/comments/classes/privacy/provider.php [new file with mode: 0644]
mod/assign/submission/comments/lang/en/assignsubmission_comments.php
mod/assign/submission/comments/tests/privacy_test.php [new file with mode: 0644]
mod/assign/submission/file/classes/privacy/provider.php [new file with mode: 0644]
mod/assign/submission/file/lang/en/assignsubmission_file.php
mod/assign/submission/file/tests/privacy_test.php [new file with mode: 0644]
mod/assign/submission/onlinetext/classes/privacy/provider.php [new file with mode: 0644]
mod/assign/submission/onlinetext/lang/en/assignsubmission_onlinetext.php
mod/assign/submission/onlinetext/tests/privacy_test.php [new file with mode: 0644]
mod/assign/tests/privacy_submission_legacy_polyfill_test.php [new file with mode: 0644]

diff --git a/mod/assign/classes/privacy/assignsubmission_provider.php b/mod/assign/classes/privacy/assignsubmission_provider.php
new file mode 100644 (file)
index 0000000..84c545e
--- /dev/null
@@ -0,0 +1,88 @@
+<?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/>.
+
+/**
+ * This file contains the assignsubmission_provider interface.
+ *
+ * Assignment Sub plugins should implement this if they store personal information.
+ *
+ * @package mod_assign
+ * @copyright 2018 Adrian Greeve <adrian@moodle.com>
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_assign\privacy;
+
+use core_privacy\local\request\contextlist;
+
+defined('MOODLE_INTERNAL') || die();
+
+interface assignsubmission_provider extends \core_privacy\local\request\plugin\subplugin_provider {
+
+    /**
+     * Retrieves the contextids associated with the provided userid for this subplugin.
+     * NOTE if your subplugin must have an entry in the assign_submission table to work, then this
+     * method can be empty.
+     *
+     * @param int $userid The user ID to get context IDs for.
+     * @param \core_privacy\local\request\contextlist $contextlist Use add_from_sql with this object to add your context IDs.
+     */
+    public static function get_context_for_userid_within_submission(int $userid, contextlist $contextlist);
+
+    /**
+     * Returns student user ids related to the provided teacher ID. If it is possible that a student ID will not be returned by
+     * the sql query in \mod_assign\privacy\provider::find_grader_info() Then you need to provide some sql to retrive those
+     * student IDs. This is highly likely if you had to fill in get_context_for_userid_within_submission above.
+     *
+     * @param  useridlist $useridlist A user ID list object that you can append your user IDs to.
+     */
+    public static function get_student_user_ids(useridlist $useridlist);
+
+    /**
+     * This method is used to export any user data this sub-plugin has using the assign_plugin_request_data object to get the
+     * context and userid.
+     * assign_plugin_request_data contains:
+     * - context
+     * - submission object
+     * - current path (subcontext)
+     * - user object
+     *
+     * @param  assign_plugin_request_data $exportdata Information to use to export user data for this sub-plugin.
+     */
+    public static function export_submission_user_data(assign_plugin_request_data $exportdata);
+
+    /**
+     * Any call to this method should delete all user data for the context defined in the deletion_criteria.
+     * assign_plugin_request_data contains:
+     * - context
+     * - assign object
+     *
+     * @param assign_plugin_request_data $requestdata Information to use to delete user data for this submission.
+     */
+    public static function delete_submission_for_context(assign_plugin_request_data $requestdata);
+
+    /**
+     * A call to this method should delete user data (where practicle) from the userid and context.
+     * assign_plugin_request_data contains:
+     * - context
+     * - submission object
+     * - user object
+     * - assign object
+     *
+     * @param  assign_plugin_request_data $exportdata Details about the user and context to focus the deletion.
+     */
+    public static function delete_submission_for_userid(assign_plugin_request_data $exportdata);
+}
diff --git a/mod/assign/classes/privacy/submission_legacy_polyfill.php b/mod/assign/classes/privacy/submission_legacy_polyfill.php
new file mode 100644 (file)
index 0000000..9b088d9
--- /dev/null
@@ -0,0 +1,101 @@
+<?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/>.
+
+/**
+ * This file contains the polyfill to allow a plugin to operate with Moodle 3.3 up.
+ *
+ * @package mod_assign
+ * @copyright 2018 Adrian Greeve <adrian@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_assign\privacy;
+
+use core_privacy\local\request\contextlist;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The trait used to provide backwards compatability for third-party plugins.
+ *
+ * @copyright 2018 Adrian Greeve <adrian@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+trait submission_legacy_polyfill {
+
+    /**
+     * Retrieves the contextids associated with the provided userid for this subplugin.
+     * NOTE if your subplugin must have an entry in the assign_submission table to work, then this
+     * method can be empty.
+     *
+     * @param  int $userid The user ID to get context IDs for.
+     * @param  \core_privacy\local\request\contextlist $contextlist Use add_from_sql with this object to add your context IDs.
+     */
+    public static function get_context_for_userid_within_submission(int $userid, contextlist $contextlist) {
+        return static::_get_context_for_userid_within_submission($userid, $contextlist);
+    }
+
+    /**
+     * Returns student user ids related to the provided teacher ID. If it is possible that a student ID will not be returned by
+     * the sql query in \mod_assign\privacy\provider::find_grader_info() Then you need to provide some sql to retrive those
+     * student IDs. This is highly likely if you had to fill in get_context_for_userid_within_submission above.
+     *
+     * @param  useridlist $useridlist A user ID list object that you can append your user IDs to.
+     */
+    public static function get_student_user_ids(useridlist $useridlist) {
+        return static::_get_student_user_ids($useridlist);
+    }
+
+    /**
+     * This method is used to export any user data this sub-plugin has using the assign_plugin_request_data object to get the
+     * context and userid.
+     * assign_plugin_request_data contains:
+     * - context
+     * - submission object
+     * - current path (subcontext)
+     * - user object
+     *
+     * @param  assign_plugin_request_data $exportdata Information to use to export user data for this sub-plugin.
+     */
+    public static function export_submission_user_data(assign_plugin_request_data $exportdata) {
+        return static::_export_submission_user_data($exportdata);
+    }
+
+    /**
+     * Any call to this method should delete all user data for the context defined in the deletion_criteria.
+     * assign_plugin_request_data contains:
+     * - context
+     * - assign object
+     *
+     * @param assign_plugin_request_data $requestdata Information to use to delete user data for this submission.
+     */
+    public static function delete_submission_for_context(assign_plugin_request_data $requestdata) {
+        return static::_delete_submission_for_context($requestdata);
+    }
+
+    /**
+     * A call to this method should delete user data (where practicle) from the userid and context.
+     * assign_plugin_request_data contains:
+     * - context
+     * - submission object
+     * - user object
+     * - assign object
+     *
+     * @param  assign_plugin_request_data $exportdata Details about the user and context to focus the deletion.
+     */
+    public static function delete_submission_for_userid(assign_plugin_request_data $exportdata) {
+        return static::_delete_submission_for_userid($exportdata);
+    }
+}
diff --git a/mod/assign/submission/comments/classes/privacy/provider.php b/mod/assign/submission/comments/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..6f95fd2
--- /dev/null
@@ -0,0 +1,131 @@
+<?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 class for requesting user data.
+ *
+ * @package    assignsubmission_comments
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace assignsubmission_comments\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\metadata\provider as metadataprovider;
+use \core_comment\privacy\provider as comments_provider;
+use \core_privacy\local\request\contextlist;
+use \mod_assign\privacy\assign_plugin_request_data;
+
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package    assignsubmission_comments
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements metadataprovider, \mod_assign\privacy\assignsubmission_provider {
+
+    /**
+     * Return meta data about this plugin.
+     *
+     * @param  collection $collection A list of information to add to.
+     * @return collection Return the collection after adding to it.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        $collection->link_subsystem('core_comment', 'privacy:metadata:commentpurpose');
+        return $collection;
+    }
+
+    /**
+     * It is possible to make a comment as a teacher without creating an entry in the submission table, so this is required
+     * to find those entries.
+     *
+     * @param  int $userid The user ID that we are finding contexts for.
+     * @param  contextlist $contextlist A context list to add sql and params to for contexts.
+     */
+    public static function get_context_for_userid_within_submission(int $userid, contextlist $contextlist) {
+        $sql = "SELECT contextid
+                  FROM {comments}
+                 WHERE component = :component
+                       AND commentarea = :commentarea
+                       AND userid = :userid";
+        $params = ['userid' => $userid, 'component' => 'assignsubmission_comments', 'commentarea' => 'submission_comments'];
+        $contextlist->add_from_sql($sql, $params);
+    }
+
+    /**
+     * Due to the fact that we can't rely on the queries in the mod_assign provider we have to add some additional sql.
+     *
+     * @param  \mod_assign\privacy\useridlist $useridlist An object for obtaining user IDs of students.
+     */
+    public static function get_student_user_ids(\mod_assign\privacy\useridlist $useridlist) {
+        $params = ['assignid' => $useridlist->get_assignid(), 'commentuserid' => $useridlist->get_teacherid(),
+                'commentuserid2' => $useridlist->get_teacherid()];
+        $sql = "SELECT DISTINCT c.userid AS id
+                  FROM {comments} c
+                  JOIN (SELECT c.itemid
+                          FROM {comments} c
+                          JOIN {assign_submission} s ON s.id = c.itemid AND s.assignment = :assignid
+                         WHERE c.userid = :commentuserid) aa ON aa.itemid = c.itemid
+                 WHERE c.userid NOT IN (:commentuserid2)";
+        $useridlist->add_from_sql($sql, $params);
+    }
+
+    /**
+     * Export all user data for this plugin.
+     *
+     * @param  assign_plugin_request_data $exportdata Data used to determine which context and user to export and other useful
+     * information to help with exporting.
+     */
+    public static function export_submission_user_data(assign_plugin_request_data $exportdata) {
+        $component = 'assignsubmission_comments';
+        $commentarea = 'submission_comments';
+
+        $userid = ($exportdata->get_user() != null);
+        $submission = $exportdata->get_pluginobject();
+
+        // For the moment we are only showing the comments made by this user.
+        comments_provider::export_comments($exportdata->get_context(), $component, $commentarea, $submission->id,
+                $exportdata->get_subcontext(), $userid);
+    }
+
+    /**
+     * Delete all the comments made for this context.
+     *
+     * @param  assign_plugin_request_data $requestdata Data to fulfill the deletion request.
+     */
+    public static function delete_submission_for_context(assign_plugin_request_data $requestdata) {
+        comments_provider::delete_comments_for_all_users($requestdata->get_context(), 'assignsubmission_comments',
+                'submission_comments');
+    }
+
+    /**
+     * A call to this method should delete user data (where practical) using the userid and submission.
+     *
+     * @param  assign_plugin_request_data $exportdata Details about the user and context to focus the deletion.
+     */
+    public static function delete_submission_for_userid(assign_plugin_request_data $exportdata) {
+        // Create an approved context list to delete the comments.
+        $contextlist = new \core_privacy\local\request\approved_contextlist($exportdata->get_user(), 'assignsubmission_comments',
+            [$exportdata->get_context()->id]);
+        comments_provider::delete_comments_for_user($contextlist, 'assignsubmission_comments', 'submission_comments');
+    }
+}
index d63312b..78da642 100644 (file)
@@ -24,6 +24,7 @@
 
 $string['blindmarkingname'] = 'Participant {$a}';
 $string['blindmarkingviewfullname'] = 'Participant {$a->participantnumber} ({$a->participantfullname})';
+$string['privacy:metadata:commentpurpose'] = 'Comments between the student and teacher about a submission.';
 $string['default'] = 'Enabled by default';
 $string['default_help'] = 'If set, this submission method will be enabled by default for all new assignments.';
 $string['enabled'] = 'Submission comments';
diff --git a/mod/assign/submission/comments/tests/privacy_test.php b/mod/assign/submission/comments/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..471174b
--- /dev/null
@@ -0,0 +1,253 @@
+<?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/>.
+
+/**
+ * Unit tests for assignsubmission_comments.
+ *
+ * @package    assignsubmission_comments
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/assign/tests/privacy_test.php');
+
+/**
+ * Unit tests for mod/assign/submission/comments/classes/privacy/
+ *
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignsubmission_comments_privacy_testcase extends \mod_assign\tests\mod_assign_privacy_testcase {
+
+    /**
+     * Convenience function for creating feedback data.
+     *
+     * @param  object   $assign         assign object
+     * @param  stdClass $student        user object
+     * @param  string   $submissiontext Submission text
+     * @return array   Submission plugin object and the submission object and the comment object.
+     */
+    protected function create_comment_submission($assign, $student, $submissiontext) {
+
+        $submission = $assign->get_user_submission($student->id, true);
+
+        $plugin = $assign->get_submission_plugin_by_type('comments');
+
+        $context = $assign->get_context();
+        $options = new stdClass();
+        $options->area = 'submission_comments';
+        $options->course = $assign->get_course();
+        $options->context = $context;
+        $options->itemid = $submission->id;
+        $options->component = 'assignsubmission_comments';
+        $options->showcount = true;
+        $options->displaycancel = true;
+
+        $comment = new comment($options);
+        $comment->set_post_permission(true);
+
+        $this->setUser($student);
+
+        $comment->add($submissiontext);
+
+        return [$plugin, $submission, $comment];
+    }
+
+    /**
+     * Quick test to make sure that get_metadata returns something.
+     */
+    public function test_get_metadata() {
+        $collection = new \core_privacy\local\metadata\collection('assignsubmission_comments');
+        $collection = \assignsubmission_comments\privacy\provider::get_metadata($collection);
+        $this->assertNotEmpty($collection);
+    }
+
+    /**
+     * Test returning the context for a user who has made a comment in an assignment.
+     */
+    public function test_get_context_for_userid_within_submission() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Student.
+        $user1 = $this->getDataGenerator()->create_user();
+        // Teacher.
+        $user2 = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'editingteacher');
+        $assign = $this->create_instance(['course' => $course]);
+
+        $context = $assign->get_context();
+
+        $studentcomment = 'Comment from user 1';
+        list($plugin, $submission, $comment) = $this->create_comment_submission($assign, $user1, $studentcomment);
+        $teachercomment = 'From the teacher';
+        $this->setUser($user2);
+        $comment->add($teachercomment);
+
+        $contextlist = new \core_privacy\local\request\contextlist();
+        \assignsubmission_comments\privacy\provider::get_context_for_userid_within_submission($user2->id, $contextlist);
+        $this->assertEquals($context->id, $contextlist->get_contextids()[0]);
+    }
+
+    /**
+     * Test returning student ids given a user ID.
+     */
+    public function test_get_student_user_ids() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Student.
+        $user1 = $this->getDataGenerator()->create_user();
+        // Teacher.
+        $user2 = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'editingteacher');
+        $assign = $this->create_instance(['course' => $course]);
+
+        $context = $assign->get_context();
+
+        $studentcomment = 'Comment from user 1';
+        list($plugin, $submission, $comment) = $this->create_comment_submission($assign, $user1, $studentcomment);
+        $teachercomment = 'From the teacher';
+        $this->setUser($user2);
+        $comment->add($teachercomment);
+
+        $useridlist = new mod_assign\privacy\useridlist($user2->id, $assign->get_instance()->id);
+        \assignsubmission_comments\privacy\provider::get_student_user_ids($useridlist);
+        $this->assertEquals($user1->id, $useridlist->get_userids()[0]->id);
+    }
+
+    /**
+     * Test that comments are exported for a user.
+     */
+    public function test_export_submission_user_data() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Student.
+        $user1 = $this->getDataGenerator()->create_user();
+        // Teacher.
+        $user2 = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'editingteacher');
+        $assign = $this->create_instance(['course' => $course]);
+
+        $context = $assign->get_context();
+
+        $studentcomment = 'Comment from user 1';
+        list($plugin, $submission, $comment) = $this->create_comment_submission($assign, $user1, $studentcomment);
+        $teachercomment = 'From the teacher';
+        $this->setUser($user2);
+        $comment->add($teachercomment);
+
+        $writer = \core_privacy\local\request\writer::with_context($context);
+        $this->assertFalse($writer->has_any_data());
+
+        // The student should be able to see the teachers feedback.
+        $exportdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign, $submission);
+        \assignsubmission_comments\privacy\provider::export_submission_user_data($exportdata);
+        $exportedcomments = $writer->get_data(['Comments']);
+        $this->assertCount(2, $exportedcomments->comments);
+        $this->assertContains($studentcomment, $exportedcomments->comments[0]->content);
+        $this->assertContains($teachercomment, $exportedcomments->comments[1]->content);
+    }
+
+    /**
+     * Test that all comments are deleted for this context.
+     */
+    public function test_delete_submission_for_context() {
+        global $DB;
+        $this->resetAfterTest();
+
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Student.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        // Teacher.
+        $user3 = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user3->id, $course->id, 'editingteacher');
+        $assign = $this->create_instance(['course' => $course]);
+
+        $context = $assign->get_context();
+
+        $studentcomment = 'Comment from user 1';
+        list($plugin, $submission, $comment) = $this->create_comment_submission($assign, $user1, $studentcomment);
+        $studentcomment = 'Comment from user 2';
+        list($plugin2, $submission2, $comment2) = $this->create_comment_submission($assign, $user2, $studentcomment);
+        $teachercomment1 = 'From the teacher';
+        $teachercomment2 = 'From the teacher for second student.';
+        $this->setUser($user3);
+        $comment->add($teachercomment1);
+        $comment2->add($teachercomment2);
+
+        // Only need the context in this plugin for this operation.
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign);
+        \assignsubmission_comments\privacy\provider::delete_submission_for_context($requestdata);
+
+        $results = $DB->get_records('comments', ['contextid' => $context->id]);
+        $this->assertEmpty($results);
+    }
+
+    /**
+     * Test that the comments for a user are deleted.
+     */
+    public function test_delete_submission_for_userid() {
+        global $DB;
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Student.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        // Teacher.
+        $user3 = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user3->id, $course->id, 'editingteacher');
+        $assign = $this->create_instance(['course' => $course]);
+
+        $context = $assign->get_context();
+
+        $studentcomment = 'Comment from user 1';
+        list($plugin, $submission, $comment) = $this->create_comment_submission($assign, $user1, $studentcomment);
+        $studentcomment = 'Comment from user 2';
+        list($plugin2, $submission2, $comment2) = $this->create_comment_submission($assign, $user2, $studentcomment);
+        $teachercomment1 = 'From the teacher';
+        $teachercomment2 = 'From the teacher for second student.';
+        $this->setUser($user3);
+        $comment->add($teachercomment1);
+        $comment2->add($teachercomment2);
+
+        // Provide full details to delete the comments.
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign, null, [], $user1);
+        \assignsubmission_comments\privacy\provider::delete_submission_for_userid($requestdata);
+
+        $results = $DB->get_records('comments', ['contextid' => $context->id]);
+        // We are only deleting the comments for user1 (one comment) so we should have three left.
+        $this->assertCount(3, $results);
+        foreach ($results as $result) {
+            // Check that none of the comments are from user1.
+            $this->assertNotEquals($user1->id, $result->userid);
+        }
+    }
+}
diff --git a/mod/assign/submission/file/classes/privacy/provider.php b/mod/assign/submission/file/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..a10e67a
--- /dev/null
@@ -0,0 +1,143 @@
+<?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 class for requesting user data.
+ *
+ * @package    assignsubmission_file
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace assignsubmission_file\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\metadata\provider as metadataprovider;
+use \core_privacy\local\request\writer;
+use \core_privacy\local\request\contextlist;
+use \mod_assign\privacy\assign_plugin_request_data;
+
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package    assignsubmission_file
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements metadataprovider, \mod_assign\privacy\assignsubmission_provider {
+
+    /**
+     * Return meta data about this plugin.
+     *
+     * @param  collection $collection A list of information to add to.
+     * @return collection Return the collection after adding to it.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        $collection->link_subsystem('core_files', 'privacy:metadata:filepurpose');
+        return $collection;
+    }
+
+    /**
+     * This is covered by mod_assign provider and the query on assign_submissions.
+     *
+     * @param  int $userid The user ID that we are finding contexts for.
+     * @param  contextlist $contextlist A context list to add sql and params to for contexts.
+     */
+    public static function get_context_for_userid_within_submission(int $userid, contextlist $contextlist) {
+        // This is already fetched from mod_assign.
+    }
+
+    /**
+     * This is also covered by the mod_assign provider and it's queries.
+     *
+     * @param  \mod_assign\privacy\useridlist $useridlist An object for obtaining user IDs of students.
+     */
+    public static function get_student_user_ids(\mod_assign\privacy\useridlist $useridlist) {
+        // No need.
+    }
+
+    /**
+     * Export all user data for this plugin.
+     *
+     * @param  assign_plugin_request_data $exportdata Data used to determine which context and user to export and other useful
+     * information to help with exporting.
+     */
+    public static function export_submission_user_data(assign_plugin_request_data $exportdata) {
+        // We currently don't show submissions to teachers when exporting their data.
+        $context = $exportdata->get_context();
+        if ($exportdata->get_user() != null) {
+            return null;
+        }
+        $user = new \stdClass();
+        $assign = $exportdata->get_assign();
+        $plugin = $assign->get_plugin_by_type('assignsubmission', 'file');
+        $files = $plugin->get_files($exportdata->get_pluginobject(), $user);
+        foreach ($files as $file) {
+            $userid = $exportdata->get_pluginobject()->userid;
+            writer::with_context($exportdata->get_context())->export_file($exportdata->get_subcontext(), $file);
+
+            // Plagiarism data.
+            $coursecontext = $context->get_course_context();
+            \core_plagiarism\privacy\provider::export_plagiarism_user_data($userid, $context, $exportdata->get_subcontext(), [
+                'cmid' => $context->instanceid,
+                'course' => $coursecontext->instanceid,
+                'userid' => $userid,
+                'file' => $file
+            ]);
+        }
+    }
+
+    /**
+     * Any call to this method should delete all user data for the context defined in the deletion_criteria.
+     *
+     * @param  assign_plugin_request_data $requestdata Information useful for deleting user data.
+     */
+    public static function delete_submission_for_context(assign_plugin_request_data $requestdata) {
+        global $DB;
+
+        \core_plagiarism\privacy\provider::delete_plagiarism_for_context($requestdata->get_context());
+
+        $fs = get_file_storage();
+        $fs->delete_area_files($requestdata->get_context()->id, 'assignsubmission_file', ASSIGNSUBMISSION_FILE_FILEAREA);
+
+        // Delete records from assignsubmission_file table.
+        $DB->delete_records('assignsubmission_file', ['assignment' => $requestdata->get_assign()->get_instance()->id]);
+    }
+
+    /**
+     * A call to this method should delete user data (where practicle) using the userid and submission.
+     *
+     * @param  assign_plugin_request_data $deletedata Details about the user and context to focus the deletion.
+     */
+    public static function delete_submission_for_userid(assign_plugin_request_data $deletedata) {
+        global $DB;
+
+        \core_plagiarism\privacy\provider::delete_plagiarism_for_user($deletedata->get_user()->id, $deletedata->get_context());
+
+        $submissionid = $deletedata->get_pluginobject()->id;
+
+        $fs = get_file_storage();
+        $fs->delete_area_files($deletedata->get_context()->id, 'assignsubmission_file', ASSIGNSUBMISSION_FILE_FILEAREA,
+                $submissionid);
+
+        $DB->delete_records('assignsubmission_file', ['assignment' => $deletedata->get_assign()->get_instance()->id,
+                'submission' => $submissionid]);
+    }
+}
index 45dd3ff..f566ef5 100644 (file)
@@ -43,6 +43,7 @@ $string['maximumsubmissionsize'] = 'Maximum submission size';
 $string['maximumsubmissionsize_help'] = 'Files uploaded by students may be up to this size.';
 $string['numfilesforlog'] = 'The number of file(s) : {$a} file(s).';
 $string['pluginname'] = 'File submissions';
+$string['privacy:metadata:filepurpose'] = 'The files loaded for this assignment submission';
 $string['siteuploadlimit'] = 'Site upload limit';
 $string['submissionfilearea'] = 'Uploaded submission files';
 // Deprecated since Moodle 3.4.
diff --git a/mod/assign/submission/file/tests/privacy_test.php b/mod/assign/submission/file/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..61f6841
--- /dev/null
@@ -0,0 +1,175 @@
+<?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/>.
+
+/**
+ * Unit tests for assignsubmission_file.
+ *
+ * @package    assignsubmission_file
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/assign/tests/privacy_test.php');
+
+/**
+ * Unit tests for mod/assign/submission/file/classes/privacy/
+ *
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignsubmission_file_privacy_testcase extends \mod_assign\tests\mod_assign_privacy_testcase {
+
+    /**
+     * Convenience function for creating feedback data.
+     *
+     * @param  object   $assign         assign object
+     * @param  stdClass $student        user object
+     * @param  string   $filename       filename for the file submission
+     * @return array   Submission plugin object and the submission object.
+     */
+    protected function create_file_submission($assign, $student, $filename) {
+        global $CFG;
+        // Create a file submission with the test pdf.
+        $submission = $assign->get_user_submission($student->id, true);
+
+        $this->setUser($student->id);
+
+        $fs = get_file_storage();
+        $pdfsubmission = (object) array(
+            'contextid' => $assign->get_context()->id,
+            'component' => 'assignsubmission_file',
+            'filearea' => ASSIGNSUBMISSION_FILE_FILEAREA,
+            'itemid' => $submission->id,
+            'filepath' => '/',
+            'filename' => $filename
+        );
+        $sourcefile = $CFG->dirroot.'/mod/assign/feedback/editpdf/tests/fixtures/submission.pdf';
+        $fi = $fs->create_file_from_pathname($pdfsubmission, $sourcefile);
+
+        $data = new \stdClass();
+        $plugin = $assign->get_submission_plugin_by_type('file');
+        $plugin->save($submission, $data);
+
+        return [$plugin, $submission];
+    }
+
+    /**
+     * Quick test to make sure that get_metadata returns something.
+     */
+    public function test_get_metadata() {
+        $collection = new \core_privacy\local\metadata\collection('assignsubmission_file');
+        $collection = \assignsubmission_file\privacy\provider::get_metadata($collection);
+        $this->assertNotEmpty($collection);
+    }
+
+    /**
+     * Test that submission files are exported for a user.
+     */
+    public function test_export_submission_user_data() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Student.
+        $user1 = $this->getDataGenerator()->create_user();
+        // Teacher.
+        $user2 = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'editingteacher');
+        $assign = $this->create_instance(['course' => $course]);
+
+        $context = $assign->get_context();
+
+        $studentfilename = 'user1file.pdf';
+        list($plugin, $submission) = $this->create_file_submission($assign, $user1, $studentfilename);
+
+        $writer = \core_privacy\local\request\writer::with_context($context);
+        $this->assertFalse($writer->has_any_data());
+
+        // The student should have a file submission.
+        $exportdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign, $submission, ['Attempt 1']);
+        \assignsubmission_file\privacy\provider::export_submission_user_data($exportdata);
+        // print_object($writer);
+        $storedfile = $writer->get_files(['Attempt 1'])['user1file.pdf'];
+        $this->assertInstanceOf('stored_file', $storedfile);
+        $this->assertEquals($studentfilename, $storedfile->get_filename());
+    }
+
+    /**
+     * Test that all submission files are deleted for this context.
+     */
+    public function test_delete_submission_for_context() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Student.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
+
+        $assign = $this->create_instance(['course' => $course]);
+
+        $context = $assign->get_context();
+
+        $studentfilename = 'user1file.pdf';
+        list($plugin, $submission) = $this->create_file_submission($assign, $user1, $studentfilename);
+        $student2filename = 'user2file.pdf';
+        list($plugin2, $submission2) = $this->create_file_submission($assign, $user2, $studentfilename);
+
+        // Only need the context and assign object in this plugin for this operation.
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign);
+        \assignsubmission_file\privacy\provider::delete_submission_for_context($requestdata);
+        // This checks that there are no files in this submission.
+        $this->assertTrue($plugin->is_empty($submission));
+        $this->assertTrue($plugin2->is_empty($submission2));
+    }
+
+    /**
+     * Test that the comments for a user are deleted.
+     */
+    public function test_delete_submission_for_userid() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Student.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
+
+        $assign = $this->create_instance(['course' => $course]);
+
+        $context = $assign->get_context();
+
+        $studentfilename = 'user1file.pdf';
+        list($plugin, $submission) = $this->create_file_submission($assign, $user1, $studentfilename);
+        $student2filename = 'user2file.pdf';
+        list($plugin2, $submission2) = $this->create_file_submission($assign, $user2, $studentfilename);
+
+        // Only need the context and assign object in this plugin for this operation.
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign, $submission, [], $user1);
+        \assignsubmission_file\privacy\provider::delete_submission_for_userid($requestdata);
+        // This checks that there are no files in this submission.
+        $this->assertTrue($plugin->is_empty($submission));
+        // There should be files here.
+        $this->assertFalse($plugin2->is_empty($submission2));
+    }
+}
diff --git a/mod/assign/submission/onlinetext/classes/privacy/provider.php b/mod/assign/submission/onlinetext/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..27a7d56
--- /dev/null
@@ -0,0 +1,162 @@
+<?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 class for requesting user data.
+ *
+ * @package    assignsubmission_onlinetext
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace assignsubmission_onlinetext\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\metadata\provider as metadataprovider;
+use \core_privacy\local\request\writer;
+use \core_privacy\local\request\contextlist;
+use \mod_assign\privacy\assign_plugin_request_data;
+
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package    assignsubmission_onlinetext
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements metadataprovider, \mod_assign\privacy\assignsubmission_provider {
+
+    /**
+     * Return meta data about this plugin.
+     *
+     * @param  collection $collection A list of information to add to.
+     * @return collection Return the collection after adding to it.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        $detail = [
+                    'assignment' => 'privacy:metadata:assignmentid',
+                    'submission' => 'privacy:metadata:submissionpurpose',
+                    'onlinetext' => 'privacy:metadata:textpurpose'
+                  ];
+        $collection->add_database_table('assignsubmission_onlinetext', $detail, 'privacy:metadata:tablepurpose');
+        $collection->link_subsystem('core_files', 'privacy:metadata:filepurpose');
+        return $collection;
+    }
+
+    /**
+     * This is covered by mod_assign provider and the query on assign_submissions.
+     *
+     * @param  int $userid The user ID that we are finding contexts for.
+     * @param  contextlist $contextlist A context list to add sql and params to for contexts.
+     */
+    public static function get_context_for_userid_within_submission(int $userid, contextlist $contextlist) {
+        // This is already fetched from mod_assign.
+    }
+
+    /**
+     * This is also covered by the mod_assign provider and it's queries.
+     *
+     * @param  \mod_assign\privacy\useridlist $useridlist An object for obtaining user IDs of students.
+     */
+    public static function get_student_user_ids(\mod_assign\privacy\useridlist $useridlist) {
+        // No need.
+    }
+
+    /**
+     * Export all user data for this plugin.
+     *
+     * @param  assign_plugin_request_data $exportdata Data used to determine which context and user to export and other useful
+     * information to help with exporting.
+     */
+    public static function export_submission_user_data(assign_plugin_request_data $exportdata) {
+        // We currently don't show submissions to teachers when exporting their data.
+        if ($exportdata->get_user() != null) {
+            return null;
+        }
+        // Retrieve text for this submission.
+        $assign = $exportdata->get_assign();
+        $plugin = $assign->get_plugin_by_type('assignsubmission', 'onlinetext');
+        $submission = $exportdata->get_pluginobject();
+        $editortext = $plugin->get_editor_text('onlinetext', $submission->id);
+        $context = $exportdata->get_context();
+        if (!empty($editortext)) {
+            $submissiontext = new \stdClass();
+            $submissiontext->text = writer::with_context($context)->rewrite_pluginfile_urls([], '', '', '', $editortext);
+            $currentpath = $exportdata->get_subcontext();
+            $currentpath[] = get_string('privacy:path', 'assignsubmission_onlinetext');
+            writer::with_context($context)
+                    ->export_area_files($currentpath, 'assignsubmission_onlinetext', 'submissions_onlinetext', $submission->id)
+                    // Add the text to the exporter.
+                    ->export_data($currentpath, $submissiontext);
+
+            // Handle plagiarism data.
+            $coursecontext = $context->get_course_context();
+            $userid = $submission->userid;
+            \core_plagiarism\privacy\provider::export_plagiarism_user_data($userid, $context, $currentpath, [
+                'cmid' => $context->instanceid,
+                'course' => $coursecontext->instanceid,
+                'userid' => $userid,
+                'content' => $editortext,
+                'assignment' => $submission->assignment
+            ]);
+        }
+    }
+
+    /**
+     * Any call to this method should delete all user data for the context defined in the deletion_criteria.
+     *
+     * @param  assign_plugin_request_data $requestdata Data useful for deleting user data from this sub-plugin.
+     */
+    public static function delete_submission_for_context(assign_plugin_request_data $requestdata) {
+        global $DB;
+
+        \core_plagiarism\privacy\provider::delete_plagiarism_for_context($requestdata->get_context());
+
+        // Delete related files.
+        $fs = get_file_storage();
+        $fs->delete_area_files($requestdata->get_context()->id, 'assignsubmission_onlinetext',
+                ASSIGNSUBMISSION_ONLINETEXT_FILEAREA);
+
+        // Delete the records in the table.
+        $DB->delete_records('assignsubmission_onlinetext', ['assignment' => $requestdata->get_assign()->get_instance()->id]);
+    }
+
+    /**
+     * A call to this method should delete user data (where practicle) from the userid and context.
+     *
+     * @param  assign_plugin_request_data $deletedata Details about the user and context to focus the deletion.
+     */
+    public static function delete_submission_for_userid(assign_plugin_request_data $deletedata) {
+        global $DB;
+
+        \core_plagiarism\privacy\provider::delete_plagiarism_for_user($deletedata->get_user()->id, $deletedata->get_context());
+
+        $submissionid = $deletedata->get_pluginobject()->id;
+
+        // Delete related files.
+        $fs = get_file_storage();
+        $fs->delete_area_files($deletedata->get_context()->id, 'assignsubmission_onlinetext', ASSIGNSUBMISSION_ONLINETEXT_FILEAREA,
+                $submissionid);
+
+        // Delete the records in the table.
+        $DB->delete_records('assignsubmission_onlinetext', ['assignment' => $deletedata->get_assign()->get_instance()->id,
+                'submission' => $submissionid]);
+    }
+}
index bb4e383..1b65578 100644 (file)
@@ -32,9 +32,15 @@ $string['nosubmission'] = 'Nothing has been submitted for this assignment';
 $string['onlinetext'] = 'Online text';
 $string['onlinetextfilename'] = 'onlinetext.html';
 $string['onlinetextsubmission'] = 'Allow online text submission';
-$string['pluginname'] = 'Online text submissions';
 $string['numwords'] = '({$a} words)';
 $string['numwordsforlog'] = 'Submission word count: {$a} words';
+$string['pluginname'] = 'Online text submissions';
+$string['privacy:metadata:assignmentid'] = 'Assignment identifier';
+$string['privacy:metadata:filepurpose'] = 'Files that are embedded in the text submission.';
+$string['privacy:metadata:submissionpurpose'] = 'The submission ID that links to submissions for the user.';
+$string['privacy:metadata:tablepurpose'] = 'Stores the text submission for each attempt.';
+$string['privacy:metadata:textpurpose'] = 'The actual text submitted for this attempt of the assignment.';
+$string['privacy:path'] = 'Submission Text';
 $string['wordlimit'] = 'Word limit';
 $string['wordlimit_help'] = 'If online text submissions are enabled, this is the maximum number ' .
         'of words that each student will be allowed to submit.';
diff --git a/mod/assign/submission/onlinetext/tests/privacy_test.php b/mod/assign/submission/onlinetext/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..ce1dd50
--- /dev/null
@@ -0,0 +1,164 @@
+<?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/>.
+
+/**
+ * Unit tests for assignsubmission_onlinetext.
+ *
+ * @package    assignsubmission_onlinetext
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/assign/tests/privacy_test.php');
+
+/**
+ * Unit tests for mod/assign/submission/onlinetext/classes/privacy/
+ *
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignsubmission_online_privacy_testcase extends \mod_assign\tests\mod_assign_privacy_testcase {
+
+    /**
+     * Convenience function for creating feedback data.
+     *
+     * @param  object   $assign         assign object
+     * @param  stdClass $student        user object
+     * @param  string   $text           Submission text.
+     * @return array   Submission plugin object and the submission object.
+     */
+    protected function create_online_submission($assign, $student, $text) {
+        global $CFG;
+
+        $this->setUser($student->id);
+        $submission = $assign->get_user_submission($student->id, true);
+        $data = new stdClass();
+        $data->onlinetext_editor = array(
+            'itemid' => file_get_unused_draft_itemid(),
+            'text' => $text,
+            'format' => FORMAT_PLAIN
+        );
+
+        $submission = $assign->get_user_submission($student->id, true);
+
+        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
+        $plugin->save($submission, $data);
+
+        return [$plugin, $submission];
+    }
+
+    /**
+     * Quick test to make sure that get_metadata returns something.
+     */
+    public function test_get_metadata() {
+        $collection = new \core_privacy\local\metadata\collection('assignsubmission_file');
+        $collection = \assignsubmission_onlinetext\privacy\provider::get_metadata($collection);
+        $this->assertNotEmpty($collection);
+    }
+
+    /**
+     * Test that submission files and text are exported for a user.
+     */
+    public function test_export_submission_user_data() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Student.
+        $user1 = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $assign = $this->create_instance(['course' => $course]);
+
+        $context = $assign->get_context();
+
+        $submissiontext = 'Just some text';
+        list($plugin, $submission) = $this->create_online_submission($assign, $user1, $submissiontext);
+
+        $writer = \core_privacy\local\request\writer::with_context($context);
+        $this->assertFalse($writer->has_any_data());
+
+        // The student should have some text submitted.
+        $exportdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign, $submission, ['Attempt 1']);
+        \assignsubmission_onlinetext\privacy\provider::export_submission_user_data($exportdata);
+        $this->assertEquals($submissiontext, $writer->get_data(['Attempt 1',
+                get_string('privacy:path', 'assignsubmission_onlinetext')])->text);
+    }
+
+    /**
+     * Test that all submission files are deleted for this context.
+     */
+    public function test_delete_submission_for_context() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Student.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
+
+        $assign = $this->create_instance(['course' => $course]);
+
+        $context = $assign->get_context();
+
+        $studenttext = 'Student one\'s text.';
+        list($plugin, $submission) = $this->create_online_submission($assign, $user1, $studenttext);
+        $studenttext2 = 'Student two\'s text.';
+        list($plugin2, $submission2) = $this->create_online_submission($assign, $user2, $studenttext2);
+
+        // Only need the context and assign object in this plugin for this operation.
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign);
+        \assignsubmission_onlinetext\privacy\provider::delete_submission_for_context($requestdata);
+        // This checks that there is no content for these submissions.
+        $this->assertTrue($plugin->is_empty($submission));
+        $this->assertTrue($plugin2->is_empty($submission2));
+    }
+
+    /**
+     * Test that the comments for a user are deleted.
+     */
+    public function test_delete_submission_for_userid() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Student.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
+
+        $assign = $this->create_instance(['course' => $course]);
+
+        $context = $assign->get_context();
+
+        $studenttext = 'Student one\'s text.';
+        list($plugin, $submission) = $this->create_online_submission($assign, $user1, $studenttext);
+        $studenttext2 = 'Student two\'s text.';
+        list($plugin2, $submission2) = $this->create_online_submission($assign, $user2, $studenttext2);
+
+        // Need more data for this operation.
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign, $submission, [], $user1);
+        \assignsubmission_onlinetext\privacy\provider::delete_submission_for_userid($requestdata);
+        // This checks that there is no content for the first submission.
+        $this->assertTrue($plugin->is_empty($submission));
+        // But there is for the second submission.
+        $this->assertFalse($plugin2->is_empty($submission2));
+    }
+}
diff --git a/mod/assign/tests/privacy_submission_legacy_polyfill_test.php b/mod/assign/tests/privacy_submission_legacy_polyfill_test.php
new file mode 100644 (file)
index 0000000..9140139
--- /dev/null
@@ -0,0 +1,226 @@
+<?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/>.
+/**
+ * Unit tests for the privacy legacy polyfill for mod_assign.
+ *
+ * @package     mod_assign
+ * @category    test
+ * @copyright   2018 Adrian Greeve <adriangreeve.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/assign/submissionplugin.php');
+require_once($CFG->dirroot . '/mod/assign/submission/comments/locallib.php');
+
+/**
+ * Unit tests for the assignment submission subplugins API's privacy legacy_polyfill.
+ *
+ * @copyright   2018 Adrian Greeve <adriangreeve.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_assignsubmission_privacy_legacy_polyfill_test extends advanced_testcase {
+
+    /**
+     * Convenience function to create an instance of an assignment.
+     *
+     * @param array $params Array of parameters to pass to the generator
+     * @return assign The assign class.
+     */
+    protected function create_instance($params = array()) {
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = \context_module::instance($cm->id);
+        return new \assign($context, $cm, $params['course']);
+    }
+
+    /**
+     * Test the get_context_for_userid_within_submission shim.
+     */
+    public function test_get_context_for_userid_within_submission() {
+        
+        $userid = 21;
+        $contextlist = new \core_privacy\local\request\contextlist();
+        $mock = $this->createMock(test_assignsubmission_legacy_polyfill_mock_wrapper::class);
+        $mock->expects($this->once())
+            ->method('get_return_value')
+            ->with('_get_context_for_userid_within_submission', [$userid, $contextlist]);
+        test_legacy_polyfill_submission_provider::$mock = $mock;
+        test_legacy_polyfill_submission_provider::get_context_for_userid_within_submission($userid, $contextlist);
+    }
+
+    /**
+     * Test the get_student_user_ids shim.
+     */
+    public function test_get_student_user_ids() {
+        $teacherid = 107;
+        $assignid = 15;
+        $useridlist = new \mod_assign\privacy\useridlist($teacherid, $assignid);
+        $mock = $this->createMock(test_assignsubmission_legacy_polyfill_mock_wrapper::class);
+        $mock->expects($this->once())
+            ->method('get_return_value')
+            ->with('_get_student_user_ids', [$useridlist]);
+        test_legacy_polyfill_submission_provider::$mock = $mock;
+        test_legacy_polyfill_submission_provider::get_student_user_ids($useridlist);
+    }
+
+    /**
+     * Test the export_submission_user_data shim.
+     */
+    public function test_export_submission_user_data() {
+        $this->resetAfterTest();
+        $course = $this->getDataGenerator()->create_course();
+        $assign = $this->create_instance(['course' => $course]);
+        $context = context_system::instance();
+        $subplugin = new assign_submission_comments($assign, 'comment');
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign);
+        $mock = $this->createMock(test_assignsubmission_legacy_polyfill_mock_wrapper::class);
+        $mock->expects($this->once())
+            ->method('get_return_value')
+            ->with('_export_submission_user_data', [$requestdata]);
+        test_legacy_polyfill_submission_provider::$mock = $mock;
+        test_legacy_polyfill_submission_provider::export_submission_user_data($requestdata);
+    }
+
+    /**
+     * Test the delete_submission_for_context shim.
+     */
+    public function test_delete_submission_for_context() {
+        $this->resetAfterTest();
+        $course = $this->getDataGenerator()->create_course();
+        $assign = $this->create_instance(['course' => $course]);
+        $context = context_system::instance();
+        $subplugin = new assign_submission_comments($assign, 'comment');
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign);
+        $mock = $this->createMock(test_assignsubmission_legacy_polyfill_mock_wrapper::class);
+        $mock->expects($this->once())
+            ->method('get_return_value')
+            ->with('_delete_submission_for_context', [$requestdata]);
+        test_legacy_polyfill_submission_provider::$mock = $mock;
+        test_legacy_polyfill_submission_provider::delete_submission_for_context($requestdata);
+    }
+
+    /**
+     * Test the delete submission for grade shim.
+     */
+    public function test_delete_submission_for_userid() {
+        $this->resetAfterTest();
+        $course = $this->getDataGenerator()->create_course();
+        $assign = $this->create_instance(['course' => $course]);
+        $context = context_system::instance();
+        $subplugin = new assign_submission_comments($assign, 'comment');
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign);
+        $mock = $this->createMock(test_assignsubmission_legacy_polyfill_mock_wrapper::class);
+        $mock->expects($this->once())
+            ->method('get_return_value')
+            ->with('_delete_submission_for_userid', [$requestdata]);
+        test_legacy_polyfill_submission_provider::$mock = $mock;
+        test_legacy_polyfill_submission_provider::delete_submission_for_userid($requestdata);
+    }
+}
+/**
+ * Legacy polyfill test class for the assignsubmission_provider.
+ *
+ * @copyright   2018 Adrian Greeve <adriangreeve.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class test_legacy_polyfill_submission_provider implements \mod_assign\privacy\assignsubmission_provider {
+    use \mod_assign\privacy\submission_legacy_polyfill;
+    /**
+     * @var test_legacy_polyfill_submission_provider $mock.
+     */
+    public static $mock = null;
+
+    /**
+     * Retrieves the contextids associated with the provided userid for this subplugin.
+     * NOTE if your subplugin must have an entry in the assign_grade table to work, then this
+     * method can be empty.
+     *
+     * @param  int $userid The user ID to get context IDs for.
+     * @param  contextlist $contextlist Use add_from_sql with this object to add your context IDs.
+     */
+    public static function _get_context_for_userid_within_submission(int $userid,
+            \core_privacy\local\request\contextlist $contextlist) {
+        static::$mock->get_return_value(__FUNCTION__, func_get_args());
+    }
+
+    /**
+     * Returns student user ids related to the provided teacher ID. If it is possible that a student ID will not be returned by
+     * the sql query in \mod_assign\privacy\provider::find_grader_info() Then you need to provide some sql to retrive those
+     * student IDs. This is highly likely if you had to fill in get_context_for_userid_within_submission above.
+     *
+     * @param  useridlist $useridlist A list of user IDs of students graded by this user.
+     */
+    public static function _get_student_user_ids(\mod_assign\privacy\useridlist $useridlist) {
+        static::$mock->get_return_value(__FUNCTION__, func_get_args());
+    }
+
+    /**
+     * This method is used to export any user data this sub-plugin has using the assign_plugin_request_data object to get the
+     * context and userid.
+     * assign_plugin_request_data contains:
+     * - context
+     * - grade object
+     * - current path (subcontext)
+     * - user object
+     *
+     * @param  assign_plugin_request_data $exportdata Contains data to help export the user information.
+     */
+    public static function _export_submission_user_data(\mod_assign\privacy\assign_plugin_request_data $exportdata) {
+        static::$mock->get_return_value(__FUNCTION__, func_get_args());
+    }
+
+    /**
+     * Any call to this method should delete all user data for the context defined in the deletion_criteria.
+     * assign_plugin_request_data contains:
+     * - context
+     * - assign object
+     *
+     * @param  assign_plugin_request_data $requestdata Data useful for deleting user data from this sub-plugin.
+     */
+    public static function _delete_submission_for_context(\mod_assign\privacy\assign_plugin_request_data $requestdata) {
+        static::$mock->get_return_value(__FUNCTION__, func_get_args());
+    }
+
+    /**
+     * A call to this method should delete user data (where practicle) from the userid and context.
+     * assign_plugin_request_data contains:
+     * - context
+     * - grade object
+     * - user object
+     * - assign object
+     *
+     * @param  assign_plugin_request_data $requestdata Data useful for deleting user data.
+     */
+    public static function _delete_submission_for_userid(\mod_assign\privacy\assign_plugin_request_data $requestdata) {
+        static::$mock->get_return_value(__FUNCTION__, func_get_args());
+    }
+}
+/**
+ * Called inside the polyfill methods in the test polyfill provider, allowing us to ensure these are called with correct params.
+ *
+ * @copyright   2018 Adrian Greeve <adriangreeve.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class test_assignsubmission_legacy_polyfill_mock_wrapper {
+    /**
+     * Get the return value for the specified item.
+     */
+    public function get_return_value() {
+    }
+}