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   * The forum module mail generation tests.
  19   *
  20   * @package    mod_forum
  21   * @category   external
  22   * @copyright  2013 Andrew Nicols
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  global $CFG;
  29  require_once($CFG->dirroot . '/mod/forum/lib.php');
  30  require_once (__DIR__ . '/cron_trait.php');
  31  require_once (__DIR__ . '/generator_trait.php');
  32  
  33  class mod_forum_mail_testcase extends advanced_testcase {
  34      // Make use of the cron tester trait.
  35      use mod_forum_tests_cron_trait;
  36  
  37      // Make use of the test generator trait.
  38      use mod_forum_tests_generator_trait;
  39  
  40      /**
  41       * @var \phpunit_message_sink
  42       */
  43      protected $messagesink;
  44  
  45      /**
  46       * @var \phpunit_mailer_sink
  47       */
  48      protected $mailsink;
  49  
  50      public function setUp(): void {
  51          global $CFG;
  52  
  53          // We must clear the subscription caches. This has to be done both before each test, and after in case of other
  54          // tests using these functions.
  55          \mod_forum\subscriptions::reset_forum_cache();
  56          \mod_forum\subscriptions::reset_discussion_cache();
  57  
  58          // Messaging is not compatible with transactions...
  59          $this->preventResetByRollback();
  60  
  61          // Catch all messages.
  62          $this->messagesink = $this->redirectMessages();
  63          $this->mailsink = $this->redirectEmails();
  64  
  65          // Forcibly reduce the maxeditingtime to a second in the past to
  66          // ensure that messages are sent out.
  67          $CFG->maxeditingtime = -1;
  68      }
  69  
  70      public function tearDown(): void {
  71          // We must clear the subscription caches. This has to be done both before each test, and after in case of other
  72          // tests using these functions.
  73          \mod_forum\subscriptions::reset_forum_cache();
  74  
  75          $this->messagesink->clear();
  76          $this->messagesink->close();
  77          unset($this->messagesink);
  78  
  79          $this->mailsink->clear();
  80          $this->mailsink->close();
  81          unset($this->mailsink);
  82      }
  83  
  84      /**
  85       * Perform message inbound setup for the mod_forum reply handler.
  86       */
  87      protected function helper_spoof_message_inbound_setup() {
  88          global $CFG, $DB;
  89          // Setup the default Inbound Message mailbox settings.
  90          $CFG->messageinbound_domain = 'example.com';
  91          $CFG->messageinbound_enabled = true;
  92  
  93          // Must be no longer than 15 characters.
  94          $CFG->messageinbound_mailbox = 'moodlemoodle123';
  95  
  96          $record = $DB->get_record('messageinbound_handlers', array('classname' => '\mod_forum\message\inbound\reply_handler'));
  97          $record->enabled = true;
  98          $record->id = $DB->update_record('messageinbound_handlers', $record);
  99      }
 100  
 101      public function test_cron_message_includes_courseid() {
 102          $this->resetAfterTest(true);
 103  
 104          // Create a course, with a forum.
 105          $course = $this->getDataGenerator()->create_course();
 106  
 107          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
 108          $forum = $this->getDataGenerator()->create_module('forum', $options);
 109  
 110          // Create two users enrolled in the course as students.
 111          list($author, $recipient) = $this->helper_create_users($course, 2);
 112  
 113          // Post a discussion to the forum.
 114          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 115  
 116          $expect = [
 117              'author' => (object) [
 118                  'userid' => $author->id,
 119                  'messages' => 1,
 120              ],
 121              'recipient' => (object) [
 122                  'userid' => $recipient->id,
 123                  'messages' => 1,
 124              ],
 125          ];
 126          $this->queue_tasks_and_assert($expect);
 127  
 128          $this->messagesink->close();
 129          $this->eventsink = $this->redirectEvents();
 130          $this->send_notifications_and_assert($author, [$post]);
 131          $events = $this->eventsink->get_events();
 132          $event = reset($events);
 133  
 134          $this->assertEquals($course->id, $event->other['courseid']);
 135  
 136          $this->send_notifications_and_assert($recipient, [$post]);
 137      }
 138  
 139      public function test_forced_subscription() {
 140          global $DB;
 141          $this->resetAfterTest(true);
 142  
 143          // Create a course, with a forum.
 144          $course = $this->getDataGenerator()->create_course();
 145  
 146          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
 147          $forum = $this->getDataGenerator()->create_module('forum', $options);
 148  
 149          // Create users enrolled in the course as students.
 150          list($author, $recipient, $unconfirmed, $deleted) = $this->helper_create_users($course, 4);
 151  
 152          // Make the third user unconfirmed (thence inactive) to make sure it does not break the notifications.
 153          $DB->set_field('user', 'confirmed', 0, ['id' => $unconfirmed->id]);
 154  
 155          // Mark the fourth user as deleted to make sure it does not break the notifications.
 156          $DB->set_field('user', 'deleted', 1, ['id' => $deleted->id]);
 157  
 158          // Post a discussion to the forum.
 159          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 160  
 161          $expect = [
 162              (object) [
 163                  'userid' => $author->id,
 164                  'messages' => 1,
 165              ],
 166              (object) [
 167                  'userid' => $recipient->id,
 168                  'messages' => 1,
 169              ],
 170              (object) [
 171                  'userid' => $unconfirmed->id,
 172                  'messages' => 0,
 173              ],
 174              (object) [
 175                  'userid' => $deleted->id,
 176                  'messages' => 0,
 177              ],
 178          ];
 179          $this->queue_tasks_and_assert($expect);
 180  
 181          $this->send_notifications_and_assert($author, [$post]);
 182          $this->send_notifications_and_assert($recipient, [$post]);
 183          $this->send_notifications_and_assert($unconfirmed, []);
 184          $this->send_notifications_and_assert($deleted, []);
 185      }
 186  
 187      /**
 188       * Ensure that for a forum with subscription disabled that standard users will not receive posts.
 189       */
 190      public function test_subscription_disabled_standard_users() {
 191          global $DB;
 192  
 193          $this->resetAfterTest(true);
 194  
 195          // Create a course, with a forum.
 196          $course = $this->getDataGenerator()->create_course();
 197  
 198          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_DISALLOWSUBSCRIBE);
 199          $forum = $this->getDataGenerator()->create_module('forum', $options);
 200  
 201          // Create two users enrolled in the course as students.
 202          list($author, $recipient) = $this->helper_create_users($course, 2);
 203  
 204          // Post a discussion to the forum.
 205          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 206  
 207          // Run cron and check that the expected number of users received the notification.
 208          $expect = [
 209              (object) [
 210                  'userid' => $author->id,
 211                  'messages' => 0,
 212              ],
 213              (object) [
 214                  'userid' => $recipient->id,
 215                  'messages' => 0,
 216              ],
 217          ];
 218          $this->queue_tasks_and_assert($expect);
 219  
 220          $this->send_notifications_and_assert($author, []);
 221          $this->send_notifications_and_assert($recipient, []);
 222      }
 223  
 224      /**
 225       * Ensure that for a forum with subscription disabled that a user subscribed to the forum will receive the post.
 226       */
 227      public function test_subscription_disabled_user_subscribed_forum() {
 228          global $DB;
 229  
 230          $this->resetAfterTest(true);
 231  
 232          // Create a course, with a forum.
 233          $course = $this->getDataGenerator()->create_course();
 234  
 235          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_DISALLOWSUBSCRIBE);
 236          $forum = $this->getDataGenerator()->create_module('forum', $options);
 237  
 238          // Create two users enrolled in the course as students.
 239          list($author, $recipient) = $this->helper_create_users($course, 2);
 240  
 241          // A user with the manageactivities capability within the course can subscribe.
 242          $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
 243          assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleids['student'], context_course::instance($course->id));
 244  
 245          // Suscribe the recipient only.
 246          \mod_forum\subscriptions::subscribe_user($recipient->id, $forum);
 247  
 248          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 249              'userid'        => $recipient->id,
 250              'forum'         => $forum->id,
 251          )));
 252  
 253          // Post a discussion to the forum.
 254          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 255  
 256          // Run cron and check that the expected number of users received the notification.
 257          $expect = [
 258              'author' => (object) [
 259                  'userid' => $author->id,
 260              ],
 261              'recipient' => (object) [
 262                  'userid' => $recipient->id,
 263                  'messages' => 1,
 264              ],
 265          ];
 266          $this->queue_tasks_and_assert($expect);
 267  
 268          $this->send_notifications_and_assert($author, []);
 269          $this->send_notifications_and_assert($recipient, [$post]);
 270      }
 271  
 272      /**
 273       * Ensure that for a forum with subscription disabled that a user subscribed to the discussion will receive the
 274       * post.
 275       */
 276      public function test_subscription_disabled_user_subscribed_discussion() {
 277          global $DB;
 278  
 279          $this->resetAfterTest(true);
 280  
 281          // Create a course, with a forum.
 282          $course = $this->getDataGenerator()->create_course();
 283  
 284          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_DISALLOWSUBSCRIBE);
 285          $forum = $this->getDataGenerator()->create_module('forum', $options);
 286  
 287          // Create two users enrolled in the course as students.
 288          list($author, $recipient) = $this->helper_create_users($course, 2);
 289  
 290          // A user with the manageactivities capability within the course can subscribe.
 291          $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
 292          assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleids['student'], context_course::instance($course->id));
 293  
 294          // Run cron and check that the expected number of users received the notification.
 295          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 296  
 297          // Subscribe the user to the discussion.
 298          \mod_forum\subscriptions::subscribe_user_to_discussion($recipient->id, $discussion);
 299          $this->helper_update_subscription_time($recipient, $discussion, -60);
 300  
 301          // Run cron and check that the expected number of users received the notification.
 302          $expect = [
 303              'author' => (object) [
 304                  'userid' => $author->id,
 305              ],
 306              'recipient' => (object) [
 307                  'userid' => $recipient->id,
 308                  'messages' => 1,
 309              ],
 310          ];
 311          $this->queue_tasks_and_assert($expect);
 312  
 313          $this->send_notifications_and_assert($author, []);
 314          $this->send_notifications_and_assert($recipient, [$post]);
 315      }
 316  
 317      /**
 318       * Ensure that for a forum with automatic subscription that users receive posts.
 319       */
 320      public function test_automatic() {
 321          $this->resetAfterTest(true);
 322  
 323          // Create a course, with a forum.
 324          $course = $this->getDataGenerator()->create_course();
 325  
 326          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
 327          $forum = $this->getDataGenerator()->create_module('forum', $options);
 328  
 329          // Create two users enrolled in the course as students.
 330          list($author, $recipient) = $this->helper_create_users($course, 2);
 331  
 332          // Post a discussion to the forum.
 333          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 334  
 335          $expect = [
 336              (object) [
 337                  'userid' => $author->id,
 338                  'messages' => 1,
 339              ],
 340              (object) [
 341                  'userid' => $recipient->id,
 342                  'messages' => 1,
 343              ],
 344          ];
 345          $this->queue_tasks_and_assert($expect);
 346  
 347          $this->send_notifications_and_assert($author, [$post]);
 348          $this->send_notifications_and_assert($recipient, [$post]);
 349      }
 350  
 351      /**
 352       * Ensure that private replies are not sent to users with an automatic subscription unless they are an expected
 353       * recipient.
 354       */
 355      public function test_automatic_with_private_reply() {
 356          $this->resetAfterTest(true);
 357  
 358          // Create a course, with a forum.
 359          $course = $this->getDataGenerator()->create_course();
 360          $forum = $this->getDataGenerator()->create_module('forum', [
 361                  'course' => $course->id,
 362                  'forcesubscribe' => FORUM_INITIALSUBSCRIBE,
 363              ]);
 364  
 365          [$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
 366          [$teacher, $otherteacher] = $this->helper_create_users($course, 2, 'teacher');
 367  
 368          [$discussion, $post] = $this->helper_post_to_forum($forum, $student);
 369          $reply = $this->helper_post_to_discussion($forum, $discussion, $teacher, [
 370                  'privatereplyto' => $student->id,
 371              ]);
 372  
 373          // The private reply is queued to all messages as reply visibility may change between queueing, and sending.
 374          $expect = [
 375              (object) [
 376                  'userid' => $student->id,
 377                  'messages' => 2,
 378              ],
 379              (object) [
 380                  'userid' => $otherstudent->id,
 381                  'messages' => 2,
 382              ],
 383              (object) [
 384                  'userid' => $teacher->id,
 385                  'messages' => 2,
 386              ],
 387              (object) [
 388                  'userid' => $otherteacher->id,
 389                  'messages' => 2,
 390              ],
 391          ];
 392          $this->queue_tasks_and_assert($expect);
 393  
 394          // The actual messages sent will respect private replies.
 395          $this->send_notifications_and_assert($student, [$post, $reply]);
 396          $this->send_notifications_and_assert($teacher, [$post, $reply]);
 397          $this->send_notifications_and_assert($otherteacher, [$post, $reply]);
 398          $this->send_notifications_and_assert($otherstudent, [$post]);
 399      }
 400  
 401      public function test_optional() {
 402          $this->resetAfterTest(true);
 403  
 404          // Create a course, with a forum.
 405          $course = $this->getDataGenerator()->create_course();
 406  
 407          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 408          $forum = $this->getDataGenerator()->create_module('forum', $options);
 409  
 410          // Create two users enrolled in the course as students.
 411          list($author, $recipient) = $this->helper_create_users($course, 2);
 412  
 413          // Post a discussion to the forum.
 414          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 415  
 416          $expect = [
 417              (object) [
 418                  'userid' => $author->id,
 419                  'messages' => 0,
 420              ],
 421              (object) [
 422                  'userid' => $recipient->id,
 423                  'messages' => 0,
 424              ],
 425          ];
 426          $this->queue_tasks_and_assert($expect);
 427  
 428          $this->send_notifications_and_assert($author, []);
 429          $this->send_notifications_and_assert($recipient, []);
 430      }
 431  
 432      public function test_automatic_with_unsubscribed_user() {
 433          $this->resetAfterTest(true);
 434  
 435          // Create a course, with a forum.
 436          $course = $this->getDataGenerator()->create_course();
 437  
 438          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
 439          $forum = $this->getDataGenerator()->create_module('forum', $options);
 440  
 441          // Create two users enrolled in the course as students.
 442          list($author, $recipient) = $this->helper_create_users($course, 2);
 443  
 444          // Unsubscribe the 'author' user from the forum.
 445          \mod_forum\subscriptions::unsubscribe_user($author->id, $forum);
 446  
 447          // Post a discussion to the forum.
 448          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 449  
 450          $expect = [
 451              (object) [
 452                  'userid' => $author->id,
 453                  'messages' => 0,
 454              ],
 455              (object) [
 456                  'userid' => $recipient->id,
 457                  'messages' => 1,
 458              ],
 459          ];
 460          $this->queue_tasks_and_assert($expect);
 461  
 462          $this->send_notifications_and_assert($author, []);
 463          $this->send_notifications_and_assert($recipient, [$post]);
 464      }
 465  
 466      public function test_optional_with_subscribed_user() {
 467          $this->resetAfterTest(true);
 468  
 469          // Create a course, with a forum.
 470          $course = $this->getDataGenerator()->create_course();
 471  
 472          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 473          $forum = $this->getDataGenerator()->create_module('forum', $options);
 474  
 475          // Create two users enrolled in the course as students.
 476          list($author, $recipient) = $this->helper_create_users($course, 2);
 477  
 478          // Subscribe the 'recipient' user from the forum.
 479          \mod_forum\subscriptions::subscribe_user($recipient->id, $forum);
 480  
 481          // Post a discussion to the forum.
 482          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 483  
 484          $expect = [
 485              (object) [
 486                  'userid' => $author->id,
 487                  'messages' => 0,
 488              ],
 489              (object) [
 490                  'userid' => $recipient->id,
 491                  'messages' => 1,
 492              ],
 493          ];
 494          $this->queue_tasks_and_assert($expect);
 495  
 496          $this->send_notifications_and_assert($author, []);
 497          $this->send_notifications_and_assert($recipient, [$post]);
 498      }
 499  
 500      public function test_automatic_with_unsubscribed_discussion() {
 501          $this->resetAfterTest(true);
 502  
 503          // Create a course, with a forum.
 504          $course = $this->getDataGenerator()->create_course();
 505  
 506          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
 507          $forum = $this->getDataGenerator()->create_module('forum', $options);
 508  
 509          // Create two users enrolled in the course as students.
 510          list($author, $recipient) = $this->helper_create_users($course, 2);
 511  
 512          // Post a discussion to the forum.
 513          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 514  
 515          // Unsubscribe the 'author' user from the discussion.
 516          \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion);
 517  
 518          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 519          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($recipient->id, $forum, $discussion->id));
 520  
 521          $expect = [
 522              (object) [
 523                  'userid' => $author->id,
 524                  'messages' => 0,
 525              ],
 526              (object) [
 527                  'userid' => $recipient->id,
 528                  'messages' => 1,
 529              ],
 530          ];
 531          $this->queue_tasks_and_assert($expect);
 532  
 533          $this->send_notifications_and_assert($author, []);
 534          $this->send_notifications_and_assert($recipient, [$post]);
 535      }
 536  
 537      public function test_optional_with_subscribed_discussion() {
 538          $this->resetAfterTest(true);
 539  
 540          // Create a course, with a forum.
 541          $course = $this->getDataGenerator()->create_course();
 542  
 543          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 544          $forum = $this->getDataGenerator()->create_module('forum', $options);
 545  
 546          // Create two users enrolled in the course as students.
 547          list($author, $recipient) = $this->helper_create_users($course, 2);
 548  
 549          // Post a discussion to the forum.
 550          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 551          $this->helper_update_post_time($post, -90);
 552  
 553          // Subscribe the 'recipient' user to the discussion.
 554          \mod_forum\subscriptions::subscribe_user_to_discussion($recipient->id, $discussion);
 555          $this->helper_update_subscription_time($recipient, $discussion, -60);
 556  
 557          // Initially we don't expect any user to receive this post as you cannot subscribe to a discussion until after
 558          // you have read it.
 559          $expect = [
 560              (object) [
 561                  'userid' => $author->id,
 562                  'messages' => 0,
 563              ],
 564              (object) [
 565                  'userid' => $recipient->id,
 566                  'messages' => 0,
 567              ],
 568          ];
 569          $this->queue_tasks_and_assert($expect);
 570  
 571          $this->send_notifications_and_assert($author, []);
 572          $this->send_notifications_and_assert($recipient, []);
 573  
 574          // Have a user reply to the discussion.
 575          $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
 576          $this->helper_update_post_time($reply, -30);
 577  
 578          // We expect only one user to receive this post.
 579          $expect = [
 580              (object) [
 581                  'userid' => $author->id,
 582                  'messages' => 0,
 583              ],
 584              (object) [
 585                  'userid' => $recipient->id,
 586                  'messages' => 1,
 587              ],
 588          ];
 589          $this->queue_tasks_and_assert($expect);
 590  
 591          $this->send_notifications_and_assert($author, []);
 592          $this->send_notifications_and_assert($recipient, [$reply]);
 593      }
 594  
 595      public function test_optional_with_subscribed_discussion_and_post() {
 596          $this->resetAfterTest(true);
 597  
 598          // Create a course, with a forum.
 599          $course = $this->getDataGenerator()->create_course();
 600  
 601          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 602          $forum = $this->getDataGenerator()->create_module('forum', $options);
 603  
 604          // Create two users enrolled in the course as students.
 605          list($author, $recipient) = $this->helper_create_users($course, 2);
 606  
 607          // Post a discussion to the forum.
 608          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 609          $this->helper_update_post_time($post, -90);
 610  
 611          // Have a user reply to the discussion before we subscribed.
 612          $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
 613          $this->helper_update_post_time($reply, -75);
 614  
 615          // Subscribe the 'recipient' user to the discussion.
 616          \mod_forum\subscriptions::subscribe_user_to_discussion($recipient->id, $discussion);
 617          $this->helper_update_subscription_time($recipient, $discussion, -60);
 618  
 619          // Have a user reply to the discussion.
 620          $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
 621          $this->helper_update_post_time($reply, -30);
 622  
 623          // We expect only one user to receive this post.
 624          // The original post won't be received as it was written before the user subscribed.
 625          $expect = [
 626              (object) [
 627                  'userid' => $author->id,
 628                  'messages' => 0,
 629              ],
 630              (object) [
 631                  'userid' => $recipient->id,
 632                  'messages' => 1,
 633              ],
 634          ];
 635          $this->queue_tasks_and_assert($expect);
 636  
 637          $this->send_notifications_and_assert($author, []);
 638          $this->send_notifications_and_assert($recipient, [$reply]);
 639      }
 640  
 641      public function test_automatic_with_subscribed_discussion_in_unsubscribed_forum() {
 642          $this->resetAfterTest(true);
 643  
 644          // Create a course, with a forum.
 645          $course = $this->getDataGenerator()->create_course();
 646  
 647          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
 648          $forum = $this->getDataGenerator()->create_module('forum', $options);
 649  
 650          // Create two users enrolled in the course as students.
 651          list($author, $recipient) = $this->helper_create_users($course, 2);
 652  
 653          // Post a discussion to the forum.
 654          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 655          $this->helper_update_post_time($post, -90);
 656  
 657          // Unsubscribe the 'author' user from the forum.
 658          \mod_forum\subscriptions::unsubscribe_user($author->id, $forum);
 659  
 660          // Then re-subscribe them to the discussion.
 661          \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion);
 662          $this->helper_update_subscription_time($author, $discussion, -60);
 663  
 664          $expect = [
 665              (object) [
 666                  'userid' => $author->id,
 667                  'messages' => 0,
 668              ],
 669              (object) [
 670                  'userid' => $recipient->id,
 671                  'messages' => 1,
 672              ],
 673          ];
 674          $this->queue_tasks_and_assert($expect);
 675  
 676          $this->send_notifications_and_assert($author, []);
 677          $this->send_notifications_and_assert($recipient, [$post]);
 678  
 679          // Now post a reply to the original post.
 680          $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
 681          $this->helper_update_post_time($reply, -30);
 682  
 683          $expect = [
 684              (object) [
 685                  'userid' => $author->id,
 686                  'messages' => 1,
 687              ],
 688              (object) [
 689                  'userid' => $recipient->id,
 690                  'messages' => 1,
 691              ],
 692          ];
 693          $this->queue_tasks_and_assert($expect);
 694  
 695          $this->send_notifications_and_assert($author, [$reply]);
 696          $this->send_notifications_and_assert($recipient, [$reply]);
 697      }
 698  
 699      public function test_optional_with_unsubscribed_discussion_in_subscribed_forum() {
 700          $this->resetAfterTest(true);
 701  
 702          // Create a course, with a forum.
 703          $course = $this->getDataGenerator()->create_course();
 704  
 705          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 706          $forum = $this->getDataGenerator()->create_module('forum', $options);
 707  
 708          // Create two users enrolled in the course as students.
 709          list($author, $recipient) = $this->helper_create_users($course, 2);
 710  
 711          // Post a discussion to the forum.
 712          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 713  
 714          // Unsubscribe the 'recipient' user from the discussion.
 715          \mod_forum\subscriptions::subscribe_user($recipient->id, $forum);
 716  
 717          // Then unsubscribe them from the discussion.
 718          \mod_forum\subscriptions::unsubscribe_user_from_discussion($recipient->id, $discussion);
 719  
 720          // We don't expect any users to receive this post.
 721          $expect = [
 722              (object) [
 723                  'userid' => $author->id,
 724                  'messages' => 0,
 725              ],
 726              (object) [
 727                  'userid' => $recipient->id,
 728                  'messages' => 0,
 729              ],
 730          ];
 731          $this->queue_tasks_and_assert($expect);
 732  
 733          $this->send_notifications_and_assert($author, []);
 734          $this->send_notifications_and_assert($recipient, []);
 735      }
 736  
 737      /**
 738       * Test that a user unsubscribed from a forum who has subscribed to a discussion, only receives posts made after
 739       * they subscribed to the discussion.
 740       */
 741      public function test_forum_discussion_subscription_forum_unsubscribed_discussion_subscribed_after_post() {
 742          $this->resetAfterTest(true);
 743  
 744          // Create a course, with a forum.
 745          $course = $this->getDataGenerator()->create_course();
 746  
 747          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 748          $forum = $this->getDataGenerator()->create_module('forum', $options);
 749  
 750          $expectedmessages = array();
 751  
 752          // Create a user enrolled in the course as a student.
 753          list($author) = $this->helper_create_users($course, 1);
 754  
 755          // Post a discussion to the forum.
 756          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 757          $this->helper_update_post_time($post, -90);
 758  
 759          $expectedmessages[] = array(
 760              'id' => $post->id,
 761              'subject' => $post->subject,
 762              'count' => 0,
 763          );
 764  
 765          // Then subscribe the user to the discussion.
 766          $this->assertTrue(\mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion));
 767          $this->helper_update_subscription_time($author, $discussion, -60);
 768  
 769          // Then post a reply to the first discussion.
 770          $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
 771          $this->helper_update_post_time($reply, -30);
 772  
 773          $expect = [
 774              (object) [
 775                  'userid' => $author->id,
 776                  'messages' => 1,
 777              ],
 778          ];
 779          $this->queue_tasks_and_assert($expect);
 780  
 781          $this->send_notifications_and_assert($author, [$reply]);
 782      }
 783  
 784      public function test_subscription_by_inactive_users() {
 785          global $DB;
 786          $this->resetAfterTest(true);
 787  
 788          $course = $this->getDataGenerator()->create_course();
 789  
 790          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 791          $forum = $this->getDataGenerator()->create_module('forum', $options);
 792  
 793          // Create two users enrolled in the course as students.
 794          list($author, $u1, $u2, $u3) = $this->helper_create_users($course, 4);
 795  
 796          // Subscribe the three users to the forum.
 797          \mod_forum\subscriptions::subscribe_user($u1->id, $forum);
 798          \mod_forum\subscriptions::subscribe_user($u2->id, $forum);
 799          \mod_forum\subscriptions::subscribe_user($u3->id, $forum);
 800  
 801          // Make the first user inactive - suspended.
 802          $DB->set_field('user', 'suspended', 1, ['id' => $u1->id]);
 803  
 804          // Make the second user inactive - unable to log in.
 805          $DB->set_field('user', 'auth', 'nologin', ['id' => $u2->id]);
 806  
 807          // Post a discussion to the forum.
 808          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 809  
 810          $expect = [
 811              (object) [
 812                  'userid' => $u1->id,
 813                  'messages' => 0,
 814              ],
 815              (object) [
 816                  'userid' => $u2->id,
 817                  'messages' => 0,
 818              ],
 819              (object) [
 820                  'userid' => $u3->id,
 821                  'messages' => 1,
 822              ],
 823          ];
 824  
 825          $this->queue_tasks_and_assert($expect);
 826          $this->send_notifications_and_assert($u1, []);
 827          $this->send_notifications_and_assert($u2, []);
 828          $this->send_notifications_and_assert($u3, [$post]);
 829      }
 830  
 831      public function test_forum_message_inbound_multiple_posts() {
 832          $this->resetAfterTest(true);
 833  
 834          // Create a course, with a forum.
 835          $course = $this->getDataGenerator()->create_course();
 836          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
 837          $forum = $this->getDataGenerator()->create_module('forum', $options);
 838  
 839          // Create a user enrolled in the course as a student.
 840          list($author) = $this->helper_create_users($course, 1);
 841  
 842          $expectedmessages = array();
 843  
 844          // Post a discussion to the forum.
 845          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 846          $this->helper_update_post_time($post, -90);
 847  
 848          $expectedmessages[] = (object) [
 849              'id' => $post->id,
 850              'subject' => $post->subject,
 851              'count' => 0,
 852          ];
 853  
 854          // Then post a reply to the first discussion.
 855          $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
 856          $this->helper_update_post_time($reply, -60);
 857  
 858          $expectedmessages[] = (object) [
 859              'id' => $reply->id,
 860              'subject' => $reply->subject,
 861              'count' => 1,
 862          ];
 863  
 864          // Ensure that messageinbound is enabled and configured for the forum handler.
 865          $this->helper_spoof_message_inbound_setup();
 866  
 867          $author->emailstop = '0';
 868          set_user_preference('message_provider_mod_forum_posts_loggedoff', 'email', $author);
 869          set_user_preference('message_provider_mod_forum_posts_loggedin', 'email', $author);
 870  
 871          // Run cron and check that the expected number of users received the notification.
 872          // Clear the mailsink, and close the messagesink.
 873          $this->mailsink->clear();
 874          $this->messagesink->close();
 875  
 876          $expect = [
 877              'author' => (object) [
 878                  'userid' => $author->id,
 879                  'messages' => count($expectedmessages),
 880              ],
 881          ];
 882          $this->queue_tasks_and_assert($expect);
 883  
 884          $this->send_notifications_and_assert($author, $expectedmessages);
 885          $messages = $this->mailsink->get_messages();
 886  
 887          // There should be the expected number of messages.
 888          $this->assertEquals(2, count($messages));
 889  
 890          foreach ($messages as $message) {
 891              $this->assertRegExp('/Reply-To: moodlemoodle123\+[^@]*@example.com/', $message->header);
 892          }
 893      }
 894  
 895      public function test_long_subject() {
 896          $this->resetAfterTest(true);
 897  
 898          // Create a course, with a forum.
 899          $course = $this->getDataGenerator()->create_course();
 900  
 901          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
 902          $forum = $this->getDataGenerator()->create_module('forum', $options);
 903  
 904          // Create a user enrolled in the course as student.
 905          list($author) = $this->helper_create_users($course, 1);
 906  
 907          // Post a discussion to the forum.
 908          $subject = 'This is the very long forum post subject that somebody was very kind of leaving, it is intended to check if long subject comes in mail correctly. Thank you.';
 909          $a = (object)array('courseshortname' => $course->shortname, 'forumname' => $forum->name, 'subject' => $subject);
 910          $expectedsubject = get_string('postmailsubject', 'forum', $a);
 911          list($discussion, $post) = $this->helper_post_to_forum($forum, $author, array('name' => $subject));
 912  
 913          // Run cron and check that the expected number of users received the notification.
 914          $expect = [
 915              'author' => (object) [
 916                  'userid' => $author->id,
 917                  'messages' => 1,
 918              ],
 919          ];
 920          $this->queue_tasks_and_assert($expect);
 921  
 922          $this->send_notifications_and_assert($author, [$post]);
 923          $messages = $this->messagesink->get_messages();
 924          $message = reset($messages);
 925          $this->assertEquals($author->id, $message->useridfrom);
 926          $this->assertEquals($expectedsubject, $message->subject);
 927      }
 928  
 929      /**
 930       * Test inital email and reply email subjects
 931       */
 932      public function test_subjects() {
 933          $this->resetAfterTest(true);
 934  
 935          $course = $this->getDataGenerator()->create_course();
 936  
 937          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
 938          $forum = $this->getDataGenerator()->create_module('forum', $options);
 939  
 940          list($author) = $this->helper_create_users($course, 1);
 941          list($commenter) = $this->helper_create_users($course, 1);
 942  
 943          $strre = get_string('re', 'forum');
 944  
 945          // New posts should not have Re: in the subject.
 946          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 947          $expect = [
 948              'author' => (object) [
 949                  'userid' => $author->id,
 950                  'messages' => 1,
 951              ],
 952              'commenter' => (object) [
 953                  'userid' => $commenter->id,
 954                  'messages' => 1,
 955              ],
 956          ];
 957          $this->queue_tasks_and_assert($expect);
 958  
 959          $this->send_notifications_and_assert($author, [$post]);
 960          $this->send_notifications_and_assert($commenter, [$post]);
 961          $messages = $this->messagesink->get_messages();
 962          $this->assertStringNotContainsString($strre, $messages[0]->subject);
 963          $this->messagesink->clear();
 964  
 965          // Replies should have Re: in the subject.
 966          $reply = $this->helper_post_to_discussion($forum, $discussion, $commenter);
 967  
 968          $expect = [
 969              'author' => (object) [
 970                  'userid' => $author->id,
 971                  'messages' => 1,
 972              ],
 973              'commenter' => (object) [
 974                  'userid' => $commenter->id,
 975                  'messages' => 1,
 976              ],
 977          ];
 978          $this->queue_tasks_and_assert($expect);
 979  
 980          $this->send_notifications_and_assert($commenter, [$reply]);
 981          $this->send_notifications_and_assert($author, [$reply]);
 982          $messages = $this->messagesink->get_messages();
 983          $this->assertStringContainsString($strre, $messages[0]->subject);
 984          $this->assertStringContainsString($strre, $messages[1]->subject);
 985      }
 986  
 987      /**
 988       * dataProvider for test_forum_post_email_templates().
 989       */
 990      public function forum_post_email_templates_provider() {
 991          // Base information, we'll build variations based on it.
 992          $base = array(
 993              'user' => array('firstname' => 'Love', 'lastname' => 'Moodle', 'mailformat' => 0, 'maildigest' => 0),
 994              'course' => array('shortname' => '101', 'fullname' => 'Moodle 101'),
 995              'forums' => array(
 996                  array(
 997                      'name' => 'Moodle Forum',
 998                      'forumposts' => array(
 999                          array(
1000                              'name' => 'Hello Moodle',
1001                              'message' => 'Welcome to Moodle',
1002                              'messageformat' => FORMAT_MOODLE,
1003                              'attachments' => array(
1004                                  array(
1005                                      'filename' => 'example.txt',
1006                                      'filecontents' => 'Basic information about the course'
1007                                  ),
1008                              ),
1009                          ),
1010                      ),
1011                  ),
1012              ),
1013              'expectations' => array(
1014                  array(
1015                      'subject' => '.*101.*Hello',
1016                      'contents' => array(
1017                          '~{$a',
1018                          '~&(amp|lt|gt|quot|\#039);(?!course)',
1019                          'Attachment example.txt:' . PHP_EOL .
1020                              'https://www.example.com/moodle/pluginfile.php/\d*/mod_forum/attachment/\d*/example.txt' . PHP_EOL,
1021                          'Hello Moodle', 'Moodle Forum', 'Welcome.*Moodle', 'Love Moodle', '1\d1'
1022                      ),
1023                  ),
1024              ),
1025          );
1026  
1027          // Build the text cases.
1028          $textcases = array('Text mail without ampersands, quotes or lt/gt' => array('data' => $base));
1029  
1030          // Single and double quotes everywhere.
1031          $newcase = $base;
1032          $newcase['user']['lastname'] = 'Moodle\'"';
1033          $newcase['course']['shortname'] = '101\'"';
1034          $newcase['forums'][0]['name'] = 'Moodle Forum\'"';
1035          $newcase['forums'][0]['forumposts'][0]['name'] = 'Hello Moodle\'"';
1036          $newcase['forums'][0]['forumposts'][0]['message'] = 'Welcome to Moodle\'"';
1037          $newcase['expectations'][0]['contents'] = array(
1038              'Attachment example.txt:', '~{\$a', '~&amp;(quot|\#039);', 'Love Moodle\'', '101\'', 'Moodle Forum\'"',
1039              'Hello Moodle\'"', 'Welcome to Moodle\'"');
1040          $textcases['Text mail with quotes everywhere'] = array('data' => $newcase);
1041  
1042          // Lt and gt everywhere. This case is completely borked because format_string()
1043          // strips tags with $CFG->formatstringstriptags and also escapes < and > (correct
1044          // for web presentation but not for text email). See MDL-19829.
1045          $newcase = $base;
1046          $newcase['user']['lastname'] = 'Moodle>';
1047          $newcase['course']['shortname'] = '101>';
1048          $newcase['forums'][0]['name'] = 'Moodle Forum>';
1049          $newcase['forums'][0]['forumposts'][0]['name'] = 'Hello Moodle>';
1050          $newcase['forums'][0]['forumposts'][0]['message'] = 'Welcome to Moodle>';
1051          $newcase['expectations'][0]['contents'] = array(
1052              'Attachment example.txt:', '~{\$a', '~&amp;gt;', 'Love Moodle>', '101>', 'Moodle Forum>',
1053              'Hello Moodle>', 'Welcome to Moodle>');
1054          $textcases['Text mail with gt and lt everywhere'] = array('data' => $newcase);
1055  
1056          // Ampersands everywhere. This case is completely borked because format_string()
1057          // escapes ampersands (correct for web presentation but not for text email). See MDL-19829.
1058          $newcase = $base;
1059          $newcase['user']['lastname'] = 'Moodle&';
1060          $newcase['course']['shortname'] = '101&';
1061          $newcase['forums'][0]['name'] = 'Moodle Forum&';
1062          $newcase['forums'][0]['forumposts'][0]['name'] = 'Hello Moodle&';
1063          $newcase['forums'][0]['forumposts'][0]['message'] = 'Welcome to Moodle&';
1064          $newcase['expectations'][0]['contents'] = array(
1065              'Attachment example.txt:', '~{\$a', '~&amp;amp;', 'Love Moodle&', '101&', 'Moodle Forum&',
1066              'Hello Moodle&', 'Welcome to Moodle&');
1067          $textcases['Text mail with ampersands everywhere'] = array('data' => $newcase);
1068  
1069          // Text+image message i.e. @@PLUGINFILE@@ token handling.
1070          $newcase = $base;
1071          $newcase['forums'][0]['forumposts'][0]['name'] = 'Text and image';
1072          $newcase['forums'][0]['forumposts'][0]['message'] = 'Welcome to Moodle, '
1073              .'@@PLUGINFILE@@/Screen%20Shot%202016-03-22%20at%205.54.36%20AM%20%281%29.png !';
1074          $newcase['expectations'][0]['subject'] = '.*101.*Text and image';
1075          $newcase['expectations'][0]['contents'] = array(
1076              '~{$a',
1077              '~&(amp|lt|gt|quot|\#039);(?!course)',
1078              'Attachment example.txt:' . PHP_EOL .
1079              'https://www.example.com/moodle/pluginfile.php/\d*/mod_forum/attachment/\d*/example.txt' .  PHP_EOL ,
1080              'Text and image', 'Moodle Forum',
1081              'Welcome to Moodle, *' . PHP_EOL . '.*'
1082                  .'https://www.example.com/moodle/pluginfile.php/\d+/mod_forum/post/\d+/'
1083                  .'Screen%20Shot%202016-03-22%20at%205\.54\.36%20AM%20%281%29\.png *' . PHP_EOL . '.*!',
1084              'Love Moodle', '1\d1');
1085          $textcases['Text mail with text+image message i.e. @@PLUGINFILE@@ token handling'] = array('data' => $newcase);
1086  
1087          // Now the html cases.
1088          $htmlcases = array();
1089  
1090          // New base for html cases, no quotes, lts, gts or ampersands.
1091          $htmlbase = $base;
1092          $htmlbase['user']['mailformat'] = 1;
1093          $htmlbase['expectations'][0]['contents'] = array(
1094              '~{\$a',
1095              '~&(amp|lt|gt|quot|\#039);(?!course|lang|version|iosappid|androidappid)',
1096              '<div class="attachments">( *\n *)?<a href',
1097              '<div class="subject">\n.*Hello Moodle', '>Moodle Forum', '>Welcome.*Moodle', '>Love Moodle', '>1\d1');
1098          $htmlcases['HTML mail without ampersands, quotes or lt/gt'] = array('data' => $htmlbase);
1099  
1100          // Single and double quotes, lt and gt, ampersands everywhere.
1101          $newcase = $htmlbase;
1102          $newcase['user']['lastname'] = 'Moodle\'">&';
1103          $newcase['course']['shortname'] = '101\'">&';
1104          $newcase['forums'][0]['name'] = 'Moodle Forum\'">&';
1105          $newcase['forums'][0]['forumposts'][0]['name'] = 'Hello Moodle\'">&';
1106          $newcase['forums'][0]['forumposts'][0]['message'] = 'Welcome to Moodle\'">&';
1107          $newcase['expectations'][0]['contents'] = array(
1108              '~{\$a',
1109              '~&amp;(amp|lt|gt|quot|\#039);',
1110              '<div class="attachments">( *\n *)?<a href',
1111              '<div class="subject">\n.*Hello Moodle\'"&gt;&amp;', '>Moodle Forum\'"&gt;&amp;',
1112              '>Welcome.*Moodle\'"&gt;&amp;', '>Love Moodle&\#039;&quot;&gt;&amp;', '>101\'"&gt;&amp');
1113          $htmlcases['HTML mail with quotes, gt, lt and ampersand  everywhere'] = array('data' => $newcase);
1114  
1115          // Text+image message i.e. @@PLUGINFILE@@ token handling.
1116          $newcase = $htmlbase;
1117          $newcase['forums'][0]['forumposts'][0]['name'] = 'HTML text and image';
1118          $newcase['forums'][0]['forumposts'][0]['message'] = '<p>Welcome to Moodle, '
1119              .'<img src="@@PLUGINFILE@@/Screen%20Shot%202016-03-22%20at%205.54.36%20AM%20%281%29.png"'
1120              .' alt="" width="200" height="393" class="img-fluid" />!</p>';
1121          $newcase['expectations'][0]['subject'] = '.*101.*HTML text and image';
1122          $newcase['expectations'][0]['contents'] = array(
1123              '~{\$a',
1124              '~&(amp|lt|gt|quot|\#039);(?!course|lang|version|iosappid|androidappid)',
1125              '<div class="attachments">( *\n *)?<a href',
1126              '<div class="subject">\n.*HTML text and image', '>Moodle Forum',
1127              '<p>Welcome to Moodle, '
1128              .'<img src="https://www.example.com/moodle/tokenpluginfile.php/[^/]*/\d+/mod_forum/post/\d+/'
1129                  .'Screen%20Shot%202016-03-22%20at%205\.54\.36%20AM%20%281%29\.png"'
1130                  .' alt="" width="200" height="393" class="img-fluid" />!</p>',
1131              '>Love Moodle', '>1\d1');
1132          $htmlcases['HTML mail with text+image message i.e. @@PLUGINFILE@@ token handling'] = array('data' => $newcase);
1133  
1134          return $textcases + $htmlcases;
1135      }
1136  
1137      /**
1138       * Verify forum emails body using templates to generate the expected results.
1139       *
1140       * @dataProvider forum_post_email_templates_provider
1141       * @param array $data provider samples.
1142       */
1143      public function test_forum_post_email_templates($data) {
1144          global $DB;
1145  
1146          $this->resetAfterTest();
1147  
1148          // Create the course, with the specified options.
1149          $options = array();
1150          foreach ($data['course'] as $option => $value) {
1151              $options[$option] = $value;
1152          }
1153          $course = $this->getDataGenerator()->create_course($options);
1154  
1155          // Create the user, with the specified options and enrol in the course.
1156          $options = array();
1157          foreach ($data['user'] as $option => $value) {
1158              $options[$option] = $value;
1159          }
1160          $user = $this->getDataGenerator()->create_user($options);
1161          $this->getDataGenerator()->enrol_user($user->id, $course->id);
1162  
1163          // Create forums, always force susbscribed (for easy), with the specified options.
1164          $posts = array();
1165          foreach ($data['forums'] as $dataforum) {
1166              $forumposts = isset($dataforum['forumposts']) ? $dataforum['forumposts'] : array();
1167              unset($dataforum['forumposts']);
1168              $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
1169              foreach ($dataforum as $option => $value) {
1170                  $options[$option] = $value;
1171              }
1172              $forum = $this->getDataGenerator()->create_module('forum', $options);
1173  
1174              // Create posts, always for immediate delivery (for easy), with the specified options.
1175              foreach ($forumposts as $forumpost) {
1176                  $attachments = isset($forumpost['attachments']) ? $forumpost['attachments'] : array();
1177                  unset($forumpost['attachments']);
1178                  $postoptions = array('course' => $course->id, 'forum' => $forum->id, 'userid' => $user->id,
1179                      'mailnow' => 1, 'attachment' => !empty($attachments));
1180                  foreach ($forumpost as $option => $value) {
1181                      $postoptions[$option] = $value;
1182                  }
1183                  list($discussion, $post) = $this->helper_post_to_forum($forum, $user, $postoptions);
1184                  $posts[$post->subject] = $post; // Need this to verify cron output.
1185  
1186                  // Add the attachments to the post.
1187                  if ($attachments) {
1188                      $fs = get_file_storage();
1189                      foreach ($attachments as $attachment) {
1190                          $filerecord = array(
1191                              'contextid' => context_module::instance($forum->cmid)->id,
1192                              'component' => 'mod_forum',
1193                              'filearea'  => 'attachment',
1194                              'itemid'    => $post->id,
1195                              'filepath'  => '/',
1196                              'filename'  => $attachment['filename']
1197                          );
1198                          $fs->create_file_from_string($filerecord, $attachment['filecontents']);
1199                      }
1200                      $DB->set_field('forum_posts', 'attachment', '1', array('id' => $post->id));
1201                  }
1202              }
1203          }
1204  
1205          // Clear the mailsink and close the messagesink.
1206          // (surely setup should provide us this cleared but...)
1207          $this->mailsink->clear();
1208          $this->messagesink->close();
1209  
1210          $expect = [
1211              'author' => (object) [
1212                  'userid' => $user->id,
1213                  'messages' => count($posts),
1214              ],
1215          ];
1216          $this->queue_tasks_and_assert($expect);
1217  
1218          $this->send_notifications_and_assert($user, $posts);
1219  
1220          // Get the mails.
1221          $mails = $this->mailsink->get_messages();
1222  
1223          // Start testing the expectations.
1224          $expectations = $data['expectations'];
1225  
1226          // Assert the number is the expected.
1227          $this->assertSame(count($expectations), count($mails));
1228  
1229          // Start processing mails, first localizing its expectations, then checking them.
1230          foreach ($mails as $mail) {
1231              // Find the corresponding expectation.
1232              $foundexpectation = null;
1233              foreach ($expectations as $key => $expectation) {
1234                  // All expectations must have a subject for matching.
1235                  if (!isset($expectation['subject'])) {
1236                      $this->fail('Provider expectation missing mandatory subject');
1237                  }
1238                  if (preg_match('!' . $expectation['subject'] . '!', $mail->subject)) {
1239                      // If we already had found the expectation, there are non-unique subjects. Fail.
1240                      if (isset($foundexpectation)) {
1241                          $this->fail('Multiple expectations found (by subject matching). Please make them unique.');
1242                      }
1243                      $foundexpectation = $expectation;
1244                      unset($expectations[$key]);
1245                  }
1246              }
1247              // Arrived here, we should have found the expectations.
1248              $this->assertNotEmpty($foundexpectation, 'Expectation not found for the mail');
1249  
1250              // If we have found the expectation and have contents to match, let's do it.
1251              if (isset($foundexpectation) and isset($foundexpectation['contents'])) {
1252                  $mail->body = quoted_printable_decode($mail->body);
1253                  if (!is_array($foundexpectation['contents'])) { // Accept both string and array.
1254                      $foundexpectation['contents'] = array($foundexpectation['contents']);
1255                  }
1256                  foreach ($foundexpectation['contents'] as $content) {
1257                      if (strpos($content, '~') !== 0) {
1258                          $this->assertRegexp('#' . $content . '#m', $mail->body);
1259                      } else {
1260                          preg_match('#' . substr($content, 1) . '#m', $mail->body, $matches);
1261                          $this->assertNotRegexp('#' . substr($content, 1) . '#m', $mail->body);
1262                      }
1263                  }
1264              }
1265          }
1266  
1267          // Finished, there should not be remaining expectations.
1268          $this->assertCount(0, $expectations);
1269      }
1270  
1271      /**
1272       * Ensure that posts already mailed are not re-sent.
1273       */
1274      public function test_already_mailed() {
1275          global $DB;
1276  
1277          $this->resetAfterTest(true);
1278  
1279          // Create a course, with a forum.
1280          $course = $this->getDataGenerator()->create_course();
1281  
1282          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
1283          $forum = $this->getDataGenerator()->create_module('forum', $options);
1284  
1285          // Create two users enrolled in the course as students.
1286          list($author, $recipient) = $this->helper_create_users($course, 2);
1287  
1288          // Post a discussion to the forum.
1289          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
1290          $DB->set_field('forum_posts', 'mailed', 1);
1291  
1292          // No posts shoudl be considered.
1293          $this->queue_tasks_and_assert([]);
1294  
1295          // No notifications should be queued.
1296          $this->send_notifications_and_assert($author, []);
1297          $this->send_notifications_and_assert($recipient, []);
1298      }
1299  
1300      /**
1301       * Ensure that posts marked mailnow are not suspect to the maxeditingtime.
1302       */
1303      public function test_mailnow() {
1304          global $CFG, $DB;
1305  
1306          // Update the maxeditingtime to 1 day so that posts won't be sent.
1307          $CFG->maxeditingtime = DAYSECS;
1308  
1309          $this->resetAfterTest(true);
1310  
1311          // Create a course, with a forum.
1312          $course = $this->getDataGenerator()->create_course();
1313  
1314          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
1315          $forum = $this->getDataGenerator()->create_module('forum', $options);
1316  
1317          // Create two users enrolled in the course as students.
1318          list($author, $recipient) = $this->helper_create_users($course, 2);
1319  
1320          // Post a discussion to the forum.
1321          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
1322  
1323          // Post a discussion to the forum.
1324          list($discussion, $postmailednow) = $this->helper_post_to_forum($forum, $author, ['mailnow' => 1]);
1325  
1326          // Only the mailnow post should be considered.
1327          $expect = [
1328              'author' => (object) [
1329                  'userid' => $author->id,
1330                  'messages' => 1,
1331              ],
1332              'recipient' => (object) [
1333                  'userid' => $recipient->id,
1334                  'messages' => 1,
1335              ],
1336          ];
1337          $this->queue_tasks_and_assert($expect);
1338  
1339          // No notifications should be queued.
1340          $this->send_notifications_and_assert($author, [$postmailednow]);
1341          $this->send_notifications_and_assert($recipient, [$postmailednow]);
1342      }
1343  
1344      /**
1345       * Ensure that if a user has no permission to view a post, then it is not sent.
1346       */
1347      public function test_access_coursemodule_hidden() {
1348          global $CFG, $DB;
1349  
1350          $this->resetAfterTest(true);
1351  
1352          // Create a course, with a forum.
1353          $course = $this->getDataGenerator()->create_course();
1354  
1355          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
1356          $forum = $this->getDataGenerator()->create_module('forum', $options);
1357  
1358          // Create two users enrolled in the course as students.
1359          list($author, $recipient) = $this->helper_create_users($course, 2);
1360  
1361          // Create one users enrolled in the course as an editing teacher.
1362          list($editor) = $this->helper_create_users($course, 1, 'editingteacher');
1363  
1364          // Post a discussion to the forum.
1365          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
1366  
1367          // Hide the coursemodule.
1368          set_coursemodule_visible($forum->cmid, 0);
1369  
1370          // Only the mailnow post should be considered.
1371          $expect = [
1372              'author' => (object) [
1373                  'userid' => $author->id,
1374                  'messages' => 1,
1375              ],
1376              'recipient' => (object) [
1377                  'userid' => $recipient->id,
1378                  'messages' => 1,
1379              ],
1380              'editor' => (object) [
1381                  'userid' => $editor->id,
1382                  'messages' => 1,
1383              ],
1384          ];
1385          $this->queue_tasks_and_assert($expect);
1386  
1387          // No notifications should be queued.
1388          $this->send_notifications_and_assert($author, [], true);
1389          $this->send_notifications_and_assert($recipient, [], true);
1390          $this->send_notifications_and_assert($editor, [$post], true);
1391      }
1392  
1393      /**
1394       * Ensure that if a user loses permission to view a post after it is queued, that it is not sent.
1395       */
1396      public function test_access_coursemodule_hidden_after_queue() {
1397          global $CFG, $DB;
1398  
1399          $this->resetAfterTest(true);
1400  
1401          // Create a course, with a forum.
1402          $course = $this->getDataGenerator()->create_course();
1403  
1404          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
1405          $forum = $this->getDataGenerator()->create_module('forum', $options);
1406  
1407          // Create two users enrolled in the course as students.
1408          list($author, $recipient) = $this->helper_create_users($course, 2);
1409  
1410          // Create one users enrolled in the course as an editing teacher.
1411          list($editor) = $this->helper_create_users($course, 1, 'editingteacher');
1412  
1413          // Post a discussion to the forum.
1414          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
1415  
1416          // Only the mailnow post should be considered.
1417          $expect = [
1418              'author' => (object) [
1419                  'userid' => $author->id,
1420                  'messages' => 1,
1421              ],
1422              'recipient' => (object) [
1423                  'userid' => $recipient->id,
1424                  'messages' => 1,
1425              ],
1426              'editor' => (object) [
1427                  'userid' => $editor->id,
1428                  'messages' => 1,
1429              ],
1430          ];
1431          $this->queue_tasks_and_assert($expect);
1432  
1433          // Hide the coursemodule.
1434          set_coursemodule_visible($forum->cmid, 0);
1435  
1436          // No notifications should be queued for the students.
1437          $this->send_notifications_and_assert($author, [], true);
1438          $this->send_notifications_and_assert($recipient, [], true);
1439  
1440          // The editing teacher should still receive the post.
1441          $this->send_notifications_and_assert($editor, [$post]);
1442      }
1443  
1444      /**
1445       * Ensure that messages are not sent until the timestart.
1446       */
1447      public function test_access_before_timestart() {
1448          global $CFG, $DB;
1449  
1450          $this->resetAfterTest(true);
1451  
1452          // Create a course, with a forum.
1453          $course = $this->getDataGenerator()->create_course();
1454  
1455          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
1456          $forum = $this->getDataGenerator()->create_module('forum', $options);
1457  
1458          // Create two users enrolled in the course as students.
1459          list($author, $recipient) = $this->helper_create_users($course, 2);
1460  
1461          // Create one users enrolled in the course as an editing teacher.
1462          list($editor) = $this->helper_create_users($course, 1, 'editingteacher');
1463  
1464          // Post a discussion to the forum.
1465          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
1466  
1467          // Update the discussion to have a timestart in the future.
1468          $DB->set_field('forum_discussions', 'timestart', time() + DAYSECS);
1469  
1470          // None should be sent.
1471          $this->queue_tasks_and_assert([]);
1472  
1473          // No notifications should be queued for any user.
1474          $this->send_notifications_and_assert($author, []);
1475          $this->send_notifications_and_assert($recipient, []);
1476          $this->send_notifications_and_assert($editor, []);
1477  
1478          // Update the discussion to have a timestart in the past.
1479          $DB->set_field('forum_discussions', 'timestart', time() - DAYSECS);
1480  
1481          // Now should be sent to all.
1482          $expect = [
1483              'author' => (object) [
1484                  'userid' => $author->id,
1485                  'messages' => 1,
1486              ],
1487              'recipient' => (object) [
1488                  'userid' => $recipient->id,
1489                  'messages' => 1,
1490              ],
1491              'editor' => (object) [
1492                  'userid' => $editor->id,
1493                  'messages' => 1,
1494              ],
1495          ];
1496          $this->queue_tasks_and_assert($expect);
1497  
1498          // No notifications should be queued for any user.
1499          $this->send_notifications_and_assert($author, [$post]);
1500          $this->send_notifications_and_assert($recipient, [$post]);
1501          $this->send_notifications_and_assert($editor, [$post]);
1502      }
1503  
1504      /**
1505       * Ensure that messages are not sent after the timeend.
1506       */
1507      public function test_access_after_timeend() {
1508          global $CFG, $DB;
1509  
1510          $this->resetAfterTest(true);
1511  
1512          // Create a course, with a forum.
1513          $course = $this->getDataGenerator()->create_course();
1514  
1515          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
1516          $forum = $this->getDataGenerator()->create_module('forum', $options);
1517  
1518          // Create two users enrolled in the course as students.
1519          list($author, $recipient) = $this->helper_create_users($course, 2);
1520  
1521          // Create one users enrolled in the course as an editing teacher.
1522          list($editor) = $this->helper_create_users($course, 1, 'editingteacher');
1523  
1524          // Post a discussion to the forum.
1525          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
1526  
1527          // Update the discussion to have a timestart in the past.
1528          $DB->set_field('forum_discussions', 'timeend', time() - DAYSECS);
1529  
1530          // None should be sent.
1531          $this->queue_tasks_and_assert([]);
1532  
1533          // No notifications should be queued for any user.
1534          $this->send_notifications_and_assert($author, []);
1535          $this->send_notifications_and_assert($recipient, []);
1536          $this->send_notifications_and_assert($editor, []);
1537  
1538          // Update the discussion to have a timestart in the past.
1539          $DB->set_field('forum_discussions', 'timeend', time() + DAYSECS);
1540  
1541          // Now should be sent to all.
1542          $expect = [
1543              'author' => (object) [
1544                  'userid' => $author->id,
1545                  'messages' => 1,
1546              ],
1547              'recipient' => (object) [
1548                  'userid' => $recipient->id,
1549                  'messages' => 1,
1550              ],
1551              'editor' => (object) [
1552                  'userid' => $editor->id,
1553                  'messages' => 1,
1554              ],
1555          ];
1556          $this->queue_tasks_and_assert($expect);
1557  
1558          // No notifications should be queued for any user.
1559          $this->send_notifications_and_assert($author, [$post]);
1560          $this->send_notifications_and_assert($recipient, [$post]);
1561          $this->send_notifications_and_assert($editor, [$post]);
1562      }
1563  
1564      /**
1565       * Test notification comes with customdata.
1566       */
1567      public function test_notification_customdata() {
1568          $this->resetAfterTest(true);
1569  
1570          $course = $this->getDataGenerator()->create_course();
1571  
1572          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
1573          $forum = $this->getDataGenerator()->create_module('forum', $options);
1574  
1575          list($author) = $this->helper_create_users($course, 1);
1576          list($commenter) = $this->helper_create_users($course, 1);
1577  
1578          $strre = get_string('re', 'forum');
1579  
1580          // New posts should not have Re: in the subject.
1581          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
1582          $expect = [
1583              'author' => (object) [
1584                  'userid' => $author->id,
1585                  'messages' => 1,
1586              ],
1587              'commenter' => (object) [
1588                  'userid' => $commenter->id,
1589                  'messages' => 1,
1590              ],
1591          ];
1592          $this->queue_tasks_and_assert($expect);
1593  
1594          $this->send_notifications_and_assert($author, [$post]);
1595          $this->send_notifications_and_assert($commenter, [$post]);
1596          $messages = $this->messagesink->get_messages();
1597          $customdata = json_decode($messages[0]->customdata);
1598          $this->assertEquals($forum->id, $customdata->instance);
1599          $this->assertEquals($forum->cmid, $customdata->cmid);
1600          $this->assertEquals($post->id, $customdata->postid);
1601          $this->assertEquals($discussion->id, $customdata->discussionid);
1602          $this->assertObjectHasAttribute('notificationiconurl', $customdata);
1603          $this->assertObjectHasAttribute('actionbuttons', $customdata);
1604          $this->assertCount(1, (array) $customdata->actionbuttons);
1605      }
1606  }