Merge branch 'MDL-61819-master' of git://github.com/andrewnicols/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 16 Apr 2018 14:12:59 +0000 (16:12 +0200)
committerDavid Monllao <davidm@moodle.com>
Mon, 16 Apr 2018 14:12:59 +0000 (16:12 +0200)
lang/en/editor.php
lib/editor/atto/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/lang/en/editor_atto.php
lib/editor/atto/tests/privacy_provider.php [new file with mode: 0644]
lib/editor/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tests/privacy_provider_test.php [new file with mode: 0644]
lib/editor/textarea/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/textarea/lang/en/editor_textarea.php
lib/editor/tinymce/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tinymce/lang/en/editor_tinymce.php

index 123bdad..54c35b5 100644 (file)
@@ -117,6 +117,8 @@ $string['pleaseenteralt'] = 'Please enter the alternate text';
 $string['popupeditor'] = 'Enlarge Editor';
 $string['preformatted'] = 'Preformatted';
 $string['preview'] = 'Preview';
+$string['privacy:metadata:preference:htmleditor'] = 'The preferred editor to use when using an HTML Text Area';
+$string['privacy:preference:htmleditor'] = 'Your preferred editor to use for writing HTML text is {$a}';
 $string['properties'] = 'Properties';
 $string['redo'] = 'Redo your last action';
 $string['regularexpressions'] = 'Use regular expressions';
diff --git a/lib/editor/atto/classes/privacy/provider.php b/lib/editor/atto/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..44effe0
--- /dev/null
@@ -0,0 +1,182 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for editor_atto.
+ *
+ * @package    editor_atto
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace editor_atto\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\request\approved_contextlist;
+use \core_privacy\local\request\writer;
+use \core_privacy\local\request\helper;
+use \core_privacy\local\request\deletion_criteria;
+use \core_privacy\local\metadata\collection;
+
+/**
+ * Privacy Subsystem implementation for editor_atto.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+        // The Atto editor stores user provided data.
+        \core_privacy\local\metadata\provider,
+
+        // The Atto editor provides data directly to core.
+        \core_privacy\local\request\plugin\provider {
+
+    /**
+     * Returns information about how editor_atto stores its data.
+     *
+     * @param   collection     $collection The initialised collection to add items to.
+     * @return  collection     A listing of user data stored through this system.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        // There isn't much point giving details about the pageid, etc.
+        $collection->add_database_table('editor_atto_autosave', [
+                'userid' => 'privacy:metadata:database:atto_autosave:userid',
+                'drafttext' => 'privacy:metadata:database:atto_autosave:drafttext',
+                'timemodified' => 'privacy:metadata:database:atto_autosave:timemodified',
+            ], 'privacy:metadata:database:atto_autosave');
+
+        return $collection;
+    }
+
+    /**
+     * Get the list of contexts that contain user information for the specified user.
+     *
+     * @param   int         $userid     The user to search.
+     * @return  contextlist   $contextlist  The contextlist containing the list of contexts used in this plugin.
+     */
+    public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist {
+        // This block doesn't know who information is stored against unless it
+        // is at the user context.
+        $contextlist = new \core_privacy\local\request\contextlist();
+        $contextuser = \context_user::instance($userid);
+
+        $sql = "SELECT contextid FROM {editor_atto_autosave} WHERE userid = :userid OR contextid = :contextid";
+        $params = [
+            'userid' => $userid,
+            'contextid' => $contextuser->id,
+        ];
+
+        $contextlist->add_from_sql($sql, $params);
+
+        return $contextlist;
+    }
+
+    /**
+     * Export all user data for the specified user, in the specified contexts.
+     *
+     * @param   approved_contextlist    $contextlist    The approved contexts to export information for.
+     */
+    public static function export_user_data(approved_contextlist $contextlist) {
+        global $DB;
+
+        $user = $contextlist->get_user();
+
+        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
+        $contextparams['userid'] = $contextlist->get_user()->id;
+
+        $sql = "SELECT *
+                  FROM {editor_atto_autosave}
+                 WHERE
+                    (userid = :userid AND contextid {$contextsql})
+                    OR
+                    (contextid = :usercontext)";
+
+        $usercontext = \context_user::instance($user->id);
+        $contextparams['usercontext'] = $usercontext->id;
+        $autosaves = $DB->get_recordset_sql($sql, $contextparams);
+
+        foreach ($autosaves as $autosave) {
+            $context = \context::instance_by_id($autosave->contextid);
+            $subcontext = [
+                get_string('autosaves', 'editor_atto'),
+                $autosave->id,
+            ];
+
+            $html = writer::with_context($context)
+                ->rewrite_pluginfile_urls($subcontext, 'user', 'draft', $autosave->draftid, $autosave->drafttext);
+
+            $data = (object) [
+                'drafttext' => format_text($html, FORMAT_HTML, static::get_filter_options()),
+                'timemodified' => \core_privacy\local\request\transform::datetime($autosave->timemodified),
+            ];
+
+            if ($autosave->userid != $user->id) {
+                $data->author = \core_privacy\local\request\transform::user($autosave->userid);
+            }
+
+            writer::with_context($context)
+                ->export_data($subcontext, $data)
+                ->export_area_files($subcontext, 'user', 'draft', $autosave->draftid);
+        }
+        $autosaves->close();
+    }
+
+    /**
+     * Delete all data for all users in the specified context.
+     *
+     * @param   context                 $context   The specific context to delete data for.
+     */
+    public static function delete_data_for_all_users_in_context(\context $context) {
+        global $DB;
+
+        $DB->delete_records('editor_atto_autosave', [
+                'contextid' => $context->id,
+            ]);
+    }
+
+    /**
+     * Delete all user data for the specified user, in the specified contexts.
+     *
+     * @param   approved_contextlist    $contextlist    The approved contexts and user information to delete information for.
+     */
+    public static function delete_data_for_user(approved_contextlist $contextlist) {
+        global $DB;
+
+        $user = $contextlist->get_user();
+
+        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
+        $contextparams['userid'] = $user->id;
+
+        $sql = "SELECT * FROM {editor_atto_autosave} WHERE contextid {$contextsql}";
+        $autosaves = $DB->delete_records_select('editor_atto_autosave', "userid = :userid AND contextid {$contextsql}",
+                $contextparams);
+    }
+
+    /**
+     * Get the filter options.
+     *
+     * This is shared to allow unit testing too.
+     *
+     * @return  \stdClass
+     */
+    public static function get_filter_options() {
+        return (object) [
+            'overflowdiv' => true,
+            'noclean' => true,
+        ];
+    }
+}
index ed7f087..fe62b3a 100644 (file)
@@ -46,3 +46,8 @@ $string['plugin_title_shortcut'] = '{$a->title} [{$a->shortcut}]';
 $string['recover'] = 'Recover';
 $string['infostatus'] = 'Information';
 $string['warningstatus'] = 'Warning';
+$string['autosaves'] = 'Editor autosave information';
+$string['privacy:metadata:database:atto_autosave'] = 'Editor drafts which was automatically saved.';
+$string['privacy:metadata:database:atto_autosave:userid'] = 'The ID of the user who\'s data was saved.';
+$string['privacy:metadata:database:atto_autosave:drafttext'] = 'The text which was saved.';
+$string['privacy:metadata:database:atto_autosave:timemodified'] = 'The time that content was modified.';
diff --git a/lib/editor/atto/tests/privacy_provider.php b/lib/editor/atto/tests/privacy_provider.php
new file mode 100644 (file)
index 0000000..456d7a6
--- /dev/null
@@ -0,0 +1,470 @@
+<?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 editor_atto implementation of the privacy API.
+ *
+ * @package    editor_atto
+ * @category   test
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\request\writer;
+use \core_privacy\local\request\approved_contextlist;
+use \editor_atto\privacy\provider;
+
+/**
+ * Unit tests for the editor_atto implementation of the privacy API.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class editor_atto_privacy_testcase extends \core_privacy\tests\provider_testcase {
+    /**
+     * One test to check fetch and export of all drafts.
+     */
+    public function test_fetch_and_exports_drafts() {
+        global $USER;
+        $this->resetAfterTest();
+
+        // Create editor drafts in:
+        // - the system; and
+        // - a course; and
+        // - current user context; and
+        // - another user.
+
+        $systemcontext = \context_system::instance();
+        $course = $this->getDataGenerator()->create_course();
+        $coursecontext = \context_course::instance($course->id);
+
+        $usercontextids = [];
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+
+        $usercontext = \context_user::instance($user->id);
+        $usercontextids[] = $usercontext->id;
+        $usercontextids[] = $systemcontext->id;
+        $usercontextids[] = $coursecontext->id;
+
+        // Add a fake inline image to the original post.
+
+        $userdraftintro = $this->create_editor_draft($usercontext, $user->id,
+                'id_user_intro', 'text for test user at own context');
+        $userdraftdescription = $this->create_editor_draft($usercontext, $user->id,
+                'id_user_description', 'text for test user at own context');
+        $systemuserdraftintro = $this->create_editor_draft($systemcontext, $user->id,
+                'id_system_intro', 'text for test user at system context', 2);
+        $systemuserdraftdescription = $this->create_editor_draft($systemcontext, $user->id,
+                'id_system_description', 'text for test user at system context', 4);
+        $coursedraftintro = $this->create_editor_draft($coursecontext, $user->id,
+                'id_course_intro', 'text for test user at course context');
+        $coursedraftdescription = $this->create_editor_draft($coursecontext, $user->id,
+                'id_course_description', 'text for test user at course context');
+
+        // Create some data as the other user too.
+        $otherusercontextids = [];
+        $otheruser = $this->getDataGenerator()->create_user();
+        $this->setUser($otheruser);
+
+        $otherusercontext = \context_user::instance($otheruser->id);
+        $otherusercontextids[] = $otherusercontext->id;
+        $otherusercontextids[] = $systemcontext->id;
+        $otherusercontextids[] = $coursecontext->id;
+
+        $otheruserdraftintro = $this->create_editor_draft($otherusercontext, $otheruser->id,
+                'id_user_intro', 'text for other user at own context');
+        $otheruserdraftdescription = $this->create_editor_draft($otherusercontext, $otheruser->id,
+                'id_user_description', 'text for other user at own context');
+        $systemotheruserdraftintro = $this->create_editor_draft($systemcontext, $otheruser->id,
+                'id_system_intro', 'text for other user at system context');
+        $systemotheruserdraftdescription = $this->create_editor_draft($systemcontext, $otheruser->id,
+                'id_system_description', 'text for other user at system context');
+        $courseotheruserdraftintro = $this->create_editor_draft($coursecontext, $otheruser->id,
+                'id_course_intro', 'text for other user at course context');
+        $courseotheruserdraftdescription = $this->create_editor_draft($coursecontext, $otheruser->id,
+                'id_course_description', 'text for other user at course context');
+
+        // Test as the original user.
+        // Get all context data for the original user.
+        $this->setUser($user);
+        $contextlist = provider::get_contexts_for_userid($user->id);
+
+        // There are three contexts in the list.
+        $this->assertCount(3, $contextlist);
+
+        // Check the list against the expected list of contexts.
+        foreach ($contextlist as $context) {
+            $this->assertContains($context->id, $usercontextids);
+        }
+
+        // Export the data for the system context.
+        // There should be two.
+        $this->export_context_data_for_user($user->id, $systemcontext, 'editor_atto');
+        $writer = \core_privacy\local\request\writer::with_context($systemcontext);
+        $this->assertTrue($writer->has_any_data());
+
+        $subcontextbase = [get_string('autosaves', 'editor_atto')];
+
+        // There should be an intro and description.
+        $intro = $writer->get_data(array_merge($subcontextbase, [$systemuserdraftintro->id]));
+        $fs = get_file_storage();
+        $this->assertEquals(
+                format_text($systemuserdraftintro->drafttext, FORMAT_HTML, provider::get_filter_options()),
+                $intro->drafttext
+            );
+        $this->assertCount(2, $writer->get_files(array_merge($subcontextbase, [$systemuserdraftintro->id])));
+
+        $description = $writer->get_data(array_merge($subcontextbase, [$systemuserdraftdescription->id]));
+        $this->assertEquals(
+                format_text($systemuserdraftdescription->drafttext, FORMAT_HTML, provider::get_filter_options()),
+                $description->drafttext
+            );
+        $this->assertCount(4, $writer->get_files(array_merge($subcontextbase, [$systemuserdraftdescription->id])));
+    }
+
+    /**
+     * Test delete_for_all_users_in_context.
+     */
+    public function test_delete_for_all_users_in_context() {
+        global $USER, $DB;
+        $this->resetAfterTest();
+
+        // Create editor drafts in:
+        // - the system; and
+        // - a course; and
+        // - current user context; and
+        // - another user.
+
+        $systemcontext = \context_system::instance();
+        $course = $this->getDataGenerator()->create_course();
+        $coursecontext = \context_course::instance($course->id);
+
+        $usercontextids = [];
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+
+        $usercontext = \context_user::instance($user->id);
+        $usercontextids[] = $usercontext->id;
+        $usercontextids[] = $systemcontext->id;
+        $usercontextids[] = $coursecontext->id;
+
+        // Add a fake inline image to the original post.
+
+        $userdraftintro = $this->create_editor_draft($usercontext, $user->id,
+                'id_user_intro', 'text for test user at own context');
+        $userdraftdescription = $this->create_editor_draft($usercontext, $user->id,
+                'id_user_description', 'text for test user at own context');
+        $systemuserdraftintro = $this->create_editor_draft($systemcontext, $user->id,
+                'id_system_intro', 'text for test user at system context', 2);
+        $systemuserdraftdescription = $this->create_editor_draft($systemcontext, $user->id,
+                'id_system_description', 'text for test user at system context', 4);
+        $coursedraftintro = $this->create_editor_draft($coursecontext, $user->id,
+                'id_course_intro', 'text for test user at course context');
+        $coursedraftdescription = $this->create_editor_draft($coursecontext, $user->id,
+                'id_course_description', 'text for test user at course context');
+
+        // Create some data as the other user too.
+        $otherusercontextids = [];
+        $otheruser = $this->getDataGenerator()->create_user();
+        $this->setUser($otheruser);
+
+        $otherusercontext = \context_user::instance($otheruser->id);
+        $otherusercontextids[] = $otherusercontext->id;
+        $otherusercontextids[] = $systemcontext->id;
+        $otherusercontextids[] = $coursecontext->id;
+
+        $otheruserdraftintro = $this->create_editor_draft($otherusercontext, $otheruser->id,
+                'id_user_intro', 'text for other user at own context');
+        $otheruserdraftdescription = $this->create_editor_draft($otherusercontext, $otheruser->id,
+                'id_user_description', 'text for other user at own context');
+        $systemotheruserdraftintro = $this->create_editor_draft($systemcontext, $otheruser->id,
+                'id_system_intro', 'text for other user at system context');
+        $systemotheruserdraftdescription = $this->create_editor_draft($systemcontext, $otheruser->id,
+                'id_system_description', 'text for other user at system context');
+        $courseotheruserdraftintro = $this->create_editor_draft($coursecontext, $otheruser->id,
+                'id_course_intro', 'text for other user at course context');
+        $courseotheruserdraftdescription = $this->create_editor_draft($coursecontext, $otheruser->id,
+                'id_course_description', 'text for other user at course context');
+
+        // Test deletion of the user context.
+        $this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $usercontext->id]));
+        provider::delete_data_for_all_users_in_context($usercontext);
+        $this->assertCount(0, $DB->get_records('editor_atto_autosave', ['contextid' => $usercontext->id]));
+
+        // No other contexts should be removed.
+        $this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $otherusercontext->id]));
+        $this->assertCount(4, $DB->get_records('editor_atto_autosave', ['contextid' => $systemcontext->id]));
+        $this->assertCount(4, $DB->get_records('editor_atto_autosave', ['contextid' => $coursecontext->id]));
+
+        // Test deletion of the course contexts.
+        provider::delete_data_for_all_users_in_context($coursecontext);
+        $this->assertCount(0, $DB->get_records('editor_atto_autosave', ['contextid' => $coursecontext->id]));
+        $this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $otherusercontext->id]));
+        $this->assertCount(4, $DB->get_records('editor_atto_autosave', ['contextid' => $systemcontext->id]));
+
+        // Test deletion of the system contexts.
+        provider::delete_data_for_all_users_in_context($systemcontext);
+        $this->assertCount(0, $DB->get_records('editor_atto_autosave', ['contextid' => $systemcontext->id]));
+        $this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $otherusercontext->id]));
+    }
+
+    /**
+     * Test delete_for_all_users_in_context.
+     */
+    public function test_delete_for_user_in_contexts() {
+        global $USER, $DB;
+        $this->resetAfterTest();
+
+        // Create editor drafts in:
+        // - the system; and
+        // - a course; and
+        // - current user context; and
+        // - another user.
+
+        $systemcontext = \context_system::instance();
+        $course = $this->getDataGenerator()->create_course();
+        $coursecontext = \context_course::instance($course->id);
+
+        $usercontextids = [];
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+
+        $usercontext = \context_user::instance($user->id);
+        $usercontextids[] = $usercontext->id;
+        $usercontextids[] = $systemcontext->id;
+        $usercontextids[] = $coursecontext->id;
+
+        // Add a fake inline image to the original post.
+
+        $userdraftintro = $this->create_editor_draft($usercontext, $user->id,
+                'id_user_intro', 'text for test user at own context');
+        $userdraftdescription = $this->create_editor_draft($usercontext, $user->id,
+                'id_user_description', 'text for test user at own context');
+        $systemuserdraftintro = $this->create_editor_draft($systemcontext, $user->id,
+                'id_system_intro', 'text for test user at system context', 2);
+        $systemuserdraftdescription = $this->create_editor_draft($systemcontext, $user->id,
+                'id_system_description', 'text for test user at system context', 4);
+        $coursedraftintro = $this->create_editor_draft($coursecontext, $user->id,
+                'id_course_intro', 'text for test user at course context');
+        $coursedraftdescription = $this->create_editor_draft($coursecontext, $user->id,
+                'id_course_description', 'text for test user at course context');
+
+        // Create some data as the other user too.
+        $otherusercontextids = [];
+        $otheruser = $this->getDataGenerator()->create_user();
+        $this->setUser($otheruser);
+
+        $otherusercontext = \context_user::instance($otheruser->id);
+        $otherusercontextids[] = $otherusercontext->id;
+        $otherusercontextids[] = $systemcontext->id;
+        $otherusercontextids[] = $coursecontext->id;
+
+        $otheruserdraftintro = $this->create_editor_draft($otherusercontext, $otheruser->id,
+                'id_user_intro', 'text for other user at own context');
+        $otheruserdraftdescription = $this->create_editor_draft($otherusercontext, $otheruser->id,
+                'id_user_description', 'text for other user at own context');
+        $systemotheruserdraftintro = $this->create_editor_draft($systemcontext, $otheruser->id,
+                'id_system_intro', 'text for other user at system context');
+        $systemotheruserdraftdescription = $this->create_editor_draft($systemcontext, $otheruser->id,
+                'id_system_description', 'text for other user at system context');
+        $courseotheruserdraftintro = $this->create_editor_draft($coursecontext, $otheruser->id,
+                'id_course_intro', 'text for other user at course context');
+        $courseotheruserdraftdescription = $this->create_editor_draft($coursecontext, $otheruser->id,
+                'id_course_description', 'text for other user at course context');
+
+        // Test deletion of all data for user in usercontext only.
+        $contextlist = new \core_privacy\tests\request\approved_contextlist(
+            \core_user::get_user($user->id),
+            'editor_atto',
+            [$usercontext->id]
+        );
+        provider::delete_data_for_user($contextlist);
+        $this->assertCount(0, $DB->get_records('editor_atto_autosave', ['contextid' => $usercontext->id]));
+
+        // No other contexts should be removed.
+        $this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $otherusercontext->id]));
+        $this->assertCount(4, $DB->get_records('editor_atto_autosave', ['contextid' => $systemcontext->id]));
+        $this->assertCount(4, $DB->get_records('editor_atto_autosave', ['contextid' => $coursecontext->id]));
+
+        // Test deletion of all data for user in course and system.
+        $contextlist = new \core_privacy\tests\request\approved_contextlist(
+            \core_user::get_user($user->id),
+            'editor_atto',
+            [$coursecontext->id, $systemcontext->id]
+        );
+        provider::delete_data_for_user($contextlist);
+        $this->assertCount(0, $DB->get_records('editor_atto_autosave', ['contextid' => $usercontext->id]));
+        $this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $otherusercontext->id]));
+        $this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $systemcontext->id]));
+        $this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $coursecontext->id]));
+
+        // Data for the other user should remain.
+        $this->assertCount(2, $DB->get_records('editor_atto_autosave', [
+            'contextid' => $coursecontext->id,
+            'userid' => $otheruser->id,
+        ]));
+
+        $this->assertCount(2, $DB->get_records('editor_atto_autosave', [
+            'contextid' => $systemcontext->id,
+            'userid' => $otheruser->id,
+        ]));
+    }
+
+    /**
+     * Test fetch and delete when another user has editted a draft in your
+     * user context. Edge case.
+     */
+    public function test_another_user_edits_you() {
+        global $USER, $DB;
+        $this->resetAfterTest();
+
+        $user = $this->getDataGenerator()->create_user();
+        $usercontext = \context_user::instance($user->id);
+        $otheruser = $this->getDataGenerator()->create_user();
+        $otherusercontext = \context_user::instance($otheruser->id);
+        $this->setUser($user);
+
+        $userdraftintro = $this->create_editor_draft($usercontext, $otheruser->id,
+                'id_user_intro', 'text for test user at other context');
+
+        // Test as the owning user.
+        $this->setUser($user);
+        $contextlist = provider::get_contexts_for_userid($user->id);
+        $contexts = $contextlist->get_contexts();
+        $this->assertCount(1, $contexts);
+        $firstcontext = reset($contexts);
+        $this->assertEquals($usercontext, $firstcontext);
+
+        // Should have the data.
+        $this->export_context_data_for_user($user->id, $usercontext, 'editor_atto');
+        $writer = \core_privacy\local\request\writer::with_context($usercontext);
+        $this->assertTrue($writer->has_any_data());
+
+        $subcontext = [
+            get_string('autosaves', 'editor_atto'),
+            $userdraftintro->id,
+        ];
+        $data = $writer->get_data($subcontext);
+        $this->assertEquals(\core_privacy\local\request\transform::user($otheruser->id), $data->author);
+
+        $contextlist = new \core_privacy\tests\request\approved_contextlist(
+            \core_user::get_user($user->id),
+            'editor_atto',
+            [$usercontext->id]
+        );
+
+
+        // Deleting for this context should _not_ delete as the user does not own this draft (crazy edge case, remember).
+        provider::delete_data_for_user($contextlist);
+        $records = $DB->get_records('editor_atto_autosave');
+        $this->assertNotEmpty($records);
+        $this->assertCount(1, $records);
+        $firstrecord = reset($records);
+        $this->assertEquals($userdraftintro->id, $firstrecord->id);
+    }
+
+    /**
+     * Test fetch and delete when you have edited another user's context.
+     */
+    public function test_another_you_edit_different_user() {
+        global $USER, $DB;
+        $this->resetAfterTest();
+
+        $user = $this->getDataGenerator()->create_user();
+        $usercontext = \context_user::instance($user->id);
+        $otheruser = $this->getDataGenerator()->create_user();
+        $otherusercontext = \context_user::instance($otheruser->id);
+        $this->setUser($user);
+
+        $userdraftintro = $this->create_editor_draft($otherusercontext, $user->id,
+                'id_user_intro', 'text for other user you just edited.');
+
+        // Test as the context owner.
+        $this->setUser($user);
+        $contextlist = provider::get_contexts_for_userid($user->id);
+        $contexts = $contextlist->get_contexts();
+        $this->assertCount(1, $contexts);
+        $firstcontext = reset($contexts);
+        $this->assertEquals($otherusercontext, $firstcontext);
+
+        // Should have the data.
+        $this->export_context_data_for_user($user->id, $otherusercontext, 'editor_atto');
+        $writer = \core_privacy\local\request\writer::with_context($otherusercontext);
+        $this->assertTrue($writer->has_any_data());
+
+        $subcontext = [
+            get_string('autosaves', 'editor_atto'),
+            $userdraftintro->id,
+        ];
+        $data = $writer->get_data($subcontext);
+        $this->assertFalse(isset($data->author));
+
+        $contextlist = new \core_privacy\tests\request\approved_contextlist(
+            \core_user::get_user($user->id),
+            'editor_atto',
+            [$otherusercontext->id]
+        );
+        provider::delete_data_for_user($contextlist);
+        $this->assertEmpty($DB->get_records('editor_atto_autosave'));
+    }
+
+   /**
+     * Create an editor draft.
+     *
+     * @param   \context    $context The context to create the draft for.
+     * @param   int         $userid The ID to create the draft for.
+     * @param   string      $elementid The elementid for the editor.
+     * @param   string      $text The text to write.
+     * @param   int         $filecount The number of files to create.
+     * @return  \stdClass   The editor draft.
+     */
+    protected function create_editor_draft(\context $context, $userid, $elementid, $text, $filecount = 0) {
+        global $DB;
+
+        $draftid = file_get_unused_draft_itemid();
+        $fs = get_file_storage();
+
+        for ($i = 0; $i < $filecount; $i++) {
+            $fs->create_file_from_string([
+                    'contextid' => $context->id,
+                    'component' => 'user',
+                    'filearea'  => 'draft',
+                    'itemid'    => $draftid,
+                    'filepath'  => '/',
+                    'filename'  => "example_{$i}.txt",
+                ],
+            "Awesome example of a text file with id {$i} for {$context->id} and {$elementid}");
+        }
+
+        $id = $DB->insert_record('editor_atto_autosave', (object) [
+                'elementid' => $elementid,
+                'contextid' => $context->id,
+                'userid' => $userid,
+                'drafttext' => $text,
+                'draftid' => $draftid,
+                'pageinstance' => 'example_page_instance_' . rand(1, 1000),
+                'timemodified' => time(),
+
+                // Page hash doesn't matter for our purposes.
+                'pagehash' => sha1("{$userid}/{$context->id}/{$elementid}/{$draftid}"),
+            ]);
+
+        return $DB->get_record('editor_atto_autosave', ['id' => $id]);
+    }
+}
diff --git a/lib/editor/classes/privacy/provider.php b/lib/editor/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..40c0786
--- /dev/null
@@ -0,0 +1,71 @@
+<?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    core_editor
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_editor\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\metadata\collection;
+use core_privacy\local\request\writer;
+
+/**
+ * Provider for the editor API.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+        // The Editor subsystem does not store any data itself.
+        // It has no database tables, and it purely acts as a conduit to the various editors.
+        \core_privacy\local\metadata\provider,
+
+        // The Editor subsystem has user preferences.
+        \core_privacy\local\request\user_preference_provider {
+
+    /**
+     * Returns meta data about this system.
+     *
+     * @param   collection     $collection The initialised collection to add items to.
+     * @return  collection     A listing of user data stored through this system.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        $collection->add_user_preference('htmleditor', 'privacy:metadata:preference:htmleditor');
+
+        return $collection;
+    }
+
+    /**
+     * Export all user preferences for the plugin.
+     *
+     * @param   int         $userid The userid of the user whose data is to be exported.
+     */
+    public static function export_user_preferences(int $userid) {
+        $preference = get_user_preferences('htmleditor');
+        if (null !== $preference) {
+            $desc = get_string('privacy:preference:htmleditor', 'core_editor',
+                    get_string('pluginname', "editor_{$preference}"));
+            writer::export_user_preference('core_editor', 'htmleditor', $preference, $desc);
+        }
+    }
+}
diff --git a/lib/editor/tests/privacy_provider_test.php b/lib/editor/tests/privacy_provider_test.php
new file mode 100644 (file)
index 0000000..d33e007
--- /dev/null
@@ -0,0 +1,79 @@
+<?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 provider tests.
+ *
+ * @package    core_editor
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+use core_privacy\local\metadata\collection;
+use core_privacy\local\request\writer;
+use core_editor\privacy\provider;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy provider tests class.
+ *
+ * @package    core_editor
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_editor_privacy_provider_testcase extends \core_privacy\tests\provider_testcase {
+    /**
+     * When no preference exists, there should be no export.
+     */
+    public function test_no_preference() {
+        global $USER;
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        provider::export_user_preferences($USER->id);
+        $this->assertFalse(writer::with_context(\context_system::instance())->has_any_data());
+    }
+
+    /**
+     * When an editor is set, the name of that editor will be reported.
+     */
+    public function test_editor_atto() {
+        global $USER;
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        set_user_preference('htmleditor', 'atto');
+
+        provider::export_user_preferences($USER->id);
+        $this->assertTrue(writer::with_context(\context_system::instance())->has_any_data());
+
+        $prefs = writer::with_context(\context_system::instance())->get_user_preferences('core_editor');
+        $this->assertNotEmpty($prefs->htmleditor);
+        $this->assertNotEmpty($prefs->htmleditor->value);
+        $this->assertNotEmpty($prefs->htmleditor->description);
+        $this->assertEquals('atto', $prefs->htmleditor->value);
+
+        $this->assertEquals(
+            get_string(
+                'privacy:preference:htmleditor',
+                'core_editor',
+                get_string('pluginname', "editor_atto")
+            ),
+            $prefs->htmleditor->description
+        );
+    }
+}
diff --git a/lib/editor/textarea/classes/privacy/provider.php b/lib/editor/textarea/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..cebcc66
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for editor_textarea.
+ *
+ * @package    editor_textarea
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace editor_textarea\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for editor_textarea implementing null_provider.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @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:metadata';
+    }
+}
\ No newline at end of file
index a3aa32f..e4df7ef 100644 (file)
@@ -25,3 +25,4 @@
  */
 
 $string['pluginname'] = 'Plain text area';
+$string['privacy:metadata'] = 'The editor_textarea plugin does not store any personal data.';
diff --git a/lib/editor/tinymce/classes/privacy/provider.php b/lib/editor/tinymce/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..6b60499
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for editor_tinymce.
+ *
+ * @package    editor_tinymce
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace editor_tinymce\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for editor_tinymce implementing null_provider.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @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:metadata';
+    }
+}
\ No newline at end of file
index 0f0cafb..82dd895 100644 (file)
@@ -894,3 +894,4 @@ $string['advanced:cut_desc'] = 'Cut';
 $string['advanced:paste_desc'] = 'Paste';
 $string['advanced:shortcuts_desc'] = 'Accessibility help';
 $string['autosave:restore_content'] = 'Restore auto-saved content';
+$string['privacy:metadata'] = 'The editor_tinymce plugin does not store any personal data.';