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