Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Tests for messagelib.php.
  19   *
  20   * @package    core_message
  21   * @category   phpunit
  22   * @copyright  2012 The Open Universtiy
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  class core_messagelib_testcase extends advanced_testcase {
  29  
  30      public function test_message_provider_disabled() {
  31          $this->resetAfterTest();
  32          $this->preventResetByRollback();
  33  
  34          // Disable instantmessage provider.
  35          $disableprovidersetting = 'moodle_instantmessage_disable';
  36          set_config($disableprovidersetting, 1, 'message');
  37          $preferences = get_message_output_default_preferences();
  38          $this->assertTrue($preferences->$disableprovidersetting == 1);
  39  
  40          $message = new \core\message\message();
  41          $message->courseid          = 1;
  42          $message->component         = 'moodle';
  43          $message->name              = 'instantmessage';
  44          $message->userfrom          = get_admin();
  45          $message->userto            = $this->getDataGenerator()->create_user();;
  46          $message->subject           = 'message subject 1';
  47          $message->fullmessage       = 'message body';
  48          $message->fullmessageformat = FORMAT_MARKDOWN;
  49          $message->fullmessagehtml   = '<p>message body</p>';
  50          $message->smallmessage      = 'small message';
  51          $message->notification      = 0;
  52  
  53          // Check message is not sent.
  54          $sink = $this->redirectEmails();
  55          message_send($message);
  56          $emails = $sink->get_messages();
  57          $this->assertEmpty($emails);
  58  
  59          // Check message is sent.
  60          set_config($disableprovidersetting, 0, 'message');
  61          $preferences = get_message_output_default_preferences();
  62          $this->assertTrue($preferences->$disableprovidersetting == 0);
  63  
  64          $sink = $this->redirectEmails();
  65          message_send($message);
  66          $emails = $sink->get_messages();
  67          $email = reset($emails);
  68          $this->assertEquals(get_string('unreadnewmessage', 'message', fullname(get_admin())), $email->subject);
  69      }
  70      public function test_message_get_providers_for_user() {
  71          global $CFG, $DB;
  72  
  73          $this->resetAfterTest();
  74  
  75          $generator = $this->getDataGenerator();
  76  
  77          // Create a course category and course.
  78          $cat = $generator->create_category(array('parent' => 0));
  79          $course = $generator->create_course(array('category' => $cat->id));
  80          $quiz = $generator->create_module('quiz', array('course' => $course->id));
  81          $user = $generator->create_user();
  82  
  83          $coursecontext = context_course::instance($course->id);
  84          $quizcontext = context_module::instance($quiz->cmid);
  85          $frontpagecontext = context_course::instance(SITEID);
  86  
  87          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
  88  
  89          // The user is a student in a course, and has the capability for quiz
  90          // confirmation emails in one quiz in that course.
  91          role_assign($studentrole->id, $user->id, $coursecontext->id);
  92          assign_capability('mod/quiz:emailconfirmsubmission', CAP_ALLOW, $studentrole->id, $quizcontext->id);
  93  
  94          // Give this message type to the front page role.
  95          assign_capability('mod/quiz:emailwarnoverdue', CAP_ALLOW, $CFG->defaultfrontpageroleid, $frontpagecontext->id);
  96  
  97          $providers = message_get_providers_for_user($user->id);
  98          $this->assertTrue($this->message_type_present('mod_forum', 'posts', $providers));
  99          $this->assertTrue($this->message_type_present('mod_quiz', 'confirmation', $providers));
 100          $this->assertTrue($this->message_type_present('mod_quiz', 'attempt_overdue', $providers));
 101          $this->assertFalse($this->message_type_present('mod_quiz', 'submission', $providers));
 102  
 103          // A user is a student in a different course, they should not get confirmation.
 104          $course2 = $generator->create_course(array('category' => $cat->id));
 105          $user2 = $generator->create_user();
 106          $coursecontext2 = context_course::instance($course2->id);
 107          role_assign($studentrole->id, $user2->id, $coursecontext2->id);
 108          accesslib_clear_all_caches_for_unit_testing();
 109          $providers = message_get_providers_for_user($user2->id);
 110          $this->assertTrue($this->message_type_present('mod_forum', 'posts', $providers));
 111          $this->assertFalse($this->message_type_present('mod_quiz', 'confirmation', $providers));
 112  
 113          // Now remove the frontpage role id, and attempt_overdue message should go away.
 114          unset_config('defaultfrontpageroleid');
 115          accesslib_clear_all_caches_for_unit_testing();
 116  
 117          $providers = message_get_providers_for_user($user->id);
 118          $this->assertTrue($this->message_type_present('mod_quiz', 'confirmation', $providers));
 119          $this->assertFalse($this->message_type_present('mod_quiz', 'attempt_overdue', $providers));
 120          $this->assertFalse($this->message_type_present('mod_quiz', 'submission', $providers));
 121      }
 122  
 123      public function test_message_get_providers_for_user_more() {
 124          global $DB;
 125  
 126          $this->resetAfterTest();
 127  
 128          // Create a course.
 129          $course = $this->getDataGenerator()->create_course();
 130          $coursecontext = context_course::instance($course->id);
 131  
 132          // It would probably be better to use a quiz instance as it has capability controlled messages
 133          // however mod_quiz doesn't have a data generator.
 134          // Instead we're going to use backup notifications and give and take away the capability at various levels.
 135          $assign = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id));
 136          $modulecontext = context_module::instance($assign->cmid);
 137  
 138          // Create and enrol a teacher.
 139          $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
 140          $teacher = $this->getDataGenerator()->create_user();
 141          role_assign($teacherrole->id, $teacher->id, $coursecontext);
 142          $enrolplugin = enrol_get_plugin('manual');
 143          $enrolplugin->add_instance($course);
 144          $enrolinstances = enrol_get_instances($course->id, false);
 145          foreach ($enrolinstances as $enrolinstance) {
 146              if ($enrolinstance->enrol === 'manual') {
 147                  break;
 148              }
 149          }
 150          $enrolplugin->enrol_user($enrolinstance, $teacher->id);
 151  
 152          // Make the teacher the current user.
 153          $this->setUser($teacher);
 154  
 155          // Teacher shouldn't have the required capability so they shouldn't be able to see the backup message.
 156          $this->assertFalse(has_capability('moodle/site:config', $modulecontext));
 157          $providers = message_get_providers_for_user($teacher->id);
 158          $this->assertFalse($this->message_type_present('moodle', 'backup', $providers));
 159  
 160          // Give the user the required capability in an activity module.
 161          // They should now be able to see the backup message.
 162          assign_capability('moodle/site:config', CAP_ALLOW, $teacherrole->id, $modulecontext->id, true);
 163          accesslib_clear_all_caches_for_unit_testing();
 164          $modulecontext = context_module::instance($assign->cmid);
 165          $this->assertTrue(has_capability('moodle/site:config', $modulecontext));
 166  
 167          $providers = message_get_providers_for_user($teacher->id);
 168          $this->assertTrue($this->message_type_present('moodle', 'backup', $providers));
 169  
 170          // Prohibit the capability for the user at the course level.
 171          // This overrules the CAP_ALLOW at the module level.
 172          // They should not be able to see the backup message.
 173          assign_capability('moodle/site:config', CAP_PROHIBIT, $teacherrole->id, $coursecontext->id, true);
 174          accesslib_clear_all_caches_for_unit_testing();
 175          $modulecontext = context_module::instance($assign->cmid);
 176          $this->assertFalse(has_capability('moodle/site:config', $modulecontext));
 177  
 178          $providers = message_get_providers_for_user($teacher->id);
 179          // Actually, handling PROHIBITs would be too expensive. We do not
 180          // care if users with PROHIBITs see a few more preferences than they should.
 181          // $this->assertFalse($this->message_type_present('moodle', 'backup', $providers));
 182      }
 183  
 184      public function test_send_message_redirection() {
 185          global $DB;
 186  
 187          $this->resetAfterTest();
 188  
 189          $user1 = $this->getDataGenerator()->create_user();
 190          $user2 = $this->getDataGenerator()->create_user();
 191  
 192          // Test basic message redirection.
 193          $message = new \core\message\message();
 194          $message->courseid = 1;
 195          $message->component = 'moodle';
 196          $message->name = 'instantmessage';
 197          $message->userfrom = $user1;
 198          $message->userto = $user2;
 199          $message->subject = 'message subject 1';
 200          $message->fullmessage = 'message body';
 201          $message->fullmessageformat = FORMAT_MARKDOWN;
 202          $message->fullmessagehtml = '<p>message body</p>';
 203          $message->smallmessage = 'small message';
 204          $message->notification = '0';
 205          $message->customdata = ['datakey' => 'data'];
 206  
 207          $sink = $this->redirectMessages();
 208          $this->setCurrentTimeStart();
 209          $messageid = message_send($message);
 210          $savedmessages = $sink->get_messages();
 211          $this->assertCount(1, $savedmessages);
 212          $savedmessage = reset($savedmessages);
 213          $this->assertEquals($messageid, $savedmessage->id);
 214          $this->assertEquals($user1->id, $savedmessage->useridfrom);
 215          $this->assertEquals($user2->id, $savedmessage->useridto);
 216          $this->assertEquals($message->fullmessage, $savedmessage->fullmessage);
 217          $this->assertEquals($message->fullmessageformat, $savedmessage->fullmessageformat);
 218          $this->assertEquals($message->fullmessagehtml, $savedmessage->fullmessagehtml);
 219          $this->assertEquals($message->smallmessage, $savedmessage->smallmessage);
 220          $this->assertEquals($message->smallmessage, $savedmessage->smallmessage);
 221          $this->assertEquals($message->notification, $savedmessage->notification);
 222          $this->assertEquals($message->customdata, $savedmessage->customdata);
 223          $this->assertStringContainsString('datakey', $savedmessage->customdata);
 224          // Check it was a unserialisable json.
 225          $customdata = json_decode($savedmessage->customdata);
 226          $this->assertEquals('data', $customdata->datakey);
 227          $this->assertEquals(1, $customdata->courseid);
 228          $this->assertTimeCurrent($savedmessage->timecreated);
 229          $record = $DB->get_record('messages', array('id' => $savedmessage->id), '*', MUST_EXIST);
 230          unset($savedmessage->useridto);
 231          unset($savedmessage->notification);
 232          $this->assertEquals($record, $savedmessage);
 233          $sink->clear();
 234          $this->assertTrue($DB->record_exists('message_user_actions', array('userid' => $user2->id, 'messageid' => $messageid,
 235              'action' => \core_message\api::MESSAGE_ACTION_READ)));
 236          $DB->delete_records('messages', array());
 237  
 238          $message = new \core\message\message();
 239          $message->courseid = 1;
 240          $message->component = 'moodle';
 241          $message->name = 'instantmessage';
 242          $message->userfrom = $user1->id;
 243          $message->userto = $user2->id;
 244          $message->subject = 'message subject 1';
 245          $message->fullmessage = 'message body';
 246          $message->fullmessageformat = FORMAT_MARKDOWN;
 247          $message->fullmessagehtml = '<p>message body</p>';
 248          $message->smallmessage = 'small message';
 249          $message->notification = '0';
 250  
 251          $sink = $this->redirectMessages();
 252          $messageid = message_send($message);
 253          $savedmessages = $sink->get_messages();
 254          $this->assertCount(1, $savedmessages);
 255          $savedmessage = reset($savedmessages);
 256          $this->assertEquals($messageid, $savedmessage->id);
 257          $this->assertEquals($user1->id, $savedmessage->useridfrom);
 258          $this->assertEquals($user2->id, $savedmessage->useridto);
 259          $this->assertEquals($message->fullmessage, $savedmessage->fullmessage);
 260          $this->assertEquals($message->fullmessageformat, $savedmessage->fullmessageformat);
 261          $this->assertEquals($message->fullmessagehtml, $savedmessage->fullmessagehtml);
 262          $this->assertEquals($message->smallmessage, $savedmessage->smallmessage);
 263          $this->assertEquals($message->smallmessage, $savedmessage->smallmessage);
 264          $this->assertEquals($message->notification, $savedmessage->notification);
 265          $this->assertTimeCurrent($savedmessage->timecreated);
 266          $record = $DB->get_record('messages', array('id' => $savedmessage->id), '*', MUST_EXIST);
 267          unset($savedmessage->useridto);
 268          unset($savedmessage->notification);
 269          $this->assertEquals($record, $savedmessage);
 270          $sink->clear();
 271          $this->assertTrue($DB->record_exists('message_user_actions', array('userid' => $user2->id, 'messageid' => $messageid,
 272              'action' => \core_message\api::MESSAGE_ACTION_READ)));
 273          $DB->delete_records('messages', array());
 274  
 275          // Test phpunit problem detection.
 276  
 277          $message = new \core\message\message();
 278          $message->courseid = 1;
 279          $message->component = 'xxxxx';
 280          $message->name = 'instantmessage';
 281          $message->userfrom = $user1;
 282          $message->userto = $user2;
 283          $message->subject = 'message subject 1';
 284          $message->fullmessage = 'message body';
 285          $message->fullmessageformat = FORMAT_MARKDOWN;
 286          $message->fullmessagehtml = '<p>message body</p>';
 287          $message->smallmessage = 'small message';
 288          $message->notification = '0';
 289  
 290          $sink = $this->redirectMessages();
 291          try {
 292              message_send($message);
 293          } catch (moodle_exception $e) {
 294              $this->assertInstanceOf('coding_exception', $e);
 295          }
 296          $this->assertCount(0, $sink->get_messages());
 297          $this->assertDebuggingCalled('Attempt to send msg from a provider xxxxx/instantmessage '.
 298              'that is inactive or not allowed for the user id='.$user2->id);
 299  
 300          $message->component = 'moodle';
 301          $message->name = 'xxx';
 302          $sink = $this->redirectMessages();
 303          try {
 304              message_send($message);
 305          } catch (moodle_exception $e) {
 306              $this->assertInstanceOf('coding_exception', $e);
 307          }
 308          $this->assertCount(0, $sink->get_messages());
 309          $this->assertDebuggingCalled('Attempt to send msg from a provider moodle/xxx '.
 310              'that is inactive or not allowed for the user id='.$user2->id);
 311          $sink->close();
 312          $this->assertFalse($DB->record_exists('messages', array()));
 313  
 314          // Invalid users.
 315  
 316          $message = new \core\message\message();
 317          $message->courseid = 1;
 318          $message->component = 'moodle';
 319          $message->name = 'instantmessage';
 320          $message->userfrom = $user1;
 321          $message->userto = -1;
 322          $message->subject = 'message subject 1';
 323          $message->fullmessage = 'message body';
 324          $message->fullmessageformat = FORMAT_MARKDOWN;
 325          $message->fullmessagehtml = '<p>message body</p>';
 326          $message->smallmessage = 'small message';
 327          $message->notification = '0';
 328  
 329          $messageid = message_send($message);
 330          $this->assertFalse($messageid);
 331          $this->assertDebuggingCalled('Attempt to send msg to unknown user');
 332  
 333          $message = new \core\message\message();
 334          $message->courseid = 1;
 335          $message->component = 'moodle';
 336          $message->name = 'instantmessage';
 337          $message->userfrom = -1;
 338          $message->userto = $user2;
 339          $message->subject = 'message subject 1';
 340          $message->fullmessage = 'message body';
 341          $message->fullmessageformat = FORMAT_MARKDOWN;
 342          $message->fullmessagehtml = '<p>message body</p>';
 343          $message->smallmessage = 'small message';
 344          $message->notification = '0';
 345  
 346          $messageid = message_send($message);
 347          $this->assertFalse($messageid);
 348          $this->assertDebuggingCalled('Attempt to send msg from unknown user');
 349  
 350          $message = new \core\message\message();
 351          $message->courseid = 1;
 352          $message->component = 'moodle';
 353          $message->name = 'instantmessage';
 354          $message->userfrom = $user1;
 355          $message->userto = core_user::NOREPLY_USER;
 356          $message->subject = 'message subject 1';
 357          $message->fullmessage = 'message body';
 358          $message->fullmessageformat = FORMAT_MARKDOWN;
 359          $message->fullmessagehtml = '<p>message body</p>';
 360          $message->smallmessage = 'small message';
 361          $message->notification = '0';
 362  
 363          $messageid = message_send($message);
 364          $this->assertFalse($messageid);
 365          $this->assertDebuggingCalled('Attempt to send msg to internal (noreply) user');
 366  
 367          // Some debugging hints for devs.
 368  
 369          unset($user2->emailstop);
 370          $message = new \core\message\message();
 371          $message->courseid = 1;
 372          $message->component = 'moodle';
 373          $message->name = 'instantmessage';
 374          $message->userfrom = $user1;
 375          $message->userto = $user2;
 376          $message->subject = 'message subject 1';
 377          $message->fullmessage = 'message body';
 378          $message->fullmessageformat = FORMAT_MARKDOWN;
 379          $message->fullmessagehtml = '<p>message body</p>';
 380          $message->smallmessage = 'small message';
 381          $message->notification = '0';
 382  
 383          $sink = $this->redirectMessages();
 384          $messageid = message_send($message);
 385          $savedmessages = $sink->get_messages();
 386          $this->assertCount(1, $savedmessages);
 387          $savedmessage = reset($savedmessages);
 388          $this->assertEquals($messageid, $savedmessage->id);
 389          $this->assertEquals($user1->id, $savedmessage->useridfrom);
 390          $this->assertEquals($user2->id, $savedmessage->useridto);
 391          $this->assertDebuggingCalled('Necessary properties missing in userto object, fetching full record');
 392          $sink->clear();
 393          $user2->emailstop = '0';
 394      }
 395  
 396      public function test_send_message() {
 397          global $DB, $CFG;
 398          $this->preventResetByRollback();
 399          $this->resetAfterTest();
 400  
 401          $user1 = $this->getDataGenerator()->create_user(array('maildisplay' => 1));
 402          $user2 = $this->getDataGenerator()->create_user();
 403          set_config('allowedemaildomains', 'example.com');
 404  
 405          // Test basic email redirection.
 406          $this->assertFileExists("$CFG->dirroot/message/output/email/version.php");
 407          $this->assertFileExists("$CFG->dirroot/message/output/popup/version.php");
 408  
 409          $DB->set_field_select('message_processors', 'enabled', 0, "name <> 'email' AND name <> 'popup'");
 410          get_message_processors(true, true);
 411  
 412          $eventsink = $this->redirectEvents();
 413  
 414          // Will always use the pop-up processor.
 415          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'none', $user2);
 416  
 417          $message = new \core\message\message();
 418          $message->courseid          = 1;
 419          $message->component         = 'moodle';
 420          $message->name              = 'instantmessage';
 421          $message->userfrom          = $user1;
 422          $message->userto            = $user2;
 423          $message->subject           = 'message subject 1';
 424          $message->fullmessage       = 'message body';
 425          $message->fullmessageformat = FORMAT_MARKDOWN;
 426          $message->fullmessagehtml   = '<p>message body</p>';
 427          $message->smallmessage      = 'small message';
 428          $message->notification      = '0';
 429  
 430          $sink = $this->redirectEmails();
 431          $messageid = message_send($message);
 432          $emails = $sink->get_messages();
 433          $this->assertCount(0, $emails);
 434          $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
 435          $sink->clear();
 436          $this->assertFalse($DB->record_exists('message_user_actions', array()));
 437          $DB->delete_records('messages', array());
 438          $DB->delete_records('message_user_actions', array());
 439          $events = $eventsink->get_events();
 440          $this->assertCount(1, $events);
 441          $this->assertInstanceOf('\core\event\message_sent', $events[0]);
 442          $eventsink->clear();
 443  
 444          // No messages are sent when the feature is disabled.
 445          $CFG->messaging = 0;
 446  
 447          $message = new \core\message\message();
 448          $message->courseid          = 1;
 449          $message->component         = 'moodle';
 450          $message->name              = 'instantmessage';
 451          $message->userfrom          = $user1;
 452          $message->userto            = $user2;
 453          $message->subject           = 'message subject 1';
 454          $message->fullmessage       = 'message body';
 455          $message->fullmessageformat = FORMAT_MARKDOWN;
 456          $message->fullmessagehtml   = '<p>message body</p>';
 457          $message->smallmessage      = 'small message';
 458          $message->notification      = '0';
 459  
 460          $messageid = message_send($message);
 461          $this->assertFalse($messageid);
 462          $this->assertDebuggingCalled('Attempt to send msg from a provider moodle/instantmessage '.
 463              'that is inactive or not allowed for the user id='.$user2->id);
 464          $emails = $sink->get_messages();
 465          $this->assertCount(0, $emails);
 466          $sink->clear();
 467          $DB->delete_records('messages', array());
 468          $DB->delete_records('message_user_actions', array());
 469          $events = $eventsink->get_events();
 470          $this->assertCount(0, $events);
 471          $eventsink->clear();
 472  
 473          // Example of a message that is sent and viewed.
 474          $CFG->messaging = 1;
 475  
 476          $message = new \core\message\message();
 477          $message->courseid          = 1;
 478          $message->component         = 'moodle';
 479          $message->name              = 'instantmessage';
 480          $message->userfrom          = $user1;
 481          $message->userto            = $user2;
 482          $message->subject           = 'message subject 1';
 483          $message->fullmessage       = 'message body';
 484          $message->fullmessageformat = FORMAT_MARKDOWN;
 485          $message->fullmessagehtml   = '<p>message body</p>';
 486          $message->smallmessage      = 'small message';
 487          $message->notification      = '1';
 488  
 489          $messageid = message_send($message);
 490          $emails = $sink->get_messages();
 491          $this->assertCount(0, $emails);
 492          $savedmessage = $DB->get_record('notifications', array('id' => $messageid), '*', MUST_EXIST);
 493          $sink->clear();
 494          $this->assertFalse($DB->record_exists('messages', array()));
 495          $DB->delete_records('notifications', array());
 496          $events = $eventsink->get_events();
 497          $this->assertCount(2, $events);
 498          $this->assertInstanceOf('\core\event\notification_sent', $events[0]);
 499          $this->assertInstanceOf('\core\event\notification_viewed', $events[1]);
 500          $eventsink->clear();
 501  
 502          // Will always use the pop-up processor.
 503          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user2);
 504  
 505          $message = new \core\message\message();
 506          $message->courseid          = 1;
 507          $message->component         = 'moodle';
 508          $message->name              = 'instantmessage';
 509          $message->userfrom          = $user1;
 510          $message->userto            = $user2;
 511          $message->subject           = 'message subject 1';
 512          $message->fullmessage       = 'message body';
 513          $message->fullmessageformat = FORMAT_MARKDOWN;
 514          $message->fullmessagehtml   = '<p>message body</p>';
 515          $message->smallmessage      = 'small message';
 516          $message->notification      = '0';
 517  
 518          $user2->emailstop = '1';
 519  
 520          $sink = $this->redirectEmails();
 521          $messageid = message_send($message);
 522          $emails = $sink->get_messages();
 523          $this->assertCount(0, $emails);
 524          $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
 525          $sink->clear();
 526          $this->assertFalse($DB->record_exists('message_user_actions', array()));
 527          $DB->delete_records('messages', array());
 528          $DB->delete_records('message_user_actions', array());
 529          $events = $eventsink->get_events();
 530          $this->assertCount(1, $events);
 531          $this->assertInstanceOf('\core\event\message_sent', $events[0]);
 532          $eventsink->clear();
 533          $user2->emailstop = '0';
 534  
 535          // Will always use the pop-up processor.
 536          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user2);
 537  
 538          $message = new \core\message\message();
 539          $message->courseid          = 1;
 540          $message->component         = 'moodle';
 541          $message->name              = 'instantmessage';
 542          $message->userfrom          = $user1;
 543          $message->userto            = $user2;
 544          $message->subject           = 'message subject 1';
 545          $message->fullmessage       = 'message body';
 546          $message->fullmessageformat = FORMAT_MARKDOWN;
 547          $message->fullmessagehtml   = '<p>message body</p>';
 548          $message->smallmessage      = 'small message';
 549          $message->notification      = '0';
 550  
 551          $messageid = message_send($message);
 552          $emails = $sink->get_messages();
 553          $this->assertCount(1, $emails);
 554          $email = reset($emails);
 555          $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
 556          $this->assertSame($user1->email, $email->from);
 557          $this->assertSame($user2->email, $email->to);
 558          $this->assertSame(get_string('unreadnewmessage', 'message', fullname($user1)), $email->subject);
 559          $this->assertNotEmpty($email->header);
 560          $this->assertNotEmpty($email->body);
 561          $sink->clear();
 562          $this->assertFalse($DB->record_exists('message_user_actions', array()));
 563          $DB->delete_records('message_user_actions', array());
 564          $events = $eventsink->get_events();
 565          $this->assertCount(1, $events);
 566          $this->assertInstanceOf('\core\event\message_sent', $events[0]);
 567          $eventsink->clear();
 568  
 569          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email,popup', $user2);
 570  
 571          $message = new \core\message\message();
 572          $message->courseid          = 1;
 573          $message->component         = 'moodle';
 574          $message->name              = 'instantmessage';
 575          $message->userfrom          = $user1;
 576          $message->userto            = $user2;
 577          $message->subject           = 'message subject 1';
 578          $message->fullmessage       = 'message body';
 579          $message->fullmessageformat = FORMAT_MARKDOWN;
 580          $message->fullmessagehtml   = '<p>message body</p>';
 581          $message->smallmessage      = 'small message';
 582          $message->notification      = '0';
 583  
 584          $messageid = message_send($message);
 585          $emails = $sink->get_messages();
 586          $this->assertCount(1, $emails);
 587          $email = reset($emails);
 588          $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
 589          $this->assertSame($user1->email, $email->from);
 590          $this->assertSame($user2->email, $email->to);
 591          $this->assertSame(get_string('unreadnewmessage', 'message', fullname($user1)), $email->subject);
 592          $this->assertNotEmpty($email->header);
 593          $this->assertNotEmpty($email->body);
 594          $sink->clear();
 595          $this->assertFalse($DB->record_exists('message_user_actions', array()));
 596          $DB->delete_records('messages', array());
 597          $DB->delete_records('message_user_actions', array());
 598          $events = $eventsink->get_events();
 599          $this->assertCount(1, $events);
 600          $this->assertInstanceOf('\core\event\message_sent', $events[0]);
 601          $eventsink->clear();
 602  
 603          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'popup', $user2);
 604  
 605          $message = new \core\message\message();
 606          $message->courseid          = 1;
 607          $message->component         = 'moodle';
 608          $message->name              = 'instantmessage';
 609          $message->userfrom          = $user1;
 610          $message->userto            = $user2;
 611          $message->subject           = 'message subject 1';
 612          $message->fullmessage       = 'message body';
 613          $message->fullmessageformat = FORMAT_MARKDOWN;
 614          $message->fullmessagehtml   = '<p>message body</p>';
 615          $message->smallmessage      = 'small message';
 616          $message->notification      = '0';
 617  
 618          $messageid = message_send($message);
 619          $emails = $sink->get_messages();
 620          $this->assertCount(0, $emails);
 621          $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
 622          $sink->clear();
 623          $this->assertFalse($DB->record_exists('message_user_actions', array()));
 624          $DB->delete_records('messages', array());
 625          $events = $eventsink->get_events();
 626          $this->assertCount(1, $events);
 627          $this->assertInstanceOf('\core\event\message_sent', $events[0]);
 628          $eventsink->clear();
 629  
 630          $this->assertFalse($DB->is_transaction_started());
 631          $transaction = $DB->start_delegated_transaction();
 632          if (!$DB->is_transaction_started()) {
 633              $this->markTestSkipped('Databases that do not support transactions should not be used at all!');
 634          }
 635          $transaction->allow_commit();
 636  
 637          // Will always use the pop-up processor.
 638          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'none', $user2);
 639  
 640          $message = new \core\message\message();
 641          $message->courseid          = 1;
 642          $message->component         = 'moodle';
 643          $message->name              = 'instantmessage';
 644          $message->userfrom          = $user1;
 645          $message->userto            = $user2;
 646          $message->subject           = 'message subject 1';
 647          $message->fullmessage       = 'message body';
 648          $message->fullmessageformat = FORMAT_MARKDOWN;
 649          $message->fullmessagehtml   = '<p>message body</p>';
 650          $message->smallmessage      = 'small message';
 651          $message->notification      = '0';
 652  
 653          $transaction = $DB->start_delegated_transaction();
 654          $sink = $this->redirectEmails();
 655          $messageid = message_send($message);
 656          $emails = $sink->get_messages();
 657          $this->assertCount(0, $emails);
 658          $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
 659          $sink->clear();
 660          $this->assertFalse($DB->record_exists('message_user_actions', array()));
 661          $DB->delete_records('messages', array());
 662          $events = $eventsink->get_events();
 663          $this->assertCount(0, $events);
 664          $eventsink->clear();
 665          $transaction->allow_commit();
 666          $events = $eventsink->get_events();
 667          $this->assertCount(1, $events);
 668          $this->assertInstanceOf('\core\event\message_sent', $events[0]);
 669  
 670          // Will always use the pop-up processor.
 671          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user2);
 672  
 673          $message = new \core\message\message();
 674          $message->courseid          = 1;
 675          $message->component         = 'moodle';
 676          $message->name              = 'instantmessage';
 677          $message->userfrom          = $user1;
 678          $message->userto            = $user2;
 679          $message->subject           = 'message subject 1';
 680          $message->fullmessage       = 'message body';
 681          $message->fullmessageformat = FORMAT_MARKDOWN;
 682          $message->fullmessagehtml   = '<p>message body</p>';
 683          $message->smallmessage      = 'small message';
 684          $message->notification      = '0';
 685  
 686          $transaction = $DB->start_delegated_transaction();
 687          $sink = $this->redirectEmails();
 688          $messageid = message_send($message);
 689          $emails = $sink->get_messages();
 690          $this->assertCount(0, $emails);
 691          $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
 692          $sink->clear();
 693          $this->assertFalse($DB->record_exists('message_user_actions', array()));
 694          $events = $eventsink->get_events();
 695          $this->assertCount(1, $events);
 696          $this->assertInstanceOf('\core\event\message_sent', $events[0]);
 697          $transaction->allow_commit();
 698          $events = $eventsink->get_events();
 699          $this->assertCount(2, $events);
 700          $this->assertInstanceOf('\core\event\message_sent', $events[1]);
 701          $eventsink->clear();
 702  
 703          $transaction = $DB->start_delegated_transaction();
 704          message_send($message);
 705          message_send($message);
 706          $this->assertCount(3, $DB->get_records('messages'));
 707          $this->assertFalse($DB->record_exists('message_user_actions', array()));
 708          $events = $eventsink->get_events();
 709          $this->assertCount(0, $events);
 710          $transaction->allow_commit();
 711          $events = $eventsink->get_events();
 712          $this->assertCount(2, $events);
 713          $this->assertInstanceOf('\core\event\message_sent', $events[0]);
 714          $this->assertInstanceOf('\core\event\message_sent', $events[1]);
 715          $eventsink->clear();
 716          $DB->delete_records('messages', array());
 717  
 718          $transaction = $DB->start_delegated_transaction();
 719          message_send($message);
 720          message_send($message);
 721          $this->assertCount(2, $DB->get_records('messages'));
 722          $this->assertCount(0, $DB->get_records('message_user_actions'));
 723          $events = $eventsink->get_events();
 724          $this->assertCount(0, $events);
 725          try {
 726              $transaction->rollback(new Exception('ignore'));
 727          } catch (Exception $e) {
 728              $this->assertSame('ignore', $e->getMessage());
 729          }
 730          $events = $eventsink->get_events();
 731          $this->assertCount(0, $events);
 732          $this->assertCount(0, $DB->get_records('messages'));
 733          message_send($message);
 734          $this->assertCount(1, $DB->get_records('messages'));
 735          $this->assertCount(0, $DB->get_records('message_user_actions'));
 736          $events = $eventsink->get_events();
 737          $this->assertCount(1, $events);
 738          $this->assertInstanceOf('\core\event\message_sent', $events[0]);
 739          $sink->clear();
 740      }
 741  
 742      /**
 743       * Tests calling message_send() with $eventdata representing a message to an individual conversation.
 744       *
 745       * This test will verify:
 746       * - that the 'messages' record is created.
 747       * - that the processors will be called for each conversation member, except the sender.
 748       * - the a single event will be generated - 'message_sent'
 749       *
 750       * Note: We won't redirect/capture messages in this test because doing so causes message_send() to return early, before
 751       * processors and events code is called. We need to test this code here, as we generally redirect messages elsewhere and we
 752       * need to be sure this is covered.
 753       */
 754      public function test_message_send_to_conversation_individual() {
 755          global $DB;
 756          $this->preventResetByRollback();
 757          $this->resetAfterTest();
 758  
 759          // Create some users and a conversation between them.
 760          $user1 = $this->getDataGenerator()->create_user(array('maildisplay' => 1));
 761          $user2 = $this->getDataGenerator()->create_user();
 762          set_config('allowedemaildomains', 'example.com');
 763          $conversation = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
 764              [$user1->id, $user2->id], '1:1 project discussion');
 765  
 766          // Generate the message.
 767          $message = new \core\message\message();
 768          $message->courseid          = 1;
 769          $message->component         = 'moodle';
 770          $message->name              = 'instantmessage';
 771          $message->userfrom          = $user1;
 772          $message->convid            = $conversation->id;
 773          $message->subject           = 'message subject 1';
 774          $message->fullmessage       = 'message body';
 775          $message->fullmessageformat = FORMAT_MARKDOWN;
 776          $message->fullmessagehtml   = '<p>message body</p>';
 777          $message->smallmessage      = 'small message';
 778          $message->notification      = '0';
 779  
 780          // Content specific to the email processor.
 781          $content = array('*' => array('header' => ' test ', 'footer' => ' test '));
 782          $message->set_additional_content('email', $content);
 783  
 784          // Ensure we're going to hit the email processor for this user.
 785          $DB->set_field_select('message_processors', 'enabled', 0, "name <> 'email'");
 786          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user2);
 787  
 788          // Now, send a message and verify the message processors (in this case, email) are hit.
 789          $sink = $this->redirectEmails();
 790          $messageid = message_send($message);
 791          $emails = $sink->get_messages();
 792          $this->assertCount(1, $emails);
 793          $email = reset($emails);
 794  
 795          // Verify the record was created in 'messages'.
 796          $recordexists = $DB->record_exists('messages', ['id' => $messageid]);
 797          $this->assertTrue($recordexists);
 798  
 799          // Verify the email information.
 800          $this->assertSame($user1->email, $email->from);
 801          $this->assertSame($user2->email, $email->to);
 802  
 803          // The message subject is generated during the call for conversation messages,
 804          // as the conversation may have many members having different lang preferences.
 805          $this->assertSame(get_string('unreadnewmessage', 'message', fullname($user1)), $email->subject);
 806  
 807          // The email content will have had an emailtagline appended to it, based on lang prefs,
 808          // so verify the expected beginning and ends.
 809          $this->assertNotEmpty($email->header);
 810          $this->assertNotEmpty($email->body);
 811          $this->assertRegExp('/test.*message body.*test/s', $email->body);
 812          $sink->clear();
 813  
 814          // Now, send the message again, and verify that the event fired includes the courseid and conversationid.
 815          $eventsink = $this->redirectEvents();
 816          $messageid = message_send($message);
 817          $events = $eventsink->get_events();
 818          $this->assertCount(1, $events);
 819          $event = reset($events);
 820          $this->assertInstanceOf(\core\event\message_sent::class, $event);
 821          $this->assertEquals($user1->id, $event->userid);
 822          $this->assertEquals($user2->id, $event->relateduserid);
 823          $this->assertEquals($message->courseid, $event->other['courseid']);
 824  
 825          $eventsink->clear();
 826          $sink->clear();
 827      }
 828  
 829      /**
 830       * Tests calling message_send() with $eventdata representing a message to a self-conversation.
 831       *
 832       * This test will verify:
 833       * - that the 'messages' record is created.
 834       * - that the processors is not called (for now self-conversations are not processed).
 835       * - the a single event will be generated - 'message_sent'
 836       *
 837       * Note: We won't redirect/capture messages in this test because doing so causes message_send() to return early, before
 838       * processors and events code is called. We need to test this code here, as we generally redirect messages elsewhere and we
 839       * need to be sure this is covered.
 840       */
 841      public function test_message_send_to_self_conversation() {
 842          global $DB;
 843          $this->preventResetByRollback();
 844          $this->resetAfterTest();
 845  
 846          // Create some users and a conversation between them.
 847          $user1 = $this->getDataGenerator()->create_user(array('maildisplay' => 1));
 848          set_config('allowedemaildomains', 'example.com');
 849          $conversation = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
 850              [$user1->id]);
 851  
 852          // Generate the message.
 853          $message = new \core\message\message();
 854          $message->courseid          = 1;
 855          $message->component         = 'moodle';
 856          $message->name              = 'instantmessage';
 857          $message->userfrom          = $user1;
 858          $message->convid            = $conversation->id;
 859          $message->subject           = 'message subject 1';
 860          $message->fullmessage       = 'message body';
 861          $message->fullmessageformat = FORMAT_MARKDOWN;
 862          $message->fullmessagehtml   = '<p>message body</p>';
 863          $message->smallmessage      = 'small message';
 864          $message->notification      = '0';
 865  
 866          // Content specific to the email processor.
 867          $content = array('*' => array('header' => ' test ', 'footer' => ' test '));
 868          $message->set_additional_content('email', $content);
 869  
 870          // Ensure we're going to hit the email processor for this user.
 871          $DB->set_field_select('message_processors', 'enabled', 0, "name <> 'email'");
 872          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user1);
 873  
 874          // Now, send a message and verify the message processors are empty (self-conversations are not processed for now).
 875          $sink = $this->redirectEmails();
 876          $messageid = message_send($message);
 877          $emails = $sink->get_messages();
 878          $this->assertCount(0, $emails);
 879          $sink->clear();
 880      }
 881  
 882      /**
 883       * Tests calling message_send() with $eventdata representing a message to an group conversation.
 884       *
 885       * This test will verify:
 886       * - that the 'messages' record is created.
 887       * - that the processors will be called for each conversation member, except the sender.
 888       * - the a single event will be generated - 'group_message_sent'
 889       *
 890       * Note: We won't redirect/capture messages in this test because doing so causes message_send() to return early, before
 891       * processors and events code is called. We need to test this code here, as we generally redirect messages elsewhere and we
 892       * need to be sure this is covered.
 893       */
 894      public function test_message_send_to_conversation_group() {
 895          global $DB;
 896          $this->preventResetByRollback();
 897          $this->resetAfterTest();
 898  
 899          $course = $this->getDataGenerator()->create_course();
 900  
 901          // Create some users and a conversation between them.
 902          $user1 = $this->getDataGenerator()->create_user(array('maildisplay' => 1));
 903          $user2 = $this->getDataGenerator()->create_user();
 904          $user3 = $this->getDataGenerator()->create_user();
 905          set_config('allowedemaildomains', 'example.com');
 906  
 907          // Create a group in the course.
 908          $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 909          groups_add_member($group1->id, $user1->id);
 910          groups_add_member($group1->id, $user2->id);
 911          groups_add_member($group1->id, $user3->id);
 912  
 913          $conversation = \core_message\api::create_conversation(
 914              \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
 915              [$user1->id, $user2->id, $user3->id],
 916              'Group project discussion',
 917              \core_message\api::MESSAGE_CONVERSATION_ENABLED,
 918              'core_group',
 919              'groups',
 920              $group1->id,
 921              context_course::instance($course->id)->id
 922          );
 923  
 924          // Generate the message.
 925          $message = new \core\message\message();
 926          $message->courseid          = 1;
 927          $message->component         = 'moodle';
 928          $message->name              = 'instantmessage';
 929          $message->userfrom          = $user1;
 930          $message->convid            = $conversation->id;
 931          $message->subject           = 'message subject 1';
 932          $message->fullmessage       = 'message body';
 933          $message->fullmessageformat = FORMAT_MARKDOWN;
 934          $message->fullmessagehtml   = '<p>message body</p>';
 935          $message->smallmessage      = 'small message';
 936          $message->notification      = '0';
 937  
 938          // Content specific to the email processor.
 939          $content = array('*' => array('header' => ' test ', 'footer' => ' test '));
 940          $message->set_additional_content('email', $content);
 941  
 942          // Ensure the email processor is enabled for the recipient users.
 943          $DB->set_field_select('message_processors', 'enabled', 0, "name <> 'email'");
 944          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user2);
 945          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user3);
 946  
 947          // Now, send a message and verify the email processor are hit.
 948          $messageid = message_send($message);
 949  
 950          $sink = $this->redirectEmails();
 951          $task = new \message_email\task\send_email_task();
 952          $task->execute();
 953          $emails = $sink->get_messages();
 954          $this->assertCount(2, $emails);
 955  
 956          // Verify the record was created in 'messages'.
 957          $recordexists = $DB->record_exists('messages', ['id' => $messageid]);
 958          $this->assertTrue($recordexists);
 959  
 960          // Now, send the message again, and verify that the event fired includes the courseid and conversationid.
 961          $eventsink = $this->redirectEvents();
 962          $messageid = message_send($message);
 963          $events = $eventsink->get_events();
 964          $this->assertCount(1, $events);
 965          $event = reset($events);
 966          $this->assertInstanceOf(\core\event\group_message_sent::class, $event);
 967          $this->assertEquals($user1->id, $event->userid);
 968          $this->assertNull($event->relateduserid);
 969          $this->assertEquals($message->courseid, $event->other['courseid']);
 970          $this->assertEquals($message->convid, $event->other['conversationid']);
 971          $eventsink->clear();
 972          $sink->clear();
 973      }
 974  
 975      /**
 976       * Verify that sending a message to a conversation is an action which can be buffered by the manager if in a DB transaction.
 977       *
 978       * This should defer all processor calls (for 2 members in this case), and event creation (1 event).
 979       */
 980      public function test_send_message_to_conversation_group_with_buffering() {
 981          global $DB, $CFG;
 982          $this->preventResetByRollback();
 983          $this->resetAfterTest();
 984  
 985          $course = $this->getDataGenerator()->create_course();
 986  
 987          $user1 = $this->getDataGenerator()->create_user(array('maildisplay' => 1));
 988          $user2 = $this->getDataGenerator()->create_user();
 989          $user3 = $this->getDataGenerator()->create_user();
 990          set_config('allowedemaildomains', 'example.com');
 991  
 992          // Create a group in the course.
 993          $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 994          groups_add_member($group1->id, $user1->id);
 995          groups_add_member($group1->id, $user2->id);
 996          groups_add_member($group1->id, $user3->id);
 997  
 998          $conversation = \core_message\api::create_conversation(
 999              \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
1000              [$user1->id, $user2->id, $user3->id],
1001              'Group project discussion',
1002              \core_message\api::MESSAGE_CONVERSATION_ENABLED,
1003              'core_group',
1004              'groups',
1005              $group1->id,
1006              context_course::instance($course->id)->id
1007          );
1008  
1009          // Test basic email redirection.
1010          $this->assertFileExists("$CFG->dirroot/message/output/email/version.php");
1011          $this->assertFileExists("$CFG->dirroot/message/output/popup/version.php");
1012  
1013          $DB->set_field_select('message_processors', 'enabled', 0, "name <> 'email' AND name <> 'popup'");
1014          get_message_processors(true, true);
1015  
1016          $eventsink = $this->redirectEvents();
1017  
1018          // Will always use the pop-up processor.
1019          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user2);
1020          set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user3);
1021  
1022          $message = new \core\message\message();
1023          $message->courseid          = 1;
1024          $message->component         = 'moodle';
1025          $message->name              = 'instantmessage';
1026          $message->userfrom          = $user1;
1027          $message->convid            = $conversation->id;
1028          $message->subject           = 'message subject 1';
1029          $message->fullmessage       = 'message body';
1030          $message->fullmessageformat = FORMAT_MARKDOWN;
1031          $message->fullmessagehtml   = '<p>message body</p>';
1032          $message->smallmessage      = 'small message';
1033          $message->notification      = '0';
1034  
1035          $transaction = $DB->start_delegated_transaction();
1036          $sink = $this->redirectEmails();
1037          message_send($message);
1038          $emails = $sink->get_messages();
1039          $this->assertCount(0, $emails);
1040          $sink->clear();
1041          $this->assertFalse($DB->record_exists('message_user_actions', array()));
1042          $events = $eventsink->get_events();
1043          $this->assertCount(0, $events);
1044          $eventsink->clear();
1045          $transaction->allow_commit();
1046          $events = $eventsink->get_events();
1047          $task = new \message_email\task\send_email_task();
1048          $task->execute();
1049          $emails = $sink->get_messages();
1050          $this->assertCount(2, $emails);
1051          $this->assertCount(1, $events);
1052          $this->assertInstanceOf('\core\event\group_message_sent', $events[0]);
1053      }
1054  
1055      public function test_rollback() {
1056          global $DB;
1057  
1058          $this->resetAfterTest();
1059          $this->preventResetByRollback();
1060  
1061          $user1 = $this->getDataGenerator()->create_user();
1062          $user2 = $this->getDataGenerator()->create_user();
1063  
1064          $message = new \core\message\message();
1065          $message->courseid          = 1;
1066          $message->component         = 'moodle';
1067          $message->name              = 'instantmessage';
1068          $message->userfrom          = $user1;
1069          $message->userto            = $user2;
1070          $message->subject           = 'message subject 1';
1071          $message->fullmessage       = 'message body';
1072          $message->fullmessageformat = FORMAT_MARKDOWN;
1073          $message->fullmessagehtml   = '<p>message body</p>';
1074          $message->smallmessage      = 'small message';
1075          $message->notification      = '0';
1076  
1077          $mailsink = $this->redirectEmails();
1078  
1079          // Sending outside of a transaction is fine.
1080          message_send($message);
1081          $this->assertEquals(1, $mailsink->count());
1082  
1083          $transaction1 = $DB->start_delegated_transaction();
1084  
1085          $mailsink->clear();
1086          message_send($message);
1087          $this->assertEquals(0, $mailsink->count());
1088  
1089          $transaction2 = $DB->start_delegated_transaction();
1090  
1091          $mailsink->clear();
1092          message_send($message);
1093          $this->assertEquals(0, $mailsink->count());
1094  
1095          try {
1096              $transaction2->rollback(new Exception('x'));
1097              $this->fail('Expecting exception');
1098          } catch (Exception $e) {}
1099          $this->assertDebuggingNotCalled();
1100          $this->assertEquals(0, $mailsink->count());
1101  
1102          $this->assertTrue($DB->is_transaction_started());
1103  
1104          try {
1105              $transaction1->rollback(new Exception('x'));
1106              $this->fail('Expecting exception');
1107          } catch (Exception $e) {}
1108          $this->assertDebuggingNotCalled();
1109          $this->assertEquals(0, $mailsink->count());
1110  
1111          $this->assertFalse($DB->is_transaction_started());
1112  
1113          message_send($message);
1114          $this->assertEquals(1, $mailsink->count());
1115      }
1116  
1117      public function test_forced_rollback() {
1118          global $DB;
1119  
1120          $this->resetAfterTest();
1121          $this->preventResetByRollback();
1122          set_config('noemailever', 1);
1123  
1124          $user1 = $this->getDataGenerator()->create_user();
1125          $user2 = $this->getDataGenerator()->create_user();
1126  
1127          $message = new \core\message\message();
1128          $message->courseid          = 1;
1129          $message->component         = 'moodle';
1130          $message->name              = 'instantmessage';
1131          $message->userfrom          = $user1;
1132          $message->userto            = $user2;
1133          $message->subject           = 'message subject 1';
1134          $message->fullmessage       = 'message body';
1135          $message->fullmessageformat = FORMAT_MARKDOWN;
1136          $message->fullmessagehtml   = '<p>message body</p>';
1137          $message->smallmessage      = 'small message';
1138          $message->notification      = '0';
1139  
1140          message_send($message);
1141          $this->assertDebuggingCalled('Not sending email due to $CFG->noemailever config setting');
1142  
1143          $transaction1 = $DB->start_delegated_transaction();
1144  
1145          message_send($message);
1146          $this->assertDebuggingNotCalled();
1147  
1148          $transaction2 = $DB->start_delegated_transaction();
1149  
1150          message_send($message);
1151          $this->assertDebuggingNotCalled();
1152  
1153          $DB->force_transaction_rollback();
1154          $this->assertFalse($DB->is_transaction_started());
1155          $this->assertDebuggingNotCalled();
1156  
1157          message_send($message);
1158          $this->assertDebuggingCalled('Not sending email due to $CFG->noemailever config setting');
1159      }
1160  
1161      public function test_message_attachment_send() {
1162          global $CFG;
1163          $this->preventResetByRollback();
1164          $this->resetAfterTest();
1165  
1166          // Set config setting to allow attachments.
1167          $CFG->allowattachments = true;
1168          unset_config('noemailever');
1169  
1170          $user = $this->getDataGenerator()->create_user();
1171          $context = context_user::instance($user->id);
1172  
1173          // Create a test file.
1174          $fs = get_file_storage();
1175          $filerecord = array(
1176                  'contextid' => $context->id,
1177                  'component' => 'core',
1178                  'filearea'  => 'unittest',
1179                  'itemid'    => 99999,
1180                  'filepath'  => '/',
1181                  'filename'  => 'emailtest.txt'
1182          );
1183          $file = $fs->create_file_from_string($filerecord, 'Test content');
1184  
1185          $message = new \core\message\message();
1186          $message->courseid          = 1;
1187          $message->component         = 'moodle';
1188          $message->name              = 'instantmessage';
1189          $message->userfrom          = get_admin();
1190          $message->userto            = $user;
1191          $message->subject           = 'message subject 1';
1192          $message->fullmessage       = 'message body';
1193          $message->fullmessageformat = FORMAT_MARKDOWN;
1194          $message->fullmessagehtml   = '<p>message body</p>';
1195          $message->smallmessage      = 'small message';
1196          $message->attachment        = $file;
1197          $message->attachname        = 'emailtest.txt';
1198          $message->notification      = 0;
1199  
1200          // Make sure we are redirecting emails.
1201          $sink = $this->redirectEmails();
1202          message_send($message);
1203  
1204          // Get the email that we just sent.
1205          $emails = $sink->get_messages();
1206          $email = reset($emails);
1207          $this->assertTrue(strpos($email->body, 'Content-Disposition: attachment;') !== false);
1208          $this->assertTrue(strpos($email->body, 'emailtest.txt') !== false);
1209  
1210          // Check if the stored file still exists after remove the temporary attachment.
1211          $storedfileexists = $fs->file_exists($filerecord['contextid'], $filerecord['component'], $filerecord['filearea'],
1212                                               $filerecord['itemid'], $filerecord['filepath'], $filerecord['filename']);
1213          $this->assertTrue($storedfileexists);
1214      }
1215  
1216      public function test_send_message_when_muted() {
1217          $this->preventResetByRollback();
1218          $this->resetAfterTest();
1219  
1220          $userfrom = $this->getDataGenerator()->create_user();
1221          $userto = $this->getDataGenerator()->create_user();
1222  
1223          // Create a conversation between the users.
1224          $conversation = \core_message\api::create_conversation(
1225              \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
1226              [
1227                  $userfrom->id,
1228                  $userto->id
1229              ]
1230          );
1231  
1232          $message = new \core\message\message();
1233          $message->courseid = 1;
1234          $message->component = 'moodle';
1235          $message->name = 'instantmessage';
1236          $message->userfrom = $userfrom;
1237          $message->convid = $conversation->id;
1238          $message->subject = 'message subject 1';
1239          $message->fullmessage = 'message body';
1240          $message->fullmessageformat = FORMAT_MARKDOWN;
1241          $message->fullmessagehtml = '<p>message body</p>';
1242          $message->smallmessage = 'small message';
1243          $message->notification = '0';
1244  
1245          $sink = $this->redirectEmails();
1246          message_send($message);
1247          $emails = $sink->get_messages();
1248          $this->assertCount(1, $emails);
1249          $sink->clear();
1250  
1251          // Mute the conversation.
1252          \core_message\api::mute_conversation($userto->id, $conversation->id);
1253  
1254          $sink = $this->redirectEmails();
1255          message_send($message);
1256          $emails = $sink->get_messages();
1257          $this->assertCount(0, $emails);
1258          $sink->clear();
1259      }
1260  
1261      /**
1262       * Is a particular message type in the list of message types.
1263       * @param string $component
1264       * @param string $name a message name.
1265       * @param array $providers as returned by message_get_providers_for_user.
1266       * @return bool whether the message type is present.
1267       */
1268      protected function message_type_present($component, $name, $providers) {
1269          foreach ($providers as $provider) {
1270              if ($provider->component == $component && $provider->name == $name) {
1271                  return true;
1272              }
1273          }
1274          return false;
1275      }
1276  }