* Privacy Subsystem implementation for core_message.
*
* @package core_message
+ * @category privacy
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
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\transform;
+use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
defined('MOODLE_INTERNAL') || die();
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\subsystem\provider,
- \core_privacy\local\request\user_preference_provider {
+ \core_privacy\local\request\user_preference_provider,
+ \core_privacy\local\request\core_userlist_provider {
/**
* Return the fields which contain personal data.
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) {
+ global $DB;
+
+ $context = $userlist->get_context();
+
+ if (!$context instanceof \context_user) {
+ return;
+ }
+
+ $userid = $context->instanceid;
+
+ // Messages are in the user context.
+ // For the sake of performance, there is no need to call add_from_sql for each of the below cases.
+ // It is enough to add the user's context as soon as we come to the conclusion that the user has some data.
+ // Also, the order of checking is sorted by the probability of occurrence (just by guess).
+ // There is no need to check the message_user_actions table, as there needs to be a message in order to be a message action.
+ // So, checking messages table would suffice.
+
+ $hasdata = false;
+ $hasdata = $hasdata || $DB->record_exists_select('notifications', 'useridfrom = ? OR useridto = ?', [$userid, $userid]);
+ $hasdata = $hasdata || $DB->record_exists('message_conversation_members', ['userid' => $userid]);
+ $hasdata = $hasdata || $DB->record_exists('messages', ['useridfrom' => $userid]);
+ $hasdata = $hasdata || $DB->record_exists_select('message_contacts', 'userid = ? OR contactid = ?', [$userid, $userid]);
+ $hasdata = $hasdata || $DB->record_exists_select('message_users_blocked', 'userid = ? OR blockeduserid = ?',
+ [$userid, $userid]);
+ $hasdata = $hasdata || $DB->record_exists_select('message_contact_requests', 'userid = ? OR requesteduserid = ?',
+ [$userid, $userid]);
+
+ if ($hasdata) {
+ $userlist->add_user($userid);
+ }
+ }
+
/**
* Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
*
static::delete_user_data($userid);
}
+ /**
+ * 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) {
+ $context = $userlist->get_context();
+
+ if (!$context instanceof \context_user) {
+ return;
+ }
+
+ // Remove invalid users. If it ends up empty then early return.
+ $userids = array_filter($userlist->get_userids(), function($userid) use($context) {
+ return $context->instanceid == $userid;
+ });
+
+ if (empty($userids)) {
+ return;
+ }
+
+ static::delete_user_data($context->instanceid);
+ }
+
/**
* Delete all user data for the specified user.
*
$this->assertEquals($user3->id, $notification->useridto);
}
+ /**
+ * Test for provider::get_users_in_context() when there is no message or notification.
+ */
+ public function test_get_users_in_context_no_data() {
+ $this->resetAfterTest();
+
+ $user = $this->getDataGenerator()->create_user();
+ $usercontext = context_user::instance($user->id);
+
+ $userlist = new \core_privacy\local\request\userlist($usercontext, 'core_message');
+ \core_message\privacy\provider::get_users_in_context($userlist);
+
+ $this->assertEmpty($userlist->get_userids());
+ }
+
+ /**
+ * Test for provider::get_users_in_context() when there is a message between users.
+ */
+ public function test_get_users_in_context_with_message() {
+ $this->resetAfterTest();
+
+ $user1 = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+
+ $user1context = context_user::instance($user1->id);
+ $user2context = context_user::instance($user2->id);
+
+ $this->create_message($user1->id, $user2->id, time() - (9 * DAYSECS));
+
+ // Test for the sender.
+ $userlist = new \core_privacy\local\request\userlist($user1context, 'core_message');
+ \core_message\privacy\provider::get_users_in_context($userlist);
+ $this->assertCount(1, $userlist);
+ $userincontext = $userlist->current();
+ $this->assertEquals($user1->id, $userincontext->id);
+
+ // Test for the receiver.
+ $userlist = new \core_privacy\local\request\userlist($user2context, 'core_message');
+ \core_message\privacy\provider::get_users_in_context($userlist);
+ $this->assertCount(1, $userlist);
+ $userincontext = $userlist->current();
+ $this->assertEquals($user2->id, $userincontext->id);
+ }
+
+ /**
+ * Test for provider::get_users_in_context() when there is a notification between users.
+ */
+ public function test_get_users_in_context_with_notification() {
+ $this->resetAfterTest();
+
+ $user1 = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+
+ $user1context = context_user::instance($user1->id);
+ $user2context = context_user::instance($user2->id);
+
+ $this->create_notification($user1->id, $user2->id, time() - (9 * DAYSECS));
+
+ // Test for the sender.
+ $userlist = new \core_privacy\local\request\userlist($user1context, 'core_message');
+ \core_message\privacy\provider::get_users_in_context($userlist);
+ $this->assertCount(1, $userlist);
+ $userincontext = $userlist->current();
+ $this->assertEquals($user1->id, $userincontext->id);
+
+ // Test for the receiver.
+ $userlist = new \core_privacy\local\request\userlist($user2context, 'core_message');
+ \core_message\privacy\provider::get_users_in_context($userlist);
+ $this->assertCount(1, $userlist);
+ $userincontext = $userlist->current();
+ $this->assertEquals($user2->id, $userincontext->id);
+ }
+
+ /**
+ * Test for provider::delete_data_for_users().
+ */
+ public function test_delete_data_for_users() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ // Create users to test with.
+ $user1 = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $user3 = $this->getDataGenerator()->create_user();
+ $user4 = $this->getDataGenerator()->create_user();
+ $user5 = $this->getDataGenerator()->create_user();
+ $user6 = $this->getDataGenerator()->create_user();
+
+ $now = time();
+ $timeread = $now - DAYSECS;
+
+ // Create contacts.
+ \core_message\api::add_contact($user1->id, $user2->id);
+ \core_message\api::add_contact($user2->id, $user3->id);
+
+ // Create contact requests.
+ \core_message\api::create_contact_request($user1->id, $user3->id);
+ \core_message\api::create_contact_request($user2->id, $user4->id);
+
+ // Block users.
+ \core_message\api::block_user($user1->id, $user5->id);
+ \core_message\api::block_user($user2->id, $user6->id);
+
+ // Create messages.
+ $m1 = $this->create_message($user1->id, $user2->id, $now + (9 * DAYSECS), $timeread);
+ $m2 = $this->create_message($user2->id, $user1->id, $now + (8 * DAYSECS));
+
+ // Create notifications.
+ $n1 = $this->create_notification($user1->id, $user2->id, $now + (9 * DAYSECS), $timeread);
+ $n2 = $this->create_notification($user2->id, $user1->id, $now + (8 * DAYSECS));
+ $n2 = $this->create_notification($user2->id, $user3->id, $now + (8 * DAYSECS));
+
+ // Delete one of the messages.
+ \core_message\api::delete_message($user1->id, $m2);
+
+ // There should be 2 contacts.
+ $this->assertEquals(2, $DB->count_records('message_contacts'));
+
+ // There should be 1 contact request.
+ $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+
+ // There should be 1 blocked user.
+ $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+
+ // There should be two messages.
+ $this->assertEquals(2, $DB->count_records('messages'));
+
+ // There should be two user actions - one for reading the message, one for deleting.
+ $this->assertEquals(2, $DB->count_records('message_user_actions'));
+
+ // There should be two conversation members.
+ $this->assertEquals(2, $DB->count_records('message_conversation_members'));
+
+ // There should be three notifications + two for the contact requests.
+ $this->assertEquals(5, $DB->count_records('notifications'));
+
+ $user1context = context_user::instance($user1->id);
+ $approveduserlist = new \core_privacy\local\request\approved_userlist($user1context, 'core_message',
+ [$user1->id, $user2->id]);
+ provider::delete_data_for_users($approveduserlist);
+
+ // Only user1's data should be deleted. User2 should be skipped as user2 is an invalid user for user1context.
+
+ // Confirm the user 2 data still exists.
+ $contacts = $DB->get_records('message_contacts');
+ $contactrequests = $DB->get_records('message_contact_requests');
+ $blockedusers = $DB->get_records('message_users_blocked');
+ $messages = $DB->get_records('messages');
+ $muas = $DB->get_records('message_user_actions');
+ $mcms = $DB->get_records('message_conversation_members');
+ $notifications = $DB->get_records('notifications');
+
+ $this->assertCount(1, $contacts);
+ $contact = reset($contacts);
+ $this->assertEquals($user2->id, $contact->userid);
+ $this->assertEquals($user3->id, $contact->contactid);
+
+ $this->assertCount(1, $contactrequests);
+ $contactrequest = reset($contactrequests);
+ $this->assertEquals($user2->id, $contactrequest->userid);
+ $this->assertEquals($user4->id, $contactrequest->requesteduserid);
+
+ $this->assertCount(1, $blockedusers);
+ $blockeduser = reset($blockedusers);
+ $this->assertEquals($user2->id, $blockeduser->userid);
+ $this->assertEquals($user6->id, $blockeduser->blockeduserid);
+
+ $this->assertCount(1, $messages);
+ $message = reset($messages);
+ $this->assertEquals($m2, $message->id);
+
+ $this->assertCount(1, $muas);
+ $mua = reset($muas);
+ $this->assertEquals($user2->id, $mua->userid);
+ $this->assertEquals($m1, $mua->messageid);
+ $this->assertEquals(\core_message\api::MESSAGE_ACTION_READ, $mua->action);
+
+ $this->assertCount(1, $mcms);
+ $mcm = reset($mcms);
+ $this->assertEquals($user2->id, $mcm->userid);
+
+ $this->assertCount(2, $notifications);
+ ksort($notifications);
+
+ $notification = array_shift($notifications);
+ $this->assertEquals($user2->id, $notification->useridfrom);
+ $this->assertEquals($user4->id, $notification->useridto);
+
+ $notification = array_shift($notifications);
+ $this->assertEquals($user2->id, $notification->useridfrom);
+ $this->assertEquals($user3->id, $notification->useridto);
+ }
+
/**
* Creates a message to be used for testing.
*