MDL-61308 assign_feedback: Privacy code for user rights.
authorAdrian Greeve <adrian@moodle.com>
Wed, 31 Jan 2018 06:20:17 +0000 (14:20 +0800)
committerAdrian Greeve <adrian@moodle.com>
Thu, 26 Apr 2018 06:17:55 +0000 (14:17 +0800)
14 files changed:
mod/assign/classes/privacy/assignfeedback_provider.php [new file with mode: 0644]
mod/assign/classes/privacy/feedback_legacy_polyfill.php [new file with mode: 0644]
mod/assign/feedback/comments/classes/privacy/provider.php [new file with mode: 0644]
mod/assign/feedback/comments/lang/en/assignfeedback_comments.php
mod/assign/feedback/comments/tests/privacy_test.php [new file with mode: 0644]
mod/assign/feedback/editpdf/classes/privacy/provider.php [new file with mode: 0644]
mod/assign/feedback/editpdf/lang/en/assignfeedback_editpdf.php
mod/assign/feedback/editpdf/tests/privacy_test.php [new file with mode: 0644]
mod/assign/feedback/file/classes/privacy/provider.php [new file with mode: 0644]
mod/assign/feedback/file/lang/en/assignfeedback_file.php
mod/assign/feedback/file/tests/privacy_test.php [new file with mode: 0644]
mod/assign/feedback/offline/classes/privacy/provider.php [new file with mode: 0644]
mod/assign/feedback/offline/lang/en/assignfeedback_offline.php
mod/assign/tests/privacy_feedback_legacy_polyfill_test.php [new file with mode: 0644]

diff --git a/mod/assign/classes/privacy/assignfeedback_provider.php b/mod/assign/classes/privacy/assignfeedback_provider.php
new file mode 100644 (file)
index 0000000..c93852a
--- /dev/null
@@ -0,0 +1,87 @@
+<?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 assignfeedback_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 assignfeedback_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_grade 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_feedback(int $userid, contextlist $contextlist);
+
+    /**
+     * Returns student user ids related to the provided teacher ID. If an entry must be present in the assign_grade table for
+     * your plugin to work then there is no need to fill in this method. If you filled in get_context_for_userid_within_feedback()
+     * then you probably have to fill this in as well.
+     *
+     * @param  useridlist $useridlist A list of user IDs of students graded by this user.
+     */
+    public static function get_student_user_ids(useridlist $useridlist);
+
+    /**
+     * Export feedback data with the available grade and userid information provided.
+     * 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_feedback_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 Data useful for deleting user data from this sub-plugin.
+     */
+    public static function delete_feedback_for_context(assign_plugin_request_data $requestdata);
+
+    /**
+     * Calling this function should delete all user data associated with this grade.
+     * 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_feedback_for_grade(assign_plugin_request_data $requestdata);
+}
\ No newline at end of file
diff --git a/mod/assign/classes/privacy/feedback_legacy_polyfill.php b/mod/assign/classes/privacy/feedback_legacy_polyfill.php
new file mode 100644 (file)
index 0000000..2fd6bdd
--- /dev/null
@@ -0,0 +1,100 @@
+<?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 feedback_legacy_polyfill {
+
+    /**
+     * 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  \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_feedback(int $userid, contextlist $contextlist) {
+        return static::_get_context_for_userid_within_feedback($userid, $contextlist);
+    }
+
+    /**
+     * Returns student user ids related to the provided teacher ID. If an entry must be present in the assign_grade table for
+     * your plugin to work then there is no need to fill in this method. If you filled in get_context_for_userid_within_feedback()
+     * then you probably have to fill this in as well.
+     *
+     * @param  useridlist $useridlist A list of user IDs of students graded by this user.
+     */
+    public static function get_student_user_ids(useridlist $useridlist) {
+        return static::_get_student_user_ids($useridlist);
+    }
+
+    /**
+     * Export feedback data with the available grade and userid information provided.
+     * 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_feedback_user_data(assign_plugin_request_data $exportdata) {
+        return static::_export_feedback_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 Data useful for deleting user data from this sub-plugin.
+     */
+    public static function delete_feedback_for_context(assign_plugin_request_data $requestdata) {
+        return static::_delete_feedback_for_context($requestdata);
+    }
+
+    /**
+     * Calling this function should delete all user data associated with this grade.
+     * 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_feedback_for_grade(assign_plugin_request_data $requestdata) {
+        return static::_delete_feedback_for_grade($requestdata);
+    }
+}
diff --git a/mod/assign/feedback/comments/classes/privacy/provider.php b/mod/assign/feedback/comments/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..ac9aa7f
--- /dev/null
@@ -0,0 +1,125 @@
+<?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    assignfeedback_comments
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace assignfeedback_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 \mod_assign\privacy\assignfeedback_provider;
+use \core_privacy\local\request\writer;
+use \core_privacy\local\request\contextlist;
+use \mod_assign\privacy\assign_plugin_request_data;
+use \mod_assign\privacy\useridlist;
+
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package    assignfeedback_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, assignfeedback_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 {
+        $data = [
+            'assignment' => 'privacy:metadata:assignmentid',
+            'grade' => 'privacy:metadata:gradepurpose',
+            'commenttext' => 'privacy:metadata:commentpurpose'
+        ];
+        $collection->add_database_table('assignfeedback_comments', $data, 'privacy:metadata:tablesummary');
+        return $collection;
+    }
+
+    /**
+     * No need to fill in this method as all information can be acquired from the assign_grades table in the mod assign
+     * provider.
+     *
+     * @param  int $userid The user ID.
+     * @param  contextlist $contextlist The context list.
+     */
+    public static function get_context_for_userid_within_feedback(int $userid, contextlist $contextlist) {
+        // This uses the assign_grades table.
+    }
+
+    /**
+     * This also does not need to be filled in as this is already collected in the mod assign provider.
+     *
+     * @param  useridlist $useridlist A list of user IDs
+     */
+    public static function get_student_user_ids(useridlist $useridlist) {
+        // Not required.
+    }
+
+    /**
+     * 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_feedback_user_data(assign_plugin_request_data $exportdata) {
+        // Get that comment information and jam it into that exporter.
+        $assign = $exportdata->get_assign();
+        $plugin = $assign->get_plugin_by_type('assignfeedback', 'comments');
+        $comments = $plugin->get_feedback_comments($exportdata->get_pluginobject()->id);
+        if ($comments && !empty($comments->commenttext)) {
+            $data = (object)['commenttext' => format_text($comments->commenttext, $comments->commentformat,
+                    ['context' => $exportdata->get_context()])];
+            writer::with_context($exportdata->get_context())
+                    ->export_data(array_merge($exportdata->get_subcontext(),
+                            [get_string('privacy:commentpath', 'assignfeedback_comments')]), $data);
+        }
+    }
+
+    /**
+     * 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_feedback_for_context(assign_plugin_request_data $requestdata) {
+        $assign = $requestdata->get_assign();
+        $plugin = $assign->get_plugin_by_type('assignfeedback', 'comments');
+        $plugin->delete_instance();
+    }
+
+    /**
+     * Calling this function should delete all user data associated with this grade entry.
+     *
+     * @param  assign_plugin_request_data $requestdata Data useful for deleting user data.
+     */
+    public static function delete_feedback_for_grade(assign_plugin_request_data $requestdata) {
+        global $DB;
+        $DB->delete_records('assignfeedback_comments', ['assignment' => $requestdata->get_assign()->get_instance()->id,
+                'grade' => $requestdata->get_pluginobject()->id]);
+    }
+}
index 525eee6..52d8681 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-
 $string['default'] = 'Enabled by default';
 $string['default_help'] = 'If set, this feedback method will be enabled by default for all new assignments.';
 $string['enabled'] = 'Feedback comments';
 $string['enabled_help'] = 'If enabled, the marker can leave feedback comments for each submission. ';
 $string['pluginname'] = 'Feedback comments';
+$string['privacy:commentpath'] = 'Feedback comments';
+$string['privacy:metadata:assignmentid'] = 'Assignment identifier';
+$string['privacy:metadata:commentpurpose'] = 'The comment text.';
+$string['privacy:metadata:gradepurpose'] = 'The grade ID associated with the comment.';
+$string['privacy:metadata:tablesummary'] = 'This stores comments made by the graders as feedback for the student on their submission.';
 $string['commentinline'] = 'Comment inline';
 $string['commentinline_help'] = 'If enabled, the submission text will be copied into the feedback comment field during grading, making it easier to comment inline (using a different colour, perhaps) or to edit the original text.';
 $string['commentinlinedefault'] = 'Comment inline by default';
 $string['commentinlinedefault_help'] = 'If set, this comment inline functionality will be enabled by default for all new assignments.';
-
diff --git a/mod/assign/feedback/comments/tests/privacy_test.php b/mod/assign/feedback/comments/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..3649452
--- /dev/null
@@ -0,0 +1,203 @@
+<?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 assignfeedback_comments.
+ *
+ * @package    assignfeedback_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/locallib.php');
+require_once($CFG->dirroot . '/mod/assign/tests/privacy_test.php');
+
+/**
+ * Unit tests for mod/assign/feedback/comments/classes/privacy/
+ *
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_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  stdClass $teacher        user object
+     * @param  string   $submissiontext Submission text
+     * @param  string   $feedbacktext   Feedback text
+     * @return array   Feedback plugin object and the grade object.
+     */
+    protected function create_feedback($assign, $student, $teacher, $submissiontext, $feedbacktext) {
+        $submission = new \stdClass();
+        $submission->assignment = $assign->get_instance()->id;
+        $submission->userid = $student->id;
+        $submission->timecreated = time();
+        $submission->onlinetext_editor = ['text' => $submissiontext,
+                                         'format' => FORMAT_MOODLE];
+
+        $this->setUser($student);
+        $notices = [];
+        $assign->save_submission($submission, $notices);
+
+        $grade = $assign->get_user_grade($student->id, true);
+
+        $this->setUser($teacher);
+
+        $plugin = $assign->get_feedback_plugin_by_type('comments');
+        $feedbackdata = new \stdClass();
+        $feedbackdata->assignfeedbackcomments_editor = [
+            'text' => $feedbacktext,
+            'format' => 1
+        ];
+
+        $plugin->save($grade, $feedbackdata);
+        return [$plugin, $grade];
+    }
+
+    /**
+     * Quick test to make sure that get_metadata returns something.
+     */
+    public function test_get_metadata() {
+        $collection = new \core_privacy\local\metadata\collection('assignfeedback_comments');
+        $collection = \assignfeedback_comments\privacy\provider::get_metadata($collection);
+        $this->assertNotEmpty($collection);
+    }
+
+    /**
+     * Test that feedback comments are exported for a user.
+     */
+    public function test_export_feedback_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();
+
+        $feedbacktext = '<p>first comment for this test</p>';
+        list($plugin, $grade) = $this->create_feedback($assign, $user1, $user2, 'Submission text', $feedbacktext);
+
+        $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, $grade, [], $user1);
+        \assignfeedback_comments\privacy\provider::export_feedback_user_data($exportdata);
+        $this->assertEquals($feedbacktext, $writer->get_data(['Feedback comments'])->commenttext);
+
+        // The teacher should also be able to see the feedback that they provided.
+        $exportdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign, $grade, [], $user2);
+        \assignfeedback_comments\privacy\provider::export_feedback_user_data($exportdata);
+        $this->assertEquals($feedbacktext, $writer->get_data(['Feedback comments'])->commenttext);
+    }
+
+    /**
+     * Test that all feedback is deleted for a context.
+     */
+    public function test_delete_feedback_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();
+        // 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();
+
+        $feedbacktext = '<p>first comment for this test</p>';
+        list($plugin1, $grade1) = $this->create_feedback($assign, $user1, $user3, 'Submission text', $feedbacktext);
+        $feedbacktext = '<p>Comment for second student.</p>';
+        list($plugin2, $grade2) = $this->create_feedback($assign, $user2, $user3, 'Submission text', $feedbacktext);
+
+        // Check that we have data.
+        $feedbackcomments = $plugin1->get_feedback_comments($grade1->id);
+        $this->assertNotEmpty($feedbackcomments);
+        $feedbackcomments = $plugin1->get_feedback_comments($grade2->id);
+        $this->assertNotEmpty($feedbackcomments);
+
+        // Delete all comments for this context.
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign);
+        assignfeedback_comments\privacy\provider::delete_feedback_for_context($requestdata);
+
+        // Check that the data is now gone.
+        $feedbackcomments = $plugin1->get_feedback_comments($grade1->id);
+        $this->assertEmpty($feedbackcomments);
+        $feedbackcomments = $plugin1->get_feedback_comments($grade2->id);
+        $this->assertEmpty($feedbackcomments);
+    }
+
+    /**
+     * Test that a grade item is deleted for a user.
+     */
+    public function test_delete_feedback_for_grade() {
+        $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();
+
+        $feedbacktext = '<p>first comment for this test</p>';
+        list($plugin1, $grade1) = $this->create_feedback($assign, $user1, $user3, 'Submission text', $feedbacktext);
+        $feedbacktext = '<p>Comment for second student.</p>';
+        list($plugin2, $grade2) = $this->create_feedback($assign, $user2, $user3, 'Submission text', $feedbacktext);
+
+        // Check that we have data.
+        $feedbackcomments = $plugin1->get_feedback_comments($grade1->id);
+        $this->assertNotEmpty($feedbackcomments);
+        $feedbackcomments = $plugin1->get_feedback_comments($grade2->id);
+        $this->assertNotEmpty($feedbackcomments);
+
+        // Delete all comments for this grade object.
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign, $grade1, [], $user1);
+        assignfeedback_comments\privacy\provider::delete_feedback_for_grade($requestdata);
+
+        // These comments should be empty.
+        $feedbackcomments = $plugin1->get_feedback_comments($grade1->id);
+        $this->assertEmpty($feedbackcomments);
+
+        // These comments should not.
+        $feedbackcomments = $plugin1->get_feedback_comments($grade2->id);
+        $this->assertNotEmpty($feedbackcomments);
+    }
+}
diff --git a/mod/assign/feedback/editpdf/classes/privacy/provider.php b/mod/assign/feedback/editpdf/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..e61e781
--- /dev/null
@@ -0,0 +1,147 @@
+<?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    assignfeedback_editpdf
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace assignfeedback_editpdf\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 \mod_assign\privacy\assignfeedback_provider;
+use \core_privacy\local\request\writer;
+use \core_privacy\local\request\contextlist;
+use \mod_assign\privacy\assign_plugin_request_data;
+use \mod_assign\privacy\useridlist;
+
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package    assignfeedback_editpdf
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements metadataprovider, assignfeedback_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 {
+        $quickdata = [
+            'userid' => 'privacy:metadata:userid',
+            'rawtext' => 'privacy:metadata:rawtextpurpose',
+            'colour' => 'privacy:metadata:colourpurpose'
+        ];
+        $collection->add_database_table('assignfeedback_editpdf_quick', $quickdata, 'privacy:metadata:tablepurpose');
+        $collection->add_subsystem_link('core_files', [], 'privacy:metadata:filepurpose');
+        $collection->add_subsystem_link('core_fileconverted', [], 'privacy:metadata:conversionpurpose');
+        return $collection;
+    }
+
+    /**
+     * No need to fill in this method as all information can be acquired from the assign_grades table in the mod assign
+     * provider.
+     *
+     * @param  int $userid The user ID.
+     * @param  contextlist $contextlist The context list.
+     */
+    public static function get_context_for_userid_within_feedback(int $userid, contextlist $contextlist) {
+        // This uses the assign_grade table.
+    }
+
+    /**
+     * This also does not need to be filled in as this is already collected in the mod assign provider.
+     *
+     * @param  useridlist $useridlist A list of user IDs
+     */
+    public static function get_student_user_ids(useridlist $useridlist) {
+        // Not required.
+    }
+
+    /**
+     * 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_feedback_user_data(assign_plugin_request_data $exportdata) {
+        $currentpath = $exportdata->get_subcontext();
+        $currentpath[] = get_string('privacy:path', 'assignfeedback_editpdf');
+        $assign = $exportdata->get_assign();
+        $plugin = $assign->get_plugin_by_type('assignfeedback', 'editpdf');
+        $fileareas = $plugin->get_file_areas();
+        $grade = $exportdata->get_pluginobject();
+        foreach ($fileareas as $filearea => $notused) {
+            writer::with_context($exportdata->get_context())
+                    ->export_area_files($currentpath, 'assignfeedback_editpdf', $filearea, $grade->id);
+        }
+    }
+
+    /**
+     * 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_feedback_for_context(assign_plugin_request_data $requestdata) {
+
+        $assign = $requestdata->get_assign();
+        $plugin = $assign->get_plugin_by_type('assignfeedback', 'editpdf');
+        $fileareas = $plugin->get_file_areas();
+        $fs = get_file_storage();
+        foreach ($fileareas as $filearea => $notused) {
+            // Delete pdf files.
+            $fs->delete_area_files($requestdata->get_context()->id, 'assignfeedback_editpdf', $filearea);
+        }
+        // Delete entries from the tables.
+        $plugin->delete_instance();
+    }
+
+    /**
+     * Calling this function should delete all user data associated with this grade.
+     *
+     * @param  assign_plugin_request_data $requestdata Data useful for deleting user data.
+     */
+    public static function delete_feedback_for_grade(assign_plugin_request_data $requestdata) {
+        global $DB;
+
+        $assign = $requestdata->get_assign();
+        $plugin = $assign->get_plugin_by_type('assignfeedback', 'editpdf');
+        $fileareas = $plugin->get_file_areas();
+        $fs = get_file_storage();
+        foreach ($fileareas as $filearea => $notused) {
+            // Delete pdf files.
+            $fs->delete_area_files($requestdata->get_context()->id, 'assignfeedback_editpdf',
+                    $filearea, $requestdata->get_pluginobject()->id);
+        }
+
+        // Remove table entries.
+        $DB->delete_records('assignfeedback_editpdf_annot', ['gradeid' => $requestdata->get_pluginobject()->id]);
+        $DB->delete_records('assignfeedback_editpdf_cmnt', ['gradeid' => $requestdata->get_pluginobject()->id]);
+        // Submission records in assignfeedback_editpdf_queue will be cleaned up in a scheduled task
+    }
+}
index fe6a7ec..1f7fac4 100644 (file)
@@ -73,6 +73,13 @@ $string['pagenumber'] = 'Page {$a}';
 $string['pagexofy'] = 'Page {$a->page} of {$a->total}';
 $string['pen'] = 'Pen';
 $string['pluginname'] = 'Annotate PDF';
+$string['privacy:metadata:colourpurpose'] = 'Colour of the comment or annotation';
+$string['privacy:metadata:conversionpurpose'] = 'Files are converted to PDFs to allow for annotations.';
+$string['privacy:metadata:filepurpose'] = 'Stores an annotated PDF with feedback for the user.';
+$string['privacy:metadata:rawtextpurpose'] = 'Stores raw text for the quick data.';
+$string['privacy:metadata:tablepurpose'] = 'Stores teacher specified quicklist comments';
+$string['privacy:metadata:userid'] = 'An identifier for the user.';
+$string['privacy:path'] = 'PDF Feedback';
 $string['generatingpdf'] = 'Generating the PDF...';
 $string['rectangle'] = 'Rectangle';
 $string['red'] = 'Red';
diff --git a/mod/assign/feedback/editpdf/tests/privacy_test.php b/mod/assign/feedback/editpdf/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..2291d6c
--- /dev/null
@@ -0,0 +1,244 @@
+<?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 assignfeedback_editpdf.
+ *
+ * @package    assignfeedback_editpdf
+ * @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/locallib.php');
+require_once($CFG->dirroot . '/mod/assign/tests/privacy_test.php');
+
+use \assignfeedback_editpdf\page_editor;
+use \mod_assign\privacy\assign_plugin_request_data;
+
+/**
+ * Unit tests for mod/assign/feedback/editpdf/classes/privacy/
+ *
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_editpdf_privacy_testcase extends \mod_assign\tests\mod_assign_privacy_testcase {
+
+    public function setUp() {
+        // Skip this test if ghostscript is not supported.
+        $result = \assignfeedback_editpdf\pdf::test_gs_path(false);
+        if ($result->status !== \assignfeedback_editpdf\pdf::GSPATH_OK) {
+            $this->markTestSkipped('Ghostscript not setup');
+            return;
+        }
+        parent::setUp();
+    }
+
+    /**
+     * Convenience function for creating feedback data.
+     *
+     * @param  object   $assign         assign object
+     * @param  stdClass $student        user object
+     * @param  stdClass $teacher        user object
+     * @return array   Feedback plugin object and the grade object.
+     */
+    protected function create_feedback($assign, $student, $teacher) {
+        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' => 'submission.pdf'
+        );
+        $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);
+
+        $this->setUser($teacher->id);
+
+        $plugin = $assign->get_feedback_plugin_by_type('editpdf');
+
+        $grade = $assign->get_user_grade($student->id, true);
+
+        $comment = new \assignfeedback_editpdf\comment();
+
+        $comment->rawtext = 'Comment text';
+        $comment->width = 100;
+        $comment->x = 100;
+        $comment->y = 100;
+        $comment->colour = 'red';
+        page_editor::set_comments($grade->id, 0, [$comment]);
+
+        $annotation = new \assignfeedback_editpdf\annotation();
+
+        $annotation->path = '';
+        $annotation->x = 100;
+        $annotation->y = 100;
+        $annotation->endx = 200;
+        $annotation->endy = 200;
+        $annotation->type = 'line';
+        $annotation->colour = 'red';
+
+        page_editor::set_annotations($grade->id, 0, [$annotation]);
+
+        $comments = page_editor::get_comments($grade->id, 0, true);
+        $annotations = page_editor::get_annotations($grade->id, 0, false);
+        page_editor::release_drafts($grade->id);
+        $storedfile = \assignfeedback_editpdf\document_services::generate_feedback_document($assign->get_instance()->id, $student->id,
+                $grade->attemptnumber);
+
+        return [$plugin, $grade, $storedfile];
+    }
+
+    /**
+     * Quick test to make sure that get_metadata returns something.
+     */
+    public function test_get_metadata() {
+        $collection = new \core_privacy\local\metadata\collection('assignfeedback_editpdf');
+        $collection = \assignfeedback_editpdf\privacy\provider::get_metadata($collection);
+        $this->assertNotEmpty($collection);
+    }
+
+    /**
+     * Test that feedback comments are exported for a user.
+     */
+    public function test_export_feedback_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,
+                'assignsubmission_file_enabled' => 1,
+                'assignsubmission_file_maxfiles' => 1,
+                'assignfeedback_editpdf_enabled' => 1,
+                'assignsubmission_file_maxsizebytes' => 1000000]);
+
+        $context = $assign->get_context();
+
+        list($plugin, $grade, $storedfile) = $this->create_feedback($assign, $user1, $user2);
+
+        // Check that we have data.
+        $this->assertFalse($plugin->is_empty($grade));
+
+        $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, $grade, [], $user1);
+        \assignfeedback_editpdf\privacy\provider::export_feedback_user_data($exportdata);
+        // print_object($writer->get_files([get_string('privacy:path', 'assignfeedback_editpdf')]));
+        // print_object($writer->get_files(['PDF feedback', $storedfile->get_filename()]));
+        $pdffile = $writer->get_files([get_string('privacy:path', 'assignfeedback_editpdf')])[$storedfile->get_filename()];
+        // The writer should have returned a stored file.
+        $this->assertInstanceOf('stored_file', $pdffile);
+    }
+
+    /**
+     * Test that all feedback is deleted for a context.
+     */
+    public function test_delete_feedback_for_context() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Students.
+        $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,
+                'assignsubmission_file_enabled' => 1,
+                'assignsubmission_file_maxfiles' => 1,
+                'assignfeedback_editpdf_enabled' => 1,
+                'assignsubmission_file_maxsizebytes' => 1000000]);
+
+        $context = $assign->get_context();
+
+        list($plugin1, $grade1, $storedfile1) = $this->create_feedback($assign, $user1, $user3);
+        list($plugin2, $grade2, $storedfile2) = $this->create_feedback($assign, $user2, $user3);
+
+        // Check that we have data.
+        $this->assertFalse($plugin1->is_empty($grade1));
+        $this->assertFalse($plugin2->is_empty($grade2));
+
+        $requestdata = new assign_plugin_request_data($context, $assign);
+        \assignfeedback_editpdf\privacy\provider::delete_feedback_for_context($requestdata);
+
+        // Check that we now have no data.
+        $this->assertTrue($plugin1->is_empty($grade1));
+        $this->assertTrue($plugin2->is_empty($grade2));
+    }
+
+    /**
+     * Test that a grade item is deleted for a user.
+     */
+    public function test_delete_feedback_for_grade() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Students.
+        $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,
+                'assignsubmission_file_enabled' => 1,
+                'assignsubmission_file_maxfiles' => 1,
+                'assignfeedback_editpdf_enabled' => 1,
+                'assignsubmission_file_maxsizebytes' => 1000000]);
+
+        $context = $assign->get_context();
+
+        list($plugin1, $grade1, $storedfile1) = $this->create_feedback($assign, $user1, $user3);
+        list($plugin2, $grade2, $storedfile2) = $this->create_feedback($assign, $user2, $user3);
+
+        // Check that we have data.
+        $this->assertFalse($plugin1->is_empty($grade1));
+        $this->assertFalse($plugin2->is_empty($grade2));
+
+        $requestdata = new assign_plugin_request_data($context, $assign, $grade1, [], $user1);
+        \assignfeedback_editpdf\privacy\provider::delete_feedback_for_grade($requestdata);
+
+        // Check that we now have no data for user 1.
+        $this->assertTrue($plugin1->is_empty($grade1));
+        // Check that user 2 data is still there.
+        $this->assertFalse($plugin2->is_empty($grade2));
+    }
+}
diff --git a/mod/assign/feedback/file/classes/privacy/provider.php b/mod/assign/feedback/file/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..f2f609f
--- /dev/null
@@ -0,0 +1,140 @@
+<?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    assignfeedback_file
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace assignfeedback_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\contextlist;
+use \mod_assign\privacy\assignfeedback_provider;
+use \mod_assign\privacy\assign_plugin_request_data;
+use mod_assign\privacy\useridlist;
+
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package    assignfeedback_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, assignfeedback_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;
+    }
+
+    /**
+     * No need to fill in this method as all information can be acquired from the assign_grades table in the mod assign
+     * provider.
+     *
+     * @param  int $userid The user ID.
+     * @param  contextlist $contextlist The context list.
+     */
+    public static function get_context_for_userid_within_feedback(int $userid, contextlist $contextlist) {
+        // This uses the assign_grade table.
+    }
+
+    /**
+     * This also does not need to be filled in as this is already collected in the mod assign provider.
+     *
+     * @param  useridlist $useridlist A list of user IDs
+     */
+    public static function get_student_user_ids(useridlist $useridlist) {
+        // Not required.
+    }
+
+    /**
+     * 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_feedback_user_data(assign_plugin_request_data $exportdata) {
+        $currentpath = $exportdata->get_subcontext();
+        $currentpath[] = get_string('privacy:path', 'assignfeedback_file');
+        $assign = $exportdata->get_assign();
+        $plugin = $assign->get_plugin_by_type('assignfeedback', 'file');
+        $gradeid = $exportdata->get_pluginobject()->id;
+        $filefeedback = $plugin->get_file_feedback($gradeid);
+        if ($filefeedback) {
+            $fileareas = $plugin->get_file_areas();
+            foreach ($fileareas as $filearea => $notused) {
+                \core_privacy\local\request\writer::with_context($exportdata->get_context())
+                        ->export_area_files($currentpath, 'assignfeedback_file', $filearea, $gradeid);
+            }
+        }
+    }
+
+    /**
+     * 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_feedback_for_context(assign_plugin_request_data $requestdata) {
+
+        $assign = $requestdata->get_assign();
+        $plugin = $assign->get_plugin_by_type('assignfeedback', 'file');
+        $fileareas = $plugin->get_file_areas();
+        $fs = get_file_storage();
+        foreach ($fileareas as $filearea => $notused) {
+            // Delete feedback files.
+            $fs->delete_area_files($requestdata->get_context()->id, 'assignfeedback_file', $filearea);
+        }
+        $plugin->delete_instance();
+    }
+
+    /**
+     * Calling this function should delete all user data associated with this grade.
+     *
+     * @param  assign_plugin_request_data $requestdata Data useful for deleting user data.
+     */
+    public static function delete_feedback_for_grade(assign_plugin_request_data $requestdata) {
+        global $DB;
+
+        $assign = $requestdata->get_assign();
+        $plugin = $assign->get_plugin_by_type('assignfeedback', 'file');
+        $fileareas = $plugin->get_file_areas();
+        $fs = get_file_storage();
+        foreach ($fileareas as $filearea => $notused) {
+            // Delete feedback files.
+            $fs->delete_area_files($requestdata->get_context()->id, 'assignfeedback_file', $filearea,
+                    $requestdata->get_pluginobject()->id);
+        }
+
+        // Delete table entries.
+        $DB->delete_records('assignfeedback_file', ['assignment' => $requestdata->get_assign()->get_instance()->id,
+                'grade' => $requestdata->get_pluginobject()->id]);
+    }
+}
index 20d4069..3765b00 100644 (file)
@@ -37,6 +37,8 @@ $string['feedbackfileadded'] = 'New feedback file "{$a->filename}" for student "
 $string['feedbackfileupdated'] = 'Modified feedback file "{$a->filename}" for student "{$a->student}"';
 $string['feedbackzip_help'] = 'A zip file containing a list of feedback files for one or more students. Feedback files will be assigned to students based on the participant id which should be the second part of each filename immediately after the users full name. This naming convention is used when downloading submissions so you can download all submissions, add comments to a few files and then rezip and upload all of the files. Files with no changes will be ignored.';
 $string['file'] = 'Feedback files';
+$string['privacy:metadata:filepurpose'] = 'Feedback files from the teacher for the student.';
+$string['privacy:path'] = 'Feedback files';
 $string['filesupdated'] = 'Feedback files updated: {$a}';
 $string['filesadded'] = 'Feedback files added: {$a}';
 $string['importfeedbackfiles'] = 'Import feedback file(s)';
diff --git a/mod/assign/feedback/file/tests/privacy_test.php b/mod/assign/feedback/file/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..5537755
--- /dev/null
@@ -0,0 +1,209 @@
+<?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 assignfeedback_file.
+ *
+ * @package    assignfeedback_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/locallib.php');
+require_once($CFG->dirroot . '/mod/assign/tests/privacy_test.php');
+
+use mod_assign\privacy\assign_plugin_request_data;
+
+/**
+ * Unit tests for mod/assign/feedback/file/classes/privacy/
+ *
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_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  stdClass $teacher        user object
+     * @param  string   $submissiontext Submission text
+     * @param  string   $feedbacktext   Feedback text
+     * @return array   Feedback plugin object and the grade object.
+     */
+    protected function create_feedback($assign, $student, $teacher, $submissiontext, $feedbacktext) {
+
+        $submission = new \stdClass();
+        $submission->assignment = $assign->get_instance()->id;
+        $submission->userid = $student->id;
+        $submission->timecreated = time();
+        $submission->onlinetext_editor = ['text' => $submissiontext,
+                                         'format' => FORMAT_MOODLE];
+
+        $this->setUser($student);
+        $notices = [];
+        $assign->save_submission($submission, $notices);
+
+        $grade = $assign->get_user_grade($student->id, true);
+
+        $this->setUser($teacher);
+
+        $context = context_user::instance($teacher->id);
+
+        $draftitemid = file_get_unused_draft_itemid();
+        file_prepare_draft_area($draftitemid, $context->id, 'assignfeedback_file', 'feedback_files', 1);
+
+        $dummy = array(
+            'contextid' => $context->id,
+            'component' => 'user',
+            'filearea' => 'draft',
+            'itemid' => $draftitemid,
+            'filepath' => '/',
+            'filename' => 'feedback1.txt'
+        );
+
+        $fs = get_file_storage();
+        $file = $fs->create_file_from_string($dummy, $feedbacktext);
+
+        // Create formdata.
+        $data = new stdClass();
+        $data->{'files_' . $teacher->id . '_filemanager'} = $draftitemid;
+
+        $plugin = $assign->get_feedback_plugin_by_type('file');
+        // Save the feedback.
+        $plugin->save($grade, $data);
+
+        return [$plugin, $grade];
+    }
+
+    /**
+     * Quick test to make sure that get_metadata returns something.
+     */
+    public function test_get_metadata() {
+        $collection = new \core_privacy\local\metadata\collection('assignfeedback_file');
+        $collection = \assignfeedback_file\privacy\provider::get_metadata($collection);
+        $this->assertNotEmpty($collection);
+    }
+
+    /**
+     * Test that feedback comments are exported for a user.
+     */
+    public function test_export_feedback_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();
+
+        $feedbacktext = '<p>first comment for this test</p>';
+        list($plugin, $grade) = $this->create_feedback($assign, $user1, $user2, 'Submission text', $feedbacktext);
+
+        $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, $grade, [], $user1);
+        \assignfeedback_file\privacy\provider::export_feedback_user_data($exportdata);
+        $feedbackfile = $writer->get_files([get_string('privacy:path', 'assignfeedback_file')])['feedback1.txt'];
+        // Check that we got a stored file.
+        $this->assertInstanceOf('stored_file', $feedbackfile);
+        $this->assertEquals('feedback1.txt', $feedbackfile->get_filename());
+    }
+
+    /**
+     * Test that all feedback is deleted for a context.
+     */
+    public function test_delete_feedback_for_context() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Students.
+        $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();
+
+        $feedbacktext = '<p>first comment for this test</p>';
+        list($plugin1, $grade1) = $this->create_feedback($assign, $user1, $user3, 'Submission text', $feedbacktext);
+        $feedbacktext = '<p>Comment for second submission.</p>';
+        list($plugin2, $grade2) = $this->create_feedback($assign, $user2, $user3, 'Submission text', $feedbacktext);
+
+        // Check that we have data.
+        $this->assertFalse($plugin1->is_empty($grade1));
+        $this->assertFalse($plugin2->is_empty($grade2));
+
+        $requestdata = new assign_plugin_request_data($context, $assign);
+        \assignfeedback_file\privacy\provider::delete_feedback_for_context($requestdata);
+
+        // Check that we now have no data.
+        $this->assertTrue($plugin1->is_empty($grade1));
+        $this->assertTrue($plugin2->is_empty($grade2));
+    }
+
+    /**
+     * Test that a grade item is deleted for a user.
+     */
+    public function test_delete_feedback_for_grade() {
+        $this->resetAfterTest();
+        // Create course, assignment, submission, and then a feedback comment.
+        $course = $this->getDataGenerator()->create_course();
+        // Students.
+        $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();
+
+        $feedbacktext = '<p>first comment for this test</p>';
+        list($plugin1, $grade1) = $this->create_feedback($assign, $user1, $user3, 'Submission text', $feedbacktext);
+        $feedbacktext = '<p>Comment for second submission.</p>';
+        list($plugin2, $grade2) = $this->create_feedback($assign, $user2, $user3, 'Submission text', $feedbacktext);
+
+        // Check that we have data.
+        $this->assertFalse($plugin1->is_empty($grade1));
+        $this->assertFalse($plugin2->is_empty($grade2));
+
+        $requestdata = new assign_plugin_request_data($context, $assign, $grade1, [], $user1);
+        \assignfeedback_file\privacy\provider::delete_feedback_for_grade($requestdata);
+
+        // Check that we now have no data.
+        $this->assertTrue($plugin1->is_empty($grade1));
+        // User 2's data should still be intact.
+        $this->assertFalse($plugin2->is_empty($grade2));
+    }
+}
diff --git a/mod/assign/feedback/offline/classes/privacy/provider.php b/mod/assign/feedback/offline/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..f3793ad
--- /dev/null
@@ -0,0 +1,47 @@
+<?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    assignfeedback_offline
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace assignfeedback_offline\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package    assignfeedback_offline
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:nullproviderreason';
+    }
+}
index aefde95..d5722ff 100644 (file)
@@ -38,6 +38,7 @@ $string['importgrades'] = 'Confirm changes in grading worksheet';
 $string['invalidgradeimport'] = 'Moodle could not read the uploaded worksheet. Make sure it is saved in comma separated value format (.csv) and try again.';
 $string['gradesfile'] = 'Grading worksheet (csv format)';
 $string['gradesfile_help'] = 'Grading worksheet with modified grades. This file must be a csv file that has been downloaded from this assignment and must contain columns for the student grade, and identifier. The encoding for the file must be &quot;UTF-8&quot;';
+$string['privacy:nullproviderreason'] = 'This plugin has no database to store user information. It only uses APIs in mod_assign to help with displaying the grading interface.';
 $string['nochanges'] = 'No modified grades found in uploaded worksheet';
 $string['offlinegradingworksheet'] = 'Grades';
 $string['pluginname'] = 'Offline grading worksheet';
diff --git a/mod/assign/tests/privacy_feedback_legacy_polyfill_test.php b/mod/assign/tests/privacy_feedback_legacy_polyfill_test.php
new file mode 100644 (file)
index 0000000..94f3ebf
--- /dev/null
@@ -0,0 +1,225 @@
+<?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/feedbackplugin.php');
+require_once($CFG->dirroot . '/mod/assign/feedback/comments/locallib.php');
+
+/**
+ * Unit tests for the assignment feedback 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_assignfeedback_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_feedback shim.
+     */
+    public function test_get_context_for_userid_within_feedback() {
+        $userid = 21;
+        $contextlist = new \core_privacy\local\request\contextlist();
+        $mock = $this->createMock(test_assignfeedback_legacy_polyfill_mock_wrapper::class);
+        $mock->expects($this->once())
+            ->method('get_return_value')
+            ->with('_get_context_for_userid_within_feedback', [$userid, $contextlist]);
+        test_legacy_polyfill_feedback_provider::$mock = $mock;
+        test_legacy_polyfill_feedback_provider::get_context_for_userid_within_feedback($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_assignfeedback_legacy_polyfill_mock_wrapper::class);
+        $mock->expects($this->once())
+            ->method('get_return_value')
+            ->with('_get_student_user_ids', [$useridlist]);
+        test_legacy_polyfill_feedback_provider::$mock = $mock;
+        test_legacy_polyfill_feedback_provider::get_student_user_ids($useridlist);
+    }
+
+    /**
+     * Test the export_feedback_user_data shim.
+     */
+    public function test_export_feedback_user_data() {
+        $this->resetAfterTest();
+        $course = $this->getDataGenerator()->create_course();
+        $assign = $this->create_instance(['course' => $course]);
+        $context = context_system::instance();
+        $subplugin = new assign_feedback_comments($assign, 'comments');
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context,$assign);
+        $mock = $this->createMock(test_assignfeedback_legacy_polyfill_mock_wrapper::class);
+        $mock->expects($this->once())
+            ->method('get_return_value')
+            ->with('_export_feedback_user_data', [$requestdata]);
+        test_legacy_polyfill_feedback_provider::$mock = $mock;
+        test_legacy_polyfill_feedback_provider::export_feedback_user_data($requestdata);
+    }
+
+    /**
+     * Test the delete_feedback_for_context shim.
+     */
+    public function test_delete_feedback_for_context() {
+        $this->resetAfterTest();
+        $course = $this->getDataGenerator()->create_course();
+        $assign = $this->create_instance(['course' => $course]);
+        $context = context_system::instance();
+        $subplugin = new assign_feedback_comments($assign, 'comments');
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context,$assign);
+        $mock = $this->createMock(test_assignfeedback_legacy_polyfill_mock_wrapper::class);
+        $mock->expects($this->once())
+            ->method('get_return_value')
+            ->with('_delete_feedback_for_context', [$requestdata]);
+        test_legacy_polyfill_feedback_provider::$mock = $mock;
+        test_legacy_polyfill_feedback_provider::delete_feedback_for_context($requestdata);
+    }
+
+    /**
+     * Test the delete feedback for grade shim.
+     */
+    public function test_delete_feedback_for_grade() {
+        $this->resetAfterTest();
+        $course = $this->getDataGenerator()->create_course();
+        $assign = $this->create_instance(['course' => $course]);
+        $context = context_system::instance();
+        $subplugin = new assign_feedback_comments($assign, 'comments');
+        $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context,$assign);
+        $mock = $this->createMock(test_assignfeedback_legacy_polyfill_mock_wrapper::class);
+        $mock->expects($this->once())
+            ->method('get_return_value')
+            ->with('_delete_feedback_for_grade', [$requestdata]);
+        test_legacy_polyfill_feedback_provider::$mock = $mock;
+        test_legacy_polyfill_feedback_provider::delete_feedback_for_grade($requestdata);
+    }
+}
+/**
+ * Legacy polyfill test class for the assignfeedback_provider.
+ *
+ * @copyright   2018 Adrian Greeve <adriangreeve.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class test_legacy_polyfill_feedback_provider implements \mod_assign\privacy\assignfeedback_provider {
+    use \mod_assign\privacy\feedback_legacy_polyfill;
+    /**
+     * @var test_legacy_polyfill_feedback_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_feedback(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 an entry must be present in the assign_grade table for
+     * your plugin to work then there is no need to fill in this method. If you filled in get_context_for_userid_within_feedback()
+     * then you probably have to fill this in as well.
+     *
+     * @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());
+    }
+
+    /**
+     * Export feedback data with the available grade and userid information provided.
+     * 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_feedback_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_feedback_for_context(\mod_assign\privacy\assign_plugin_request_data $requestdata) {
+        static::$mock->get_return_value(__FUNCTION__, func_get_args());
+    }
+
+    /**
+     * Calling this function should delete all user data associated with this grade.
+     * 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_feedback_for_grade(\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_assignfeedback_legacy_polyfill_mock_wrapper {
+    /**
+     * Get the return value for the specified item.
+     */
+    public function get_return_value() {
+    }
+}