diff --git a/favourites/classes/local/service/user_favourite_service.php b/favourites/classes/local/service/user_favourite_service.php index a63692a675c..e569523ec63 100644 --- a/favourites/classes/local/service/user_favourite_service.php +++ b/favourites/classes/local/service/user_favourite_service.php @@ -110,6 +110,51 @@ class user_favourite_service { ); } + /** + * Returns the SQL required to include favourite information for a given component/itemtype combination. + * + * Generally, find_favourites_by_type() is the recommended way to fetch favourites. + * + * This method is used to include favourite information in external queries, for items identified by their + * component and itemtype, matching itemid to the $joinitemid, and for the user to which this service is scoped. + * + * It uses a LEFT JOIN to preserve the original records. If you wish to restrict your records, please consider using a + * "WHERE {$tablealias}.id IS NOT NULL" in your query. + * + * Example usage: + * + * list($sql, $params) = $service->get_join_sql_by_type('core_message', 'message_conversations', 'myfavouritetablealias', + * 'conv.id'); + * Results in $sql: + * "LEFT JOIN {favourite} fav + * ON fav.component = :favouritecomponent + * AND fav.itemtype = :favouriteitemtype + * AND fav.userid = 1234 + * AND fav.itemid = conv.id" + * and $params: + * ['favouritecomponent' => 'core_message', 'favouriteitemtype' => 'message_conversations'] + * + * @param string $component the frankenstyle component name. + * @param string $itemtype the type of the favourited item. + * @param string $tablealias the desired alias for the favourites table. + * @param string $joinitemid the table and column identifier which the itemid is joined to. E.g. conversation.id. + * @return array the list of sql and params, in the format [$sql, $params]. + */ + public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array { + $sql = " LEFT JOIN {favourite} {$tablealias} + ON {$tablealias}.component = :favouritecomponent + AND {$tablealias}.itemtype = :favouriteitemtype + AND {$tablealias}.userid = {$this->userid} + AND {$tablealias}.itemid = {$joinitemid} "; + + $params = [ + 'favouritecomponent' => $component, + 'favouriteitemtype' => $itemtype, + ]; + + return [$sql, $params]; + } + /** * Delete a favourite item from an area and from within a context. * @@ -186,7 +231,7 @@ class user_favourite_service { * @param string $component the frankenstyle component name. * @param string $itemtype the type of the favourited item. * @param \context|null $context the context of the item which was favourited. - * @return favourite|null + * @return int */ public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) { $criteria = [ diff --git a/favourites/tests/service_test.php b/favourites/tests/service_test.php index 5ead879fff7..c48e81feeac 100644 --- a/favourites/tests/service_test.php +++ b/favourites/tests/service_test.php @@ -429,4 +429,35 @@ class user_favourite_service_testcase extends advanced_testcase { // Gets counted if we include all contexts. $this->assertEquals(3, $service->count_favourites_by_type('core_course', 'course')); } + + /** + * Verify that the join sql generated by get_join_sql_by_type is valid and can be used to include favourite information. + */ + public function test_get_join_sql_by_type() { + global $DB; + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Get a user_favourite_service for the user. + // We need to use a real (DB) repository, as we want to run the SQL. + $repo = new \core_favourites\local\repository\favourite_repository(); + $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); + + // Favourite the first course only. + $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); + + // Generate the join snippet. + list($favsql, $favparams) = $service->get_join_sql_by_type('core_course', 'course', 'favalias', 'c.id'); + + // Join against a simple select, including the 2 courses only. + $params = ['courseid1' => $course1context->instanceid, 'courseid2' => $course2context->instanceid]; + $params = $params + $favparams; + $records = $DB->get_records_sql("SELECT c.id, favalias.component + FROM {course} c $favsql + WHERE c.id = :courseid1 OR c.id = :courseid2", $params); + + // Verify the favourite information is returned, but only for the favourited course. + $this->assertCount(2, $records); + $this->assertEquals('core_course', $records[$course1context->instanceid]->component); + $this->assertEmpty($records[$course2context->instanceid]->component); + } } diff --git a/lib/db/services.php b/lib/db/services.php index 45262875da4..541fecbca0d 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -1125,6 +1125,15 @@ $functions = array( 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), 'ajax' => true, ), + 'core_message_get_conversation_counts' => array( + 'classname' => 'core_message_external', + 'methodname' => 'get_conversation_counts', + 'classpath' => 'message/externallib.php', + 'description' => 'Retrieve a list of conversation counts, indexed by type.', + 'type' => 'read', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), + 'ajax' => true, + ), 'core_message_get_conversation_members' => array( 'classname' => 'core_message_external', 'methodname' => 'get_conversation_members', diff --git a/message/classes/api.php b/message/classes/api.php index 017b81cd500..28d7706ae4c 100644 --- a/message/classes/api.php +++ b/message/classes/api.php @@ -730,12 +730,11 @@ class api { // Now, create the final return structure. $arrconversations = []; foreach ($conversations as $conversation) { - // Do not include any individual conversation which: - // a) Contains a deleted member or - // b) Does not contain a recent message for the user (this happens if the user has deleted all messages). + // Do not include any individual conversations which do not contain a recent message for the user. + // This happens if the user has deleted all messages. // Group conversations with deleted users or no messages are always returned. if ($conversation->conversationtype == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL - && (isset($deletedmembers[$conversation->id]) || empty($conversation->messageid))) { + && (empty($conversation->messageid))) { continue; } @@ -1489,78 +1488,80 @@ class api { * Returns the count of conversations (collection of messages from a single user) for * the given user. * - * @param \stdClass $user The user who's conversations should be counted - * @param int $type The conversation type - * @param bool $excludefavourites Exclude favourite conversations - * @return int the count of the user's unread conversations + * @param int $userid The user whose conversations should be counted. + * @return array the array of conversations counts, indexed by type. */ - public static function count_conversations($user, int $type = null, bool $excludefavourites = false) { + public static function get_conversation_counts(int $userid) : array { global $DB; - $params = []; - $favouritessql = ''; + // Some restrictions we need to be aware of: + // - Individual conversations containing soft-deleted user must be counted. + // - Individual conversations containing only deleted messages must NOT be counted. + // - Group conversations with 0 messages must be counted. + // - Linked conversations which are disabled (enabled = 0) must NOT be counted. + // - Any type of conversation can be included in the favourites count, however, the type counts and the favourites count + // are mutually exclusive; any conversations which are counted in favourites cannot be counted elsewhere. - if ($excludefavourites) { - $favouritessql = "AND m.conversationid NOT IN ( - SELECT itemid - FROM {favourite} - WHERE component = 'core_message' - AND itemtype = 'message_conversations' - AND userid = ? - )"; - $params[] = $user->id; + // First, ask the favourites service to give us the join SQL for favourited conversations, + // so we can include favourite information in the query. + $usercontext = \context_user::instance($userid); + $favservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); + list($favsql, $favparams) = $favservice->get_join_sql_by_type('core_message', 'message_conversations', 'fav', 'mc.id'); + + $sql = "SELECT mc.type, fav.itemtype, COUNT(DISTINCT mc.id) as count + FROM {message_conversations} mc + INNER JOIN {message_conversation_members} mcm + ON mcm.conversationid = mc.id + LEFT JOIN ( + SELECT m.conversationid as convid, MAX(m.timecreated) as maxtime + FROM {messages} m + INNER JOIN {message_conversation_members} mcm + ON mcm.conversationid = m.conversationid + LEFT JOIN {message_user_actions} mua + ON (mua.messageid = m.id AND mua.userid = :userid AND mua.action = :action) + WHERE mua.id is NULL + AND mcm.userid = :userid2 + GROUP BY m.conversationid + ) maxvisibleconvmessage + ON maxvisibleconvmessage.convid = mc.id + $favsql + WHERE mcm.userid = :userid3 + AND mc.enabled = :enabled + AND ((mc.type = :individualtype AND maxvisibleconvmessage.convid IS NOT NULL) OR (mc.type = :grouptype)) + GROUP BY mc.type, fav.itemtype + ORDER BY mc.type ASC"; + + $params = [ + 'userid' => $userid, + 'userid2' => $userid, + 'userid3' => $userid, + 'userid4' => $userid, + 'action' => self::MESSAGE_ACTION_DELETED, + 'enabled' => self::MESSAGE_CONVERSATION_ENABLED, + 'individualtype' => self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, + 'grouptype' => self::MESSAGE_CONVERSATION_TYPE_GROUP, + ] + $favparams; + + // Assemble the return array. + $counts = [ + 'favourites' => 0, + 'types' => [ + self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + self::MESSAGE_CONVERSATION_TYPE_GROUP => 0 + ] + ]; + + $countsrs = $DB->get_recordset_sql($sql, $params); + foreach ($countsrs as $key => $val) { + if (!empty($val->itemtype)) { + $counts['favourites'] = $val->count; + continue; + } + $counts['types'][$val->type] = $val->count; } + $countsrs->close(); - switch($type) { - case null: - $params = array_merge([$user->id, self::MESSAGE_ACTION_DELETED, $user->id], $params); - $sql = "SELECT COUNT(DISTINCT(m.conversationid)) - FROM {messages} m - LEFT JOIN {message_conversations} c - ON m.conversationid = c.id - LEFT JOIN {message_user_actions} ma - ON ma.messageid = m.id - LEFT JOIN {message_conversation_members} mcm - ON m.conversationid = mcm.conversationid - WHERE mcm.userid = ? - AND ( - c.type != " . self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL . " - OR - ( - (ma.action IS NULL OR ma.action != ? OR ma.userid != ?) - AND c.type = " . self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL . " - ) - ) - ${favouritessql}"; - break; - case self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL: - $params = array_merge([self::MESSAGE_ACTION_DELETED, $user->id, $user->id], $params); - $sql = "SELECT COUNT(DISTINCT(m.conversationid)) - FROM {messages} m - LEFT JOIN {message_conversations} c - ON m.conversationid = c.id - LEFT JOIN {message_user_actions} ma - ON ma.messageid = m.id - LEFT JOIN {message_conversation_members} mcm - ON m.conversationid = mcm.conversationid - WHERE (ma.action IS NULL OR ma.action != ? OR ma.userid != ?) - AND mcm.userid = ? - AND c.type = " . self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL . " - ${favouritessql}"; - break; - default: - $params = array_merge([$user->id, $type], $params); - $sql = "SELECT COUNT(m.conversationid) - FROM {message_conversation_members} m - LEFT JOIN {message_conversations} c - ON m.conversationid = c.id - WHERE m.userid = ? - AND c.type = ? - ${favouritessql}"; - - } - - return $DB->count_records_sql($sql, $params); + return $counts; } /** diff --git a/message/classes/helper.php b/message/classes/helper.php index 2e9c99a0b6c..c51c619f31e 100644 --- a/message/classes/helper.php +++ b/message/classes/helper.php @@ -557,7 +557,7 @@ class helper { $sender = new \stdClass(); $sender->id = $referenceuserid; - $data->canmessage = api::can_post_message($recipient, $sender); + $data->canmessage = !$data->isdeleted && api::can_post_message($recipient, $sender); } // Populate the contact requests, even if we don't need them. diff --git a/message/externallib.php b/message/externallib.php index ad6e260419d..c5a3fca2c8f 100644 --- a/message/externallib.php +++ b/message/externallib.php @@ -4351,4 +4351,79 @@ class core_message_external extends external_api { self::get_conversation_member_structure() ); } + + /** + * Returns description of method parameters for get_conversation_counts() method. + * + * @return external_function_parameters + */ + public static function get_conversation_counts_parameters() { + return new external_function_parameters( + [ + 'userid' => new external_value(PARAM_INT, 'id of the user, 0 for current user', VALUE_DEFAULT, 0) + ] + ); + } + + /** + * Returns an array of conversation counts for the various types of conversations, including favourites. + * + * Return format: + * [ + * 'favourites' => 0, + * 'types' => [ + * \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + * \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0 + * ] + * ] + * + * @param int $userid the id of the user whose counts we are fetching. + * @return array the array of conversation counts, indexed by type. + * @throws moodle_exception if the current user cannot perform this action. + */ + public static function get_conversation_counts(int $userid) { + global $CFG, $USER; + + // All the business logic checks that really shouldn't be in here. + if (empty($CFG->messaging)) { + throw new moodle_exception('disabled', 'message'); + } + + if (empty($userid)) { + $userid = $USER->id; + } + + $params = ['userid' => $userid]; + $params = self::validate_parameters(self::get_conversation_counts_parameters(), $params); + + $systemcontext = context_system::instance(); + self::validate_context($systemcontext); + + if (($USER->id != $params['userid']) && !has_capability('moodle/site:readallmessages', $systemcontext)) { + throw new moodle_exception('You do not have permission to perform this action.'); + } + + return \core_message\api::get_conversation_counts($params['userid']); + } + + /** + * Get conversation counts return description. + * + * @return external_description + */ + public static function get_conversation_counts_returns() { + return new external_single_structure( + [ + 'favourites' => new external_value(PARAM_INT, 'Total number of favourite conversations'), + 'types' => new external_single_structure( + [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => new external_value(PARAM_INT, + 'Total number of individual conversations'), + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => new external_value(PARAM_INT, + 'Total number of group conversations'), + ] + ), + ] + ); + } } diff --git a/message/lib.php b/message/lib.php index 16196d0925e..e477900f657 100644 --- a/message/lib.php +++ b/message/lib.php @@ -834,20 +834,12 @@ function core_message_standard_after_main_region_html() { } $renderer = $PAGE->get_renderer('core'); - $individualconversationcount = \core_message\api::count_conversations( - $USER, - \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, - true - ); - $groupconversationcount = \core_message\api::count_conversations( - $USER, - \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, - true - ); - $systemcontext = \context_system::instance(); - $usercontext = \context_user::instance($USER->id); - $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); - $favouriteconversationcount = $ufservice->count_favourites_by_type('core_message', 'message_conversations', $systemcontext); + + // Get the counts. + $conversationcounts = \core_message\api::get_conversation_counts($USER->id); + $individualconversationcount = $conversationcounts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL]; + $groupconversationcount = $conversationcounts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP]; + $favouriteconversationcount = $conversationcounts['favourites']; $requestcount = \core_message\api::count_received_contact_requests($USER); $contactscount = \core_message\api::count_contacts($USER->id); diff --git a/message/tests/api_test.php b/message/tests/api_test.php index 5793128183f..bdee4edf40b 100644 --- a/message/tests/api_test.php +++ b/message/tests/api_test.php @@ -925,8 +925,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase { // Retrieve the conversations. $conversations = \core_message\api::get_conversations($user1->id, 0, 20, null, true); - // We should only have one conversation because the other user was deleted. - $this->assertCount(1, $conversations); + // We should have both conversations, despite the other user being soft-deleted. + $this->assertCount(2, $conversations); // Confirm the conversation is from the non-deleted user. $conversation = reset($conversations); @@ -1373,27 +1373,28 @@ class core_message_api_testcase extends core_message_messagelib_testcase { $gc1, $gc2, $gc3, $gc4, $gc5, $gc6) = $this->create_conversation_test_data(); // Delete the second user and retrieve the conversations. - // We should have 5, as $ic1 drops off the list. - // Group conversations remain albeit with less members. + // We should have 6 still, as conversations with soft-deleted users are still returned. + // Group conversations are also present, albeit with less members. delete_user($user2); // This is to confirm an exception is not thrown when a user AND the user context is deleted. // We no longer delete the user context, but historically we did. context_helper::delete_instance(CONTEXT_USER, $user2->id); $conversations = \core_message\api::get_conversations($user1->id); - $this->assertCount(5, $conversations); + $this->assertCount(6, $conversations); $this->assertEquals($gc3->id, $conversations[0]->id); $this->assertcount(1, $conversations[0]->members); $this->assertEquals($gc2->id, $conversations[1]->id); $this->assertcount(1, $conversations[1]->members); $this->assertEquals($ic2->id, $conversations[2]->id); - $this->assertEquals($gc5->id, $conversations[3]->id); - $this->assertEquals($gc4->id, $conversations[4]->id); + $this->assertEquals($ic1->id, $conversations[3]->id); + $this->assertEquals($gc5->id, $conversations[4]->id); + $this->assertEquals($gc4->id, $conversations[5]->id); // Delete a user from a group conversation where that user had sent the most recent message. // This user will still be present in the members array, as will the message in the messages array. delete_user($user4); $conversations = \core_message\api::get_conversations($user1->id); - $this->assertCount(5, $conversations); + $this->assertCount(6, $conversations); $this->assertEquals($gc2->id, $conversations[1]->id); $this->assertcount(1, $conversations[1]->members); $this->assertEquals($user4->id, $conversations[1]->members[$user4->id]->id); @@ -1401,17 +1402,19 @@ class core_message_api_testcase extends core_message_messagelib_testcase { $this->assertEquals($user4->id, $conversations[1]->messages[0]->useridfrom); // Delete the third user and retrieve the conversations. - // We should have 4, as $ic1, $ic2 drop off the list. - // Group conversations remain albeit with less members. + // We should have 6 still, as conversations with soft-deleted users are still returned. + // Group conversations are also present, albeit with less members. delete_user($user3); $conversations = \core_message\api::get_conversations($user1->id); - $this->assertCount(4, $conversations); + $this->assertCount(6, $conversations); $this->assertEquals($gc3->id, $conversations[0]->id); $this->assertcount(1, $conversations[0]->members); $this->assertEquals($gc2->id, $conversations[1]->id); $this->assertcount(1, $conversations[1]->members); - $this->assertEquals($gc5->id, $conversations[2]->id); - $this->assertEquals($gc4->id, $conversations[3]->id); + $this->assertEquals($ic2->id, $conversations[2]->id); + $this->assertEquals($ic1->id, $conversations[3]->id); + $this->assertEquals($gc5->id, $conversations[4]->id); + $this->assertEquals($gc4->id, $conversations[5]->id); } /** @@ -5673,193 +5676,252 @@ class core_message_api_testcase extends core_message_messagelib_testcase { } /** - * Data provider for test_count_conversations(). + * Data provider for test_get_conversation_counts(). */ - public function test_count_conversations_test_cases() { + public function test_get_conversation_counts_test_cases() { $typeindividual = \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL; $typegroup = \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP; - list($user1, $user2, $user3, $user4, $user5) = [0, 1, 2, 3, 4]; + list($user1, $user2, $user3, $user4, $user5, $user6, $user7) = [0, 1, 2, 3, 4, 5, 6]; $conversations = [ [ 'type' => $typeindividual, 'users' => [$user1, $user2], 'messages' => [$user1, $user2], - 'favourites' => [$user1] + 'favourites' => [$user1], + 'enabled' => null // Individual conversations cannot be disabled. ], [ 'type' => $typeindividual, 'users' => [$user1, $user3], 'messages' => [$user1, $user1], - 'favourites' => [] + 'favourites' => [], + 'enabled' => null // Individual conversations cannot be disabled. ], [ 'type' => $typegroup, 'users' => [$user1, $user2, $user3, $user4], 'messages' => [$user1, $user2, $user3, $user4], - 'favourites' => [] + 'favourites' => [], + 'enabled' => true + ], + [ + 'type' => $typegroup, + 'users' => [$user6, $user7], + 'messages' => [$user6, $user7], + 'favourites' => [$user6], + 'enabled' => false ], ]; return [ 'No conversations' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => null, - 'delete' => [], + 'deletemessagesuser' => null, + 'deletemessages' => [], 'arguments' => [$user5], - 'expected' => 0 - ], - 'No individual conversations' => [ - 'conversationConfigs' => $conversations, - 'deleteuser' => null, - 'delete' => [], - 'arguments' => [$user4, $typeindividual], - 'expected' => 0 + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0 + ]], + 'deletedusers' => [] ], 'No individual conversations, 1 group conversation' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => null, - 'delete' => [], + 'deletemessagesuser' => null, + 'deletemessages' => [], 'arguments' => [$user4], - 'expected' => 1 + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] ], - '1 - Multiple individual conversations, 1 group conversation' => [ + '2 individual conversations (one favourited), 1 group conversation' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => null, - 'delete' => [], - 'arguments' => [$user1, $typegroup], - 'expected' => 1 + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] ], - '2 - Multiple individual conversations, 1 group conversation' => [ + '1 individual conversation, 1 group conversation' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => null, - 'delete' => [], - 'arguments' => [$user2, $typegroup], - 'expected' => 1 + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user2], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] ], - '3 - Multiple individual conversations, 1 group conversation' => [ + '1 group conversation only' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => null, - 'delete' => [], - 'arguments' => [$user3, $typegroup], - 'expected' => 1 + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user4], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] ], - '4 - Multiple individual conversations, 1 group conversation' => [ + 'All conversation types, delete a message from individual favourited, messages remaining' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => null, - 'delete' => [], - 'arguments' => [$user4, $typegroup], - 'expected' => 1 + 'deletemessagesuser' => $user1, + 'deletemessages' => [0], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] ], - 'Individual exclude favourites' => [ + 'All conversation types, delete a message from individual non-favourited, messages remaining' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => null, - 'delete' => [], - 'arguments' => [$user1, $typeindividual, true], - 'expected' => 1 + 'deletemessagesuser' => $user1, + 'deletemessages' => [2], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] ], - 'Individual include favourites' => [ + 'All conversation types, delete all messages from individual favourited, no messages remaining' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => null, - 'delete' => [], - 'arguments' => [$user1, $typeindividual, false], - 'expected' => 2 + 'deletemessagesuser' => $user1, + 'deletemessages' => [0, 1], + 'arguments' => [$user1], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] ], - 'All exclude favourites' => [ + 'All conversation types, delete all messages from individual non-favourited, no messages remaining' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => null, - 'delete' => [], - 'arguments' => [$user1, null, true], - 'expected' => 2 + 'deletemessagesuser' => $user1, + 'deletemessages' => [2, 3], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] ], - 'All include favourites' => [ + 'All conversation types, delete all messages from individual favourited, no messages remaining, different user' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => null, - 'delete' => [], - 'arguments' => [$user1, null, false], - 'expected' => 3 + 'deletemessagesuser' => $user1, + 'deletemessages' => [0, 1], + 'arguments' => [$user2], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] ], - 'Delete single message individual' => [ + 'All conversation types, delete all messages from individual non-favourited, no messages remaining, different user' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => $user1, - 'delete' => [1], - 'arguments' => [$user1, $typeindividual], - 'expected' => 2 + 'deletemessagesuser' => $user1, + 'deletemessages' => [2, 3], + 'arguments' => [$user3], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] ], - 'Delete single message all' => [ + 'All conversation types, delete some messages from group non-favourited, messages remaining,' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => $user1, - 'delete' => [1], - 'arguments' => [$user1, null], - 'expected' => 3 + 'deletemessagesuser' => $user1, + 'deletemessages' => [4, 5], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] ], - 'Delete all message individual conversation include favourites' => [ + 'All conversation types, delete all messages from group non-favourited, no messages remaining,' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => $user1, - 'delete' => [2, 3], - 'arguments' => [$user1, $typeindividual, false], - 'expected' => 1 + 'deletemessagesuser' => $user1, + 'deletemessages' => [4, 5, 6, 7], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] ], - 'Delete all message individual conversation exclude favourites' => [ + 'All conversation types, another user soft deleted' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => $user1, - 'delete' => [2, 3], - 'arguments' => [$user1, $typeindividual, true], - 'expected' => 0 + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [$user2] ], - 'Delete all message individual conversation include favourites diff user' => [ + 'All conversation types, all group users soft deleted' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => $user1, - 'delete' => [2, 3], - 'arguments' => [$user2, $typeindividual, false], - 'expected' => 1 + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [$user2, $user3, $user4] ], - 'Delete all message individual conversation exclude favourites diff user' => [ + 'Group conversation which is disabled, favourited' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => $user1, - 'delete' => [2, 3], - 'arguments' => [$user2, $typeindividual, true], - 'expected' => 1 + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user6], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0 + ]], + 'deletedusers' => [] ], - 'Delete all message group conversation include favourites' => [ + 'Group conversation which is disabled, non-favourited' => [ 'conversationConfigs' => $conversations, - 'deleteuser' => $user1, - 'delete' => [4, 5, 6, 7], - 'arguments' => [$user1, $typegroup, false], - 'expected' => 1 + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user7], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0 + ]], + 'deletedusers' => [] ], - 'Delete all message group conversation include favourites' => [ - 'conversationConfigs' => $conversations, - 'deleteuser' => $user1, - 'delete' => [4, 5, 6, 7], - 'arguments' => [$user1, null, false], - 'expected' => 3 - ], - 'Delete all message group conversation exclude favourites' => [ - 'conversationConfigs' => $conversations, - 'deleteuser' => $user1, - 'delete' => [4, 5, 6, 7], - 'arguments' => [$user1, null, true], - 'expected' => 2 - ] ]; } /** - * Test the count_conversations() function. + * Test the get_conversation_counts() function. * - * @dataProvider test_count_conversations_test_cases() + * @dataProvider test_get_conversation_counts_test_cases() * @param array $conversationconfigs Conversations to create - * @param int $deleteuser The user who is deleting the messages - * @param array $delete The list of messages to delete (by index) + * @param int $deletemessagesuser The user who is deleting the messages + * @param array $deletemessages The list of messages to delete (by index) * @param array $arguments Arguments for the count conversations function - * @param int $expected The expected result + * @param array $expected The expected result + * @param array $deletedusers the array of users to soft delete. */ - public function test_count_conversations( + public function test_get_conversation_counts( $conversationconfigs, - $deleteuser, - $delete, + $deletemessagesuser, + $deletemessages, $arguments, - $expected + $expected, + $deletedusers ) { $generator = $this->getDataGenerator(); $users = [ @@ -5867,12 +5929,13 @@ class core_message_api_testcase extends core_message_messagelib_testcase { $generator->create_user(), $generator->create_user(), $generator->create_user(), + $generator->create_user(), + $generator->create_user(), $generator->create_user() ]; - $user = $users[$arguments[0]]; - $deleteuser = !is_null($deleteuser) ? $users[$deleteuser] : null; - $arguments[0] = $user; + $deleteuser = !is_null($deletemessagesuser) ? $users[$deletemessagesuser] : null; + $arguments[0] = $users[$arguments[0]]->id; $systemcontext = \context_system::instance(); $conversations = []; $messageids = []; @@ -5882,7 +5945,9 @@ class core_message_api_testcase extends core_message_messagelib_testcase { $config['type'], array_map(function($userindex) use ($users) { return $users[$userindex]->id; - }, $config['users']) + }, $config['users']), + null, + ($config['enabled'] ?? true) ); foreach ($config['messages'] as $userfromindex) { @@ -5900,11 +5965,21 @@ class core_message_api_testcase extends core_message_messagelib_testcase { $conversations[] = $conversation; } - foreach ($delete as $messageindex) { + foreach ($deletemessages as $messageindex) { \core_message\api::delete_message($deleteuser->id, $messageids[$messageindex]); } - $this->assertEquals($expected, \core_message\api::count_conversations(...$arguments)); + foreach ($deletedusers as $deleteduser) { + delete_user($users[$deleteduser]); + } + + $counts = \core_message\api::get_conversation_counts(...$arguments); + + $this->assertEquals($expected['favourites'], $counts['favourites']); + $this->assertEquals($expected['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL], + $counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL]); + $this->assertEquals($expected['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP], + $counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP]); } /** diff --git a/message/tests/externallib_test.php b/message/tests/externallib_test.php index 7e89cc661a8..8019307630d 100644 --- a/message/tests/externallib_test.php +++ b/message/tests/externallib_test.php @@ -5224,20 +5224,21 @@ class core_message_externallib_testcase extends externallib_advanced_testcase { $this->setUser($user1); // Delete the second user and retrieve the conversations. - // We should have 5, as $ic1 drops off the list. - // Group conversations remain albeit with less members. + // We should have 6 still, as conversations with soft-deleted users are still returned. + // Group conversations are also present, albeit with less members. delete_user($user2); $result = core_message_external::get_conversations($user1->id); $result = external_api::clean_returnvalue(core_message_external::get_conversations_returns(), $result); $conversations = $result['conversations']; - $this->assertCount(5, $conversations); + $this->assertCount(6, $conversations); $this->assertEquals($gc3->id, $conversations[0]['id']); $this->assertcount(1, $conversations[0]['members']); $this->assertEquals($gc2->id, $conversations[1]['id']); $this->assertcount(1, $conversations[1]['members']); $this->assertEquals($ic2->id, $conversations[2]['id']); - $this->assertEquals($gc5->id, $conversations[3]['id']); - $this->assertEquals($gc4->id, $conversations[4]['id']); + $this->assertEquals($ic1->id, $conversations[3]['id']); + $this->assertEquals($gc5->id, $conversations[4]['id']); + $this->assertEquals($gc4->id, $conversations[5]['id']); // Delete a user from a group conversation where that user had sent the most recent message. // This user will still be present in the members array, as will the message in the messages array. @@ -5245,7 +5246,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase { $result = core_message_external::get_conversations($user1->id); $result = external_api::clean_returnvalue(core_message_external::get_conversations_returns(), $result); $conversations = $result['conversations']; - $this->assertCount(5, $conversations); + $this->assertCount(6, $conversations); $this->assertEquals($gc2->id, $conversations[1]['id']); $this->assertcount(1, $conversations[1]['members']); $this->assertEquals($user4->id, $conversations[1]['members'][0]['id']); @@ -5253,19 +5254,21 @@ class core_message_externallib_testcase extends externallib_advanced_testcase { $this->assertEquals($user4->id, $conversations[1]['messages'][0]['useridfrom']); // Delete the third user and retrieve the conversations. - // We should have 4, as $ic1, $ic2 drop off the list. - // Group conversations remain albeit with less members. + // We should have 6 still, as conversations with soft-deleted users are still returned. + // Group conversations are also present, albeit with less members. delete_user($user3); $result = core_message_external::get_conversations($user1->id); $result = external_api::clean_returnvalue(core_message_external::get_conversations_returns(), $result); $conversations = $result['conversations']; - $this->assertCount(4, $conversations); + $this->assertCount(6, $conversations); $this->assertEquals($gc3->id, $conversations[0]['id']); $this->assertcount(1, $conversations[0]['members']); $this->assertEquals($gc2->id, $conversations[1]['id']); $this->assertcount(1, $conversations[1]['members']); - $this->assertEquals($gc5->id, $conversations[2]['id']); - $this->assertEquals($gc4->id, $conversations[3]['id']); + $this->assertEquals($ic2->id, $conversations[2]['id']); + $this->assertEquals($ic1->id, $conversations[3]['id']); + $this->assertEquals($gc5->id, $conversations[4]['id']); + $this->assertEquals($gc4->id, $conversations[5]['id']); } /** @@ -6020,4 +6023,314 @@ class core_message_externallib_testcase extends externallib_advanced_testcase { $this->assertEquals($message2id, $conv['messages'][0]->id); $this->assertEquals($user2->id, $conv['messages'][0]->useridfrom); } + + /** + * Data provider for test_get_conversation_counts(). + */ + public function test_get_conversation_counts_test_cases() { + $typeindividual = \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL; + $typegroup = \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP; + list($user1, $user2, $user3, $user4, $user5, $user6, $user7) = [0, 1, 2, 3, 4, 5, 6]; + $conversations = [ + [ + 'type' => $typeindividual, + 'users' => [$user1, $user2], + 'messages' => [$user1, $user2], + 'favourites' => [$user1], + 'enabled' => null // Individual conversations cannot be disabled. + ], + [ + 'type' => $typeindividual, + 'users' => [$user1, $user3], + 'messages' => [$user1, $user1], + 'favourites' => [], + 'enabled' => null // Individual conversations cannot be disabled. + ], + [ + 'type' => $typegroup, + 'users' => [$user1, $user2, $user3, $user4], + 'messages' => [$user1, $user2, $user3, $user4], + 'favourites' => [], + 'enabled' => true + ], + [ + 'type' => $typegroup, + 'users' => [$user6, $user7], + 'messages' => [$user6, $user7], + 'favourites' => [$user6], + 'enabled' => false + ], + ]; + + return [ + 'No conversations' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user5], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0 + ]], + 'deletedusers' => [] + ], + 'No individual conversations, 1 group conversation' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user4], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] + ], + '2 individual conversations (one favourited), 1 group conversation' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] + ], + '1 individual conversation, 1 group conversation' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user2], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] + ], + '1 group conversation only' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user4], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] + ], + 'All conversation types, delete a message from individual favourited, messages remaining' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => $user1, + 'deletemessages' => [0], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] + ], + 'All conversation types, delete a message from individual non-favourited, messages remaining' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => $user1, + 'deletemessages' => [2], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] + ], + 'All conversation types, delete all messages from individual favourited, no messages remaining' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => $user1, + 'deletemessages' => [0, 1], + 'arguments' => [$user1], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] + ], + 'All conversation types, delete all messages from individual non-favourited, no messages remaining' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => $user1, + 'deletemessages' => [2, 3], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] + ], + 'All conversation types, delete all messages from individual favourited, no messages remaining, different user' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => $user1, + 'deletemessages' => [0, 1], + 'arguments' => [$user2], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] + ], + 'All conversation types, delete all messages from individual non-favourited, no messages remaining, different user' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => $user1, + 'deletemessages' => [2, 3], + 'arguments' => [$user3], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] + ], + 'All conversation types, delete some messages from group non-favourited, messages remaining,' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => $user1, + 'deletemessages' => [4, 5], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] + ], + 'All conversation types, delete all messages from group non-favourited, no messages remaining,' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => $user1, + 'deletemessages' => [4, 5, 6, 7], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [] + ], + 'All conversation types, another user soft deleted' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [$user2] + ], + 'All conversation types, all group users soft deleted' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user1], + 'expected' => ['favourites' => 1, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1 + ]], + 'deletedusers' => [$user2, $user3, $user4] + ], + 'Group conversation which is disabled, favourited' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user6], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0 + ]], + 'deletedusers' => [] + ], + 'Group conversation which is disabled, non-favourited' => [ + 'conversationConfigs' => $conversations, + 'deletemessagesuser' => null, + 'deletemessages' => [], + 'arguments' => [$user7], + 'expected' => ['favourites' => 0, 'types' => [ + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0 + ]], + 'deletedusers' => [] + ], + ]; + } + + /** + * Test the get_conversation_counts() function. + * + * @dataProvider test_get_conversation_counts_test_cases() + * @param array $conversationconfigs Conversations to create + * @param int $deletemessagesuser The user who is deleting the messages + * @param array $deletemessages The list of messages to delete (by index) + * @param array $arguments Arguments for the count conversations function + * @param array $expected The expected result + * @param array $deletedusers the array of users to soft delete. + */ + public function test_get_conversation_counts( + $conversationconfigs, + $deletemessagesuser, + $deletemessages, + $arguments, + $expected, + $deletedusers + ) { + $this->resetAfterTest(); + $generator = $this->getDataGenerator(); + $users = [ + $generator->create_user(), + $generator->create_user(), + $generator->create_user(), + $generator->create_user(), + $generator->create_user(), + $generator->create_user(), + $generator->create_user() + ]; + + $deleteuser = !is_null($deletemessagesuser) ? $users[$deletemessagesuser] : null; + $this->setUser($users[$arguments[0]]); + $arguments[0] = $users[$arguments[0]]->id; + $systemcontext = \context_system::instance(); + $conversations = []; + $messageids = []; + + foreach ($conversationconfigs as $config) { + $conversation = \core_message\api::create_conversation( + $config['type'], + array_map(function($userindex) use ($users) { + return $users[$userindex]->id; + }, $config['users']), + null, + ($config['enabled'] ?? true) + ); + + foreach ($config['messages'] as $userfromindex) { + $userfrom = $users[$userfromindex]; + $messageids[] = testhelper::send_fake_message_to_conversation($userfrom, $conversation->id); + } + + foreach ($config['favourites'] as $userfromindex) { + $userfrom = $users[$userfromindex]; + $usercontext = \context_user::instance($userfrom->id); + $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); + $ufservice->create_favourite('core_message', 'message_conversations', $conversation->id, $systemcontext); + } + + $conversations[] = $conversation; + } + + foreach ($deletemessages as $messageindex) { + \core_message\api::delete_message($deleteuser->id, $messageids[$messageindex]); + } + + foreach ($deletedusers as $deleteduser) { + delete_user($users[$deleteduser]); + } + + $counts = core_message_external::get_conversation_counts(...$arguments); + $counts = external_api::clean_returnvalue(core_message_external::get_conversation_counts_returns(), $counts); + + $this->assertEquals($expected['favourites'], $counts['favourites']); + $this->assertEquals($expected['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL], + $counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL]); + $this->assertEquals($expected['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP], + $counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP]); + } }