--- /dev/null
+<?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_course
+ * @copyright 2018 Adrian Greeve <adrian@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_course\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\request\contextlist;
+use \core_privacy\local\request\approved_contextlist;
+use \core_privacy\local\request\writer;
+use \core_privacy\local\request\transform;
+
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package core_course
+ * @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\provider,
+ \core_privacy\local\request\context_aware_provider,
+ \core_privacy\local\request\plugin\provider,
+ \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_subsystem_link('core_completion', [], 'privacy:metadata:completionsummary');
+ $collection->add_user_preference('coursecat_management_perpage', 'privacy:perpage');
+ 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 {
+ list($join, $where, $params) = \core_completion\privacy\provider::get_course_completion_join_sql($userid, 'cc', 'c.id');
+ $sql = "SELECT ctx.id
+ FROM {context} ctx
+ JOIN {course} c ON ctx.instanceid = c.id AND ctx.contextlevel = :contextcourse
+ {$join}
+ WHERE {$where}";
+ $params['contextcourse'] = CONTEXT_COURSE;
+ $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;
+
+ // Get the course.
+ list($select, $params) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
+ $params['contextcourse'] = CONTEXT_COURSE;
+
+ $sql = "SELECT c.*
+ FROM {course} c
+ JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = :contextcourse
+ WHERE ctx.id $select";
+
+ $courses = $DB->get_recordset_sql($sql, $params);
+ foreach ($courses as $course) {
+ $coursecompletion = \core_completion\privacy\provider::get_course_completion_info($contextlist->get_user(), $course);
+ writer::with_context(\context_course::instance($course->id))->export_data(
+ [get_string('privacy:completionpath', 'course')], (object) $coursecompletion);
+ }
+ $courses->close();
+ }
+
+ /**
+ * Exports course information based on the whole approved context list collection.
+ *
+ * @param \core_privacy\local\request\contextlist_collection $contextcollection The collection of approved context lists.
+ */
+ public static function export_complete_context_data(\core_privacy\local\request\contextlist_collection $completelist) {
+ global $DB;
+
+ $coursecontextids = $DB->get_records('context', ['contextlevel' => CONTEXT_COURSE], '', 'id, instanceid');
+
+ $courseids = [];
+ foreach ($completelist as $component) {
+ foreach ($component->get_contexts() as $context) {
+ if ($context->contextlevel == CONTEXT_USER
+ || $context->contextlevel == CONTEXT_SYSTEM
+ || $context->contextlevel == CONTEXT_COURSECAT) {
+ // Move onto the next context as these will not contain course contexts.
+ continue;
+ }
+ foreach ($coursecontextids as $contextid => $record) {
+ if (stripos($context->path, '/' . $contextid . '/') !== false) {
+ $courseids[$contextid] = $record->instanceid;
+ }
+ }
+ }
+ }
+ if (empty($courseids)) {
+ return;
+ }
+
+ // Export general data for these contexts.
+ list($sql, $params) = $DB->get_in_or_equal($courseids);
+ $sql = 'id ' . $sql;
+ $coursedata = $DB->get_records_select('course', $sql, $params);
+
+ foreach ($coursedata as $course) {
+ $context = \context_course::instance($course->id);
+ $data = (object) [
+ 'fullname' => $course->fullname,
+ 'shortname' => $course->shortname,
+ 'idnumber' => $course->idnumber,
+ 'summary' => writer::with_context($context)->rewrite_pluginfile_urls([], 'course', 'summary', 0, $course->summary),
+ 'format' => get_string('pluginname', 'format_' . $course->format),
+ 'startdate' => transform::datetime($course->startdate),
+ 'enddate' => transform::datetime($course->enddate)
+ ];
+ writer::with_context($context)
+ ->export_area_files([], 'course', 'summary', 0)
+ ->export_area_files([], 'course', 'overviewfiles', 0)
+ ->export_data([], $data);
+ }
+ }
+
+ /**
+ * 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) {
+ $perpage = get_user_preferences('coursecat_management_perpage', null, $userid);
+ if (isset($perpage)) {
+ writer::export_user_preference('core_course',
+ 'coursecat_management_perpage',
+ $perpage,
+ get_string('privacy:perpage', 'course')
+ );
+ }
+ }
+
+ /**
+ * 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) {
+ // Check what context we've been delivered.
+ if ($context->contextlevel == CONTEXT_COURSE) {
+ // Delete course completion data.
+ \core_completion\privacy\provider::delete_completion(null, $context->instanceid);
+ }
+ }
+
+ /**
+ * 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) {
+ foreach ($contextlist as $context) {
+ if ($context->contextlevel == CONTEXT_COURSE) {
+ // Delete course completion data.
+ \core_completion\privacy\provider::delete_completion($contextlist->get_user(), $context->instanceid);
+ }
+ }
+ }
+}
--- /dev/null
+<?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 tests for core_course.
+ *
+ * @package core_course
+ * @category test
+ * @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 . '/completion/tests/fixtures/completion_creation.php');
+
+/**
+ * Unit tests for course/classes/privacy/policy
+ *
+ * @copyright 2018 Adrian Greeve <adrian@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_course_privacy_testcase extends \core_privacy\tests\provider_testcase {
+
+ use completion_creation;
+
+ /**
+ * Test getting the appropriate context for the userid. This should only ever
+ * return the user context for the user id supplied.
+ */
+ public function test_get_contexts_for_userid() {
+ $this->resetAfterTest();
+ $user = $this->getDataGenerator()->create_user();
+ $this->create_course_completion();
+ $this->complete_course($user);
+ $contextlist = \core_course\privacy\provider::get_contexts_for_userid($user->id);
+ $this->assertEquals($this->coursecontext->id, $contextlist->current()->id);
+ }
+
+ /**
+ * Test that user data is exported.
+ */
+ public function test_export_user_data() {
+ $this->resetAfterTest();
+ $user = $this->getDataGenerator()->create_user();
+ $this->create_course_completion();
+ $this->complete_course($user);
+ $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'core_course',
+ [$this->coursecontext->id]);
+ $writer = \core_privacy\local\request\writer::with_context($this->coursecontext);
+ \core_course\privacy\provider::export_user_data($approvedlist);
+ $completiondata = $writer->get_data([get_string('privacy:completionpath', 'course')]);
+ $this->assertEquals('In progress', $completiondata->status);
+ $this->assertCount(2, $completiondata->criteria);
+ }
+
+ public function test_export_complete_context_data() {
+ $this->resetAfterTest();
+ $user = $this->getDataGenerator()->create_user();
+ $course1 = $this->getDataGenerator()->create_course(['fullname' => 'Course 1', 'shortname' => 'C1']);
+ $context1 = context_course::instance($course1->id);
+ $course2 = $this->getDataGenerator()->create_course(['fullname' => 'Course 2', 'shortname' => 'C2']);
+ $context2 = context_course::instance($course2->id);
+ $course3 = $this->getDataGenerator()->create_course(['fullname' => 'Course 3', 'shortname' => 'C3']);
+
+ $this->setUser($user);
+ $modforum = $this->getDataGenerator()->create_module('forum', ['course' => $course1->id]);
+ $modresource = $this->getDataGenerator()->create_module('resource', ['course' => $course2->id]);
+ $modpage = $this->getDataGenerator()->create_module('page', ['course' => $course3->id]);
+ $forumcontext = context_module::instance($modforum->cmid);
+ $resourcecontext = context_module::instance($modresource->cmid);
+
+ $collection = new \core_privacy\local\request\contextlist_collection($user->id);
+ $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'mod_forum', [$forumcontext->id]);
+ $collection->add_contextlist($approvedlist);
+ $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'mod_resource', [$resourcecontext->id]);
+ $collection->add_contextlist($approvedlist);
+
+ $writer = \core_privacy\local\request\writer::with_context(context_system::instance());
+ \core_course\privacy\provider::export_complete_context_data($collection);
+ $courses = $writer->get_data();
+ print_object($courses);
+ // print_object($writer);
+ }
+
+ /**
+ * Test deleting all user data for one 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();
+ $this->create_course_completion();
+ $this->complete_course($user1);
+ $this->complete_course($user2);
+ $records = $DB->get_records('course_modules_completion');
+ $this->assertCount(2, $records);
+ $records = $DB->get_records('course_completion_crit_compl');
+ $this->assertCount(2, $records);
+ \core_course\privacy\provider::delete_data_for_all_users_in_context($this->coursecontext);
+ $records = $DB->get_records('course_modules_completion');
+ $this->assertCount(0, $records);
+ $records = $DB->get_records('course_completion_crit_compl');
+ $this->assertCount(0, $records);
+ }
+
+ /**
+ * Test deleting data for only one user.
+ */
+ public function test_delete_data_for_user() {
+ global $DB;
+ $this->resetAfterTest();
+ $user1 = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $this->create_course_completion();
+ $this->complete_course($user1);
+ $this->complete_course($user2);
+ $records = $DB->get_records('course_modules_completion');
+ $this->assertCount(2, $records);
+ $records = $DB->get_records('course_completion_crit_compl');
+ $this->assertCount(2, $records);
+ $approvedlist = new \core_privacy\local\request\approved_contextlist($user1, 'core_course',
+ [$this->coursecontext->id]);
+ \core_course\privacy\provider::delete_data_for_user($approvedlist);
+ $records = $DB->get_records('course_modules_completion');
+ $this->assertCount(1, $records);
+ $records = $DB->get_records('course_completion_crit_compl');
+ $this->assertCount(1, $records);
+ }
+}
--- /dev/null
+<?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/>.
+
+/**
+ * Strings for component 'course', language 'en', branch 'MOODLE_20_STABLE'
+ *
+ * @package core_course
+ * @copyright 2018 Adrian Greeve <adriangreeve.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['privacy:perpage'] = 'The number of courses to show per page.';
+$string['privacy:completionpath'] = 'Course completion';
+$string['privacy:metadata:completionsummary'] = 'The course contains completion information about the user.';