--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package tool_monitor
+ * @copyright 2018 Adrian Greeve <adrian@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_monitor\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;
+use \tool_monitor\subscription_manager;
+use \tool_monitor\rule_manager;
+
+/**
+ * Privacy provider for tool_monitor
+ *
+ * @package tool_monitor
+ * @copyright 2018 Adrian Greeve <adrian@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\plugin\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 {
+ $toolmonitorrules = [
+ 'description' => 'privacy:metadata:description',
+ 'name' => 'privacy:metadata:name',
+ 'userid' => 'privacy:metadata:userid',
+ 'plugin' => 'privacy:metadata:plugin',
+ 'eventname' => 'privacy:metadata:eventname',
+ 'template' => 'privacy:metadata:template',
+ 'frequency' => 'privacy:metadata:frequency',
+ 'timewindow' => 'privacy:metadata:timewindow',
+ 'timemodified' => 'privacy:metadata:timemodifiedrule',
+ 'timecreated' => 'privacy:metadata:timecreatedrule'
+ ];
+ $toolmonitorsubscriptions = [
+ 'userid' => 'privacy:metadata:useridsub',
+ 'timecreated' => 'privacy:metadata:timecreatedsub',
+ 'lastnotificationsent' => 'privacy:metadata:lastnotificationsent',
+ 'inactivedate' => 'privacy:metadata:inactivedate'
+ ];
+ // Tool monitor history doesn't look like it is used at all.
+ $toolmonitorhistory = [
+ 'userid' => 'privacy:metadata:useridhistory',
+ 'timesent' => 'privacy:metadata:timesent'
+ ];
+ $collection->add_database_table('tool_monitor_rules', $toolmonitorrules, 'privacy:metadata:rulessummary');
+ $collection->add_database_table('tool_monitor_subscriptions', $toolmonitorsubscriptions,
+ 'privacy:metadata:subscriptionssummary');
+ $collection->add_database_table('tool_monitor_history', $toolmonitorhistory, 'privacy:metadata:historysummary');
+ $collection->link_subsystem('core_message', 'privacy:metadata:messagesummary');
+ return $collection;
+ }
+
+ /**
+ * Return all contexts for this userid. In this situation the user context.
+ *
+ * @param int $userid The user ID.
+ * @return contextlist The list of context IDs.
+ */
+ public static function get_contexts_for_userid(int $userid) : contextlist {
+ $params = ['useridrules' => $userid, 'useridsubscriptions' => $userid, 'contextuserrule' => CONTEXT_USER,
+ 'contextusersub' => CONTEXT_USER];
+ $sql = "SELECT DISTINCT ctx.id
+ FROM {context} ctx
+ LEFT JOIN {tool_monitor_rules} mr ON ctx.instanceid = mr.userid AND ctx.contextlevel = :contextuserrule
+ LEFT JOIN {tool_monitor_subscriptions} ms ON ctx.instanceid = ms.userid AND ctx.contextlevel = :contextusersub
+ WHERE (ms.userid = :useridrules OR mr.userid = :useridsubscriptions)";
+
+ $contextlist = new contextlist();
+ $contextlist->add_from_sql($sql, $params);
+ return $contextlist;
+ }
+
+ /**
+ * Export all event monitor information for the list of contexts and this user.
+ *
+ * @param approved_contextlist $contextlist The list of approved contexts for a user.
+ */
+ public static function export_user_data(approved_contextlist $contextlist) {
+ global $DB;
+ // Export rules.
+ $context = \context_user::instance($contextlist->get_user()->id);
+ $rules = $DB->get_records('tool_monitor_rules', ['userid' => $contextlist->get_user()->id]);
+ if ($rules) {
+ static::export_monitor_rules($rules, $context);
+ }
+ // Export subscriptions.
+ $subscriptions = subscription_manager::get_user_subscriptions(0, 0, $contextlist->get_user()->id);
+ if ($subscriptions) {
+ static::export_monitor_subscriptions($subscriptions, $context);
+ }
+ }
+
+ /**
+ * 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) {
+ // Only delete data for user contexts.
+ if ($context->contextlevel == CONTEXT_USER) {
+ static::delete_user_data($context->instanceid);
+ }
+ }
+
+ /**
+ * 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) {
+ static::delete_user_data($contextlist->get_user()->id);
+ }
+
+ /**
+ * This does the deletion of user data for the event monitor.
+ *
+ * @param int $userid The user ID
+ */
+ protected static function delete_user_data(int $userid) {
+ global $DB;
+ // Delete this user's subscriptions first.
+ subscription_manager::delete_user_subscriptions($userid);
+ // Because we only use user contexts the instance ID is the user ID.
+ // Get the rules and check if this user has the capability to delete them.
+ $rules = $DB->get_records('tool_monitor_rules', ['userid' => $userid]);
+ foreach ($rules as $ruledata) {
+ $rule = rule_manager::get_rule($ruledata);
+ // If no-one is suscribed to the rule then it is safe to delete.
+ if ($rule->can_manage_rule($userid) && subscription_manager::count_rule_subscriptions($rule->id) == 0) {
+ $rule->delete_rule();
+ }
+ }
+ }
+
+ /**
+ * This formats and then exports the monitor rules.
+ *
+ * @param array $rules The monitor rules.
+ * @param context_user $context The user context
+ */
+ protected static function export_monitor_rules(array $rules, \context_user $context) {
+ foreach ($rules as $rule) {
+ $rule = rule_manager::get_rule($rule);
+ $ruledata = new \stdClass();
+ $ruledata->name = $rule->name;
+ $ruledata->eventname = $rule->get_event_name();
+ $ruledata->description = $rule->get_description($context);
+ $ruledata->plugin = $rule->get_plugin_name();
+ $ruledata->template = $rule->template;
+ $ruledata->frequency = $rule->get_filters_description();
+ $ruledata->course = $rule->get_course_name($context);
+ $ruledata->timecreated = transform::datetime($rule->timecreated);
+ $ruledata->timemodified = transform::datetime($rule->timemodified);
+ writer::with_context($context)->export_data([get_string('privacy:createdrules', 'tool_monitor'),
+ $rule->name . '_' . $rule->id], $ruledata);
+ }
+ }
+
+ /**
+ * This formats and then exports the event monitor subscriptions.
+ *
+ * @param array $subscriptions Subscriptions
+ * @param \context_user $context The user context
+ */
+ protected static function export_monitor_subscriptions(array $subscriptions, \context_user $context) {
+ foreach ($subscriptions as $subscription) {
+ $subscriptiondata = new \stdClass();
+ $subscriptiondata->instancename = $subscription->get_instance_name();
+ $subscriptiondata->eventname = $subscription->get_event_name();
+ $subscriptiondata->frequency = $subscription->get_filters_description();
+ $subscriptiondata->name = $subscription->get_name($context);
+ $subscriptiondata->description = $subscription->get_description($context);
+ $subscriptiondata->pluginname = $subscription->get_plugin_name();
+ $subscriptiondata->course = $subscription->get_course_name($context);
+ $subscriptiondata->timecreated = transform::datetime($subscription->timecreated);
+ $subscriptiondata->lastnotificationsent = transform::datetime($subscription->lastnotificationsent);
+ writer::with_context($context)->export_data([get_string('privacy:subscriptions', 'tool_monitor'),
+ $subscriptiondata->name . '_' . $subscription->id, $subscriptiondata->course, $subscriptiondata->instancename],
+ $subscriptiondata);
+ }
+ }
+}
--- /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 the event monitor
+ *
+ * @package tool_monitor
+ * @category test
+ * @copyright 2018 Adrian Greeve <adriangreeve.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use \tool_monitor\privacy\provider;
+use \core_privacy\local\request\approved_contextlist;
+
+/**
+ * Privacy test for the event monitor
+ *
+ * @package tool_monitor
+ * @category test
+ * @copyright 2018 Adrian Greeve <adriangreeve.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_monitor_privacy_testcase extends advanced_testcase {
+
+ /**
+ * Set up method.
+ */
+ public function setUp() {
+ $this->resetAfterTest();
+ // Enable monitor.
+ set_config('enablemonitor', 1, 'tool_monitor');
+ }
+
+ /**
+ * Assign a capability to $USER
+ * The function creates a student $USER if $USER->id is empty
+ *
+ * @param string $capability capability name
+ * @param int $contextid
+ * @param int $roleid
+ * @return int the role id - mainly returned for creation, so calling function can reuse it
+ */
+ public static function assign_user_capability($capability, $contextid, $roleid = null) {
+ global $USER;
+
+ // Create a new student $USER if $USER doesn't exist.
+ if (empty($USER->id)) {
+ $user = self::getDataGenerator()->create_user();
+ self::setUser($user);
+ }
+
+ if (empty($roleid)) {
+ $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
+ }
+
+ assign_capability($capability, CAP_ALLOW, $roleid, $contextid);
+
+ role_assign($roleid, $USER->id, $contextid);
+
+ accesslib_clear_all_caches_for_unit_testing();
+
+ return $roleid;
+ }
+
+ /**
+ * Test that a collection with data is returned when calling this function.
+ */
+ public function test_get_metadata() {
+ $collection = new \core_privacy\local\metadata\collection('tool_monitor');
+ $collection = provider::get_metadata($collection);
+ $this->assertNotEmpty($collection);
+ }
+
+ /**
+ * Check that a user context is returned if there is any user data for this user.
+ */
+ public function test_get_contexts_for_userid() {
+ $user = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $usercontext = \context_user::instance($user->id);
+ $usercontext2 = \context_user::instance($user2->id);
+ $this->assertEmpty(provider::get_contexts_for_userid($user->id));
+ $this->assertEmpty(provider::get_contexts_for_userid($user2->id));
+
+ $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+ // Create a rule with this user.
+ $this->setUser($user);
+ $rule = $monitorgenerator->create_rule();
+ $contextlist = provider::get_contexts_for_userid($user->id);
+
+ // Check that we only get back one context.
+ $this->assertCount(1, $contextlist);
+
+ // Check that a context is returned for just creating a rule.
+ $this->assertEquals($usercontext->id, $contextlist->get_contextids()[0]);
+
+ $this->setUser($user2);
+
+ $record = new stdClass();
+ $record->courseid = 0;
+ $record->userid = $user2->id;
+ $record->ruleid = $rule->id;
+
+ $subscription = $monitorgenerator->create_subscription($record);
+ $contextlist = provider::get_contexts_for_userid($user2->id);
+
+ // Check that we only get back one context.
+ $this->assertCount(1, $contextlist);
+
+ // Check that a context is returned for just subscribing to a rule.
+ $this->assertEquals($usercontext2->id, $contextlist->get_contextids()[0]);
+ }
+
+ /**
+ * Test that user data is exported correctly.
+ */
+ public function test_export_user_data() {
+ $user = $this->getDataGenerator()->create_user();
+ $usercontext = \context_user::instance($user->id);
+ $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+ $this->setUser($user);
+ $rulerecord = (object)['name' => 'privacy rule'];
+ $rule = $monitorgenerator->create_rule($rulerecord);
+
+ $secondrulerecord = (object)['name' => 'privacy rule2'];
+ $rule2 = $monitorgenerator->create_rule($secondrulerecord);
+
+ $subscription = (object)['ruleid' => $rule->id, 'userid' => $user->id];
+ $subscription = $monitorgenerator->create_subscription($subscription);
+
+ $writer = \core_privacy\local\request\writer::with_context($usercontext);
+ $this->assertFalse($writer->has_any_data());
+
+ $approvedlist = new approved_contextlist($user, 'tool_monitor', [$usercontext->id]);
+ provider::export_user_data($approvedlist);
+
+ // Check that the rules created by this user are exported.
+ $this->assertEquals($rulerecord->name, $writer->get_data([get_string('privacy:createdrules', 'tool_monitor'),
+ $rulerecord->name . '_' . $rule->id])->name);
+ $this->assertEquals($secondrulerecord->name, $writer->get_data([get_string('privacy:createdrules', 'tool_monitor'),
+ $secondrulerecord->name . '_' . $rule2->id])->name);
+
+ // Check that the subscriptions for this user are also exported.
+ $this->assertEquals($rulerecord->name, $writer->get_data([get_string('privacy:subscriptions', 'tool_monitor'),
+ $rulerecord->name . '_' . $subscription->id, 'Site' , 'All events'])->name);
+ }
+
+ /**
+ * Test deleting all user data for a specific context.
+ */
+ public function test_delete_data_for_all_users_in_context() {
+ global $DB;
+
+ $user = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $usercontext = \context_user::instance($user->id);
+ $usercontext2 = \context_user::instance($user2->id);
+ $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+ $this->setUser($user);
+ // Need to give user one the ability to manage rules.
+ $this->assign_user_capability('tool/monitor:managerules', \context_system::instance());
+
+ $rulerecord = (object)['name' => 'privacy rule'];
+ $rule = $monitorgenerator->create_rule($rulerecord);
+
+ $secondrulerecord = (object)['name' => 'privacy rule2'];
+ $rule2 = $monitorgenerator->create_rule($secondrulerecord);
+
+ $subscription = (object)['ruleid' => $rule->id, 'userid' => $user->id];
+ $subscription = $monitorgenerator->create_subscription($subscription);
+
+ // Have user 2 subscribe to the second rule created by user 1.
+ $subscription2 = (object)['ruleid' => $rule2->id, 'userid' => $user2->id];
+ $subscription2 = $monitorgenerator->create_subscription($subscription2);
+
+ $this->setUser($user2);
+ $thirdrulerecord = (object)['name' => 'privacy rule for second user'];
+ $rule3 = $monitorgenerator->create_rule($thirdrulerecord);
+
+ $subscription3 = (object)['ruleid' => $rule3->id, 'userid' => $user2->id];
+ $subscription3 = $monitorgenerator->create_subscription($subscription3);
+
+ // Try a different context first.
+ provider::delete_data_for_all_users_in_context(context_system::instance());
+
+ // Get all of the monitor rules.
+ $dbrules = $DB->get_records('tool_monitor_rules');
+
+ // All of the rules should still be present.
+ $this->assertCount(3, $dbrules);
+ $this->assertEquals($user->id, $dbrules[$rule->id]->userid);
+ $this->assertEquals($user->id, $dbrules[$rule2->id]->userid);
+ $this->assertEquals($user2->id, $dbrules[$rule3->id]->userid);
+
+ // Delete everything for the first user context.
+ provider::delete_data_for_all_users_in_context($usercontext);
+
+ // Get all of the monitor rules.
+ $dbrules = $DB->get_records('tool_monitor_rules');
+
+ // Only the rules for user 1 that does not have any more subscriptions should be deleted (the first rule).
+ $this->assertCount(2, $dbrules);
+ $this->assertEquals($user->id, $dbrules[$rule2->id]->userid);
+ $this->assertEquals($user2->id, $dbrules[$rule3->id]->userid);
+
+ // Get all of the monitor subscriptions.
+ $dbsubs = $DB->get_records('tool_monitor_subscriptions');
+ // There should be two subscriptions left, both for user 2.
+ $this->assertCount(2, $dbsubs);
+ $this->assertEquals($user2->id, $dbsubs[$subscription2->id]->userid);
+ $this->assertEquals($user2->id, $dbsubs[$subscription3->id]->userid);
+ }
+
+ /**
+ * This should work identical to the above test.
+ */
+ public function test_delete_data_for_user() {
+ global $DB;
+
+ $user = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $usercontext = \context_user::instance($user->id);
+ $usercontext2 = \context_user::instance($user2->id);
+ $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+ $this->setUser($user);
+ // Need to give user one the ability to manage rules.
+ $this->assign_user_capability('tool/monitor:managerules', \context_system::instance());
+
+ $rulerecord = (object)['name' => 'privacy rule'];
+ $rule = $monitorgenerator->create_rule($rulerecord);
+
+ $secondrulerecord = (object)['name' => 'privacy rule2'];
+ $rule2 = $monitorgenerator->create_rule($secondrulerecord);
+
+ $subscription = (object)['ruleid' => $rule->id, 'userid' => $user->id];
+ $subscription = $monitorgenerator->create_subscription($subscription);
+
+ // Have user 2 subscribe to the second rule created by user 1.
+ $subscription2 = (object)['ruleid' => $rule2->id, 'userid' => $user2->id];
+ $subscription2 = $monitorgenerator->create_subscription($subscription2);
+
+ $this->setUser($user2);
+ $thirdrulerecord = (object)['name' => 'privacy rule for second user'];
+ $rule3 = $monitorgenerator->create_rule($thirdrulerecord);
+
+ $subscription3 = (object)['ruleid' => $rule3->id, 'userid' => $user2->id];
+ $subscription3 = $monitorgenerator->create_subscription($subscription3);
+
+ $approvedlist = new approved_contextlist($user, 'tool_monitor', [$usercontext->id]);
+
+ // Delete everything for the first user.
+ provider::delete_data_for_user($approvedlist);
+
+ // Get all of the monitor rules.
+ $dbrules = $DB->get_records('tool_monitor_rules');
+
+ // Only the rules for user 1 that does not have any more subscriptions should be deleted (the first rule).
+ $this->assertCount(2, $dbrules);
+ $this->assertEquals($user->id, $dbrules[$rule2->id]->userid);
+ $this->assertEquals($user2->id, $dbrules[$rule3->id]->userid);
+
+ // Get all of the monitor subscriptions.
+ $dbsubs = $DB->get_records('tool_monitor_subscriptions');
+ // There should be two subscriptions left, both for user 2.
+ $this->assertCount(2, $dbsubs);
+ $this->assertEquals($user2->id, $dbsubs[$subscription2->id]->userid);
+ $this->assertEquals($user2->id, $dbsubs[$subscription3->id]->userid);
+ }
+}