MDL-63692 core_message: Add conversation support to Privacy API
authorSara Arjona <sara@moodle.com>
Mon, 12 Nov 2018 18:09:21 +0000 (19:09 +0100)
committerSara Arjona <sara@moodle.com>
Mon, 19 Nov 2018 21:08:22 +0000 (22:08 +0100)
lang/en/message.php
message/classes/privacy/provider.php
message/tests/privacy_provider_test.php

index 670c6c5..2ec14d9 100644 (file)
@@ -153,6 +153,7 @@ $string['privacy:metadata:notifications:useridfrom'] = 'The ID of the user who s
 $string['privacy:metadata:notifications:useridto'] = 'The ID of the user who received the notification';
 $string['privacy:metadata:preference:core_message_settings'] = 'Settings related to messaging';
 $string['privacy:request:preference:set'] = 'The value of the setting \'{$a->name}\' was \'{$a->value}\'';
+$string['privacy:export:conversationprefix'] = 'Conversation: ';
 $string['processorsettings'] = 'Processor settings';
 $string['removecontact'] = 'Remove contact';
 $string['removecoursefilter'] = 'Remove filter for course {$a}';
index 5c24544..adee354 100644 (file)
@@ -41,10 +41,20 @@ defined('MOODLE_INTERNAL') || die();
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class provider implements
+    // The messaging subsystem contains data.
     \core_privacy\local\metadata\provider,
+
+    // The messaging subsystem provides all the messages at user context - i.e. individual ones.
     \core_privacy\local\request\subsystem\provider,
+
+    // This plugin has some sitewide user preferences to export.
     \core_privacy\local\request\user_preference_provider,
-    \core_privacy\local\request\core_userlist_provider {
+
+    // This plugin is capable of determining which users have data within it.
+    \core_privacy\local\request\core_userlist_provider,
+
+    // The messaging subsystem provides a data service to other components.
+    \core_privacy\local\request\subsystem\plugin_provider {
 
     /**
      * Return the fields which contain personal data.
@@ -192,8 +202,18 @@ class provider implements
 
         $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]);
+        $sql = "SELECT mc.id
+              FROM {message_conversations} mc
+              JOIN {message_conversation_members} mcm
+                ON (mcm.conversationid = mc.id AND mcm.userid = :userid)
+             WHERE mc.contextid IS NULL";
+        $hasdata = $hasdata || $DB->record_exists_sql($sql, ['userid' => $userid]);
+        $sql = "SELECT mc.id
+              FROM {message_conversations} mc
+              JOIN {messages} m
+                ON (m.conversationid = mc.id AND m.useridfrom = :useridfrom)
+             WHERE mc.contextid IS NULL";
+        $hasdata = $hasdata || $DB->record_exists_sql($sql, ['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]);
@@ -232,8 +252,18 @@ class provider implements
 
         $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]);
+        $sql = "SELECT mc.id
+              FROM {message_conversations} mc
+              JOIN {message_conversation_members} mcm
+                ON (mcm.conversationid = mc.id AND mcm.userid = :userid)
+             WHERE mc.contextid IS NULL";
+        $hasdata = $hasdata || $DB->record_exists_sql($sql, ['userid' => $userid]);
+        $sql = "SELECT mc.id
+              FROM {message_conversations} mc
+              JOIN {messages} m
+                ON (m.conversationid = mc.id AND m.useridfrom = :useridfrom)
+             WHERE mc.contextid IS NULL";
+        $hasdata = $hasdata || $DB->record_exists_sql($sql, ['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]);
@@ -278,8 +308,9 @@ class provider implements
         // Export the notifications.
         self::export_user_data_notifications($userid);
 
-        // Export the messages, with any related actions.
-        self::export_user_data_messages($userid);
+        // Conversations with empty contextid should be exported here because they are not related to any component/itemid.
+        $context = reset($contexts);
+        self::export_conversations($userid, '', '', $context);
     }
 
     /**
@@ -341,6 +372,349 @@ class provider implements
         static::delete_user_data($context->instanceid);
     }
 
+    /**
+     * Provide a list of contexts which have conversations for the user, in the respective area (component/itemtype combination).
+     *
+     * This method is to be called by consumers of the messaging subsystem (plugins), in their get_contexts_for_userid() method,
+     * to add the contexts for items which may have any conversation, but would normally not be reported as having user data by the
+     * plugin responsible for them.
+     *
+     * @param contextlist $contextlist
+     * @param int $userid The id of the user in scope.
+     * @param string $component the frankenstyle component name.
+     * @param string $itemtype the type of the conversation items.
+     * @param int $itemid Optional itemid associated with component.
+     */
+    public static function add_contexts_for_conversations(contextlist $contextlist, int $userid, string $component,
+                                                          string $itemtype, int $itemid = 0) {
+        // Search for conversations for this user in this area.
+        $sql = "SELECT mc.contextid
+                  FROM {message_conversations} mc
+                  JOIN {message_conversation_members} mcm
+                    ON (mcm.conversationid = mc.id AND mcm.userid = :userid)
+                  JOIN {context} ctx
+                    ON mc.contextid = ctx.id
+                 WHERE mc.component = :component AND mc.itemtype = :itemtype";
+        $params = [
+            'userid' => $userid,
+            'component' => $component,
+            'itemtype' => $itemtype,
+        ];
+
+        if (!empty($itemid)) {
+            $sql .= " AND itemid = :itemid";
+            $params['itemid'] = $itemid;
+        }
+
+        $contextlist->add_from_sql($sql, $params);
+    }
+
+    /**
+     * Add the list of users who have a conversation in the specified area (component + itemtype + itemid).
+     *
+     * @param userlist $userlist The userlist to add the users to.
+     * @param string $component The component to check.
+     * @param string $itemtype The type of the conversation items.
+     * @param int $itemid Optional itemid associated with component.
+     */
+    public static function add_conversations_in_context(userlist $userlist, string $component, string $itemtype, int $itemid = 0) {
+        $sql = "SELECT mcm.userid
+                  FROM {message_conversation_members} mcm
+            INNER JOIN {message_conversations} mc
+                    ON mc.id = mcm.conversationid
+                 WHERE mc.contextid = :contextid AND mc.component = :component AND mc.itemtype = :itemtype";
+        $params = [
+            'contextid' => $userlist->get_context()->id,
+            'component' => $component,
+            'itemtype' => $itemtype
+        ];
+
+        if (!empty($itemid)) {
+            $sql .= " AND itemid = :itemid";
+            $params['itemid'] = $itemid;
+        }
+
+        $userlist->add_from_sql('userid', $sql, $params);
+    }
+
+    /**
+     * Store all conversations which match the specified component, itemtype, and itemid.
+     *
+     * Conversations without context (for now, the private ones) are stored in '<$context> | Messages | <Other user id>'.
+     * Conversations with context are stored in '<$context> | Messages | <Conversation item type> | <Conversation name>'.
+     *
+     * @param   int         $userid The user whose information is to be exported.
+     * @param   string      $component The component to fetch data from.
+     * @param   string      $itemtype The itemtype that the data was exported in within the component.
+     * @param   \context    $context The context to export for.
+     * @param   array       $subcontext The sub-context in which to export this data.
+     * @param   int         $itemid Optional itemid associated with component.
+     */
+    public static function export_conversations(int $userid, string $component, string $itemtype, \context $context,
+                                                array $subcontext = [], int $itemid = 0) {
+        global $DB;
+
+        // Search for conversations for this user in this area.
+        $sql = "SELECT DISTINCT mc.*
+                  FROM {message_conversations} mc
+                  JOIN {message_conversation_members} mcm
+                    ON (mcm.conversationid = mc.id AND mcm.userid = :userid)";
+        $params = [
+            'userid' => $userid
+        ];
+
+        // Get the conversations for the defined component and itemtype.
+        if (!empty($component) && !empty($itemtype)) {
+            $sql .= " WHERE mc.component = :component AND mc.itemtype = :itemtype";
+            $params['component'] = $component;
+            $params['itemtype'] = $itemtype;
+            if (!empty($itemid)) {
+                $sql .= " AND mc.itemid = :itemid";
+                $params['itemid'] = $itemid;
+            }
+        } else {
+            // Get all the conversations without any component and itemtype, so with null contextid.
+            $sql .= " WHERE mc.contextid IS NULL";
+        }
+
+        if ($conversations = $DB->get_records_sql($sql, $params)) {
+            // Export conversation messages.
+            foreach ($conversations as $conversation) {
+                self::export_user_data_conversation_messages($userid, $conversation, $context, $subcontext);
+            }
+        }
+    }
+
+    /**
+     * Deletes all group memberships for a specified context and component.
+     *
+     * @param \context  $context    Details about which context to delete group memberships for.
+     * @param string    $component  The component to delete. Empty string means no component.
+     * @param string    $itemtype   The itemtype of the component to delele. Empty string means no itemtype.
+     * @param int       $itemid     Optional itemid associated with component.
+     */
+    public static function delete_conversations_for_all_users(\context $context, string $component, string $itemtype,
+                                                              int $itemid = 0) {
+        global $DB;
+
+        if (empty($context)) {
+            return;
+        }
+
+        $select = "contextid = :contextid AND component = :component AND itemtype = :itemtype";
+        $params = [
+            'contextid' => $context->id,
+            'component' => $component,
+            'itemtype' => $itemtype
+        ];
+
+        if (!empty($itemid)) {
+            $select .= " AND itemid = :itemid";
+            $params['itemid'] = $itemid;
+        }
+
+        // Get and remove all the conversations and messages for the specified context and area.
+        if ($conversationids = $DB->get_records_select('message_conversations', $select, $params, '', 'id')) {
+            $conversationids = array_keys($conversationids);
+            $messageids = $DB->get_records_list('messages', 'conversationid', $conversationids);
+            $messageids = array_keys($messageids);
+
+            // Delete messages and user_actions.
+            $DB->delete_records_list('message_user_actions', 'messageid', $messageids);
+            $DB->delete_records_list('messages', 'id', $messageids);
+
+            // Delete members and conversations.
+            $DB->delete_records_list('message_conversation_members', 'conversationid', $conversationids);
+            $DB->delete_records_list('message_conversations', 'id', $conversationids);
+        }
+    }
+
+    /**
+     * Deletes all records for a user from a list of approved contexts.
+     *
+     * When the component and the itemtype are empty and there is only one user context in the list, all the
+     * conversations without contextid will be removed. However, if the component and itemtype are defined,
+     * only the conversations in these area for the contexts in $contextlist wil be deleted.
+     *
+     * @param approved_contextlist  $contextlist    Contains the user ID and a list of contexts to be deleted from.
+     * @param string    $component  The component to delete. Empty string means no component.
+     * @param string    $itemtype   The itemtype of the component to delele. Empty string means no itemtype.
+     * @param int       $itemid     Optional itemid associated with component.
+     */
+    public static function delete_conversations_for_user(approved_contextlist $contextlist, string $component, string $itemtype,
+                                                         int $itemid = 0) {
+        self::delete_user_data_conversations(
+            $contextlist->get_user()->id,
+            $contextlist->get_contextids(),
+            $component,
+            $itemtype,
+            $itemid
+        );
+    }
+
+    /**
+     * Deletes all records for multiple users within a single context.
+     *
+     * @param approved_userlist $userlist  The approved context and user information to delete information for.
+     * @param string    $component  The component to delete. Empty string means no component.
+     * @param string    $itemtype   The itemtype of the component to delele. Empty string means no itemtype.
+     * @param int       $itemid     Optional itemid associated with component.
+     */
+    public static function delete_conversations_for_users(approved_userlist $userlist, string $component, string $itemtype,
+                                                          int $itemid = 0) {
+        global $DB;
+
+        $userids = $userlist->get_userids();
+        if (empty($userids)) {
+            return;
+        }
+
+        $context = $userlist->get_context();
+        $select = "mc.contextid = :contextid AND mc.component = :component AND mc.itemtype = :itemtype";
+        $params = [
+            'contextid' => $context->id,
+            'component' => $component,
+            'itemtype' => $itemtype
+        ];
+        if (!empty($itemid)) {
+            $select .= " AND itemid = :itemid";
+            $params['itemid'] = $itemid;
+        }
+
+        // Get conversations in this area where the specified users are a member of.
+        list($useridsql, $useridparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
+        $sql = "SELECT DISTINCT mcm.conversationid as id
+                  FROM {message_conversation_members} mcm
+            INNER JOIN {message_conversations} mc
+                    ON mc.id = mcm.conversationid
+                 WHERE mcm.userid $useridsql AND $select";
+        $params += $useridparams;
+        $conversationids = array_keys($DB->get_records_sql($sql, $params));
+        if (!empty($conversationids)) {
+            list($conversationidsql, $conversationidparams) = $DB->get_in_or_equal($conversationids, SQL_PARAMS_NAMED);
+
+            // Get all the messages for these conversations which has some action stored for these users.
+            $sql = "SELECT DISTINCT m.id
+                      FROM {messages} m
+                INNER JOIN {message_conversations} mc
+                        ON mc.id = m.conversationid
+                INNER JOIN {message_user_actions} mua
+                        ON mua.messageid = m.id
+                     WHERE mua.userid $useridsql  AND mc.id $conversationidsql";
+            $params = $useridparams + $conversationidparams;
+            $messageids = array_keys($DB->get_records_sql($sql, $params));
+            if (!empty($messageids)) {
+                // Delete all the user_actions for the messages on these conversations where the user has any action.
+                list($messageidsql, $messageidparams) = $DB->get_in_or_equal($messageids, SQL_PARAMS_NAMED);
+                $select = "messageid $messageidsql AND userid $useridsql";
+                $DB->delete_records_select('message_user_actions', $select, $messageidparams + $useridparams);
+            }
+
+            // Get all the messages for these conversations sent by these users.
+            $sql = "SELECT DISTINCT m.id
+                      FROM {messages} m
+                     WHERE m.useridfrom $useridsql AND m.conversationid $conversationidsql";
+            // Reuse the $params var because it contains the useridparams and the conversationids.
+            $messageids = array_keys($DB->get_records_sql($sql, $params));
+            if (!empty($messageids)) {
+                // Delete all the user_actions for the messages sent by any of these users.
+                $DB->delete_records_list('message_user_actions', 'messageid', $messageids);
+
+                // Delete all the messages sent by any of these users.
+                $DB->delete_records_list('messages', 'id', $messageids);
+            }
+
+            // In that case, conversations can't be removed, because they could have more members and messages.
+            // So, remove only users from the context conversations where they are member of.
+            $sql = "conversationid $conversationidsql AND userid $useridsql";
+            // Reuse the $params var because it contains the useridparams and the conversationids.
+            $DB->delete_records_select('message_conversation_members', $sql, $params);
+        }
+    }
+
+    /**
+     * Deletes all records for multiple users within multiple contexts in a component area.
+     *
+     * @param  int    $userid     The user identifier to delete information for.
+     * @param  array  $contextids The context identifiers to delete information for. Empty array means no context (for
+     *                            individual conversations).
+     * @param  string $component  The component to delete. Empty string means no component (for individual conversations).
+     * @param  string $itemtype   The itemtype of the component to delele. Empty string means no itemtype (for individual
+     *                            conversations).
+     * @param  int    $itemid     Optional itemid associated with component.
+     */
+    protected static function delete_user_data_conversations(int $userid, array $contextids, string $component,
+                                                            string $itemtype, int $itemid = 0) {
+        global $DB;
+
+        if (empty($contextids) && empty($component) && empty($itemtype) && empty($itemid)) {
+            // Individual conversations haven't context, component neither itemtype.
+            $select = "mc.contextid IS NULL";
+            $params = [];
+        } else {
+            list($contextidsql, $contextidparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
+            $select = "mc.contextid $contextidsql AND mc.component = :component AND mc.itemtype = :itemtype";
+            $params = [
+                'component' => $component,
+                'itemtype' => $itemtype
+            ];
+            $params += $contextidparams;
+            if (!empty($itemid)) {
+                $select .= " AND itemid = :itemid";
+                $params['itemid'] = $itemid;
+            }
+        }
+
+        // Get conversations in these contexts where the specified userid is a member of.
+        $sql = "SELECT DISTINCT mcm.conversationid as id
+                  FROM {message_conversation_members} mcm
+            INNER JOIN {message_conversations} mc
+                    ON mc.id = mcm.conversationid
+                 WHERE mcm.userid = :userid AND $select";
+        $params['userid'] = $userid;
+        $conversationids = array_keys($DB->get_records_sql($sql, $params));
+        if (!empty($conversationids)) {
+            list($conversationidsql, $conversationidparams) = $DB->get_in_or_equal($conversationids, SQL_PARAMS_NAMED);
+
+            // Get all the messages for these conversations which has some action stored for the userid.
+            $sql = "SELECT DISTINCT m.id
+                      FROM {messages} m
+                INNER JOIN {message_conversations} mc
+                        ON mc.id = m.conversationid
+                INNER JOIN {message_user_actions} mua
+                        ON mua.messageid = m.id
+                     WHERE mua.userid = :userid AND mc.id $conversationidsql";
+            $params = ['userid' => $userid] + $conversationidparams;
+            $messageids = array_keys($DB->get_records_sql($sql, $params));
+            if (!empty($messageids)) {
+                // Delete all the user_actions for the messages on these conversations where the user has any action.
+                list($messageidsql, $messageidparams) = $DB->get_in_or_equal($messageids, SQL_PARAMS_NAMED);
+                $select = "messageid $messageidsql AND userid = :userid";
+                $DB->delete_records_select('message_user_actions', $select, $messageidparams + ['userid' => $userid]);
+            }
+
+            // Get all the messages for these conversations sent by the userid.
+            $sql = "SELECT DISTINCT m.id
+                      FROM {messages} m
+                     WHERE m.useridfrom = :userid AND m.conversationid $conversationidsql";
+            // Reuse the $params var because it contains the userid and the conversationids.
+            $messageids = array_keys($DB->get_records_sql($sql, $params));
+            if (!empty($messageids)) {
+                // Delete all the user_actions for the messages sent by the userid.
+                $DB->delete_records_list('message_user_actions', 'messageid', $messageids);
+
+                // Delete all the messages sent by the userid.
+                $DB->delete_records_list('messages', 'id', $messageids);
+            }
+
+            // In that case, conversations can't be removed, because they could have more members and messages.
+            // So, remove only userid from the context conversations where he/she is member of.
+            $sql = "conversationid $conversationidsql AND userid = :userid";
+            // Reuse the $params var because it contains the userid and the conversationids.
+            $DB->delete_records_select('message_conversation_members', $sql, $params);
+        }
+    }
+
     /**
      * Delete all user data for the specified user.
      *
@@ -349,9 +723,10 @@ class provider implements
     protected static function delete_user_data(int $userid) {
         global $DB;
 
-        $DB->delete_records('messages', ['useridfrom' => $userid]);
-        $DB->delete_records('message_user_actions', ['userid' => $userid]);
-        $DB->delete_records('message_conversation_members', ['userid' => $userid]);
+        // Delete individual conversations information for this user.
+        self::delete_user_data_conversations($userid, [], '', '');
+
+        // Delete contacts, requests, users blocked and notifications.
         $DB->delete_records_select('message_contacts', 'userid = ? OR contactid = ?', [$userid, $userid]);
         $DB->delete_records_select('message_contact_requests', 'userid = ? OR requesteduserid = ?', [$userid, $userid]);
         $DB->delete_records_select('message_users_blocked', 'userid = ? OR blockeduserid = ?', [$userid, $userid]);
@@ -434,74 +809,89 @@ class provider implements
     }
 
     /**
-     * Export the messaging data.
+     * Export conversation messages.
      *
-     * @param int $userid
+     * @param int $userid The user identifier.
+     * @param \stdClass $conversation The conversation to export the messages.
+     * @param \context $context The context to export for.
+     * @param array $subcontext The sub-context in which to export this data.
      */
-    protected static function export_user_data_messages(int $userid) {
+    protected static function export_user_data_conversation_messages(int $userid, \stdClass $conversation, \context $context,
+                                                                     array $subcontext = []) {
         global $DB;
 
-        $context = \context_user::instance($userid);
+        // Get all the messages for this conversation from start to finish.
+        $sql = "SELECT m.*, muadelete.timecreated as timedeleted, muaread.timecreated as timeread
+                  FROM {messages} m
+             LEFT JOIN {message_user_actions} muadelete
+                    ON m.id = muadelete.messageid AND muadelete.action = :deleteaction AND muadelete.userid = :deleteuserid
+             LEFT JOIN {message_user_actions} muaread
+                    ON m.id = muaread.messageid AND muaread.action = :readaction AND muaread.userid = :readuserid
+                 WHERE conversationid = :conversationid
+              ORDER BY m.timecreated ASC";
+        $messages = $DB->get_recordset_sql($sql, ['deleteaction' => \core_message\api::MESSAGE_ACTION_DELETED,
+            'readaction' => \core_message\api::MESSAGE_ACTION_READ, 'conversationid' => $conversation->id,
+            'deleteuserid' => $userid, 'readuserid' => $userid]);
+        $messagedata = [];
+        foreach ($messages as $message) {
+            $timeread = !is_null($message->timeread) ? transform::datetime($message->timeread) : '-';
+            $issender = $userid == $message->useridfrom;
+
+            $data = [
+                'issender' => transform::yesno($issender),
+                'message' => message_format_message_text($message),
+                'timecreated' => transform::datetime($message->timecreated),
+                'timeread' => $timeread
+            ];
+            if ($conversation->type == \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP && !$issender) {
+                // Only export sender for group conversations when is not the current user.
+                $data['sender'] = transform::user($message->useridfrom);
+            }
 
-        $sql = "SELECT DISTINCT mcm.conversationid as id
-                  FROM {message_conversation_members} mcm
-                 WHERE mcm.userid = :userid";
-        if ($conversations = $DB->get_records_sql($sql, ['userid' => $userid])) {
-            // Ok, let's get the other users in the conversations.
-            $conversationids = array_keys($conversations);
-            list($conversationidsql, $conversationparams) = $DB->get_in_or_equal($conversationids, SQL_PARAMS_NAMED);
-            $userfields = \user_picture::fields('u');
-            $userssql = "SELECT mcm.conversationid, $userfields
-                           FROM {user} u
-                     INNER JOIN {message_conversation_members} mcm
-                             ON u.id = mcm.userid
-                          WHERE mcm.conversationid $conversationidsql
-                            AND mcm.userid != :userid
-                            AND u.deleted = 0";
-            $otherusers = $DB->get_records_sql($userssql, $conversationparams + ['userid' => $userid]);
-            foreach ($conversations as $conversation) {
-                $otheruserfullname = get_string('unknownuser', 'core_message');
+            if (!is_null($message->timedeleted)) {
+                $data['timedeleted'] = transform::datetime($message->timedeleted);
+            }
 
-                // It's possible the other user has requested to be deleted, so might not exist
-                // as a conversation member, or they have just been deleted.
-                if (isset($otherusers[$conversation->id])) {
-                    $otheruserfullname = fullname($otherusers[$conversation->id]);
+            $messagedata[] = (object) $data;
+        }
+        $messages->close();
+
+        if (!empty($messagedata)) {
+            // Get subcontext.
+            if (empty($conversation->contextid)) {
+                // Conversations without context are stored in 'Messages | <Other user id>'.
+                $members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation->id]);
+                $members = array_filter($members, function ($member) use ($userid) {
+                    return $member->userid != $userid;
+                });
+                if ($otheruser = reset($members)) {
+                    $otherusertext = $otheruser->userid;
+                } else {
+                    $otherusertext = get_string('unknownuser', 'core_message') . '_' . $conversation->id;
                 }
 
-                // Get all the messages for this conversation from start to finish.
-                $sql = "SELECT m.*, muadelete.timecreated as timedeleted, muaread.timecreated as timeread
-                          FROM {messages} m
-                     LEFT JOIN {message_user_actions} muadelete
-                            ON m.id = muadelete.messageid AND muadelete.action = :deleteaction
-                     LEFT JOIN {message_user_actions} muaread
-                            ON m.id = muaread.messageid AND muaread.action = :readaction
-                         WHERE conversationid = :conversationid
-                      ORDER BY m.timecreated ASC";
-                $messages = $DB->get_recordset_sql($sql, ['deleteaction' => \core_message\api::MESSAGE_ACTION_DELETED,
-                    'readaction' => \core_message\api::MESSAGE_ACTION_READ, 'conversationid' => $conversation->id]);
-                $messagedata = [];
-                foreach ($messages as $message) {
-                    $timeread = !is_null($message->timeread) ? transform::datetime($message->timeread) : '-';
-                    $issender = $userid == $message->useridfrom;
-
-                    $data = [
-                        'sender' => transform::yesno($issender),
-                        'message' => message_format_message_text($message),
-                        'timecreated' => transform::datetime($message->timecreated),
-                        'timeread' => $timeread
-                    ];
-
-                    if (!is_null($message->timedeleted)) {
-                        $data['timedeleted'] = transform::datetime($message->timedeleted);
-                    }
-
-                    $messagedata[] = (object) $data;
+                $subcontext = array_merge(
+                    $subcontext,
+                    [get_string('messages', 'core_message'), $otherusertext]
+                );
+            } else {
+                // Conversations with context are stored in 'Messages | <Conversation item type> | <Conversation name>'.
+                if (get_string_manager()->string_exists($conversation->itemtype, $conversation->component)) {
+                    $itemtypestring = get_string($conversation->itemtype, $conversation->component);
+                } else {
+                    // If the itemtype doesn't exist in the component string file, the raw itemtype will be returned.
+                    $itemtypestring = $conversation->itemtype;
                 }
-                $messages->close();
 
-                writer::with_context($context)->export_data([get_string('messages', 'core_message'), $otheruserfullname],
-                    (object) $messagedata);
+                $conversationname = get_string('privacy:export:conversationprefix', 'core_message') . $conversation->name;
+                $subcontext = array_merge(
+                    $subcontext,
+                    [get_string('messages', 'core_message'), $itemtypestring, $conversationname]
+                );
             }
+
+            // Export the conversation messages.
+            writer::with_context($context)->export_data($subcontext, (object) $messagedata);
         }
     }
 
index 101adbb..1a63583 100644 (file)
 
 use core_privacy\local\metadata\collection;
 use core_message\privacy\provider;
+use \core_privacy\local\request\contextlist;
 use \core_privacy\local\request\writer;
 use \core_privacy\local\request\transform;
+use \core_message\tests\helper as testhelper;
 
 defined('MOODLE_INTERNAL') || die();
 
@@ -203,37 +205,138 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
     }
 
     /**
-     * Test for provider::get_contexts_for_userid() when there is a message between users.
+     * Test for provider::get_contexts_for_userid() when there is a private message between users.
      */
-    public function test_get_contexts_for_userid_with_message() {
+    public function test_get_contexts_for_userid_with_private_messages() {
         $this->resetAfterTest();
 
         $user1 = $this->getDataGenerator()->create_user();
         $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
 
-        // Test nothing is found before message is sent.
+        // Test nothing is found before group conversations is created or message is sent.
         $contextlist = provider::get_contexts_for_userid($user1->id);
         $this->assertCount(0, $contextlist);
         $contextlist = provider::get_contexts_for_userid($user2->id);
         $this->assertCount(0, $contextlist);
 
-        $this->create_message($user1->id, $user2->id, time() - (9 * DAYSECS));
+        // Send some private messages.
+        $pm1id = $this->create_message($user1->id, $user2->id, time() - (9 * DAYSECS));
 
-        // Test for the sender.
+        // Test for the sender (user1).
         $contextlist = provider::get_contexts_for_userid($user1->id);
         $this->assertCount(1, $contextlist);
         $contextforuser = $contextlist->current();
         $this->assertEquals(
-                context_user::instance($user1->id)->id,
+                \context_user::instance($user1->id)->id,
                 $contextforuser->id);
 
-        // Test for the receiver.
+        // Test for the receiver (user2).
         $contextlist = provider::get_contexts_for_userid($user2->id);
         $this->assertCount(1, $contextlist);
         $contextforuser = $contextlist->current();
         $this->assertEquals(
-                context_user::instance($user2->id)->id,
+                \context_user::instance($user2->id)->id,
+                $contextforuser->id);
+
+        // Test for user3 (no private messages).
+        $contextlist = provider::get_contexts_for_userid($user3->id);
+        $this->assertCount(0, $contextlist);
+    }
+
+    /**
+     * Test for provider::get_contexts_for_userid() when there is several messages (private and group).
+     */
+    public function test_get_contexts_for_userid_with_messages() {
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $user4 = $this->getDataGenerator()->create_user();
+
+        // Test nothing is found before group conversations is created or message is sent.
+        $contextlist = provider::get_contexts_for_userid($user1->id);
+        $this->assertCount(0, $contextlist);
+        $contextlist = provider::get_contexts_for_userid($user2->id);
+        $this->assertCount(0, $contextlist);
+
+        // Create course.
+        $course1 = $this->getDataGenerator()->create_course();
+        $coursecontext1 = context_course::instance($course1->id);
+
+        // Enrol users to courses.
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user3->id, $course1->id);
+
+        // Create groups (only one with enablemessaging = 1).
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
+
+        // Add users to groups.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user3->id));
+
+        // Get conversation.
+        $component = 'core_group';
+        $itemtype = 'groups';
+        $conversation1 = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group1a->id,
+            $coursecontext1->id
+        );
+
+        // Send some messages to the group conversation.
+        $now = time();
+        $m1id = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1', $now + 1);
+        $m2id = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 2', $now + 2);
+        $m3id = testhelper::send_fake_message_to_conversation($user2, $conversation1->id, 'Message 3', $now + 3);
+
+        // Test for user1 (although is member of the conversation, hasn't any private message).
+        $contextlist = provider::get_contexts_for_userid($user1->id);
+        $this->assertCount(0, $contextlist);
+
+        // Test for user2 (although is member of the conversation, hasn't any private message).
+        $contextlist = provider::get_contexts_for_userid($user2->id);
+        $this->assertCount(0, $contextlist);
+
+        // Test for user3 (although is member of the conversation, hasn't any private message).
+        $contextlist = provider::get_contexts_for_userid($user3->id);
+        $this->assertCount(0, $contextlist);
+
+        // Test for user4 (doesn't belong to the conversation).
+        $contextlist = provider::get_contexts_for_userid($user4->id);
+        $this->assertCount(0, $contextlist);
+
+        // Send some private messages.
+        $pm1id = $this->create_message($user1->id, $user2->id, time() - (9 * DAYSECS));
+
+        // Test user1 now has the user context because of the private message.
+        $contextlist = provider::get_contexts_for_userid($user1->id);
+        $this->assertCount(1, $contextlist);
+        $contextforuser = $contextlist->current();
+        $this->assertEquals(
+                \context_user::instance($user1->id)->id,
+                $contextforuser->id);
+
+        // Test user2 now has the user context because of the private message.
+        $contextlist = provider::get_contexts_for_userid($user2->id);
+        $this->assertCount(1, $contextlist);
+        $contextforuser = $contextlist->current();
+        $this->assertEquals(
+                \context_user::instance($user2->id)->id,
                 $contextforuser->id);
+
+        // Test for user3 (although is member of the conversation, hasn't still any private message).
+        $contextlist = provider::get_contexts_for_userid($user3->id);
+        $this->assertCount(0, $contextlist);
+
+        // Test for user4 (doesn't belong to the conversation and hasn't any private message).
+        $contextlist = provider::get_contexts_for_userid($user4->id);
+        $this->assertCount(0, $contextlist);
     }
 
     /**
@@ -487,7 +590,7 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
     /**
      * Test for provider::export_user_data().
      */
-    public function test_export_for_context_with_messages() {
+    public function test_export_for_context_with_private_messages() {
         global $DB;
 
         $this->resetAfterTest();
@@ -527,7 +630,7 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $this->assertTrue($writer->has_any_data());
 
         // Confirm the messages with user 2 are correct.
-        $messages = (array) $writer->get_data([get_string('messages', 'core_message'), fullname($user2)]);
+        $messages = (array) $writer->get_data([get_string('messages', 'core_message'), $user2->id]);
         $this->assertCount(3, $messages);
 
         $dbm1 = $DB->get_record('messages', ['id' => $m1]);
@@ -539,25 +642,25 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $m2 = array_shift($messages);
         $m3 = array_shift($messages);
 
-        $this->assertEquals(get_string('yes'), $m1->sender);
+        $this->assertEquals(get_string('yes'), $m1->issender);
         $this->assertEquals(message_format_message_text($dbm1), $m1->message);
         $this->assertEquals(transform::datetime($now - (9 * DAYSECS)), $m1->timecreated);
-        $this->assertNotEquals('-', $m1->timeread);
+        $this->assertEquals('-', $m1->timeread);
         $this->assertArrayNotHasKey('timedeleted', (array) $m1);
 
-        $this->assertEquals(get_string('no'), $m2->sender);
+        $this->assertEquals(get_string('no'), $m2->issender);
         $this->assertEquals(message_format_message_text($dbm2), $m2->message);
         $this->assertEquals(transform::datetime($now - (8 * DAYSECS)), $m2->timecreated);
         $this->assertEquals('-', $m2->timeread);
         $this->assertArrayHasKey('timedeleted', (array) $m2);
 
-        $this->assertEquals(get_string('yes'), $m3->sender);
+        $this->assertEquals(get_string('yes'), $m3->issender);
         $this->assertEquals(message_format_message_text($dbm3), $m3->message);
         $this->assertEquals(transform::datetime($now - (7 * DAYSECS)), $m3->timecreated);
         $this->assertEquals('-', $m3->timeread);
 
         // Confirm the messages with user 3 are correct.
-        $messages = (array) $writer->get_data([get_string('messages', 'core_message'), fullname($user3)]);
+        $messages = (array) $writer->get_data([get_string('messages', 'core_message'), $user3->id]);
         $this->assertCount(3, $messages);
 
         $dbm4 = $DB->get_record('messages', ['id' => $m4]);
@@ -569,24 +672,126 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $m5 = array_shift($messages);
         $m6 = array_shift($messages);
 
-        $this->assertEquals(get_string('no'), $m4->sender);
+        $this->assertEquals(get_string('no'), $m4->issender);
         $this->assertEquals(message_format_message_text($dbm4), $m4->message);
         $this->assertEquals(transform::datetime($now - (6 * DAYSECS)), $m4->timecreated);
         $this->assertNotEquals('-', $m4->timeread);
         $this->assertArrayNotHasKey('timedeleted', (array) $m4);
 
-        $this->assertEquals(get_string('yes'), $m5->sender);
+        $this->assertEquals(get_string('yes'), $m5->issender);
         $this->assertEquals(message_format_message_text($dbm5), $m5->message);
         $this->assertEquals(transform::datetime($now - (5 * DAYSECS)), $m5->timecreated);
         $this->assertEquals('-', $m5->timeread);
         $this->assertArrayHasKey('timedeleted', (array) $m5);
 
-        $this->assertEquals(get_string('no'), $m6->sender);
+        $this->assertEquals(get_string('no'), $m6->issender);
         $this->assertEquals(message_format_message_text($dbm6), $m6->message);
         $this->assertEquals(transform::datetime($now - (4 * DAYSECS)), $m6->timecreated);
         $this->assertEquals('-', $m6->timeread);
     }
 
+    /**
+     * Test for provider::export_user_data().
+     */
+    public function test_export_for_context_with_messages() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $now = time();
+        $systemcontext = \context_system::instance();
+
+        // Create users to test with.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $user1context = \context_user::instance($user1->id);
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $coursecontext1 = \context_course::instance($course1->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+
+        // Enrol users to courses.
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user3->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+
+        // Create course groups with group messaging enabled.
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id, 'enablemessaging' => 1));
+
+        // Add users to groups.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user3->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user1->id));
+
+        // Get conversation.
+        $component = 'core_group';
+        $itemtype = 'groups';
+        $conversation = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group1a->id,
+            $coursecontext1->id
+        );
+
+        // Send some private messages between user 1 and user 2.
+        $pm1id = $this->create_message($user1->id, $user2->id, $now);
+
+        $dbpm1 = $DB->get_record('messages', ['id' => $pm1id]);
+
+        // Send some messages to the conversation.
+        $m1 = testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 1', $now + 1);
+        $m2 = testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 2', $now + 2);
+        $m3 = testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Message 3', $now + 3);
+
+        $dbm1 = $DB->get_record('messages', ['id' => $m1]);
+        $dbm2 = $DB->get_record('messages', ['id' => $m2]);
+        $dbm3 = $DB->get_record('messages', ['id' => $m3]);
+
+        // Mark as read and delete some messages.
+        \core_message\api::mark_message_as_read($user2->id, $dbm1);
+        \core_message\api::delete_message($user1->id, $m2);
+
+        // Confirm the user1 has no data in any course context because private messages are related to user context.
+        $this->export_context_data_for_user($user1->id, $coursecontext2, 'core_message');
+
+        // Check that system context hasn't been exported.
+        $writer = writer::with_context($systemcontext);
+        $this->assertFalse($writer->has_any_data());
+
+        // Check that course1 context hasn't been exported.
+        $writer = writer::with_context($coursecontext1);
+        $this->assertFalse($writer->has_any_data());
+
+        // Check that course2 context has been exported and contains data.
+        $writer = writer::with_context($coursecontext2);
+        $this->assertFalse($writer->has_any_data());
+
+        // Confirm the user1 has only private messages in the user context.
+        $this->export_context_data_for_user($user1->id, $user1context, 'core_message');
+        $writer = writer::with_context($user1context);
+        $this->assertTrue($writer->has_any_data());
+
+        // Confirm the messages with user 2 are correct.
+        $messages = (array) $writer->get_data([get_string('messages', 'core_message'), $user2->id]);
+        $this->assertCount(1, $messages);
+        $m1 = reset($messages);
+
+        $this->assertEquals(get_string('yes'), $m1->issender);
+        $this->assertEquals(message_format_message_text($dbpm1), $m1->message);
+        $this->assertEquals(transform::datetime($now), $m1->timecreated);
+        $this->assertEquals('-', $m1->timeread);
+        $this->assertArrayNotHasKey('timedeleted', (array) $m1);
+
+        // Confirm the messages with user 3 are correct.
+        $messages = (array) $writer->get_data([get_string('messages', 'core_message'), fullname($user3)]);
+        $this->assertCount(0, $messages);
+    }
+
     /**
      * Test for provider::export_user_data().
      */
@@ -722,8 +927,8 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         // And none of them are from user1.
         $this->assertEquals(0, $DB->count_records('messages', ['useridfrom' => $user1->id]));
 
-        // Confirm there is only 1 user action left - the one that is for user2 reading the message.
-        $this->assertEquals(1, $DB->count_records('message_user_actions'));
+        // Confirm there is 0 user action left.
+        $this->assertEquals(0, $DB->count_records('message_user_actions'));
         // And it is not for user1.
         $this->assertEquals(0, $DB->count_records('message_user_actions', ['userid' => $user1->id]));
 
@@ -836,11 +1041,7 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $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(0, $muas);
 
         $this->assertCount(1, $mcms);
         $mcm = reset($mcms);
@@ -1157,11 +1358,7 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $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(0, $muas);
 
         $this->assertCount(1, $mcms);
         $mcm = reset($mcms);
@@ -1179,6 +1376,1141 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $this->assertEquals($user3->id, $notification->useridto);
     }
 
+    /**
+     * Test for provider::add_contexts_for_conversations().
+     */
+    public function test_add_contexts_for_conversations() {
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $component = 'core_group';
+        $itemtype = 'groups';
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $user4 = $this->getDataGenerator()->create_user();
+
+        // Test nothing is found before group conversations is created or message is sent.
+        $contextlist = new contextlist();
+        provider::add_contexts_for_conversations($contextlist, $user1->id, $component, $itemtype);
+        $this->assertCount(0, $contextlist);
+        provider::add_contexts_for_conversations($contextlist, $user2->id, $component, $itemtype);
+        $this->assertCount(0, $contextlist);
+
+        // Create courses.
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $coursecontext1 = \context_course::instance($course1->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+
+        // Enrol users to courses.
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user3->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id);
+
+        // Create course groups with messaging enabled.
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id, 'enablemessaging' => 1));
+
+        // Add users to groups.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user3->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user1->id));
+
+        // Get conversation.
+        $conversation1 = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group1a->id,
+            $coursecontext1->id
+        );
+
+        // Send some messages to the group conversation.
+        $now = time();
+        $m1id = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1', $now + 1);
+        $m2id = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 2', $now + 2);
+        $m3id = testhelper::send_fake_message_to_conversation($user2, $conversation1->id, 'Message 3', $now + 3);
+
+        // Test for user1 (is member of the conversation and has sent a message).
+        $contextlist = new contextlist();
+        provider::add_contexts_for_conversations($contextlist, $user1->id, $component, $itemtype);
+        $this->assertCount(2, $contextlist);
+        $this->assertContains($coursecontext1->id, $contextlist->get_contextids());
+        $this->assertContains($coursecontext2->id, $contextlist->get_contextids());
+
+        // Test for user2 (is member of the conversation and has sent a message).
+        $contextlist = new contextlist();
+        provider::add_contexts_for_conversations($contextlist, $user2->id, $component, $itemtype);
+        $this->assertCount(1, $contextlist);
+        $this->assertEquals($coursecontext1, $contextlist->current());
+
+        // Test for user3 (is member of the conversation).
+        $contextlist = new contextlist();
+        provider::add_contexts_for_conversations($contextlist, $user3->id, $component, $itemtype);
+        $this->assertCount(1, $contextlist);
+        $this->assertEquals($coursecontext1, $contextlist->current());
+
+        // Test for user4 (doesn't belong to the conversation).
+        $contextlist = new contextlist();
+        provider::add_contexts_for_conversations($contextlist, $user4->id, $component, $itemtype);
+        $this->assertCount(0, $contextlist);
+    }
+
+    /**
+     * Test for provider::add_conversations_in_context().
+     */
+    public function test_add_conversations_in_context() {
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $component = 'core_group';
+        $itemtype = 'groups';
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $user4 = $this->getDataGenerator()->create_user();
+
+        // Create courses.
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $coursecontext1 = \context_course::instance($course1->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+
+        // Test nothing is found before group conversations is created or message is sent.
+        $userlist1 = new \core_privacy\local\request\userlist($coursecontext1, 'core_message');
+        provider::add_conversations_in_context($userlist1, $component, $itemtype);
+        $this->assertCount(0, $userlist1);
+
+        // Enrol users to courses.
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user3->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user4->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id);
+
+        // Create course groups with messaging enabled.
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id, 'enablemessaging' => 1));
+
+        // Add users to groups.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user3->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user1->id));
+
+        // Get conversation.
+        $conversation1 = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group1a->id,
+            $coursecontext1->id
+        );
+
+        // Send some messages to the group conversation.
+        $now = time();
+        $m1id = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1', $now + 1);
+        $m2id = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 2', $now + 2);
+        $m3id = testhelper::send_fake_message_to_conversation($user2, $conversation1->id, 'Message 3', $now + 3);
+
+        // Test for users with any group conversation in course1.
+        provider::add_conversations_in_context($userlist1, $component, $itemtype);
+        $this->assertCount(3, $userlist1);
+        $this->assertEquals(
+                [$user1->id, $user2->id, $user3->id],
+                $userlist1->get_userids());
+
+        // Test for users with any group conversation in course2.
+        $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, 'core_message');
+        provider::add_conversations_in_context($userlist2, $component, $itemtype);
+        $this->assertCount(1, $userlist2);
+        $this->assertEquals(
+                [$user1->id],
+                $userlist2->get_userids());
+    }
+
+    /**
+     * Test for provider::export_conversations().
+     */
+    public function test_export_conversations() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $now = time();
+        $systemcontext = \context_system::instance();
+
+        // Create users to test with.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $user1context = \context_user::instance($user1->id);
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $coursecontext1 = \context_course::instance($course1->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+
+        // Enrol users to courses.
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user3->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+
+        // Create course groups with group messaging enabled.
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id, 'enablemessaging' => 1));
+
+        // Add users to groups.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user3->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user1->id));
+
+        // Get conversation.
+        $component = 'core_group';
+        $itemtype = 'groups';
+        $conversation1 = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group1a->id,
+            $coursecontext1->id
+        );
+
+        // Send some messages to the conversation.
+        $m1 = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1', $now + 1);
+        $m2 = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 2', $now + 2);
+        $m3 = testhelper::send_fake_message_to_conversation($user2, $conversation1->id, 'Message 3', $now + 3);
+
+        $dbm1 = $DB->get_record('messages', ['id' => $m1]);
+        $dbm2 = $DB->get_record('messages', ['id' => $m2]);
+        $dbm3 = $DB->get_record('messages', ['id' => $m3]);
+
+        // Send some private messages between user 1 and user 2.
+        $pm1id = $this->create_message($user1->id, $user2->id, $now);
+
+        // Mark as read and delete some messages.
+        \core_message\api::mark_message_as_read($user1->id, $dbm3, $now + 5);
+        \core_message\api::delete_message($user1->id, $m2);
+
+        // Export all the conversations related to the groups in course1 for user1.
+        provider::export_conversations($user1->id, 'core_group', 'groups', $coursecontext1);
+
+        // Check that system context hasn't been exported.
+        $writer = writer::with_context($systemcontext);
+        $this->assertFalse($writer->has_any_data());
+
+        // Check that course2 context hasn't been exported.
+        $writer = writer::with_context($coursecontext2);
+        $this->assertFalse($writer->has_any_data());
+
+        // Check that course1 context has been exported for user1 and contains data.
+        $writer = writer::with_context($coursecontext1);
+        $this->assertTrue($writer->has_any_data());
+
+        // Confirm the messages for conversation1 are correct.
+        $messages = (array) $writer->get_data([
+            get_string('messages', 'core_message'),
+            get_string($conversation1->itemtype, $conversation1->component),
+            get_string('privacy:export:conversationprefix', 'core_message') . $conversation1->name
+        ]);
+        $this->assertCount(3, $messages);
+
+        usort($messages, ['static', 'sort_messages']);
+        $m1 = array_shift($messages);
+        $m2 = array_shift($messages);
+        $m3 = array_shift($messages);
+
+        // Check message 1 is correct.
+        $this->assertEquals(get_string('yes'), $m1->issender);
+        $this->assertEquals(message_format_message_text($dbm1), $m1->message);
+        $this->assertEquals(transform::datetime($now + 1), $m1->timecreated);
+        $this->assertEquals('-', $m1->timeread);
+        $this->assertArrayNotHasKey('timedeleted', (array) $m1);
+
+        // Check message 2 is correct.
+        $this->assertEquals(get_string('yes'), $m2->issender);
+        $this->assertEquals(message_format_message_text($dbm2), $m2->message);
+        $this->assertEquals(transform::datetime($now + 2), $m2->timecreated);
+        $this->assertEquals('-', $m2->timeread);
+        $this->assertArrayHasKey('timedeleted', (array) $m2);
+
+        // Check message 3 is correct.
+        $this->assertEquals(get_string('no'), $m3->issender);
+        $this->assertEquals(message_format_message_text($dbm3), $m3->message);
+        $this->assertEquals(transform::datetime($now + 3), $m3->timecreated);
+        $this->assertEquals(transform::datetime($now + 5), $m3->timeread);
+        $this->assertArrayNotHasKey('timedeleted', (array) $m3);
+
+        // Export all the conversations related to the groups in course1 for user2.
+        provider::export_conversations($user2->id, 'core_group', 'groups', $coursecontext1);
+
+        // Check that system context hasn't been exported.
+        $writer = writer::with_context($systemcontext);
+        $this->assertFalse($writer->has_any_data());
+
+        // Check that course2 context hasn't been exported.
+        $writer = writer::with_context($coursecontext2);
+        $this->assertFalse($writer->has_any_data());
+
+        // Check that course1 context has been exported for user2 and contains data.
+        $writer = writer::with_context($coursecontext1);
+        $this->assertTrue($writer->has_any_data());
+
+        // Confirm the messages for conversation1 are correct.
+        $messages = (array) $writer->get_data([
+            get_string('messages', 'core_message'),
+            get_string($conversation1->itemtype, $conversation1->component),
+            get_string('privacy:export:conversationprefix', 'core_message') . $conversation1->name
+        ]);
+        $this->assertCount(3, $messages);
+
+        usort($messages, ['static', 'sort_messages']);
+        $m1 = array_shift($messages);
+        $m2 = array_shift($messages);
+        $m3 = array_shift($messages);
+
+        // Check message 1 is correct.
+        $this->assertEquals(get_string('no'), $m1->issender);
+        $this->assertEquals(message_format_message_text($dbm1), $m1->message);
+        $this->assertEquals(transform::datetime($now + 1), $m1->timecreated);
+        $this->assertEquals('-', $m1->timeread);
+        $this->assertArrayNotHasKey('timedeleted', (array) $m1);
+
+        // Check message 2 is correct.
+        $this->assertEquals(get_string('no'), $m2->issender);
+        $this->assertEquals(message_format_message_text($dbm2), $m2->message);
+        $this->assertEquals(transform::datetime($now + 2), $m2->timecreated);
+        $this->assertEquals('-', $m2->timeread);
+        $this->assertArrayNotHasKey('timedeleted', (array) $m2);
+
+        // Check message 3 is correct.
+        $this->assertEquals(get_string('yes'), $m3->issender);
+        $this->assertEquals(message_format_message_text($dbm3), $m3->message);
+        $this->assertEquals(transform::datetime($now + 3), $m3->timecreated);
+        $this->assertEquals('-', $m3->timeread);
+        $this->assertArrayNotHasKey('timedeleted', (array) $m3);
+    }
+
+    /**
+     * Test for provider::delete_conversations_for_all_users().
+     */
+    public function test_delete_conversations_for_all_users() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $now = time();
+        $timeread = $now - DAYSECS;
+        $component = 'core_group';
+        $itemtype = 'groups';
+
+        // 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();
+        $user1context = \context_user::instance($user1->id);
+
+        // 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 a user.
+        \core_message\api::block_user($user1->id, $user3->id);
+        \core_message\api::block_user($user3->id, $user4->id);
+
+        // Create individual messages.
+        $im1 = $this->create_message($user1->id, $user2->id, $now + (9 * DAYSECS), true);
+        $im2 = $this->create_message($user2->id, $user1->id, $now + (8 * DAYSECS), true);
+        $im3 = $this->create_message($user2->id, $user3->id, $now + (7 * 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));
+        $n3 = $this->create_notification($user2->id, $user3->id, $now + (7 * DAYSECS));
+
+        // Delete one of the messages.
+        \core_message\api::delete_message($user1->id, $im2);
+
+        // Create course2.
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $coursecontext1 = \context_course::instance($course1->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+
+        // Enrol users to courses.
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user3->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id);
+
+        // Create course groups with group messaging enabled.
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id, 'enablemessaging' => 1));
+
+        // Add users to groups.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user3->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user2->id));
+
+        // Get conversation.
+        $conversation1 = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group1a->id,
+            $coursecontext1->id
+        );
+        $conversation2 = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group2a->id,
+            $coursecontext2->id
+        );
+
+        // Send some messages to the conversation.
+        $gm1 = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1.1', $now + 1);
+        $gm2 = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1.2', $now + 2);
+        $gm3 = testhelper::send_fake_message_to_conversation($user2, $conversation1->id, 'Message 1.3', $now + 3);
+        $gm4 = testhelper::send_fake_message_to_conversation($user1, $conversation2->id, 'Message 2.1', $now + 4);
+        $gm5 = testhelper::send_fake_message_to_conversation($user2, $conversation2->id, 'Message 2.2', $now + 5);
+
+        $dbgm1 = $DB->get_record('messages', ['id' => $gm1]);
+        $dbgm2 = $DB->get_record('messages', ['id' => $gm2]);
+        $dbgm3 = $DB->get_record('messages', ['id' => $gm3]);
+        $dbgm4 = $DB->get_record('messages', ['id' => $gm4]);
+        $dbgm5 = $DB->get_record('messages', ['id' => $gm5]);
+
+        // Mark as read one of the conversation messages.
+        \core_message\api::mark_message_as_read($user1->id, $dbgm3, $now + 5);
+
+        // There should be 2 contacts.
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+
+        // There should be 2 contact requests.
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+
+        // There should be 2 blocked users.
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+
+        // There should be 8 messages.
+        $this->assertEquals(8, $DB->count_records('messages'));
+
+        // There should be 4 user actions - 3 for reading the message, 1 for deleting.
+        $this->assertEquals(4, $DB->count_records('message_user_actions'));
+
+        // There should be 4 conversations - 2 individual + 2 group.
+        $this->assertEquals(4, $DB->count_records('message_conversations'));
+
+        // There should be 9 conversation members - (2 + 2) individual + (3 + 2) group.
+        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+
+        // There should be 5 notifications - 3 notifications + 2 for the contact request.
+        $this->assertEquals(5, $DB->count_records('notifications'));
+
+        // Delete conversations for all users in course1.
+        provider::delete_conversations_for_all_users($coursecontext1, $component, $itemtype);
+
+        // There should be still 2 contacts.
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+
+        // There should be still 2 contact requests.
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+
+        // There should be still 2 blocked users.
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+
+        // There should be still 5 notifications - 3 notifications + 2 for the contact request.
+        $this->assertEquals(5, $DB->count_records('notifications'));
+
+        // There should be 5 messages - 3 individual - 2 group (course2).
+        $this->assertEquals(5, $DB->count_records('messages'));
+        $messages = array_keys($DB->get_records('messages'));
+        $this->assertContains($im1, $messages);
+        $this->assertContains($im2, $messages);
+        $this->assertContains($im3, $messages);
+        $this->assertContains($gm4, $messages);
+        $this->assertContains($gm5, $messages);
+
+        // There should be 3 user actions - 2 for reading the message, 1 for deleting.
+        $this->assertEquals(3, $DB->count_records('message_user_actions'));
+        $useractions = $DB->get_records('message_user_actions');
+        $useractions = array_map(function($action) {
+                return $action->messageid;
+        }, $useractions);
+        $this->assertNotContains($gm3, $useractions);
+
+        // There should be 3 conversations - 2 individual + 1 group (course2).
+        $this->assertEquals(3, $DB->count_records('message_conversations'));
+        $conversations = $DB->get_records('message_conversations');
+        $this->assertArrayNotHasKey($conversation1->id, $conversations);
+
+        // There should be 6 conversation members - (2 + 2) individual + 2 group.
+        $this->assertEquals(6, $DB->count_records('message_conversation_members'));
+    }
+
+    /**
+     * Test for provider::delete_conversations_for_all_users() in the system context.
+     */
+    public function test_delete_conversations_for_all_users_systemcontext() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $now = time();
+        $timeread = $now - DAYSECS;
+        $systemcontext = \context_system::instance();
+        $component = 'core_group';
+        $itemtype = 'groups';
+
+        // 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();
+
+        // 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 a user.
+        \core_message\api::block_user($user1->id, $user3->id);
+        \core_message\api::block_user($user3->id, $user4->id);
+
+        // Create individual messages.
+        $im1 = $this->create_message($user1->id, $user2->id, $now + (9 * DAYSECS), true);
+        $im2 = $this->create_message($user2->id, $user1->id, $now + (8 * DAYSECS), true);
+        $im3 = $this->create_message($user2->id, $user3->id, $now + (7 * 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));
+        $n3 = $this->create_notification($user2->id, $user3->id, $now + (7 * DAYSECS));
+
+        // Delete one of the messages.
+        \core_message\api::delete_message($user1->id, $im2);
+
+        // Create course2.
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $coursecontext1 = \context_course::instance($course1->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+
+        // Enrol users to courses.
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user3->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id);
+
+        // Create course groups with group messaging enabled.
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id, 'enablemessaging' => 1));
+
+        // Add users to groups.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user3->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user2->id));
+
+        // Get conversation.
+        $conversation1 = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group1a->id,
+            $coursecontext1->id
+        );
+        $conversation2 = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group2a->id,
+            $coursecontext2->id
+        );
+
+        // Send some messages to the conversation.
+        $gm1 = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1.1', $now + 1);
+        $gm2 = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1.2', $now + 2);
+        $gm3 = testhelper::send_fake_message_to_conversation($user2, $conversation1->id, 'Message 1.3', $now + 3);
+        $gm4 = testhelper::send_fake_message_to_conversation($user1, $conversation2->id, 'Message 2.1', $now + 4);
+        $gm5 = testhelper::send_fake_message_to_conversation($user2, $conversation2->id, 'Message 2.2', $now + 5);
+
+        $dbgm3 = $DB->get_record('messages', ['id' => $gm3]);
+
+        // Mark as read one of the conversation messages.
+        \core_message\api::mark_message_as_read($user1->id, $dbgm3, $now + 5);
+
+        // There should be 2 contacts.
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+
+        // There should be 2 contact requests.
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+
+        // There should be 2 blocked users.
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+
+        // There should be 8 messages.
+        $this->assertEquals(8, $DB->count_records('messages'));
+
+        // There should be 4 user actions - 3 for reading the message, 1 for deleting.
+        $this->assertEquals(4, $DB->count_records('message_user_actions'));
+
+        // There should be 4 conversations - 2 individual + 2 group.
+        $this->assertEquals(4, $DB->count_records('message_conversations'));
+
+        // There should be 9 conversation members - (2 + 2) individual + (3 + 2) group.
+        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+
+        // There should be 5 notifications - 3 notifications + 2 for the contact request.
+        $this->assertEquals(5, $DB->count_records('notifications'));
+
+        // Delete group conversations for all users in system context.
+        provider::delete_conversations_for_all_users($systemcontext, $component, $itemtype);
+
+        // No conversations should be removed, because they are in the course context.
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+        $this->assertEquals(8, $DB->count_records('messages'));
+        $this->assertEquals(4, $DB->count_records('message_user_actions'));
+        $this->assertEquals(4, $DB->count_records('message_conversations'));
+        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+        $this->assertEquals(5, $DB->count_records('notifications'));
+
+        // Delete individual conversations for all users in system context.
+        provider::delete_conversations_for_all_users($systemcontext, '', '');
+
+        // No conversations should be removed, because they've been moved to user context.
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+        $this->assertEquals(8, $DB->count_records('messages'));
+        $this->assertEquals(4, $DB->count_records('message_user_actions'));
+        $this->assertEquals(4, $DB->count_records('message_conversations'));
+        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+        $this->assertEquals(5, $DB->count_records('notifications'));
+    }
+
+    /**
+     * Test for provider::delete_conversations_for_all_users() in the user context.
+     */
+    public function test_delete_conversations_for_all_users_usercontext() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $now = time();
+        $timeread = $now - DAYSECS;
+        $component = 'core_group';
+        $itemtype = 'groups';
+
+        // 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();
+        $user1context = \context_user::instance($user1->id);
+
+        // 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 a user.
+        \core_message\api::block_user($user1->id, $user3->id);
+        \core_message\api::block_user($user3->id, $user4->id);
+
+        // Create individual messages.
+        $im1 = $this->create_message($user1->id, $user2->id, $now + (9 * DAYSECS), true);
+        $im2 = $this->create_message($user2->id, $user1->id, $now + (8 * DAYSECS), true);
+        $im3 = $this->create_message($user2->id, $user3->id, $now + (7 * 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));
+        $n3 = $this->create_notification($user2->id, $user3->id, $now + (7 * DAYSECS));
+
+        // Delete one of the messages.
+        \core_message\api::delete_message($user1->id, $im2);
+
+        // Create course2.
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $coursecontext1 = \context_course::instance($course1->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+
+        // Enrol users to courses.
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user3->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id);
+
+        // Create course groups with group messaging enabled.
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id, 'enablemessaging' => 1));
+
+        // Add users to groups.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user3->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group2a->id, 'userid' => $user2->id));
+
+        // Get conversation.
+        $iconversation1id = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]);
+        $iconversation2id = \core_message\api::get_conversation_between_users([$user2->id, $user3->id]);
+        $conversation1 = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group1a->id,
+            $coursecontext1->id
+        );
+        $conversation2 = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group2a->id,
+            $coursecontext2->id
+        );
+
+        // Send some messages to the conversation.
+        $gm1 = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1.1', $now + 1);
+        $gm2 = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1.2', $now + 2);
+        $gm3 = testhelper::send_fake_message_to_conversation($user2, $conversation1->id, 'Message 1.3', $now + 3);
+        $gm4 = testhelper::send_fake_message_to_conversation($user1, $conversation2->id, 'Message 2.1', $now + 4);
+        $gm5 = testhelper::send_fake_message_to_conversation($user2, $conversation2->id, 'Message 2.2', $now + 5);
+
+        $dbgm3 = $DB->get_record('messages', ['id' => $gm3]);
+
+        // Mark as read one of the conversation messages.
+        \core_message\api::mark_message_as_read($user1->id, $dbgm3, $now + 5);
+
+        // There should be 2 contacts.
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+
+        // There should be 2 contact requests.
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+
+        // There should be 2 blocked users.
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+
+        // There should be 8 messages - 3 individual + 5 group.
+        $this->assertEquals(8, $DB->count_records('messages'));
+
+        // There should be 4 user actions - 3 for reading the message, 1 for deleting.
+        $this->assertEquals(4, $DB->count_records('message_user_actions'));
+
+        // There should be 4 conversations - 2 individual + 2 group.
+        $this->assertEquals(4, $DB->count_records('message_conversations'));
+
+        // There should be 9 conversation members - (2 + 2) individual + (3 + 2) group.
+        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+
+        // There should be 5 notifications - 3 notifications + 2 for the contact request.
+        $this->assertEquals(5, $DB->count_records('notifications'));
+
+        // Delete group conversations for all users in user context.
+        provider::delete_conversations_for_all_users($user1context, $component, $itemtype);
+
+        // No conversations should be removed, because they are in the course context.
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+        $this->assertEquals(8, $DB->count_records('messages'));
+        $this->assertEquals(4, $DB->count_records('message_user_actions'));
+        $this->assertEquals(4, $DB->count_records('message_conversations'));
+        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+        $this->assertEquals(5, $DB->count_records('notifications'));
+
+        // Delete individual conversations for all users in user context.
+        provider::delete_conversations_for_all_users($user1context, '', '');
+
+        // No conversations should be removed, because they are in the course context.
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+        $this->assertEquals(8, $DB->count_records('messages'));
+        $this->assertEquals(4, $DB->count_records('message_user_actions'));
+        $this->assertEquals(4, $DB->count_records('message_conversations'));
+        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+        $this->assertEquals(5, $DB->count_records('notifications'));
+    }
+
+    /**
+     * Test for provider::delete_conversations_for_user().
+     */
+    public function test_delete_conversations_for_user() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $now = time();
+        $timeread = $now - DAYSECS;
+        $systemcontext = \context_system::instance();
+        $component = 'core_group';
+        $itemtype = 'groups';
+
+        // 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();
+        $user1context = \context_user::instance($user1->id);
+
+        // 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 a user.
+        \core_message\api::block_user($user1->id, $user3->id);
+        \core_message\api::block_user($user3->id, $user4->id);
+
+        // Create private messages.
+        $pm1 = $this->create_message($user1->id, $user2->id, $now + (9 * DAYSECS), true);
+        $pm2 = $this->create_message($user2->id, $user1->id, $now + (8 * DAYSECS), true);
+        $pm3 = $this->create_message($user2->id, $user3->id, $now + (7 * 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));
+        $n3 = $this->create_notification($user2->id, $user3->id, $now + (7 * DAYSECS));
+
+        // Delete one of the messages.
+        \core_message\api::delete_message($user1->id, $pm2);
+
+        // Create course.
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $coursecontext1 = \context_course::instance($course1->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+
+        // Enrol users to courses.
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user3->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+
+        // Create course groups with group messaging enabled.
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
+
+        // Add users to groups.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user3->id));
+
+        // Get conversation.
+        $conversation1 = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group1a->id,
+            $coursecontext1->id
+        );
+
+        // Send some messages to the conversation.
+        $gm1 = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1', $now + 1);
+        $gm2 = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 2', $now + 2);
+        $gm3 = testhelper::send_fake_message_to_conversation($user2, $conversation1->id, 'Message 3', $now + 3);
+
+        $dbm3 = $DB->get_record('messages', ['id' => $gm3]);
+
+        // Mark as read one of the conversation messages.
+        \core_message\api::mark_message_as_read($user1->id, $dbm3, $now + 5);
+
+        // There should be 2 contacts.
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+
+        // There should be 2 contact requests.
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+
+        // There should be 2 blocked users.
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+
+        // There should be 3 notifications + 2 for the contact request.
+        $this->assertEquals(5, $DB->count_records('notifications'));
+
+        // There should be 6 messages.
+        $this->assertEquals(6, $DB->count_records('messages'));
+
+        // There should be 4 user actions - 3 for reading the message, one for deleting.
+        $this->assertEquals(4, $DB->count_records('message_user_actions'));
+
+        // There should be 3 conversations - 2 private + 1 group.
+        $this->assertEquals(3, $DB->count_records('message_conversations'));
+
+        // There should be 7 conversation members - 2 + 2 private conversations + 3 group conversation.
+        $this->assertEquals(7, $DB->count_records('message_conversation_members'));
+        $members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation1->id]);
+        $members = array_map(function($member) {
+                return $member->userid;
+        }, $members);
+        $this->assertContains($user1->id, $members);
+
+        // Delete group conversations for user1 in course1 and course2.
+        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist($user1, 'core_message',
+                [$coursecontext1->id, $coursecontext2->id]);
+        provider::delete_conversations_for_user($approvedcontextlist, $component, $itemtype);
+
+        // There should be still 2 contacts.
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+
+        // There should be still 2 contact requests.
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+
+        // There should be still 2 blocked users.
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+
+        // There should be still 3 notifications + 2 for the contact request.
+        $this->assertEquals(5, $DB->count_records('notifications'));
+
+        // There should be 4 messages - 3 private + 1 group sent by user2.
+        $this->assertEquals(4, $DB->count_records('messages'));
+        $messages = array_keys($DB->get_records('messages'));
+        $this->assertContains($pm1, $messages);
+        $this->assertContains($pm2, $messages);
+        $this->assertContains($pm3, $messages);
+        $this->assertContains($gm3, $messages);
+
+        // There should be 3 user actions - 2 for reading the message, one for deleting.
+        $this->assertEquals(3, $DB->count_records('message_user_actions'));
+        $useractions = $DB->get_records('message_user_actions');
+        $useractions = array_map(function($action) {
+                return $action->messageid;
+        }, $useractions);
+        $this->assertNotContains($gm3, $useractions);
+
+        // There should be still 3 conversations - 2 private + 1 group.
+        $this->assertEquals(3, $DB->count_records('message_conversations'));
+
+        // There should be 6 conversation members - 2 + 2 private conversations + 2 group conversation.
+        $this->assertEquals(6, $DB->count_records('message_conversation_members'));
+        $members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation1->id]);
+        $members = array_map(function($member) {
+                return $member->userid;
+        }, $members);
+        $this->assertNotContains($user1->id, $members);
+    }
+
+
+    /**
+     * Test for provider::delete_conversations_for_users().
+     */
+    public function test_delete_conversations_for_users() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $now = time();
+        $timeread = $now - DAYSECS;
+        $systemcontext = \context_system::instance();
+        $component = 'core_group';
+        $itemtype = 'groups';
+
+        // 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();
+        $user1context = \context_user::instance($user1->id);
+
+        // 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 a user.
+        \core_message\api::block_user($user1->id, $user3->id);
+        \core_message\api::block_user($user3->id, $user4->id);
+
+        // Create private messages.
+        $pm1 = $this->create_message($user1->id, $user2->id, $now + (9 * DAYSECS), true);
+        $pm2 = $this->create_message($user2->id, $user1->id, $now + (8 * DAYSECS), true);
+        $pm3 = $this->create_message($user2->id, $user3->id, $now + (7 * 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));
+        $n3 = $this->create_notification($user2->id, $user3->id, $now + (7 * DAYSECS));
+
+        // Delete one of the messages.
+        \core_message\api::delete_message($user1->id, $pm2);
+
+        // Create course.
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $coursecontext1 = \context_course::instance($course1->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+
+        // Enrol users to courses.
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user3->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user4->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+
+        // Create course groups with group messaging enabled.
+        $group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
+        $group2a = $this->getDataGenerator()->create_group(array('courseid' => $course2->id, 'enablemessaging' => 1));
+
+        // Add users to groups.
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user2->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user3->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group1a->id, 'userid' => $user4->id));
+
+        // Get conversation.
+        $conversation1 = \core_message\api::get_conversation_by_area(
+            $component,
+            $itemtype,
+            $group1a->id,
+            $coursecontext1->id
+        );
+
+        // Send some messages to the conversation.
+        $gm1 = testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1', $now + 1);
+        $gm2 = testhelper::send_fake_message_to_conversation($user2, $conversation1->id, 'Message 2', $now + 2);
+        $gm3 = testhelper::send_fake_message_to_conversation($user3, $conversation1->id, 'Message 3', $now + 3);
+
+        $dbm3 = $DB->get_record('messages', ['id' => $gm3]);
+
+        // Mark as read one of the conversation messages.
+        \core_message\api::mark_message_as_read($user1->id, $dbm3, $now + 5);
+
+        // There should be 2 contacts.
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+
+        // There should be 2 contact requests.
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+
+        // There should be 2 blocked users.
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+
+        // There should be 3 notifications + 2 for the contact request.
+        $this->assertEquals(5, $DB->count_records('notifications'));
+
+        // There should be 6 messages.
+        $this->assertEquals(6, $DB->count_records('messages'));
+
+        // There should be 4 user actions - 3 for reading the message, one for deleting.
+        $this->assertEquals(4, $DB->count_records('message_user_actions'));
+
+        // There should be 3 conversations - 2 private + 2 group.
+        $this->assertEquals(4, $DB->count_records('message_conversations'));
+
+        // There should be 8 conversation members - (2 + 2) private + 4 group.
+        $this->assertEquals(8, $DB->count_records('message_conversation_members'));
+        $members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation1->id]);
+        $members = array_map(function($member) {
+                return $member->userid;
+        }, $members);
+        $this->assertContains($user1->id, $members);
+        $this->assertContains($user4->id, $members);
+
+        // Delete group conversations for user1 and user2 in course2 context.
+        $approveduserlist = new \core_privacy\local\request\approved_userlist($coursecontext2, 'core_message',
+                [$user1->id, $user2->id]);
+        provider::delete_conversations_for_users($approveduserlist, $component, $itemtype);
+
+        // There should be exactly the same content, because $user1 and $user2 don't belong to any group in course2).
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+        $this->assertEquals(5, $DB->count_records('notifications'));
+        $this->assertEquals(6, $DB->count_records('messages'));
+        $this->assertEquals(4, $DB->count_records('message_user_actions'));
+        $this->assertEquals(4, $DB->count_records('message_conversations'));
+        $this->assertEquals(8, $DB->count_records('message_conversation_members'));
+
+        // Delete group conversations for user4 in course1 context.
+        $approveduserlist = new \core_privacy\local\request\approved_userlist($coursecontext1, 'core_message',
+                [$user4->id]);
+        provider::delete_conversations_for_users($approveduserlist, $component, $itemtype);
+
+        // There should be the same content except for the members (to remove user4 from the group1 in course1).
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+        $this->assertEquals(5, $DB->count_records('notifications'));
+        $this->assertEquals(6, $DB->count_records('messages'));
+        $this->assertEquals(4, $DB->count_records('message_user_actions'));
+        $this->assertEquals(4, $DB->count_records('message_conversations'));
+        // There should be 7 conversation members - (2 + 2) private + 3 group.
+        $this->assertEquals(7, $DB->count_records('message_conversation_members'));
+
+        // Delete group conversations for user1 and user2 in course1 context.
+        $approveduserlist = new \core_privacy\local\request\approved_userlist($coursecontext1, 'core_message',
+                [$user1->id, $user2->id]);
+        provider::delete_conversations_for_users($approveduserlist, $component, $itemtype);
+
+        // There should be still 2 contacts.
+        $this->assertEquals(2, $DB->count_records('message_contacts'));
+
+        // There should be still 2 contact requests.
+        $this->assertEquals(2, $DB->count_records('message_contact_requests'));
+
+        // There should be still 2 blocked users.
+        $this->assertEquals(2, $DB->count_records('message_users_blocked'));
+
+        // There should be still 3 notifications + 2 for the contact request.
+        $this->assertEquals(5, $DB->count_records('notifications'));
+
+        // There should be 4 messages - 3 private + 1 group sent by user3.
+        $this->assertEquals(4, $DB->count_records('messages'));
+        $messages = array_keys($DB->get_records('messages'));
+        $this->assertContains($pm1, $messages);
+        $this->assertContains($pm2, $messages);
+        $this->assertContains($pm3, $messages);
+        $this->assertContains($gm3, $messages);
+
+        // There should be 3 user actions - 2 for reading the message, one for deleting.
+        $this->assertEquals(3, $DB->count_records('message_user_actions'));
+        $useractions = $DB->get_records('message_user_actions');
+        $useractions = array_map(function($action) {
+                return $action->messageid;
+        }, $useractions);
+        $this->assertNotContains($gm3, $useractions);
+
+        // There should be still 4 conversations - 2 private + 2 group.
+        $this->assertEquals(4, $DB->count_records('message_conversations'));
+
+        // There should be 5 conversation members - (2 + 2) private + 1 group.
+        $this->assertEquals(5, $DB->count_records('message_conversation_members'));
+        $members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation1->id]);
+        $members = array_map(function($member) {
+                return $member->userid;
+        }, $members);
+        $this->assertNotContains($user1->id, $members);
+        $this->assertNotContains($user2->id, $members);
+    }
+
     /**
      * Creates a message to be used for testing.
      *