Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

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