MDL-63724 core_message: add conversation support to message_send()

Added a test confirming that processors are called for each member
and that a single event is fired.
This commit is contained in:
Jake Dallimore 2018-11-08 18:07:00 +08:00
parent 9581bc3c00
commit 0eddf920be
2 changed files with 275 additions and 9 deletions

View File

@ -55,7 +55,7 @@ require_once(__DIR__ . '/../message/lib.php');
* @return mixed the integer ID of the new message or false if there was a problem with submitted data
*/
function message_send(\core\message\message $eventdata) {
global $CFG, $DB;
global $CFG, $DB, $SITE;
//new message ID to return
$messageid = false;
@ -74,6 +74,89 @@ function message_send(\core\message\message $eventdata) {
$eventdata->notification = 1;
}
// This is a message directed to a conversation, not a specific user as was the way in legacy messaging .
// We must call send_message_to_conversation(), which handles per-member processor iteration and triggers
// a per-conversation event.
if (!$eventdata->notification && $eventdata->convid) {
if (!is_object($eventdata->userfrom)) {
$eventdata->userfrom = core_user::get_user($eventdata->userfrom);
}
if (!$eventdata->userfrom) {
debugging('Attempt to send msg from unknown user', DEBUG_NORMAL);
return false;
}
// Only one message will be saved to the DB.
$conversationid = $eventdata->convid;
$table = 'messages';
$tabledata = new stdClass();
$tabledata->courseid = $eventdata->courseid;
$tabledata->useridfrom = $eventdata->userfrom->id;
$tabledata->conversationid = $conversationid;
$tabledata->subject = $eventdata->subject;
$tabledata->fullmessage = $eventdata->fullmessage;
$tabledata->fullmessageformat = $eventdata->fullmessageformat;
$tabledata->fullmessagehtml = $eventdata->fullmessagehtml;
$tabledata->smallmessage = $eventdata->smallmessage;
$tabledata->timecreated = time();
if (PHPUNIT_TEST and class_exists('phpunit_util')) {
// Add some more tests to make sure the normal code can actually work.
$componentdir = core_component::get_component_directory($eventdata->component);
if (!$componentdir or !is_dir($componentdir)) {
throw new coding_exception('Invalid component specified in message-send(): '.$eventdata->component);
}
if (!file_exists("$componentdir/db/messages.php")) {
throw new coding_exception("$eventdata->component does not contain db/messages.php necessary for message_send()");
}
$messageproviders = null;
include("$componentdir/db/messages.php");
if (!isset($messageproviders[$eventdata->name])) {
$errormsg = "Missing messaging defaults for event '$eventdata->name' in '$eventdata->component' messages.php file";
throw new coding_exception($errormsg);
}
unset($componentdir);
unset($messageproviders);
// Now ask phpunit if it wants to catch this message.
if (phpunit_util::is_redirecting_messages()) {
$messageid = $DB->insert_record($table, $tabledata);
$message = $DB->get_record($table, array('id' => $messageid));
// Mark the message as read for each of the other users.
$sql = "SELECT u.*
FROM {message_conversation_members} mcm
JOIN {user} u
ON (mcm.conversationid = :convid AND u.id = mcm.userid AND u.id != :userid)";
$otherusers = $DB->get_records_sql($sql, ['convid' => $eventdata->convid, 'userid' => $eventdata->userfrom->id]);
foreach ($otherusers as $othermember) {
\core_message\api::mark_message_as_read($othermember->id, $message);
}
// Unit tests need this detail.
$message->notification = $eventdata->notification;
phpunit_util::message_sent($message);
return $messageid;
}
}
// Cache messages.
if (!empty($eventdata->convid)) {
// Cache the timecreated value of the last message in this conversation.
$cache = cache::make('core', 'message_time_last_message_between_users');
$key = \core_message\helper::get_last_message_time_created_cache_key($eventdata->convid);
$cache->set($key, $tabledata->timecreated);
}
// Store unread message just in case we get a fatal error any time later.
$tabledata->id = $DB->insert_record($table, $tabledata);
$eventdata->savedmessageid = $tabledata->id;
return \core\message\manager::send_message_to_conversation($eventdata, $tabledata);
}
// Notifications and legacy messaging code:
// Most of the next steps are shared by both the legacy message code (those being sent to a single 'userto', not to a
// conversation), and for notifications. Any message-specific or notification-specific steps are clearly marked.
if (!is_object($eventdata->userto)) {
$eventdata->userto = core_user::get_user($eventdata->userto);
}
@ -290,14 +373,13 @@ function message_send(\core\message\message $eventdata) {
}
}
// Only cache messages, not notifications.
if (!$eventdata->notification) {
if (!empty($eventdata->convid)) {
// Cache the timecreated value of the last message in this conversation.
$cache = cache::make('core', 'message_time_last_message_between_users');
$key = \core_message\helper::get_last_message_time_created_cache_key($eventdata->convid);
$cache->set($key, $tabledata->timecreated);
}
// We have either created or derived a conversationid, which we can use to
// update the 'message_time_last_message_between_users' cache.
if (!empty($tabledata->conversationid)) {
// Cache the timecreated value of the last message in this conversation.
$cache = cache::make('core', 'message_time_last_message_between_users');
$key = \core_message\helper::get_last_message_time_created_cache_key($tabledata->conversationid);
$cache->set($key, $tabledata->timecreated);
}
// Store unread message just in case we get a fatal error any time later.

View File

@ -732,6 +732,190 @@ class core_messagelib_testcase extends advanced_testcase {
$sink->clear();
}
/**
* Tests calling message_send() with $eventdata representing a message to an individual conversation.
*
* This test will verify:
* - that the 'messages' record is created.
* - that the processors will be called for each conversation member, except the sender.
* - 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_conversation_individual() {
global $DB;
$this->preventResetByRollback();
$this->resetAfterTest();
// Create some users and a conversation between them.
$user1 = $this->getDataGenerator()->create_user(array('maildisplay' => 1));
$user2 = $this->getDataGenerator()->create_user();
set_config('allowedemaildomains', 'example.com');
$conversation = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
[$user1->id, $user2->id], '1:1 project discussion');
// 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', $user2);
// Now, send a message and verify the message processors (in this case, email) are hit.
$sink = $this->redirectEmails();
$messageid = message_send($message);
$emails = $sink->get_messages();
$this->assertCount(1, $emails);
$email = reset($emails);
// Verify the record was created in 'messages'.
$recordexists = $DB->record_exists('messages', ['id' => $messageid]);
$this->assertTrue($recordexists);
// Verify the email information.
$this->assertSame($user1->email, $email->from);
$this->assertSame($user2->email, $email->to);
// The message subject is generated during the call for conversation messages,
// as the conversation may have many members having different lang preferences.
$this->assertSame(get_string('unreadnewmessage', 'message', fullname($user1)), $email->subject);
// The email content will have had an emailtagline appended to it, based on lang prefs,
// so verify the expected beginning and ends.
$this->assertNotEmpty($email->header);
$this->assertNotEmpty($email->body);
$this->assertRegExp('/test message body.*test/s', $email->body);
$sink->clear();
// Now, send the message again, and verify that the event fired includes the courseid and conversationid.
$eventsink = $this->redirectEvents();
$messageid = message_send($message);
$events = $eventsink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf(\core\event\message_sent::class, $event);
$this->assertEquals($user1->id, $event->userid);
$this->assertEquals($user2->id, $event->relateduserid);
$this->assertEquals($message->courseid, $event->other['courseid']);
$eventsink->clear();
$sink->clear();
}
/**
* Tests calling message_send() with $eventdata representing a message to an group conversation.
*
* This test will verify:
* - that the 'messages' record is created.
* - that the processors will be called for each conversation member, except the sender.
* - the a single event will be generated - 'group_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_conversation_group() {
global $DB, $CFG, $SITE;
$this->preventResetByRollback();
$this->resetAfterTest();
// Create some users and a conversation between them.
$user1 = $this->getDataGenerator()->create_user(array('maildisplay' => 1));
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
set_config('allowedemaildomains', 'example.com');
$conversation = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
[$user1->id, $user2->id, $user3->id], 'Group project discussion');
// 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 the recipient users.
$DB->set_field_select('message_processors', 'enabled', 0, "name <> 'email'");
set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user2);
set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user3);
// Now, send a message and verify the message processors (in this case, email) are hit.
$sink = $this->redirectEmails();
$messageid = message_send($message);
$emails = $sink->get_messages();
$this->assertCount(2, $emails);
// Verify the record was created in 'messages'.
$recordexists = $DB->record_exists('messages', ['id' => $messageid]);
$this->assertTrue($recordexists);
// Verify the email information. Ordering is not guaranteed.
$members = [$user2->email => '', $user3->email => ''];
$email = $emails[0];
$this->assertSame($user1->email, $email->from);
$this->assertArrayHasKey($email->to, $members);
unset($members[$email->to]);
$email = $emails[1];
$this->assertSame($user1->email, $email->from);
$this->assertArrayHasKey($email->to, $members);
unset($members[$email->to]);
// The message subject is generated during the call for conversation messages,
// as the conversation may have many members having different lang preferences.
$tmp = (object) ['name' => fullname($user1), 'conversationname' => $conversation->name];
$this->assertSame(get_string('unreadnewgroupconversationmessage', 'message', $tmp), $email->subject);
// The email content will have had an emailtagline appended to it, based on lang prefs,
// so verify the expected beginning and ends.
$this->assertNotEmpty($email->header);
$this->assertNotEmpty($email->body);
$this->assertRegExp('/test message body.*test/s', $email->body);
$sink->clear();
// Now, send the message again, and verify that the event fired includes the courseid and conversationid.
$eventsink = $this->redirectEvents();
$messageid = message_send($message);
$events = $eventsink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf(\core\event\group_message_sent::class, $event);
$this->assertEquals($user1->id, $event->userid);
$this->assertNull($event->relateduserid);
$this->assertEquals($message->courseid, $event->other['courseid']);
$this->assertEquals($message->convid, $event->other['conversationid']);
$eventsink->clear();
$sink->clear();
}
public function test_rollback() {
global $DB;