Merge branch 'MDL-63968-master' of git://github.com/andrewnicols/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 12 Nov 2018 09:02:17 +0000 (10:02 +0100)
committerDavid Monllao <davidm@moodle.com>
Mon, 12 Nov 2018 09:02:17 +0000 (10:02 +0100)
1  2 
message/classes/api.php
message/tests/api_test.php

diff --combined message/classes/api.php
@@@ -151,7 -151,7 +151,7 @@@ class api 
      /**
       * Handles searching for user in a particular course in the message area.
       *
 -     * TODO: This function should be removed once new group messaging UI is in place and old messaging UI is removed.
 +     * TODO: This function should be removed once the new group messaging UI is in place and the old messaging UI is removed.
       * For now we are not removing/deprecating this function for backwards compatibility with messaging UI.
       * But we are deprecating data_for_messagearea_search_users_in_course external function.
       * Followup: MDL-63915
      /**
       * Handles searching for user in the message area.
       *
 -     * TODO: This function should be removed once new group messaging UI is in place and old messaging UI is removed.
 +     * TODO: This function should be removed once the new group messaging UI is in place and the old messaging UI is removed.
       * For now we are not removing/deprecating this function for backwards compatibility with messaging UI.
       * But we are deprecating data_for_messagearea_search_users external function.
       * Followup: MDL-63915
          // If we need to return ONLY favourites, or NO favourites, generate the SQL snippet.
          $favouritesql = "";
          $favouriteparams = [];
-         if (is_bool($favourites)) {
-             if (!empty($favouriteconversationids)) {
-                 list ($insql, $inparams) = $DB->get_in_or_equal($favouriteconversationids, SQL_PARAMS_NAMED, 'favouriteids');
-                 $favouritesql = $favourites ? " AND mc.id {$insql} " : " AND mc.id NOT {$insql} ";
-                 $favouriteparams = $inparams;
-             }
+         if (null !== $favourites && !empty($favouriteconversationids)) {
+             list ($insql, $favouriteparams) =
+                     $DB->get_in_or_equal($favouriteconversationids, SQL_PARAMS_NAMED, 'favouriteids', $favourites);
+             $favouritesql = " AND mc.id {$insql} ";
          }
  
          // If we need to restrict type, generate the SQL snippet.
          $conversationset = $DB->get_recordset_sql($sql, $params, $limitfrom, $limitnum);
  
          $conversations = [];
 -        $uniquemembers = [];
          $members = [];
 +        $individualmembers = [];
 +        $groupmembers = [];
          foreach ($conversationset as $conversation) {
 -            $conversations[] = $conversation;
 +            $conversations[$conversation->id] = $conversation;
              $members[$conversation->id] = [];
          }
          $conversationset->close();
              if ($conversation->conversationtype == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) {
                  if (!is_null($conversation->useridfrom) && $conversation->useridfrom != $userid) {
                      $members[$conversation->id][$conversation->useridfrom] = $conversation->useridfrom;
 -                    $uniquemembers[$conversation->useridfrom] = $conversation->useridfrom;
 +                    $individualmembers[$conversation->useridfrom] = $conversation->useridfrom;
                  } else {
                      $individualconversations[] = $conversation->id;
                  }
                  // If we have a recent message, the sender is our member.
                  if (!is_null($conversation->useridfrom)) {
                      $members[$conversation->id][$conversation->useridfrom] = $conversation->useridfrom;
 -                    $uniquemembers[$conversation->useridfrom] = $conversation->useridfrom;
 +                    $groupmembers[$conversation->useridfrom] = $conversation->useridfrom;
                  }
              }
          }
  
              foreach ($conversationmembers as $mid => $member) {
                  $members[$member->conversationid][$member->userid] = $member->userid;
 -                $uniquemembers[$member->userid] = $member->userid;
 +                $individualmembers[$member->userid] = $member->userid;
              }
          }
 -        $memberids = array_values($uniquemembers);
  
          // We could fail early here if we're sure that:
          // a) we have no otherusers for all the conversations (users may have been deleted)
          // needs to be done in a separate query to avoid doing a join on the messages tables and the user
          // tables because on large sites these tables are massive which results in extremely slow
          // performance (typically due to join buffer exhaustion).
 -        if (!empty($memberids)) {
 -            $memberinfo = helper::get_member_info($userid, $memberids);
 +        if (!empty($individualmembers) || !empty($groupmembers)) {
 +            // Now, we want to remove any duplicates from the group members array. For individual members we will
 +            // be doing a more extensive call as we want their contact requests as well as privacy information,
 +            // which is not necessary for group conversations.
 +            $diffgroupmembers = array_diff($groupmembers, $individualmembers);
 +
 +            $individualmemberinfo = helper::get_member_info($userid, $individualmembers, true, true);
 +            $groupmemberinfo = helper::get_member_info($userid, $diffgroupmembers);
 +
 +            // Don't use array_merge, as we lose array keys.
 +            $memberinfo = $individualmemberinfo + $groupmemberinfo;
  
              // Update the members array with the member information.
              $deletedmembers = [];
                          if ($memberinfo[$memberid]->isdeleted) {
                              $deletedmembers[$convid][] = $memberid;
                          }
 -                        $members[$convid][$key] = $memberinfo[$memberid];
 +
 +                        $members[$convid][$key] = clone $memberinfo[$memberid];
 +
 +                        if ($conversations[$convid]->conversationtype == self::MESSAGE_CONVERSATION_TYPE_GROUP) {
 +                            // Remove data we don't need for group.
 +                            $members[$convid][$key]->requirescontact = null;
 +                            $members[$convid][$key]->canmessage = null;
 +                            $members[$convid][$key]->contactrequests = [];
 +                        }
                      }
                  }
              }
      /**
       * Returns the contacts to display in the contacts area.
       *
 +     * TODO: This function should be removed once the new group messaging UI is in place and the old messaging UI is removed.
 +     * For now we are not removing/deprecating this function for backwards compatibility with messaging UI.
 +     * Followup: MDL-63915
 +     *
       * @param int $userid The user id
       * @param int $limitfrom
       * @param int $limitnum
      /**
       * Returns the messages to display in the message area.
       *
 -     * @deprecated since 3.6
 +     * TODO: This function should be removed once the new group messaging UI is in place and the old messaging UI is removed.
 +     * For now we are not removing/deprecating this function for backwards compatibility with messaging UI.
 +     * Followup: MDL-63915
 +     *
       * @param int $userid the current user
       * @param int $otheruserid the other user
       * @param int $limitfrom
       */
      public static function get_messages($userid, $otheruserid, $limitfrom = 0, $limitnum = 0,
              $sort = 'timecreated ASC', $timefrom = 0, $timeto = 0) {
 -        debugging('\core_message\api::get_messages() is deprecated, please use ' .
 -            '\core_message\api::get_conversation_messages() instead.', DEBUG_DEVELOPER);
  
          if (!empty($timefrom)) {
              // Get the conversation between userid and otheruserid.
      /**
       * Returns the most recent message between two users.
       *
 -     * @deprecated since 3.6
 +     * TODO: This function should be removed once the new group messaging UI is in place and the old messaging UI is removed.
 +     * For now we are not removing/deprecating this function for backwards compatibility with messaging UI.
 +     * Followup: MDL-63915
 +     *
       * @param int $userid the current user
       * @param int $otheruserid the other user
       * @return \stdClass|null
       */
      public static function get_most_recent_message($userid, $otheruserid) {
 -        debugging('\core_message\api::get_most_recent_message() is deprecated, please use ' .
 -            '\core_message\api::get_most_recent_conversation_message() instead.', DEBUG_DEVELOPER);
 -
          // We want two messages here so we get an accurate 'blocktime' value.
          if ($messages = helper::get_messages($userid, $otheruserid, 0, 0, 2, 'timecreated DESC')) {
              // Swap the order so we now have them in historical order.
      /**
       * Returns the profile information for a contact for a user.
       *
 +     * TODO: This function should be removed once the new group messaging UI is in place and the old messaging UI is removed.
 +     * For now we are not removing/deprecating this function for backwards compatibility with messaging UI.
 +     * Followup: MDL-63915
 +     *
       * @param int $userid The user id
       * @param int $otheruserid The id of the user whose profile we want to view.
       * @return \stdClass
          }
  
          // Check if the recipient can be messaged by the sender.
 -        return (self::can_contact_user($recipient, $sender));
 +        return (self::can_contact_user($recipient->id, $sender->id));
 +    }
 +
 +    /**
 +     * Determines if a user is permitted to send a message to a given conversation.
 +     * If no sender is provided then it defaults to the logged in user.
 +     *
 +     * @param int $userid the id of the user on which the checks will be applied.
 +     * @param int $conversationid the id of the conversation we wish to check.
 +     * @return bool true if the user can send a message to the conversation, false otherwise.
 +     * @throws \moodle_exception
 +     */
 +    public static function can_send_message_to_conversation(int $userid, int $conversationid) : bool {
 +        global $DB;
 +
 +        $systemcontext = \context_system::instance();
 +        if (!has_capability('moodle/site:sendmessage', $systemcontext, $userid)) {
 +            return false;
 +        }
 +
 +        if (!self::is_user_in_conversation($userid, $conversationid)) {
 +            return false;
 +        }
 +
 +        // User can post messages and is in the conversation, but we need to check the conversation type to
 +        // know whether or not to check the user privacy settings via can_contact_user().
 +        $conversation = $DB->get_record('message_conversations', ['id' => $conversationid], '*', MUST_EXIST);
 +        if ($conversation->type == self::MESSAGE_CONVERSATION_TYPE_GROUP) {
 +            return true;
 +        } else if ($conversation->type == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) {
 +            // Get the other user in the conversation.
 +            $members = self::get_conversation_members($userid, $conversationid);
 +            $otheruser = array_filter($members, function($member) use($userid) {
 +                return $member->id != $userid;
 +            });
 +            $otheruser = reset($otheruser);
 +
 +            return self::can_contact_user($otheruser->id, $userid);
 +        } else {
 +            throw new \moodle_exception("Invalid conversation type '$conversation->type'.");
 +        }
 +    }
 +
 +    /**
 +     * Send a message from a user to a conversation.
 +     *
 +     * This method will create the basic eventdata and delegate to message creation to message_send.
 +     * The message_send() method is responsible for event data that is specific to each recipient.
 +     *
 +     * @param int $userid the sender id.
 +     * @param int $conversationid the conversation id.
 +     * @param string $message the message to send.
 +     * @param int $format the format of the message to send.
 +     * @return \stdClass the message created.
 +     * @throws \coding_exception
 +     * @throws \moodle_exception if the user is not permitted to send a message to the conversation.
 +     */
 +    public static function send_message_to_conversation(int $userid, int $conversationid, string $message,
 +                                                        int $format) : \stdClass {
 +        global $DB;
 +
 +        if (!self::can_send_message_to_conversation($userid, $conversationid)) {
 +            throw new \moodle_exception("User $userid cannot send a message to conversation $conversationid");
 +        }
 +
 +        $eventdata = new \core\message\message();
 +        $eventdata->courseid         = 1;
 +        $eventdata->component        = 'moodle';
 +        $eventdata->name             = 'instantmessage';
 +        $eventdata->userfrom         = $userid;
 +        $eventdata->convid           = $conversationid;
 +
 +        if ($format == FORMAT_HTML) {
 +            $eventdata->fullmessagehtml  = $message;
 +            // Some message processors may revert to sending plain text even if html is supplied,
 +            // so we keep both plain and html versions if we're intending to send html.
 +            $eventdata->fullmessage = html_to_text($eventdata->fullmessagehtml);
 +        } else {
 +            $eventdata->fullmessage      = $message;
 +            $eventdata->fullmessagehtml  = '';
 +        }
 +
 +        $eventdata->fullmessageformat = $format;
 +        $eventdata->smallmessage = $message; // Store the message unfiltered. Clean up on output.
 +
 +        $eventdata->timecreated     = time();
 +        $eventdata->notification    = 0;
 +        $messageid = message_send($eventdata);
 +
 +        $messagerecord = $DB->get_record('messages', ['id' => $messageid], 'id, useridfrom, fullmessage, timecreated');
 +        $message = (object) [
 +            'id' => $messagerecord->id,
 +            'useridfrom' => $messagerecord->useridfrom,
 +            'text' => $messagerecord->fullmessage,
 +            'timecreated' => $messagerecord->timecreated
 +        ];
 +        return $message;
      }
  
      /**
       * It will not include blocked users.
       *
       * @param int $userid
 +     * @param int $limitfrom
 +     * @param int $limitnum
       * @return array The list of contact requests
       */
 -    public static function get_contact_requests(int $userid) : array {
 +    public static function get_contact_requests(int $userid, int $limitfrom = 0, int $limitnum = 0) : array {
          global $DB;
  
 -        $ufields = \user_picture::fields('u');
 -        $sql = "SELECT $ufields, mcr.id as contactrequestid
 -                  FROM {user} u
 -                  JOIN {message_contact_requests} mcr
 -                    ON u.id = mcr.userid
 +        $sql = "SELECT mcr.userid
 +                  FROM {message_contact_requests} mcr
               LEFT JOIN {message_users_blocked} mub
 -                    ON (mub.userid = ? AND mub.blockeduserid = u.id)
 +                    ON (mub.userid = ? AND mub.blockeduserid = mcr.userid)
                   WHERE mcr.requesteduserid = ?
 -                   AND u.deleted = 0
                     AND mub.id is NULL
 -              ORDER BY mcr.timecreated DESC";
 +              ORDER BY mcr.timecreated ASC";
 +        if ($contactrequests = $DB->get_records_sql($sql, [$userid, $userid], $limitfrom, $limitnum)) {
 +            $userids = array_keys($contactrequests);
 +            return helper::get_member_info($userid, $userids);
 +        }
  
 -        return $DB->get_records_sql($sql, [$userid, $userid]);
 +        return [];
      }
  
      /**
      /**
       * Checks if the sender can message the recipient.
       *
 -     * @param \stdClass $recipient The user object.
 -     * @param \stdClass $sender The user object.
 +     * @param int $recipientid
 +     * @param int $senderid
       * @return bool true if recipient hasn't blocked sender and sender can contact to recipient, false otherwise.
       */
 -    protected static function can_contact_user(\stdClass $recipient, \stdClass $sender) : bool {
 -        if (has_capability('moodle/site:messageanyuser', \context_system::instance(), $sender->id)) {
 +    protected static function can_contact_user(int $recipientid, int $senderid) : bool {
 +        if (has_capability('moodle/site:messageanyuser', \context_system::instance(), $senderid)) {
              // The sender has the ability to contact any user across the entire site.
              return true;
          }
          // The initial value of $cancontact is null to indicate that a value has not been determined.
          $cancontact = null;
  
 -        if (self::is_blocked($recipient->id, $sender->id)) {
 +        if (self::is_blocked($recipientid, $senderid)) {
              // The recipient has specifically blocked this sender.
              $cancontact = false;
          }
              //
              // The Site option is only possible when the messagingallusers site setting is also enabled.
  
 -            $privacypreference = self::get_user_privacy_messaging_preference($recipient->id);
 +            $privacypreference = self::get_user_privacy_messaging_preference($recipientid);
              if (self::MESSAGE_PRIVACY_SITE === $privacypreference) {
                  // The user preference is to allow any user to contact them.
                  // No need to check anything else.
              } else {
                  // This user only allows their own contacts, and possibly course peers, to contact them.
                  // If the users are contacts then we can avoid the more expensive shared courses check.
 -                $cancontact = self::is_contact($sender->id, $recipient->id);
 +                $cancontact = self::is_contact($senderid, $recipientid);
  
                  if (!$cancontact && self::MESSAGE_PRIVACY_COURSEMEMBER === $privacypreference) {
                      // The users are not contacts and the user allows course member messaging.
                      // Check whether these two users share any course together.
 -                    $sharedcourses = enrol_get_shared_courses($recipient->id, $sender->id, true);
 +                    $sharedcourses = enrol_get_shared_courses($recipientid, $senderid, true);
                      $cancontact = (!empty($sharedcourses));
                  }
              }
  
              // Note: You cannot use empty($sharedcourses) here because this may be an empty array.
              if (null === $sharedcourses) {
 -                $sharedcourses = enrol_get_shared_courses($recipient->id, $sender->id, true);
 +                $sharedcourses = enrol_get_shared_courses($recipientid, $senderid, true);
              }
  
              foreach ($sharedcourses as $course) {
                  // Note: enrol_get_shared_courses will preload any shared context.
 -                if (has_capability('moodle/site:messageanyuser', \context_course::instance($course->id), $sender->id)) {
 +                if (has_capability('moodle/site:messageanyuser', \context_course::instance($course->id), $senderid)) {
                      $cancontact = true;
                      break;
                  }
          if ($members = $DB->get_records('message_conversation_members', ['conversationid' => $conversationid],
                  'timecreated ASC, id ASC', 'userid', $limitfrom, $limitnum)) {
              $userids = array_keys($members);
 -            $members = helper::get_member_info($userid, $userids);
 -
 -            // Check if we want to include contact requests as well.
 -            if ($includecontactrequests) {
 -                list($useridsql, $usersparams) = $DB->get_in_or_equal($userids);
 -
 -                $wheresql = "(userid $useridsql OR requesteduserid $useridsql)";
 -                if ($contactrequests = $DB->get_records_select('message_contact_requests', $wheresql,
 -                        array_merge($usersparams, $usersparams), 'timecreated ASC, id ASC')) {
 -                    foreach ($contactrequests as $contactrequest) {
 -                        if (isset($members[$contactrequest->userid])) {
 -                            $members[$contactrequest->userid]->contactrequests[] = $contactrequest;
 -                        }
 -                        if (isset($members[$contactrequest->requesteduserid])) {
 -                            $members[$contactrequest->requesteduserid]->contactrequests[] = $contactrequest;
 -                        }
 -                    }
 -                }
 -            }
 +            $members = helper::get_member_info($userid, $userids, $includecontactrequests);
  
              return $members;
          }
@@@ -1089,10 -1089,6 +1089,10 @@@ class core_message_api_testcase extend
                  $this->assertObjectHasAttribute('showonlinestatus', $member);
                  $this->assertObjectHasAttribute('isblocked', $member);
                  $this->assertObjectHasAttribute('iscontact', $member);
 +                $this->assertObjectHasAttribute('isdeleted', $member);
 +                $this->assertObjectHasAttribute('canmessage', $member);
 +                $this->assertObjectHasAttribute('requirescontact', $member);
 +                $this->assertObjectHasAttribute('contactrequests', $member);
              }
              $this->assertObjectHasAttribute('messages', $conv);
              foreach ($conv->messages as $message) {
          $this->assertNotContains($ic1->id, array_column($conversations, 'id'));
      }
  
+     /**
+      * Test verifying the behaviour of get_conversations() when fetching favourite conversations with only a single
+      * favourite.
+      */
+     public function test_get_conversations_favourite_conversations_single() {
+         // Get a bunch of conversations, some group, some individual and in different states.
+         list($user1, $user2, $user3, $user4, $ic1, $ic2, $ic3,
+             $gc1, $gc2, $gc3, $gc4, $gc5, $gc6) = $this->create_conversation_test_data();
+         // Mark a single conversation as favourites.
+         \core_message\api::set_favourite_conversation($ic2->id, $user1->id);
+         // Get the conversation, first with no restrictions, confirming the favourite status of the conversations.
+         $conversations = \core_message\api::get_conversations($user1->id);
+         $this->assertCount(6, $conversations);
+         foreach ($conversations as $conv) {
+             if (in_array($conv->id, [$ic2->id])) {
+                 $this->assertTrue($conv->isfavourite);
+             } else {
+                 $this->assertFalse($conv->isfavourite);
+             }
+         }
+         // Now, get ONLY favourite conversations.
+         $conversations = \core_message\api::get_conversations($user1->id, 0, 20, null, true);
+         $this->assertCount(1, $conversations);
+         foreach ($conversations as $conv) {
+             $this->assertTrue($conv->isfavourite);
+             $this->assertEquals($ic2->id, $conv->id);
+         }
+         // Now, try ONLY favourites of type 'group'.
+         $conversations = \core_message\api::get_conversations($user1->id, 0, 20,
+             \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, true);
+         $this->assertEmpty($conversations);
+         // And NO favourite conversations.
+         $conversations = \core_message\api::get_conversations($user1->id, 0, 20, null, false);
+         $this->assertCount(5, $conversations);
+         foreach ($conversations as $conv) {
+             $this->assertFalse($conv->isfavourite);
+             $this->assertNotEquals($ic2, $conv->id);
+         }
+     }
      /**
       * Test verifying the behaviour of get_conversations() when fetching favourite conversations.
       */
          \core_message\api::set_favourite_conversation($ic1->id, $user1->id);
          \core_message\api::set_favourite_conversation($gc2->id, $user1->id);
          \core_message\api::set_favourite_conversation($gc5->id, $user1->id);
+         $favouriteids = [$ic1->id, $gc2->id, $gc5->id];
  
          // Get the conversations, first with no restrictions, confirming the favourite status of the conversations.
          $conversations = \core_message\api::get_conversations($user1->id);
          $this->assertCount(6, $conversations);
          foreach ($conversations as $conv) {
-             if (in_array($conv->id, [$ic1->id, $gc2->id, $gc5->id])) {
+             if (in_array($conv->id, $favouriteids)) {
                  $this->assertTrue($conv->isfavourite);
+             } else {
+                 $this->assertFalse($conv->isfavourite);
              }
          }
  
          $this->assertCount(3, $conversations);
          foreach ($conversations as $conv) {
              $this->assertTrue($conv->isfavourite);
+             $this->assertNotFalse(array_search($conv->id, $favouriteids));
          }
  
          // Now, try ONLY favourites of type 'group'.
          $this->assertCount(2, $conversations);
          foreach ($conversations as $conv) {
              $this->assertTrue($conv->isfavourite);
+             $this->assertNotFalse(array_search($conv->id, [$gc2->id, $gc5->id]));
          }
  
          // And NO favourite conversations.
          $this->assertCount(3, $conversations);
          foreach ($conversations as $conv) {
              $this->assertFalse($conv->isfavourite);
+             $this->assertFalse(array_search($conv->id, $favouriteids));
          }
      }
  
 +    /**
 +     * Test verifying get_conversations when there are users in a group and/or individual conversation. The reason this
 +     * test is performed is because we do not need as much data for group conversations (saving DB calls), so we want
 +     * to confirm this happens.
 +     */
 +    public function test_get_conversations_user_in_group_and_individual_chat() {
 +        $this->resetAfterTest();
 +
 +        $user1 = self::getDataGenerator()->create_user();
 +        $user2 = self::getDataGenerator()->create_user();
 +        $user3 = self::getDataGenerator()->create_user();
 +
 +        $conversation = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
 +            [
 +                $user1->id,
 +                $user2->id
 +            ],
 +            'Individual conversation'
 +        );
 +
 +        testhelper::send_fake_message_to_conversation($user1, $conversation->id);
 +
 +        $conversation = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
 +            [
 +                $user1->id,
 +                $user2->id,
 +            ],
 +            'Group conversation'
 +        );
 +
 +        testhelper::send_fake_message_to_conversation($user1, $conversation->id);
 +
 +        \core_message\api::create_contact_request($user1->id, $user2->id);
 +        \core_message\api::create_contact_request($user1->id, $user3->id);
 +
 +        $conversations = \core_message\api::get_conversations($user2->id);
 +
 +        $groupconversation = array_shift($conversations);
 +        $individualconversation = array_shift($conversations);
 +
 +        $this->assertEquals('Group conversation', $groupconversation->name);
 +        $this->assertEquals('Individual conversation', $individualconversation->name);
 +
 +        $this->assertCount(1, $groupconversation->members);
 +        $this->assertCount(1, $individualconversation->members);
 +
 +        $groupmember = reset($groupconversation->members);
 +        $this->assertNull($groupmember->requirescontact);
 +        $this->assertNull($groupmember->canmessage);
 +        $this->assertEmpty($groupmember->contactrequests);
 +
 +        $individualmember = reset($individualconversation->members);
 +        $this->assertNotNull($individualmember->requirescontact);
 +        $this->assertNotNull($individualmember->canmessage);
 +        $this->assertNotEmpty($individualmember->contactrequests);
 +    }
 +
      /**
       * Test verifying that group linked conversations are returned and contain a subname matching the course name.
       */
  
          // Retrieve the messages.
          $messages = \core_message\api::get_messages($user1->id, $user2->id);
 -        $this->assertDebuggingCalledCount(3);
  
          // Confirm the message data is correct.
          $this->assertEquals(4, count($messages));
  
          // Retrieve the most recent messages.
          $message = \core_message\api::get_most_recent_message($user1->id, $user2->id);
 -        $this->assertDebuggingCalledCount(3);
  
          // Check the results are correct.
          $this->assertEquals($user2->id, $message->useridfrom);
          $this->assertTrue(\core_message\api::can_post_message($student2, $teacher1));
      }
  
 +    /**
 +     * Verify the expected behaviour of the can_send_message_to_conversation() method for authenticated users with default settings.
 +     */
 +    public function test_can_send_message_to_conversation_basic() {
 +        // Create some users.
 +        $user1 = self::getDataGenerator()->create_user();
 +        $user2 = self::getDataGenerator()->create_user();
 +        $user3 = self::getDataGenerator()->create_user();
 +
 +        // Create an individual conversation between user1 and user2.
 +        $ic1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
 +            [
 +                $user1->id,
 +                $user2->id
 +            ]
 +        );
 +
 +        // Create a group conversation between and users 1, 2 and 3.
 +        $gc1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
 +            [
 +                $user1->id,
 +                $user2->id,
 +                $user3->id
 +            ]
 +        );
 +
 +        // For group conversations, there are no user privacy checks, so only membership in the conversation is needed.
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $gc1->id));
 +
 +        // For individual conversations, the default privacy setting of 'only contacts and course members' applies.
 +        // Users are not in the same course, nor are they contacts, so messages cannot be sent.
 +        $this->assertFalse(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +
 +        // Enrol the users into the same course.
 +        $course = $this->getDataGenerator()->create_course();
 +        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 +
 +        // After enrolling users to the course, they should be able to message them with the default privacy setting.
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +    }
 +
 +    /**
 +     * Verify the behaviour of can_send_message_to_conversation() for authenticated users without the sendmessage capability.
 +     */
 +    public function test_can_send_message_to_conversation_sendmessage_cap() {
 +        global $DB;
 +
 +        $user1 = self::getDataGenerator()->create_user();
 +        $user2 = self::getDataGenerator()->create_user();
 +        $user3 = self::getDataGenerator()->create_user();
 +
 +        // Enrol the users into the same course.
 +        $course = $this->getDataGenerator()->create_course();
 +        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user3->id, $course->id);
 +
 +        // Create an individual conversation between user1 and user2.
 +        $ic1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
 +            [
 +                $user1->id,
 +                $user2->id
 +            ]
 +        );
 +
 +        // Group conversation between and users 1, 2 and 3.
 +        $gc1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
 +            [
 +                $user1->id,
 +                $user2->id,
 +                $user3->id
 +            ]
 +        );
 +
 +        // Default settings - user1 can send a message to both conversations.
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $gc1->id));
 +
 +        // Remove the capability to send a message.
 +        $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
 +        unassign_capability('moodle/site:sendmessage', $roleids['user'], context_system::instance());
 +
 +        // Verify that a user cannot send a message to either an individual or a group conversation.
 +        $this->assertFalse(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +        $this->assertFalse(\core_message\api::can_send_message_to_conversation($user1->id, $gc1->id));
 +    }
 +
 +    /**
 +     * Verify the behaviour of can_send_message_to_conversation() for authenticated users without the messageanyuser capability.
 +     */
 +    public function test_can_send_message_to_conversation_messageanyuser_cap() {
 +        global $DB;
 +
 +        $user1 = self::getDataGenerator()->create_user();
 +        $user2 = self::getDataGenerator()->create_user();
 +        $user3 = self::getDataGenerator()->create_user();
 +
 +        // Enrol the users into the same course.
 +        $course = $this->getDataGenerator()->create_course();
 +        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user3->id, $course->id);
 +
 +        // Create an individual conversation between user1 and user2.
 +        $ic1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
 +            [
 +                $user1->id,
 +                $user2->id
 +            ]
 +        );
 +
 +        // Group conversation between and users 1, 2 and 3.
 +        $gc1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
 +            [
 +                $user1->id,
 +                $user2->id,
 +                $user3->id
 +            ]
 +        );
 +
 +        // Update the message preference for user2, so they can only be messaged by contacts.
 +        set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS, $user2->id);
 +
 +        // Verify that the user cannot be contacted in the individual conversation and that groups are unaffected.
 +        $this->assertFalse(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $gc1->id));
 +
 +        // Assign the 'messageanyuser' capability to user1 at system context.
 +        $systemcontext = context_system::instance();
 +        $authenticateduser = $DB->get_record('role', ['shortname' => 'user']);
 +        assign_capability('moodle/site:messageanyuser', CAP_ALLOW, $authenticateduser->id, $systemcontext->id);
 +
 +        // Check that user1 can now message user2 due to the capability, and that group conversations is again unaffected.
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $gc1->id));
 +    }
 +
 +    /**
 +     * Test verifying that users cannot send messages to conversations they are not a part of.
 +     */
 +    public function test_can_post_message_to_conversation_non_member() {
 +        // Create some users.
 +        $user1 = self::getDataGenerator()->create_user();
 +        $user2 = self::getDataGenerator()->create_user();
 +        $user3 = self::getDataGenerator()->create_user();
 +        $user4 = self::getDataGenerator()->create_user();
 +
 +        // Enrol the users into the same course.
 +        $course = $this->getDataGenerator()->create_course();
 +        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user3->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user4->id, $course->id);
 +
 +        // Create an individual conversation between user1 and user2.
 +        $ic1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
 +            [
 +                $user1->id,
 +                $user2->id
 +            ]
 +        );
 +
 +        // Create a group conversation between and users 1, 2 and 3.
 +        $gc1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
 +            [
 +                $user1->id,
 +                $user2->id,
 +                $user3->id
 +            ]
 +        );
 +
 +        // Verify, non members cannot send a message.
 +        $this->assertFalse(\core_message\api::can_send_message_to_conversation($user4->id, $gc1->id));
 +        $this->assertFalse(\core_message\api::can_send_message_to_conversation($user4->id, $ic1->id));
 +    }
 +
 +    /**
 +     * Test verifying the behaviour of the can_send_message_to_conversation method when privacy is set to contacts only.
 +     */
 +    public function test_can_send_message_to_conversation_privacy_contacts_only() {
 +        // Create some users.
 +        $user1 = self::getDataGenerator()->create_user();
 +        $user2 = self::getDataGenerator()->create_user();
 +        $user3 = self::getDataGenerator()->create_user();
 +
 +        // Create an individual conversation between user1 and user2.
 +        $ic1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
 +            [
 +                $user1->id,
 +                $user2->id
 +            ]
 +        );
 +
 +        // Create a group conversation between and users 1, 2 and 3.
 +        $gc1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
 +            [
 +                $user1->id,
 +                $user2->id,
 +                $user3->id
 +            ]
 +        );
 +
 +        // Set the message privacy preference to 'contacts only' for user 2.
 +        set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS, $user2->id);
 +
 +        // Verify that user1 cannot send a message to the individual conversation, but that the group conversation is unaffected.
 +        $this->assertFalse(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $gc1->id));
 +
 +        // Now, simulate a contact request (and approval) between user1 and user2.
 +        \core_message\api::create_contact_request($user1->id, $user2->id);
 +        \core_message\api::confirm_contact_request($user1->id, $user2->id);
 +
 +        // Verify user1 can now message user2 again via their individual conversation.
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +    }
 +
 +    /**
 +     * Test verifying the behaviour of the can_send_message_to_conversation method when privacy is set to contacts / course members.
 +     */
 +    public function test_can_send_message_to_conversation_privacy_contacts_course() {
 +        // Create some users.
 +        $user1 = self::getDataGenerator()->create_user();
 +        $user2 = self::getDataGenerator()->create_user();
 +        $user3 = self::getDataGenerator()->create_user();
 +
 +        // Set the message privacy preference to 'contacts + course members' for user 2.
 +        set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER, $user2->id);
 +
 +        // Create an individual conversation between user1 and user2.
 +        $ic1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
 +            [
 +                $user1->id,
 +                $user2->id
 +            ]
 +        );
 +
 +        // Create a group conversation between and users 1, 2 and 3.
 +        $gc1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
 +            [
 +                $user1->id,
 +                $user2->id,
 +                $user3->id
 +            ]
 +        );
 +
 +        // Verify that users in a group conversation can message one another (i.e. privacy controls ignored).
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $gc1->id));
 +
 +        // Verify that user1 can not message user2 unless they are either contacts, or share a course.
 +        $this->assertFalse(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +
 +        // Enrol the users into the same course.
 +        $course = $this->getDataGenerator()->create_course();
 +        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user3->id, $course->id);
 +
 +        // Verify that user1 can send a message to user2, based on the shared course, without being a contact.
 +        $this->assertFalse(\core_message\api::is_contact($user1->id, $user2->id));
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +    }
 +
 +    /**
 +     * Test verifying the behaviour of the can_send_message_to_conversation method when privacy is set to any user.
 +     */
 +    public function test_can_send_message_to_conversation_privacy_sitewide() {
 +        // Create some users.
 +        $user1 = self::getDataGenerator()->create_user();
 +        $user2 = self::getDataGenerator()->create_user();
 +        $user3 = self::getDataGenerator()->create_user();
 +
 +        // Create an individual conversation between user1 and user2.
 +        $ic1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
 +            [
 +                $user1->id,
 +                $user2->id
 +            ]
 +        );
 +
 +        // Create a group conversation between and users 1, 2 and 3.
 +        $gc1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
 +            [
 +                $user1->id,
 +                $user2->id,
 +                $user3->id
 +            ]
 +        );
 +
 +        // By default, the messaging privacy dictates that users can only be contacted by contacts, and members of their courses.
 +        // Verify also, that groups are not restricted in this way.
 +        $this->assertFalse(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $gc1->id));
 +
 +        // Enable site-wide messagging privacy setting.
 +        // This enables a privacy option for users, allowing them to choose to be contactable by anybody on the site.
 +        set_config('messagingallusers', true);
 +
 +        // Set the second user's preference to receive messages from everybody.
 +        set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_SITE, $user2->id);
 +
 +        // Check that user1 can send user2 a message, and that the group conversation is unaffected.
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $gc1->id));
 +
 +        // Disable site-wide messagging privacy setting. The user will be able to receive messages from contacts
 +        // and members sharing a course with her.
 +        set_config('messagingallusers', false);
 +
 +        // As site-wide messaging setting is disabled, the value for user2 will be changed to MESSAGE_PRIVACY_COURSEMEMBER.
 +        // Verify also that the group conversation is unaffected.
 +        $this->assertFalse(\core_message\api::can_send_message_to_conversation($user1->id, $ic1->id));
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $gc1->id));
 +    }
 +
 +    /**
 +     * Test verifying the behaviour of the can_send_message_to_conversation method when a user is blocked.
 +     */
 +    public function test_can_send_message_to_conversation_when_blocked() {
 +        $user1 = self::getDataGenerator()->create_user();
 +        $user2 = self::getDataGenerator()->create_user();
 +        $user3 = self::getDataGenerator()->create_user();
 +
 +        // Create an individual conversation between user1 and user2.
 +        $ic1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
 +            [
 +                $user1->id,
 +                $user2->id
 +            ]
 +        );
 +
 +        // Create a group conversation between and users 1, 2 and 3.
 +        $gc1 = \core_message\api::create_conversation(
 +            \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
 +            [
 +                $user1->id,
 +                $user2->id,
 +                $user3->id
 +            ]
 +        );
 +
 +        // Enrol the users into the same course.
 +        $course = $this->getDataGenerator()->create_course();
 +        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user3->id, $course->id);
 +
 +        // Block the second user.
 +        \core_message\api::block_user($user1->id, $user2->id);
 +
 +        // Check that user2 can not send user1 a message in their individual conversation.
 +        $this->assertFalse(\core_message\api::can_send_message_to_conversation($user2->id, $ic1->id));
 +
 +        // Verify that group conversations are unaffected.
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $gc1->id));
 +        $this->assertTrue(\core_message\api::can_send_message_to_conversation($user2->id, $gc1->id));
 +    }
 +
      /**
       * Tests get_user_privacy_messaging_preference method.
       */
  
          // Retrieve the messages from $time, which should be all of them.
          $messages = \core_message\api::get_messages($user1->id, $user2->id, 0, 0, 'timecreated ASC', $time);
 -        $this->assertDebuggingCalledCount(3);
  
          // Confirm the message data is correct.
          $this->assertEquals(4, count($messages));
  
          // Retrieve the messages from $time + 3, which should only be the 2 last messages.
          $messages = \core_message\api::get_messages($user1->id, $user2->id, 0, 0, 'timecreated ASC', $time + 3);
 -        $this->assertDebuggingCalledCount(3);
  
          // Confirm the message data is correct.
          $this->assertEquals(2, count($messages));
  
          // Retrieve the messages up until $time + 4, which should be all of them.
          $messages = \core_message\api::get_messages($user1->id, $user2->id, 0, 0, 'timecreated ASC', 0, $time + 4);
 -        $this->assertDebuggingCalledCount(3);
  
          // Confirm the message data is correct.
          $this->assertEquals(4, count($messages));
  
          // Retrieve the messages up until $time + 2, which should be the first two.
          $messages = \core_message\api::get_messages($user1->id, $user2->id, 0, 0, 'timecreated ASC', 0, $time + 2);
 -        $this->assertDebuggingCalledCount(3);
  
          // Confirm the message data is correct.
          $this->assertEquals(2, count($messages));
  
          // Retrieve the messages from $time + 2 up until $time + 3, which should be 2nd and 3rd message.
          $messages = \core_message\api::get_messages($user1->id, $user2->id, 0, 0, 'timecreated ASC', $time + 2, $time + 3);
 -        $this->assertDebuggingCalledCount(3);
  
          // Confirm the message data is correct.
          $this->assertEquals(2, count($messages));
          $request = reset($requests);
  
          $this->assertEquals($user2->id, $request->id);
 -        $this->assertEquals($user2->picture, $request->picture);
 -        $this->assertEquals($user2->firstname, $request->firstname);
 -        $this->assertEquals($user2->lastname, $request->lastname);
 -        $this->assertEquals($user2->firstnamephonetic, $request->firstnamephonetic);
 -        $this->assertEquals($user2->lastnamephonetic, $request->lastnamephonetic);
 -        $this->assertEquals($user2->middlename, $request->middlename);
 -        $this->assertEquals($user2->alternatename, $request->alternatename);
 -        $this->assertEquals($user2->email, $request->email);
 +        $this->assertEquals(fullname($user2), $request->fullname);
 +        $this->assertObjectHasAttribute('profileimageurl', $request);
 +        $this->assertObjectHasAttribute('profileimageurlsmall', $request);
 +        $this->assertObjectHasAttribute('isonline', $request);
 +        $this->assertObjectHasAttribute('showonlinestatus', $request);
 +        $this->assertObjectHasAttribute('isblocked', $request);
 +        $this->assertObjectHasAttribute('iscontact', $request);
 +    }
 +
 +    /**
 +     * Test getting contact requests when there are none.
 +     */
 +    public function test_get_contact_requests_no_requests() {
 +        $this->resetAfterTest();
 +
 +        $user1 = self::getDataGenerator()->create_user();
 +
 +        $requests = \core_message\api::get_contact_requests($user1->id);
 +
 +        $this->assertEmpty($requests);
 +    }
 +
 +    /**
 +     * Test getting contact requests with limits.
 +     */
 +    public function test_get_contact_requests_with_limits() {
 +        $this->resetAfterTest();
 +
 +        $user1 = self::getDataGenerator()->create_user();
 +        $user2 = self::getDataGenerator()->create_user();
 +        $user3 = self::getDataGenerator()->create_user();
 +
 +        \core_message\api::create_contact_request($user2->id, $user1->id);
 +        \core_message\api::create_contact_request($user3->id, $user1->id);
 +
 +        $requests = \core_message\api::get_contact_requests($user1->id, 0, 1);
 +
 +        $this->assertCount(1, $requests);
      }
  
      /**
          $this->assertEquals(true, $member1->showonlinestatus);
          $this->assertEquals(false, $member1->iscontact);
          $this->assertEquals(false, $member1->isblocked);
 -        $this->assertObjectNotHasAttribute('contactrequests', $member1);
 +        $this->assertObjectHasAttribute('contactrequests', $member1);
 +        $this->assertEmpty($member1->contactrequests);
  
          $this->assertEquals($user2->id, $member2->id);
          $this->assertEquals(fullname($user2), $member2->fullname);
          $this->assertEquals(true, $member2->showonlinestatus);
          $this->assertEquals(true, $member2->iscontact);
          $this->assertEquals(false, $member2->isblocked);
 -        $this->assertObjectNotHasAttribute('contactrequests', $member2);
 +        $this->assertObjectHasAttribute('contactrequests', $member2);
 +        $this->assertEmpty($member2->contactrequests);
  
          $this->assertEquals($user3->id, $member3->id);
          $this->assertEquals(fullname($user3), $member3->fullname);
          $this->assertEquals(true, $member3->showonlinestatus);
          $this->assertEquals(false, $member3->iscontact);
          $this->assertEquals(true, $member3->isblocked);
 -        $this->assertObjectNotHasAttribute('contactrequests', $member3);
 +        $this->assertObjectHasAttribute('contactrequests', $member3);
 +        $this->assertEmpty($member3->contactrequests);
      }
  
      /**
          $this->assertEquals(true, $member1->showonlinestatus);
          $this->assertEquals(false, $member1->iscontact);
          $this->assertEquals(false, $member1->isblocked);
 -        $this->assertCount(3, $member1->contactrequests);
 +        $this->assertCount(2, $member1->contactrequests);
  
          $this->assertEquals($user2->id, $member2->id);
          $this->assertEquals(fullname($user2), $member2->fullname);
          $this->assertEquals(true, $member2->showonlinestatus);
          $this->assertEquals(true, $member2->iscontact);
          $this->assertEquals(false, $member2->isblocked);
 -        $this->assertCount(2, $member2->contactrequests);
 +        $this->assertCount(1, $member2->contactrequests);
  
          $this->assertEquals($user3->id, $member3->id);
          $this->assertEquals(fullname($user3), $member3->fullname);
          $this->assertEquals(true, $member3->showonlinestatus);
          $this->assertEquals(false, $member3->iscontact);
          $this->assertEquals(true, $member3->isblocked);
 -        $this->assertCount(2, $member3->contactrequests);
 +        $this->assertCount(1, $member3->contactrequests);
  
          // Confirm the contact requests are OK.
          $request1 = array_shift($member1->contactrequests);
          $request2 = array_shift($member1->contactrequests);
 -        $request3 = array_shift($member1->contactrequests);
  
          $this->assertEquals($user1->id, $request1->userid);
          $this->assertEquals($user2->id, $request1->requesteduserid);
          $this->assertEquals($user1->id, $request2->userid);
          $this->assertEquals($user3->id, $request2->requesteduserid);
  
 -        $this->assertEquals($user1->id, $request3->userid);
 -        $this->assertEquals($user4->id, $request3->requesteduserid);
 -
          $request1 = array_shift($member2->contactrequests);
 -        $request2 = array_shift($member2->contactrequests);
  
          $this->assertEquals($user1->id, $request1->userid);
          $this->assertEquals($user2->id, $request1->requesteduserid);
  
 -        $this->assertEquals($user2->id, $request2->userid);
 -        $this->assertEquals($user3->id, $request2->requesteduserid);
 -
          $request1 = array_shift($member3->contactrequests);
 -        $request2 = array_shift($member3->contactrequests);
  
          $this->assertEquals($user1->id, $request1->userid);
          $this->assertEquals($user3->id, $request1->requesteduserid);
 +    }
  
 -        $this->assertEquals($user2->id, $request2->userid);
 -        $this->assertEquals($user3->id, $request2->requesteduserid);
 +    /**
 +     * Test verifying that messages can be sent to existing individual conversations.
 +     */
 +    public function test_send_message_to_conversation_individual_conversation() {
 +        // Get a bunch of conversations, some group, some individual and in different states.
 +        list($user1, $user2, $user3, $user4, $ic1, $ic2, $ic3,
 +            $gc1, $gc2, $gc3, $gc4, $gc5, $gc6) = $this->create_conversation_test_data();
 +
 +        // Enrol the users into the same course so the privacy checks will pass using default (contact+course members) setting.
 +        $course = $this->getDataGenerator()->create_course();
 +        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user3->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user4->id, $course->id);
 +
 +        // Redirect messages.
 +        // This marks messages as read, but we can still observe and verify the number of conversation recipients,
 +        // based on the message_viewed events generated as part of marking the message as read for each user.
 +        $this->preventResetByRollback();
 +        $sink = $this->redirectMessages();
 +
 +        // Send a message to an individual conversation.
 +        $sink = $this->redirectEvents();
 +        $message1 = \core_message\api::send_message_to_conversation($user1->id, $ic1->id, 'this is a message', FORMAT_MOODLE);
 +        $events = $sink->get_events();
 +
 +        // Verify the message returned.
 +        $this->assertInstanceOf(\stdClass::class, $message1);
 +        $this->assertObjectHasAttribute('id', $message1);
 +        $this->assertAttributeEquals($user1->id, 'useridfrom', $message1);
 +        $this->assertAttributeEquals('this is a message', 'text', $message1);
 +        $this->assertObjectHasAttribute('timecreated', $message1);
 +
 +        // Verify events. Note: the event is a message read event because of an if (PHPUNIT) conditional within message_send(),
 +        // however, we can still determine the number and ids of any recipients this way.
 +        $this->assertCount(1, $events);
 +        $userids = array_column($events, 'userid');
 +        $this->assertNotContains($user1->id, $userids);
 +        $this->assertContains($user2->id, $userids);
 +    }
 +
 +    /**
 +     * Test verifying that messages can be sent to existing group conversations.
 +     */
 +    public function test_send_message_to_conversation_group_conversation() {
 +        // Get a bunch of conversations, some group, some individual and in different states.
 +        list($user1, $user2, $user3, $user4, $ic1, $ic2, $ic3,
 +            $gc1, $gc2, $gc3, $gc4, $gc5, $gc6) = $this->create_conversation_test_data();
 +
 +        // Enrol the users into the same course so the privacy checks will pass using default (contact+course members) setting.
 +        $course = $this->getDataGenerator()->create_course();
 +        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user3->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user4->id, $course->id);
 +
 +        // Redirect messages.
 +        // This marks messages as read, but we can still observe and verify the number of conversation recipients,
 +        // based on the message_viewed events generated as part of marking the message as read for each user.
 +        $this->preventResetByRollback();
 +        $sink = $this->redirectMessages();
 +
 +        // Send a message to a group conversation.
 +        $sink = $this->redirectEvents();
 +        $message1 = \core_message\api::send_message_to_conversation($user1->id, $gc2->id, 'message to the group', FORMAT_MOODLE);
 +        $events = $sink->get_events();
 +
 +        // Verify the message returned.
 +        $this->assertInstanceOf(\stdClass::class, $message1);
 +        $this->assertObjectHasAttribute('id', $message1);
 +        $this->assertAttributeEquals($user1->id, 'useridfrom', $message1);
 +        $this->assertAttributeEquals('message to the group', 'text', $message1);
 +        $this->assertObjectHasAttribute('timecreated', $message1);
 +
 +        // Verify events. Note: the event is a message read event because of an if (PHPUNIT) conditional within message_send(),
 +        // however, we can still determine the number and ids of any recipients this way.
 +        $this->assertCount(2, $events);
 +        $userids = array_column($events, 'userid');
 +        $this->assertNotContains($user1->id, $userids);
 +        $this->assertContains($user3->id, $userids);
 +        $this->assertContains($user4->id, $userids);
 +    }
 +
 +    /**
 +     * Test verifying that messages cannot be sent to conversations that don't exist.
 +     */
 +    public function test_send_message_to_conversation_non_existent_conversation() {
 +        // Get a bunch of conversations, some group, some individual and in different states.
 +        list($user1, $user2, $user3, $user4, $ic1, $ic2, $ic3,
 +            $gc1, $gc2, $gc3, $gc4, $gc5, $gc6) = $this->create_conversation_test_data();
 +
 +        $this->expectException(\moodle_exception::class);
 +        \core_message\api::send_message_to_conversation($user1->id, 0, 'test', FORMAT_MOODLE);
 +    }
 +
 +    /**
 +     * Test verifying that messages cannot be sent to conversations by users who are not members.
 +     */
 +    public function test_send_message_to_conversation_non_member() {
 +        // Get a bunch of conversations, some group, some individual and in different states.
 +        list($user1, $user2, $user3, $user4, $ic1, $ic2, $ic3,
 +            $gc1, $gc2, $gc3, $gc4, $gc5, $gc6) = $this->create_conversation_test_data();
 +
 +        // Enrol the users into the same course so the privacy checks will pass using default (contact+course members) setting.
 +        $course = $this->getDataGenerator()->create_course();
 +        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user3->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user4->id, $course->id);
 +
 +        $this->expectException(\moodle_exception::class);
 +        \core_message\api::send_message_to_conversation($user3->id, $ic1->id, 'test', FORMAT_MOODLE);
 +    }
 +
 +    /**
 +     * Test verifying that messages cannot be sent to conversations by users who are not members.
 +     */
 +    public function test_send_message_to_conversation_blocked_user() {
 +        // Get a bunch of conversations, some group, some individual and in different states.
 +        list($user1, $user2, $user3, $user4, $ic1, $ic2, $ic3,
 +            $gc1, $gc2, $gc3, $gc4, $gc5, $gc6) = $this->create_conversation_test_data();
 +
 +        // Enrol the users into the same course so the privacy checks will pass using default (contact+course members) setting.
 +        $course = $this->getDataGenerator()->create_course();
 +        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user3->id, $course->id);
 +        $this->getDataGenerator()->enrol_user($user4->id, $course->id);
 +
 +        // User 1 blocks user 2.
 +        \core_message\api::block_user($user1->id, $user2->id);
 +
 +        // Verify that a message can be sent to any group conversation in which user1 and user2 are members.
 +        $this->assertNotEmpty(\core_message\api::send_message_to_conversation($user1->id, $gc2->id, 'Hey guys', FORMAT_PLAIN));
 +
 +        // User 2 cannot send a message to the conversation with user 1.
 +        $this->expectException(\moodle_exception::class);
 +        \core_message\api::send_message_to_conversation($user2->id, $ic1->id, 'test', FORMAT_MOODLE);
      }
  
      /**