--- /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 Subsystem implementation for core_role.
+ *
+ * @package core_role
+ * @copyright 2018 Carlos Escobedo <carlos@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core_role\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\transform;
+use \core_privacy\local\request\writer;
+
+/**
+ * Privacy provider for core_role.
+ *
+ * @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,
+ \core_privacy\local\request\subsystem\plugin_provider,
+ \core_privacy\local\request\user_preference_provider {
+
+ /**
+ * Get information about the user data stored by this plugin.
+ *
+ * @param collection $collection An object for storing metadata.
+ * @return collection The metadata.
+ */
+ public static function get_metadata(collection $collection) : collection {
+ $rolecapabilities = [
+ 'roleid' => 'privacy:metadata:role_capabilities:roleid',
+ 'capability' => 'privacy:metadata:role_capabilities:capability',
+ 'permission' => 'privacy:metadata:role_capabilities:permission',
+ 'timemodified' => 'privacy:metadata:role_capabilities:timemodified',
+ 'modifierid' => 'privacy:metadata:role_capabilities:modifierid'
+ ];
+ $roleassignments = [
+ 'roleid' => 'privacy:metadata:role_assignments:roleid',
+ 'userid' => 'privacy:metadata:role_assignments:userid',
+ 'timemodified' => 'privacy:metadata:role_assignments:timemodified',
+ 'modifierid' => 'privacy:metadata:role_assignments:modifierid',
+ 'component' => 'privacy:metadata:role_assignments:component',
+ 'itemid' => 'privacy:metadata:role_assignments:itemid'
+ ];
+ $collection->add_database_table('role_capabilities', $rolecapabilities,
+ 'privacy:metadata:role_capabilities:tableexplanation');
+ $collection->add_database_table('role_assignments', $roleassignments,
+ 'privacy:metadata:role_assignments:tableexplanation');
+
+ $collection->add_user_preference('definerole_showadvanced',
+ 'privacy:metadata:preference:showadvanced');
+
+ 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) {
+ $showadvanced = get_user_preferences('definerole_showadvanced', null, $userid);
+ if ($showadvanced !== null) {
+ writer::export_user_preference('core_role',
+ 'definerole_showadvanced',
+ transform::yesno($showadvanced),
+ get_string('privacy:metadata:preference:showadvanced', 'core_role')
+ );
+ }
+ }
+ /**
+ * Return all contexts for this userid.
+ *
+ * @param int $userid The user ID.
+ * @return contextlist The list of context IDs.
+ */
+ public static function get_contexts_for_userid(int $userid) : contextlist {
+ global $DB;
+
+ $contextlist = new contextlist();
+
+ // The role_capabilities table contains user data.
+ $contexts = [
+ CONTEXT_SYSTEM,
+ CONTEXT_USER,
+ CONTEXT_COURSECAT,
+ CONTEXT_COURSE,
+ CONTEXT_MODULE,
+ CONTEXT_BLOCK
+ ];
+ list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
+ $sql = "SELECT ctx.id
+ FROM {context} ctx
+ JOIN {role_capabilities} rc
+ ON rc.contextid = ctx.id
+ AND ((ctx.contextlevel {$insql} AND rc.modifierid = :modifierid)
+ OR (ctx.contextlevel = :contextlevel AND ctx.instanceid = :userid))";
+ $params = [
+ 'modifierid' => $userid,
+ 'contextlevel' => CONTEXT_USER,
+ 'userid' => $userid
+ ];
+ $params += $inparams;
+
+ $contextlist->add_from_sql($sql, $params);
+
+ // The role_assignments table contains user data.
+ $contexts = [
+ CONTEXT_SYSTEM,
+ CONTEXT_USER,
+ CONTEXT_COURSECAT,
+ CONTEXT_COURSE,
+ CONTEXT_MODULE,
+ CONTEXT_BLOCK
+ ];
+ list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
+ $params = [
+ 'userid' => $userid,
+ 'modifierid' => $userid
+ ];
+ $params += $inparams;
+ $sql = "SELECT ctx.id
+ FROM {role_assignments} ra
+ JOIN {context} ctx
+ ON ctx.id = ra.contextid
+ AND ctx.contextlevel {$insql}
+ WHERE (ra.userid = :userid
+ OR ra.modifierid = :modifierid)
+ AND ra.component != 'tool_cohortroles'";
+ $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 list of approved contexts for a user.
+ */
+ public static function export_user_data(approved_contextlist $contextlist) {
+ global $DB;
+
+ if (empty($contextlist)) {
+ return;
+ }
+
+ $rolesnames = self::get_roles_name();
+ $userid = $contextlist->get_user()->id;
+ $ctxfields = \context_helper::get_preload_record_columns_sql('ctx');
+ list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
+
+ // Role Assignments export data.
+ $contexts = [
+ CONTEXT_SYSTEM,
+ CONTEXT_USER,
+ CONTEXT_COURSECAT,
+ CONTEXT_COURSE,
+ CONTEXT_MODULE,
+ CONTEXT_BLOCK
+ ];
+ list($inctxsql, $ctxparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
+ $sql = "SELECT ra.id, ra.contextid, ra.roleid, ra.userid, ra.timemodified, ra.modifierid, $ctxfields
+ FROM {role_assignments} ra
+ JOIN {context} ctx
+ ON ctx.id = ra.contextid
+ AND ctx.contextlevel {$inctxsql}
+ AND (ra.userid = :userid OR ra.modifierid = :modifierid)
+ AND ra.component != 'tool_cohortroles'
+ JOIN {role} r
+ ON r.id = ra.roleid
+ WHERE ctx.id {$insql}";
+ $params = ['userid' => $userid, 'modifierid' => $userid];
+ $params += $inparams;
+ $params += $ctxparams;
+ $assignments = $DB->get_recordset_sql($sql, $params);
+ foreach ($assignments as $assignment) {
+ \context_helper::preload_from_record($assignment);
+ $alldata[$assignment->contextid][$rolesnames[$assignment->roleid]][] = (object)[
+ 'timemodified' => transform::datetime($assignment->timemodified),
+ 'userid' => transform::user($assignment->userid),
+ 'modifierid' => transform::user($assignment->modifierid)
+ ];
+ }
+ $assignments->close();
+ if (!empty($alldata)) {
+ array_walk($alldata, function($roledata, $contextid) {
+ $context = \context::instance_by_id($contextid);
+ array_walk($roledata, function($data, $rolename) use ($context) {
+ writer::with_context($context)->export_data(
+ [get_string('privacy:metadata:role_assignments', 'core_role'), $rolename],
+ (object)$data);
+ });
+ });
+ unset($alldata);
+ }
+
+ // Role Capabilities export data.
+ $strpermissions = self::get_permissions_name();
+ $contexts = [
+ CONTEXT_SYSTEM,
+ CONTEXT_USER,
+ CONTEXT_COURSECAT,
+ CONTEXT_COURSE,
+ CONTEXT_MODULE,
+ CONTEXT_BLOCK
+ ];
+ list($inctxsql, $ctxparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
+ $sql = "SELECT rc.id, rc.contextid, rc.capability, rc.permission, rc.timemodified, rc.roleid, $ctxfields
+ FROM {context} ctx
+ JOIN {role_capabilities} rc
+ ON rc.contextid = ctx.id
+ AND ((ctx.contextlevel {$inctxsql} AND rc.modifierid = :modifierid)
+ OR (ctx.contextlevel = :contextlevel AND ctx.instanceid = :userid))
+ WHERE ctx.id {$insql}";
+ $params = [
+ 'modifierid' => $userid,
+ 'contextlevel' => CONTEXT_USER,
+ 'userid' => $userid
+ ];
+ $params += $inparams;
+ $params += $ctxparams;
+ $capabilities = $DB->get_recordset_sql($sql, $params);
+ foreach ($capabilities as $capability) {
+ \context_helper::preload_from_record($capability);
+ $alldata[$capability->contextid][$rolesnames[$capability->roleid]][] = (object)[
+ 'timemodified' => transform::datetime($capability->timemodified),
+ 'capability' => $capability->capability,
+ 'permission' => $strpermissions[$capability->permission]
+ ];
+ }
+ $capabilities->close();
+ if (!empty($alldata)) {
+ array_walk($alldata, function($capdata, $contextid) {
+ $context = \context::instance_by_id($contextid);
+ array_walk($capdata, function($data, $rolename) use ($context) {
+ writer::with_context($context)->export_data(
+ [get_string('privacy:metadata:role_capabilities', 'core_role'), $rolename],
+ (object)$data);
+ });
+ });
+ }
+ }
+ /**
+ * Exports the data relating to tool_cohortroles component on role assignments by
+ * Assign user roles to cohort feature.
+ *
+ * @param int $userid The user ID.
+ */
+ public static function export_user_role_to_cohort(int $userid) {
+ global $DB;
+
+ $rolesnames = self::get_roles_name();
+ $sql = "SELECT ra.id, ra.contextid, ra.roleid, ra.userid, ra.timemodified, ra.modifierid, r.id as roleid
+ FROM {role_assignments} ra
+ JOIN {context} ctx
+ ON ctx.id = ra.contextid
+ AND ctx.contextlevel = :contextlevel
+ AND ra.component = 'tool_cohortroles'
+ JOIN {role} r
+ ON r.id = ra.roleid
+ WHERE ctx.instanceid = :instanceid
+ OR ra.userid = :userid";
+ $params = ['userid' => $userid, 'instanceid' => $userid, 'contextlevel' => CONTEXT_USER];
+ $assignments = $DB->get_recordset_sql($sql, $params);
+ foreach ($assignments as $assignment) {
+ $alldata[$assignment->contextid][$rolesnames[$assignment->roleid]][] = (object)[
+ 'timemodified' => transform::datetime($assignment->timemodified),
+ 'userid' => transform::user($assignment->userid),
+ 'modifierid' => transform::user($assignment->modifierid)
+ ];
+ }
+ $assignments->close();
+ if (!empty($alldata)) {
+ array_walk($alldata, function($roledata, $contextid) {
+ $context = \context::instance_by_id($contextid);
+ array_walk($roledata, function($data, $rolename) use ($context) {
+ writer::with_context($context)->export_related_data(
+ [get_string('privacy:metadata:role_cohortroles', 'core_role'), $rolename], 'cohortroles',
+ (object)$data);
+ });
+ });
+ }
+ }
+ /**
+ * Delete all user data for this context.
+ *
+ * @param \context $context The context to delete data for.
+ */
+ public static function delete_data_for_all_users_in_context(\context $context) {
+ global $DB;
+
+ // Don't remove data from role_capabilities.
+ // Because this data affects the whole Moodle, there are override capabilities.
+ // Don't belong to the modifier user.
+
+ // Remove data from role_assignments.
+ if (empty($context)) {
+ return;
+ }
+ $DB->delete_records('role_assignments', ['contextid' => $context->id]);
+ }
+ /**
+ * Delete all user data for this user only.
+ *
+ * @param approved_contextlist $contextlist The list of approved contexts for a user.
+ */
+ public static function delete_data_for_user(approved_contextlist $contextlist) {
+ global $DB;
+
+ // Don't remove data from role_capabilities.
+ // Because this data affects the whole Moodle, there are override capabilities.
+ // Don't belong to the modifier user.
+
+ // Remove data from role_assignments.
+ if (empty($contextlist->count())) {
+ return;
+ }
+ $userid = $contextlist->get_user()->id;
+ foreach ($contextlist->get_contexts() as $context) {
+ // Only delete the roles assignments where the user is assigned in all contexts.
+ $DB->delete_records('role_assignments', ['userid' => $userid, 'contextid' => $context->id]);
+ }
+ }
+ /**
+ * Delete user entries in role_assignments related to the feature
+ * Assign user roles to cohort feature.
+ *
+ * @param int $userid The user ID.
+ */
+ public static function delete_user_role_to_cohort(int $userid) {
+ global $DB;
+
+ // Delete entries where userid is a mentor by tool_cohortroles.
+ $DB->delete_records('role_assignments', ['userid' => $userid, 'component' => 'tool_cohortroles']);
+ }
+ /**
+ * Get all the localised roles name in a simple array.
+ *
+ * @return array Array of name of the roles by roleid.
+ */
+ protected static function get_roles_name() {
+ $roles = role_fix_names(get_all_roles(), \context_system::instance(), ROLENAME_ORIGINAL);
+ $rolesnames = array();
+ foreach ($roles as $role) {
+ $rolesnames[$role->id] = $role->localname;
+ }
+ return $rolesnames;
+ }
+ /**
+ * Get all the permissions name in a simple array.
+ *
+ * @return array Array of permissions name.
+ */
+ protected static function get_permissions_name() {
+ $strpermissions = array(
+ CAP_INHERIT => get_string('inherit', 'role'),
+ CAP_ALLOW => get_string('allow', 'role'),
+ CAP_PREVENT => get_string('prevent', 'role'),
+ CAP_PROHIBIT => get_string('prohibit', 'role')
+ );
+ return $strpermissions;
+ }
+}
\ No newline at end of file
--- /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 test for core_role
+ *
+ * @package core_role
+ * @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_role\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;
+use \tool_cohortroles\api;
+
+/**
+ * Privacy test for core_role
+ *
+ * @copyright 2018 Carlos Escobedo <carlos@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_role_privacy_testcase extends provider_testcase {
+ /**
+ * Test to check export_user_preferences.
+ * returns user preferences data.
+ */
+ public function test_export_user_preferences() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ $user = $this->getDataGenerator()->create_user();
+ $this->setUser($user);
+ $showadvanced = 1;
+ set_user_preference('definerole_showadvanced', $showadvanced);
+ provider::export_user_preferences($user->id);
+ $writer = writer::with_context(\context_system::instance());
+ $prefs = $writer->get_user_preferences('core_role');
+ $this->assertEquals(transform::yesno($showadvanced), transform::yesno($prefs->definerole_showadvanced->value));
+ $this->assertEquals(get_string('privacy:metadata:preference:showadvanced', 'core_role'),
+ $prefs->definerole_showadvanced->description);
+ }
+ /**
+ * Check all contexts are returned if there is any user data for this user.
+ */
+ public function test_get_contexts_for_userid() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ $user = $this->getDataGenerator()->create_user();
+ $this->assertEmpty(provider::get_contexts_for_userid($user->id));
+
+ $user2 = $this->getDataGenerator()->create_user();
+ $usercontext2 = \context_user::instance($user2->id);
+ $course = $this->getDataGenerator()->create_course();
+ $course2 = $this->getDataGenerator()->create_course();
+ $coursecat = $this->getDataGenerator()->create_category();
+ $cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
+ $cmcontext = \context_module::instance($cm->cmid);
+ $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
+ $cmcontext2 = \context_module::instance($page->cmid);
+ $coursecontext = \context_course::instance($course->id);
+ $coursecontext2 = \context_course::instance($course2->id);
+ $coursecatcontext = \context_coursecat::instance($coursecat->id);
+ $systemcontext = \context_system::instance();
+ $block = $this->getDataGenerator()->create_block('online_users');
+ $blockcontext = \context_block::instance($block->id);
+
+ $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
+ $manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
+
+ // Role assignments, where the user is assigned.
+ role_assign($student->id, $user->id, $cmcontext2->id);
+ role_assign($student->id, $user->id, $coursecontext2->id);
+ role_assign($student->id, $user->id, $blockcontext->id);
+ role_assign($manager->id, $user->id, $usercontext2->id);
+ // Role assignments, where the user makes assignments.
+ $this->setUser($user);
+ role_assign($student->id, $user2->id, $coursecontext->id);
+ role_assign($manager->id, $user2->id, $coursecatcontext->id);
+ role_assign($manager->id, $user2->id, $systemcontext->id);
+
+ // Role capabilities.
+ $this->setUser($user);
+ $result = assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $cmcontext->id);
+
+ $contextlist = provider::get_contexts_for_userid($user->id)->get_contextids();
+ $this->assertCount(8, $contextlist);
+ $this->assertTrue(in_array($cmcontext->id, $contextlist));
+ }
+
+ /**
+ * Test that user data is exported correctly.
+ */
+ public function test_export_user_data() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ $user = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $usercontext2 = \context_user::instance($user2->id);
+ $course = $this->getDataGenerator()->create_course();
+ $course2 = $this->getDataGenerator()->create_course();
+ $coursecat = $this->getDataGenerator()->create_category();
+ $cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
+ $cmcontext = \context_module::instance($cm->cmid);
+ $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
+ $cmcontext2 = \context_module::instance($page->cmid);
+ $coursecontext = \context_course::instance($course->id);
+ $coursecontext2 = \context_course::instance($course2->id);
+ $coursecatcontext = \context_coursecat::instance($coursecat->id);
+ $systemcontext = \context_system::instance();
+ $block = $this->getDataGenerator()->create_block('online_users');
+ $blockcontext = \context_block::instance($block->id);
+
+ $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
+ $manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
+ $rolesnames = self::get_roles_name();
+
+ $subcontextstudent = [
+ get_string('privacy:metadata:role_assignments', 'core_role'),
+ $rolesnames[$student->id]
+ ];
+ $subcontextmanager = [
+ get_string('privacy:metadata:role_assignments', 'core_role'),
+ $rolesnames[$manager->id]
+ ];
+ $subcontextrc = [
+ get_string('privacy:metadata:role_capabilities', 'core_role'),
+ $rolesnames[$student->id]
+ ];
+
+ // Test over role assignments.
+ // Where the user is assigned.
+ role_assign($student->id, $user->id, $cmcontext2->id);
+ role_assign($student->id, $user->id, $coursecontext2->id);
+ role_assign($student->id, $user->id, $blockcontext->id);
+ role_assign($manager->id, $user->id, $usercontext2->id);
+ // Where the user makes assignments.
+ $this->setUser($user);
+ role_assign($manager->id, $user2->id, $coursecatcontext->id);
+ role_assign($manager->id, $user2->id, $systemcontext->id);
+
+ // Test overridable roles in module, course, category, user, system and block.
+ assign_capability('moodle/backup:backupactivity', CAP_ALLOW, $student->id, $cmcontext->id, true);
+ assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $coursecontext->id, true);
+ assign_capability('moodle/category:manage', CAP_ALLOW, $student->id, $coursecatcontext->id, true);
+ assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $systemcontext->id, true);
+ assign_capability('moodle/block:edit', CAP_ALLOW, $student->id, $blockcontext->id, true);
+ assign_capability('moodle/competency:evidencedelete', CAP_ALLOW, $student->id, $usercontext2->id, true);
+
+ // Retrieve the user's context ids.
+ $contextlist = provider::get_contexts_for_userid($user->id);
+ $approvedcontextlist = new approved_contextlist($user, 'core_role', $contextlist->get_contextids());
+
+ $strpermissions = array(
+ CAP_INHERIT => get_string('inherit', 'role'),
+ CAP_ALLOW => get_string('allow', 'role'),
+ CAP_PREVENT => get_string('prevent', 'role'),
+ CAP_PROHIBIT => get_string('prohibit', 'role')
+ );
+ // Retrieve role capabilities and role assignments.
+ provider::export_user_data($approvedcontextlist);
+ foreach ($contextlist as $context) {
+ $writer = writer::with_context($context);
+ $this->assertTrue($writer->has_any_data());
+ if ($context->contextlevel == CONTEXT_MODULE) {
+ if ($data = $writer->get_data($subcontextstudent)) {
+ $this->assertEquals($user->id, reset($data)->userid);
+ }
+ if ($data = $writer->get_data($subcontextrc)) {
+ $this->assertEquals('moodle/backup:backupactivity', reset($data)->capability);
+ $this->assertEquals($strpermissions[CAP_ALLOW], reset($data)->permission);
+ }
+ }
+ if ($context->contextlevel == CONTEXT_COURSE) {
+ if ($data = $writer->get_data($subcontextstudent)) {
+ $this->assertEquals($user->id, reset($data)->userid);
+ }
+ if ($data = $writer->get_data($subcontextrc)) {
+ $this->assertEquals('moodle/backup:backupcourse', reset($data)->capability);
+ }
+ }
+ if ($context->contextlevel == CONTEXT_COURSECAT) {
+ if ($data = $writer->get_data($subcontextmanager)) {
+ $this->assertEquals($user->id, reset($data)->modifierid);
+ }
+ if ($data = $writer->get_data($subcontextrc)) {
+ $this->assertEquals('moodle/category:manage', reset($data)->capability);
+ }
+ }
+ if ($context->contextlevel == CONTEXT_SYSTEM) {
+ if ($data = $writer->get_data($subcontextmanager)) {
+ $this->assertEquals($user->id, reset($data)->modifierid);
+ }
+ if ($data = $writer->get_data($subcontextrc)) {
+ $this->assertEquals('moodle/backup:backupcourse', reset($data)->capability);
+ }
+ }
+ if ($context->contextlevel == CONTEXT_BLOCK) {
+ if ($data = $writer->get_data($subcontextstudent)) {
+ $this->assertEquals($user->id, reset($data)->userid);
+ }
+ if ($data = $writer->get_data($subcontextrc)) {
+ $this->assertEquals('moodle/block:edit', reset($data)->capability);
+ }
+ }
+ if ($context->contextlevel == CONTEXT_USER) {
+ if ($data = $writer->get_data($subcontextmanager)) {
+ $this->assertEquals($user->id, reset($data)->userid);
+ }
+ if ($data = $writer->get_data($subcontextrc)) {
+ $this->assertEquals('moodle/competency:evidencedelete', reset($data)->capability);
+ }
+ }
+ }
+ }
+ /**
+ * Test for provider::delete_data_for_all_users_in_context().
+ */
+ public function test_delete_data_for_all_users_in_context() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ $user = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $usercontext2 = \context_user::instance($user2->id);
+ $user3 = $this->getDataGenerator()->create_user();
+ $course = $this->getDataGenerator()->create_course();
+ $coursecontext = \context_course::instance($course->id);
+ $coursecat = $this->getDataGenerator()->create_category();
+ $coursecatcontext = \context_coursecat::instance($coursecat->id);
+ $systemcontext = \context_system::instance();
+ $cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
+ $cmcontext = \context_module::instance($cm->cmid);
+ $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
+ $manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
+ $block = $this->getDataGenerator()->create_block('online_users');
+ $blockcontext = \context_block::instance($block->id);
+
+ // Role assignments CONTEXT_COURSE.
+ role_assign($student->id, $user->id, $coursecontext->id);
+ role_assign($student->id, $user2->id, $coursecontext->id);
+ role_assign($student->id, $user3->id, $coursecontext->id);
+ $count = $DB->count_records('role_assignments', ['contextid' => $coursecontext->id]);
+ $this->assertEquals(3, $count);
+ // Role assignments CONTEXT_COURSECAT.
+ role_assign($student->id, $user2->id, $coursecatcontext->id);
+ role_assign($student->id, $user3->id, $coursecatcontext->id);
+ $count = $DB->count_records('role_assignments', ['contextid' => $coursecatcontext->id]);
+ $this->assertEquals(2, $count);
+ // Role assignments CONTEXT_SYSTEM.
+ role_assign($student->id, $user->id, $systemcontext->id);
+ $count = $DB->count_records('role_assignments', ['contextid' => $systemcontext->id]);
+ $this->assertEquals(1, $count);
+ // Role assignments CONTEXT_MODULE.
+ role_assign($student->id, $user->id, $cmcontext->id);
+ $count = $DB->count_records('role_assignments', ['contextid' => $cmcontext->id]);
+ $this->assertEquals(1, $count);
+ // Role assigments CONTEXT_BLOCK.
+ role_assign($student->id, $user->id, $blockcontext->id);
+ $count = $DB->count_records('role_assignments', ['contextid' => $blockcontext->id]);
+ $this->assertEquals(1, $count);
+ // Role assigments CONTEXT_USER.
+ role_assign($manager->id, $user->id, $usercontext2->id);
+ $count = $DB->count_records('role_assignments', ['contextid' => $usercontext2->id]);
+ $this->assertEquals(1, $count);
+
+ // Delete data based on CONTEXT_COURSE context.
+ provider::delete_data_for_all_users_in_context($coursecontext);
+ // After deletion, the role_assignments entries for this context should have been deleted.
+ $count = $DB->count_records('role_assignments', ['contextid' => $coursecontext->id]);
+ $this->assertEquals(0, $count);
+ // Check it is not removing data on other contexts.
+ $count = $DB->count_records('role_assignments', ['contextid' => $coursecatcontext->id]);
+ $this->assertEquals(2, $count);
+ $count = $DB->count_records('role_assignments', ['contextid' => $systemcontext->id]);
+ $this->assertEquals(1, $count);
+ $count = $DB->count_records('role_assignments', ['contextid' => $cmcontext->id]);
+ $this->assertEquals(1, $count);
+ // Delete data based on CONTEXT_COURSECAT context.
+ provider::delete_data_for_all_users_in_context($coursecatcontext);
+ // After deletion, the role_assignments entries for this context should have been deleted.
+ $count = $DB->count_records('role_assignments', ['contextid' => $coursecatcontext->id]);
+ $this->assertEquals(0, $count);
+ // Delete data based on CONTEXT_SYSTEM context.
+ provider::delete_data_for_all_users_in_context($systemcontext);
+ // After deletion, the role_assignments entries for this context should have been deleted.
+ $count = $DB->count_records('role_assignments', ['contextid' => $systemcontext->id]);
+ $this->assertEquals(0, $count);
+ // Delete data based on CONTEXT_MODULE context.
+ provider::delete_data_for_all_users_in_context($cmcontext);
+ // After deletion, the role_assignments entries for this context should have been deleted.
+ $count = $DB->count_records('role_assignments', ['contextid' => $cmcontext->id]);
+ $this->assertEquals(0, $count);
+ // Delete data based on CONTEXT_BLOCK context.
+ provider::delete_data_for_all_users_in_context($usercontext2);
+ // After deletion, the role_assignments entries for this context should have been deleted.
+ $count = $DB->count_records('role_assignments', ['contextid' => $usercontext2->id]);
+ $this->assertEquals(0, $count);
+ }
+ /**
+ * Test for provider::delete_data_for_user().
+ */
+ public function test_delete_data_for_user() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ $user = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $usercontext2 = \context_user::instance($user2->id);
+ $user3 = $this->getDataGenerator()->create_user();
+ $usercontext3 = \context_user::instance($user3->id);
+ $course = $this->getDataGenerator()->create_course();
+ $course2 = $this->getDataGenerator()->create_course();
+ $course3 = $this->getDataGenerator()->create_course();
+ $coursecontext = \context_course::instance($course->id);
+ $coursecontext2 = \context_course::instance($course2->id);
+ $coursecontext3 = \context_course::instance($course3->id);
+ $coursecat = $this->getDataGenerator()->create_category();
+ $coursecatcontext = \context_coursecat::instance($coursecat->id);
+ $systemcontext = \context_system::instance();
+ $cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
+ $cmcontext = \context_module::instance($cm->cmid);
+ $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
+ $manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
+ $block = $this->getDataGenerator()->create_block('online_users');
+ $blockcontext = \context_block::instance($block->id);
+
+ // Role assignments, Where the user is assigned.
+ role_assign($student->id, $user->id, $coursecontext->id);
+ role_assign($student->id, $user->id, $coursecontext2->id);
+ role_assign($student->id, $user->id, $coursecatcontext->id);
+ role_assign($student->id, $user->id, $cmcontext->id);
+ role_assign($student->id, $user->id, $systemcontext->id);
+ role_assign($student->id, $user->id, $blockcontext->id);
+ role_assign($manager->id, $user->id, $usercontext2->id);
+ role_assign($manager->id, $user->id, $usercontext3->id);
+ $count = $DB->count_records('role_assignments', ['userid' => $user->id]);
+ $this->assertEquals(8, $count);
+ // Role assignments, where the user makes assignments.
+ $this->setUser($user);
+ role_assign($student->id, $user2->id, $coursecontext3->id);
+ role_assign($student->id, $user3->id, $coursecontext3->id);
+ $count = $DB->count_records('role_assignments', ['modifierid' => $user->id]);
+ $this->assertEquals(2, $count);
+
+ $contextlist = provider::get_contexts_for_userid($user->id);
+ $approvedcontextlist = new approved_contextlist($user, 'core_role', $contextlist->get_contextids());
+ provider::delete_data_for_user($approvedcontextlist);
+ // After deletion, the role_assignments assigned to the user should have been deleted.
+ $count = $DB->count_records('role_assignments', ['userid' => $user->id]);
+ $this->assertEquals(0, $count);
+ // After deletion, the role_assignments assigned by the user should not have been deleted.
+ $count = $DB->count_records('role_assignments', ['modifierid' => $user->id]);
+ $this->assertEquals(2, $count);
+ }
+ /**
+ * Export for a user with a key against a script where no instance is specified.
+ */
+ public function test_export_user_role_to_cohort() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ // Assign user roles to cohort.
+ $user = $this->getDataGenerator()->create_user();
+ $contextuser = \context_user::instance($user->id);
+ $teacher = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
+ $cohort = $this->getDataGenerator()->create_cohort();
+ $userassignover = $this->getDataGenerator()->create_user();
+ $contextuserassignover = \context_user::instance($userassignover->id);
+ cohort_add_member($cohort->id, $userassignover->id);
+ $this->setAdminUser();
+ $params = (object) array(
+ 'userid' => $user->id,
+ 'roleid' => $teacher->id,
+ 'cohortid' => $cohort->id
+ );
+ api::create_cohort_role_assignment($params);
+ api::sync_all_cohort_roles();
+ $rolesnames = self::get_roles_name();
+ $subcontextteacher = [
+ get_string('privacy:metadata:role_cohortroles', 'core_role'),
+ $rolesnames[$teacher->id]
+ ];
+ // Test User is assigned role teacher to cohort.
+ provider::export_user_role_to_cohort($user->id);
+ $writer = writer::with_context($contextuserassignover);
+ $this->assertTrue($writer->has_any_data());
+ $exported = $writer->get_related_data($subcontextteacher, 'cohortroles');
+ $this->assertEquals($user->id, reset($exported)->userid);
+
+ // Test User is member of a cohort which User2 is assigned to role to this cohort.
+ $user2 = $this->getDataGenerator()->create_user();
+ $cohort2 = $this->getDataGenerator()->create_cohort();
+ cohort_add_member($cohort2->id, $user->id);
+ $params = (object) array(
+ 'userid' => $user2->id,
+ 'roleid' => $teacher->id,
+ 'cohortid' => $cohort2->id
+ );
+ api::create_cohort_role_assignment($params);
+ api::sync_all_cohort_roles();
+ provider::export_user_role_to_cohort($user->id);
+ $writer = writer::with_context($contextuser);
+ $this->assertTrue($writer->has_any_data());
+ $exported = $writer->get_related_data($subcontextteacher, 'cohortroles');
+ $this->assertEquals($user2->id, reset($exported)->userid);
+ }
+ /**
+ * Test for provider::delete_user_role_to_cohort().
+ */
+ public function test_delete_user_role_to_cohort() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ // Assign user roles to cohort.
+ $user = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $user3 = $this->getDataGenerator()->create_user();
+ $user4 = $this->getDataGenerator()->create_user();
+ $teacher = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
+ $cohort = $this->getDataGenerator()->create_cohort();
+ cohort_add_member($cohort->id, $user2->id);
+ cohort_add_member($cohort->id, $user3->id);
+ cohort_add_member($cohort->id, $user4->id);
+ $this->setAdminUser();
+ $params = (object) array(
+ 'userid' => $user->id,
+ 'roleid' => $teacher->id,
+ 'cohortid' => $cohort->id
+ );
+ api::create_cohort_role_assignment($params);
+ api::sync_all_cohort_roles();
+
+ $count = $DB->count_records('role_assignments', ['userid' => $user->id, 'component' => 'tool_cohortroles']);
+ $this->assertEquals(3, $count);
+
+ provider::delete_user_role_to_cohort($user->id);
+ $count = $DB->count_records('role_assignments', ['userid' => $user->id, 'component' => 'tool_cohortroles']);
+ $this->assertEquals(0, $count);
+ }
+ /**
+ * Supoort function to get all the localised roles name
+ * in a simple array for testing.
+ *
+ * @return array Array of name of the roles by roleid.
+ */
+ protected static function get_roles_name() {
+ $roles = role_fix_names(get_all_roles(), \context_system::instance(), ROLENAME_ORIGINAL);
+ $rolesnames = array();
+ foreach ($roles as $role) {
+ $rolesnames[$role->id] = $role->localname;
+ }
+ return $rolesnames;
+ }
+}
\ No newline at end of file
$setting->set_force_ltr(true);
$temp->add($setting);
+ // See {@link https://gdpr-info.eu/art-8-gdpr/}.
+ $ageofdigitalconsentmap = implode(PHP_EOL, [
+ '*, 16',
+ 'AT, 14',
+ 'ES, 14',
+ 'US, 13'
+ ]);
$setting = new admin_setting_agedigitalconsentmap('agedigitalconsentmap',
new lang_string('ageofdigitalconsentmap', 'admin'),
new lang_string('ageofdigitalconsentmap_desc', 'admin'),
- // See {@link https://gdpr-info.eu/art-8-gdpr/}.
- implode(PHP_EOL, [
- '*, 16',
- 'AT, 14',
- 'CZ, 13',
- 'DE, 14',
- 'DK, 13',
- 'ES, 13',
- 'FI, 15',
- 'GB, 13',
- 'HU, 14',
- 'IE, 13',
- 'LT, 16',
- 'LU, 16',
- 'NL, 16',
- 'PL, 13',
- 'SE, 13',
- ]),
+ $ageofdigitalconsentmap,
PARAM_RAW
);
$temp->add($setting);
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_analytics'; // Full name of the plugin (used for diagnostics).
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300;
-$plugin->requires = 2017110800;
+$plugin->version = 2018051400;
+$plugin->requires = 2018050800;
$plugin->component = 'tool_assignmentupgrade';
-$plugin->dependencies = array('mod_assign' => 2017110800);
+$plugin->dependencies = array('mod_assign' => 2018050800);
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300;
-$plugin->requires = 2017110800;
+$plugin->version = 2018051400;
+$plugin->requires = 2018050800;
$plugin->component = 'tool_availabilityconditions';
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires = 2017110800; // Requires this Moodle version
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires = 2018050800; // Requires this Moodle version
$plugin->component = 'tool_behat'; // Full name of the plugin (used for diagnostics)
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_capability'; // Full name of the plugin (used for diagnostics).
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_cohortroles'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = array(
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300;
-$plugin->requires = 2017110800;
+$plugin->version = 2018051400;
+$plugin->requires = 2018050800;
$plugin->component = 'tool_customlang'; // Full name of the plugin (used for diagnostics)
use required_capability_exception;
use stdClass;
use tool_dataprivacy\external\data_request_exporter;
+use tool_dataprivacy\local\helper;
use tool_dataprivacy\task\initiate_data_request_task;
use tool_dataprivacy\task\process_data_request_task;
$datarequest = new data_request();
// The user the request is being made for.
$datarequest->set('userid', $foruser);
+
+ $requestinguser = $USER->id;
+ // Check when the user is making a request on behalf of another.
+ if ($requestinguser != $foruser) {
+ if (self::is_site_dpo($requestinguser)) {
+ // The user making the request is a DPO. Should be fine.
+ $datarequest->set('dpo', $requestinguser);
+ } else {
+ // If not a DPO, only users with the capability to make data requests for the user should be allowed.
+ // (e.g. users with the Parent role, etc).
+ if (!api::can_create_data_request_for_user($foruser)) {
+ $forusercontext = \context_user::instance($foruser);
+ throw new required_capability_exception($forusercontext,
+ 'tool/dataprivacy:makedatarequestsforchildren', 'nopermissions', '');
+ }
+ }
+ }
// The user making the request.
- $datarequest->set('requestedby', $USER->id);
+ $datarequest->set('requestedby', $requestinguser);
// Set status.
$datarequest->set('status', self::DATAREQUEST_STATUS_PENDING);
// Set request type.
* @throws dml_exception
*/
public static function get_data_requests($userid = 0) {
- global $USER;
+ global $DB, $USER;
$results = [];
$sort = 'status ASC, timemodified ASC';
if ($userid) {
// Get the data requests for the user or data requests made by the user.
- $select = "userid = :userid OR requestedby = :requestedby";
+ $select = "(userid = :userid OR requestedby = :requestedby)";
$params = [
'userid' => $userid,
'requestedby' => $userid
];
+
+ // Build a list of user IDs that the user is allowed to make data requests for.
+ // Of course, the user should be included in this list.
+ $alloweduserids = [$userid];
+ // Get any users that the user can make data requests for.
+ if ($children = helper::get_children_of_user($userid)) {
+ // Get the list of user IDs of the children and merge to the allowed user IDs.
+ $alloweduserids = array_merge($alloweduserids, array_keys($children));
+ }
+ list($insql, $inparams) = $DB->get_in_or_equal($alloweduserids, SQL_PARAMS_NAMED);
+ $select .= " AND userid $insql";
+ $params = array_merge($params, $inparams);
+
$results = data_request::get_records_select($select, $params, $sort);
} else {
// If the current user is one of the site's Data Protection Officers, then fetch all data requests.
* @param int $requestid The request identifier.
* @param int $status The request status.
* @param int $dpoid The user ID of the Data Protection Officer
+ * @param string $comment The comment about the status update.
* @return bool
* @throws invalid_persistent_exception
* @throws coding_exception
*/
- public static function update_request_status($requestid, $status, $dpoid = 0) {
+ public static function update_request_status($requestid, $status, $dpoid = 0, $comment = '') {
// Update the request.
$datarequest = new data_request($requestid);
$datarequest->set('status', $status);
if ($dpoid) {
$datarequest->set('dpo', $dpoid);
}
+ $datarequest->set('dpocomment', $comment);
return $datarequest->update();
}
return message_send($message);
}
+ /**
+ * Checks whether a non-DPO user can make a data request for another user.
+ *
+ * @param int $user The user ID of the target user.
+ * @param int $requester The user ID of the user making the request.
+ * @return bool
+ * @throws coding_exception
+ */
+ public static function can_create_data_request_for_user($user, $requester = null) {
+ $usercontext = \context_user::instance($user);
+ return has_capability('tool/dataprivacy:makedatarequestsforchildren', $usercontext, $requester);
+ }
+
/**
* Creates a new data purpose.
*
public static function update_purpose(stdClass $record) {
self::check_can_manage_data_registry();
+ if (!isset($record->sensitivedatareasons)) {
+ $record->sensitivedatareasons = '';
+ }
+
$purpose = new purpose($record->id);
$purpose->from_record($record);
* @param int $status the status to set the contexts to.
*/
public static function add_request_contexts_with_status(contextlist_collection $clcollection, int $requestid, int $status) {
+ $request = new data_request($requestid);
foreach ($clcollection as $contextlist) {
// Convert the \core_privacy\local\request\contextlist into a contextlist persistent and store it.
$clp = \tool_dataprivacy\contextlist::from_contextlist($contextlist);
// Store the associated contexts in the contextlist.
foreach ($contextlist->get_contextids() as $contextid) {
+ if ($request->get('type') == static::DATAREQUEST_TYPE_DELETE) {
+ $context = \context::instance_by_id($contextid);
+ if (($purpose = static::get_effective_context_purpose($context)) && !empty($purpose->get('protected'))) {
+ continue;
+ }
+ }
$context = new contextlist_context();
$context->set('contextid', $contextid)
->set('contextlistid', $contextlistid)
}
$contexts = [];
}
+
$contexts[] = $record->contextid;
$lastcomponent = $record->component;
}
* @return \context|false
*/
protected function delete_expired_context(\core_privacy\manager $privacymanager, \tool_dataprivacy\expired_context $expiredctx) {
- if (!$context = parent::delete_expired_context($privacymanager, $expiredctx)) {
+ $context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
+ if (!$context) {
+ api::delete_expired_context($expiredctx->get('contextid'));
return false;
}
- // Delete the user.
+ if (!PHPUNIT_TEST) {
+ mtrace('Deleting context ' . $context->id . ' - ' .
+ shorten_text($context->get_context_name(true, true)));
+ }
+
+ // To ensure that all user data is deleted, instead of deleting by context, we run through and collect any stray
+ // contexts for the user that may still exist and call delete_data_for_user().
$user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
+ $approvedlistcollection = new \core_privacy\local\request\contextlist_collection($user->id);
+ $contextlistcollection = $privacymanager->get_contexts_for_userid($user->id);
+
+ foreach ($contextlistcollection as $contextlist) {
+ $approvedlistcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist(
+ $user,
+ $contextlist->get_component(),
+ $contextlist->get_contextids()
+ ));
+ }
+
+ $privacymanager->delete_data_for_user($approvedlistcollection);
+ api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED);
+
+ // Delete the user.
delete_user($user);
return $context;
self::validate_context($context);
// Ensure the request exists.
- $select = 'id = :id AND requestedby = :requestedby';
- $params = ['id' => $requestid, 'requestedby' => $USER->id];
+ $select = 'id = :id AND (userid = :userid OR requestedby = :requestedby)';
+ $params = ['id' => $requestid, 'userid' => $USER->id, 'requestedby' => $USER->id];
$requestexists = data_request::record_exists_select($select, $params);
$result = false;
$subjectscope = get_string('noassignedroles', 'tool_dataprivacy');
}
$this->_form->addElement('static', 'subjectscope', get_string('subjectscope', 'tool_dataprivacy'), $subjectscope);
+ $this->_form->addHelpButton('subjectscope', 'subjectscope', 'tool_dataprivacy');
$this->add_purpose_category($this->_customdata['context']->contextlevel);
$addcategory = $mform->createElement('button', 'addcategory', $addcategorytext, ['data-add-element' => 'category']);
$mform->addElement('group', 'categorygroup', get_string('category', 'tool_dataprivacy'),
[$categoryselect, $addcategory], null, false);
+ $mform->addHelpButton('categorygroup', 'category', 'tool_dataprivacy');
$mform->setType('categoryid', PARAM_INT);
$mform->setDefault('categoryid', 0);
$addpurpose = $mform->createElement('button', 'addpurpose', $addpurposetext, ['data-add-element' => 'purpose']);
$mform->addElement('group', 'purposegroup', get_string('purpose', 'tool_dataprivacy'),
[$purposeselect, $addpurpose], null, false);
+ $mform->addHelpButton('purposegroup', 'purpose', 'tool_dataprivacy');
$mform->setType('purposeid', PARAM_INT);
$mform->setDefault('purposeid', 0);
if (!empty($this->_customdata['currentretentionperiod'])) {
$mform->addElement('static', 'retention_current', get_string('retentionperiod', 'tool_dataprivacy'),
$this->_customdata['currentretentionperiod']);
+ $mform->addHelpButton('retention_current', 'retentionperiod', 'tool_dataprivacy');
}
}
$purposeoptions = data_registry_page::category_options($this->_customdata['purposes'], false, $includeinherit);
$mform->addElement('select', $categoryvar, get_string('category', 'tool_dataprivacy'), $categoryoptions);
+ $mform->addHelpButton($categoryvar, 'categorydefault', 'tool_dataprivacy');
$mform->setType($categoryvar, PARAM_INT);
$mform->addElement('select', $purposevar, get_string('purpose', 'tool_dataprivacy'), $purposeoptions);
+ $mform->addHelpButton($purposevar, 'purposedefault', 'tool_dataprivacy');
$mform->setType($purposevar, PARAM_INT);
}
throw new moodle_exception('errorinvalidrequeststatus', 'tool_dataprivacy');
}
}
+
+ /**
+ * Get the users that a user can make data request for.
+ *
+ * E.g. User having a parent role and has the 'tool/dataprivacy:makedatarequestsforchildren' capability.
+ * @param int $userid The user's ID.
+ * @return array
+ */
+ public static function get_children_of_user($userid) {
+ global $DB;
+
+ // Get users that the user has role assignments to.
+ $allusernames = get_all_user_name_fields(true, 'u');
+ $sql = "SELECT u.id, $allusernames
+ FROM {role_assignments} ra, {context} c, {user} u
+ WHERE ra.userid = :userid
+ AND ra.contextid = c.id
+ AND c.instanceid = u.id
+ AND c.contextlevel = :contextlevel";
+ $params = [
+ 'userid' => $userid,
+ 'contextlevel' => CONTEXT_USER
+ ];
+
+ // The final list of users that we will return;
+ $finalresults = [];
+
+ // Our prospective list of users.
+ if ($candidates = $DB->get_records_sql($sql, $params)) {
+ foreach ($candidates as $key => $child) {
+ $childcontext = \context_user::instance($child->id);
+ if (has_capability('tool/dataprivacy:makedatarequestsforchildren', $childcontext, $userid)) {
+ $finalresults[$key] = $child;
+ }
+ }
+ }
+ return $finalresults;
+ }
}
* @return array An array of plugin types which contain plugin data.
*/
protected function get_full_component_list() {
+ global $CFG;
+
$list = \core_component::get_component_list();
+ $list['core']['core'] = "{$CFG->dirroot}/lib";
$formattedlist = [];
foreach ($list as $plugintype => $plugin) {
$formattedlist[] = ['plugin_type' => $plugintype, 'plugins' => array_keys($plugin)];
}
+
return $formattedlist;
}
$data->actions = $actionmenu->export_for_template($output);
if (!data_registry::defaults_set()) {
+ $data->info = (object)[
+ 'message' => get_string('dataregistryinfo', 'tool_dataprivacy'),
+ 'announce' => 1
+ ];
$data->nosystemdefaults = (object)[
'message' => get_string('nosystemdefaults', 'tool_dataprivacy'),
'announce' => 1
'text' => get_string('user'),
'contextlevel' => CONTEXT_USER,
], [
- 'text' => get_string('categories', 'tool_dataprivacy'),
+ 'text' => get_string('categories'),
'branches' => $categorybranches,
'expandelement' => 'category',
], [
$branches = [];
- $blockinstances = \core_block_external::get_course_blocks($coursecontext->instanceid);
- if (empty($blockinstances['blocks'])) {
- return $branches;
- }
+ $children = $coursecontext->get_child_contexts();
+ foreach ($children as $childcontext) {
- foreach ($blockinstances['blocks'] as $bi) {
- if (function_exists('block_instance_by_id')) {
- $blockinstance = block_instance_by_id($bi['instanceid']);
- } else {
- // TODO To be removed when MDL-61621 gets integrated.
- $blockinstance = $DB->get_record('block_instances', ['id' => $bi['instanceid']]);
- $blockinstance = block_instance($blockinstance->blockname, $blockinstance);
+ if ($childcontext->contextlevel !== CONTEXT_BLOCK) {
+ continue;
}
- $blockcontext = \context_block::instance($bi['instanceid']);
- $displayname = shorten_text(format_string($blockinstance->get_title(), true, ['context' => $blockcontext->id]));
+
+ $blockinstance = block_instance_by_id($childcontext->instanceid);
+ $displayname = shorten_text(format_string($blockinstance->get_title(), true, ['context' => $childcontext]));
$branches[] = self::complete([
'text' => $displayname,
- 'contextid' => $blockcontext->id,
+ 'contextid' => $childcontext->id,
]);
+
}
return $branches;
-
}
/**
$data->newdatarequesturl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php');
$data->newdatarequesturl->param('manage', true);
+ if (!is_https()) {
+ $httpwarningmessage = get_string('httpwarning', 'tool_dataprivacy');
+ $data->httpsite = array('message' => $httpwarningmessage, 'announce' => 1);
+ }
+
$requests = [];
foreach ($this->requests as $request) {
$requestid = $request->get('id');
* @throws moodle_exception
*/
public function export_for_template(renderer_base $output) {
+ global $USER;
+
$data = new stdClass();
$data->newdatarequesturl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php');
+ if (!is_https()) {
+ $httpwarningmessage = get_string('httpwarning', 'tool_dataprivacy');
+ $data->httpsite = array('message' => $httpwarningmessage, 'announce' => 1);
+ }
+
$requests = [];
foreach ($this->requests as $request) {
$requestid = $request->get('id');
$status = $request->get('status');
$userid = $request->get('userid');
- $usercontext = context_user::instance($userid);
- $requestexporter = new data_request_exporter($request, ['context' => $usercontext]);
+
+ $usercontext = context_user::instance($userid, IGNORE_MISSING);
+ if (!$usercontext) {
+ // Use the context system.
+ $outputcontext = \context_system::instance();
+ } else {
+ $outputcontext = $usercontext;
+ }
+
+ $requestexporter = new data_request_exporter($request, ['context' => $outputcontext]);
$item = $requestexporter->export($output);
+ if ($request->get('userid') != $USER->id) {
+ // Append user name if it differs from $USER.
+ $a = (object)['typename' => $item->typename, 'user' => $item->foruser->fullname];
+ $item->typename = get_string('requesttypeuser', 'tool_dataprivacy', $a);
+ }
+
$candownload = false;
$cancancel = true;
switch ($status) {
$canceltext = get_string('cancelrequest', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($cancelurl, null, $canceltext, $canceldata);
}
- if ($candownload) {
+ if ($candownload && $usercontext) {
$downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $requestid, '/',
'export.zip', true);
$downloadtext = get_string('download', 'tool_dataprivacy');
public function render_contact_dpo_link($replytoemail) {
$params = [
'data-action' => 'contactdpo',
- 'data-replytoemail' => $replytoemail
+ 'data-replytoemail' => $replytoemail,
+ 'class' => 'contactdpo'
];
return html_writer::link('#', get_string('contactdataprotectionofficer', 'tool_dataprivacy'), $params);
}
return;
}
+ $requestedby = $datarequest->get('requestedby');
+ $valid = true;
+ $comment = '';
+ $foruser = $datarequest->get('userid');
+ if ($foruser != $requestedby) {
+ if (!$valid = api::can_create_data_request_for_user($foruser, $requestedby)) {
+ $params = (object)[
+ 'requestedby' => $requestedby,
+ 'userid' => $foruser
+ ];
+ $comment = get_string('errornocapabilitytorequestforothers', 'tool_dataprivacy', $params);
+ mtrace($comment);
+ }
+ }
+ // Reject the request outright if it's invalid.
+ if (!$valid) {
+ $dpo = $datarequest->get('dpo');
+ api::update_request_status($requestid, api::DATAREQUEST_STATUS_REJECTED, $dpo, $comment);
+ return;
+ }
+
// Update the status of this request as pre-processing.
mtrace('Generating the contexts containing personal data for the user...');
api::update_request_status($requestid, api::DATAREQUEST_STATUS_PREPROCESSING);
$request = $requestpersistent->to_record();
// Check if this request still needs to be processed. e.g. The user might have cancelled it before this task has run.
- if ($request->status != api::DATAREQUEST_STATUS_APPROVED) {
- mtrace("Request {$request->id} hasn\'t been approved yet or it has already been processed. Skipping...");
+ $status = $requestpersistent->get('status');
+ if (!api::is_active($status)) {
+ mtrace("Request {$requestid} with status {$status} doesn't need to be processed. Skipping...");
return;
}
}
mtrace('Message sent to user: ' . $messagetextdata['username']);
- // Send to the requester as well. requestedby is 0 if the request was made on behalf of the user by a DPO.
- if (!empty($request->requestedby) && $foruser->id != $request->requestedby) {
- $requestedby = core_user::get_user($request->requestedby);
- $message->userto = $requestedby;
- $messagetextdata['username'] = fullname($requestedby);
- // Render message email body.
- $messagehtml = $output->render_from_template('tool_dataprivacy/data_request_results_email', $messagetextdata);
- $message->fullmessage = html_to_text($messagehtml);
- $message->fullmessagehtml = $messagehtml;
-
- // Send message.
- if ($emailonly) {
- email_to_user($requestedby, $dpo, $subject, $message->fullmessage, $messagehtml);
- } else {
- message_send($message);
+ // Send to requester as well if this request was made on behalf of another user who's not a DPO,
+ // and has the capability to make data requests for the user (e.g. Parent).
+ if (!api::is_site_dpo($request->requestedby) && $foruser->id != $request->requestedby) {
+ // Ensure the requester has the capability to make data requests for this user.
+ if (api::can_create_data_request_for_user($request->userid, $request->requestedby)) {
+ $requestedby = core_user::get_user($request->requestedby);
+ $message->userto = $requestedby;
+ $messagetextdata['username'] = fullname($requestedby);
+ // Render message email body.
+ $messagehtml = $output->render_from_template('tool_dataprivacy/data_request_results_email', $messagetextdata);
+ $message->fullmessage = html_to_text($messagehtml);
+ $message->fullmessagehtml = $messagehtml;
+
+ // Send message.
+ if ($emailonly) {
+ email_to_user($requestedby, $dpo, $subject, $message->fullmessage, $messagehtml);
+ } else {
+ message_send($message);
+ }
+ mtrace('Message sent to requester: ' . $messagetextdata['username']);
}
- mtrace('Message sent to requester: ' . $messagetextdata['username']);
}
if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
*/
use tool_dataprivacy\api;
+use tool_dataprivacy\local\helper;
defined('MOODLE_INTERNAL') || die();
} else {
// Get users whom you are being a guardian to if your role has the capability to make data requests for children.
- $allusernames = get_all_user_name_fields(true, 'u');
- $sql = "SELECT u.id, $allusernames
- FROM {role_assignments} ra, {context} c, {user} u
- WHERE ra.userid = :userid
- AND ra.contextid = c.id
- AND c.instanceid = u.id
- AND c.contextlevel = :contextlevel";
- $params = [
- 'userid' => $USER->id,
- 'contextlevel' => CONTEXT_USER
- ];
- $children = $DB->get_records_sql($sql, $params);
-
- if ($children) {
- $useroptions = [];
- $useroptions[$USER->id] = fullname($USER);
- foreach ($children as $child) {
- $childcontext = context_user::instance($child->id);
- if (has_capability('tool/dataprivacy:makedatarequestsforchildren', $childcontext)) {
- $useroptions[$child->id] = fullname($child);
- }
+ if ($children = helper::get_children_of_user($USER->id)) {
+ $useroptions = [
+ $USER->id => fullname($USER)
+ ];
+ foreach ($children as $key => $child) {
+ $useroptions[$key] = fullname($child);
}
$mform->addElement('autocomplete', 'userid', get_string('requestfor', 'tool_dataprivacy'), $useroptions);
$mform->addRule('userid', null, 'required', null, 'client');
$string['cancelrequestconfirmation'] = 'Do you really want cancel this data request?';
$string['categories'] = 'Categories';
$string['category'] = 'Category';
+$string['category_help'] = 'A category in the data registry describes a type of data. A new category may be added, or if Inherit is selected, the data category from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > Site.';
$string['categorycreated'] = 'Category created';
+$string['categorydefault'] = 'Default category';
+$string['categorydefault_help'] = 'The default category is the data category applied to any new instances. If Inherit is selected, the data category from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > User > Site.';
$string['categorieslist'] = 'List of data categories';
$string['categoryupdated'] = 'Category updated';
$string['close'] = 'Close';
$string['confirmcontextdeletion'] = 'Do you really want to confirm the deletion of the selected contexts? This will also delete all of the user data for their respective sub-contexts.';
$string['confirmdenial'] = 'Do you really want deny this data request?';
$string['contactdataprotectionofficer'] = 'Contact Data Protection Officer';
-$string['contactdataprotectionofficer_desc'] = 'Enabling this feature will provide a link for users to contact the site\'s Data Protection Officer through this site. This link will be shown on their profile page, and on the site\'s privacy policy page, as well. The link leads to a form in which the user can make a data request to the Data Protection Officer.';
+$string['contactdataprotectionofficer_desc'] = 'If enabled, users will be able to contact the Data Protection Officer and make a data request via a link on their profile page.';
$string['contextlevelname10'] = 'Site';
$string['contextlevelname30'] = 'Users';
$string['contextlevelname40'] = 'Course categories';
$string['contextlevelname70'] = 'Activity modules';
$string['contextlevelname80'] = 'Blocks';
$string['contextpurposecategorysaved'] = 'Purpose and category saved.';
-$string['contactdpoviaprivacypolicy'] = 'Please contact the site\'s Data Protection Officer as described in the Privacy Policy';
+$string['contactdpoviaprivacypolicy'] = 'Please contact the Data Protection Officer as described in the privacy policy.';
$string['createcategory'] = 'Create data category';
$string['createpurpose'] = 'Create data purpose';
$string['datadeletion'] = 'Data deletion';
-$string['datadeletionpagehelp'] = 'This page lists the contexts that are already past their retention period and need to be confirmed for user data deletion. Once the selected contexts have been confirmed for deletion, the user data related to these contexts will be deleted on the next execution of the "Delete expired contexts" scheduled task.';
-$string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for children';
+$string['datadeletionpagehelp'] = 'Data for which the retention period has expired are listed here. Please review and confirm data deletion, which will then be executed by the "Delete expired contexts" scheduled task.';
+$string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for minors';
$string['dataprivacy:managedatarequests'] = 'Manage data requests';
$string['dataprivacy:managedataregistry'] = 'Manage data registry';
$string['dataregistry'] = 'Data registry';
+$string['dataregistryinfo'] = 'The data registry enables categories (types of data) and purposes (the reasons for processing data) to be set for all content on the site - from users and courses down to activities and blocks. For each purpose, a retention period may be set. When a retention period has expired, the data is flagged and listed for deletion, awaiting admin confirmation.';
$string['datarequestcreatedforuser'] = 'Data request created for {$a}';
$string['datarequestemailsubject'] = 'Data request: {$a}';
$string['datarequests'] = 'Data requests';
$string['denyrequest'] = 'Deny request';
$string['download'] = 'Download';
$string['dporolemapping'] = 'Data Protection Officer role mapping';
-$string['dporolemapping_desc'] = 'Select one or more roles that map to the Data Protection Officer role. Users with these roles will be able to manage data requests. This requires the selected role(s) to have the capability \'tool/dataprivacy:managedatarequests\'';
+$string['dporolemapping_desc'] = 'The Data Protection Officer can manage data requests. The capability tool/dataprivacy:managedatarequests must be allowed for a role to be listed as a Data Protection Officer role mapping option.';
$string['editcategories'] = 'Edit categories';
$string['editcategory'] = 'Edit category';
$string['editcategories'] = 'Edit categories';
$string['emailsalutation'] = 'Dear {$a},';
$string['errorinvalidrequeststatus'] = 'Invalid request status!';
$string['errorinvalidrequesttype'] = 'Invalid request type!';
+$string['errornocapabilitytorequestforothers'] = 'User {$a->requestedby} doesn\'t have the capability to make a data request on behalf of user {$a->userid}';
$string['errornoexpiredcontexts'] = 'There are no expired contexts to process';
$string['errorcontexthasunexpiredchildren'] = 'The context "{$a}" still has sub-contexts that have not yet expired. No contexts have been flagged for deletion.';
$string['errorrequestalreadyexists'] = 'You already have an ongoing request.';
$string['gdpr_art_9_2_j_description'] = 'Processing is necessary for archiving purposes in the public interest, scientific or historical research purposes or statistical purposes in accordance with Article 89(1) based on Union or Member State law which shall be proportionate to the aim pursued, respect the essence of the right to data protection and provide for suitable and specific measures to safeguard the fundamental rights and the interests of the data subject';
$string['gdpr_art_9_2_j_name'] = 'Public interest, or scientific/historical/statistical research (GDPR Art. 9.2(j))';
$string['hide'] = 'Collapse all';
+$string['httpwarning'] = 'Any data downloaded from this site may not be encrypted. Please contact your system administrator and request that they install SSL on this site.';
$string['inherit'] = 'Inherit';
$string['lawfulbases'] = 'Lawful bases';
$string['lawfulbases_help'] = 'Select at least one option that will serve as the lawful basis for processing personal data. For details on these lawful bases, please see <a href="https://gdpr-info.eu/art-6-gdpr/" target="_blank">GDPR Art. 6.1</a>';
$string['noblockstoload'] = 'No blocks';
$string['nocategories'] = 'There are no categories yet';
$string['nocoursestoload'] = 'No activities';
-$string['noexpiredcontexts'] = 'There are no expired contexts in this level that need to be confirmed for deletion.';
+$string['noexpiredcontexts'] = 'This context level has no data for which the retention period has expired.';
$string['nopersonaldatarequests'] = 'You don\'t have any personal data requests';
$string['nopurposes'] = 'There are no purposes yet';
$string['nosubjectaccessrequests'] = 'There are no data requests that you need to act on';
$string['protected'] = 'Protected';
$string['protectedlabel'] = 'The retention of this data has a higher legal precedent over a user\'s request to be forgotten. This data will only be deleted after the retention period has expired.';
$string['purpose'] = 'Purpose';
+$string['purpose_help'] = 'The purpose describes the reason for processing the data. A new purpose may be added, or if Inherit is selected, the purpose from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > User > Site.';
$string['purposecreated'] = 'Purpose created';
+$string['purposedefault'] = 'Default purpose';
+$string['purposedefault_help'] = 'The default purpose is the purpose which is applied to any new instances. If Inherit is selected, the purpose from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > User > Site.';
$string['purposes'] = 'Purposes';
$string['purposeslist'] = 'List of data purposes';
$string['purposeupdated'] = 'Purpose updated';
$string['requestactions'] = 'Actions';
$string['requestby'] = 'Requested by';
$string['requestcomments'] = 'Comments';
-$string['requestcomments_help'] = 'Please feel free to provide more details about your request';
+$string['requestcomments_help'] = 'This box enables you to enter any further details about your data request.';
$string['requestemailintro'] = 'You have received a data request:';
$string['requestfor'] = 'Requesting for';
$string['requeststatus'] = 'Status';
$string['requestsubmitted'] = 'Your request has been submitted to the site\'s Data Protection Officer';
$string['requesttype'] = 'Type';
+$string['requesttypeuser'] = '{$a->typename} ({$a->user})';
$string['requesttype_help'] = 'Select the reason why you would like to contact the site\'s Data Protection Officer';
$string['requesttypedelete'] = 'Delete all of my personal data';
$string['requesttypedeleteshort'] = 'Delete';
$string['resultdownloadready'] = 'Your copy of your personal data in {$a} that you recently requested is now available for download. Please click on the link below to go to the download page.';
$string['reviewdata'] = 'Review data';
$string['retentionperiod'] = 'Retention period';
+$string['retentionperiod_help'] = 'The retention period specifies the length of time that data should be kept for. When the retention period has expired, the data is flagged and listed for deletion, awaiting admin confirmation.';
$string['retentionperiodnotdefined'] = 'No retention period was defined';
$string['retentionperiodzero'] = 'No retention period';
$string['send'] = 'Send';
$string['statuspending'] = 'Pending';
$string['statusrejected'] = 'Rejected';
$string['subjectscope'] = 'Subject scope';
+$string['subjectscope_help'] = 'The subject scope lists the roles which may be assigned in this context.';
$string['user'] = 'User';
$string['viewrequest'] = 'View the request';
$string['visible'] = 'Expand all';
.nav-pills .nav-pills {
margin-left: 1rem;
}
-
+.data-registry > .top-nav > * {
+ margin-right: 0.5rem;
+}
/*Extra attribute selection to have preference over bs2's .moodle-actionmenu[data-enhance] */
.data-registry > .top-nav > .singlebutton,
.data-registry > .top-nav > .moodle-actionmenu[data-owner='dataregistry-actions'] {
height: 70vh;
overflow-y: scroll;
}
+
+dd a.contactdpo {
+ /* Reverting dd's left margin */
+ margin-left: -10px;
+}
+
+.card dd a.contactdpo {
+ /* Reverting dd's left margin */
+ margin-left: inherit;
+}
}
}}
<div class="container">
- <div class="row">
+ <div class="row m-b-2">
<label class="col-md-3 span3 col-form-label">{{#str}}replyto, tool_dataprivacy{{/str}}</label>
<div class="col-md-9 span9 col-form-label">{{replytoemail}}</div>
</div>
}
}}
<div class="data-registry">
- <div class="top-nav">
+ <div class="top-nav d-flex">
{{#defaultsbutton}}
{{> core/action_link}}
{{/defaultsbutton}}
{{> core/action_menu}}
{{/actions}}
</div>
+ {{#info}}
+ <div class="m-t-1">
+ {{> core/notification_info}}
+ </div>
+ {{/info}}
{{#nosystemdefaults}}
<div class="m-t-1">
{{> core/notification_warning}}
}
}}
+{{#httpsite}}
+ {{> core/notification_warning}}
+{{/httpsite}}
+
<div data-region="datarequests">
<div class="m-t-1 m-b-1">
<a href="{{newdatarequesturl}}" class="btn btn-primary" data-action="new-request">
}
}}
+{{#httpsite}}
+ {{> core/notification_warning}}
+{{/httpsite}}
+
<div data-region="datarequests">
<div class="m-t-1 m-b-1">
<a href="{{newdatarequesturl}}" class="btn btn-primary" data-action="new-request">
<table class="generaltable fullwidth">
<caption class="accesshide">{{#str}}purposeslist, tool_dataprivacy{{/str}}</caption>
<thead>
- <tr style="display: flex;">
+ <tr>
<th scope="col" class="col-md-2">{{#str}}name{{/str}}</th>
<th scope="col" class="col-md-3">{{#str}}description{{/str}}</th>
<th scope="col" class="col-md-2">{{#str}}lawfulbases, tool_dataprivacy{{/str}}</th>
public function test_update_request_status() {
$generator = new testing_data_generator();
$s1 = $generator->create_user();
+ $this->setUser($s1);
// Create the sample data request.
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
// Create the sample data request.
+ $this->setUser($s1);
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
$requestid = $datarequest->get('id');
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
// Create the sample data request.
+ $this->setUser($s1);
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
$requestid = $datarequest->get('id');
$teacher = $generator->create_user();
// Create the sample data request.
+ $this->setUser($student);
$datarequest = api::create_data_request($student->id, api::DATAREQUEST_TYPE_EXPORT);
$requestid = $datarequest->get('id');
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
$this->assertEquals($user->id, $datarequest->get('userid'));
$this->assertEquals($user->id, $datarequest->get('requestedby'));
+ $this->assertEquals(0, $datarequest->get('dpo'));
+ $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
+ $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
+ $this->assertEquals($comment, $datarequest->get('comments'));
+
+ // Test adhoc task creation.
+ $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
+ $this->assertCount(1, $adhoctasks);
+ }
+
+ /**
+ * Test for api::create_data_request() made by DPO.
+ */
+ public function test_create_data_request_by_dpo() {
+ global $USER;
+
+ $generator = new testing_data_generator();
+ $user = $generator->create_user();
+ $comment = 'sample comment';
+
+ // Login as DPO (Admin is DPO by default).
+ $this->setAdminUser();
+
+ // Test data request creation.
+ $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
+ $this->assertEquals($user->id, $datarequest->get('userid'));
+ $this->assertEquals($USER->id, $datarequest->get('requestedby'));
+ $this->assertEquals($USER->id, $datarequest->get('dpo'));
+ $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
+ $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
+ $this->assertEquals($comment, $datarequest->get('comments'));
+
+ // Test adhoc task creation.
+ $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
+ $this->assertCount(1, $adhoctasks);
+ }
+
+ /**
+ * Test for api::create_data_request() made by a parent.
+ */
+ public function test_create_data_request_by_parent() {
+ global $DB;
+
+ $generator = new testing_data_generator();
+ $user = $generator->create_user();
+ $parent = $generator->create_user();
+ $comment = 'sample comment';
+
+ // Get the teacher role pretend it's the parent roles ;).
+ $systemcontext = context_system::instance();
+ $usercontext = context_user::instance($user->id);
+ $parentroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
+ // Give the manager role with the capability to manage data requests.
+ assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentroleid, $systemcontext->id, true);
+ // Assign the parent to user.
+ role_assign($parentroleid, $parent->id, $usercontext->id);
+
+ // Login as the user's parent.
+ $this->setUser($parent);
+
+ // Test data request creation.
+ $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
+ $this->assertEquals($user->id, $datarequest->get('userid'));
+ $this->assertEquals($parent->id, $datarequest->get('requestedby'));
+ $this->assertEquals(0, $datarequest->get('dpo'));
$this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
$this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
$this->assertEquals($comment, $datarequest->get('comments'));
$comment = 'sample comment';
// Make a data request as user 1.
+ $this->setUser($user1);
$d1 = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
// Make a data request as user 2.
+ $this->setUser($user2);
$d2 = api::create_data_request($user2->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
// Fetching data requests of specific users.
$user1 = $generator->create_user();
// Make a data request as user 1.
+ $this->setUser($user1);
$request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
// Set the status.
api::update_request_status($request->get('id'), $status);
[$module1, $module2]
];
}
+
+ /**
+ * Test that delete requests filter out protected purpose contexts.
+ */
+ public function test_add_request_contexts_with_status_delete() {
+ $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_DELETE);
+ $contextids = $data->list->get_contextids();
+
+ $this->assertCount(1, $contextids);
+ $this->assertEquals($data->contexts->unprotected, $contextids);
+ }
+
+ /**
+ * Test that export requests don't filter out protected purpose contexts.
+ */
+ public function test_add_request_contexts_with_status_export() {
+ $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_EXPORT);
+ $contextids = $data->list->get_contextids();
+
+ $this->assertCount(2, $contextids);
+ $this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true);
+ }
+
+ /**
+ * Perform setup for the test_add_request_contexts_with_status_xxxxx tests.
+ *
+ * @param int $type The type of request to create
+ * @return \stdClass
+ */
+ protected function setup_test_add_request_contexts_with_status($type) {
+ $this->setAdminUser();
+
+ // User under test.
+ $s1 = $this->getDataGenerator()->create_user();
+
+ // Create three sample contexts.
+ // 1 which should not be returned; and
+ // 1 which will be returned and is not protected; and
+ // 1 which will be returned and is protected.
+
+ $c1 = $this->getDataGenerator()->create_course();
+ $c2 = $this->getDataGenerator()->create_course();
+ $c3 = $this->getDataGenerator()->create_course();
+
+ $ctx1 = \context_course::instance($c1->id);
+ $ctx2 = \context_course::instance($c2->id);
+ $ctx3 = \context_course::instance($c3->id);
+
+ $unprotected = api::create_purpose((object)[
+ 'name' => 'Unprotected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
+ $protected = api::create_purpose((object) [
+ 'name' => 'Protected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a', 'protected' => true]);
+
+ $cat1 = api::create_category((object)['name' => 'a']);
+
+ // Set the defaults.
+ list($purposevar, $categoryvar) = data_registry::var_names_from_context(
+ \context_helper::get_class_for_level(CONTEXT_SYSTEM)
+ );
+ set_config($purposevar, $unprotected->get('id'), 'tool_dataprivacy');
+ set_config($categoryvar, $cat1->get('id'), 'tool_dataprivacy');
+
+ $contextinstance1 = api::set_context_instance((object) [
+ 'contextid' => $ctx1->id,
+ 'purposeid' => $unprotected->get('id'),
+ 'categoryid' => $cat1->get('id'),
+ ]);
+
+ $contextinstance2 = api::set_context_instance((object) [
+ 'contextid' => $ctx2->id,
+ 'purposeid' => $unprotected->get('id'),
+ 'categoryid' => $cat1->get('id'),
+ ]);
+
+ $contextinstance3 = api::set_context_instance((object) [
+ 'contextid' => $ctx3->id,
+ 'purposeid' => $protected->get('id'),
+ 'categoryid' => $cat1->get('id'),
+ ]);
+
+ $collection = new \core_privacy\local\request\contextlist_collection($s1->id);
+ $contextlist = new \core_privacy\local\request\contextlist();
+ $contextlist->set_component('tool_dataprivacy');
+ $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx2, :ctx3)', [
+ 'ctx2' => $ctx2->id,
+ 'ctx3' => $ctx3->id,
+ ]);
+
+ $collection->add_contextlist($contextlist);
+
+ // Create the sample data request.
+ $datarequest = api::create_data_request($s1->id, $type);
+ $requestid = $datarequest->get('id');
+
+ // Add the full collection with contexts 2, and 3.
+ api::add_request_contexts_with_status($collection, $requestid, \tool_dataprivacy\contextlist_context::STATUS_PENDING);
+
+ // Mark it as approved.
+ api::update_request_contexts_with_status($requestid, \tool_dataprivacy\contextlist_context::STATUS_APPROVED);
+
+ // Fetch the list.
+ $approvedcollection = api::get_approved_contextlist_collection_for_request($datarequest);
+
+ return (object) [
+ 'contexts' => (object) [
+ 'unused' => [
+ $ctx1->id,
+ ],
+ 'used' => [
+ $ctx2->id,
+ $ctx3->id,
+ ],
+ 'unprotected' => [
+ $ctx2->id,
+ ],
+ 'protected' => [
+ $ctx3->id,
+ ],
+ ],
+ 'list' => $approvedcollection->get_contextlist_for_component('tool_dataprivacy'),
+ ];
+ }
}
$this->getDataGenerator()->enrol_user($user3->id, $course2->id, 'student');
$this->getDataGenerator()->enrol_user($user4->id, $course3->id, 'student');
+ // Add an activity and some data for user 2.
+ $assignmod = $this->getDataGenerator()->create_module('assign', ['course' => $course2->id]);
+ $data = (object) [
+ 'assignment' => $assignmod->id,
+ 'userid' => $user2->id,
+ 'timecreated' => time(),
+ 'timemodified' => time(),
+ 'status' => 'new',
+ 'groupid' => 0,
+ 'attemptnumber' => 0,
+ 'latest' => 1,
+ ];
+ $DB->insert_record('assign_submission', $data);
+ // We should have one record in the assign submission table.
+ $this->assertEquals(1, $DB->count_records('assign_submission'));
+
// Users without lastaccess are skipped as well as users enroled in courses with no end date.
$expired = new \tool_dataprivacy\expired_user_contexts();
$numexpired = $expired->flag_expired();
$deleted = $expired->delete();
$this->assertEquals(0, $deleted);
+ // No user data left in mod_assign.
+ $this->assertEquals(0, $DB->count_records('assign_submission'));
+
// The user is deleted.
$deleteduser = \core_user::get_user($user2->id, 'id, deleted', IGNORE_MISSING);
$this->assertEquals(1, $deleteduser->deleted);
}
// Let's check core subsystems.
+ // The Privacy API adds an extra component in the form of 'core'.
$coresubsystems = \core_component::get_core_subsystems();
- $this->assertEquals(count($coresubsystems), count($data['core']));
+ $this->assertEquals(count($coresubsystems) + 1, count($data['core']));
}
/**
defined('MOODLE_INTERNAL') || die;
-$plugin->version = 2018040500;
-$plugin->requires = 2018040500; // Moodle 3.5dev (Build 2018031600) and upwards.
+$plugin->version = 2018051400;
+$plugin->requires = 2018050800; // Moodle 3.5dev (Build 2018031600) and upwards.
$plugin->component = 'tool_dataprivacy';
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_dbtransfer'; // Full name of the plugin (used for diagnostics).
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300;
-$plugin->requires = 2017110800;
+$plugin->version = 2018051400;
+$plugin->requires = 2018050800;
$plugin->component = 'tool_filetypes';
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300;
-$plugin->requires = 2017110800;
+$plugin->version = 2018051400;
+$plugin->requires = 2018050800;
$plugin->component = 'tool_generator';
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires = 2017110800; // Requires this Moodle version
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires = 2018050800; // Requires this Moodle version
$plugin->component = 'tool_health'; // Full name of the plugin (used for diagnostics)
$plugin->maturity = MATURITY_ALPHA; // this version's maturity level
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_httpsreplace'; // Full name of the plugin (used for diagnostics).
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires = 2017110800; // Requires this Moodle version
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires = 2018050800; // Requires this Moodle version
$plugin->component = 'tool_innodb'; // Full name of the plugin (used for diagnostics)
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'tool_installaddon';
-$plugin->version = 2017111300;
-$plugin->requires = 2017110800;
+$plugin->version = 2018051400;
+$plugin->requires = 2018050800;
$plugin->maturity = MATURITY_STABLE;
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires = 2017110800; // Requires this Moodle version
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires = 2018050800; // Requires this Moodle version
$plugin->component = 'tool_langimport'; // Full name of the plugin (used for diagnostics)
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'logstore_database'; // Full name of the plugin (used for diagnostics).
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'logstore_legacy'; // Full name of the plugin (used for diagnostics).
*/
public static function add_contexts_for_userid(contextlist $contextlist, $userid) {
$sql = "
- SELECT ctx.id
- FROM {context} ctx
- JOIN {logstore_standard_log} l
- ON l.contextid = ctx.id
+ SELECT l.contextid
+ FROM {logstore_standard_log} l
WHERE l.userid = :userid1
OR l.relateduserid = :userid2
OR l.realuserid = :userid3";
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'logstore_standard'; // Full name of the plugin (used for diagnostics).
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_log'; // Full name of the plugin (used for diagnostics).
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_lp'; // Full name of the plugin (used for diagnostics).
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_lpimportcsv'; // Full name of the plugin (used for diagnostics).
-$plugin->dependencies = array('tool_lp' => 2017110800);
+$plugin->dependencies = array('tool_lp' => 2018050800);
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_lpmigrate'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = array(
'tool_lp' => ANY_VERSION
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300;
-$plugin->requires = 2017110800;
+$plugin->version = 2018051400;
+$plugin->requires = 2018050800;
$plugin->component = 'tool_messageinbound';
*/
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111301; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = array(
- 'webservice_rest' => 2017110800
+ 'webservice_rest' => 2018050800
);
$sql = "SELECT DISTINCT ctx.id
FROM {context} ctx
LEFT JOIN {tool_monitor_rules} mr ON ctx.instanceid = mr.userid AND ctx.contextlevel = :contextuserrule
+ AND mr.userid = :useridsubscriptions
LEFT JOIN {tool_monitor_subscriptions} ms ON ctx.instanceid = ms.userid AND ctx.contextlevel = :contextusersub
- WHERE (ms.userid = :useridrules OR mr.userid = :useridsubscriptions)";
+ AND ms.userid = :useridrules
+ WHERE ms.id IS NOT NULL OR mr.id IS NOT NULL";
$contextlist = new contextlist();
$contextlist->add_from_sql($sql, $params);
defined('MOODLE_INTERNAL') || die;
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_monitor'; // Full name of the plugin (used for diagnostics).
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires = 2017110800; // Requires this Moodle version
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires = 2018050800; // Requires this Moodle version
$plugin->component = 'tool_multilangupgrade'; // Full name of the plugin (used for diagnostics)
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_oauth2'; // Full name of the plugin (used for diagnostics).
--- /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 Subsystem implementation for tool_phpunit.
+ *
+ * @package tool_phpunit
+ * @copyright 2018 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_phpunit\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for tool_phpunit implementing null_provider.
+ *
+ * @copyright 2018 Mark Nelson <markn@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:metadata';
+ }
+}
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires = 2017110800; // Requires this Moodle version
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires = 2018050800; // Requires this Moodle version
$plugin->component = 'tool_phpunit'; // Full name of the plugin (used for diagnostics)
// and $SESSION->wantsurl is defined, redirect to the return page.
$hasagreedsignupuser = empty($USER->id) && $this->signupuserpolicyagreed;
$hasagreedloggeduser = $USER->id == $userid && !empty($USER->policyagreed);
- $canrevoke = api::can_revoke_policies($USER->id);
- if (!is_siteadmin() && ($hasagreedsignupuser ||
- ($hasagreedloggeduser && !$canrevoke))) {
+ if (!is_siteadmin() && ($hasagreedsignupuser || $hasagreedloggeduser)) {
$this->redirect_to_previous_url();
}
// Page setup.
$PAGE->set_context(context_system::instance());
- $PAGE->set_pagelayout('standard');
$PAGE->set_url($myurl);
$PAGE->set_heading($SITE->fullname);
$PAGE->set_title(get_string('policiesagreements', 'tool_policy'));
$data = (object) [
'pluginbaseurl' => (new moodle_url('/admin/tool/policy'))->out(false),
'returnurl' => $this->returnurl ? (new moodle_url($this->returnurl))->out(false) : null,
- 'editurl' => ($this->manage && $this->policy->status != policy_version::STATUS_ARCHIVED) ?
- (new moodle_url('/admin/tool/policy/editpolicydoc.php',
- ['policyid' => $this->policy->policyid, 'versionid' => $this->policy->id]))->out(false) : null,
'numpolicy' => $this->numpolicy ? : null,
'totalpolicies' => $this->totalpolicies ? : null,
];
+ if ($this->manage && $this->policy->status != policy_version::STATUS_ARCHIVED) {
+ $paramsurl = ['policyid' => $this->policy->policyid, 'versionid' => $this->policy->id];
+ $data->editurl = (new moodle_url('/admin/tool/policy/editpolicydoc.php', $paramsurl))->out(false);
+ }
$data->policy = clone($this->policy);
$behalfid = optional_param('userid', null, PARAM_INT);
$PAGE->set_context(context_system::instance());
+$PAGE->set_pagelayout('standard');
$PAGE->set_url('/admin/tool/policy/index.php');
$PAGE->set_popup_notification_allowed(false);
defined('MOODLE_INTERNAL') || die();
-$string['acceptanceacknowledgement'] = 'I acknowledge that I have received the user\'s request to consent on the abovementioned policy on behalf of the user.';
+$string['acceptanceacknowledgement'] = 'I acknowledge that I have received a request to give consent on behalf of user(s).';
$string['acceptancecount'] = '{$a->agreedcount} of {$a->policiescount}';
$string['acceptancenote'] = 'Remarks';
$string['acceptancepolicies'] = 'Policies';
-$string['acceptancessavedsucessfully'] = 'The agreements has been saved successfully.';
+$string['acceptancessavedsucessfully'] = 'The agreements have been saved successfully.';
$string['acceptancestatusoverall'] = 'Overall';
$string['acceptanceusers'] = 'Users';
$string['actions'] = 'Actions';
$string['activate'] = 'Set status to "Active"';
$string['activating'] = 'Activating a policy';
-$string['activateconfirm'] = '<p>You are about to activate policy <em>\'{$a->name}\'</em> and make the version <em>\'{$a->revision}\'</em> the current one.</p><p>All users will be required to accept this new policy version to be able to use the site.</p>';
+$string['activateconfirm'] = '<p>You are about to activate policy <em>\'{$a->name}\'</em> and make the version <em>\'{$a->revision}\'</em> the current one.</p><p>All users will be required to agree to this new policy version to be able to use the site.</p>';
$string['activateconfirmyes'] = 'Activate';
$string['agreed'] = 'Agreed';
$string['agreedby'] = 'Agreed by';
-$string['agreedno'] = 'Not agreed';
-$string['agreednowithlink'] = 'Not agreed, click to agree to "{$a}"';
-$string['agreednowithlinkall'] = 'Not agreed, click to agree to all policies';
+$string['agreedno'] = 'Consent not given';
+$string['agreednowithlink'] = 'Consent not given; click to give consent on behalf of user for {$a}';
+$string['agreednowithlinkall'] = 'Consent not given; click to give consent on behalf of user for all policies';
$string['agreedon'] = 'Agreed on';
$string['agreedyes'] = 'Agreed';
-$string['agreedyesonbehalf'] = 'Agreed on behalf of';
-$string['agreedyesonbehalfwithlink'] = 'Agreed on behalf of, click to withdraw consent to "{$a}"';
-$string['agreedyesonbehalfwithlinkall'] = 'Agreed on behalf of, click to withdraw consent to all policies"';
-$string['agreedyeswithlink'] = 'Agreed, click to withdraw consent to "{$a}"';
-$string['agreedyeswithlinkall'] = 'Agreed, click to withdraw consent to all policies';
+$string['agreedyesonbehalf'] = 'Consent given on behalf of user';
+$string['agreedyesonbehalfwithlink'] = 'Consent given on behalf of user; click to withdraw user consent for {$a}';
+$string['agreedyesonbehalfwithlinkall'] = 'Consent given on behalf of user; click to withdraw user consent for all policies';
+$string['agreedyeswithlink'] = 'Consent given; click to withdraw user consent for {$a}';
+$string['agreedyeswithlinkall'] = 'Consent given; click to withdraw user consent for all policies';
$string['agreepolicies'] = 'Please agree to the following policies';
$string['backtotop'] = 'Back to top';
$string['consentbulk'] = 'Consent';
-$string['consentdetails'] = 'Agree on behalf of the user';
+$string['consentdetails'] = 'Give consent on behalf of user';
$string['consentpagetitle'] = 'Consent';
$string['contactdpo'] = 'For questions regarding the policies please contact the Data Protection Officer.';
$string['dataproc'] = 'Personal data processing';
$string['editingpolicydocument'] = 'Editing policy';
$string['errorpolicyversionnotfound'] = 'There isn\'t any policy version with this identifier.';
$string['errorsaveasdraft'] = 'Minor change can not be saved as draft';
-$string['errorusercantviewpolicyversion'] = 'The user hasn\'t access to this policy version.';
+$string['errorusercantviewpolicyversion'] = 'The user doesn\'t have access to this policy version.';
$string['event_acceptance_created'] = 'User policy agreement created';
$string['event_acceptance_updated'] = 'User policy agreement updated';
$string['filtercapabilityno'] = 'Permission: Can not agree';
$string['guestconsent:continue'] = 'Continue';
$string['guestconsentmessage'] = 'If you continue browsing this website, you agree to our policies:';
$string['iagree'] = 'I agree to the {$a}';
-$string['iagreetothepolicy'] = 'Agree';
+$string['iagreetothepolicy'] = 'Give consent on behalf of user';
$string['inactivate'] = 'Set status to "Inactive"';
$string['inactivating'] = 'Inactivating a policy';
-$string['inactivatingconfirm'] = '<p>You are about to inactivate policy <em>\'{$a->name}\'</em> version <em>\'{$a->revision}\'</em>.</p><p>The policy will not apply until some version is made the current one.</p>';
+$string['inactivatingconfirm'] = '<p>You are about to inactivate policy <em>\'{$a->name}\'</em> version <em>\'{$a->revision}\'</em>.</p>';
$string['inactivatingconfirmyes'] = 'Inactivate';
$string['invalidversionid'] = 'There is no policy with this identifier!';
-$string['irevokethepolicy'] = 'Withdraw consent';
+$string['irevokethepolicy'] = 'Withdraw user consent';
$string['minorchange'] = 'Minor change';
-$string['minorchangeinfo'] = 'Minor changes do not amend the meaning of the policy text, terms or conditions. Users do not need to reconfirm their consent.';
+$string['minorchangeinfo'] = 'A minor change does not alter the meaning of the policy. Users are not required to agree to the policy again if the edit is marked as a minor change.';
$string['managepolicies'] = 'Manage policies';
$string['movedown'] = 'Move down';
$string['moveup'] = 'Move up';
$string['nopermissiontoagreedocs_desc'] = 'Sorry, you do not have the required permissions to agree to the policies.<br />You will not be able to use this site until the following policies are agreed:';
$string['nopermissiontoagreedocsbehalf'] = 'No permission to agree to the policies on behalf of this user';
$string['nopermissiontoagreedocsbehalf_desc'] = 'Sorry, you do not have the required permission to agree to the following policies on behalf of {$a}:';
-$string['nopermissiontoagreedocscontact'] = 'For further assistance, please contact the following person:';
+$string['nopermissiontoagreedocscontact'] = 'For further assistance, please contact';
$string['nopermissiontoviewpolicyversion'] = 'You do not have permissions to view this policy version.';
$string['nopolicies'] = 'There are no policies for registered users with an active version.';
$string['selectpolicyandversion'] = 'Use the filter above to select policy and/or version';
$string['pluginname'] = 'Policies';
$string['policiesagreements'] = 'Policies and agreements';
$string['policy:accept'] = 'Agree to the policies';
-$string['policy:acceptbehalf'] = 'Agree to the policies on someone else\'s behalf';
+$string['policy:acceptbehalf'] = 'Give consent for policies on someone else\'s behalf';
$string['policy:managedocs'] = 'Manage policies';
$string['policy:viewacceptances'] = 'View user agreement reports';
$string['policydocaudience'] = 'User consent';
$string['policydoctype1'] = 'Privacy policy';
$string['policydoctype2'] = 'Third parties policy';
$string['policydoctype99'] = 'Other policy';
-$string['policyversionacceptedinbehalf'] = 'This policy version has been agreed to by another user on behalf of you.';
-$string['policyversionacceptedinotherlang'] = 'This policy version has been agreed to in a different language.';
+$string['policyversionacceptedinbehalf'] = 'Consent for this policy has been given on your behalf.';
+$string['policyversionacceptedinotherlang'] = 'Consent for this policy version has been given in a different language.';
$string['previousversions'] = '{$a} previous versions';
-$string['privacy:metadata:acceptances'] = 'Information from policies agreements made by the users of this site.';
+$string['privacy:metadata:acceptances'] = 'Information from policy agreements made by site users';
$string['privacy:metadata:acceptances:policyversionid'] = 'The ID of the accepted version policy.';
$string['privacy:metadata:acceptances:userid'] =