Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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

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