Merge branch 'MDL-53166' of https://github.com/eugeneventer/moodle-fixes
[moodle.git] / mod / forum / tests / mail_test.php
CommitLineData
a7a84903
AN
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * The forum module mail generation tests.
19 *
20 * @package mod_forum
21 * @category external
22 * @copyright 2013 Andrew Nicols
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27
28global $CFG;
29
30class mod_forum_mail_testcase extends advanced_testcase {
31
32 protected $helper;
33
34 public function setUp() {
59075a43
AN
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();
a54702b2 38 \mod_forum\subscriptions::reset_discussion_cache();
59075a43 39
a7a84903
AN
40 global $CFG;
41 require_once($CFG->dirroot . '/mod/forum/lib.php');
42
43 $helper = new stdClass();
44
45 // Messaging is not compatible with transactions...
46 $this->preventResetByRollback();
47
48 // Catch all messages.
49 $helper->messagesink = $this->redirectMessages();
50 $helper->mailsink = $this->redirectEmails();
51
52 // Confirm that we have an empty message sink so far.
53 $messages = $helper->messagesink->get_messages();
54 $this->assertEquals(0, count($messages));
55
56 $messages = $helper->mailsink->get_messages();
57 $this->assertEquals(0, count($messages));
58
555837c5
AN
59 // Forcibly reduce the maxeditingtime to a second in the past to
60 // ensure that messages are sent out.
61 $CFG->maxeditingtime = -1;
a7a84903 62
a7a84903
AN
63 $this->helper = $helper;
64 }
65
66 public function tearDown() {
59075a43
AN
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();
70
a7a84903
AN
71 $this->helper->messagesink->clear();
72 $this->helper->messagesink->close();
73
74 $this->helper->mailsink->clear();
75 $this->helper->mailsink->close();
76 }
77
e7f0b4d3
AN
78 /**
79 * Perform message inbound setup for the mod_forum reply handler.
80 */
81 protected function helper_spoof_message_inbound_setup() {
82 global $CFG, $DB;
83 // Setup the default Inbound Message mailbox settings.
84 $CFG->messageinbound_domain = 'example.com';
85 $CFG->messageinbound_enabled = true;
86
87 // Must be no longer than 15 characters.
88 $CFG->messageinbound_mailbox = 'moodlemoodle123';
89
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);
93 }
94
a7a84903
AN
95 /**
96 * Helper to create the required number of users in the specified
97 * course.
98 * Users are enrolled as students.
99 *
100 * @param stdClass $course The course object
101 * @param integer $count The number of users to create
102 * @return array The users created
103 */
104 protected function helper_create_users($course, $count) {
105 $users = array();
106
107 for ($i = 0; $i < $count; $i++) {
108 $user = $this->getDataGenerator()->create_user();
109 $this->getDataGenerator()->enrol_user($user->id, $course->id);
110 $users[] = $user;
111 }
112
113 return $users;
114 }
115
116 /**
117 * Create a new discussion and post within the specified forum, as the
118 * specified author.
119 *
120 * @param stdClass $forum The forum to post in
121 * @param stdClass $author The author to post as
28c0c4af 122 * @param array $fields any other fields in discussion (name, message, messageformat, ...)
a7a84903
AN
123 * @param array An array containing the discussion object, and the post object
124 */
28c0c4af 125 protected function helper_post_to_forum($forum, $author, $fields = array()) {
555837c5 126 global $DB;
a7a84903
AN
127 $generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
128
129 // Create a discussion in the forum, and then add a post to that discussion.
28c0c4af 130 $record = (object)$fields;
a7a84903
AN
131 $record->course = $forum->course;
132 $record->userid = $author->id;
133 $record->forum = $forum->id;
134 $discussion = $generator->create_discussion($record);
135
555837c5
AN
136 // Retrieve the post which was created by create_discussion.
137 $post = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
a7a84903
AN
138
139 return array($discussion, $post);
140 }
141
7122e154
AN
142 /**
143 * Update the post time for the specified post by $factor.
144 *
145 * @param stdClass $post The post to update
146 * @param int $factor The amount to update by
147 */
148 protected function helper_update_post_time($post, $factor) {
149 global $DB;
150
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));
153 }
154
155 /**
156 * Update the subscription time for the specified user/discussion by $factor.
157 *
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
161 */
162 protected function helper_update_subscription_time($user, $discussion, $factor) {
163 global $DB;
164
165 $sub = $DB->get_record('forum_discussion_subs', array('userid' => $user->id, 'discussion' => $discussion->id));
166
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));
169 }
170
171 /**
172 * Create a new post within an existing discussion, as the specified author.
173 *
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
178 */
179 protected function helper_post_to_discussion($forum, $discussion, $author) {
180 global $DB;
181
182 $generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
183
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;
191
192 $post = $generator->create_post($record);
193
194 return $post;
195 }
196
a7a84903
AN
197 /**
198 * Run the forum cron, and check that the specified post was sent the
199 * specified number of times.
200 *
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
204 */
205 protected function helper_run_cron_check_count($post, $expected) {
eb451c79 206
a7a84903
AN
207 // Clear the sinks before running cron.
208 $this->helper->messagesink->clear();
209 $this->helper->mailsink->clear();
210
211 // Cron daily uses mtrace, turn on buffering to silence output.
212 $this->expectOutputRegex("/{$expected} users were sent post {$post->id}, '{$post->subject}'/");
213 forum_cron();
214
215 // Now check the results in the message sink.
216 $messages = $this->helper->messagesink->get_messages();
217
218 // There should be the expected number of messages.
219 $this->assertEquals($expected, count($messages));
220
221 return $messages;
222 }
223
eb451c79
AN
224 /**
225 * Run the forum cron, and check that the specified posts were sent the
226 * specified number of times.
227 *
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
231 */
232 protected function helper_run_cron_check_counts($posts, $expected) {
233
234 // Clear the sinks before running cron.
235 $this->helper->messagesink->clear();
236 $this->helper->mailsink->clear();
237
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']}'/");
241 }
242 forum_cron();
243
244 // Now check the results in the message sink.
245 $messages = $this->helper->messagesink->get_messages();
246
247 // There should be the expected number of messages.
248 $this->assertEquals($expected, count($messages));
249
250 return $messages;
251 }
252
a7a84903
AN
253 public function test_forced_subscription() {
254 $this->resetAfterTest(true);
255
256 // Create a course, with a forum.
257 $course = $this->getDataGenerator()->create_course();
258
259 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
260 $forum = $this->getDataGenerator()->create_module('forum', $options);
261
262 // Create two users enrolled in the course as students.
263 list($author, $recipient) = $this->helper_create_users($course, 2);
264
265 // Post a discussion to the forum.
266 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
267
268 // We expect both users to receive this post.
269 $expected = 2;
270
271 // Run cron and check that the expected number of users received the notification.
272 $messages = $this->helper_run_cron_check_count($post, $expected);
273
274 $seenauthor = false;
275 $seenrecipient = false;
276 foreach ($messages as $message) {
277 // They should both be from our user.
278 $this->assertEquals($author->id, $message->useridfrom);
279
280 if ($message->useridto == $author->id) {
281 $seenauthor = true;
282 } else if ($message->useridto = $recipient->id) {
283 $seenrecipient = true;
284 }
285 }
286
287 // Check we saw messages for both users.
288 $this->assertTrue($seenauthor);
289 $this->assertTrue($seenrecipient);
290 }
291
292 public function test_subscription_disabled() {
293 global $DB;
294
295 $this->resetAfterTest(true);
296
297 // Create a course, with a forum.
298 $course = $this->getDataGenerator()->create_course();
299
300 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_DISALLOWSUBSCRIBE);
301 $forum = $this->getDataGenerator()->create_module('forum', $options);
302
303 // Create two users enrolled in the course as students.
304 list($author, $recipient) = $this->helper_create_users($course, 2);
305
306 // Post a discussion to the forum.
307 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
308
309 // We expect both users to receive this post.
310 $expected = 0;
311
312 // Run cron and check that the expected number of users received the notification.
313 $messages = $this->helper_run_cron_check_count($post, $expected);
314
315 // A user with the manageactivities capability within the course can subscribe.
316 $expected = 1;
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));
7122e154 319 \mod_forum\subscriptions::subscribe_user($recipient->id, $forum);
a7a84903
AN
320
321 $this->assertEquals($expected, $DB->count_records('forum_subscriptions', array(
7122e154 322 'userid' => $recipient->id,
a7a84903
AN
323 'forum' => $forum->id,
324 )));
325
326 // Run cron and check that the expected number of users received the notification.
7122e154 327 list($discussion, $post) = $this->helper_post_to_forum($forum, $recipient);
a7a84903
AN
328 $messages = $this->helper_run_cron_check_count($post, $expected);
329
330 // Unsubscribe the user again.
7122e154 331 \mod_forum\subscriptions::unsubscribe_user($recipient->id, $forum);
a7a84903
AN
332
333 $expected = 0;
334 $this->assertEquals($expected, $DB->count_records('forum_subscriptions', array(
7122e154 335 'userid' => $recipient->id,
a7a84903
AN
336 'forum' => $forum->id,
337 )));
338
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);
342
7122e154
AN
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);
346
347 $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
348 $this->helper_update_post_time($reply, -30);
349
350 $messages = $this->helper_run_cron_check_count($reply, $expected);
a7a84903
AN
351 }
352
353 public function test_automatic() {
354 $this->resetAfterTest(true);
355
356 // Create a course, with a forum.
357 $course = $this->getDataGenerator()->create_course();
358
359 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
360 $forum = $this->getDataGenerator()->create_module('forum', $options);
361
362 // Create two users enrolled in the course as students.
363 list($author, $recipient) = $this->helper_create_users($course, 2);
364
365 // Post a discussion to the forum.
366 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
367
368 // We expect both users to receive this post.
369 $expected = 2;
370
371 // Run cron and check that the expected number of users received the notification.
372 $messages = $this->helper_run_cron_check_count($post, $expected);
373
374 $seenauthor = false;
375 $seenrecipient = false;
376 foreach ($messages as $message) {
377 // They should both be from our user.
378 $this->assertEquals($author->id, $message->useridfrom);
379
380 if ($message->useridto == $author->id) {
381 $seenauthor = true;
382 } else if ($message->useridto = $recipient->id) {
383 $seenrecipient = true;
384 }
385 }
386
387 // Check we saw messages for both users.
388 $this->assertTrue($seenauthor);
389 $this->assertTrue($seenrecipient);
390 }
391
392 public function test_optional() {
393 $this->resetAfterTest(true);
394
395 // Create a course, with a forum.
396 $course = $this->getDataGenerator()->create_course();
397
398 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
399 $forum = $this->getDataGenerator()->create_module('forum', $options);
400
401 // Create two users enrolled in the course as students.
402 list($author, $recipient) = $this->helper_create_users($course, 2);
403
404 // Post a discussion to the forum.
405 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
406
407 // We expect both users to receive this post.
408 $expected = 0;
409
410 // Run cron and check that the expected number of users received the notification.
411 $messages = $this->helper_run_cron_check_count($post, $expected);
412 }
413
414 public function test_automatic_with_unsubscribed_user() {
415 $this->resetAfterTest(true);
416
417 // Create a course, with a forum.
418 $course = $this->getDataGenerator()->create_course();
419
420 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
421 $forum = $this->getDataGenerator()->create_module('forum', $options);
422
423 // Create two users enrolled in the course as students.
424 list($author, $recipient) = $this->helper_create_users($course, 2);
425
426 // Unsubscribe the 'author' user from the forum.
59075a43 427 \mod_forum\subscriptions::unsubscribe_user($author->id, $forum);
a7a84903
AN
428
429 // Post a discussion to the forum.
430 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
431
432 // We expect only one user to receive this post.
433 $expected = 1;
434
435 // Run cron and check that the expected number of users received the notification.
436 $messages = $this->helper_run_cron_check_count($post, $expected);
437
438 $seenauthor = false;
439 $seenrecipient = false;
440 foreach ($messages as $message) {
441 // They should both be from our user.
442 $this->assertEquals($author->id, $message->useridfrom);
443
444 if ($message->useridto == $author->id) {
445 $seenauthor = true;
446 } else if ($message->useridto = $recipient->id) {
447 $seenrecipient = true;
448 }
449 }
450
451 // Check we only saw one user.
452 $this->assertFalse($seenauthor);
453 $this->assertTrue($seenrecipient);
454 }
455
456 public function test_optional_with_subscribed_user() {
457 $this->resetAfterTest(true);
458
459 // Create a course, with a forum.
460 $course = $this->getDataGenerator()->create_course();
461
462 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
463 $forum = $this->getDataGenerator()->create_module('forum', $options);
464
465 // Create two users enrolled in the course as students.
466 list($author, $recipient) = $this->helper_create_users($course, 2);
467
468 // Subscribe the 'recipient' user from the forum.
59075a43 469 \mod_forum\subscriptions::subscribe_user($recipient->id, $forum);
a7a84903
AN
470
471 // Post a discussion to the forum.
472 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
473
474 // We expect only one user to receive this post.
475 $expected = 1;
476
477 // Run cron and check that the expected number of users received the notification.
478 $messages = $this->helper_run_cron_check_count($post, $expected);
479
480 $seenauthor = false;
481 $seenrecipient = false;
482 foreach ($messages as $message) {
483 // They should both be from our user.
484 $this->assertEquals($author->id, $message->useridfrom);
485
486 if ($message->useridto == $author->id) {
487 $seenauthor = true;
488 } else if ($message->useridto = $recipient->id) {
489 $seenrecipient = true;
490 }
491 }
492
493 // Check we only saw one user.
494 $this->assertFalse($seenauthor);
495 $this->assertTrue($seenrecipient);
496 }
497
49566c8a
AN
498 public function test_automatic_with_unsubscribed_discussion() {
499 $this->resetAfterTest(true);
500
501 // Create a course, with a forum.
502 $course = $this->getDataGenerator()->create_course();
503
504 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
505 $forum = $this->getDataGenerator()->create_module('forum', $options);
506
507 // Create two users enrolled in the course as students.
508 list($author, $recipient) = $this->helper_create_users($course, 2);
509
510 // Post a discussion to the forum.
511 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
512
513 // Unsubscribe the 'author' user from the discussion.
514 \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion);
515
eb451c79
AN
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));
518
49566c8a
AN
519 // We expect only one user to receive this post.
520 $expected = 1;
521
522 // Run cron and check that the expected number of users received the notification.
523 $messages = $this->helper_run_cron_check_count($post, $expected);
524
525 $seenauthor = false;
526 $seenrecipient = false;
527 foreach ($messages as $message) {
528 // They should both be from our user.
529 $this->assertEquals($author->id, $message->useridfrom);
530
531 if ($message->useridto == $author->id) {
532 $seenauthor = true;
533 } else if ($message->useridto = $recipient->id) {
534 $seenrecipient = true;
535 }
536 }
537
538 // Check we only saw one user.
539 $this->assertFalse($seenauthor);
540 $this->assertTrue($seenrecipient);
541 }
542
543 public function test_optional_with_subscribed_discussion() {
544 $this->resetAfterTest(true);
545
546 // Create a course, with a forum.
547 $course = $this->getDataGenerator()->create_course();
548
549 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
550 $forum = $this->getDataGenerator()->create_module('forum', $options);
551
552 // Create two users enrolled in the course as students.
553 list($author, $recipient) = $this->helper_create_users($course, 2);
554
555 // Post a discussion to the forum.
556 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
7122e154 557 $this->helper_update_post_time($post, -90);
49566c8a
AN
558
559 // Subscribe the 'recipient' user to the discussion.
560 \mod_forum\subscriptions::subscribe_user_to_discussion($recipient->id, $discussion);
7122e154
AN
561 $this->helper_update_subscription_time($recipient, $discussion, -60);
562
563 // Initially we don't expect any user to receive this post as you cannot subscribe to a discussion until after
564 // you have read it.
565 $expected = 0;
566
567 // Run cron and check that the expected number of users received the notification.
568 $messages = $this->helper_run_cron_check_count($post, $expected);
569
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);
49566c8a
AN
573
574 // We expect only one user to receive this post.
575 $expected = 1;
576
577 // Run cron and check that the expected number of users received the notification.
7122e154 578 $messages = $this->helper_run_cron_check_count($reply, $expected);
49566c8a
AN
579
580 $seenauthor = false;
581 $seenrecipient = false;
582 foreach ($messages as $message) {
583 // They should both be from our user.
584 $this->assertEquals($author->id, $message->useridfrom);
585
586 if ($message->useridto == $author->id) {
587 $seenauthor = true;
588 } else if ($message->useridto = $recipient->id) {
589 $seenrecipient = true;
590 }
591 }
592
593 // Check we only saw one user.
594 $this->assertFalse($seenauthor);
595 $this->assertTrue($seenrecipient);
596 }
597
598 public function test_automatic_with_subscribed_discussion_in_unsubscribed_forum() {
599 $this->resetAfterTest(true);
600
601 // Create a course, with a forum.
602 $course = $this->getDataGenerator()->create_course();
603
604 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
605 $forum = $this->getDataGenerator()->create_module('forum', $options);
606
607 // Create two users enrolled in the course as students.
608 list($author, $recipient) = $this->helper_create_users($course, 2);
609
610 // Post a discussion to the forum.
611 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
7122e154 612 $this->helper_update_post_time($post, -90);
49566c8a 613
7122e154 614 // Unsubscribe the 'author' user from the forum.
49566c8a
AN
615 \mod_forum\subscriptions::unsubscribe_user($author->id, $forum);
616
617 // Then re-subscribe them to the discussion.
618 \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion);
7122e154 619 $this->helper_update_subscription_time($author, $discussion, -60);
49566c8a 620
7122e154
AN
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.
623 $expected = 1;
49566c8a
AN
624
625 // Run cron and check that the expected number of users received the notification.
626 $messages = $this->helper_run_cron_check_count($post, $expected);
627
628 $seenauthor = false;
629 $seenrecipient = false;
630 foreach ($messages as $message) {
631 // They should both be from our user.
632 $this->assertEquals($author->id, $message->useridfrom);
633
634 if ($message->useridto == $author->id) {
635 $seenauthor = true;
636 } else if ($message->useridto = $recipient->id) {
637 $seenrecipient = true;
638 }
639 }
640
641 // Check we only saw one user.
7122e154
AN
642 $this->assertFalse($seenauthor);
643 $this->assertTrue($seenrecipient);
644
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);
648
649 // We expect two users to receive this post.
650 $expected = 2;
651
652 // Run cron and check that the expected number of users received the notification.
653 $messages = $this->helper_run_cron_check_count($reply, $expected);
654
655 $seenauthor = false;
656 $seenrecipient = false;
657 foreach ($messages as $message) {
658 // They should both be from our user.
659 $this->assertEquals($author->id, $message->useridfrom);
660
661 if ($message->useridto == $author->id) {
662 $seenauthor = true;
663 } else if ($message->useridto = $recipient->id) {
664 $seenrecipient = true;
665 }
666 }
667
668 // Check we saw both users.
49566c8a
AN
669 $this->assertTrue($seenauthor);
670 $this->assertTrue($seenrecipient);
671 }
672
673 public function test_optional_with_unsubscribed_discussion_in_subscribed_forum() {
674 $this->resetAfterTest(true);
675
676 // Create a course, with a forum.
677 $course = $this->getDataGenerator()->create_course();
678
679 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
680 $forum = $this->getDataGenerator()->create_module('forum', $options);
681
682 // Create two users enrolled in the course as students.
683 list($author, $recipient) = $this->helper_create_users($course, 2);
684
685 // Post a discussion to the forum.
686 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
687
688 // Unsubscribe the 'recipient' user from the discussion.
689 \mod_forum\subscriptions::subscribe_user($recipient->id, $forum);
690
691 // Then unsubscribe them from the discussion.
692 \mod_forum\subscriptions::unsubscribe_user_from_discussion($recipient->id, $discussion);
693
694 // We don't expect any users to receive this post.
695 $expected = 0;
696
697 // Run cron and check that the expected number of users received the notification.
698 $messages = $this->helper_run_cron_check_count($post, $expected);
699 }
eb451c79
AN
700
701 /**
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.
704 */
705 public function test_forum_discussion_subscription_forum_unsubscribed_discussion_subscribed_after_post() {
706 $this->resetAfterTest(true);
eb451c79
AN
707
708 // Create a course, with a forum.
709 $course = $this->getDataGenerator()->create_course();
710
711 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
712 $forum = $this->getDataGenerator()->create_module('forum', $options);
713
714 $expectedmessages = array();
715
716 // Create a user enrolled in the course as a student.
717 list($author) = $this->helper_create_users($course, 1);
718
719 // Post a discussion to the forum.
720 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
7122e154 721 $this->helper_update_post_time($post, -90);
eb451c79
AN
722
723 $expectedmessages[] = array(
724 'id' => $post->id,
725 'subject' => $post->subject,
726 'count' => 0,
727 );
728
729 // Then subscribe the user to the discussion.
730 $this->assertTrue(\mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion));
7122e154 731 $this->helper_update_subscription_time($author, $discussion, -60);
eb451c79
AN
732
733 // Then post a reply to the first discussion.
7122e154
AN
734 $reply = $this->helper_post_to_discussion($forum, $discussion, $author);
735 $this->helper_update_post_time($reply, -30);
eb451c79
AN
736
737 $expectedmessages[] = array(
738 'id' => $reply->id,
739 'subject' => $reply->subject,
740 'count' => 1,
741 );
742
eb451c79
AN
743 $expectedcount = 1;
744
745 // Run cron and check that the expected number of users received the notification.
746 $messages = $this->helper_run_cron_check_counts($expectedmessages, $expectedcount);
eb451c79 747 }
e7f0b4d3
AN
748
749 public function test_forum_message_inbound_multiple_posts() {
750 $this->resetAfterTest(true);
751
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);
756
757 // Create a user enrolled in the course as a student.
758 list($author) = $this->helper_create_users($course, 1);
759
760 $expectedmessages = array();
761
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);
765
766 $expectedmessages[] = array(
767 'id' => $post->id,
768 'subject' => $post->subject,
769 'count' => 0,
770 );
771
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);
775
776 $expectedmessages[] = array(
777 'id' => $reply->id,
778 'subject' => $reply->subject,
779 'count' => 1,
780 );
781
782 $expectedcount = 2;
783
784 // Ensure that messageinbound is enabled and configured for the forum handler.
785 $this->helper_spoof_message_inbound_setup();
786
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);
790
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();
795
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']}'/");
799 }
800
801 forum_cron();
802 $messages = $this->helper->mailsink->get_messages();
803
804 // There should be the expected number of messages.
805 $this->assertEquals($expectedcount, count($messages));
806
807 foreach ($messages as $message) {
808 $this->assertRegExp('/Reply-To: moodlemoodle123\+[^@]*@example.com/', $message->header);
809 }
810 }
28c0c4af
MG
811
812 public function test_long_subject() {
813 $this->resetAfterTest(true);
814
815 // Create a course, with a forum.
816 $course = $this->getDataGenerator()->create_course();
817
818 $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
819 $forum = $this->getDataGenerator()->create_module('forum', $options);
820
821 // Create a user enrolled in the course as student.
822 list($author) = $this->helper_create_users($course, 1);
823
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));
829
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);
835 }
9dbbdcb4
EL
836
837 /**
838 * dataProvider for test_forum_post_email_templates().
839 */
840 public function forum_post_email_templates_provider() {
841 // Base information, we'll build variations based on it.
842 $base = array(
843 'user' => array('firstname' => 'Love', 'lastname' => 'Moodle', 'mailformat' => 0, 'maildigest' => 0),
844 'course' => array('shortname' => '101', 'fullname' => 'Moodle 101'),
845 'forums' => array(
846 array(
847 'name' => 'Moodle Forum',
848 'forumposts' => array(
849 array(
850 'name' => 'Hello Moodle',
851 'message' => 'Welcome to Moodle',
852 'messageformat' => FORMAT_MOODLE,
853 'attachments' => array(
854 array(
855 'filename' => 'example.txt',
856 'filecontents' => 'Basic information about the course'
857 ),
858 ),
859 ),
860 ),
861 ),
862 ),
863 'expectations' => array(
864 array(
865 'subject' => '.*101.*Hello',
866 'contents' => array(
867 '~{$a',
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'
872 ),
873 ),
874 ),
875 );
876
877 // Build the text cases.
878 $textcases = array('Text mail without ampersands, quotes or lt/gt' => array('data' => $base));
879
880 // Single and double quotes everywhere.
881 $newcase = $base;
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', '~&amp;(quot|\#039);', 'Love Moodle\'', '101\'', 'Moodle Forum\'"',
891 'Hello Moodle\'"', 'Welcome to Moodle\'"');
892 $textcases['Text mail with quotes everywhere'] = array('data' => $newcase);
893
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.
897 $newcase = $base;
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', '~&amp;gt;', 'Love Moodle>', '101&gt;', 'Moodle Forum&gt;',
905 'Hello Moodle&gt;', 'Welcome to Moodle>');
906 $textcases['Text mail with gt and lt everywhere'] = array('data' => $newcase);
907
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.
910 $newcase = $base;
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;amp;', 'Love Moodle&', '101&amp;', 'Moodle Forum&amp;',
918 'Hello Moodle&amp;', 'Welcome to Moodle&');
919 $textcases['Text mail with ampersands everywhere'] = array('data' => $newcase);
920
921 // Now the html cases.
922 $htmlcases = array();
923
924 // New base for html cases, no quotes, lts, gts or ampersands.
925 $htmlbase = $base;
926 $htmlbase['user']['mailformat'] = 1;
927 $htmlbase['expectations'][0]['contents'] = array(
928 '~{\$a',
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);
933
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(
944 '~{\$a',
945 '~&amp;(amp|lt|gt|quot|\#039);',
946 '<div class=3D"attachments">( *\n *)?<a href',
947 '<div class=3D"subject">\n.*Hello Moodle\'"&gt;&amp;', '>Moodle Forum\'"&gt;&amp;',
948 '>Welcome.*Moodle\'"&gt;&amp;', '>Love Moodle&\#039;&gt;&amp;', '>1\d1\'&gt;&amp');
949 $htmlcases['HTML mail with quotes, gt, lt and ampersand everywhere'] = array('data' => $newcase);
950
951 return $textcases + $htmlcases;
952 }
953
954 /**
955 * Verify forum emails body using templates to generate the expected results.
956 *
957 * @dataProvider forum_post_email_templates_provider
958 * @param array $data provider samples.
959 */
960 public function test_forum_post_email_templates($data) {
961 global $DB;
962
963 $this->resetAfterTest();
964
965 // Create the course, with the specified options.
966 $options = array();
967 foreach ($data['course'] as $option => $value) {
968 $options[$option] = $value;
969 }
970 $course = $this->getDataGenerator()->create_course($options);
971
972 // Create the user, with the specified options and enrol in the course.
973 $options = array();
974 foreach ($data['user'] as $option => $value) {
975 $options[$option] = $value;
976 }
977 $user = $this->getDataGenerator()->create_user($options);
978 $this->getDataGenerator()->enrol_user($user->id, $course->id);
979
980 // Create forums, always force susbscribed (for easy), with the specified options.
981 $posts = array();
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;
988 }
989 $forum = $this->getDataGenerator()->create_module('forum', $options);
990
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;
999 }
1000 list($discussion, $post) = $this->helper_post_to_forum($forum, $user, $postoptions);
1001 $posts[$post->subject] = $post; // Need this to verify cron output.
1002
1003 // Add the attachments to the post.
1004 if ($attachments) {
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,
1012 'filepath' => '/',
1013 'filename' => $attachment['filename']
1014 );
1015 $fs->create_file_from_string($filerecord, $attachment['filecontents']);
1016 }
1017 $DB->set_field('forum_posts', 'attachment', '1', array('id' => $post->id));
1018 }
1019 }
1020 }
1021
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();
1026
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}'/");
1030 }
1031 forum_cron(); // It's really annoying that we have to run cron to test this.
1032
1033 // Get the mails.
1034 $mails = $this->helper->mailsink->get_messages();
1035
1036 // Start testing the expectations.
1037 $expectations = $data['expectations'];
1038
1039 // Assert the number is the expected.
1040 $this->assertSame(count($expectations), count($mails));
1041
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');
1050 }
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.');
1055 }
1056 $foundexpectation = $expectation;
1057 unset($expectations[$key]);
1058 }
1059 }
1060 // Arrived here, we should have found the expectations.
1061 $this->assertNotEmpty($foundexpectation, 'Expectation not found for the mail');
1062
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']);
1067 }
1068 foreach ($foundexpectation['contents'] as $content) {
1069 if (strpos($content, '~') !== 0) {
1070 $this->assertRegexp('#' . $content . '#m', $mail->body);
1071 } else {
1072 $this->assertNotRegexp('#' . substr($content, 1) . '#m', $mail->body);
1073 }
1074 }
1075 }
1076 }
1077 // Finished, there should not be remaining expectations.
1078 $this->assertCount(0, $expectations);
1079 }
a7a84903 1080}