$string['defaults'] = 'Defaults';
$string['deleteallconfirm'] = "Are you sure you would like to delete this entire conversation? This will not delete it for other conversation participants.";
$string['deleteallmessages'] = "Delete all messages";
+$string['deleteallselfconfirm'] = "Are you sure you would like to delete this entire personal conversation?";
$string['deleteconversation'] = "Delete conversation";
$string['deleteselectedmessages'] = 'Delete selected messages';
$string['deleteselectedmessagesconfirm'] = 'Are you sure you would like to delete the selected messages? This will not delete them for other conversation participants.';
+$string['deleteselectedmessagesconfirmselfconversation'] = 'Are you sure you would like to delete the selected personal messages?';
$string['disableall'] = 'Disable notifications';
$string['disabled'] = 'Messaging is disabled on this site';
$string['disallowed'] = 'Disallowed';
$string['seeall'] = 'See all';
$string['selectmessagestodelete'] = 'Select messages to delete';
$string['selectnotificationtoview'] = 'Select from the list of notifications on the side to view more details';
+$string['selfconversation'] = 'Personal space';
+$string['selfconversationdefaultmessage'] = 'Save draft messages, links, notes etc. to access later.';
$string['send'] = 'Send';
$string['sender'] = '{$a}:';
$string['sendingvia'] = 'Sending "{$a->provider}" via "{$a->processor}"';
// Get conversation type and name. We'll use this to determine which message subject to generate, depending on type.
$conv = $DB->get_record('message_conversations', ['id' => $eventdata->convid], 'id, type, name');
+ // For now Self conversations are not processed because users are aware of the messages sent by themselves, so we
+ // can return early.
+ if ($conv->type == \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF) {
+ return $savemessage->id;
+ }
+
// We treat individual conversations the same as any direct message with 'userfrom' and 'userto' specified.
// We know the other user, so set the 'userto' field so that the event code will get access to this field.
// If this was a legacy caller (eventdata->userto is set), then use that instead, as we want to use the fields specified
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
'ajax' => true
),
+ 'core_message_get_self_conversation' => array(
+ 'classname' => 'core_message_external',
+ 'methodname' => 'get_self_conversation',
+ 'classpath' => 'message/externallib.php',
+ 'description' => 'Retrieve a self-conversation for a user',
+ 'type' => 'read',
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
+ 'ajax' => true
+ ),
'core_message_get_messages' => array(
'classname' => 'core_message_external',
'methodname' => 'get_messages',
upgrade_main_savepoint(true, 2019041000.02);
}
+ if ($oldversion < 2019041300.01) {
+ // STEP 1. For the existing and migrated self-conversations, set the type to the new MESSAGE_CONVERSATION_TYPE_SELF, update
+ // the convhash and star them.
+ $sql = "SELECT mcm.conversationid, mcm.userid, MAX(mcm.id) as maxid
+ FROM {message_conversation_members} mcm
+ GROUP BY mcm.conversationid, mcm.userid
+ HAVING COUNT(*) > 1";
+ $selfconversationsrs = $DB->get_recordset_sql($sql);
+ $maxids = [];
+ foreach ($selfconversationsrs as $selfconversation) {
+ $DB->update_record('message_conversations',
+ ['id' => $selfconversation->conversationid,
+ 'type' => \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ 'convhash' => \core_message\helper::get_conversation_hash([$selfconversation->userid])
+ ]
+ );
+
+ // Star the existing self-conversation.
+ $favouriterecord = new \stdClass();
+ $favouriterecord->component = 'core_message';
+ $favouriterecord->itemtype = 'message_conversations';
+ $favouriterecord->itemid = $selfconversation->conversationid;
+ $userctx = \context_user::instance($selfconversation->userid);
+ $favouriterecord->contextid = $userctx->id;
+ $favouriterecord->userid = $selfconversation->userid;
+ $favouriterecord->timecreated = time();
+ $favouriterecord->timemodified = $favouriterecord->timecreated;
+
+ $DB->insert_record('favourite', $favouriterecord);
+
+ // Set the self-conversation member with maxid to remove it later.
+ $maxids[] = $selfconversation->maxid;
+ }
+ $selfconversationsrs->close();
+
+ // Remove the repeated member with the higher id for all the existing self-conversations.
+ if (!empty($maxids)) {
+ list($insql, $inparams) = $DB->get_in_or_equal($maxids);
+ $DB->delete_records_select('message_conversation_members', "id $insql", $inparams);
+ }
+
+ // STEP 2. Migrate existing self-conversation relying on old message tables, setting the type to the new
+ // MESSAGE_CONVERSATION_TYPE_SELF and the convhash to the proper one. Star them also.
+
+ // On the messaging legacy tables, self-conversations are only present in the 'message_read' table, so we don't need to
+ // check the content in the 'message' table.
+ $select = 'useridfrom = useridto AND notification = 0';
+ $legacyselfmessagesrs = $DB->get_recordset_select('message_read', $select);
+ foreach ($legacyselfmessagesrs as $message) {
+ // Get the self-conversation or create and star it if doesn't exist.
+ $conditions = [
+ 'type' => \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ 'convhash' => \core_message\helper::get_conversation_hash([$message->useridfrom])
+ ];
+ $selfconversation = $DB->get_record('message_conversations', $conditions);
+ if (empty($selfconversation)) {
+ // Create the self-conversation.
+ $selfconversation = new \stdClass();
+ $selfconversation->type = \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF;
+ $selfconversation->convhash = \core_message\helper::get_conversation_hash([$message->useridfrom]);
+ $selfconversation->enabled = 1;
+ $selfconversation->timecreated = time();
+ $selfconversation->timemodified = $selfconversation->timecreated;
+
+ $selfconversation->id = $DB->insert_record('message_conversations', $selfconversation);
+
+ // Add user to this self-conversation.
+ $member = new \stdClass();
+ $member->conversationid = $selfconversation->id;
+ $member->userid = $message->useridfrom;
+ $member->timecreated = time();
+
+ $member->id = $DB->insert_record('message_conversation_members', $member);
+
+ // Star the self-conversation.
+ $favouriterecord = new \stdClass();
+ $favouriterecord->component = 'core_message';
+ $favouriterecord->itemtype = 'message_conversations';
+ $favouriterecord->itemid = $selfconversation->id;
+ $userctx = \context_user::instance($message->useridfrom);
+ $favouriterecord->contextid = $userctx->id;
+ $favouriterecord->userid = $message->useridfrom;
+ $favouriterecord->timecreated = time();
+ $favouriterecord->timemodified = $favouriterecord->timecreated;
+
+ $DB->insert_record('favourite', $favouriterecord);
+ }
+
+ // Create the object we will be inserting into the database.
+ $tabledata = new \stdClass();
+ $tabledata->useridfrom = $message->useridfrom;
+ $tabledata->conversationid = $selfconversation->id;
+ $tabledata->subject = $message->subject;
+ $tabledata->fullmessage = $message->fullmessage;
+ $tabledata->fullmessageformat = $message->fullmessageformat ?? FORMAT_MOODLE;
+ $tabledata->fullmessagehtml = $message->fullmessagehtml;
+ $tabledata->smallmessage = $message->smallmessage;
+ $tabledata->timecreated = $message->timecreated;
+
+ $messageid = $DB->insert_record('messages', $tabledata);
+
+ // Check if we need to mark this message as deleted (self-conversations add this information on the
+ // timeuserfromdeleted field.
+ if ($message->timeuserfromdeleted) {
+ $mua = new \stdClass();
+ $mua->userid = $message->useridfrom;
+ $mua->messageid = $messageid;
+ $mua->action = \core_message\api::MESSAGE_ACTION_DELETED;
+ $mua->timecreated = $message->timeuserfromdeleted;
+
+ $DB->insert_record('message_user_actions', $mua);
+ }
+
+ // Mark this message as read.
+ $mua = new \stdClass();
+ $mua->userid = $message->useridto;
+ $mua->messageid = $messageid;
+ $mua->action = \core_message\api::MESSAGE_ACTION_READ;
+ $mua->timecreated = $message->timeread;
+
+ $DB->insert_record('message_user_actions', $mua);
+ }
+ $legacyselfmessagesrs->close();
+
+ // We can now delete the records from legacy table because the self-conversations have been migrated from the legacy tables.
+ $DB->delete_records_select('message_read', $select);
+
+ // STEP 3. For existing users without self-conversations, create and star it.
+
+ // Get all the users without a self-conversation.
+ $sql = "SELECT u.id
+ FROM {user} u
+ WHERE u.id NOT IN (SELECT mcm.userid
+ FROM {message_conversation_members} mcm
+ INNER JOIN mdl_message_conversations mc
+ ON mc.id = mcm.conversationid AND mc.type = ?
+ )";
+ $useridsrs = $DB->get_recordset_sql($sql, [\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF]);
+ // Create the self-conversation for all these users.
+ foreach ($useridsrs as $user) {
+ $conditions = [
+ 'type' => \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ 'convhash' => \core_message\helper::get_conversation_hash([$user->id])
+ ];
+ $selfconversation = $DB->get_record('message_conversations', $conditions);
+ if (empty($selfconversation)) {
+ // Create the self-conversation.
+ $selfconversation = new \stdClass();
+ $selfconversation->type = \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF;
+ $selfconversation->convhash = \core_message\helper::get_conversation_hash([$user->id]);
+ $selfconversation->enabled = 1;
+ $selfconversation->timecreated = time();
+ $selfconversation->timemodified = $selfconversation->timecreated;
+
+ $selfconversation->id = $DB->insert_record('message_conversations', $selfconversation);
+
+ // Add user to this self-conversation.
+ $member = new \stdClass();
+ $member->conversationid = $selfconversation->id;
+ $member->userid = $user->id;
+ $member->timecreated = time();
+
+ $member->id = $DB->insert_record('message_conversation_members', $member);
+
+ // Star the self-conversation.
+ $favouriterecord = new \stdClass();
+ $favouriterecord->component = 'core_message';
+ $favouriterecord->itemtype = 'message_conversations';
+ $favouriterecord->itemid = $selfconversation->id;
+ $userctx = \context_user::instance($user->id);
+ $favouriterecord->contextid = $userctx->id;
+ $favouriterecord->userid = $user->id;
+ $favouriterecord->timecreated = time();
+ $favouriterecord->timemodified = $favouriterecord->timecreated;
+
+ $DB->insert_record('favourite', $favouriterecord);
+ }
+ }
+ $useridsrs->close();
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2019041300.01);
+ }
+
return true;
}
return false;
}
- if (!$conversationid = \core_message\api::get_conversation_between_users([$eventdata->userfrom->id,
- $eventdata->userto->id])) {
- $conversation = \core_message\api::create_conversation(
- \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
- [
- $eventdata->userfrom->id,
- $eventdata->userto->id
- ]
- );
+ if ($eventdata->userfrom->id == $eventdata->userto->id) {
+ // It's a self conversation.
+ $conversation = \core_message\api::get_self_conversation($eventdata->userfrom->id);
+ if (empty($conversation)) {
+ $conversation = \core_message\api::create_conversation(
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ [$eventdata->userfrom->id]
+ );
+ }
+ } else {
+ if (!$conversationid = \core_message\api::get_conversation_between_users([$eventdata->userfrom->id,
+ $eventdata->userto->id])) {
+ // It's a private conversation between users.
+ $conversation = \core_message\api::create_conversation(
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
+ [
+ $eventdata->userfrom->id,
+ $eventdata->userto->id
+ ]
+ );
+ }
}
// We either have found a conversation, or created one.
- $conversationid = $conversationid ? $conversationid : $conversation->id;
+ $conversationid = !empty($conversationid) ? $conversationid : $conversation->id;
$eventdata->convid = $conversationid;
}
$sink->clear();
}
+ /**
+ * Tests calling message_send() with $eventdata representing a message to a self-conversation.
+ *
+ * This test will verify:
+ * - that the 'messages' record is created.
+ * - that the processors is not called (for now self-conversations are not processed).
+ * - the a single event will be generated - 'message_sent'
+ *
+ * Note: We won't redirect/capture messages in this test because doing so causes message_send() to return early, before
+ * processors and events code is called. We need to test this code here, as we generally redirect messages elsewhere and we
+ * need to be sure this is covered.
+ */
+ public function test_message_send_to_self_conversation() {
+ global $DB;
+ $this->preventResetByRollback();
+ $this->resetAfterTest();
+
+ // Create some users and a conversation between them.
+ $user1 = $this->getDataGenerator()->create_user(array('maildisplay' => 1));
+ set_config('allowedemaildomains', 'example.com');
+ $conversation = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ [$user1->id]);
+
+ // Generate the message.
+ $message = new \core\message\message();
+ $message->courseid = 1;
+ $message->component = 'moodle';
+ $message->name = 'instantmessage';
+ $message->userfrom = $user1;
+ $message->convid = $conversation->id;
+ $message->subject = 'message subject 1';
+ $message->fullmessage = 'message body';
+ $message->fullmessageformat = FORMAT_MARKDOWN;
+ $message->fullmessagehtml = '<p>message body</p>';
+ $message->smallmessage = 'small message';
+ $message->notification = '0';
+
+ // Content specific to the email processor.
+ $content = array('*' => array('header' => ' test ', 'footer' => ' test '));
+ $message->set_additional_content('email', $content);
+
+ // Ensure we're going to hit the email processor for this user.
+ $DB->set_field_select('message_processors', 'enabled', 0, "name <> 'email'");
+ set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user1);
+
+ // Now, send a message and verify the message processors are empty (self-conversations are not processed for now).
+ $sink = $this->redirectEmails();
+ $messageid = message_send($message);
+ $emails = $sink->get_messages();
+ $this->assertCount(0, $emails);
+ $sink->clear();
+ }
+
/**
* Tests calling message_send() with $eventdata representing a message to an group conversation.
*
in this category. To work with list of courses use API methods in core_course_category and also 'course' form element.
* It is possible to pass additional conditions to get_courses_search();
core_course_category::search_courses() now allows to search only among courses with completion enabled.
+* A new conversation type has been created for self-conversations. During the upgrading process:
+ - Firstly, the existing self-conversations will be starred and migrated to the new type, removing the duplicated members in the
+ message_conversation_members table.
+ - Secondly, the legacy self conversations will be migrated from the legacy 'message_read' table. They will be created using the
+ new conversation type and will be favourited.
+ - Finally, the self-conversations for all remaining users without them will be created and starred.
+Besides, from now, a self-conversation will be created and starred by default to all the new users (even when $CFG->messaging
+is disabled).
=== 3.6 ===
* @return {Number} Userid.
*/
var getOtherUserId = function() {
- if (!viewState || viewState.type != CONVERSATION_TYPES.PRIVATE) {
+ if (!viewState || (viewState.type != CONVERSATION_TYPES.PRIVATE && viewState.type != CONVERSATION_TYPES.SELF)) {
return null;
}
var loggedInUserId = viewState.loggedInUserId;
+ if (viewState.type == CONVERSATION_TYPES.SELF) {
+ // It's a self-conversation, so the other user is the one logged in.
+ return loggedInUserId;
+ }
+
var otherUserIds = Object.keys(viewState.members).filter(function(userId) {
return loggedInUserId != userId;
});
if (!carry) {
var state = stateCache[id].state;
- if (state.type == CONVERSATION_TYPES.PRIVATE) {
+ if (state.type == CONVERSATION_TYPES.PRIVATE || state.type == CONVERSATION_TYPES.SELF) {
if (userId in state.members) {
// We've found a cached conversation for this user!
carry = state.id;
newState = StateManager.setLoadingMembers(newState, false);
newState = StateManager.setLoadingMessages(newState, false);
newState = StateManager.setName(newState, profile.fullname);
- newState = StateManager.setType(newState, 1);
+ newState = StateManager.setType(newState, CONVERSATION_TYPES.PRIVATE);
newState = StateManager.setImageUrl(newState, profile.profileimageurl);
newState = StateManager.setTotalMemberCount(newState, 2);
return render(newState)
});
};
+ /**
+ * Load up an empty self-conversation for the logged in user.
+ * Sets all of the conversation details based on the current user.
+ *
+ * A conversation isn't created until the user sends the first message.
+ *
+ * @param {Object} loggedInUserProfile The logged in user profile.
+ * @return {Object} Profile returned from repository.
+ */
+ var loadEmptySelfConversation = function(loggedInUserProfile) {
+ var loggedInUserId = loggedInUserProfile.id;
+ var newState = StateManager.setLoadingMembers(viewState, true);
+ newState = StateManager.setLoadingMessages(newState, true);
+ return render(newState)
+ .then(function() {
+ return Repository.getMemberInfo(loggedInUserId, [loggedInUserId], true, true);
+ })
+ .then(function(profiles) {
+ if (profiles.length) {
+ return profiles[0];
+ } else {
+ throw new Error('Unable to load other user profile');
+ }
+ })
+ .then(function(profile) {
+ var newState = StateManager.addMembers(viewState, [profile, loggedInUserProfile]);
+ newState = StateManager.setLoadingMembers(newState, false);
+ newState = StateManager.setLoadingMessages(newState, false);
+ newState = StateManager.setName(newState, profile.fullname);
+ newState = StateManager.setType(newState, CONVERSATION_TYPES.SELF);
+ newState = StateManager.setImageUrl(newState, profile.profileimageurl);
+ newState = StateManager.setTotalMemberCount(newState, 1);
+ return render(newState)
+ .then(function() {
+ return profile;
+ });
+ })
+ .catch(function(error) {
+ var newState = StateManager.setLoadingMembers(viewState, false);
+ render(newState);
+ Notification.exception(error);
+ });
+ };
+
/**
* Create a new state from a conversation object.
*
* @return {Object} new state.
*/
var updateStateFromConversation = function(conversation, loggedInUserId) {
- var otherUsers = conversation.members.filter(function(member) {
- return member.id != loggedInUserId;
- });
- var otherUser = otherUsers.length ? otherUsers[0] : null;
+ var otherUser = null;
+ if (conversation.type == CONVERSATION_TYPES.PRIVATE) {
+ // For private conversations, remove current logged in user from the members list to get the other user.
+ var otherUsers = conversation.members.filter(function(member) {
+ return member.id != loggedInUserId;
+ });
+ otherUser = otherUsers.length ? otherUsers[0] : null;
+ } else if (conversation.type == CONVERSATION_TYPES.SELF) {
+ // Self-conversations have only one member.
+ otherUser = conversation.members[0];
+ }
+
var name = conversation.name;
var imageUrl = conversation.imageurl;
-
- if (conversation.type == CONVERSATION_TYPES.PRIVATE) {
+ if (conversation.type == CONVERSATION_TYPES.PRIVATE || conversation.type == CONVERSATION_TYPES.SELF) {
name = name || otherUser ? otherUser.fullname : '';
imageUrl = imageUrl || otherUser ? otherUser.profileimageurl : '';
}
newState = StateManager.setPendingDeleteConversation(newState, false);
newState = StateManager.setLoadingConfirmAction(newState, false);
PubSub.publish(MessageDrawerEvents.CONVERSATION_DELETED, newState.id);
+
return render(newState);
});
};
var newConversationId = null;
return render(newState)
.then(function() {
- if (!conversationId && viewState.type == CONVERSATION_TYPES.PRIVATE) {
+ if (!conversationId &&
+ (viewState.type == CONVERSATION_TYPES.PRIVATE || viewState.type == CONVERSATION_TYPES.SELF)) {
// If it's a new private conversation then we need to use the old
// web service function to create the conversation.
var otherUserId = getOtherUserId();
};
/**
- * Load a new empty private conversation between two users.
+ * Load a new empty private conversation between two users or self-conversation.
*
* @param {Object} body Conversation body container element.
* @param {Object} loggedInUserProfile The logged in user's profile.
var resetNoConversation = function(body, loggedInUserProfile, otherUserId) {
// Always reset the state back to the initial state so that the
// state manager and patcher can work correctly.
- return resetState(body, null, loggedInUserProfile)
- .then(function() {
- return Repository.getConversationBetweenUsers(
- loggedInUserProfile.id,
- otherUserId,
- true,
- true,
- 0,
- 0,
- LOAD_MESSAGE_LIMIT,
- 0,
- NEWEST_FIRST
- )
- .then(function(conversation) {
- // Looks like we have a conversation after all! Let's use that.
- return resetByConversation(body, conversation, loggedInUserProfile);
- })
- .catch(function() {
- // Can't find a conversation. Oh well. Just load up a blank one.
- return loadEmptyPrivateConversation(loggedInUserProfile, otherUserId);
- });
- });
+ if (loggedInUserProfile.id != otherUserId) {
+ // This is a private conversation between two users.
+ return resetState(body, null, loggedInUserProfile)
+ .then(function() {
+ return Repository.getConversationBetweenUsers(
+ loggedInUserProfile.id,
+ otherUserId,
+ true,
+ true,
+ 0,
+ 0,
+ LOAD_MESSAGE_LIMIT,
+ 0,
+ NEWEST_FIRST
+ )
+ .then(function(conversation) {
+ // Looks like we have a conversation after all! Let's use that.
+ return resetByConversation(body, conversation, loggedInUserProfile);
+ })
+ .catch(function() {
+ // Can't find a conversation. Oh well. Just load up a blank one.
+ return loadEmptyPrivateConversation(loggedInUserProfile, otherUserId);
+ });
+ });
+ } else {
+ // This is a self-conversation.
+ return resetState(body, null, loggedInUserProfile)
+ .then(function() {
+ return Repository.getSelfConversation(
+ loggedInUserProfile.id,
+ LOAD_MESSAGE_LIMIT,
+ 0,
+ NEWEST_FIRST
+ )
+ .then(function(conversation) {
+ // Looks like we have a conversation after all! Let's use that.
+ return resetByConversation(body, conversation, loggedInUserProfile);
+ })
+ .catch(function() {
+ // Can't find a conversation. Oh well. Just load up a blank one.
+ return loadEmptySelfConversation(loggedInUserProfile);
+ });
+ });
+ }
};
/**
MORE_MESSAGES_LOADING_ICON_CONTAINER: '[data-region="more-messages-loading-icon-container"]',
MUTED_ICON_CONTAINER: '[data-region="muted-icon-container"]',
PLACEHOLDER_CONTAINER: '[data-region="placeholder-container"]',
+ SELF_CONVERSATION_MESSAGE_CONTAINER: '[data-region="self-conversation-message-container"]',
SEND_MESSAGE_BUTTON: '[data-action="send-message"]',
SEND_MESSAGE_ICON_CONTAINER: '[data-region="send-icon-container"]',
TEXT: '[data-region="text"]',
HEADER_PRIVATE: 'core_message/message_drawer_view_conversation_header_content_type_private',
HEADER_PRIVATE_NO_CONTROLS: 'core_message/message_drawer_view_conversation_header_content_type_private_no_controls',
HEADER_PUBLIC: 'core_message/message_drawer_view_conversation_header_content_type_public',
+ HEADER_SELF: 'core_message/message_drawer_view_conversation_header_content_type_self',
DAY: 'core_message/message_drawer_view_conversation_body_day',
MESSAGE: 'core_message/message_drawer_view_conversation_body_message',
MESSAGES: 'core_message/message_drawer_view_conversation_body_messages'
};
+ // Conversation types. They must have the same values defined in \core_message\api.
var CONVERSATION_TYPES = {
PRIVATE: 1,
- PUBLIC: 2
+ PUBLIC: 2,
+ SELF: 3
+ };
+
+ // Categories displayed in the message drawer. Some methods (such as filterCountsByType) are expecting their value
+ // will be the same as the defined in the CONVERSATION_TYPES, except for the favourite.
+ var CONVERSATION_CATEGORY_TYPES = {
+ PRIVATE: 1,
+ PUBLIC: 2,
+ FAVOURITE: null
};
return {
SELECTORS: SELECTORS,
TEMPLATES: TEMPLATES,
CONVERSATION_TYPES: CONVERSATION_TYPES,
+ CONVERSATION_CATEGORY_TYPES: CONVERSATION_CATEGORY_TYPES,
NEWEST_MESSAGES_FIRST: true,
LOAD_MESSAGE_LIMIT: 100,
INITIAL_NEW_MESSAGE_POLL_TIMEOUT: 1000
return null;
};
+ /**
+ * Build a patch for the header of this conversation. Check if this conversation
+ * is a group conversation.
+ *
+ * @param {Object} state The current state.
+ * @param {Object} newState The new state.
+ * @return {Object} patch
+ */
+ var buildHeaderPatchTypeSelf = function(state, newState) {
+ var shouldRenderHeader = (state.name === null);
+
+ if (shouldRenderHeader) {
+ return {
+ type: Constants.CONVERSATION_TYPES.SELF,
+ // Don't display the controls for the self-conversations.
+ showControls: false,
+ context: {
+ id: newState.id,
+ name: newState.name,
+ subname: newState.subname,
+ imageurl: newState.imageUrl,
+ isfavourite: newState.isFavourite,
+ // Don't show favouriting if we don't have a conversation.
+ showfavourite: newState.id !== null,
+ showonlinestatus: true,
+ }
+ };
+ }
+
+ return null;
+ };
/**
* Build a patch for the header of this conversation. Check if this conversation
*
* @param {Object} state The current state.
* @param {Object} newState The new state.
- * @return {Bool|Null}
+ * @return {int|Null} The conversation type of the messages to be deleted.
*/
var buildConfirmDeleteSelectedMessages = function(state, newState) {
if (newState.pendingDeleteMessageIds.length) {
- return true;
+ return newState.type;
} else if (state.pendingDeleteMessageIds.length) {
return false;
}
*
* @param {Object} state The current state.
* @param {Object} newState The new state.
- * @return {Bool|Null}
+ * @return {int|Null} The conversation type to be deleted.
*/
var buildConfirmDeleteConversation = function(state, newState) {
if (!state.pendingDeleteConversation && newState.pendingDeleteConversation) {
- return true;
+ return newState.type;
} else if (state.pendingDeleteConversation && !newState.pendingDeleteConversation) {
return false;
}
var oldOtherUser = getOtherUserFromState(state);
var newOtherUser = getOtherUserFromState(newState);
+ if (newState.type == Constants.CONVERSATION_TYPES.SELF) {
+ // Users always can send message themselves on self-conversations.
+ return null;
+ }
+
if (!oldOtherUser && !newOtherUser) {
return null;
} else if (oldOtherUser && !newOtherUser) {
return null;
};
+ /**
+ * We should show this message always, for all the self-conversations.
+ *
+ * The message should be hidden when it's not a self-conversation.
+ *
+ * @param {Object} state The current state.
+ * @param {Object} newState The new state.
+ * @return {bool}
+ */
+ var buildSelfConversationMessage = function(state, newState) {
+ return (newState.type == Constants.CONVERSATION_TYPES.SELF);
+ };
+
/**
* We should show the contact request sent message if the user just sent
* a contact request to the other user and there are no messages in the
header: buildHeaderPatchTypePublic,
footer: buildFooterPatchTypePublic,
};
+ // These build functions are only applicable to self-conversations.
+ config[Constants.CONVERSATION_TYPES.SELF] = {
+ header: buildHeaderPatchTypeSelf,
+ footer: buildFooterPatchTypePublic,
+ confirmDeleteConversation: buildConfirmDeleteConversation,
+ selfConversationMessage: buildSelfConversationMessage
+ };
var patchConfig = $.extend({}, config.all);
if (newState.type && newState.type in config) {
getMessagesContainer(body).addClass('hidden');
};
+ /**
+ * Get the self-conversation message container element.
+ *
+ * @param {Object} body Conversation body container element.
+ * @return {Object} The messages container element.
+ */
+ var getSelfConversationMessageContainer = function(body) {
+ return body.find(SELECTORS.SELF_CONVERSATION_MESSAGE_CONTAINER);
+ };
+
+ /**
+ * Hide the self-conversation message container element.
+ *
+ * @param {Object} body Conversation body container element.
+ * @return {Object} The messages container element.
+ */
+ var hideSelfConversationMessageContainer = function(body) {
+ return getSelfConversationMessageContainer(body).addClass('hidden');
+ };
+
/**
* Get the contact request sent container element.
*
if (data.type == CONVERSATION_TYPES.PRIVATE) {
template = data.showControls ? TEMPLATES.HEADER_PRIVATE : TEMPLATES.HEADER_PRIVATE_NO_CONTROLS;
+ } else if (data.type == CONVERSATION_TYPES.SELF) {
+ template = TEMPLATES.HEADER_SELF;
}
return Templates.render(template, data.context)
* @param {Object} header The header container element.
* @param {Object} body The body container element.
* @param {Object} footer The footer container element.
- * @param {Bool} show If the dialogue should show.
+ * @param {int|Null} type The messages conversation type to be removed.
* @return {Object} jQuery promise
*/
- var renderConfirmDeleteSelectedMessages = function(header, body, footer, show) {
- if (show) {
- return Str.get_string('deleteselectedmessagesconfirm', 'core_message')
+ var renderConfirmDeleteSelectedMessages = function(header, body, footer, type) {
+ var showmessage = null;
+ if (type == CONVERSATION_TYPES.SELF) {
+ // Message displayed to self-conversations is slighly different.
+ showmessage = 'deleteselectedmessagesconfirmselfconversation';
+ } else if (type) {
+ // This other message should be displayed.
+ showmessage = 'deleteselectedmessagesconfirm';
+ }
+
+ if (showmessage) {
+ return Str.get_string(showmessage, 'core_message')
.then(function(string) {
return showConfirmDialogue(
header,
* @param {Object} header The header container element.
* @param {Object} body The body container element.
* @param {Object} footer The footer container element.
- * @param {Bool} show If the dialogue should show
+ * @param {int|Null} type The conversation type to be removed.
* @return {Object} jQuery promise
*/
- var renderConfirmDeleteConversation = function(header, body, footer, show) {
- if (show) {
- return Str.get_string('deleteallconfirm', 'core_message')
+ var renderConfirmDeleteConversation = function(header, body, footer, type) {
+ var showmessage = null;
+ if (type == CONVERSATION_TYPES.SELF) {
+ // Message displayed to self-conversations is slighly different.
+ showmessage = 'deleteallselfconfirm';
+ } else if (type) {
+ // This other message should be displayed.
+ showmessage = 'deleteallconfirm';
+ }
+
+ if (showmessage) {
+ return Str.get_string(showmessage, 'core_message')
.then(function(string) {
return showConfirmDialogue(
header,
}
};
+ /**
+ * Show or hide the self-conversation message.
+ *
+ * @param {Object} header The header container element.
+ * @param {Object} body The body container element.
+ * @param {Object} footer The footer container element.
+ * @param {Object} displayMessage should the message be displayed?.
+ * @return {Object|true} jQuery promise
+ */
+ var renderSelfConversationMessage = function(header, body, footer, displayMessage) {
+ var container = getSelfConversationMessageContainer(body);
+ if (displayMessage) {
+ container.removeClass('hidden');
+ } else {
+ container.addClass('hidden');
+ }
+ return true;
+ };
+
/**
* Show or hide the require add contact panel.
*
var renderReset = function(header, body, footer) {
hideConfirmDialogue(header, body, footer);
hideContactRequestSentContainer(body);
+ hideSelfConversationMessageContainer(body);
hideAllHeaderElements(header);
showHeaderPlaceholder(header);
hideAllFooterElements(footer);
confirmDeleteConversation: renderConfirmDeleteConversation,
confirmContactRequest: renderConfirmContactRequest,
requireAddContact: renderRequireAddContact,
+ selfConversationMessage: renderSelfConversationMessage,
contactRequestSent: renderContactRequestSent
},
{
'core_message/message_drawer_routes',
'core_message/message_drawer_events',
'core_message/message_drawer_view_overview_section',
- 'core_message/message_repository'
+ 'core_message/message_repository',
+ 'core_message/message_drawer_view_conversation_constants'
],
function(
$,
Routes,
MessageDrawerEvents,
Section,
- MessageRepository
+ MessageRepository,
+ Constants
) {
var SELECTORS = {
SECTION_TOGGLE_BUTTON: '[data-toggle]'
};
- var CONVERSATION_TYPES = {
- PRIVATE: 1,
- PUBLIC: 2,
- FAVOURITE: null
- };
-
var loadAllCountsPromise = null;
/**
* @return {Number}
*/
var filterCountsByType = function(counts, type) {
- return type === CONVERSATION_TYPES.FAVOURITE ? counts.favourites : counts.types[type];
+ var total = 0;
+ if (type === Constants.CONVERSATION_CATEGORY_TYPES.PRIVATE && counts.types[Constants.CONVERSATION_TYPES.SELF]) {
+ // As private and self conversations are displayed together, we need to add the counts for the self-conversations
+ // to the private ones, when there is any self-conversation.
+ total = counts.types[Constants.CONVERSATION_TYPES.PRIVATE] + counts.types[Constants.CONVERSATION_TYPES.SELF];
+ } else {
+ total = type === Constants.CONVERSATION_CATEGORY_TYPES.FAVOURITE ? counts.favourites : counts.types[type];
+ }
+ return total;
};
/**
var sections = [
// Favourite conversations section.
- [body.find(SELECTORS.FAVOURITES), CONVERSATION_TYPES.FAVOURITE, true],
+ [body.find(SELECTORS.FAVOURITES), Constants.CONVERSATION_CATEGORY_TYPES.FAVOURITE, true],
// Group conversations section.
- [body.find(SELECTORS.GROUP_MESSAGES), CONVERSATION_TYPES.PUBLIC, false],
+ [body.find(SELECTORS.GROUP_MESSAGES), Constants.CONVERSATION_CATEGORY_TYPES.PUBLIC, false],
// Private conversations section.
- [body.find(SELECTORS.MESSAGES), CONVERSATION_TYPES.PRIVATE, false]
+ [body.find(SELECTORS.MESSAGES), Constants.CONVERSATION_CATEGORY_TYPES.PRIVATE, false]
];
sections.forEach(function(args) {
lastmessage: lastMessage ? $(lastMessage.text).text() || lastMessage.text : null
};
- if (conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.PRIVATE) {
- var otherUser = conversation.members.reduce(function(carry, member) {
+ var otherUser = null;
+ if (conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF) {
+ // Self-conversations have only one member.
+ otherUser = conversation.members[0];
+ } else if (conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.PRIVATE) {
+ // For private conversations, remove the current userId from the members to get the other user.
+ otherUser = conversation.members.reduce(function(carry, member) {
if (!carry && member.id != userId) {
carry = member;
}
return carry;
}, null);
+ }
+ if (otherUser !== null) {
formattedConversation.userid = otherUser.id;
formattedConversation.showonlinestatus = otherUser.showonlinestatus;
formattedConversation.isonline = otherUser.isonline;
type,
LOAD_LIMIT + 1,
offset,
- includeFavourites
+ includeFavourites,
+ true // Always merge self-conversations with private conversations, to display them together.
)
.then(function(response) {
var conversations = response.conversations;
});
PubSub.subscribe(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, function(conversation) {
+ // Self-conversations could be displayed as private conversations when they are not starred. So we need to exclude
+ // them from the following check to make sure last messages are updated properly for them.
if (
- (type && conversation.type != type) ||
+ (type && conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF &&
+ type != MessageDrawerViewConversationContants.CONVERSATION_TYPES.PRIVATE && !conversation.isFavourite) ||
+ (type && conversation.type != MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF &&
+ type != conversation.type) ||
(includeFavourites && !conversation.isFavourite) ||
(!includeFavourites && conversation.isFavourite)
) {
if (!conversationElement.length) {
createNewConversation(root, conversation);
}
- } else if (type == conversation.type) {
+ } else if (type == conversation.type ||
+ (type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.PRIVATE &&
+ conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF)) {
+ // Self-conversations are displayed in the private conversations section, so they should be removed from
+ // there when they are favourited.
conversationElement = getConversationElement(root, conversation.id);
if (conversationElement.length) {
deleteConversation(root, conversationElement);
if (conversationElement.length) {
deleteConversation(root, conversationElement);
}
- } else if (type == conversation.type) {
+ } else if (type == conversation.type ||
+ (type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.PRIVATE &&
+ conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF)) {
+ // Self-conversations are displayed in the private conversations section, so they should be added
+ // there when they are unfavourited.
conversationElement = getConversationElement(root, conversation.id);
if (!conversationElement.length) {
createNewConversation(root, conversation);
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) {
-
- var CONVERSATION_TYPES = {
- PRIVATE: 1,
- PUBLIC: 2
- };
+define(
+[
+ 'jquery',
+ 'core/ajax',
+ 'core/notification',
+ 'core_message/message_drawer_view_conversation_constants'
+], function(
+ $,
+ Ajax,
+ Notification,
+ Constants) {
+
+ var CONVERSATION_TYPES = Constants.CONVERSATION_TYPES;
/**
* Retrieve a list of messages from the server.
return Ajax.call([request])[0];
};
+ /**
+ * Get a self-conversation.
+ *
+ * @param {int} loggedInUserId The logged in user
+ * @param {int} messageLimit Limit for messages
+ * @param {int} messageOffset Offset for messages
+ * @param {bool} newestMessagesFirst Order the messages by newest first
+ * @return {object} jQuery promise
+ */
+ var getSelfConversation = function(
+ loggedInUserId,
+ messageLimit,
+ messageOffset,
+ newestMessagesFirst
+ ) {
+ var args = {
+ userid: loggedInUserId
+ };
+
+ if (typeof messageLimit != 'undefined' && messageLimit !== null) {
+ args.messagelimit = messageLimit;
+ }
+
+ if (typeof messageOffset != 'undefined' && messageOffset !== null) {
+ args.messageoffset = messageOffset;
+ }
+
+ if (typeof newestMessagesFirst != 'undefined' && newestMessagesFirst !== null) {
+ args.newestmessagesfirst = newestMessagesFirst;
+ }
+
+ var request = {
+ methodname: 'core_message_get_self_conversation',
+ args: args
+ };
+
+ return Ajax.call([request])[0];
+ };
+
/**
* Get the conversations for a user.
*
type,
limit,
offset,
- favourites
+ favourites,
+ mergeself
) {
var args = {
userid: userId,
args.favourites = favourites;
}
+ if (typeof mergeself != 'undefined' && mergeself !== null) {
+ args.mergeself = mergeself;
+ }
+
var request = {
methodname: 'core_message_get_conversations',
args: args
.then(function(result) {
if (result.conversations.length) {
result.conversations = result.conversations.map(function(conversation) {
- if (conversation.type == CONVERSATION_TYPES.PRIVATE) {
+ if (conversation.type == CONVERSATION_TYPES.PRIVATE || conversation.type == CONVERSATION_TYPES.SELF) {
var otherUser = conversation.members.length ? conversation.members[0] : null;
if (otherUser) {
declineContactRequest: declineContactRequest,
getConversation: getConversation,
getConversationBetweenUsers: getConversationBetweenUsers,
+ getSelfConversation: getSelfConversation,
getConversations: getConversations,
getConversationMembers: getConversationMembers,
setFavouriteConversations: setFavouriteConversations,
*/
const MESSAGE_CONVERSATION_TYPE_GROUP = 2;
+ /**
+ * A self conversation.
+ */
+ const MESSAGE_CONVERSATION_TYPE_SELF = 3;
+
/**
* The state for an enabled conversation area.
*/
LEFT JOIN {message_user_actions} mua
ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?)
WHERE (m.useridfrom = ? OR mcm.userid = ?)
- AND m.useridfrom != mcm.userid
+ AND (m.useridfrom != mcm.userid OR mc.type = ?)
AND u.deleted = 0
AND u2.deleted = 0
AND mua.id is NULL
AND " . $DB->sql_like('smallmessage', '?', false) . "
ORDER BY timecreated DESC";
- $params = array($userid, $userid, $userid, self::MESSAGE_ACTION_DELETED, $userid, $userid, '%' . $search . '%');
+ $params = array($userid, $userid, $userid, self::MESSAGE_ACTION_DELETED, $userid, $userid,
+ self::MESSAGE_CONVERSATION_TYPE_SELF, '%' . $search . '%');
// Convert the messages into searchable contacts with their last message being the message that was searched.
$conversations = array();
$fullname = $DB->sql_fullname();
// Users not to include.
- $excludeusers = array($userid, $CFG->siteguest);
+ $excludeusers = array($CFG->siteguest);
+ if (!$selfconversation = self::get_self_conversation($userid)) {
+ // Userid should only be excluded when she hasn't a self-conversation.
+ $excludeusers[] = $userid;
+ }
list($exclude, $excludeparams) = $DB->get_in_or_equal($excludeusers, SQL_PARAMS_NAMED, 'param', false);
$params = array('search' => '%' . $DB->sql_like_escape($search) . '%', 'userid1' => $userid, 'userid2' => $userid);
if (!empty($foundusers)) {
$noncontacts = helper::get_member_info($userid, array_keys($foundusers));
foreach ($noncontacts as $memberuserid => $memberinfo) {
- $noncontacts[$memberuserid]->conversations = self::get_conversations_between_users($userid, $memberuserid, 0, 1000);
+ if ($memberuserid !== $userid) {
+ $noncontacts[$memberuserid]->conversations = self::get_conversations_between_users($userid, $memberuserid, 0,
+ 1000);
+ } else {
+ $noncontacts[$memberuserid]->conversations[$selfconversation->id] = $selfconversation;
+ }
}
}
* @param int $limitnum
* @param int $type the type of the conversation, if you wish to filter to a certain type (see api constants).
* @param bool $favourites whether to include NO favourites (false) or ONLY favourites (true), or null to ignore this setting.
+ * @param bool $mergeself whether to include self-conversations (true) or ONLY private conversations (false)
+ * when private conversations are requested.
* @return array the array of conversations
* @throws \moodle_exception
*/
public static function get_conversations($userid, $limitfrom = 0, $limitnum = 20, int $type = null,
- bool $favourites = null) {
+ bool $favourites = null, bool $mergeself = false) {
global $DB;
if (!is_null($type) && !in_array($type, [self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
- self::MESSAGE_CONVERSATION_TYPE_GROUP])) {
+ self::MESSAGE_CONVERSATION_TYPE_GROUP, self::MESSAGE_CONVERSATION_TYPE_SELF])) {
throw new \moodle_exception("Invalid value ($type) for type param, please see api constants.");
}
}
// If we need to restrict type, generate the SQL snippet.
- $typesql = !is_null($type) ? " AND mc.type = :convtype " : "";
+ $typesql = "";
+ $typeparams = [];
+ if (!is_null($type)) {
+ if ($mergeself && $type == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) {
+ // When $megerself is set to true, the self-conversations are returned also with the private conversations.
+ $typesql = " AND (mc.type = :convtype1 OR mc.type = :convtype2) ";
+ $typeparams = ['convtype1' => $type, 'convtype2' => self::MESSAGE_CONVERSATION_TYPE_SELF];
+ } else {
+ $typesql = " AND mc.type = :convtype ";
+ $typeparams = ['convtype' => $type];
+ }
+ }
$sql = "SELECT m.id as messageid, mc.id as id, mc.name as conversationname, mc.type as conversationtype, m.useridfrom,
m.smallmessage, m.fullmessage, m.fullmessageformat, m.fullmessagetrust, m.fullmessagehtml, m.timecreated,
AND mc.enabled = 1 $typesql $favouritesql
ORDER BY (CASE WHEN m.timecreated IS NULL THEN 0 ELSE 1 END) DESC, m.timecreated DESC, id DESC";
- $params = array_merge($favouriteparams, ['userid' => $userid, 'action' => self::MESSAGE_ACTION_DELETED,
- 'userid2' => $userid, 'userid3' => $userid, 'userid4' => $userid, 'convaction' => self::CONVERSATION_ACTION_MUTED,
- 'convtype' => $type]);
+ $params = array_merge($favouriteparams, $typeparams, ['userid' => $userid, 'action' => self::MESSAGE_ACTION_DELETED,
+ 'userid2' => $userid, 'userid3' => $userid, 'userid4' => $userid, 'convaction' => self::CONVERSATION_ACTION_MUTED]);
$conversationset = $DB->get_recordset_sql($sql, $params, $limitfrom, $limitnum);
$conversations = [];
- $selfconversations = []; // Used to track legacy conversations with one's self (both conv members the same user).
+ $selfconversations = []; // Used to track conversations with one's self.
$members = [];
$individualmembers = [];
$groupmembers = [];
+ $selfmembers = [];
foreach ($conversationset as $conversation) {
$conversations[$conversation->id] = $conversation;
$members[$conversation->id] = [];
//
// For 'individual' type conversations between 2 users, regardless of who sent the last message,
// we want the details of the other member in the conversation (i.e. not the current user).
- // The only exception to the 'not the current user' rule is for 'self' conversations - a legacy construct in which a user
- // can message themselves via user bulk actions. Subsequently, there are 2 records for the same user created in the members
- // table.
//
// For 'group' type conversations, we want the details of the member who sent the last message, if there is one.
// This can be the current user or another group member, but for groups without messages, this will be empty.
//
+ // For 'self' type conversations, we want the details of the current user.
+ //
// This also means that if type filtering is specified and only group conversations are returned, we don't need this extra
// query to get the 'other' user as we already have that information.
$members[$conversation->id][$conversation->useridfrom] = $conversation->useridfrom;
$groupmembers[$conversation->useridfrom] = $conversation->useridfrom;
}
+ } else if ($conversation->conversationtype == self::MESSAGE_CONVERSATION_TYPE_SELF) {
+ $selfconversations[$conversation->id] = $conversation->id;
+ $members[$conversation->id][$userid] = $userid;
+ $selfmembers[$userid] = $userid;
}
}
// If we need to fetch any member information for any of the individual conversations.
$members[$member->conversationid][$member->userid] = $member->userid;
$individualmembers[$member->userid] = $member->userid;
}
-
- // Self conversations: If any of the individual conversations which were missing members are still missing members,
- // we know these must be 'self' conversations. This is a legacy scenario, created via user bulk actions.
- // In such cases, the member returned should be the current user.
- //
- // NOTE: Currently, these conversations are not returned by this method, however,
- // identifying them is important for future reference.
- foreach ($individualconversations as $indconvid) {
- if (empty($members[$indconvid])) {
- // Keep track of the self conversation (for future use).
- $selfconversations[$indconvid] = $indconvid;
-
- // Set the member to the current user.
- $members[$indconvid][$userid] = $userid;
- $individualmembers[$userid] = $userid;
- }
- }
}
// We could fail early here if we're sure that:
// 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($individualmembers) || !empty($groupmembers)) {
+ if (!empty($individualmembers) || !empty($groupmembers) || !empty($selfmembers)) {
// 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.
$individualmemberinfo = helper::get_member_info($userid, $individualmembers, true, true);
$groupmemberinfo = helper::get_member_info($userid, $diffgroupmembers);
+ $selfmemberinfo = helper::get_member_info($userid, $selfmembers);
// Don't use array_merge, as we lose array keys.
- $memberinfo = $individualmemberinfo + $groupmemberinfo;
+ $memberinfo = $individualmemberinfo + $groupmemberinfo + $selfmemberinfo;
if (empty($memberinfo)) {
return [];
$unreadcounts = $DB->get_records_sql($unreadcountssql, [$userid, self::MESSAGE_ACTION_READ, self::MESSAGE_ACTION_DELETED,
$userid, $userid]);
+ // For the self-conversations, get the total number of messages (to know if the conversation is new or it has been emptied).
+ $selfmessagessql = "SELECT COUNT(m.id)
+ FROM {messages} m
+ INNER JOIN {message_conversations} mc
+ ON mc.id = m.conversationid
+ WHERE mc.type = ? AND convhash = ?";
+ $selfmessagestotal = $DB->count_records_sql(
+ $selfmessagessql,
+ [self::MESSAGE_CONVERSATION_TYPE_SELF, helper::get_conversation_hash([$userid])]
+ );
+
// Because we'll be calling format_string on each conversation name and passing contexts, we preload them here.
// This warms the cache and saves potentially hitting the DB once for each context fetch below.
\context_helper::preload_contexts_by_id(array_column($conversations, 'contextid'));
// Now, create the final return structure.
$arrconversations = [];
foreach ($conversations as $conversation) {
- // Do not include any individual conversations which do not contain a recent message for the user.
+ // Do not include any individual which do not contain a recent message for the user.
// This happens if the user has deleted all messages.
+ // Exclude the self-conversations with messages but without a recent message because the user has deleted all them.
+ // Self-conversations without any message should be included, to display them first time they are created.
// Group conversations with deleted users or no messages are always returned.
- if ($conversation->conversationtype == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL
- && (empty($conversation->messageid))) {
- continue;
- }
-
- // Exclude 'self' conversations for now.
- if (isset($selfconversations[$conversation->id])) {
+ if ($conversation->conversationtype == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL && empty($conversation->messageid) ||
+ ($conversation->conversationtype == self::MESSAGE_CONVERSATION_TYPE_SELF && empty($conversation->messageid)
+ && $selfmessagestotal > 0)) {
continue;
}
$memberoffset,
$memberlimit
);
- // Strip out the requesting user to match what get_conversations does.
- $members = array_filter($members, function($member) use ($userid) {
- return $member->id != $userid;
- });
+ if ($conversation->type != self::MESSAGE_CONVERSATION_TYPE_SELF) {
+ // Strip out the requesting user to match what get_conversations does, except for self-conversations.
+ $members = array_filter($members, function($member) use ($userid) {
+ return $member->id != $userid;
+ });
+ }
$messages = self::get_conversation_messages(
$userid,
// 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.
- // - Individual conversations which are legacy 'self' conversations (2 members, both the same user) must NOT be counted.
+ // - Self-conversations with 0 messages must be counted.
+ // - Self-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
$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
+ $sql = "SELECT mc.type, fav.itemtype, COUNT(DISTINCT mc.id) as count, MAX(maxvisibleconvmessage.convid) as maxconvidmessage
FROM {message_conversations} mc
INNER JOIN {message_conversation_members} mcm
ON mcm.conversationid = mc.id
- INNER JOIN (
- SELECT mcm.conversationid, count(distinct mcm.userid) as membercount
- FROM {message_conversation_members} mcm
- WHERE mcm.conversationid IN (
- SELECT DISTINCT conversationid
- FROM {message_conversation_members} mcm2
- WHERE userid = :userid5
- )
- GROUP BY mcm.conversationid
- ) uniquemembercount
- ON uniquemembercount.conversationid = mc.id
LEFT JOIN (
SELECT m.conversationid as convid, MAX(m.timecreated) as maxtime
FROM {messages} m
WHERE mcm.userid = :userid3
AND mc.enabled = :enabled
AND (
- (mc.type = :individualtype AND maxvisibleconvmessage.convid IS NOT NULL AND membercount > 1) OR
- (mc.type = :grouptype)
+ (mc.type = :individualtype AND maxvisibleconvmessage.convid IS NOT NULL) OR
+ (mc.type = :grouptype) OR
+ (mc.type = :selftype)
)
GROUP BY mc.type, fav.itemtype
ORDER BY mc.type ASC";
'enabled' => self::MESSAGE_CONVERSATION_ENABLED,
'individualtype' => self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
'grouptype' => self::MESSAGE_CONVERSATION_TYPE_GROUP,
+ 'selftype' => self::MESSAGE_CONVERSATION_TYPE_SELF,
] + $favparams;
// Assemble the return array.
'favourites' => 0,
'types' => [
self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- self::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ self::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ self::MESSAGE_CONVERSATION_TYPE_SELF => 0
]
];
+ // For the self-conversations, get the total number of messages (to know if the conversation is new or it has been emptied).
+ $selfmessagessql = "SELECT COUNT(m.id)
+ FROM {messages} m
+ INNER JOIN {message_conversations} mc
+ ON mc.id = m.conversationid
+ WHERE mc.type = ? AND convhash = ?";
+ $selfmessagestotal = $DB->count_records_sql(
+ $selfmessagessql,
+ [self::MESSAGE_CONVERSATION_TYPE_SELF, helper::get_conversation_hash([$userid])]
+ );
+
$countsrs = $DB->get_recordset_sql($sql, $params);
foreach ($countsrs as $key => $val) {
+ // Empty self-conversations with deleted messages should be excluded.
+ if ($val->type == self::MESSAGE_CONVERSATION_TYPE_SELF && empty($val->maxconvidmessage) && $selfmessagestotal > 0) {
+ continue;
+ }
if (!empty($val->itemtype)) {
$counts['favourites'] += $val->count;
continue;
// 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) {
+ if ($conversation->type == self::MESSAGE_CONVERSATION_TYPE_GROUP ||
+ $conversation->type == self::MESSAGE_CONVERSATION_TYPE_SELF) {
return true;
} else if ($conversation->type == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) {
// Get the other user in the conversation.
return $conversations;
}
+ /**
+ * Returns the self conversation for a user.
+ *
+ * @param int $userid The user id to get the self-conversations
+ * @return \stdClass|false The self-conversation object or false if it doesn't exist
+ * @since Moodle 3.7
+ */
+ public static function get_self_conversation(int $userid) {
+ global $DB;
+
+ $conditions = [
+ 'type' => self::MESSAGE_CONVERSATION_TYPE_SELF,
+ 'convhash' => helper::get_conversation_hash([$userid])
+ ];
+ return $DB->get_record('message_conversations', $conditions);
+ }
+
/**
* Creates a conversation between two users.
*
$validtypes = [
self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
- self::MESSAGE_CONVERSATION_TYPE_GROUP
+ self::MESSAGE_CONVERSATION_TYPE_GROUP,
+ self::MESSAGE_CONVERSATION_TYPE_SELF
];
if (!in_array($type, $validtypes)) {
if (count($userids) > 2) {
throw new \moodle_exception('An individual conversation can not have more than two users.');
}
+ if ($userids[0] == $userids[1]) {
+ throw new \moodle_exception('Trying to create an individual conversation instead of a self conversation.');
+ }
+ } else if ($type == self::MESSAGE_CONVERSATION_TYPE_SELF) {
+ if (count($userids) != 1) {
+ throw new \moodle_exception('A self conversation can not have more than one user.');
+ }
}
$conversation = new \stdClass();
$conversation->type = $type;
$conversation->name = $name;
$conversation->convhash = null;
- if ($type == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) {
+ if ($type == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL || $type == self::MESSAGE_CONVERSATION_TYPE_SELF) {
$conversation->convhash = helper::get_conversation_hash($userids);
}
$conversation->component = $component;
* @return bool true if recipient hasn't blocked sender and sender can contact to recipient, false otherwise.
*/
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.
+ if (has_capability('moodle/site:messageanyuser', \context_system::instance(), $senderid) ||
+ $recipientid == $senderid) {
+ // The sender has the ability to contact any user across the entire site or themselves.
return true;
}
// Assemble the return array.
$counts = ['favourites' => 0, 'types' => [
self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- self::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ self::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ self::MESSAGE_CONVERSATION_TYPE_SELF => 0
]];
foreach ($unreadcounts as $convid => $info) {
if (isset($favouriteconvids[$convid])) {
// Get subcontext.
if (empty($conversation->contextid)) {
// Conversations without context are stored in 'Messages | <Other user id>'.
- $members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation->id]);
- $members = array_filter($members, function ($member) use ($userid) {
- return $member->userid != $userid;
- });
- if ($otheruser = reset($members)) {
- $otherusertext = $otheruser->userid;
+ if ($conversation->type == \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF) {
+ // This is a self-conversation. The other user is the same userid.
+ $otherusertext = $userid;
} else {
- $otherusertext = get_string('unknownuser', 'core_message') . '_' . $conversation->id;
+ $members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation->id]);
+ $members = array_filter($members, function ($member) use ($userid) {
+ return $member->userid != $userid;
+ });
+ if ($otheruser = reset($members)) {
+ $otherusertext = $otheruser->userid;
+ } else {
+ $otherusertext = get_string('unknownuser', 'core_message') . '_' . $conversation->id;
+ }
}
$subcontext = array_merge(
private function migrate_data($userid, $otheruserid) {
global $DB;
- if (!$conversationid = \core_message\api::get_conversation_between_users([$userid, $otheruserid])) {
+ if ($userid == $otheruserid) {
+ // Since 3.7, pending self-conversations should be migrated during the upgrading process so shouldn't be any
+ // self-conversations on the legacy tables. However, this extra-check has been added just in case.
+ $conversation = \core_message\api::get_self_conversation($userid);
+ if (empty($conversation)) {
+ $conversation = \core_message\api::create_conversation(
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ [$userid]
+ );
+ }
+ $conversationid = $conversation->id;
+ } else if (!$conversationid = \core_message\api::get_conversation_between_users([$userid, $otheruserid])) {
$conversation = \core_message\api::create_conversation(
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
[
'name' => new external_value(PARAM_TEXT, 'The conversation name, if set', VALUE_DEFAULT, null),
'subname' => new external_value(PARAM_TEXT, 'A subtitle for the conversation name, if set', VALUE_DEFAULT, null),
'imageurl' => new external_value(PARAM_URL, 'A link to the conversation picture, if set', VALUE_DEFAULT, null),
- 'type' => new external_value(PARAM_INT, 'The type of the conversation (1=individual,2=group)'),
+ 'type' => new external_value(PARAM_INT, 'The type of the conversation (1=individual,2=group,3=self)'),
'membercount' => new external_value(PARAM_INT, 'Total number of conversation members'),
'ismuted' => new external_value(PARAM_BOOL, 'If the user muted this conversation'),
'isfavourite' => new external_value(PARAM_BOOL, 'If the user marked this conversation as a favourite'),
'favourites' => new external_value(PARAM_BOOL, 'Whether to restrict the results to contain NO favourite
conversations (false), ONLY favourite conversation (true), or ignore any restriction altogether (null)',
VALUE_DEFAULT, null),
-
+ 'mergeself' => new external_value(PARAM_BOOL, 'Whether to include self-conversations (true) or ONLY private
+ conversations (false) when private conversations are requested.',
+ VALUE_DEFAULT, false),
)
);
}
* @param int $limitnum
* @param int|null $type
* @param bool|null $favourites
+ * @param bool $mergeself whether to include self-conversations (true) or ONLY private conversations (false)
+ * when private conversations are requested.
* @return stdClass
* @throws \moodle_exception if the messaging feature is disabled on the site.
* @since 3.2
*/
- public static function get_conversations($userid, $limitfrom = 0, $limitnum = 0, int $type = null, bool $favourites = null) {
+ public static function get_conversations($userid, $limitfrom = 0, $limitnum = 0, int $type = null, bool $favourites = null,
+ bool $mergeself = false) {
global $CFG, $USER;
// All the standard BL checks.
'limitfrom' => $limitfrom,
'limitnum' => $limitnum,
'type' => $type,
- 'favourites' => $favourites
+ 'favourites' => $favourites,
+ 'mergeself' => $mergeself
);
$params = self::validate_parameters(self::get_conversations_parameters(), $params);
$params['limitfrom'],
$params['limitnum'],
$params['type'],
- $params['favourites']
+ $params['favourites'],
+ $params['mergeself']
);
return (object) ['conversations' => $conversations];
return self::get_conversation_structure(true);
}
+ /**
+ * Get self-conversation parameters.
+ *
+ * @return external_function_parameters
+ */
+ public static function get_self_conversation_parameters() {
+ return new external_function_parameters(
+ array(
+ 'userid' => new external_value(PARAM_INT, 'The id of the user who we are viewing self-conversations for'),
+ 'messagelimit' => new external_value(PARAM_INT, 'Limit for number of messages', VALUE_DEFAULT, 100),
+ 'messageoffset' => new external_value(PARAM_INT, 'Offset for messages list', VALUE_DEFAULT, 0),
+ 'newestmessagesfirst' => new external_value(PARAM_BOOL, 'Order messages by newest first', VALUE_DEFAULT, true)
+ )
+ );
+ }
+
+ /**
+ * Get a single self-conversation.
+ *
+ * @param int $userid The user id to get the self-conversation for
+ * @param int $messagelimit Limit number of messages to load
+ * @param int $messageoffset Offset the messages
+ * @param bool $newestmessagesfirst Order messages by newest first
+ * @return stdClass
+ * @throws \moodle_exception if the messaging feature is disabled on the site.
+ * @since Moodle 3.7
+ */
+ public static function get_self_conversation(
+ int $userid,
+ int $messagelimit = 0,
+ int $messageoffset = 0,
+ bool $newestmessagesfirst = true
+ ) {
+ global $CFG;
+
+ // All the standard BL checks.
+ if (empty($CFG->messaging)) {
+ throw new moodle_exception('disabled', 'message');
+ }
+
+ $params = [
+ 'userid' => $userid,
+ 'messagelimit' => $messagelimit,
+ 'messageoffset' => $messageoffset,
+ 'newestmessagesfirst' => $newestmessagesfirst
+ ];
+ self::validate_parameters(self::get_self_conversation_parameters(), $params);
+
+ $systemcontext = context_system::instance();
+ self::validate_context($systemcontext);
+
+ $conversation = \core_message\api::get_self_conversation($params['userid']);
+
+ if ($conversation) {
+ $conversation = \core_message\api::get_conversation(
+ $params['userid'],
+ $conversation->id,
+ false,
+ false,
+ 0,
+ 0,
+ $params['messagelimit'],
+ $params['messageoffset'],
+ $params['newestmessagesfirst']
+ );
+ }
+
+ if ($conversation) {
+ return $conversation;
+ } else {
+ // We have to throw an exception here because the external functions annoyingly
+ // don't accept null to be returned for a single structure.
+ throw new \moodle_exception('errorconversationdoesnotexist', 'message');
+ }
+ }
+
+ /**
+ * Get conversation returns.
+ *
+ * @return external_single_structure
+ */
+ public static function get_self_conversation_returns() {
+ return self::get_conversation_structure();
+ }
+
/**
* The messagearea conversations parameters.
*
'Total number of individual conversations'),
\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => new external_value(PARAM_INT,
'Total number of group conversations'),
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => new external_value(PARAM_INT,
+ 'Total number of self conversations'),
]
),
]
'Total number of unread individual conversations'),
\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => new external_value(PARAM_INT,
'Total number of unread group conversations'),
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => new external_value(PARAM_INT,
+ 'Total number of unread self conversations'),
]
),
]
style="overflow-y: auto; overflow-x: hidden"
>
<div class="position-relative h-100" data-region="content-container" style="overflow-y: auto; overflow-x: hidden">
+ <div class="p-3 text-center hidden" data-region="self-conversation-message-container">
+ <p class="m-0">{{#str}} selfconversation, core_message {{/str}}</p>
+ <p class="font-italic font-weight-light" data-region="text">{{#str}} selfconversationdefaultmessage, core_message {{/str}}</p>
+ </div>
<div class="p-3 text-center hidden" data-region="contact-request-sent-message-container">
<p class="m-0">{{#str}} contactrequestsent, core_message {{/str}}</p>
<p class="font-italic font-weight-light" data-region="text"></p>
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template core_message/message_drawer_view_conversation_header_content_type_self
+
+ This template will render the header content of the conversation page in
+ the message message drawer.
+
+ Classes required for JS:
+ * none
+
+ Data attributes required for JS:
+ * All data attributes are required
+
+ Context variables required for this template:
+ * urls The URLs for the popover
+ * name
+ * subname
+ * imageurl
+ * isfavourite
+ * showonlinestatus
+
+
+ Example context (json):
+ {}
+
+}}
+
+<div class="d-flex align-items-center">
+ <div class="align-self-stretch" >
+ <a class="h-100 mr-2 d-flex align-items-center" href="#" data-route-back>
+ {{> core_message/message_drawer_icon_back }}
+ </a>
+ </div>
+ <div class="d-flex text-truncate">
+ {{#imageurl}}
+ <div class="d-flex align-items-center">
+ <img
+ class="rounded-circle"
+ src="{{{.}}}"
+ alt="{{name}}"
+ aria-hidden="true"
+ style="height: 38px"
+ >
+ </div>
+ {{/imageurl}}
+ <div class="w-100 text-truncate ml-2">
+ <div class="d-flex">
+ <strong class="m-0 text-truncate">{{name}}</strong>
+ <span class="{{^isfavourite}}hidden{{/isfavourite}} ml-1 text-primary" data-region="favourite-icon-container">
+ {{#pix}} i/star-rating, core {{/pix}}
+ </span>
+ </div>
+ {{#showonlinestatus}}
+ <p class="m-0 font-weight-light text-truncate">
+ {{#str}} loggedin, core_message {{/str}}
+ </p>
+ {{/showonlinestatus}}
+ </div>
+ </div>
+ <div class="ml-auto dropdown">
+ <button class="btn btn-link btn-icon icon-size-3" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ {{#pix}} i/moremenu, core {{/pix}}
+ </button>
+ <div class="dropdown-menu pull-right">
+ <a class="dropdown-item {{#isfavourite}}hidden{{/isfavourite}} {{^showfavourite}}hidden{{/showfavourite}}" href="#" data-action="confirm-favourite">
+ {{#str}} addtofavourites, core_message {{/str}}
+ </a>
+ <a class="dropdown-item {{^isfavourite}}hidden{{/isfavourite}} {{^showfavourite}}hidden{{/showfavourite}}" href="#" data-action="confirm-unfavourite">
+ {{#str}} removefromfavourites, core_message {{/str}}
+ </a>
+ <a class="dropdown-item" href="#" data-action="request-delete-conversation">
+ {{#str}} deleteconversation, core_message {{/str}}
+ </a>
+ </div>
+ </div>
+</div>
\ No newline at end of file
$this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, $noncontacts[3]->conversations[$gc1->id]->type);
}
+ /**
+ * Verify searching for users find themselves when they have self-conversations.
+ */
+ public function test_message_search_users_self_conversations() {
+ $this->resetAfterTest();
+
+ // Create some users.
+ $user1 = new stdClass();
+ $user1->firstname = 'User';
+ $user1->lastname = 'One';
+ $user1 = $this->getDataGenerator()->create_user($user1);
+ $user2 = new stdClass();
+ $user2->firstname = 'User';
+ $user2->lastname = 'Two';
+ $user2 = $this->getDataGenerator()->create_user($user2);
+
+ // Create self-conversation for user1.
+ $sc1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user1->id]);
+ testhelper::send_fake_message_to_conversation($user1, $sc1->id, 'Hi myself!');
+
+ // Perform a search as user1.
+ $this->setUser($user1);
+ $result = \core_message\api::message_search_users($user1->id, 'One');
+
+ // Check user1 is found as non-contacts.
+ $this->assertCount(0, $result[0]);
+ $this->assertCount(1, $result[1]);
+ }
+
/**
* Verify searching for users works even if no matching users from either contacts, or non-contacts can be found.
*/
$this->assertCount(0, \core_message\api::get_conversations_between_users($user6->id, $user1->id));
}
+ /**
+ * Tests getting self-conversations.
+ */
+ public function test_get_self_conversation() {
+ // Create some users.
+ $user1 = new stdClass();
+ $user1->firstname = 'User';
+ $user1->lastname = 'One';
+ $user1 = self::getDataGenerator()->create_user($user1);
+
+ $user2 = new stdClass();
+ $user2->firstname = 'User';
+ $user2->lastname = 'Two';
+ $user2 = self::getDataGenerator()->create_user($user2);
+
+ $user3 = new stdClass();
+ $user3->firstname = 'User search';
+ $user3->lastname = 'Three';
+ $user3 = self::getDataGenerator()->create_user($user3);
+
+ // Add some users as contacts.
+ \core_message\api::add_contact($user1->id, $user2->id);
+ \core_message\api::add_contact($user3->id, $user1->id);
+
+ // Create private conversations with some users.
+ \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
+ array($user1->id, $user2->id));
+ \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
+ array($user3->id, $user1->id));
+
+ // Create a group conversation with users.
+ $gc = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
+ array($user1->id, $user2->id, $user3->id),
+ 'Project chat');
+
+ // Create self-conversations.
+ $sc1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ array($user1->id));
+ $sc2 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ array($user2->id));
+
+ // Send message to self-conversation.
+ testhelper::send_fake_message_to_conversation($user1, $sc1->id, 'Message to myself!');
+
+ $rsc1 = \core_message\api::get_self_conversation($user1->id);
+ $rsc2 = \core_message\api::get_self_conversation($user2->id);
+ $rsc3 = \core_message\api::get_self_conversation($user3->id);
+
+ // Check that we retrieved the correct conversations.
+ $this->assertEquals($sc1->id, $rsc1->id);
+ $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, $sc1->type);
+ $this->assertEquals($sc2->id, $rsc2->id);
+ $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, $sc2->type);
+ $this->assertFalse($rsc3);
+ }
+
/**
* Tests searching messages.
*/
// The person doing the search.
$this->setUser($user1);
+ // Create self-conversation.
+ $sc = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user1->id]);
+
// Create group conversation.
$gc = \core_message\api::create_conversation(
\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
// Send some messages back and forth.
$time = 1;
- testhelper::send_fake_message_to_conversation($user1, $gc->id, 'My hero!', $time);
- $this->send_fake_message($user3, $user1, 'Don\'t block me.', 0, $time + 1);
- $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 2);
- $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 3);
- $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 4);
- $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 5);
+ testhelper::send_fake_message_to_conversation($user1, $sc->id, 'Test message to self!', $time);
+ testhelper::send_fake_message_to_conversation($user1, $gc->id, 'My hero!', $time + 1);
+ $this->send_fake_message($user3, $user1, 'Don\'t block me.', 0, $time + 2);
+ $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 3);
+ $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 4);
+ $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 5);
+ $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 6);
$convid = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]);
$conv2id = \core_message\api::get_conversation_between_users([$user1->id, $user3->id]);
$messages = \core_message\api::search_messages($user1->id, 'o');
// Confirm the data is correct.
- $this->assertEquals(4, count($messages));
-
+ $this->assertEquals(5, count($messages));
$message1 = $messages[0];
$message2 = $messages[1];
$message3 = $messages[2];
$message4 = $messages[3];
+ $message5 = $messages[4];
$this->assertEquals($user2->id, $message1->userid);
$this->assertEquals($user2->id, $message1->useridfrom);
$this->assertTrue($message4->isread);
$this->assertNull($message4->unreadcount);
$this->assertEquals($gc->id, $message4->conversationid);
+
+ $this->assertEquals($user1->id, $message5->userid);
+ $this->assertEquals($user1->id, $message5->useridfrom);
+ $this->assertEquals(fullname($user1), $message5->fullname);
+ $this->assertTrue($message5->ismessaging);
+ $this->assertEquals('Test message to self!', $message5->lastmessage);
+ $this->assertNotEmpty($message5->messageid);
+ $this->assertFalse($message5->isonline);
+ $this->assertTrue($message5->isread);
+ $this->assertFalse($message5->isblocked);
+ $this->assertNull($message5->unreadcount);
+ $this->assertEquals($sc->id, $message5->conversationid);
}
/**
}
/**
- * Tests retrieving conversations when a legacy 'self' conversation exists.
+ * Tests retrieving conversations when a 'self' conversation exists.
*/
- public function test_get_conversations_legacy_self_conversations() {
+ public function test_get_conversations_self_conversations() {
global $DB;
- // Create a legacy conversation between one user and themself.
+ // Create a conversation between one user and themself.
$user1 = self::getDataGenerator()->create_user();
- $conversation = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
- [$user1->id, $user1->id]);
- testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Test message to self!');
+ $user2 = self::getDataGenerator()->create_user();
+ $user3 = self::getDataGenerator()->create_user();
+ $user4 = self::getDataGenerator()->create_user();
+
+ // Create some individual conversations.
+ $ic1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
+ [$user1->id, $user2->id]);
+ $ic2 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
+ [$user1->id, $user3->id]);
+ testhelper::send_fake_message_to_conversation($user1, $ic1->id, 'Message from user1 to user2');
+
+ // Create some self-conversations.
+ $sc1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user1->id]);
+ $sc4 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user4->id]);
+ testhelper::send_fake_message_to_conversation($user1, $sc1->id, 'Test message to self 1!');
// Verify we are in a 'self' conversation state.
- $members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation->id]);
- $this->assertCount(2, $members);
- $member = array_pop($members);
- $this->assertEquals($user1->id, $member->userid);
+ $members = $DB->get_records('message_conversation_members', ['conversationid' => $sc1->id]);
+ $this->assertCount(1, $members);
$member = array_pop($members);
$this->assertEquals($user1->id, $member->userid);
- // Verify this conversation is not returned by the method.
- $conversations = \core_message\api::get_conversations($user1->id);
- $this->assertCount(0, $conversations);
+ // Verify the self-conversations are returned by the method.
+ $conversations = \core_message\api::get_conversations($user1->id, 0, 20, \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF);
+ $this->assertCount(1, $conversations);
+ $conversation = array_pop($conversations);
+ $this->assertEquals($conversation->id, $sc1->id);
+
+ $conversations = \core_message\api::get_conversations($user4->id);
+ // The self-conversation.
+ $this->assertCount(1, $conversations);
+
+ // Get only private conversations for user1 (empty conversations, like $ic2, are not returned).
+ $conversations = \core_message\api::get_conversations($user1->id, 0, 20,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL);
+ $this->assertCount(1, $conversations);
+
+ // Merge self with private conversations for user1.
+ $conversations = \core_message\api::get_conversations($user1->id, 0, 20,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, null, true);
+ $this->assertCount(2, $conversations);
+
+ // Get only private conversations for user2.
+ $conversations = \core_message\api::get_conversations($user2->id, 0, 20,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL);
+ $this->assertCount(1, $conversations);
+
+ // Merge self with private conversations for user2 (is the same result than before because user2 hasn't self-conversations).
+ $conversations = \core_message\api::get_conversations($user2->id, 0, 20,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, null, true);
+ $this->assertCount(1, $conversations);
}
/**
// The person doing the search.
$this->setUser($user1);
+ // Create self-conversation.
+ $sc1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user1->id]);
+ $sc2 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user2->id]);
+
// Send some messages back and forth.
$time = 1;
$m1id = $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 1);
$m2id = $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2);
$m3id = $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3);
$m4id = $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 4);
+ $m5id = testhelper::send_fake_message_to_conversation($user1, $sc1->id, 'Hi to myself!', $time + 5);
+ $m6id = testhelper::send_fake_message_to_conversation($user2, $sc2->id, 'I am talking with myself', $time + 6);
- // Delete the conversation as user 1.
$conversationid = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]);
+
+ // Delete the individual conversation between user1 and user2 (only for user1).
\core_message\api::delete_conversation_by_id($user1->id, $conversationid);
$muas = $DB->get_records('message_user_actions', array(), 'timecreated ASC');
$this->assertEquals($user1->id, $mua4->userid);
$this->assertEquals($m4id, $mua4->messageid);
$this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua4->action);
+
+ // Delete the self-conversation as user 1.
+ \core_message\api::delete_conversation_by_id($user1->id, $sc1->id);
+
+ $muas = $DB->get_records('message_user_actions', array(), 'timecreated ASC');
+ $this->assertCount(5, $muas);
+
+ // Sort by id.
+ ksort($muas);
+
+ $mua1 = array_shift($muas);
+ $mua2 = array_shift($muas);
+ $mua3 = array_shift($muas);
+ $mua4 = array_shift($muas);
+ $mua5 = array_shift($muas);
+
+ // Check only messages in self-conversion for user1 are deleted (self-conversation for user2 shouldn't be removed).
+ $this->assertEquals($user1->id, $mua5->userid);
+ $this->assertEquals($m5id, $mua5->messageid);
+ $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua5->action);
}
/**
]
);
+ // Create a self-conversation for user1.
+ $sc1 = \core_message\api::create_conversation(
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ [$user1->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 self 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, $sc1->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));
]
);
+ // Create a self-conversation for user1.
+ $sc1 = \core_message\api::create_conversation(
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ [$user1->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));
+ $this->assertFalse(\core_message\api::can_send_message_to_conversation($user4->id, $sc1->id));
}
/**
$this->assertEquals($user3->id, $request1->requesteduserid);
}
+ /**
+ * Test returning members of a self conversation.
+ */
+ public function test_get_conversation_members_with_self_conversation() {
+ $lastaccess = new stdClass();
+ $lastaccess->lastaccess = time();
+
+ $user1 = self::getDataGenerator()->create_user($lastaccess);
+
+ $selfconversation = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ [$user1->id]);
+ testhelper::send_fake_message_to_conversation($user1, $selfconversation->id, 'This is a self-message!');
+
+ // Get the members for the self-conversation.
+ $members = \core_message\api::get_conversation_members($user1->id, $selfconversation->id);
+ $this->assertCount(1, $members);
+
+ $member1 = array_shift($members);
+
+ // Confirm the standard fields are OK.
+ $this->assertEquals($user1->id, $member1->id);
+ $this->assertEquals(fullname($user1), $member1->fullname);
+ $this->assertEquals(true, $member1->isonline);
+ $this->assertEquals(true, $member1->showonlinestatus);
+ $this->assertEquals(false, $member1->iscontact);
+ $this->assertEquals(false, $member1->isblocked);
+ }
+
/**
* Test verifying that messages can be sent to existing individual conversations.
*/
public function test_get_conversation_counts_test_cases() {
$typeindividual = \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL;
$typegroup = \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP;
+ $typeself = \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF;
list($user1, $user2, $user3, $user4, $user5, $user6, $user7, $user8) = [0, 1, 2, 3, 4, 5, 6, 7];
$conversations = [
[
'enabled' => false
],
[
- 'type' => $typeindividual,
- 'users' => [$user8, $user8],
- 'messages' => [$user8, $user8],
+ 'type' => $typeself,
+ 'users' => [$user8],
+ 'messages' => [$user8],
'favourites' => [],
- 'enabled' => null // Individual conversations cannot be disabled.
+ 'enabled' => null // Self-conversations cannot be disabled.
],
];
'arguments' => [$user5],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user4],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user2],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user4],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user2],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user3],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => [$user2]
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => [$user2, $user3, $user4]
],
'arguments' => [$user6],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user7],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user8],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 1
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
$counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL]);
$this->assertEquals($expectedcounts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP],
$counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP]);
+ $this->assertEquals($expectedcounts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF],
+ $counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF]);
}
/**
$counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL]);
$this->assertEquals($expectedunreadcounts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP],
$counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP]);
+ $this->assertEquals($expectedunreadcounts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF],
+ $counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF]);
}
public function test_delete_all_conversation_data() {
$this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, $noncontacts[3]['conversations'][0]['type']);
}
+ /**
+ * Verify searching for users find themselves when they have self-conversations.
+ */
+ public function test_message_search_users_self_conversations() {
+ $this->resetAfterTest();
+
+ // Create some users.
+ $user1 = new stdClass();
+ $user1->firstname = 'User';
+ $user1->lastname = 'One';
+ $user1 = $this->getDataGenerator()->create_user($user1);
+ $user2 = new stdClass();
+ $user2->firstname = 'User';
+ $user2->lastname = 'Two';
+ $user2 = $this->getDataGenerator()->create_user($user2);
+
+ // Create self-conversation for user1.
+ $sc1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user1->id]);
+ testhelper::send_fake_message_to_conversation($user1, $sc1->id, 'Hi myself!');
+
+ // Perform a search as user1.
+ $this->setUser($user1);
+ $result = core_message_external::message_search_users($user1->id, 'One');
+ $result = external_api::clean_returnvalue(core_message_external::message_search_users_returns(), $result);
+
+ // Check results are empty.
+ $this->assertCount(0, $result['contacts']);
+ $this->assertCount(1, $result['noncontacts']);
+ }
+
/**
* Verify searching for users works even if no matching users from either contacts, or non-contacts can be found.
*/
}
/**
- * Tests retrieving conversations when a legacy 'self' conversation exists.
+ * Tests retrieving conversations when a 'self' conversation exists.
*/
- public function test_get_conversations_legacy_self_conversations() {
+ public function test_get_conversations_self_conversations() {
global $DB;
$this->resetAfterTest();
- // Create a legacy conversation between one user and themself.
+ // Create a conversation between one user and themself.
$user1 = self::getDataGenerator()->create_user();
- $conversation = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
- [$user1->id, $user1->id]);
+ $conversation = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ [$user1->id]);
testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Test message to self!');
// Verify we are in a 'self' conversation state.
$members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation->id]);
- $this->assertCount(2, $members);
- $member = array_pop($members);
- $this->assertEquals($user1->id, $member->userid);
+ $this->assertCount(1, $members);
$member = array_pop($members);
$this->assertEquals($user1->id, $member->userid);
- // Verify this conversation is not returned by the method.
+ // Verify this conversation is returned by the method.
$this->setUser($user1);
$result = core_message_external::get_conversations($user1->id, 0, 20);
$result = external_api::clean_returnvalue(core_message_external::get_conversations_returns(), $result);
$conversations = $result['conversations'];
- $this->assertCount(0, $conversations);
+ $this->assertCount(1, $conversations);
}
/**
public function test_get_conversation_counts_test_cases() {
$typeindividual = \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL;
$typegroup = \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP;
+ $typeself = \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF;
list($user1, $user2, $user3, $user4, $user5, $user6, $user7, $user8) = [0, 1, 2, 3, 4, 5, 6, 7];
$conversations = [
[
'enabled' => false
],
[
- 'type' => $typeindividual,
- 'users' => [$user8, $user8],
- 'messages' => [$user8, $user8],
+ 'type' => $typeself,
+ 'users' => [$user8],
+ 'messages' => [$user8],
'favourites' => [],
'enabled' => null // Individual conversations cannot be disabled.
],
'arguments' => [$user5],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user4],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user2],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user4],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user2],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user3],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => [$user2]
],
'arguments' => [$user1],
'expectedcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 1, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => [$user2, $user3, $user4]
],
'arguments' => [$user6],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user7],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
'arguments' => [$user8],
'expectedcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 1
]],
'expectedunreadcounts' => ['favourites' => 0, 'types' => [
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
- \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
]],
'deletedusers' => []
],
$counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL]);
$this->assertEquals($expectedcounts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP],
$counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP]);
+ $this->assertEquals($expectedcounts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF],
+ $counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF]);
}
/**
$counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL]);
$this->assertEquals($expectedunreadcounts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP],
$counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP]);
+ $this->assertEquals($expectedunreadcounts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF],
+ $counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF]);
}
}
return $DB->insert_record('notifications', $record);
}
- if (!$conversationid = \core_message\api::get_conversation_between_users([$userfrom->id, $userto->id])) {
+ if ($userfrom->id == $userto->id) {
+ // It's a self conversation.
+ $conversation = \core_message\api::get_self_conversation($userfrom->id);
+ if (empty($conversation)) {
+ $conversation = \core_message\api::create_conversation(
+ \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+ [$userfrom->id]
+ );
+ }
+ $conversationid = $conversation->id;
+ } else if (!$conversationid = \core_message\api::get_conversation_between_users([$userfrom->id, $userto->id])) {
+ // It's an individual conversation between two different users.
$conversation = \core_message\api::create_conversation(
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
[
functionality and remove all the legacy code (see MDL-63915).
Note - It's still possible to view another user's messages if you have the right capabilities and are able to
'log-in as' them.
+* A new parameter 'mergeself' has been added to the methods \core_message\api::get_conversations() and
+ core_message_external::get_conversations(), to decide whether the self-conversations should be included or not when the
+ private ones are requested, to display them together.
=== 3.6 ===
\core\event\user_created::create_from_userid($newuserid)->trigger();
}
+ // All new users must have a starred self-conversation.
+ $selfconversation = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$newuserid]);
+ \core_message\api::set_favourite_conversation($selfconversation->id, $newuserid);
+
// Purge the associated caches for the current user only.
$presignupcache = \cache::make('core', 'presignup');
$presignupcache->purge_current_user();
defined('MOODLE_INTERNAL') || die();
-$version = 2019041300.00; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2019041300.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.