MDL-85777 message: Disable message input when sending isn’t allowed

This commit is contained in:
yusufwib01
2025-07-29 10:51:15 +08:00
parent a1e5573ee8
commit bdd055b8ed
26 changed files with 309 additions and 114 deletions
+1
View File
@@ -86,3 +86,4 @@ maxsectionslimit,core
maxsectionaddmessage,core_courseformat
activities_help,core
resources_help,core
unabletomessage,core_message
+5 -1
View File
@@ -39,6 +39,7 @@ $string['blockuserconfirm'] = 'Are you sure you want to block {$a}?';
$string['blockuserconfirmbutton'] = 'Block';
$string['blocknoncontacts'] = 'Prevent non-contacts from messaging me';
$string['cancelselection'] = 'Cancel message selection';
$string['cannotsendmessages'] = 'Cannot send messages';
$string['cantblockuser'] = 'You can\'t block {$a} because they have a role with permission to message all users.';
$string['contactableprivacy'] = 'Accept messages from:';
$string['contactableprivacy_onlycontacts'] = 'My contacts only';
@@ -128,6 +129,7 @@ $string['nocontactsgetstarted'] = 'No contacts';
$string['nogroupconversations'] = 'No group conversations';
$string['noindividualconversations'] = 'No private conversations';
$string['nomessagesfound'] = 'No messages were found';
$string['nopermissiontosend'] = 'You do not have permission to send messages in this conversation';
$string['noreply'] = 'Do not reply to this message';
$string['noncontacts'] = 'Non-contacts';
$string['nonotifications'] = 'You have no notifications';
@@ -253,7 +255,6 @@ $string['successproviderupdate'] = '\'{$a}\' updated.';
$string['togglemessagemenu'] = 'Toggle messaging drawer';
$string['totalconversations'] = '{$a} total conversations';
$string['touserdoesntexist'] = 'You cannot send a message to a user ID ({$a}) that doesn\'t exist.';
$string['unabletomessage'] = 'You are unable to message this user';
$string['unblock'] = 'Unblock';
$string['unblockcontact'] = 'Unblock contact';
$string['unblockuser'] = 'Unblock user';
@@ -283,3 +284,6 @@ $string['yourcontactrequestpending'] = 'Your contact request is pending with {$a
// Deprecated since Moodle 5.0.
$string['togglenotificationmenu'] = 'Toggle notifications menu';
// Deprecated since Moodle 5.1.
$string['unabletomessage'] = 'You are unable to message this user';
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -362,6 +362,7 @@ function(
newState = StateManager.setIsMuted(newState, conversation.ismuted);
newState = StateManager.addMessages(newState, conversation.messages);
newState = StateManager.setCanDeleteMessagesForAllUsers(newState, conversation.candeletemessagesforallusers);
newState = StateManager.setCanSendMessageToConversation(newState, conversation.cansendmessagetoconversation);
return newState;
};
@@ -62,7 +62,6 @@ define([], function() {
CONTENT_MESSAGES_FOOTER_EDIT_MODE_CONTAINER: '[data-region="content-messages-footer-edit-mode-container"]',
CONTENT_MESSAGES_FOOTER_REQUIRE_CONTACT_CONTAINER: '[data-region="content-messages-footer-require-contact-container"]',
CONTENT_MESSAGES_FOOTER_REQUIRE_UNBLOCK_CONTAINER: '[data-region="content-messages-footer-require-unblock-container"]',
CONTENT_MESSAGES_FOOTER_UNABLE_TO_MESSAGE_CONTAINER: '[data-region="content-messages-footer-unable-to-message"]',
DAY_MESSAGES_CONTAINER: '[data-region="day-messages-container"]',
DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE: '[data-region="delete-messages-for-all-users-toggle"]',
DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER: '[data-region="delete-messages-for-all-users-toggle-container"]',
@@ -97,7 +96,9 @@ define([], function() {
TEXT_CONTAINER: '[data-region="text-container"]',
TIME_CREATED: '[data-region="time-created"]',
TITLE: '[data-region="title"]',
TOGGLE_EMOJI_PICKER_BUTTON: '[data-action="toggle-emoji-picker"]'
TOGGLE_EMOJI_PICKER_BUTTON: '[data-action="toggle-emoji-picker"]',
DAY_MESSAGE_UNABLE_TO_SEND_CONTAINER: '[data-region="day-message-unable-to-send-container"]',
UNABLE_TO_MESSAGE_CONTAINER: '[data-region="unable-to-send-container"]',
};
var TEMPLATES = {
@@ -374,7 +374,8 @@ function(
// Handle adding or removing whole days.
days: buildDaysPatch(current, daysDiff.missingFromB, daysDiff.missingFromA),
// Handle updating messages that don't require adding/removing a whole day.
messages: buildMessagesPatch(daysDiff.matches)
messages: buildMessagesPatch(daysDiff.matches),
unableToMessage: buildUnableToMessagePatch(state, newState),
};
} else {
return null;
@@ -1125,30 +1126,30 @@ function(
*
* @param {Object} state The current state.
* @param {Object} newState The new state.
* @return {Bool|Null}
* @return {Bool}
*/
var buildUnableToMessage = function(state, newState) {
var oldOtherUser = getOtherUserFromState(state);
var newOtherUser = getOtherUserFromState(newState);
const buildUnableToMessagePatch = (state, newState) => {
if (newState.type == Constants.CONVERSATION_TYPES.SELF) {
// Users always can send message themselves on self-conversations.
return null;
return false;
}
const oldOtherUser = getOtherUserFromState(state);
const newOtherUser = getOtherUserFromState(newState);
if (!oldOtherUser && !newOtherUser) {
return null;
return false;
} else if (oldOtherUser && !newOtherUser) {
return oldOtherUser.canmessage ? null : true;
return !oldOtherUser.canmessage;
} else if (!oldOtherUser && newOtherUser) {
return newOtherUser.canmessage ? null : true;
return !newOtherUser.canmessage;
} else if (!oldOtherUser.canmessage && newOtherUser.canmessage) {
return false;
} else if (oldOtherUser.canmessage && !newOtherUser.canmessage) {
return true;
}
return null;
return !newState.canSendMessageToConversation;
};
/**
@@ -1163,7 +1164,6 @@ function(
var inEditMode = buildInEditMode(state, newState);
var requireAddContact = buildRequireAddContact(state, newState);
var requireUnblock = buildRequireUnblock(state, newState);
var unableToMessage = buildUnableToMessage(state, newState);
var showRequireAddContact = requireAddContact !== null ? requireAddContact.show && requireAddContact.hasMessages : null;
var otherUser = getOtherUserFromState(newState);
var generateReturnValue = function(checkValue, successReturn) {
@@ -1179,8 +1179,6 @@ function(
type: 'add-contact',
user: otherUser
};
} else if (!otherUser.canmessage && (otherUser.requirescontact && !otherUser.iscontact)) {
return {type: 'unable-to-message'};
}
}
@@ -1199,7 +1197,6 @@ function(
var checks = [
[loadingFirstMessages, {type: 'placeholder'}],
[inEditMode, {type: 'edit-mode'}],
[unableToMessage, {type: 'unable-to-message'}],
[requireUnblock, {type: 'unblock'}],
[showRequireAddContact, {type: 'add-contact', user: otherUser}]
];
@@ -1365,7 +1362,8 @@ function(
isFavourite: buildIsFavourite,
isMuted: buildIsMuted,
showEmojiPicker: buildShowEmojiPicker,
showEmojiAutoComplete: buildShowEmojiAutoComplete
showEmojiAutoComplete: buildShowEmojiAutoComplete,
unableToMessage: buildUnableToMessagePatch
}
};
// These build functions are only applicable to private conversations.
@@ -89,6 +89,46 @@ function(
return body.find(SELECTORS.SELF_CONVERSATION_MESSAGE_CONTAINER);
};
/**
* Get the unable to message container element.
*
* @param {Object} body Conversation body container element.
* @return {Object} The unable to message container element.
*/
const getUnableToMessageContainer = (body) => {
return body.find(SELECTORS.UNABLE_TO_MESSAGE_CONTAINER);
};
/**
* Get the message text area element.
*
* @param {Object} footer Conversation footer container element.
* @return {Object} The message text area element.
*/
const getMessageTextArea = (footer) => {
return footer.find(SELECTORS.MESSAGE_TEXT_AREA);
};
/**
* Get the send message button element.
*
* @param {Object} footer Conversation footer container element.
* @return {Object} The send message button element.
*/
const getSendMessageButton = (footer) => {
return footer.find(SELECTORS.SEND_MESSAGE_BUTTON);
};
/**
* Get the emoji picker button element.
*
* @param {Object} footer Conversation footer container element.
* @return {Object} The emoji picker button element.
*/
const getEmojiPickerButton = (footer) => {
return footer.find(SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON);
};
/**
* Hide the self-conversation message container element.
*
@@ -259,33 +299,6 @@ function(
getFooterRequireUnblockContainer(footer).addClass('hidden');
};
/**
* Get the footer Unable to message contact container element.
*
* @param {Object} footer Conversation footer container element.
* @return {Object} The footer Unable to message contact container element.
*/
var getFooterUnableToMessageContainer = function(footer) {
return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_UNABLE_TO_MESSAGE_CONTAINER);
};
/**
* Show the footer Unable to message contact container element.
*
* @param {Object} footer Conversation footer container element.
*/
var showFooterUnableToMessage = function(footer) {
getFooterUnableToMessageContainer(footer).removeClass('hidden');
};
/**
* Hide the footer Unable to message contact container element.
*
* @param {Object} footer Conversation footer container element.
*/
var hideFooterUnableToMessage = function(footer) {
getFooterUnableToMessageContainer(footer).addClass('hidden');
};
/**
* Hide all header elements.
@@ -309,7 +322,6 @@ function(
hideFooterPlaceholder(footer);
hideFooterRequireContact(footer);
hideFooterRequireUnblock(footer);
hideFooterUnableToMessage(footer);
};
/**
@@ -368,6 +380,111 @@ function(
getHeaderContent(header).addClass('hidden');
};
/**
* Show the unable to message container.
*
* @param {Object} body Conversation body container element.
*/
const showUnableToMessage = (body) => {
getUnableToMessageContainer(body).removeClass('hidden');
};
/**
* Hide the unable to message container.
*
* @param {Object} body Conversation body container element.
*/
const hideUnableToMessage = (body) => {
getUnableToMessageContainer(body).addClass('hidden');
};
/**
* Enable the emoji picker button.
*
* @param {Object} footer Conversation footer container element.
*/
const enableEmojiPickerButton = (footer) => {
getEmojiPickerButton(footer).prop('disabled', false);
};
/**
* Enable the send message button.
*
* @param {Object} footer Conversation footer container element.
*/
const enableSendMessageButton = (footer) => {
getSendMessageButton(footer).prop('disabled', false);
};
/**
* Enable the message text area.
*
* @param {Object} footer Conversation footer container element.
*/
const enableMessageTextArea = (footer) => {
const messageTextArea = getMessageTextArea(footer);
messageTextArea.prop('disabled', false);
Aria.unhide(messageTextArea.get());
Str.get_string('writeamessage', 'core_message').done(function(string) {
if (!messageTextArea.prop('disabled')) {
messageTextArea.prop('placeholder', string);
}
});
};
/**
* Enable all footer content elements.
*
* @param {Object} footer Conversation footer container element.
*/
const enableAllFooterContentElements = (footer) => {
enableEmojiPickerButton(footer);
enableSendMessageButton(footer);
enableMessageTextArea(footer);
};
/**
* Disable the emoji picker button.
*
* @param {Object} footer Conversation footer container element.
*/
const disableEmojiPickerButton = (footer) => {
getEmojiPickerButton(footer).prop('disabled', true);
};
/**
* Disable the send message button.
*
* @param {Object} footer Conversation footer container element.
*/
const disableSendMessageButton = (footer) => {
getSendMessageButton(footer).prop('disabled', true);
};
/**
* Disable the message text area.
*
* @param {Object} footer Conversation footer container element.
*/
const disableMessageTextArea = (footer) => {
const messageTextArea = getMessageTextArea(footer);
messageTextArea.prop('disabled', true);
messageTextArea.removeAttr('placeholder');
Aria.unhide(messageTextArea.get());
};
/**
* Disable all footer content elements.
*
* @param {Object} footer Conversation footer container element.
*/
const disableAllFooterContentElements = (footer) => {
disableEmojiPickerButton(footer);
disableSendMessageButton(footer);
disableMessageTextArea(footer);
};
/**
* Get the header edit mode container element.
*
@@ -572,9 +689,10 @@ function(
* @param {Object} footer The footer container element.
* @param {Array} days Array of days containing messages.
* @param {Object} datesCache Cache timestamps and their formatted date string.
* @param {Boolean} isAbleToMessage Whether the user can send a message to the conversation.
* @return {Promise} Days rendering promises.
*/
var renderAddDays = function(header, body, footer, days, datesCache) {
var renderAddDays = function(header, body, footer, days, datesCache, isAbleToMessage) {
var messagesContainer = getMessagesContainer(body);
var daysRenderPromises = days.map(function(data) {
var timestampDate = new Date(data.value.timestamp * 1000);
@@ -589,6 +707,7 @@ function(
// Wait until all of the rendering is done for each of the days
// to ensure they are added to the page in the correct order.
days.forEach(function(data, index) {
// eslint-disable-next-line promise/no-nesting
daysRenderPromises[index]
.then(function(html) {
if (data.before) {
@@ -598,6 +717,12 @@ function(
return messagesContainer.append(html);
}
})
.always(function() {
const showUnableToMessage = !isAbleToMessage && index === days.length - 1;
messagesContainer.find(SELECTORS.DAY_MESSAGE_UNABLE_TO_SEND_CONTAINER)
.last().toggleClass('hidden', !showUnableToMessage);
hideUnableToMessage(body);
})
.catch(function() {
// Fail silently.
});
@@ -823,7 +948,7 @@ function(
if (hasAddDays) {
renderingPromises.push(datesCachePromise.then(function(datesCache) {
return renderAddDays(header, body, footer, data.days.add, datesCache);
return renderAddDays(header, body, footer, data.days.add, datesCache, !data.unableToMessage);
}));
}
@@ -919,8 +1044,6 @@ function(
return showFooterContent(footer);
case 'unblock':
return showFooterRequireUnblock(footer);
case 'unable-to-message':
return showFooterUnableToMessage(footer);
}
return true;
@@ -1644,6 +1767,29 @@ function(
return true;
};
/**
* Show or hide the unable to message container and enable or disable the footer messaging controls.
*
* @param {Object} header The header container element.
* @param {Object} body The body container element.
* @param {Object} footer The footer container element.
* @param {Object} disable should the message be displayed?.
* @return {Object|true} jQuery promise
*/
var renderUnableToMessage = function(header, body, footer, disable) {
if (disable) {
disableAllFooterContentElements(footer);
if (!body.find(SELECTORS.DAY_MESSAGE_UNABLE_TO_SEND_CONTAINER).length) {
showUnableToMessage(body);
}
} else {
enableAllFooterContentElements(footer);
hideUnableToMessage(body);
}
return true;
};
/**
* Show or hide the require add contact panel.
*
@@ -1684,6 +1830,8 @@ function(
showHeaderPlaceholder(header);
hideAllFooterElements(footer);
showFooterPlaceholder(footer);
enableAllFooterContentElements(footer);
hideUnableToMessage(body);
return true;
};
@@ -1708,7 +1856,8 @@ function(
confirmContactRequest: renderConfirmContactRequest,
requireAddContact: renderRequireAddContact,
selfConversationMessage: renderSelfConversationMessage,
contactRequestSent: renderContactRequestSent
contactRequestSent: renderContactRequestSent,
unableToMessage: renderUnableToMessage,
},
{
loadingMembers: renderLoadingMembers,
@@ -147,7 +147,8 @@ define(['jquery'], function($) {
pendingDeleteConversation: false,
selectedMessageIds: [],
showEmojiAutoComplete: false,
showEmojiPicker: false
showEmojiPicker: false,
canSendMessageToConversation: true,
};
};
@@ -836,6 +837,19 @@ define(['jquery'], function($) {
return newState;
};
/**
* Set whether the user can send messages to the conversation.
*
* @param {Object} state Current state.
* @param {Bool} value If it can send message to conversation.
* @return {Object} New state.
*/
const setCanSendMessageToConversation = function(state, value) {
const newState = cloneState(state);
newState.canSendMessageToConversation = value;
return newState;
};
return {
buildInitialState: buildInitialState,
addMessages: addMessages,
@@ -877,6 +891,7 @@ define(['jquery'], function($) {
removeSelectedMessagesById: removeSelectedMessagesById,
markMessagesAsRead: markMessagesAsRead,
addContactRequests: addContactRequests,
removeContactRequests: removeContactRequests
removeContactRequests: removeContactRequests,
setCanSendMessageToConversation: setCanSendMessageToConversation,
};
});
+3 -1
View File
@@ -771,6 +771,7 @@ class api {
$conv->unreadcount = isset($unreadcounts[$conv->id]) ? $unreadcounts[$conv->id]->unreadcount : null;
$conv->ismuted = $conversation->ismuted ? true : false;
$conv->members = $members[$conv->id];
$conv->cansendmessagetoconversation = self::can_send_message_to_conversation($userid, $conv->id);
// Add the most recent message information.
$conv->messages = [];
@@ -956,7 +957,8 @@ class api {
'ismuted' => $ismuted,
'members' => $members,
'messages' => $messages['messages'],
'candeletemessagesforallusers' => has_capability('moodle/site:deleteanymessage', $deleteallcontext)
'candeletemessagesforallusers' => has_capability('moodle/site:deleteanymessage', $deleteallcontext),
'cansendmessagetoconversation' => self::can_send_message_to_conversation($userid, $conversation->id),
];
}
+2
View File
@@ -1043,6 +1043,8 @@ class core_message_external extends external_api {
),
'candeletemessagesforallusers' => new external_value(PARAM_BOOL,
'If the user can delete messages in the conversation for all users', VALUE_DEFAULT, false),
'cansendmessagetoconversation' => new external_value(PARAM_BOOL,
'If the user can send messages in the conversation', VALUE_DEFAULT, true),
)
);
}
@@ -54,7 +54,11 @@
<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="fst-italic fw-light" data-region="text">{{#str}} selfconversationdefaultmessage, core_message {{/str}}</p>
</div>
</div>
<div class="p-3 text-center hidden" data-region="unable-to-send-container">
<p class="m-0">{{#str}} cannotsendmessages, core_message {{/str}}</p>
<p class="fst-italic fw-light" data-region="text">{{#str}} nopermissiontosend, core_message {{/str}}</p>
</div>
<div class="hidden text-center p-3" data-region="more-messages-loading-icon-container">{{> core/loading }}</div>
</div>
<div class="p-4 w-100 h-100 hidden position-absolute z-index-1" data-region="confirm-dialogue-container" style="top: 0; background: rgba(0,0,0,0.3);">
@@ -38,4 +38,8 @@
{{#messages}}
{{> core_message/message_drawer_view_conversation_body_message }}
{{/messages}}
<div class="p-3 text-center hidden" data-region="day-message-unable-to-send-container">
<p class="m-0">{{#str}} cannotsendmessages, core_message {{/str}}</p>
<p class="fst-italic fw-light" data-region="text">{{#str}} nopermissiontosend, core_message {{/str}}</p>
</div>
</div>
@@ -53,9 +53,6 @@
<div class="hidden bg-secondary p-sm-3" data-region="content-messages-footer-require-unblock-container">
{{> core_message/message_drawer_view_conversation_footer_require_unblock }}
</div>
<div class="hidden bg-secondary p-sm-3" data-region="content-messages-footer-unable-to-message">
{{> core_message/message_drawer_view_conversation_footer_unable_to_message }}
</div>
<div class="p-sm-2" data-region="placeholder-container">
{{> core_message/message_drawer_view_conversation_footer_placeholder }}
</div>
@@ -1,40 +0,0 @@
{{!
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_footer_unable_to_message
This template will render the footer content when the user is unable to message
in a conversation in the message drawer.
Classes required for JS:
* none
Data attributes required for JS:
* All data attributes are required
Context variables required for this template:
* userid The logged in user id
* urls The URLs for the popover
Example context (json):
{}
}}
<div class="p-3 bg-white">
<p class="text-muted" data-region="text">{{#str}} unabletomessage, core_message {{/str}}</p>
</div>
@@ -33,7 +33,7 @@ Feature: To be able to block users that we are able to or to see a message if we
When I log in as "student2"
And I open messaging
And I select "Student 1" user in messaging
Then I should see "You are unable to message this user"
Then I should see "Cannot send message"
Scenario: Unable to block a user
Given I log in as "student1"
@@ -58,7 +58,7 @@ Feature: To be able to block users that we are able to or to see a message if we
And I log out
And I log in as "student2"
And I select "Student 1" user in messaging
And I should not see "You are unable to message this user"
And I should not see "Cannot send message"
And I log out
And I log in as "student1"
And I select "Student 2" user in messaging
@@ -12,10 +12,12 @@ Feature: Message send messages
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| teacher1 | C1 | editingteacher |
And the following "groups" exist:
| name | course | idnumber | enablemessaging |
| Group 1 | C1 | G1 | 1 |
@@ -96,3 +98,19 @@ Feature: Message send messages
Then I should see "You have an unsent message. It will be lost if you leave this page."
And I press "Send message"
And I should see "What you doing?" in the "Student 2" "core_message > Message conversation"
Scenario: Student cannot reply to a message from a teacher not in their course
Given I log in as "teacher1"
And I open messaging
And I send "Hi!" message to "Student 1" user
And I am on "C1" course homepage
And I navigate to course participants
And I click on "Unenrol" "link" in the "student1" "table_row"
And I click on "Unenrol" "button" in the "Unenrol" "dialogue"
And I log in as "student1"
When I open messaging
And I select "Teacher 1" conversation in messaging
Then I should see "Cannot send message"
And the "disabled" attribute of "textarea[data-region='send-message-txt']" "css_element" should contain "true"
And the "disabled" attribute of "button[data-action='send-message']" "css_element" should contain "true"
And the "disabled" attribute of "button[data-action='toggle-emoji-picker']" "css_element" should contain "true"
+39
View File
@@ -5920,4 +5920,43 @@ final class externallib_test extends externallib_advanced_testcase {
$result = external_api::clean_returnvalue(external\get_unsent_message::execute_returns(), $result);
$this->assertEmpty($result);
}
/**
* Tests conversation messaging is restricted by each users messaging permission.
*
* @covers ::get_conversation
*/
public function test_get_conversation_send_message_permission(): void {
$this->resetAfterTest();
// Create some users.
$user1 = self::getDataGenerator()->create_user();
$user2 = self::getDataGenerator()->create_user();
$user3 = self::getDataGenerator()->create_user();
// Create a course and enrol user1 and user2.
$course1 = $this->getDataGenerator()->create_course();
$this->setAdminUser();
$this->getDataGenerator()->enrol_user($user1->id, $course1->id);
$this->getDataGenerator()->enrol_user($user2->id, $course1->id);
// Conversation between two enrolled users.
$conversation1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
[$user1->id, $user2->id]);
// Conversation involving a non-enrolled user.
$conversation2 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
[$user1->id, $user3->id]);
// User2 should be able to send a message to user1.
$this->setUser($user2);
$conv = core_message_external::get_conversation($user2->id, $conversation1->id);
$conv = external_api::clean_returnvalue(core_message_external::get_conversation_returns(), $conv);
$this->assertTrue($conv['cansendmessagetoconversation']);
// User3 should not be able to send a message to user1.
$this->setUser($user3);
$conv = core_message_external::get_conversation($user3->id, $conversation2->id);
$conv = external_api::clean_returnvalue(core_message_external::get_conversation_returns(), $conv);
$this->assertFalse($conv['cansendmessagetoconversation']);
}
}