MDL-62010 core_enrol: Add privacy implementation for core_enrol
authorcescobedo <carlos.escobedo@gmail.com>
Tue, 8 May 2018 16:52:25 +0000 (18:52 +0200)
committercescobedo <carlos.escobedo@gmail.com>
Tue, 8 May 2018 16:52:25 +0000 (18:52 +0200)
enrol/classes/privacy/provider.php [new file with mode: 0644]
enrol/tests/privacy_test.php [new file with mode: 0644]
lang/en/enrol.php

diff --git a/enrol/classes/privacy/provider.php b/enrol/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..46a0c63
--- /dev/null
@@ -0,0 +1,233 @@
+<?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 core_enrol.
+ *
+ * @package    core_enrol
+ * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core_enrol\privacy;
+defined('MOODLE_INTERNAL') || die();
+
+use core_privacy\local\metadata\collection;
+use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\context;
+use core_privacy\local\request\contextlist;
+use core_privacy\local\request\transform;
+use core_privacy\local\request\writer;
+
+/**
+ * Privacy Subsystem for core_enrol implementing metadata and plugin providers.
+ *
+ * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+        \core_privacy\local\metadata\provider,
+        \core_privacy\local\request\subsystem\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_database_table(
+            'user_enrolments',
+            [
+                'status' => 'privacy:metadata:user_enrolments:status',
+                'enrolid' => 'privacy:metadata:user_enrolments:enrolid',
+                'userid' => 'privacy:metadata:user_enrolments:userid',
+                'timestart' => 'privacy:metadata:user_enrolments:timestart',
+                'timeend' => 'privacy:metadata:user_enrolments:timeend',
+                'modifierid' => 'privacy:metadata:user_enrolments:modifierid',
+                'timecreated' => 'privacy:metadata:user_enrolments:timecreated',
+                'timemodified' => 'privacy:metadata:user_enrolments:timemodified'
+            ],
+            'privacy:metadata:user_enrolments:tableexplanation'
+        );
+
+        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) : contextlist {
+        $sql = "SELECT ctx.id
+                  FROM {user_enrolments} ue
+                  JOIN {enrol} e
+                    ON e.id = ue.enrolid
+                   AND ue.userid = :userid
+                  JOIN {context} ctx
+                    ON ctx.instanceid = e.courseid
+                   AND ctx.contextlevel = :contextlevel";
+        $params = [
+            'contextlevel' => CONTEXT_COURSE,
+            'userid'       => $userid
+        ];
+        $contextlist = new contextlist();
+        $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;
+
+        if (empty($contextlist->count())) {
+            return;
+        }
+        $userid = $contextlist->get_user()->id;
+        list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
+        $params = [
+            'contextlevel' => CONTEXT_COURSE,
+            'userid' => $userid
+         ];
+        $params += $inparams;
+        $sql = "SELECT ue.id,
+                       ue.status,
+                       ue.timestart,
+                       ue.timeend,
+                       ue.timecreated,
+                       ue.timemodified,
+                       e.enrol,
+                       ctx.id as contextid
+                  FROM {user_enrolments} ue
+                  JOIN {enrol} e
+                    ON e.id = ue.enrolid
+                   AND ue.userid = :userid
+                  JOIN {context} ctx
+                    ON ctx.instanceid = e.courseid
+                   AND ctx.contextlevel = :contextlevel
+                 WHERE ctx.id $insql
+                 ORDER BY ctx.id, e.enrol";
+        $data = [];
+        $lastcontextid = null;
+        $lastenrol = null;
+        $path = [get_string('privacy:metadata:user_enrolments', 'core_enrol')];
+        $flush = function($lastcontextid, $lastenrol, $data) use ($path) {
+            $context = \context::instance_by_id($lastcontextid);
+            writer::with_context($context)->export_related_data(
+                $path,
+                $lastenrol,
+                (object)$data
+            );
+        };
+        $userenrolments = $DB->get_recordset_sql($sql, $params);
+        foreach ($userenrolments as $userenrolment) {
+            if (($lastcontextid && $lastcontextid != $userenrolment->contextid) ||
+                    ($lastenrol && $lastenrol != $userenrolment->enrol)) {
+                $flush($lastcontextid, $lastenrol, $data);
+                $data = [];
+            }
+            $data[] = (object) [
+                'status' => $userenrolment->status,
+                'timecreated' => transform::datetime($userenrolment->timecreated),
+                'timemodified' => transform::datetime($userenrolment->timemodified),
+                'timestart' => transform::datetime($userenrolment->timestart),
+                'timeend' => transform::datetime($userenrolment->timeend)
+            ];
+            $lastcontextid = $userenrolment->contextid;
+            $lastenrol = $userenrolment->enrol;
+        }
+        if (!empty($data)) {
+            $flush($lastcontextid, $lastenrol, $data);
+        }
+        $userenrolments->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;
+
+        if (empty($context)) {
+            return;
+        }
+        // Sanity check that context is at the User context level.
+        if ($context->contextlevel == CONTEXT_COURSE) {
+            $sql = "SELECT ue.id
+                      FROM {user_enrolments} ue
+                      JOIN {enrol} e
+                        ON e.id = ue.enrolid
+                      JOIN {context} ctx
+                        ON ctx.instanceid = e.courseid
+                     WHERE ctx.id = :contextid";
+            $params = ['contextid' => $context->id];
+            $enrolsids = $DB->get_fieldset_sql($sql, $params);
+            if (!empty($enrolsids)) {
+                list($insql, $inparams) = $DB->get_in_or_equal($enrolsids, SQL_PARAMS_NAMED);
+                static::delete_user_data($insql, $inparams);
+            }
+        }
+    }
+    /**
+     * 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;
+
+        if (empty($contextlist->count())) {
+            return;
+        }
+        $userid = $contextlist->get_user()->id;
+        list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
+        $params = [
+            'contextlevel' => CONTEXT_COURSE,
+            'userid' => $userid
+         ];
+        $params += $inparams;
+        $sql = "SELECT ue.id
+                  FROM {user_enrolments} ue
+                  JOIN {enrol} e
+                    ON e.id = ue.enrolid
+                   AND ue.userid = :userid
+                  JOIN {context} ctx
+                    ON ctx.instanceid = e.courseid
+                   AND ctx.contextlevel = :contextlevel
+                 WHERE ctx.id $insql";
+        $enrolsids = $DB->get_fieldset_sql($sql, $params);
+        if (!empty($enrolsids)) {
+            list($insql, $inparams) = $DB->get_in_or_equal($enrolsids, SQL_PARAMS_NAMED);
+            static::delete_user_data($insql, $inparams);
+        }
+    }
+
+    /**
+     * Delete data from $tablename with the IDs returned by $sql query.
+     *
+     * @param  string $sql    SQL query for getting the IDs of the uer enrolments entries to delete.
+     * @param  array  $params SQL params for the query.
+     */
+    protected static function delete_user_data(string $sql, array $params) {
+        global $DB;
+
+        $DB->delete_records_select('user_enrolments', "id $sql", $params);
+    }
+
+}
diff --git a/enrol/tests/privacy_test.php b/enrol/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..8d98fe2
--- /dev/null
@@ -0,0 +1,175 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+/**
+ * Privacy test for the core_enrol implementation of the privacy API.
+ *
+ * @package    core_enrol
+ * @category   test
+ * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die();
+use core_enrol\privacy\provider;
+use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\writer;
+use core_privacy\tests\provider_testcase;
+use \core_privacy\local\request\transform;
+/**
+ * Privacy test for the core_enrol.
+ *
+ * @package    core_enrol
+ * @category   test
+ * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_enrol_privacy_testcase extends provider_testcase {
+    /**
+     * Check that a course context is returned if there is any user data for this user.
+     */
+    public function test_get_contexts_for_userid() {
+        $this->resetAfterTest();
+        $user1 = $this->getDataGenerator()->create_user();
+        $course1 = $this->getDataGenerator()->create_course();
+        $this->assertEmpty(provider::get_contexts_for_userid($user1->id));
+        // Enrol user into courses and check contextlist.
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id,  null, 'manual');
+        $contextlist = provider::get_contexts_for_userid($user1->id);
+        // Check that we only get back two context.
+        $this->assertCount(1, $contextlist);
+        // Check that the context is returned is the expected.
+        $coursecontext1 = \context_course::instance($course1->id);
+        $this->assertEquals($coursecontext1->id, $contextlist->get_contextids()[0]);
+    }
+    /**
+     * Test that user data is exported correctly.
+     */
+    public function test_export_user_data() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $user1 = $this->getDataGenerator()->create_user();
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id,  null, 'manual');
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id,  null, 'self');
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id,  null, 'manual');
+        $subcontexts = [
+            get_string('privacy:metadata:user_enrolments', 'core_enrol')
+        ];
+        $coursecontext1 = \context_course::instance($course1->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+        $this->setUser($user1);
+        $writer = writer::with_context($coursecontext1);
+        $this->assertFalse($writer->has_any_data());
+        $this->export_context_data_for_user($user1->id, $coursecontext1, 'core_enrol');
+        $data = $writer->get_related_data($subcontexts);
+        $this->assertCount(2, (array)$data);
+
+        $sql = "SELECT ue.id,
+                       ue.status,
+                       ue.timestart,
+                       ue.timeend,
+                       ue.timecreated,
+                       ue.timemodified
+                  FROM {user_enrolments} ue
+                  JOIN {enrol} e
+                    ON e.id = ue.enrolid
+                   AND e.courseid = :courseid
+                 WHERE ue.userid = :userid";
+        $enrolmentcouse2 = $DB->get_record_sql($sql, array('userid' => $user1->id, 'courseid' => $course2->id));
+        writer::reset();
+        $writer = writer::with_context($coursecontext2);
+        $this->export_context_data_for_user($user1->id, $coursecontext2, 'core_enrol');
+        $data = $writer->get_related_data($subcontexts, 'manual');
+        $this->assertEquals($enrolmentcouse2->status, reset($data)->status);
+        $this->assertEquals(transform::datetime($enrolmentcouse2->timestart), reset($data)->timestart);
+        $this->assertEquals(transform::datetime($enrolmentcouse2->timeend), reset($data)->timeend);
+        $this->assertEquals(transform::datetime($enrolmentcouse2->timecreated), reset($data)->timecreated);
+        $this->assertEquals(transform::datetime($enrolmentcouse2->timemodified), reset($data)->timemodified);
+    }
+    /**
+     * Test deleting all user data for a specific context.
+     */
+    public function test_delete_data_for_all_users_in_context() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id,  null, 'manual');
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id,  null, 'manual');
+        $this->getDataGenerator()->enrol_user($user3->id, $course1->id,  null, 'manual');
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id,  null, 'manual');
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id,  null, 'manual');
+        // Get all user enrolments.
+        $userenrolments = $DB->get_records('user_enrolments', array());
+        $this->assertCount(5, $userenrolments);
+        // Get all user enrolments match with course1.
+        $sql = "SELECT ue.id
+                  FROM {user_enrolments} ue
+                  JOIN {enrol} e
+                    ON e.id = ue.enrolid
+                   AND e.courseid = :courseid";
+        $userenrolments = $DB->get_records_sql($sql, array('courseid' => $course1->id));
+        $this->assertCount(3, $userenrolments);
+        // Delete everything for the first course context.
+        $coursecontext1 = \context_course::instance($course1->id);
+        provider::delete_data_for_all_users_in_context($coursecontext1);
+        // Get all user enrolments match with this course contest.
+        $userenrolments = $DB->get_records_sql($sql, array('courseid' => $course1->id));
+        $this->assertCount(0, $userenrolments);
+        // Get all user enrolments.
+        $userenrolments = $DB->get_records('user_enrolments', array());
+        $this->assertCount(2, $userenrolments);
+    }
+    /**
+     * This should work identical to the above test.
+     */
+    public function test_delete_data_for_user() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id,  null, 'manual');
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id,  null, 'manual');
+        $this->getDataGenerator()->enrol_user($user3->id, $course1->id,  null, 'manual');
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id,  null, 'manual');
+
+        // Get all user enrolments.
+        $userenrolments = $DB->get_records('user_enrolments', array());
+        $this->assertCount(4, $userenrolments);
+        // Get all user enrolments match with user1.
+        $userenrolments = $DB->get_records('user_enrolments', array('userid' => $user1->id));
+        $this->assertCount(2, $userenrolments);
+        // Delete everything for the user1 in the context course 1.
+        $coursecontext1 = \context_course::instance($course1->id);
+        $approvedlist = new approved_contextlist($user1, 'core_enrol', [$coursecontext1->id]);
+        provider::delete_data_for_user($approvedlist);
+        // Get all user enrolments match with user.
+        $userenrolments = $DB->get_records('user_enrolments', ['userid' => $user1->id]);
+        $this->assertCount(1, $userenrolments);
+        // Get all user enrolments accounts.
+        $userenrolments = $DB->get_records('user_enrolments', array());
+        $this->assertCount(3, $userenrolments);
+    }
+}
\ No newline at end of file
index 1d29ca6..7315719 100644 (file)
@@ -149,3 +149,13 @@ $string['extremovedsuspend'] = 'Disable course enrolment';
 $string['extremovedsuspendnoroles'] = 'Disable course enrolment and remove roles';
 $string['extremovedkeep'] = 'Keep user enrolled';
 $string['extremovedunenrol'] = 'Unenrol user from course';
+$string['privacy:metadata:user_enrolments'] = 'Enrolments';
+$string['privacy:metadata:user_enrolments:enrolid'] = 'The instance of the enrol plugin.';
+$string['privacy:metadata:user_enrolments:modifierid'] = 'The ID of the user who last modified the user enrolment.';
+$string['privacy:metadata:user_enrolments:status'] = 'The status of the user enrolment in a course.';
+$string['privacy:metadata:user_enrolments:tableexplanation'] = 'This is where Enrol management stores enrolled users.';
+$string['privacy:metadata:user_enrolments:timecreated'] = 'The date/time of when the user enrolment was created.';
+$string['privacy:metadata:user_enrolments:timeend'] = 'The date/time of when the user enrolment ends.';
+$string['privacy:metadata:user_enrolments:timestart'] = 'The date/time of when the user enrolment starts.';
+$string['privacy:metadata:user_enrolments:timemodified'] = 'The date/time of when the user enrolment was modified.';
+$string['privacy:metadata:user_enrolments:userid'] = 'The ID of the user.';