Skip to content

Commit

Permalink
MDL-63724 core_message: add send_message_to_conversation() to manager
Browse files Browse the repository at this point in the history
  • Loading branch information
snake committed Nov 12, 2018
1 parent aa083ab commit 9581bc3
Show file tree
Hide file tree
Showing 2 changed files with 221 additions and 1 deletion.
3 changes: 2 additions & 1 deletion lang/en/message.php
Expand Up @@ -186,6 +186,7 @@
$string['unblockcontact'] = 'Unblock contact';
$string['unknownuser'] = 'Unknown user';
$string['unreadnotification'] = 'Unread notification: {$a}';
$string['unreadnewgroupconversationmessage'] = 'New message from {$a->name} in {$a->conversationname}';
$string['unreadnewmessage'] = 'New message from {$a}';
$string['usercantbemessaged'] = 'You can\'t message {$a} due to their message preferences. Try adding them as a contact.';
$string['viewfullnotification'] = 'View full notification';
Expand All @@ -201,4 +202,4 @@
$string['eventmessagecontactunblocked'] = 'Message contact unblocked';
$string['messagingdisabled'] = 'Messaging is disabled on this site, emails will be sent instead';
$string['userisblockingyou'] = 'This user has blocked you from sending messages to them.';
$string['userisblockingyounoncontact'] = '{$a} only accepts messages from their contacts.';
$string['userisblockingyounoncontact'] = '{$a} only accepts messages from their contacts.';
219 changes: 219 additions & 0 deletions lib/classes/message/manager.php
Expand Up @@ -45,6 +45,209 @@ class manager {
/** @var array buffer of pending messages */
protected static $buffer = array();

/**
* Used for calling processors, and generating event data when sending a message to a conversation.
*
* This is ONLY used for messages of type 'message' (notification=0), and is responsible for:
*
* 1. generation of per-user event data (to pass to processors)
* 2. generation of the processors for each recipient member of the conversation
* 3. calling said processors for each member, passing in the per-user (local) eventdata.
* 4. generation of an appropriate event for the message send, depending on the conversation type
* - messages to individual conversations generate a 'message_sent' event (as per legacy send_message())
* - messages to group conversations generate a 'group_message_sent' event.
*
* @param message $eventdata
* @param \stdClass $savemessage
* @return int
*/
public static function send_message_to_conversation(message $eventdata, \stdClass $savemessage) : int {
global $DB, $CFG, $SITE;

if (empty($eventdata->convid)) {
throw new \moodle_exception("Message is not being sent to a conversation. Please check event data.");
}

// Fetch default (site) preferences.
$defaultpreferences = get_message_output_default_preferences();
$preferencebase = $eventdata->component.'_'.$eventdata->name;

// Because we're dealing with multiple recipients, we need to send a localised (per-user) version of the eventdata to each
// processor, because of things like the language-specific subject. We're going to modify this, for each recipient member.
// Any time we're modifying the event data here, we should be using the localised version.
// This localised version is based on the generic event data, but we should keep that object intact, so we clone it.
$localisedeventdata = clone $eventdata;

// Get user records for all members of the conversation.
$sql = "SELECT u.*
FROM {message_conversation_members} mcm
JOIN {user} u
ON (mcm.conversationid = :convid AND u.id = mcm.userid)
ORDER BY id desc";
$members = $DB->get_records_sql($sql, ['convid' => $eventdata->convid]);
if (empty($members)) {
throw new \moodle_exception("Conversation has no members or does not exist.");
}

if (!is_object($localisedeventdata->userfrom)) {
$localisedeventdata->userfrom = $members[$localisedeventdata->userfrom];
}

// This should now hold only the other users (recipients).
unset($members[$localisedeventdata->userfrom->id]);
$otherusers = $members;

// 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');

// 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
// in that object instead of using one fetched from the DB.
$legacymessage = false;
if ($conv->type == \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) {
if (isset($eventdata->userto)) {
$legacymessage = true;
} else {
$otheruser = reset($otherusers);
$eventdata->userto = $otheruser;
}
}

// Fetch enabled processors.
// If we are dealing with a message some processors may want to handle it regardless of user and site settings.
$processors = array_filter(get_message_processors(false), function($processor) {
if ($processor->object->force_process_messages()) {
return true;
}

return ($processor->enabled && $processor->configured);
});

// For each member of the conversation, other than the sender:
// 1. Set recipient specific event data (language specific, user prefs, etc)
// 2. Generate recipient specific processor list
// 3. Call send_message() to pass the message to processors and generate the relevant per-user events.
$eventprocmaps = []; // Init the event/processors buffer.
foreach ($otherusers as $recipient) {
// If this message was a legacy (1:1) message, then we use the userto.
if ($legacymessage) {
$recipient = $eventdata->userto;
}

$usertoisrealuser = (\core_user::is_real_user($recipient->id) != false);

// Using string manager directly so that strings in the message will be in the message recipients language rather than
// the sender's.
if ($conv->type == \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) {
$localisedeventdata->subject = get_string_manager()->get_string('unreadnewmessage', 'message',
fullname($localisedeventdata->userfrom), $recipient->lang);
} else if ($conv->type == \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP) {
$stringdata = (object) ['name' => fullname($localisedeventdata->userfrom), 'conversationname' => $conv->name];
$localisedeventdata->subject = get_string_manager()->get_string('unreadnewgroupconversationmessage', 'message',
$stringdata, $recipient->lang);
}

// Spoof the userto based on the current member id.
$localisedeventdata->userto = $recipient;

$s = new \stdClass();
$s->sitename = format_string($SITE->shortname, true, array('context' => \context_course::instance(SITEID)));
// When the new interface lands, the URL may be reintroduced, but for now it isn't supported, so just hit the index.
$s->url = $CFG->wwwroot.'/message/index.php';
$emailtagline = get_string_manager()->get_string('emailtagline', 'message', $s, $recipient->lang);

$localisedeventdata->fullmessage = $eventdata->fullmessage;
$localisedeventdata->fullmessagehtml = $eventdata->fullmessagehtml;
if (!empty($localisedeventdata->fullmessage)) {
$localisedeventdata->fullmessage .= "\n\n---------------------------------------------------------------------\n"
. $emailtagline;
}
if (!empty($localisedeventdata->fullmessagehtml)) {
$localisedeventdata->fullmessagehtml .=
"<br><br>---------------------------------------------------------------------<br>" . $emailtagline;
}

// If recipient is internal user (noreply user), and emailstop is set then don't send any msg.
if (!$usertoisrealuser && !empty($recipient->emailstop)) {
debugging('Attempt to send msg to internal (noreply) user', DEBUG_NORMAL);
return false;
}

// Set the online state.
if (isset($CFG->block_online_users_timetosee)) {
$timetoshowusers = $CFG->block_online_users_timetosee * 60;
} else {
$timetoshowusers = 300;
}

// Work out if the user is logged in or not.
$userstate = 'loggedoff';
if (!empty($localisedeventdata->userto->lastaccess)
&& (time() - $timetoshowusers) < $localisedeventdata->userto->lastaccess) {
$userstate = 'loggedin';
}

// Fill in the array of processors to be used based on default and user preferences.
$processorlist = [];
foreach ($processors as $processor) {
// Skip adding processors for internal user, if processor doesn't support sending message to internal user.
if (!$usertoisrealuser && !$processor->object->can_send_to_any_users()) {
continue;
}

// First find out permissions.
$defaultpreference = $processor->name.'_provider_'.$preferencebase.'_permitted';
if (isset($defaultpreferences->{$defaultpreference})) {
$permitted = $defaultpreferences->{$defaultpreference};
} else {
// MDL-25114 They supplied an $eventdata->component $eventdata->name combination which doesn't
// exist in the message_provider table (thus there is no default settings for them).
$preferrormsg = "Could not load preference $defaultpreference. Make sure the component and name you supplied
to message_send() are valid.";
throw new coding_exception($preferrormsg);
}

// Find out if user has configured this output.
// Some processors cannot function without settings from the user.
$userisconfigured = $processor->object->is_user_configured($recipient);

// DEBUG: notify if we are forcing unconfigured output.
if ($permitted == 'forced' && !$userisconfigured) {
debugging('Attempt to force message delivery to user who has "'.$processor->name.'" output unconfigured',
DEBUG_NORMAL);
}

// Populate the list of processors we will be using.
if (!$eventdata->notification && $processor->object->force_process_messages()) {
$processorlist[] = $processor->name;
} else if ($permitted == 'forced' && $userisconfigured) {
// An admin is forcing users to use this message processor. Use this processor unconditionally.
$processorlist[] = $processor->name;
} else if ($permitted == 'permitted' && $userisconfigured && !$recipient->emailstop) {
// User has not disabled notifications.
// See if user set any notification preferences, otherwise use site default ones.
$userpreferencename = 'message_provider_'.$preferencebase.'_'.$userstate;
if ($userpreference = get_user_preferences($userpreferencename, null, $recipient)) {
if (in_array($processor->name, explode(',', $userpreference))) {
$processorlist[] = $processor->name;
}
} else if (isset($defaultpreferences->{$userpreferencename})) {
if (in_array($processor->name, explode(',', $defaultpreferences->{$userpreferencename}))) {
$processorlist[] = $processor->name;
}
}
}
}
// Send the localised eventdata to each processor for the current member.
self::call_processors($localisedeventdata, $processorlist);
}
// Trigger the event using the original message eventdata as events don't need any of the localised information.
self::trigger_message_events($eventdata, $savemessage);

return $savemessage->id;
}

/**
* Do the message sending.
*
Expand Down Expand Up @@ -149,6 +352,7 @@ public static function database_transaction_commited() {
*/
public static function database_transaction_rolledback() {
self::$buffer = array();
self::$convmessagebuffer = array();
}

/**
Expand All @@ -173,6 +377,7 @@ protected static function process_buffer() {
* @throws \coding_exception
*/
protected static function trigger_message_events(message $eventdata, \stdClass $savemessage) {
global $DB;
if ($eventdata->notification) {
\core\event\notification_sent::create_from_ids(
$eventdata->userfrom->id,
Expand All @@ -181,6 +386,20 @@ protected static function trigger_message_events(message $eventdata, \stdClass $
$eventdata->courseid
)->trigger();
} else { // Must be a message.
// If the message is a group conversation, then trigger the 'group_message_sent' event.
if ($eventdata->convid) {
$conv = $DB->get_record('message_conversations', ['id' => $eventdata->convid], 'id, type');
if ($conv->type == \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP) {
\core\event\group_message_sent::create_from_ids(
$eventdata->userfrom->id,
$eventdata->convid,
$savemessage->id,
$eventdata->courseid
)->trigger();
return;
}
// Individual type conversations fall through to the default 'message_sent' event.
}
\core\event\message_sent::create_from_ids(
$eventdata->userfrom->id,
$eventdata->userto->id,
Expand Down

0 comments on commit 9581bc3

Please sign in to comment.