diff --git a/message/classes/api.php b/message/classes/api.php index d9b507edb40..992afe2e67b 100644 --- a/message/classes/api.php +++ b/message/classes/api.php @@ -24,6 +24,8 @@ namespace core_message; +use core_favourites\local\entity\favourite; + defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/lib/messagelib.php'); @@ -274,11 +276,31 @@ class api { * @param int $userid The user id * @param int $limitfrom * @param int $limitnum + * @param int $type the conversation type. + * @param bool $favouritesonly whether to retrieve only the favourite conversations for the user, or not. * @return array */ - public static function get_conversations($userid, $limitfrom = 0, $limitnum = 20) { + public static function get_conversations($userid, $limitfrom = 0, $limitnum = 20, int $type = null, + bool $favouritesonly = false) { global $DB; + $favouritesql = ""; + $favouriteparams = []; + if ($favouritesonly) { + // Ask the favourites subsystem for the user's favourite conversations. + $service = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($userid)); + $favourites = $service->find_favourites_by_type('core_message', 'message_conversations'); + if (empty($favourites)) { + return []; // No favourited conversations, so return none. + } + $favids = array_values(array_map(function ($fav) { + return $fav->itemid; + }, $favourites)); + list ($insql, $inparams) = $DB->get_in_or_equal($favids, SQL_PARAMS_NAMED, 'favouriteids'); + $favouritesql = " AND m.conversationid {$insql} "; + $favouriteparams = $inparams; + } + // Get the last message from each conversation that the user belongs to. $sql = "SELECT m.id, m.conversationid, m.useridfrom, mcm2.userid as useridto, m.smallmessage, m.timecreated FROM {messages} m @@ -305,10 +327,12 @@ class api { INNER JOIN {message_conversation_members} mcm2 ON mcm2.conversationid = m.conversationid WHERE mcm.userid = m.useridfrom - AND mcm.id != mcm2.id + AND mcm.id != mcm2.id $favouritesql ORDER BY m.timecreated DESC"; - $messageset = $DB->get_recordset_sql($sql, ['userid' => $userid, 'action' => self::MESSAGE_ACTION_DELETED, - 'userid2' => $userid], $limitfrom, $limitnum); + + $params = array_merge($favouriteparams, ['userid' => $userid, 'action' => self::MESSAGE_ACTION_DELETED, + 'userid2' => $userid]); + $messageset = $DB->get_recordset_sql($sql, $params, $limitfrom, $limitnum); $messages = []; foreach ($messageset as $message) { @@ -409,6 +433,34 @@ class api { return $arrconversations; } + /** + * Mark a conversation as a favourite for the given user. + * + * @param int $conversationid the id of the conversation to mark as a favourite. + * @param int $userid the id of the user to whom the favourite belongs. + * @return favourite the favourite object. + * @throws \moodle_exception if the user or conversation don't exist. + */ + public static function set_favourite_conversation(int $conversationid, int $userid) : favourite { + if (!self::is_user_in_conversation($userid, $conversationid)) { + throw new \moodle_exception("Conversation doesn't exist or user is not a member"); + } + $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($userid)); + return $ufservice->create_favourite('core_message', 'message_conversations', $conversationid, \context_system::instance()); + } + + /** + * Unset a conversation as a favourite for the given user. + * + * @param int $conversationid the id of the conversation to unset as a favourite. + * @param int $userid the id to whom the favourite belongs. + * @throws \moodle_exception if the favourite does not exist for the user. + */ + public static function unset_favourite_conversation(int $conversationid, int $userid) { + $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($userid)); + $ufservice->delete_favourite('core_message', 'message_conversations', $conversationid, \context_system::instance()); + } + /** * Returns the contacts to display in the contacts area. * diff --git a/message/tests/api_test.php b/message/tests/api_test.php index 8be0f3f6070..23ad644adf4 100644 --- a/message/tests/api_test.php +++ b/message/tests/api_test.php @@ -341,6 +341,304 @@ class core_message_api_testcase extends core_message_messagelib_testcase { $this->assertNull($message3->unreadcount); } + /** + * Test verifying that favourited conversations can be retrieved. + */ + public function test_get_favourite_conversations() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + $user3 = self::getDataGenerator()->create_user(); + $user4 = self::getDataGenerator()->create_user(); + + // The person doing the search. + $this->setUser($user1); + + // No conversations yet. + $this->assertEquals([], \core_message\api::get_conversations($user1->id)); + + // Create some conversations for user1. + $time = 1; + $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); + $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); + $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); + $messageid1 = $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 4); + + $this->send_fake_message($user1, $user3, 'Booyah', 0, $time + 5); + $this->send_fake_message($user3, $user1, 'Whaaat?', 0, $time + 6); + $this->send_fake_message($user1, $user3, 'Nothing.', 0, $time + 7); + $messageid2 = $this->send_fake_message($user3, $user1, 'Cool.', 0, $time + 8); + + $this->send_fake_message($user1, $user4, 'Hey mate, you see the new messaging UI in Moodle?', 0, $time + 9); + $this->send_fake_message($user4, $user1, 'Yah brah, it\'s pretty rad.', 0, $time + 10); + $messageid3 = $this->send_fake_message($user1, $user4, 'Dope.', 0, $time + 11); + + // Favourite the first 2 conversations for user1. + $convoids = []; + $convoids[] = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]); + $convoids[] = \core_message\api::get_conversation_between_users([$user1->id, $user3->id]); + $user1context = context_user::instance($user1->id); + $service = \core_favourites\service_factory::get_service_for_user_context($user1context); + foreach ($convoids as $convoid) { + $service->create_favourite('core_message', 'message_conversations', $convoid, $user1context); + } + + // We should have 3 conversations. + $this->assertCount(3, \core_message\api::get_conversations($user1->id)); + + // And 2 favourited conversations. + $conversations = \core_message\api::get_conversations($user1->id, 0, 20, null, true); + $this->assertCount(2, $conversations); + } + + /** + * Tests retrieving favourite conversations with a limit and offset to ensure pagination works correctly. + */ + public function test_get_favourite_conversations_limit_offset() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + $user3 = self::getDataGenerator()->create_user(); + $user4 = self::getDataGenerator()->create_user(); + + // The person doing the search. + $this->setUser($user1); + + // No conversations yet. + $this->assertEquals([], \core_message\api::get_conversations($user1->id)); + + // Create some conversations for user1. + $time = 1; + $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); + $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); + $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); + $messageid1 = $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 4); + + $this->send_fake_message($user1, $user3, 'Booyah', 0, $time + 5); + $this->send_fake_message($user3, $user1, 'Whaaat?', 0, $time + 6); + $this->send_fake_message($user1, $user3, 'Nothing.', 0, $time + 7); + $messageid2 = $this->send_fake_message($user3, $user1, 'Cool.', 0, $time + 8); + + $this->send_fake_message($user1, $user4, 'Hey mate, you see the new messaging UI in Moodle?', 0, $time + 9); + $this->send_fake_message($user4, $user1, 'Yah brah, it\'s pretty rad.', 0, $time + 10); + $messageid3 = $this->send_fake_message($user1, $user4, 'Dope.', 0, $time + 11); + + // Favourite the all conversations for user1. + $convoids = []; + $convoids[] = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]); + $convoids[] = \core_message\api::get_conversation_between_users([$user1->id, $user3->id]); + $convoids[] = \core_message\api::get_conversation_between_users([$user1->id, $user4->id]); + $user1context = context_user::instance($user1->id); + $service = \core_favourites\service_factory::get_service_for_user_context($user1context); + foreach ($convoids as $convoid) { + $service->create_favourite('core_message', 'message_conversations', $convoid, $user1context); + } + + // Get all records, using offset 0 and large limit. + $this->assertCount(2, \core_message\api::get_conversations($user1->id, 1, 10, null, true)); + + // Now, get 10 conversations starting at the second record. We should see 2 conversations. + $this->assertCount(2, \core_message\api::get_conversations($user1->id, 1, 10, null, true)); + + // Now, try to get favourited conversations using an invalid offset. + $this->assertCount(0, \core_message\api::get_conversations($user1->id, 4, 10, null, true)); + } + + /** + * Tests retrieving favourite conversations when a conversation contains a deleted user. + */ + public function test_get_favourite_conversations_with_deleted_user() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + $user3 = self::getDataGenerator()->create_user(); + + // Send some messages back and forth, have some different conversations with different users. + $time = 1; + $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); + $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); + $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); + $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 4); + + $this->send_fake_message($user1, $user3, 'Booyah', 0, $time + 5); + $this->send_fake_message($user3, $user1, 'Whaaat?', 0, $time + 6); + $this->send_fake_message($user1, $user3, 'Nothing.', 0, $time + 7); + $this->send_fake_message($user3, $user1, 'Cool.', 0, $time + 8); + + // Favourite the all conversations for user1. + $convoids = []; + $convoids[] = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]); + $convoids[] = \core_message\api::get_conversation_between_users([$user1->id, $user3->id]); + $user1context = context_user::instance($user1->id); + $service = \core_favourites\service_factory::get_service_for_user_context($user1context); + foreach ($convoids as $convoid) { + $service->create_favourite('core_message', 'message_conversations', $convoid, $user1context); + } + + // Delete the second user. + delete_user($user2); + + // 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); + + // Confirm the conversation is from the non-deleted user. + $conversation = reset($conversations); + $this->assertEquals($user3->id, $conversation->userid); + } + + /** + * Test confirming that conversations can be marked as favourites. + */ + public function test_set_favourite_conversation() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + $user3 = self::getDataGenerator()->create_user(); + + // Send some messages back and forth, have some different conversations with different users. + $time = 1; + $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); + $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); + $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); + $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 4); + + $this->send_fake_message($user1, $user3, 'Booyah', 0, $time + 5); + $this->send_fake_message($user3, $user1, 'Whaaat?', 0, $time + 6); + $this->send_fake_message($user1, $user3, 'Nothing.', 0, $time + 7); + $this->send_fake_message($user3, $user1, 'Cool.', 0, $time + 8); + + // Favourite the first conversation as user 1. + $conversationid1 = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]); + \core_message\api::set_favourite_conversation($conversationid1, $user1->id); + + // Verify we have a single favourite conversation a user 1. + $this->assertCount(1, \core_message\api::get_conversations($user1->id, 0, 20, null, true)); + + // Verify we have no favourites as user2, despite being a member in that conversation. + $this->assertCount(0, \core_message\api::get_conversations($user2->id, 0, 20, null, true)); + + // Try to favourite the same conversation again. + $this->expectException(\moodle_exception::class); + \core_message\api::set_favourite_conversation($conversationid1, $user1->id); + } + + /** + * Test verifying that trying to mark a non-existent conversation as a favourite, results in an exception. + */ + public function test_set_favourite_conversation_nonexistent_conversation() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + // Try to favourite a non-existent conversation. + $this->expectException(\moodle_exception::class); + \core_message\api::set_favourite_conversation(0, $user1->id); + } + + /** + * Test verifying that a conversation cannot be marked as favourite unless the user is a member of that conversation. + */ + public function test_set_favourite_conversation_non_member() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + $user3 = self::getDataGenerator()->create_user(); + + // Send some messages back and forth, have some different conversations with different users. + $time = 1; + $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); + $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); + $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); + $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 4); + + $this->send_fake_message($user1, $user3, 'Booyah', 0, $time + 5); + $this->send_fake_message($user3, $user1, 'Whaaat?', 0, $time + 6); + $this->send_fake_message($user1, $user3, 'Nothing.', 0, $time + 7); + $this->send_fake_message($user3, $user1, 'Cool.', 0, $time + 8); + + // Try to favourite the first conversation as user 3, who is not a member. + $conversationid1 = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]); + $this->expectException(\moodle_exception::class); + \core_message\api::set_favourite_conversation($conversationid1, $user3->id); + } + + /** + * Test confirming that those conversations marked as favourites can be unfavourited. + */ + public function test_unset_favourite_conversation() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + $user3 = self::getDataGenerator()->create_user(); + + // Send some messages back and forth, have some different conversations with different users. + $time = 1; + $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); + $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); + $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); + $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 4); + + $this->send_fake_message($user1, $user3, 'Booyah', 0, $time + 5); + $this->send_fake_message($user3, $user1, 'Whaaat?', 0, $time + 6); + $this->send_fake_message($user1, $user3, 'Nothing.', 0, $time + 7); + $this->send_fake_message($user3, $user1, 'Cool.', 0, $time + 8); + + // Favourite the first conversation as user 1 and the second as user 3. + $conversationid1 = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]); + $conversationid2 = \core_message\api::get_conversation_between_users([$user1->id, $user3->id]); + \core_message\api::set_favourite_conversation($conversationid1, $user1->id); + \core_message\api::set_favourite_conversation($conversationid2, $user3->id); + + // Verify we have a single favourite conversation for both user 1 and user 3. + $this->assertCount(1, \core_message\api::get_conversations($user1->id, 0, 20, null, true)); + $this->assertCount(1, \core_message\api::get_conversations($user3->id, 0, 20, null, true)); + + // Now unfavourite the conversation as user 1. + \core_message\api::unset_favourite_conversation($conversationid1, $user1->id); + + // Verify we have a single favourite conversation user 3 only, and none for user1. + $this->assertCount(1, \core_message\api::get_conversations($user3->id, 0, 20, null, true)); + $this->assertCount(0, \core_message\api::get_conversations($user1->id, 0, 20, null, true)); + + // Try to favourite the same conversation again as user 1. + $this->expectException(\moodle_exception::class); + \core_message\api::unset_favourite_conversation($conversationid1, $user1->id); + } + + /** + * Test verifying that a valid conversation cannot be unset as a favourite if it's not marked as a favourite. + */ + public function test_unset_favourite_conversation_not_favourite() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + + // Send some messages back and forth, have some different conversations with different users. + $time = 1; + $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); + $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); + $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); + $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 4); + + // Now try to unfavourite the conversation as user 1. + $conversationid1 = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]); + $this->expectException(\moodle_exception::class); + \core_message\api::unset_favourite_conversation($conversationid1, $user1->id); + } + + /** + * Test verifying that a non-existent conversation cannot be unset as a favourite. + */ + public function test_unset_favourite_conversation_non_existent_conversation() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + + // Now try to unfavourite the conversation as user 1. + $this->expectException(\moodle_exception::class); + \core_message\api::unset_favourite_conversation(0, $user1->id); + } + /** * Tests retrieving conversations. */