From 9e96c5292aa4eb0b3c5ea756dd4e8d303e73ce21 Mon Sep 17 00:00:00 2001 From: Michael Hawkins Date: Mon, 10 Sep 2018 18:00:59 +0800 Subject: [PATCH] MDL-63497 mod_chat: Add support for removal of context users This issue is part of the MDL-62560 Epic. --- mod/chat/classes/privacy/provider.php | 54 ++++++++++++ mod/chat/tests/privacy_test.php | 122 ++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) diff --git a/mod/chat/classes/privacy/provider.php b/mod/chat/classes/privacy/provider.php index 92cf51add9f..e4d4794cc47 100644 --- a/mod/chat/classes/privacy/provider.php +++ b/mod/chat/classes/privacy/provider.php @@ -29,11 +29,15 @@ defined('MOODLE_INTERNAL') || die(); use context; use context_helper; use context_module; +use moodle_recordset; use stdClass; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\approved_userlist; +use core_privacy\local\request\contextlist; use core_privacy\local\request\helper; use core_privacy\local\request\transform; +use core_privacy\local\request\userlist; use core_privacy\local\request\writer; /** @@ -46,6 +50,7 @@ use core_privacy\local\request\writer; */ class provider implements \core_privacy\local\metadata\provider, + \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** @@ -121,6 +126,33 @@ class provider implements return $contextlist; } + /** + * Get the list of users who have data within a context. + * + * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. + */ + public static function get_users_in_context(userlist $userlist) { + $context = $userlist->get_context(); + + if (!is_a($context, \context_module::class)) { + return; + } + + $params = [ + 'instanceid' => $context->instanceid, + 'modulename' => 'chat', + ]; + + $sql = "SELECT chm.userid + FROM {course_modules} cm + JOIN {modules} m ON m.id = cm.module AND m.name = :modulename + JOIN {chat} c ON c.id = cm.instance + JOIN {chat_messages} chm ON chm.chatid = c.id + WHERE cm.id = :instanceid"; + + $userlist->add_from_sql('userid', $sql, $params); + } + /** * Export all user data for the specified user, in the specified contexts. * @@ -223,6 +255,28 @@ class provider implements $DB->delete_records_select('chat_users', $sql, $params); } + + /** + * Delete multiple users within a single context. + * + * @param approved_userlist $userlist The approved context and user information to delete information for. + */ + public static function delete_data_for_users(approved_userlist $userlist) { + global $DB; + + $context = $userlist->get_context(); + $cm = $DB->get_record('course_modules', ['id' => $context->instanceid]); + $chat = $DB->get_record('chat', ['id' => $cm->instance]); + + list($userinsql, $userinparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); + $params = array_merge(['chatid' => $chat->id], $userinparams); + $sql = "chatid = :chatid AND userid {$userinsql}"; + + $DB->delete_records_select('chat_messages', $sql, $params); + $DB->delete_records_select('chat_messages_current', $sql, $params); + $DB->delete_records_select('chat_users', $sql, $params); + } + /** * Return a dict of chat IDs mapped to their course module ID. * diff --git a/mod/chat/tests/privacy_test.php b/mod/chat/tests/privacy_test.php index e4fdf7da6a2..e3b4d9b908c 100644 --- a/mod/chat/tests/privacy_test.php +++ b/mod/chat/tests/privacy_test.php @@ -29,6 +29,7 @@ global $CFG; use core_privacy\tests\provider_testcase; use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\approved_userlist; use core_privacy\local\request\transform; use core_privacy\local\request\writer; use mod_chat\privacy\provider; @@ -96,6 +97,71 @@ class mod_chat_privacy_testcase extends provider_testcase { $this->assertTrue(in_array(context_module::instance($chat2a->cmid)->id, $contextids)); } + /** + * Test that only users with relevant contexts are fetched. + */ + public function test_get_users_in_context() { + $component = 'mod_chat'; + $dg = $this->getDataGenerator(); + $c1 = $dg->create_course(); + $c2 = $dg->create_course(); + + $u1 = $dg->create_user(); + $u2 = $dg->create_user(); + + $chat1a = $dg->create_module('chat', ['course' => $c1]); + $chat1b = $dg->create_module('chat', ['course' => $c1]); + $chat2a = $dg->create_module('chat', ['course' => $c2]); + + // Logins but no message. + $chatuser = $this->login_user_in_course_chat($u1, $c1, $chat1a); + + // Logins and messages. + $chatuser = $this->login_user_in_course_chat($u1, $c1, $chat1b); + chat_send_chatmessage($chatuser, 'Hello world!'); + + // Silent login (no system message). + $chatuser = $this->login_user_in_course_chat($u1, $c2, $chat2a, 0, true); + + // Silent login and messages. + $chatuser = $this->login_user_in_course_chat($u2, $c1, $chat1b, 0, true); + chat_send_chatmessage($chatuser, 'Ça va ?'); + chat_send_chatmessage($chatuser, 'Moi, ça va.'); + + // Silent login and messages. + $chatuser = $this->login_user_in_course_chat($u2, $c2, $chat2a); + chat_send_chatmessage($chatuser, 'What\'s happening here?'); + + $context1a = context_module::instance($chat1a->cmid); + $context1b = context_module::instance($chat1b->cmid); + $context2a = context_module::instance($chat2a->cmid); + + $userlist1a = new \core_privacy\local\request\userlist($context1a, $component); + $userlist1b = new \core_privacy\local\request\userlist($context1b, $component); + $userlist2a = new \core_privacy\local\request\userlist($context2a, $component); + \mod_chat\privacy\provider::get_users_in_context($userlist1a); + \mod_chat\privacy\provider::get_users_in_context($userlist1b); + \mod_chat\privacy\provider::get_users_in_context($userlist2a); + + // Ensure correct users are found in relevant contexts. + $this->assertCount(1, $userlist1a); + $expected = [$u1->id]; + $actual = $userlist1a->get_userids(); + $this->assertEquals($expected, $actual); + + $this->assertCount(2, $userlist1b); + $expected = [$u1->id, $u2->id]; + $actual = $userlist1b->get_userids(); + sort($expected); + sort($actual); + $this->assertEquals($expected, $actual); + + $this->assertCount(1, $userlist2a); + $expected = [$u1->id]; + $actual = $userlist1a->get_userids(); + $this->assertEquals($expected, $actual); + } + public function test_delete_data_for_all_users_in_context() { global $DB; $dg = $this->getDataGenerator(); @@ -190,6 +256,62 @@ class mod_chat_privacy_testcase extends provider_testcase { $this->assert_has_no_data_in_chat($u2, $chat1b); } + /** + * Test that data for users in approved userlist is deleted. + */ + public function test_delete_data_for_users() { + global $DB; + $component = 'mod_chat'; + $dg = $this->getDataGenerator(); + $c1 = $dg->create_course(); + + $u1 = $dg->create_user(); + $u2 = $dg->create_user(); + $u3 = $dg->create_user(); + + $chat1 = $dg->create_module('chat', ['course' => $c1]); + $chat1context = context_module::instance($chat1->cmid); + + $u1chat1 = $this->login_user_in_course_chat($u1, $c1, $chat1); + $u2chat1 = $this->login_user_in_course_chat($u2, $c1, $chat1); + $u3chat1 = $this->login_user_in_course_chat($u3, $c1, $chat1); + chat_send_chatmessage($u1chat1, 'Ça va ?'); + chat_send_chatmessage($u2chat1, 'Oui, et toi ?'); + chat_send_chatmessage($u1chat1, 'Bien merci.'); + chat_send_chatmessage($u2chat1, 'Pourquoi ils disent omelette "du" fromage ?!'); + chat_send_chatmessage($u1chat1, 'Aucune idée'); + chat_send_chatmessage($u3chat1, 'Je ne comprends pas'); + $this->assert_has_data_in_chat($u1, $chat1); + $this->assert_has_data_in_chat($u2, $chat1); + $this->assert_has_data_in_chat($u3, $chat1); + + $chat2 = $dg->create_module('chat', ['course' => $c1]); + + $u1chat2 = $this->login_user_in_course_chat($u1, $c1, $chat2); + $u2chat2 = $this->login_user_in_course_chat($u2, $c1, $chat2); + $u3chat2 = $this->login_user_in_course_chat($u3, $c1, $chat2); + chat_send_chatmessage($u1chat2, 'Why do we have a separate chat?'); + chat_send_chatmessage($u2chat2, 'I have no idea!'); + chat_send_chatmessage($u3chat2, 'Me either.'); + $this->assert_has_data_in_chat($u1, $chat2); + $this->assert_has_data_in_chat($u2, $chat2); + $this->assert_has_data_in_chat($u3, $chat2); + + // Delete user 1 and 2 data from chat 1 context only. + $approveduserids = [$u1->id, $u2->id]; + $approvedlist = new approved_userlist($chat1context, $component, $approveduserids); + provider::delete_data_for_users($approvedlist); + + // Ensure correct chat data is deleted. + $this->assert_has_no_data_in_chat($u1, $chat1); + $this->assert_has_no_data_in_chat($u2, $chat1); + $this->assert_has_data_in_chat($u3, $chat1); + + $this->assert_has_data_in_chat($u1, $chat2); + $this->assert_has_data_in_chat($u2, $chat2); + $this->assert_has_data_in_chat($u3, $chat2); + } + public function test_export_data_for_user() { global $DB; $dg = $this->getDataGenerator();