2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * The forum module mail generation tests.
22 * @copyright 2013 Andrew Nicols
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') || die();
30 class mod_forum_mail_testcase extends advanced_testcase {
34 public function setUp() {
35 // We must clear the subscription caches. This has to be done both before each test, and after in case of other
36 // tests using these functions.
37 \mod_forum\subscriptions::reset_forum_cache();
38 \mod_forum\subscriptions::reset_discussion_cache();
41 require_once($CFG->dirroot . '/mod/forum/lib.php');
43 $helper = new stdClass();
45 // Messaging is not compatible with transactions...
46 $this->preventResetByRollback();
48 // Catch all messages.
49 $helper->messagesink = $this->redirectMessages();
50 $helper->mailsink = $this->redirectEmails();
52 // Confirm that we have an empty message sink so far.
53 $messages = $helper->messagesink->get_messages();
54 $this->assertEquals(0, count($messages));
56 $messages = $helper->mailsink->get_messages();
57 $this->assertEquals(0, count($messages));
59 // Forcibly reduce the maxeditingtime to a second in the past to
60 // ensure that messages are sent out.
61 $CFG->maxeditingtime = -1;
63 $this->helper = $helper;
66 public function tearDown() {
67 // We must clear the subscription caches. This has to be done both before each test, and after in case of other
68 // tests using these functions.
69 \mod_forum\subscriptions::reset_forum_cache();
71 $this->helper->messagesink->clear();
72 $this->helper->messagesink->close();
74 $this->helper->mailsink->clear();
75 $this->helper->mailsink->close();
79 * Perform message inbound setup for the mod_forum reply handler.
81 protected function helper_spoof_message_inbound_setup() {
83 // Setup the default Inbound Message mailbox settings.
84 $CFG->messageinbound_domain = 'example.com';
85 $CFG->messageinbound_enabled = true;
87 // Must be no longer than 15 characters.
88 $CFG->messageinbound_mailbox = 'moodlemoodle123';
90 $record = $DB->get_record('messageinbound_handlers', array('classname' => '\mod_forum\message\inbound\reply_handler'));
91 $record->enabled = true;
92 $record->id = $DB->update_record('messageinbound_handlers', $record);
96 * Helper to create the required number of users in the specified
98 * Users are enrolled as students.
100 * @param stdClass $course The course object
101 * @param integer $count The number of users to create
102 * @return array The users created
104 protected function helper_create_users($course, $count) {
107 for ($i = 0; $i < $count; $i++) {
108 $user = $this->getDataGenerator()->create_user();
109 $this->getDataGenerator()->enrol_user($user->id, $course->id);
117 * Create a new discussion and post within the specified forum, as the
120 * @param stdClass $forum The forum to post in
121 * @param stdClass $author The author to post as
122 * @param array $fields any other fields in discussion (name, message, messageformat, ...)
123 * @param array An array containing the discussion object, and the post object
125 protected function helper_post_to_forum($forum, $author, $fields = array()) {
127 $generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
129 // Create a discussion in the forum, and then add a post to that discussion.
130 $record = (object)$fields;
131 $record->course = $forum->course;
132 $record->userid = $author->id;
133 $record->forum = $forum->id;
134 $discussion = $generator->create_discussion($record);
136 // Retrieve the post which was created by create_discussion.
137 $post = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
139 return array($discussion, $post);
143 * Update the post time for the specified post by $factor.
145 * @param stdClass $post The post to update
146 * @param int $factor The amount to update by
148 protected function helper_update_post_time($post, $factor) {
151 // Update the post to have a created in the past.
152 $DB->set_field('forum_posts', 'created', $post->created + $factor, array('id' => $post->id));
156 * Update the subscription time for the specified user/discussion by $factor.
158 * @param stdClass $user The user to update
159 * @param stdClass $discussion The discussion to update for this user
160 * @param int $factor The amount to update by
162 protected function helper_update_subscription_time($user, $discussion, $factor) {
165 $sub = $DB->get_record('forum_discussion_subs', array('userid' => $user->id, 'discussion' => $discussion->id));
167 // Update the subscription to have a preference in the past.
168 $DB->set_field('forum_discussion_subs', 'preference', $sub->preference + $factor, array('id' => $sub->id));
172 * Create a new post within an existing discussion, as the specified author.
174 * @param stdClass $forum The forum to post in
175 * @param stdClass $discussion The discussion to post in
176 * @param stdClass $author The author to post as
177 * @return stdClass The forum post
179 protected function helper_post_to_discussion($forum, $discussion, $author) {
182 $generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
184 // Add a post to the discussion.
185 $record = new stdClass();
186 $record->course = $forum->course;
187 $record->userid = $author->id;
188 $record->forum = $forum->id;
189 $record->discussion = $discussion->id;
190 $record->mailnow = 1;
192 $post = $generator->create_post($record);
198 * Run the forum cron, and check that the specified post was sent the
199 * specified number of times.
201 * @param stdClass $post The forum post object
202 * @param integer $expected The number of times that the post should have been sent
203 * @return array An array of the messages caught by the message sink
205 protected function helper_run_cron_check_count($post, $expected) {
207 // Clear the sinks before running cron.
208 $this->helper->messagesink->clear();
209 $this->helper->mailsink->clear();
211 // Cron daily uses mtrace, turn on buffering to silence output.
212 $this->expectOutputRegex("/{$expected} users were sent post {$post->id}, '{$post->subject}'/");
215 // Now check the results in the message sink.
216 $messages = $this->helper->messagesink->get_messages();
218 // There should be the expected number of messages.
219 $this->assertEquals($expected, count($messages));
225 * Run the forum cron, and check that the specified posts were sent the
226 * specified number of times.
228 * @param stdClass $post The forum post object
229 * @param integer $expected The number of times that the post should have been sent
230 * @return array An array of the messages caught by the message sink
232 protected function helper_run_cron_check_counts($posts, $expected) {
234 // Clear the sinks before running cron.
235 $this->helper->messagesink->clear();
236 $this->helper->mailsink->clear();
238 // Cron daily uses mtrace, turn on buffering to silence output.
239 foreach ($posts as $post) {
240 $this->expectOutputRegex("/{$post['count']} users were sent post {$post['id']}, '{$post['subject']}'/");
244 // Now check the results in the message sink.
245 $messages = $this->helper->messagesink->get_messages();
247 // There should be the expected number of messages.
248 $this->assertEquals($expected, count($messages));
253 public function test_forced_subscription() {
254 $this->resetAfterTest(true);
256 // Create a course, with a forum.
257 $course = $this->getDataGenerator()->create_course();
259 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
260 $forum = $this->getDataGenerator()->create_module('forum', $options);
262 // Create two users enrolled in the course as students.
263 list($author, $recipient) = $this->helper_create_users($course, 2);
265 // Post a discussion to the forum.
266 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
268 // We expect both users to receive this post.
271 // Run cron and check that the expected number of users received the notification.
272 $messages = $this->helper_run_cron_check_count($post, $expected);
275 $seenrecipient = false;
276 foreach ($messages as $message) {
277 // They should both be from our user.
278 $this->assertEquals($author->id, $message->useridfrom);
280 if ($message->useridto == $author->id) {
282 } else if ($message->useridto = $recipient->id) {
283 $seenrecipient = true;
287 // Check we saw messages for both users.
288 $this->assertTrue($seenauthor);
289 $this->assertTrue($seenrecipient);
292 public function test_subscription_disabled() {
295 $this->resetAfterTest(true);
297 // Create a course, with a forum.
298 $course = $this->getDataGenerator()->create_course();
300 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_DISALLOWSUBSCRIBE);
301 $forum = $this->getDataGenerator()->create_module('forum', $options);
303 // Create two users enrolled in the course as students.
304 list($author, $recipient) = $this->helper_create_users($course, 2);
306 // Post a discussion to the forum.
307 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
309 // We expect both users to receive this post.
312 // Run cron and check that the expected number of users received the notification.
313 $messages = $this->helper_run_cron_check_count($post, $expected);
315 // A user with the manageactivities capability within the course can subscribe.
317 $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
318 assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleids['student'], context_course::instance($course->id));
319 \mod_forum\subscriptions::subscribe_user($recipient->id, $forum);
321 $this->assertEquals($expected, $DB->count_records('forum_subscriptions', array(
322 'userid' => $recipient->id,
323 'forum' => $forum->id,
326 // Run cron and check that the expected number of users received the notification.
327 list($discussion, $post) = $this->helper_post_to_forum($forum, $recipient);
328 $messages = $this->helper_run_cron_check_count($post, $expected);
330 // Unsubscribe the user again.
331 \mod_forum\subscriptions::unsubscribe_user($recipient->id, $forum);
334 $this->assertEquals($expected, $DB->count_records('forum_subscriptions', array(
335 'userid' => $recipient->id,
336 'forum' => $forum->id,
339 // Run cron and check that the expected number of users received the notification.
340 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
341 $messages = $this->helper_run_cron_check_count($post, $expected);
343 // Subscribe the user to the discussion.
344 \mod_forum\subscriptions::subscribe_user_to_discussion($recipient->id, $discussion);
345 $this->helper_update_subscription_time($recipient, $discussion, -60);
347 $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
348 $this->helper_update_post_time($reply, -30);
350 $messages = $this->helper_run_cron_check_count($reply, $expected);
353 public function test_automatic() {
354 $this->resetAfterTest(true);
356 // Create a course, with a forum.
357 $course = $this->getDataGenerator()->create_course();
359 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
360 $forum = $this->getDataGenerator()->create_module('forum', $options);
362 // Create two users enrolled in the course as students.
363 list($author, $recipient) = $this->helper_create_users($course, 2);
365 // Post a discussion to the forum.
366 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
368 // We expect both users to receive this post.
371 // Run cron and check that the expected number of users received the notification.
372 $messages = $this->helper_run_cron_check_count($post, $expected);
375 $seenrecipient = false;
376 foreach ($messages as $message) {
377 // They should both be from our user.
378 $this->assertEquals($author->id, $message->useridfrom);
380 if ($message->useridto == $author->id) {
382 } else if ($message->useridto = $recipient->id) {
383 $seenrecipient = true;
387 // Check we saw messages for both users.
388 $this->assertTrue($seenauthor);
389 $this->assertTrue($seenrecipient);
392 public function test_optional() {
393 $this->resetAfterTest(true);
395 // Create a course, with a forum.
396 $course = $this->getDataGenerator()->create_course();
398 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
399 $forum = $this->getDataGenerator()->create_module('forum', $options);
401 // Create two users enrolled in the course as students.
402 list($author, $recipient) = $this->helper_create_users($course, 2);
404 // Post a discussion to the forum.
405 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
407 // We expect both users to receive this post.
410 // Run cron and check that the expected number of users received the notification.
411 $messages = $this->helper_run_cron_check_count($post, $expected);
414 public function test_automatic_with_unsubscribed_user() {
415 $this->resetAfterTest(true);
417 // Create a course, with a forum.
418 $course = $this->getDataGenerator()->create_course();
420 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
421 $forum = $this->getDataGenerator()->create_module('forum', $options);
423 // Create two users enrolled in the course as students.
424 list($author, $recipient) = $this->helper_create_users($course, 2);
426 // Unsubscribe the 'author' user from the forum.
427 \mod_forum\subscriptions::unsubscribe_user($author->id, $forum);
429 // Post a discussion to the forum.
430 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
432 // We expect only one user to receive this post.
435 // Run cron and check that the expected number of users received the notification.
436 $messages = $this->helper_run_cron_check_count($post, $expected);
439 $seenrecipient = false;
440 foreach ($messages as $message) {
441 // They should both be from our user.
442 $this->assertEquals($author->id, $message->useridfrom);
444 if ($message->useridto == $author->id) {
446 } else if ($message->useridto = $recipient->id) {
447 $seenrecipient = true;
451 // Check we only saw one user.
452 $this->assertFalse($seenauthor);
453 $this->assertTrue($seenrecipient);
456 public function test_optional_with_subscribed_user() {
457 $this->resetAfterTest(true);
459 // Create a course, with a forum.
460 $course = $this->getDataGenerator()->create_course();
462 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
463 $forum = $this->getDataGenerator()->create_module('forum', $options);
465 // Create two users enrolled in the course as students.
466 list($author, $recipient) = $this->helper_create_users($course, 2);
468 // Subscribe the 'recipient' user from the forum.
469 \mod_forum\subscriptions::subscribe_user($recipient->id, $forum);
471 // Post a discussion to the forum.
472 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
474 // We expect only one user to receive this post.
477 // Run cron and check that the expected number of users received the notification.
478 $messages = $this->helper_run_cron_check_count($post, $expected);
481 $seenrecipient = false;
482 foreach ($messages as $message) {
483 // They should both be from our user.
484 $this->assertEquals($author->id, $message->useridfrom);
486 if ($message->useridto == $author->id) {
488 } else if ($message->useridto = $recipient->id) {
489 $seenrecipient = true;
493 // Check we only saw one user.
494 $this->assertFalse($seenauthor);
495 $this->assertTrue($seenrecipient);
498 public function test_automatic_with_unsubscribed_discussion() {
499 $this->resetAfterTest(true);
501 // Create a course, with a forum.
502 $course = $this->getDataGenerator()->create_course();
504 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
505 $forum = $this->getDataGenerator()->create_module('forum', $options);
507 // Create two users enrolled in the course as students.
508 list($author, $recipient) = $this->helper_create_users($course, 2);
510 // Post a discussion to the forum.
511 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
513 // Unsubscribe the 'author' user from the discussion.
514 \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion);
516 $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
517 $this->assertTrue(\mod_forum\subscriptions::is_subscribed($recipient->id, $forum, $discussion->id));
519 // We expect only one user to receive this post.
522 // Run cron and check that the expected number of users received the notification.
523 $messages = $this->helper_run_cron_check_count($post, $expected);
526 $seenrecipient = false;
527 foreach ($messages as $message) {
528 // They should both be from our user.
529 $this->assertEquals($author->id, $message->useridfrom);
531 if ($message->useridto == $author->id) {
533 } else if ($message->useridto = $recipient->id) {
534 $seenrecipient = true;
538 // Check we only saw one user.
539 $this->assertFalse($seenauthor);
540 $this->assertTrue($seenrecipient);
543 public function test_optional_with_subscribed_discussion() {
544 $this->resetAfterTest(true);
546 // Create a course, with a forum.
547 $course = $this->getDataGenerator()->create_course();
549 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
550 $forum = $this->getDataGenerator()->create_module('forum', $options);
552 // Create two users enrolled in the course as students.
553 list($author, $recipient) = $this->helper_create_users($course, 2);
555 // Post a discussion to the forum.
556 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
557 $this->helper_update_post_time($post, -90);
559 // Subscribe the 'recipient' user to the discussion.
560 \mod_forum\subscriptions::subscribe_user_to_discussion($recipient->id, $discussion);
561 $this->helper_update_subscription_time($recipient, $discussion, -60);
563 // Initially we don't expect any user to receive this post as you cannot subscribe to a discussion until after
567 // Run cron and check that the expected number of users received the notification.
568 $messages = $this->helper_run_cron_check_count($post, $expected);
570 // Have a user reply to the discussion.
571 $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
572 $this->helper_update_post_time($reply, -30);
574 // We expect only one user to receive this post.
577 // Run cron and check that the expected number of users received the notification.
578 $messages = $this->helper_run_cron_check_count($reply, $expected);
581 $seenrecipient = false;
582 foreach ($messages as $message) {
583 // They should both be from our user.
584 $this->assertEquals($author->id, $message->useridfrom);
586 if ($message->useridto == $author->id) {
588 } else if ($message->useridto = $recipient->id) {
589 $seenrecipient = true;
593 // Check we only saw one user.
594 $this->assertFalse($seenauthor);
595 $this->assertTrue($seenrecipient);
598 public function test_automatic_with_subscribed_discussion_in_unsubscribed_forum() {
599 $this->resetAfterTest(true);
601 // Create a course, with a forum.
602 $course = $this->getDataGenerator()->create_course();
604 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
605 $forum = $this->getDataGenerator()->create_module('forum', $options);
607 // Create two users enrolled in the course as students.
608 list($author, $recipient) = $this->helper_create_users($course, 2);
610 // Post a discussion to the forum.
611 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
612 $this->helper_update_post_time($post, -90);
614 // Unsubscribe the 'author' user from the forum.
615 \mod_forum\subscriptions::unsubscribe_user($author->id, $forum);
617 // Then re-subscribe them to the discussion.
618 \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion);
619 $this->helper_update_subscription_time($author, $discussion, -60);
621 // We expect just the user subscribed to the forum to receive this post at the moment as the discussion
622 // subscription time is after the post time.
625 // Run cron and check that the expected number of users received the notification.
626 $messages = $this->helper_run_cron_check_count($post, $expected);
629 $seenrecipient = false;
630 foreach ($messages as $message) {
631 // They should both be from our user.
632 $this->assertEquals($author->id, $message->useridfrom);
634 if ($message->useridto == $author->id) {
636 } else if ($message->useridto = $recipient->id) {
637 $seenrecipient = true;
641 // Check we only saw one user.
642 $this->assertFalse($seenauthor);
643 $this->assertTrue($seenrecipient);
645 // Now post a reply to the original post.
646 $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
647 $this->helper_update_post_time($reply, -30);
649 // We expect two users to receive this post.
652 // Run cron and check that the expected number of users received the notification.
653 $messages = $this->helper_run_cron_check_count($reply, $expected);
656 $seenrecipient = false;
657 foreach ($messages as $message) {
658 // They should both be from our user.
659 $this->assertEquals($author->id, $message->useridfrom);
661 if ($message->useridto == $author->id) {
663 } else if ($message->useridto = $recipient->id) {
664 $seenrecipient = true;
668 // Check we saw both users.
669 $this->assertTrue($seenauthor);
670 $this->assertTrue($seenrecipient);
673 public function test_optional_with_unsubscribed_discussion_in_subscribed_forum() {
674 $this->resetAfterTest(true);
676 // Create a course, with a forum.
677 $course = $this->getDataGenerator()->create_course();
679 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
680 $forum = $this->getDataGenerator()->create_module('forum', $options);
682 // Create two users enrolled in the course as students.
683 list($author, $recipient) = $this->helper_create_users($course, 2);
685 // Post a discussion to the forum.
686 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
688 // Unsubscribe the 'recipient' user from the discussion.
689 \mod_forum\subscriptions::subscribe_user($recipient->id, $forum);
691 // Then unsubscribe them from the discussion.
692 \mod_forum\subscriptions::unsubscribe_user_from_discussion($recipient->id, $discussion);
694 // We don't expect any users to receive this post.
697 // Run cron and check that the expected number of users received the notification.
698 $messages = $this->helper_run_cron_check_count($post, $expected);
702 * Test that a user unsubscribed from a forum who has subscribed to a discussion, only receives posts made after
703 * they subscribed to the discussion.
705 public function test_forum_discussion_subscription_forum_unsubscribed_discussion_subscribed_after_post() {
706 $this->resetAfterTest(true);
708 // Create a course, with a forum.
709 $course = $this->getDataGenerator()->create_course();
711 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
712 $forum = $this->getDataGenerator()->create_module('forum', $options);
714 $expectedmessages = array();
716 // Create a user enrolled in the course as a student.
717 list($author) = $this->helper_create_users($course, 1);
719 // Post a discussion to the forum.
720 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
721 $this->helper_update_post_time($post, -90);
723 $expectedmessages[] = array(
725 'subject' => $post->subject,
729 // Then subscribe the user to the discussion.
730 $this->assertTrue(\mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion));
731 $this->helper_update_subscription_time($author, $discussion, -60);
733 // Then post a reply to the first discussion.
734 $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
735 $this->helper_update_post_time($reply, -30);
737 $expectedmessages[] = array(
739 'subject' => $reply->subject,
745 // Run cron and check that the expected number of users received the notification.
746 $messages = $this->helper_run_cron_check_counts($expectedmessages, $expectedcount);
749 public function test_forum_message_inbound_multiple_posts() {
750 $this->resetAfterTest(true);
752 // Create a course, with a forum.
753 $course = $this->getDataGenerator()->create_course();
754 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
755 $forum = $this->getDataGenerator()->create_module('forum', $options);
757 // Create a user enrolled in the course as a student.
758 list($author) = $this->helper_create_users($course, 1);
760 $expectedmessages = array();
762 // Post a discussion to the forum.
763 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
764 $this->helper_update_post_time($post, -90);
766 $expectedmessages[] = array(
768 'subject' => $post->subject,
772 // Then post a reply to the first discussion.
773 $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
774 $this->helper_update_post_time($reply, -60);
776 $expectedmessages[] = array(
778 'subject' => $reply->subject,
784 // Ensure that messageinbound is enabled and configured for the forum handler.
785 $this->helper_spoof_message_inbound_setup();
787 $author->emailstop = '0';
788 set_user_preference('message_provider_mod_forum_posts_loggedoff', 'email', $author);
789 set_user_preference('message_provider_mod_forum_posts_loggedin', 'email', $author);
791 // Run cron and check that the expected number of users received the notification.
792 // Clear the mailsink, and close the messagesink.
793 $this->helper->mailsink->clear();
794 $this->helper->messagesink->close();
796 // Cron daily uses mtrace, turn on buffering to silence output.
797 foreach ($expectedmessages as $post) {
798 $this->expectOutputRegex("/{$post['count']} users were sent post {$post['id']}, '{$post['subject']}'/");
802 $messages = $this->helper->mailsink->get_messages();
804 // There should be the expected number of messages.
805 $this->assertEquals($expectedcount, count($messages));
807 foreach ($messages as $message) {
808 $this->assertRegExp('/Reply-To: moodlemoodle123\+[^@]*@example.com/', $message->header);
812 public function test_long_subject() {
813 $this->resetAfterTest(true);
815 // Create a course, with a forum.
816 $course = $this->getDataGenerator()->create_course();
818 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
819 $forum = $this->getDataGenerator()->create_module('forum', $options);
821 // Create a user enrolled in the course as student.
822 list($author) = $this->helper_create_users($course, 1);
824 // Post a discussion to the forum.
825 $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.';
826 $a = (object)array('courseshortname' => $course->shortname, 'forumname' => $forum->name, 'subject' => $subject);
827 $expectedsubject = get_string('postmailsubject', 'forum', $a);
828 list($discussion, $post) = $this->helper_post_to_forum($forum, $author, array('name' => $subject));
830 // Run cron and check that the expected number of users received the notification.
831 $messages = $this->helper_run_cron_check_count($post, 1);
832 $message = reset($messages);
833 $this->assertEquals($author->id, $message->useridfrom);
834 $this->assertEquals($expectedsubject, $message->subject);
838 * dataProvider for test_forum_post_email_templates().
840 public function forum_post_email_templates_provider() {
841 // Base information, we'll build variations based on it.
843 'user' => array('firstname' => 'Love', 'lastname' => 'Moodle', 'mailformat' => 0, 'maildigest' => 0),
844 'course' => array('shortname' => '101', 'fullname' => 'Moodle 101'),
847 'name' => 'Moodle Forum',
848 'forumposts' => array(
850 'name' => 'Hello Moodle',
851 'message' => 'Welcome to Moodle',
852 'messageformat' => FORMAT_MOODLE,
853 'attachments' => array(
855 'filename' => 'example.txt',
856 'filecontents' => 'Basic information about the course'
863 'expectations' => array(
865 'subject' => '.*101.*Hello',
868 '~&(amp|lt|gt|quot|\#039);(?!course)',
869 'Attachment example.txt:\n' .
870 'http://www.example.com/moodle/pluginfile.php/\d*/mod_forum/attachment/\d*/example.txt\n',
871 'Hello Moodle', 'Moodle Forum', 'Welcome.*Moodle', 'Love Moodle', '1\d1'
877 // Build the text cases.
878 $textcases = array('Text mail without ampersands, quotes or lt/gt' => array('data' => $base));
880 // Single and double quotes everywhere.
882 $newcase['user']['lastname'] = 'Moodle\'';
883 // $newcase['user']['lastname'] = 'Moodle\'"'; // TODO: This breaks badly. See MDL-52136.
884 $newcase['course']['shortname'] = '101\'';
885 // $newcase['course']['shortname'] = '101\'"'; // TODO: This breaks badly. See MDL-52136.
886 $newcase['forums'][0]['name'] = 'Moodle Forum\'"';
887 $newcase['forums'][0]['forumposts'][0]['name'] = 'Hello Moodle\'"';
888 $newcase['forums'][0]['forumposts'][0]['message'] = 'Welcome to Moodle\'"';
889 $newcase['expectations'][0]['contents'] = array(
890 'Attachment example.txt:', '~{\$a', '~&(quot|\#039);', 'Love Moodle\'', '101\'', 'Moodle Forum\'"',
891 'Hello Moodle\'"', 'Welcome to Moodle\'"');
892 $textcases['Text mail with quotes everywhere'] = array('data' => $newcase);
894 // Lt and gt everywhere. This case is completely borked because format_string()
895 // strips tags with $CFG->formatstringstriptags and also escapes < and > (correct
896 // for web presentation but not for text email). See MDL-19829.
898 $newcase['user']['lastname'] = 'Moodle>';
899 $newcase['course']['shortname'] = '101>';
900 $newcase['forums'][0]['name'] = 'Moodle Forum>';
901 $newcase['forums'][0]['forumposts'][0]['name'] = 'Hello Moodle>';
902 $newcase['forums'][0]['forumposts'][0]['message'] = 'Welcome to Moodle>';
903 $newcase['expectations'][0]['contents'] = array(
904 'Attachment example.txt:', '~{\$a', '~&gt;', 'Love Moodle>', '101>', 'Moodle Forum>',
905 'Hello Moodle>', 'Welcome to Moodle>');
906 $textcases['Text mail with gt and lt everywhere'] = array('data' => $newcase);
908 // Ampersands everywhere. This case is completely borked because format_string()
909 // escapes ampersands (correct for web presentation but not for text email). See MDL-19829.
911 $newcase['user']['lastname'] = 'Moodle&';
912 $newcase['course']['shortname'] = '101&';
913 $newcase['forums'][0]['name'] = 'Moodle Forum&';
914 $newcase['forums'][0]['forumposts'][0]['name'] = 'Hello Moodle&';
915 $newcase['forums'][0]['forumposts'][0]['message'] = 'Welcome to Moodle&';
916 $newcase['expectations'][0]['contents'] = array(
917 'Attachment example.txt:', '~{\$a', '~&amp;', 'Love Moodle&', '101&', 'Moodle Forum&',
918 'Hello Moodle&', 'Welcome to Moodle&');
919 $textcases['Text mail with ampersands everywhere'] = array('data' => $newcase);
921 // Now the html cases.
922 $htmlcases = array();
924 // New base for html cases, no quotes, lts, gts or ampersands.
926 $htmlbase['user']['mailformat'] = 1;
927 $htmlbase['expectations'][0]['contents'] = array(
929 '~&(amp|lt|gt|quot|\#039);(?!course)',
930 '<div class=3D"attachments">( *\n *)?<a href',
931 '<div class=3D"subject">\n.*Hello Moodle', '>Moodle Forum', '>Welcome.*Moodle', '>Love Moodle', '>1\d1');
932 $htmlcases['HTML mail without ampersands, quotes or lt/gt'] = array('data' => $htmlbase);
934 // Single and double quotes, lt and gt, ampersands everywhere.
935 $newcase = $htmlbase;
936 $newcase['user']['lastname'] = 'Moodle\'>&';
937 // $newcase['user']['lastname'] = 'Moodle\'">&'; // TODO: This breaks badly. See MDL-52136.
938 $newcase['course']['shortname'] = '101\'>&';
939 // $newcase['course']['shortname'] = '101\'">&'; // TODO: This breaks badly. See MDL-52136.
940 $newcase['forums'][0]['name'] = 'Moodle Forum\'">&';
941 $newcase['forums'][0]['forumposts'][0]['name'] = 'Hello Moodle\'">&';
942 $newcase['forums'][0]['forumposts'][0]['message'] = 'Welcome to Moodle\'">&';
943 $newcase['expectations'][0]['contents'] = array(
945 '~&(amp|lt|gt|quot|\#039);',
946 '<div class=3D"attachments">( *\n *)?<a href',
947 '<div class=3D"subject">\n.*Hello Moodle\'">&', '>Moodle Forum\'">&',
948 '>Welcome.*Moodle\'">&', '>Love Moodle&\#039;>&', '>1\d1\'>&');
949 $htmlcases['HTML mail with quotes, gt, lt and ampersand everywhere'] = array('data' => $newcase);
951 return $textcases + $htmlcases;
955 * Verify forum emails body using templates to generate the expected results.
957 * @dataProvider forum_post_email_templates_provider
958 * @param array $data provider samples.
960 public function test_forum_post_email_templates($data) {
963 $this->resetAfterTest();
965 // Create the course, with the specified options.
967 foreach ($data['course'] as $option => $value) {
968 $options[$option] = $value;
970 $course = $this->getDataGenerator()->create_course($options);
972 // Create the user, with the specified options and enrol in the course.
974 foreach ($data['user'] as $option => $value) {
975 $options[$option] = $value;
977 $user = $this->getDataGenerator()->create_user($options);
978 $this->getDataGenerator()->enrol_user($user->id, $course->id);
980 // Create forums, always force susbscribed (for easy), with the specified options.
982 foreach ($data['forums'] as $dataforum) {
983 $forumposts = isset($dataforum['forumposts']) ? $dataforum['forumposts'] : array();
984 unset($dataforum['forumposts']);
985 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
986 foreach ($dataforum as $option => $value) {
987 $options[$option] = $value;
989 $forum = $this->getDataGenerator()->create_module('forum', $options);
991 // Create posts, always for immediate delivery (for easy), with the specified options.
992 foreach ($forumposts as $forumpost) {
993 $attachments = isset($forumpost['attachments']) ? $forumpost['attachments'] : array();
994 unset($forumpost['attachments']);
995 $postoptions = array('course' => $course->id, 'forum' => $forum->id, 'userid' => $user->id,
996 'mailnow' => 1, 'attachment' => !empty($attachments));
997 foreach ($forumpost as $option => $value) {
998 $postoptions[$option] = $value;
1000 list($discussion, $post) = $this->helper_post_to_forum($forum, $user, $postoptions);
1001 $posts[$post->subject] = $post; // Need this to verify cron output.
1003 // Add the attachments to the post.
1005 $fs = get_file_storage();
1006 foreach ($attachments as $attachment) {
1007 $filerecord = array(
1008 'contextid' => context_module::instance($forum->cmid)->id,
1009 'component' => 'mod_forum',
1010 'filearea' => 'attachment',
1011 'itemid' => $post->id,
1013 'filename' => $attachment['filename']
1015 $fs->create_file_from_string($filerecord, $attachment['filecontents']);
1017 $DB->set_field('forum_posts', 'attachment', '1', array('id' => $post->id));
1022 // Clear the mailsink and close the messagesink.
1023 // (surely setup should provide us this cleared but...)
1024 $this->helper->mailsink->clear();
1025 $this->helper->messagesink->close();
1027 // Capture and silence cron output, verifying contents.
1028 foreach ($posts as $post) {
1029 $this->expectOutputRegex("/1 users were sent post {$post->id}, '{$post->subject}'/");
1031 forum_cron(); // It's really annoying that we have to run cron to test this.
1034 $mails = $this->helper->mailsink->get_messages();
1036 // Start testing the expectations.
1037 $expectations = $data['expectations'];
1039 // Assert the number is the expected.
1040 $this->assertSame(count($expectations), count($mails));
1042 // Start processing mails, first localizing its expectations, then checking them.
1043 foreach ($mails as $mail) {
1044 // Find the corresponding expectation.
1045 $foundexpectation = null;
1046 foreach ($expectations as $key => $expectation) {
1047 // All expectations must have a subject for matching.
1048 if (!isset($expectation['subject'])) {
1049 $this->fail('Provider expectation missing mandatory subject');
1051 if (preg_match('!' . $expectation['subject'] . '!', $mail->subject)) {
1052 // If we already had found the expectation, there are non-unique subjects. Fail.
1053 if (isset($foundexpectation)) {
1054 $this->fail('Multiple expectations found (by subject matching). Please make them unique.');
1056 $foundexpectation = $expectation;
1057 unset($expectations[$key]);
1060 // Arrived here, we should have found the expectations.
1061 $this->assertNotEmpty($foundexpectation, 'Expectation not found for the mail');
1063 // If we have found the expectation and have contents to match, let's do it.
1064 if (isset($foundexpectation) and isset($foundexpectation['contents'])) {
1065 if (!is_array($foundexpectation['contents'])) { // Accept both string and array.
1066 $foundexpectation['contents'] = array($foundexpectation['contents']);
1068 foreach ($foundexpectation['contents'] as $content) {
1069 if (strpos($content, '~') !== 0) {
1070 $this->assertRegexp('#' . $content . '#m', $mail->body);
1072 $this->assertNotRegexp('#' . substr($content, 1) . '#m', $mail->body);
1077 // Finished, there should not be remaining expectations.
1078 $this->assertCount(0, $expectations);