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 module forums tests
19 *
20 * @package    mod_forum
21 * @copyright  2013 Frédéric Massart
22 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25defined('MOODLE_INTERNAL') || die();
26
27global $CFG;
28require_once($CFG->dirroot . '/mod/forum/lib.php');
29require_once($CFG->dirroot . '/mod/forum/locallib.php');
30require_once($CFG->dirroot . '/rating/lib.php');
31
32class mod_forum_lib_testcase extends advanced_testcase {
33
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    }
39
40    public function tearDown() {
41        // We must clear the subscription caches. This has to be done both before each test, and after in case of other
42        // tests using these functions.
43        \mod_forum\subscriptions::reset_forum_cache();
44    }
45
46    public function test_forum_trigger_content_uploaded_event() {
47        $this->resetAfterTest();
48
49        $user = $this->getDataGenerator()->create_user();
50        $course = $this->getDataGenerator()->create_course();
51        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
52        $context = context_module::instance($forum->cmid);
53
54        $this->setUser($user->id);
55        $fakepost = (object) array('id' => 123, 'message' => 'Yay!', 'discussion' => 100);
56        $cm = get_coursemodule_from_instance('forum', $forum->id);
57
58        $fs = get_file_storage();
59        $dummy = (object) array(
60            'contextid' => $context->id,
61            'component' => 'mod_forum',
62            'filearea' => 'attachment',
63            'itemid' => $fakepost->id,
64            'filepath' => '/',
65            'filename' => 'myassignmnent.pdf'
66        );
67        $fi = $fs->create_file_from_string($dummy, 'Content of ' . $dummy->filename);
68
69        $data = new stdClass();
70        $sink = $this->redirectEvents();
71        forum_trigger_content_uploaded_event($fakepost, $cm, 'some triggered from value');
72        $events = $sink->get_events();
73
74        $this->assertCount(1, $events);
75        $event = reset($events);
76        $this->assertInstanceOf('\mod_forum\event\assessable_uploaded', $event);
77        $this->assertEquals($context->id, $event->contextid);
78        $this->assertEquals($fakepost->id, $event->objectid);
79        $this->assertEquals($fakepost->message, $event->other['content']);
80        $this->assertEquals($fakepost->discussion, $event->other['discussionid']);
81        $this->assertCount(1, $event->other['pathnamehashes']);
82        $this->assertEquals($fi->get_pathnamehash(), $event->other['pathnamehashes'][0]);
83        $expected = new stdClass();
84        $expected->modulename = 'forum';
85        $expected->name = 'some triggered from value';
86        $expected->cmid = $forum->cmid;
87        $expected->itemid = $fakepost->id;
88        $expected->courseid = $course->id;
89        $expected->userid = $user->id;
90        $expected->content = $fakepost->message;
91        $expected->pathnamehashes = array($fi->get_pathnamehash());
92        $this->assertEventLegacyData($expected, $event);
93        $this->assertEventContextNotUsed($event);
94    }
95
96    public function test_forum_get_courses_user_posted_in() {
97        $this->resetAfterTest();
98
99        $user1 = $this->getDataGenerator()->create_user();
100        $user2 = $this->getDataGenerator()->create_user();
101        $user3 = $this->getDataGenerator()->create_user();
102
103        $course1 = $this->getDataGenerator()->create_course();
104        $course2 = $this->getDataGenerator()->create_course();
105        $course3 = $this->getDataGenerator()->create_course();
106
107        // Create 3 forums, one in each course.
108        $record = new stdClass();
109        $record->course = $course1->id;
110        $forum1 = $this->getDataGenerator()->create_module('forum', $record);
111
112        $record = new stdClass();
113        $record->course = $course2->id;
114        $forum2 = $this->getDataGenerator()->create_module('forum', $record);
115
116        $record = new stdClass();
117        $record->course = $course3->id;
118        $forum3 = $this->getDataGenerator()->create_module('forum', $record);
119
120        // Add a second forum in course 1.
121        $record = new stdClass();
122        $record->course = $course1->id;
123        $forum4 = $this->getDataGenerator()->create_module('forum', $record);
124
125        // Add discussions to course 1 started by user1.
126        $record = new stdClass();
127        $record->course = $course1->id;
128        $record->userid = $user1->id;
129        $record->forum = $forum1->id;
130        $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
131
132        $record = new stdClass();
133        $record->course = $course1->id;
134        $record->userid = $user1->id;
135        $record->forum = $forum4->id;
136        $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
137
138        // Add discussions to course2 started by user1.
139        $record = new stdClass();
140        $record->course = $course2->id;
141        $record->userid = $user1->id;
142        $record->forum = $forum2->id;
143        $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
144
145        // Add discussions to course 3 started by user2.
146        $record = new stdClass();
147        $record->course = $course3->id;
148        $record->userid = $user2->id;
149        $record->forum = $forum3->id;
150        $discussion3 = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
151
152        // Add post to course 3 by user1.
153        $record = new stdClass();
154        $record->course = $course3->id;
155        $record->userid = $user1->id;
156        $record->forum = $forum3->id;
157        $record->discussion = $discussion3->id;
158        $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
159
160        // User 3 hasn't posted anything, so shouldn't get any results.
161        $user3courses = forum_get_courses_user_posted_in($user3);
162        $this->assertEmpty($user3courses);
163
164        // User 2 has only posted in course3.
165        $user2courses = forum_get_courses_user_posted_in($user2);
166        $this->assertCount(1, $user2courses);
167        $user2course = array_shift($user2courses);
168        $this->assertEquals($course3->id, $user2course->id);
169        $this->assertEquals($course3->shortname, $user2course->shortname);
170
171        // User 1 has posted in all 3 courses.
172        $user1courses = forum_get_courses_user_posted_in($user1);
173        $this->assertCount(3, $user1courses);
174        foreach ($user1courses as $course) {
175            $this->assertContains($course->id, array($course1->id, $course2->id, $course3->id));
176            $this->assertContains($course->shortname, array($course1->shortname, $course2->shortname,
177                $course3->shortname));
178
179        }
180
181        // User 1 has only started a discussion in course 1 and 2 though.
182        $user1courses = forum_get_courses_user_posted_in($user1, true);
183        $this->assertCount(2, $user1courses);
184        foreach ($user1courses as $course) {
185            $this->assertContains($course->id, array($course1->id, $course2->id));
186            $this->assertContains($course->shortname, array($course1->shortname, $course2->shortname));
187        }
188    }
189
190    /**
191     * Test the logic in the forum_tp_can_track_forums() function.
192     */
193    public function test_forum_tp_can_track_forums() {
194        global $CFG;
195
196        $this->resetAfterTest();
197
198        $useron = $this->getDataGenerator()->create_user(array('trackforums' => 1));
199        $useroff = $this->getDataGenerator()->create_user(array('trackforums' => 0));
200        $course = $this->getDataGenerator()->create_course();
201        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OFF); // Off.
202        $forumoff = $this->getDataGenerator()->create_module('forum', $options);
203
204        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_FORCED); // On.
205        $forumforce = $this->getDataGenerator()->create_module('forum', $options);
206
207        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OPTIONAL); // Optional.
208        $forumoptional = $this->getDataGenerator()->create_module('forum', $options);
209
210        // Allow force.
211        $CFG->forum_allowforcedreadtracking = 1;
212
213        // User on, forum off, should be off.
214        $result = forum_tp_can_track_forums($forumoff, $useron);
215        $this->assertEquals(false, $result);
216
217        // User on, forum on, should be on.
218        $result = forum_tp_can_track_forums($forumforce, $useron);
219        $this->assertEquals(true, $result);
220
221        // User on, forum optional, should be on.
222        $result = forum_tp_can_track_forums($forumoptional, $useron);
223        $this->assertEquals(true, $result);
224
225        // User off, forum off, should be off.
226        $result = forum_tp_can_track_forums($forumoff, $useroff);
227        $this->assertEquals(false, $result);
228
229        // User off, forum force, should be on.
230        $result = forum_tp_can_track_forums($forumforce, $useroff);
231        $this->assertEquals(true, $result);
232
233        // User off, forum optional, should be off.
234        $result = forum_tp_can_track_forums($forumoptional, $useroff);
235        $this->assertEquals(false, $result);
236
237        // Don't allow force.
238        $CFG->forum_allowforcedreadtracking = 0;
239
240        // User on, forum off, should be off.
241        $result = forum_tp_can_track_forums($forumoff, $useron);
242        $this->assertEquals(false, $result);
243
244        // User on, forum on, should be on.
245        $result = forum_tp_can_track_forums($forumforce, $useron);
246        $this->assertEquals(true, $result);
247
248        // User on, forum optional, should be on.
249        $result = forum_tp_can_track_forums($forumoptional, $useron);
250        $this->assertEquals(true, $result);
251
252        // User off, forum off, should be off.
253        $result = forum_tp_can_track_forums($forumoff, $useroff);
254        $this->assertEquals(false, $result);
255
256        // User off, forum force, should be off.
257        $result = forum_tp_can_track_forums($forumforce, $useroff);
258        $this->assertEquals(false, $result);
259
260        // User off, forum optional, should be off.
261        $result = forum_tp_can_track_forums($forumoptional, $useroff);
262        $this->assertEquals(false, $result);
263
264    }
265
266    /**
267     * Test the logic in the test_forum_tp_is_tracked() function.
268     */
269    public function test_forum_tp_is_tracked() {
270        global $CFG;
271
272        $this->resetAfterTest();
273
274        $cache = cache::make('mod_forum', 'forum_is_tracked');
275        $useron = $this->getDataGenerator()->create_user(array('trackforums' => 1));
276        $useroff = $this->getDataGenerator()->create_user(array('trackforums' => 0));
277        $course = $this->getDataGenerator()->create_course();
278        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OFF); // Off.
279        $forumoff = $this->getDataGenerator()->create_module('forum', $options);
280
281        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_FORCED); // On.
282        $forumforce = $this->getDataGenerator()->create_module('forum', $options);
283
284        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OPTIONAL); // Optional.
285        $forumoptional = $this->getDataGenerator()->create_module('forum', $options);
286
287        // Allow force.
288        $CFG->forum_allowforcedreadtracking = 1;
289
290        // User on, forum off, should be off.
291        $result = forum_tp_is_tracked($forumoff, $useron);
292        $this->assertEquals(false, $result);
293
294        // User on, forum force, should be on.
295        $result = forum_tp_is_tracked($forumforce, $useron);
296        $this->assertEquals(true, $result);
297
298        // User on, forum optional, should be on.
299        $result = forum_tp_is_tracked($forumoptional, $useron);
300        $this->assertEquals(true, $result);
301
302        // User off, forum off, should be off.
303        $result = forum_tp_is_tracked($forumoff, $useroff);
304        $this->assertEquals(false, $result);
305
306        // User off, forum force, should be on.
307        $result = forum_tp_is_tracked($forumforce, $useroff);
308        $this->assertEquals(true, $result);
309
310        // User off, forum optional, should be off.
311        $result = forum_tp_is_tracked($forumoptional, $useroff);
312        $this->assertEquals(false, $result);
313
314        $cache->purge();
315        // Don't allow force.
316        $CFG->forum_allowforcedreadtracking = 0;
317
318        // User on, forum off, should be off.
319        $result = forum_tp_is_tracked($forumoff, $useron);
320        $this->assertEquals(false, $result);
321
322        // User on, forum force, should be on.
323        $result = forum_tp_is_tracked($forumforce, $useron);
324        $this->assertEquals(true, $result);
325
326        // User on, forum optional, should be on.
327        $result = forum_tp_is_tracked($forumoptional, $useron);
328        $this->assertEquals(true, $result);
329
330        // User off, forum off, should be off.
331        $result = forum_tp_is_tracked($forumoff, $useroff);
332        $this->assertEquals(false, $result);
333
334        // User off, forum force, should be off.
335        $result = forum_tp_is_tracked($forumforce, $useroff);
336        $this->assertEquals(false, $result);
337
338        // User off, forum optional, should be off.
339        $result = forum_tp_is_tracked($forumoptional, $useroff);
340        $this->assertEquals(false, $result);
341
342        // Stop tracking so we can test again.
343        forum_tp_stop_tracking($forumforce->id, $useron->id);
344        forum_tp_stop_tracking($forumoptional->id, $useron->id);
345        forum_tp_stop_tracking($forumforce->id, $useroff->id);
346        forum_tp_stop_tracking($forumoptional->id, $useroff->id);
347
348        $cache->purge();
349        // Allow force.
350        $CFG->forum_allowforcedreadtracking = 1;
351
352        // User on, preference off, forum force, should be on.
353        $result = forum_tp_is_tracked($forumforce, $useron);
354        $this->assertEquals(true, $result);
355
356        // User on, preference off, forum optional, should be on.
357        $result = forum_tp_is_tracked($forumoptional, $useron);
358        $this->assertEquals(false, $result);
359
360        // User off, preference off, forum force, should be on.
361        $result = forum_tp_is_tracked($forumforce, $useroff);
362        $this->assertEquals(true, $result);
363
364        // User off, preference off, forum optional, should be off.
365        $result = forum_tp_is_tracked($forumoptional, $useroff);
366        $this->assertEquals(false, $result);
367
368        $cache->purge();
369        // Don't allow force.
370        $CFG->forum_allowforcedreadtracking = 0;
371
372        // User on, preference off, forum force, should be on.
373        $result = forum_tp_is_tracked($forumforce, $useron);
374        $this->assertEquals(false, $result);
375
376        // User on, preference off, forum optional, should be on.
377        $result = forum_tp_is_tracked($forumoptional, $useron);
378        $this->assertEquals(false, $result);
379
380        // User off, preference off, forum force, should be off.
381        $result = forum_tp_is_tracked($forumforce, $useroff);
382        $this->assertEquals(false, $result);
383
384        // User off, preference off, forum optional, should be off.
385        $result = forum_tp_is_tracked($forumoptional, $useroff);
386        $this->assertEquals(false, $result);
387    }
388
389    /**
390     * Test the logic in the forum_tp_get_course_unread_posts() function.
391     */
392    public function test_forum_tp_get_course_unread_posts() {
393        global $CFG;
394
395        $this->resetAfterTest();
396
397        $useron = $this->getDataGenerator()->create_user(array('trackforums' => 1));
398        $useroff = $this->getDataGenerator()->create_user(array('trackforums' => 0));
399        $course = $this->getDataGenerator()->create_course();
400        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OFF); // Off.
401        $forumoff = $this->getDataGenerator()->create_module('forum', $options);
402
403        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_FORCED); // On.
404        $forumforce = $this->getDataGenerator()->create_module('forum', $options);
405
406        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OPTIONAL); // Optional.
407        $forumoptional = $this->getDataGenerator()->create_module('forum', $options);
408
409        // Add discussions to the tracking off forum.
410        $record = new stdClass();
411        $record->course = $course->id;
412        $record->userid = $useron->id;
413        $record->forum = $forumoff->id;
414        $discussionoff = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
415
416        // Add discussions to the tracking forced forum.
417        $record = new stdClass();
418        $record->course = $course->id;
419        $record->userid = $useron->id;
420        $record->forum = $forumforce->id;
421        $discussionforce = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
422
423        // Add post to the tracking forced discussion.
424        $record = new stdClass();
425        $record->course = $course->id;
426        $record->userid = $useroff->id;
427        $record->forum = $forumforce->id;
428        $record->discussion = $discussionforce->id;
429        $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
430
431        // Add discussions to the tracking optional forum.
432        $record = new stdClass();
433        $record->course = $course->id;
434        $record->userid = $useron->id;
435        $record->forum = $forumoptional->id;
436        $discussionoptional = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
437
438        // Allow force.
439        $CFG->forum_allowforcedreadtracking = 1;
440
441        $result = forum_tp_get_course_unread_posts($useron->id, $course->id);
442        $this->assertEquals(2, count($result));
443        $this->assertEquals(false, isset($result[$forumoff->id]));
444        $this->assertEquals(true, isset($result[$forumforce->id]));
445        $this->assertEquals(2, $result[$forumforce->id]->unread);
446        $this->assertEquals(true, isset($result[$forumoptional->id]));
447        $this->assertEquals(1, $result[$forumoptional->id]->unread);
448
449        $result = forum_tp_get_course_unread_posts($useroff->id, $course->id);
450        $this->assertEquals(1, count($result));
451        $this->assertEquals(false, isset($result[$forumoff->id]));
452        $this->assertEquals(true, isset($result[$forumforce->id]));
453        $this->assertEquals(2, $result[$forumforce->id]->unread);
454        $this->assertEquals(false, isset($result[$forumoptional->id]));
455
456        // Don't allow force.
457        $CFG->forum_allowforcedreadtracking = 0;
458
459        $result = forum_tp_get_course_unread_posts($useron->id, $course->id);
460        $this->assertEquals(2, count($result));
461        $this->assertEquals(false, isset($result[$forumoff->id]));
462        $this->assertEquals(true, isset($result[$forumforce->id]));
463        $this->assertEquals(2, $result[$forumforce->id]->unread);
464        $this->assertEquals(true, isset($result[$forumoptional->id]));
465        $this->assertEquals(1, $result[$forumoptional->id]->unread);
466
467        $result = forum_tp_get_course_unread_posts($useroff->id, $course->id);
468        $this->assertEquals(0, count($result));
469        $this->assertEquals(false, isset($result[$forumoff->id]));
470        $this->assertEquals(false, isset($result[$forumforce->id]));
471        $this->assertEquals(false, isset($result[$forumoptional->id]));
472
473        // Stop tracking so we can test again.
474        forum_tp_stop_tracking($forumforce->id, $useron->id);
475        forum_tp_stop_tracking($forumoptional->id, $useron->id);
476        forum_tp_stop_tracking($forumforce->id, $useroff->id);
477        forum_tp_stop_tracking($forumoptional->id, $useroff->id);
478
479        // Allow force.
480        $CFG->forum_allowforcedreadtracking = 1;
481
482        $result = forum_tp_get_course_unread_posts($useron->id, $course->id);
483        $this->assertEquals(1, count($result));
484        $this->assertEquals(false, isset($result[$forumoff->id]));
485        $this->assertEquals(true, isset($result[$forumforce->id]));
486        $this->assertEquals(2, $result[$forumforce->id]->unread);
487        $this->assertEquals(false, isset($result[$forumoptional->id]));
488
489        $result = forum_tp_get_course_unread_posts($useroff->id, $course->id);
490        $this->assertEquals(1, count($result));
491        $this->assertEquals(false, isset($result[$forumoff->id]));
492        $this->assertEquals(true, isset($result[$forumforce->id]));
493        $this->assertEquals(2, $result[$forumforce->id]->unread);
494        $this->assertEquals(false, isset($result[$forumoptional->id]));
495
496        // Don't allow force.
497        $CFG->forum_allowforcedreadtracking = 0;
498
499        $result = forum_tp_get_course_unread_posts($useron->id, $course->id);
500        $this->assertEquals(0, count($result));
501        $this->assertEquals(false, isset($result[$forumoff->id]));
502        $this->assertEquals(false, isset($result[$forumforce->id]));
503        $this->assertEquals(false, isset($result[$forumoptional->id]));
504
505        $result = forum_tp_get_course_unread_posts($useroff->id, $course->id);
506        $this->assertEquals(0, count($result));
507        $this->assertEquals(false, isset($result[$forumoff->id]));
508        $this->assertEquals(false, isset($result[$forumforce->id]));
509        $this->assertEquals(false, isset($result[$forumoptional->id]));
510    }
511
512    /**
513     * Test the logic in the test_forum_tp_get_untracked_forums() function.
514     */
515    public function test_forum_tp_get_untracked_forums() {
516        global $CFG;
517
518        $this->resetAfterTest();
519
520        $useron = $this->getDataGenerator()->create_user(array('trackforums' => 1));
521        $useroff = $this->getDataGenerator()->create_user(array('trackforums' => 0));
522        $course = $this->getDataGenerator()->create_course();
523        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OFF); // Off.
524        $forumoff = $this->getDataGenerator()->create_module('forum', $options);
525
526        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_FORCED); // On.
527        $forumforce = $this->getDataGenerator()->create_module('forum', $options);
528
529        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OPTIONAL); // Optional.
530        $forumoptional = $this->getDataGenerator()->create_module('forum', $options);
531
532        // Allow force.
533        $CFG->forum_allowforcedreadtracking = 1;
534
535        // On user with force on.
536        $result = forum_tp_get_untracked_forums($useron->id, $course->id);
537        $this->assertEquals(1, count($result));
538        $this->assertEquals(true, isset($result[$forumoff->id]));
539
540        // Off user with force on.
541        $result = forum_tp_get_untracked_forums($useroff->id, $course->id);
542        $this->assertEquals(2, count($result));
543        $this->assertEquals(true, isset($result[$forumoff->id]));
544        $this->assertEquals(true, isset($result[$forumoptional->id]));
545
546        // Don't allow force.
547        $CFG->forum_allowforcedreadtracking = 0;
548
549        // On user with force off.
550        $result = forum_tp_get_untracked_forums($useron->id, $course->id);
551        $this->assertEquals(1, count($result));
552        $this->assertEquals(true, isset($result[$forumoff->id]));
553
554        // Off user with force off.
555        $result = forum_tp_get_untracked_forums($useroff->id, $course->id);
556        $this->assertEquals(3, count($result));
557        $this->assertEquals(true, isset($result[$forumoff->id]));
558        $this->assertEquals(true, isset($result[$forumoptional->id]));
559        $this->assertEquals(true, isset($result[$forumforce->id]));
560
561        // Stop tracking so we can test again.
562        forum_tp_stop_tracking($forumforce->id, $useron->id);
563        forum_tp_stop_tracking($forumoptional->id, $useron->id);
564        forum_tp_stop_tracking($forumforce->id, $useroff->id);
565        forum_tp_stop_tracking($forumoptional->id, $useroff->id);
566
567        // Allow force.
568        $CFG->forum_allowforcedreadtracking = 1;
569
570        // On user with force on.
571        $result = forum_tp_get_untracked_forums($useron->id, $course->id);
572        $this->assertEquals(2, count($result));
573        $this->assertEquals(true, isset($result[$forumoff->id]));
574        $this->assertEquals(true, isset($result[$forumoptional->id]));
575
576        // Off user with force on.
577        $result = forum_tp_get_untracked_forums($useroff->id, $course->id);
578        $this->assertEquals(2, count($result));
579        $this->assertEquals(true, isset($result[$forumoff->id]));
580        $this->assertEquals(true, isset($result[$forumoptional->id]));
581
582        // Don't allow force.
583        $CFG->forum_allowforcedreadtracking = 0;
584
585        // On user with force off.
586        $result = forum_tp_get_untracked_forums($useron->id, $course->id);
587        $this->assertEquals(3, count($result));
588        $this->assertEquals(true, isset($result[$forumoff->id]));
589        $this->assertEquals(true, isset($result[$forumoptional->id]));
590        $this->assertEquals(true, isset($result[$forumforce->id]));
591
592        // Off user with force off.
593        $result = forum_tp_get_untracked_forums($useroff->id, $course->id);
594        $this->assertEquals(3, count($result));
595        $this->assertEquals(true, isset($result[$forumoff->id]));
596        $this->assertEquals(true, isset($result[$forumoptional->id]));
597        $this->assertEquals(true, isset($result[$forumforce->id]));
598    }
599
600    /**
601     * Test subscription using automatic subscription on create.
602     */
603    public function test_forum_auto_subscribe_on_create() {
604        global $CFG;
605
606        $this->resetAfterTest();
607
608        $usercount = 5;
609        $course = $this->getDataGenerator()->create_course();
610        $users = array();
611
612        for ($i = 0; $i < $usercount; $i++) {
613            $user = $this->getDataGenerator()->create_user();
614            $users[] = $user;
615            $this->getDataGenerator()->enrol_user($user->id, $course->id);
616        }
617
618        $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE); // Automatic Subscription.
619        $forum = $this->getDataGenerator()->create_module('forum', $options);
620
621        $result = \mod_forum\subscriptions::fetch_subscribed_users($forum);
622        $this->assertEquals($usercount, count($result));
623        foreach ($users as $user) {
624            $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum));
625        }
626    }
627
628    /**
629     * Test subscription using forced subscription on create.
630     */
631    public function test_forum_forced_subscribe_on_create() {
632        global $CFG;
633
634        $this->resetAfterTest();
635
636        $usercount = 5;
637        $course = $this->getDataGenerator()->create_course();
638        $users = array();
639
640        for ($i = 0; $i < $usercount; $i++) {
641            $user = $this->getDataGenerator()->create_user();
642            $users[] = $user;
643            $this->getDataGenerator()->enrol_user($user->id, $course->id);
644        }
645
646        $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE); // Forced subscription.
647        $forum = $this->getDataGenerator()->create_module('forum', $options);
648
649        $result = \mod_forum\subscriptions::fetch_subscribed_users($forum);
650        $this->assertEquals($usercount, count($result));
651        foreach ($users as $user) {
652            $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum));
653        }
654    }
655
656    /**
657     * Test subscription using optional subscription on create.
658     */
659    public function test_forum_optional_subscribe_on_create() {
660        global $CFG;
661
662        $this->resetAfterTest();
663
664        $usercount = 5;
665        $course = $this->getDataGenerator()->create_course();
666        $users = array();
667
668        for ($i = 0; $i < $usercount; $i++) {
669            $user = $this->getDataGenerator()->create_user();
670            $users[] = $user;
671            $this->getDataGenerator()->enrol_user($user->id, $course->id);
672        }
673
674        $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); // Subscription optional.
675        $forum = $this->getDataGenerator()->create_module('forum', $options);
676
677        $result = \mod_forum\subscriptions::fetch_subscribed_users($forum);
678        // No subscriptions by default.
679        $this->assertEquals(0, count($result));
680        foreach ($users as $user) {
681            $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum));
682        }
683    }
684
685    /**
686     * Test subscription using disallow subscription on create.
687     */
688    public function test_forum_disallow_subscribe_on_create() {
689        global $CFG;
690
691        $this->resetAfterTest();
692
693        $usercount = 5;
694        $course = $this->getDataGenerator()->create_course();
695        $users = array();
696
697        for ($i = 0; $i < $usercount; $i++) {
698            $user = $this->getDataGenerator()->create_user();
699            $users[] = $user;
700            $this->getDataGenerator()->enrol_user($user->id, $course->id);
701        }
702
703        $options = array('course' => $course->id, 'forcesubscribe' => FORUM_DISALLOWSUBSCRIBE); // Subscription prevented.
704        $forum = $this->getDataGenerator()->create_module('forum', $options);
705
706        $result = \mod_forum\subscriptions::fetch_subscribed_users($forum);
707        // No subscriptions by default.
708        $this->assertEquals(0, count($result));
709        foreach ($users as $user) {
710            $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum));
711        }
712    }
713
714    /**
715     * Test that context fetching returns the appropriate context.
716     */
717    public function test_forum_get_context() {
718        global $DB, $PAGE;
719
720        $this->resetAfterTest();
721
722        // Setup test data.
723        $course = $this->getDataGenerator()->create_course();
724        $coursecontext = \context_course::instance($course->id);
725
726        $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
727        $forum = $this->getDataGenerator()->create_module('forum', $options);
728        $forumcm = get_coursemodule_from_instance('forum', $forum->id);
729        $forumcontext = \context_module::instance($forumcm->id);
730
731        // First check that specifying the context results in the correct context being returned.
732        // Do this before we set up the page object and we should return from the coursemodule record.
733        // There should be no DB queries here because the context type was correct.
734        $startcount = $DB->perf_get_reads();
735        $result = forum_get_context($forum->id, $forumcontext);
736        $aftercount = $DB->perf_get_reads();
737        $this->assertEquals($forumcontext, $result);
738        $this->assertEquals(0, $aftercount - $startcount);
739
740        // And a context which is not the correct type.
741        // This tests will result in a DB query to fetch the course_module.
742        $startcount = $DB->perf_get_reads();
743        $result = forum_get_context($forum->id, $coursecontext);
744        $aftercount = $DB->perf_get_reads();
745        $this->assertEquals($forumcontext, $result);
746        $this->assertEquals(1, $aftercount - $startcount);
747
748        // Now do not specify a context at all.
749        // This tests will result in a DB query to fetch the course_module.
750        $startcount = $DB->perf_get_reads();
751        $result = forum_get_context($forum->id);
752        $aftercount = $DB->perf_get_reads();
753        $this->assertEquals($forumcontext, $result);
754        $this->assertEquals(1, $aftercount - $startcount);
755
756        // Set up the default page event to use the forum.
757        $PAGE = new moodle_page();
758        $PAGE->set_context($forumcontext);
759        $PAGE->set_cm($forumcm, $course, $forum);
760
761        // Now specify a context which is not a context_module.
762        // There should be no DB queries here because we use the PAGE.
763        $startcount = $DB->perf_get_reads();
764        $result = forum_get_context($forum->id, $coursecontext);
765        $aftercount = $DB->perf_get_reads();
766        $this->assertEquals($forumcontext, $result);
767        $this->assertEquals(0, $aftercount - $startcount);
768
769        // Now do not specify a context at all.
770        // There should be no DB queries here because we use the PAGE.
771        $startcount = $DB->perf_get_reads();
772        $result = forum_get_context($forum->id);
773        $aftercount = $DB->perf_get_reads();
774        $this->assertEquals($forumcontext, $result);
775        $this->assertEquals(0, $aftercount - $startcount);
776
777        // Now specify the page context of the course instead..
778        $PAGE = new moodle_page();
779        $PAGE->set_context($coursecontext);
780
781        // Now specify a context which is not a context_module.
782        // This tests will result in a DB query to fetch the course_module.
783        $startcount = $DB->perf_get_reads();
784        $result = forum_get_context($forum->id, $coursecontext);
785        $aftercount = $DB->perf_get_reads();
786        $this->assertEquals($forumcontext, $result);
787        $this->assertEquals(1, $aftercount - $startcount);
788
789        // Now do not specify a context at all.
790        // This tests will result in a DB query to fetch the course_module.
791        $startcount = $DB->perf_get_reads();
792        $result = forum_get_context($forum->id);
793        $aftercount = $DB->perf_get_reads();
794        $this->assertEquals($forumcontext, $result);
795        $this->assertEquals(1, $aftercount - $startcount);
796    }
797
798    /**
799     * Test getting the neighbour threads of a discussion.
800     */
801    public function test_forum_get_neighbours() {
802        global $CFG, $DB;
803        $this->resetAfterTest();
804
805        $timenow = time();
806        $timenext = $timenow;
807
808        // Setup test data.
809        $forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum');
810        $course = $this->getDataGenerator()->create_course();
811        $user = $this->getDataGenerator()->create_user();
812        $user2 = $this->getDataGenerator()->create_user();
813
814        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
815        $cm = get_coursemodule_from_instance('forum', $forum->id);
816        $context = context_module::instance($cm->id);
817
818        $record = new stdClass();
819        $record->course = $course->id;
820        $record->userid = $user->id;
821        $record->forum = $forum->id;
822        $record->timemodified = time();
823        $disc1 = $forumgen->create_discussion($record);
824        $record->timemodified++;
825        $disc2 = $forumgen->create_discussion($record);
826        $record->timemodified++;
827        $disc3 = $forumgen->create_discussion($record);
828        $record->timemodified++;
829        $disc4 = $forumgen->create_discussion($record);
830        $record->timemodified++;
831        $disc5 = $forumgen->create_discussion($record);
832
833        // Getting the neighbours.
834        $neighbours = forum_get_discussion_neighbours($cm, $disc1, $forum);
835        $this->assertEmpty($neighbours['prev']);
836        $this->assertEquals($disc2->id, $neighbours['next']->id);
837
838        $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum);
839        $this->assertEquals($disc1->id, $neighbours['prev']->id);
840        $this->assertEquals($disc3->id, $neighbours['next']->id);
841
842        $neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum);
843        $this->assertEquals($disc2->id, $neighbours['prev']->id);
844        $this->assertEquals($disc4->id, $neighbours['next']->id);
845
846        $neighbours = forum_get_discussion_neighbours($cm, $disc4, $forum);
847        $this->assertEquals($disc3->id, $neighbours['prev']->id);
848        $this->assertEquals($disc5->id, $neighbours['next']->id);
849
850        $neighbours = forum_get_discussion_neighbours($cm, $disc5, $forum);
851        $this->assertEquals($disc4->id, $neighbours['prev']->id);
852        $this->assertEmpty($neighbours['next']);
853
854        // Post in some discussions. We manually update the discussion record because
855        // the data generator plays with timemodified in a way that would break this test.
856        $record->timemodified++;
857        $disc1->timemodified = $record->timemodified;
858        $DB->update_record('forum_discussions', $disc1);
859
860        $neighbours = forum_get_discussion_neighbours($cm, $disc5, $forum);
861        $this->assertEquals($disc4->id, $neighbours['prev']->id);
862        $this->assertEquals($disc1->id, $neighbours['next']->id);
863
864        $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum);
865        $this->assertEmpty($neighbours['prev']);
866        $this->assertEquals($disc3->id, $neighbours['next']->id);
867
868        $neighbours = forum_get_discussion_neighbours($cm, $disc1, $forum);
869        $this->assertEquals($disc5->id, $neighbours['prev']->id);
870        $this->assertEmpty($neighbours['next']);
871
872        // After some discussions were created.
873        $record->timemodified++;
874        $disc6 = $forumgen->create_discussion($record);
875        $neighbours = forum_get_discussion_neighbours($cm, $disc6, $forum);
876        $this->assertEquals($disc1->id, $neighbours['prev']->id);
877        $this->assertEmpty($neighbours['next']);
878
879        $record->timemodified++;
880        $disc7 = $forumgen->create_discussion($record);
881        $neighbours = forum_get_discussion_neighbours($cm, $disc7, $forum);
882        $this->assertEquals($disc6->id, $neighbours['prev']->id);
883        $this->assertEmpty($neighbours['next']);
884
885        // Adding timed discussions.
886        $CFG->forum_enabletimedposts = true;
887        $now = $record->timemodified;
888        $past = $now - 600;
889        $future = $now + 600;
890
891        $record = new stdClass();
892        $record->course = $course->id;
893        $record->userid = $user->id;
894        $record->forum = $forum->id;
895        $record->timestart = $past;
896        $record->timeend = $future;
897        $record->timemodified = $now;
898        $record->timemodified++;
899        $disc8 = $forumgen->create_discussion($record);
900        $record->timemodified++;
901        $record->timestart = $future;
902        $record->timeend = 0;
903        $disc9 = $forumgen->create_discussion($record);
904        $record->timemodified++;
905        $record->timestart = 0;
906        $record->timeend = 0;
907        $disc10 = $forumgen->create_discussion($record);
908        $record->timemodified++;
909        $record->timestart = 0;
910        $record->timeend = $past;
911        $disc11 = $forumgen->create_discussion($record);
912        $record->timemodified++;
913        $record->timestart = $past;
914        $record->timeend = $future;
915        $disc12 = $forumgen->create_discussion($record);
916        $record->timemodified++;
917        $record->timestart = $future + 1; // Should be last post for those that can see it.
918        $record->timeend = 0;
919        $disc13 = $forumgen->create_discussion($record);
920
921        // Admin user ignores the timed settings of discussions.
922        // Post ordering taking into account timestart:
923        //  8 = t
924        // 10 = t+3
925        // 11 = t+4
926        // 12 = t+5
927        //  9 = t+60
928        // 13 = t+61.
929        $this->setAdminUser();
930        $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum);
931        $this->assertEquals($disc7->id, $neighbours['prev']->id);
932        $this->assertEquals($disc10->id, $neighbours['next']->id);
933
934        $neighbours = forum_get_discussion_neighbours($cm, $disc9, $forum);
935        $this->assertEquals($disc12->id, $neighbours['prev']->id);
936        $this->assertEquals($disc13->id, $neighbours['next']->id);
937
938        $neighbours = forum_get_discussion_neighbours($cm, $disc10, $forum);
939        $this->assertEquals($disc8->id, $neighbours['prev']->id);
940        $this->assertEquals($disc11->id, $neighbours['next']->id);
941
942        $neighbours = forum_get_discussion_neighbours($cm, $disc11, $forum);
943        $this->assertEquals($disc10->id, $neighbours['prev']->id);
944        $this->assertEquals($disc12->id, $neighbours['next']->id);
945
946        $neighbours = forum_get_discussion_neighbours($cm, $disc12, $forum);
947        $this->assertEquals($disc11->id, $neighbours['prev']->id);
948        $this->assertEquals($disc9->id, $neighbours['next']->id);
949
950        $neighbours = forum_get_discussion_neighbours($cm, $disc13, $forum);
951        $this->assertEquals($disc9->id, $neighbours['prev']->id);
952        $this->assertEmpty($neighbours['next']);
953
954        // Normal user can see their own timed discussions.
955        $this->setUser($user);
956        $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum);
957        $this->assertEquals($disc7->id, $neighbours['prev']->id);
958        $this->assertEquals($disc10->id, $neighbours['next']->id);
959
960        $neighbours = forum_get_discussion_neighbours($cm, $disc9, $forum);
961        $this->assertEquals($disc12->id, $neighbours['prev']->id);
962        $this->assertEquals($disc13->id, $neighbours['next']->id);
963
964        $neighbours = forum_get_discussion_neighbours($cm, $disc10, $forum);
965        $this->assertEquals($disc8->id, $neighbours['prev']->id);
966        $this->assertEquals($disc11->id, $neighbours['next']->id);
967
968        $neighbours = forum_get_discussion_neighbours($cm, $disc11, $forum);
969        $this->assertEquals($disc10->id, $neighbours['prev']->id);
970        $this->assertEquals($disc12->id, $neighbours['next']->id);
971
972        $neighbours = forum_get_discussion_neighbours($cm, $disc12, $forum);
973        $this->assertEquals($disc11->id, $neighbours['prev']->id);
974        $this->assertEquals($disc9->id, $neighbours['next']->id);
975
976        $neighbours = forum_get_discussion_neighbours($cm, $disc13, $forum);
977        $this->assertEquals($disc9->id, $neighbours['prev']->id);
978        $this->assertEmpty($neighbours['next']);
979
980        // Normal user does not ignore timed settings.
981        $this->setUser($user2);
982        $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum);
983        $this->assertEquals($disc7->id, $neighbours['prev']->id);
984        $this->assertEquals($disc10->id, $neighbours['next']->id);
985
986        $neighbours = forum_get_discussion_neighbours($cm, $disc10, $forum);
987        $this->assertEquals($disc8->id, $neighbours['prev']->id);
988        $this->assertEquals($disc12->id, $neighbours['next']->id);
989
990        $neighbours = forum_get_discussion_neighbours($cm, $disc12, $forum);
991        $this->assertEquals($disc10->id, $neighbours['prev']->id);
992        $this->assertEmpty($neighbours['next']);
993
994        // Reset to normal mode.
995        $CFG->forum_enabletimedposts = false;
996        $this->setAdminUser();
997
998        // Two discussions with identical timemodified will sort by id.
999        $record->timemodified += 25;
1000        $DB->update_record('forum_discussions', (object) array('id' => $disc3->id, 'timemodified' => $record->timemodified));
1001        $DB->update_record('forum_discussions', (object) array('id' => $disc2->id, 'timemodified' => $record->timemodified));
1002        $DB->update_record('forum_discussions', (object) array('id' => $disc12->id, 'timemodified' => $record->timemodified - 5));
1003        $disc2 = $DB->get_record('forum_discussions', array('id' => $disc2->id));
1004        $disc3 = $DB->get_record('forum_discussions', array('id' => $disc3->id));
1005
1006        $neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum);
1007        $this->assertEquals($disc2->id, $neighbours['prev']->id);
1008        $this->assertEmpty($neighbours['next']);
1009
1010        $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum);
1011        $this->assertEquals($disc12->id, $neighbours['prev']->id);
1012        $this->assertEquals($disc3->id, $neighbours['next']->id);
1013
1014        // Set timemodified to not be identical.
1015        $DB->update_record('forum_discussions', (object) array('id' => $disc2->id, 'timemodified' => $record->timemodified - 1));
1016
1017        // Test pinned posts behave correctly.
1018        $disc8->pinned = FORUM_DISCUSSION_PINNED;
1019        $DB->update_record('forum_discussions', (object) array('id' => $disc8->id, 'pinned' => $disc8->pinned));
1020        $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum);
1021        $this->assertEquals($disc3->id, $neighbours['prev']->id);
1022        $this->assertEmpty($neighbours['next']);
1023
1024        $neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum);
1025        $this->assertEquals($disc2->id, $neighbours['prev']->id);
1026        $this->assertEquals($disc8->id, $neighbours['next']->id);
1027
1028        // Test 3 pinned posts.
1029        $disc6->pinned = FORUM_DISCUSSION_PINNED;
1030        $DB->update_record('forum_discussions', (object) array('id' => $disc6->id, 'pinned' => $disc6->pinned));
1031        $disc4->pinned = FORUM_DISCUSSION_PINNED;
1032        $DB->update_record('forum_discussions', (object) array('id' => $disc4->id, 'pinned' => $disc4->pinned));
1033
1034        $neighbours = forum_get_discussion_neighbours($cm, $disc6, $forum);
1035        $this->assertEquals($disc4->id, $neighbours['prev']->id);
1036        $this->assertEquals($disc8->id, $neighbours['next']->id);
1037
1038        $neighbours = forum_get_discussion_neighbours($cm, $disc4, $forum);
1039        $this->assertEquals($disc3->id, $neighbours['prev']->id);
1040        $this->assertEquals($disc6->id, $neighbours['next']->id);
1041
1042        $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum);
1043        $this->assertEquals($disc6->id, $neighbours['prev']->id);
1044        $this->assertEmpty($neighbours['next']);
1045    }
1046
1047    /**
1048     * Test getting the neighbour threads of a blog-like forum.
1049     */
1050    public function test_forum_get_neighbours_blog() {
1051        global $CFG, $DB;
1052        $this->resetAfterTest();
1053
1054        $timenow = time();
1055        $timenext = $timenow;
1056
1057        // Setup test data.
1058        $forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum');
1059        $course = $this->getDataGenerator()->create_course();
1060        $user = $this->getDataGenerator()->create_user();
1061        $user2 = $this->getDataGenerator()->create_user();
1062
1063        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'type' => 'blog'));
1064        $cm = get_coursemodule_from_instance('forum', $forum->id);
1065        $context = context_module::instance($cm->id);
1066
1067        $record = new stdClass();
1068        $record->course = $course->id;
1069        $record->userid = $user->id;
1070        $record->forum = $forum->id;
1071        $record->timemodified = time();
1072        $disc1 = $forumgen->create_discussion($record);
1073        $record->timemodified++;
1074        $disc2 = $forumgen->create_discussion($record);
1075        $record->timemodified++;
1076        $disc3 = $forumgen->create_discussion($record);
1077        $record->timemodified++;
1078        $disc4 = $forumgen->create_discussion($record);
1079        $record->timemodified++;
1080        $disc5 = $forumgen->create_discussion($record);
1081
1082        // Getting the neighbours.
1083        $neighbours = forum_get_discussion_neighbours($cm, $disc1, $forum);
1084        $this->assertEmpty($neighbours['prev']);
1085        $this->assertEquals($disc2->id, $neighbours['next']->id);
1086
1087        $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum);
1088        $this->assertEquals($disc1->id, $neighbours['prev']->id);
1089        $this->assertEquals($disc3->id, $neighbours['next']->id);
1090
1091        $neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum);
1092        $this->assertEquals($disc2->id, $neighbours['prev']->id);
1093        $this->assertEquals($disc4->id, $neighbours['next']->id);
1094
1095        $neighbours = forum_get_discussion_neighbours($cm, $disc4, $forum);
1096        $this->assertEquals($disc3->id, $neighbours['prev']->id);
1097        $this->assertEquals($disc5->id, $neighbours['next']->id);
1098
1099        $neighbours = forum_get_discussion_neighbours($cm, $disc5, $forum);
1100        $this->assertEquals($disc4->id, $neighbours['prev']->id);
1101        $this->assertEmpty($neighbours['next']);
1102
1103        // Make sure that the thread's timemodified does not affect the order.
1104        $record->timemodified++;
1105        $disc1->timemodified = $record->timemodified;
1106        $DB->update_record('forum_discussions', $disc1);
1107
1108        $neighbours = forum_get_discussion_neighbours($cm, $disc1, $forum);
1109        $this->assertEmpty($neighbours['prev']);
1110        $this->assertEquals($disc2->id, $neighbours['next']->id);
1111
1112        $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum);
1113        $this->assertEquals($disc1->id, $neighbours['prev']->id);
1114        $this->assertEquals($disc3->id, $neighbours['next']->id);
1115
1116        // Add another blog post.
1117        $record->timemodified++;
1118        $disc6 = $forumgen->create_discussion($record);
1119        $neighbours = forum_get_discussion_neighbours($cm, $disc6, $forum);
1120        $this->assertEquals($disc5->id, $neighbours['prev']->id);
1121        $this->assertEmpty($neighbours['next']);
1122
1123        $record->timemodified++;
1124        $disc7 = $forumgen->create_discussion($record);
1125        $neighbours = forum_get_discussion_neighbours($cm, $disc7, $forum);
1126        $this->assertEquals($disc6->id, $neighbours['prev']->id);
1127        $this->assertEmpty($neighbours['next']);
1128
1129        // Adding timed discussions.
1130        $CFG->forum_enabletimedposts = true;
1131        $now = $record->timemodified;
1132        $past = $now - 600;
1133        $future = $now + 600;
1134
1135        $record = new stdClass();
1136        $record->course = $course->id;
1137        $record->userid = $user->id;
1138        $record->forum = $forum->id;
1139        $record->timestart = $past;
1140        $record->timeend = $future;
1141        $record->timemodified = $now;
1142        $record->timemodified++;
1143        $disc8 = $forumgen->create_discussion($record);
1144        $record->timemodified++;
1145        $record->timestart = $future;
1146        $record->timeend = 0;
1147        $disc9 = $forumgen->create_discussion($record);
1148        $record->timemodified++;
1149        $record->timestart = 0;
1150        $record->timeend = 0;
1151        $disc10 = $forumgen->create_discussion($record);
1152        $record->timemodified++;
1153        $record->timestart = 0;
1154        $record->timeend = $past;
1155        $disc11 = $forumgen->create_discussion($record);
1156        $record->timemodified++;
1157        $record->timestart = $past;
1158        $record->timeend = $future;
1159        $disc12 = $forumgen->create_discussion($record);
1160
1161        // Admin user ignores the timed settings of discussions.
1162        $this->setAdminUser();
1163        $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum);
1164        $this->assertEquals($disc7->id, $neighbours['prev']->id);
1165        $this->assertEquals($disc9->id, $neighbours['next']->id);
1166
1167        $neighbours = forum_get_discussion_neighbours($cm, $disc9, $forum);
1168        $this->assertEquals($disc8->id, $neighbours['prev']->id);
1169        $this->assertEquals($disc10->id, $neighbours['next']->id);
1170
1171        $neighbours = forum_get_discussion_neighbours($cm, $disc10, $forum);
1172        $this->assertEquals($disc9->id, $neighbours['prev']->id);
1173        $this->assertEquals($disc11->id, $neighbours['next']->id);
1174
1175        $neighbours = forum_get_discussion_neighbours($cm, $disc11, $forum);
1176        $this->assertEquals($disc10->id, $neighbours['prev']->id);
1177        $this->assertEquals($disc12->id, $neighbours['next']->id);
1178
1179        $neighbours = forum_get_discussion_neighbours($cm, $disc12, $forum);
1180        $this->assertEquals($disc11->id, $neighbours['prev']->id);
1181        $this->assertEmpty($neighbours['next']);
1182
1183        // Normal user can see their own timed discussions.
1184        $this->setUser($user);
1185        $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum);
1186        $this->assertEquals($disc7->id, $neighbours['prev']->id);
1187        $this->assertEquals($disc9->id, $neighbours['next']->id);
1188
1189        $neighbours = forum_get_discussion_neighbours($cm, $disc9, $forum);
1190        $this->assertEquals($disc8->id, $neighbours['prev']->id);
1191        $this->assertEquals($disc10->id, $neighbours['next']->id);
1192
1193        $neighbours = forum_get_discussion_neighbours($cm, $disc10, $forum);
1194        $this->assertEquals($disc9->id, $neighbours['prev']->id);
1195        $this->assertEquals($disc11->id, $neighbours['next']->id);
1196
1197        $neighbours = forum_get_discussion_neighbours($cm, $disc11, $forum);
1198        $this->assertEquals($disc10->id, $neighbours['prev']->id);
1199        $this->assertEquals($disc12->id, $neighbours['next']->id);
1200
1201        $neighbours = forum_get_discussion_neighbours($cm, $disc12, $forum);
1202        $this->assertEquals($disc11->id, $neighbours['prev']->id);
1203        $this->assertEmpty($neighbours['next']);
1204
1205        // Normal user does not ignore timed settings.
1206        $this->setUser($user2);
1207        $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum);
1208        $this->assertEquals($disc7->id, $neighbours['prev']->id);
1209        $this->assertEquals($disc10->id, $neighbours['next']->id);
1210
1211        $neighbours = forum_get_discussion_neighbours($cm, $disc10, $forum);
1212        $this->assertEquals($disc8->id, $neighbours['prev']->id);
1213        $this->assertEquals($disc12->id, $neighbours['next']->id);
1214
1215        $neighbours = forum_get_discussion_neighbours($cm, $disc12, $forum);
1216        $this->assertEquals($disc10->id, $neighbours['prev']->id);
1217        $this->assertEmpty($neighbours['next']);
1218
1219        // Reset to normal mode.
1220        $CFG->forum_enabletimedposts = false;
1221        $this->setAdminUser();
1222
1223        $record->timemodified++;
1224        // Two blog posts with identical creation time will sort by id.
1225        $DB->update_record('forum_posts', (object) array('id' => $disc2->firstpost, 'created' => $record->timemodified));
1226        $DB->update_record('forum_posts', (object) array('id' => $disc3->firstpost, 'created' => $record->timemodified));
1227
1228        $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum);
1229        $this->assertEquals($disc12->id, $neighbours['prev']->id);
1230        $this->assertEquals($disc3->id, $neighbours['next']->id);
1231
1232        $neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum);
1233        $this->assertEquals($disc2->id, $neighbours['prev']->id);
1234        $this->assertEmpty($neighbours['next']);
1235    }
1236
1237    /**
1238     * Test getting the neighbour threads of a discussion.
1239     */
1240    public function test_forum_get_neighbours_with_groups() {
1241        $this->resetAfterTest();
1242
1243        $timenow = time();
1244        $timenext = $timenow;
1245
1246        // Setup test data.
1247        $forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum');
1248        $course = $this->getDataGenerator()->create_course();
1249        $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1250        $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1251        $user1 = $this->getDataGenerator()->create_user();
1252        $user2 = $this->getDataGenerator()->create_user();
1253        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
1254        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
1255        $this->getDataGenerator()->create_group_member(array('userid' => $user1->id, 'groupid' => $group1->id));
1256
1257        $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'groupmode' => VISIBLEGROUPS));
1258        $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'groupmode' => SEPARATEGROUPS));
1259        $cm1 = get_coursemodule_from_instance('forum', $forum1->id);
1260        $cm2 = get_coursemodule_from_instance('forum', $forum2->id);
1261        $context1 = context_module::instance($cm1->id);
1262        $context2 = context_module::instance($cm2->id);
1263
1264        // Creating discussions in both forums.
1265        $record = new stdClass();
1266        $record->course = $course->id;
1267        $record->userid = $user1->id;
1268        $record->forum = $forum1->id;
1269        $record->groupid = $group1->id;
1270        $record->timemodified = time();
1271        $disc11 = $forumgen->create_discussion($record);
1272        $record->forum = $forum2->id;
1273        $record->timemodified++;
1274        $disc21 = $forumgen->create_discussion($record);
1275
1276        $record->timemodified++;
1277        $record->userid = $user2->id;
1278        $record->forum = $forum1->id;
1279        $record->groupid = $group2->id;
1280        $disc12 = $forumgen->create_discussion($record);
1281        $record->forum = $forum2->id;
1282        $disc22 = $forumgen->create_discussion($record);
1283
1284        $record->timemodified++;
1285        $record->userid = $user1->id;
1286        $record->forum = $forum1->id;
1287        $record->groupid = null;
1288        $disc13 = $forumgen->create_discussion($record);
1289        $record->forum = $forum2->id;
1290        $disc23 = $forumgen->create_discussion($record);
1291
1292        $record->timemodified++;
1293        $record->userid = $user2->id;
1294        $record->forum = $forum1->id;
1295        $record->groupid = $group2->id;
1296        $disc14 = $forumgen->create_discussion($record);
1297        $record->forum = $forum2->id;
1298        $disc24 = $forumgen->create_discussion($record);
1299
1300        $record->timemodified++;
1301        $record->userid = $user1->id;
1302        $record->forum = $forum1->id;
1303        $record->groupid = $group1->id;
1304        $disc15 = $forumgen->create_discussion($record);
1305        $record->forum = $forum2->id;
1306        $disc25 = $forumgen->create_discussion($record);
1307
1308        // Admin user can see all groups.
1309        $this->setAdminUser();
1310        $neighbours = forum_get_discussion_neighbours($cm1, $disc11, $forum1);
1311        $this->assertEmpty($neighbours['prev']);
1312        $this->assertEquals($disc12->id, $neighbours['next']->id);
1313        $neighbours = forum_get_discussion_neighbours($cm2, $disc21, $forum2);
1314        $this->assertEmpty($neighbours['prev']);
1315        $this->assertEquals($disc22->id, $neighbours['next']->id);
1316
1317        $neighbours = forum_get_discussion_neighbours($cm1, $disc12, $forum1);
1318        $this->assertEquals($disc11->id, $neighbours['prev']->id);
1319        $this->assertEquals($disc13->id, $neighbours['next']->id);
1320        $neighbours = forum_get_discussion_neighbours($cm2, $disc22, $forum2);
1321        $this->assertEquals($disc21->id, $neighbours['prev']->id);
1322        $this->assertEquals($disc23->id, $neighbours['next']->id);
1323
1324        $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1);
1325        $this->assertEquals($disc12->id, $neighbours['prev']->id);
1326        $this->assertEquals($disc14->id, $neighbours['next']->id);
1327        $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2);
1328        $this->assertEquals($disc22->id, $neighbours['prev']->id);
1329        $this->assertEquals($disc24->id, $neighbours['next']->id);
1330
1331        $neighbours = forum_get_discussion_neighbours($cm1, $disc14, $forum1);
1332        $this->assertEquals($disc13->id, $neighbours['prev']->id);
1333        $this->assertEquals($disc15->id, $neighbours['next']->id);
1334        $neighbours = forum_get_discussion_neighbours($cm2, $disc24, $forum2);
1335        $this->assertEquals($disc23->id, $neighbours['prev']->id);
1336        $this->assertEquals($disc25->id, $neighbours['next']->id);
1337
1338        $neighbours = forum_get_discussion_neighbours($cm1, $disc15, $forum1);
1339        $this->assertEquals($disc14->id, $neighbours['prev']->id);
1340        $this->assertEmpty($neighbours['next']);
1341        $neighbours = forum_get_discussion_neighbours($cm2, $disc25, $forum2);
1342        $this->assertEquals($disc24->id, $neighbours['prev']->id);
1343        $this->assertEmpty($neighbours['next']);
1344
1345        // Admin user is only viewing group 1.
1346        $_POST['group'] = $group1->id;
1347        $this->assertEquals($group1->id, groups_get_activity_group($cm1, true));
1348        $this->assertEquals($group1->id, groups_get_activity_group($cm2, true));
1349
1350        $neighbours = forum_get_discussion_neighbours($cm1, $disc11, $forum1);
1351        $this->assertEmpty($neighbours['prev']);
1352        $this->assertEquals($disc13->id, $neighbours['next']->id);
1353        $neighbours = forum_get_discussion_neighbours($cm2, $disc21, $forum2);
1354        $this->assertEmpty($neighbours['prev']);
1355        $this->assertEquals($disc23->id, $neighbours['next']->id);
1356
1357        $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1);
1358        $this->assertEquals($disc11->id, $neighbours['prev']->id);
1359        $this->assertEquals($disc15->id, $neighbours['next']->id);
1360        $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2);
1361        $this->assertEquals($disc21->id, $neighbours['prev']->id);
1362        $this->assertEquals($disc25->id, $neighbours['next']->id);
1363
1364        $neighbours = forum_get_discussion_neighbours($cm1, $disc15, $forum1);
1365        $this->assertEquals($disc13->id, $neighbours['prev']->id);
1366        $this->assertEmpty($neighbours['next']);
1367        $neighbours = forum_get_discussion_neighbours($cm2, $disc25, $forum2);
1368        $this->assertEquals($disc23->id, $neighbours['prev']->id);
1369        $this->assertEmpty($neighbours['next']);
1370
1371        // Normal user viewing non-grouped posts (this is only possible in visible groups).
1372        $this->setUser($user1);
1373        $_POST['group'] = 0;
1374        $this->assertEquals(0, groups_get_activity_group($cm1, true));
1375
1376        // They can see anything in visible groups.
1377        $neighbours = forum_get_discussion_neighbours($cm1, $disc12, $forum1);
1378        $this->assertEquals($disc11->id, $neighbours['prev']->id);
1379        $this->assertEquals($disc13->id, $neighbours['next']->id);
1380        $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1);
1381        $this->assertEquals($disc12->id, $neighbours['prev']->id);
1382        $this->assertEquals($disc14->id, $neighbours['next']->id);
1383
1384        // Normal user, orphan of groups, can only see non-grouped posts in separate groups.
1385        $this->setUser($user2);
1386        $_POST['group'] = 0;
1387        $this->assertEquals(0, groups_get_activity_group($cm2, true));
1388
1389        $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2);
1390        $this->assertEmpty($neighbours['prev']);
1391        $this->assertEmpty($neighbours['next']);
1392
1393        $neighbours = forum_get_discussion_neighbours($cm2, $disc22, $forum2);
1394        $this->assertEmpty($neighbours['prev']);
1395        $this->assertEquals($disc23->id, $neighbours['next']->id);
1396
1397        $neighbours = forum_get_discussion_neighbours($cm2, $disc24, $forum2);
1398        $this->assertEquals($disc23->id, $neighbours['prev']->id);
1399        $this->assertEmpty($neighbours['next']);
1400
1401        // Switching to viewing group 1.
1402        $this->setUser($user1);
1403        $_POST['group'] = $group1->id;
1404        $this->assertEquals($group1->id, groups_get_activity_group($cm1, true));
1405        $this->assertEquals($group1->id, groups_get_activity_group($cm2, true));
1406
1407        // They can see non-grouped or same group.
1408        $neighbours = forum_get_discussion_neighbours($cm1, $disc11, $forum1);
1409        $this->assertEmpty($neighbours['prev']);
1410        $this->assertEquals($disc13->id, $neighbours['next']->id);
1411        $neighbours = forum_get_discussion_neighbours($cm2, $disc21, $forum2);
1412        $this->assertEmpty($neighbours['prev']);
1413        $this->assertEquals($disc23->id, $neighbours['next']->id);
1414
1415        $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1);
1416        $this->assertEquals($disc11->id, $neighbours['prev']->id);
1417        $this->assertEquals($disc15->id, $neighbours['next']->id);
1418        $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2);
1419        $this->assertEquals($disc21->id, $neighbours['prev']->id);
1420        $this->assertEquals($disc25->id, $neighbours['next']->id);
1421
1422        $neighbours = forum_get_discussion_neighbours($cm1, $disc15, $forum1);
1423        $this->assertEquals($disc13->id, $neighbours['prev']->id);
1424        $this->assertEmpty($neighbours['next']);
1425        $neighbours = forum_get_discussion_neighbours($cm2, $disc25, $forum2);
1426        $this->assertEquals($disc23->id, $neighbours['prev']->id);
1427        $this->assertEmpty($neighbours['next']);
1428
1429        // Querying the neighbours of a discussion passing the wrong CM.
1430        $this->expectException('coding_exception');
1431        forum_get_discussion_neighbours($cm2, $disc11, $forum2);
1432    }
1433
1434    /**
1435     * Test getting the neighbour threads of a blog-like forum with groups involved.
1436     */
1437    public function test_forum_get_neighbours_with_groups_blog() {
1438        $this->resetAfterTest();
1439
1440        $timenow = time();
1441        $timenext = $timenow;
1442
1443        // Setup test data.
1444        $forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum');
1445        $course = $this->getDataGenerator()->create_course();
1446        $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1447        $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1448        $user1 = $this->getDataGenerator()->create_user();
1449        $user2 = $this->getDataGenerator()->create_user();
1450        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
1451        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
1452        $this->getDataGenerator()->create_group_member(array('userid' => $user1->id, 'groupid' => $group1->id));
1453
1454        $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'type' => 'blog',
1455                'groupmode' => VISIBLEGROUPS));
1456        $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'type' => 'blog',
1457                'groupmode' => SEPARATEGROUPS));
1458        $cm1 = get_coursemodule_from_instance('forum', $forum1->id);
1459        $cm2 = get_coursemodule_from_instance('forum', $forum2->id);
1460        $context1 = context_module::instance($cm1->id);
1461        $context2 = context_module::instance($cm2->id);
1462
1463        // Creating blog posts in both forums.
1464        $record = new stdClass();
1465        $record->course = $course->id;
1466        $record->userid = $user1->id;
1467        $record->forum = $forum1->id;
1468        $record->groupid = $group1->id;
1469        $record->timemodified = time();
1470        $disc11 = $forumgen->create_discussion($record);
1471        $record->timenow = $timenext++;
1472        $record->forum = $forum2->id;
1473        $record->timemodified++;
1474        $disc21 = $forumgen->create_discussion($record);
1475
1476        $record->timemodified++;
1477        $record->userid = $user2->id;
1478        $record->forum = $forum1->id;
1479        $record->groupid = $group2->id;
1480        $disc12 = $forumgen->create_discussion($record);
1481        $record->forum = $forum2->id;
1482        $disc22 = $forumgen->create_discussion($record);
1483
1484        $record->timemodified++;
1485        $record->userid = $user1->id;
1486        $record->forum = $forum1->id;
1487        $record->groupid = null;
1488        $disc13 = $forumgen->create_discussion($record);
1489        $record->forum = $forum2->id;
1490        $disc23 = $forumgen->create_discussion($record);
1491
1492        $record->timemodified++;
1493        $record->userid = $user2->id;
1494        $record->forum = $forum1->id;
1495        $record->groupid = $group2->id;
1496        $disc14 = $forumgen->create_discussion($record);
1497        $record->forum = $forum2->id;
1498        $disc24 = $forumgen->create_discussion($record);
1499
1500        $record->timemodified++;
1501        $record->userid = $user1->id;
1502        $record->forum = $forum1->id;
1503        $record->groupid = $group1->id;
1504        $disc15 = $forumgen->create_discussion($record);
1505        $record->forum = $forum2->id;
1506        $disc25 = $forumgen->create_discussion($record);
1507
1508        // Admin user can see all groups.
1509        $this->setAdminUser();
1510        $neighbours = forum_get_discussion_neighbours($cm1, $disc11, $forum1);
1511        $this->assertEmpty($neighbours['prev']);
1512        $this->assertEquals($disc12->id, $neighbours['next']->id);
1513        $neighbours = forum_get_discussion_neighbours($cm2, $disc21, $forum2);
1514        $this->assertEmpty($neighbours['prev']);
1515        $this->assertEquals($disc22->id, $neighbours['next']->id);
1516
1517        $neighbours = forum_get_discussion_neighbours($cm1, $disc12, $forum1);
1518        $this->assertEquals($disc11->id, $neighbours['prev']->id);
1519        $this->assertEquals($disc13->id, $neighbours['next']->id);
1520        $neighbours = forum_get_discussion_neighbours($cm2, $disc22, $forum2);
1521        $this->assertEquals($disc21->id, $neighbours['prev']->id);
1522        $this->assertEquals($disc23->id, $neighbours['next']->id);
1523
1524        $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1);
1525        $this->assertEquals($disc12->id, $neighbours['prev']->id);
1526        $this->assertEquals($disc14->id, $neighbours['next']->id);
1527        $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2);
1528        $this->assertEquals($disc22->id, $neighbours['prev']->id);
1529        $this->assertEquals($disc24->id, $neighbours['next']->id);
1530
1531        $neighbours = forum_get_discussion_neighbours($cm1, $disc14, $forum1);
1532        $this->assertEquals($disc13->id, $neighbours['prev']->id);
1533        $this->assertEquals($disc15->id, $neighbours['next']->id);
1534        $neighbours = forum_get_discussion_neighbours($cm2, $disc24, $forum2);
1535        $this->assertEquals($disc23->id, $neighbours['prev']->id);
1536        $this->assertEquals($disc25->id, $neighbours['next']->id);
1537
1538        $neighbours = forum_get_discussion_neighbours($cm1, $disc15, $forum1);
1539        $this->assertEquals($disc14->id, $neighbours['prev']->id);
1540        $this->assertEmpty($neighbours['next']);
1541        $neighbours = forum_get_discussion_neighbours($cm2, $disc25, $forum2);
1542        $this->assertEquals($disc24->id, $neighbours['prev']->id);
1543        $this->assertEmpty($neighbours['next']);
1544
1545        // Admin user is only viewing group 1.
1546        $_POST['group'] = $group1->id;
1547        $this->assertEquals($group1->id, groups_get_activity_group($cm1, true));
1548        $this->assertEquals($group1->id, groups_get_activity_group($cm2, true));
1549
1550        $neighbours = forum_get_discussion_neighbours($cm1, $disc11, $forum1);
1551        $this->assertEmpty($neighbours['prev']);
1552        $this->assertEquals($disc13->id, $neighbours['next']->id);
1553        $neighbours = forum_get_discussion_neighbours($cm2, $disc21, $forum2);
1554        $this->assertEmpty($neighbours['prev']);
1555        $this->assertEquals($disc23->id, $neighbours['next']->id);
1556
1557        $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1);
1558        $this->assertEquals($disc11->id, $neighbours['prev']->id);
1559        $this->assertEquals($disc15->id, $neighbours['next']->id);
1560        $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2);
1561        $this->assertEquals($disc21->id, $neighbours['prev']->id);
1562        $this->assertEquals($disc25->id, $neighbours['next']->id);
1563
1564        $neighbours = forum_get_discussion_neighbours($cm1, $disc15, $forum1);
1565        $this->assertEquals($disc13->id, $neighbours['prev']->id);
1566        $this->assertEmpty($neighbours['next']);
1567        $neighbours = forum_get_discussion_neighbours($cm2, $disc25, $forum2);
1568        $this->assertEquals($disc23->id, $neighbours['prev']->id);
1569        $this->assertEmpty($neighbours['next']);
1570
1571        // Normal user viewing non-grouped posts (this is only possible in visible groups).
1572        $this->setUser($user1);
1573        $_POST['group'] = 0;
1574        $this->assertEquals(0, groups_get_activity_group($cm1, true));
1575
1576        // They can see anything in visible groups.
1577        $neighbours = forum_get_discussion_neighbours($cm1, $disc12, $forum1);
1578        $this->assertEquals($disc11->id, $neighbours['prev']->id);
1579        $this->assertEquals($disc13->id, $neighbours['next']->id);
1580        $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1);
1581        $this->assertEquals($disc12->id, $neighbours['prev']->id);
1582        $this->assertEquals($disc14->id, $neighbours['next']->id);
1583
1584        // Normal user, orphan of groups, can only see non-grouped posts in separate groups.
1585        $this->setUser($user2);
1586        $_POST['group'] = 0;
1587        $this->assertEquals(0, groups_get_activity_group($cm2, true));
1588
1589        $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2);
1590        $this->assertEmpty($neighbours['prev']);
1591        $this->assertEmpty($neighbours['next']);
1592
1593        $neighbours = forum_get_discussion_neighbours($cm2, $disc22, $forum2);
1594        $this->assertEmpty($neighbours['prev']);
1595        $this->assertEquals($disc23->id, $neighbours['next']->id);
1596
1597        $neighbours = forum_get_discussion_neighbours($cm2, $disc24, $forum2);
1598        $this->assertEquals($disc23->id, $neighbours['prev']->id);
1599        $this->assertEmpty($neighbours['next']);
1600
1601        // Switching to viewing group 1.
1602        $this->setUser($user1);
1603        $_POST['group'] = $group1->id;
1604        $this->assertEquals($group1->id, groups_get_activity_group($cm1, true));
1605        $this->assertEquals($group1->id, groups_get_activity_group($cm2, true));
1606
1607        // They can see non-grouped or same group.
1608        $neighbours = forum_get_discussion_neighbours($cm1, $disc11, $forum1);
1609        $this->assertEmpty($neighbours['prev']);
1610        $this->assertEquals($disc13->id, $neighbours['next']->id);
1611        $neighbours = forum_get_discussion_neighbours($cm2, $disc21, $forum2);
1612        $this->assertEmpty($neighbours['prev']);
1613        $this->assertEquals($disc23->id, $neighbours['next']->id);
1614
1615        $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1);
1616        $this->assertEquals($disc11->id, $neighbours['prev']->id);
1617        $this->assertEquals($disc15->id, $neighbours['next']->id);
1618        $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2);
1619        $this->assertEquals($disc21->id, $neighbours['prev']->id);
1620        $this->assertEquals($disc25->id, $neighbours['next']->id);
1621
1622        $neighbours = forum_get_discussion_neighbours($cm1, $disc15, $forum1);
1623        $this->assertEquals($disc13->id, $neighbours['prev']->id);
1624        $this->assertEmpty($neighbours['next']);
1625        $neighbours = forum_get_discussion_neighbours($cm2, $disc25, $forum2);
1626        $this->assertEquals($disc23->id, $neighbours['prev']->id);
1627        $this->assertEmpty($neighbours['next']);
1628
1629        // Querying the neighbours of a discussion passing the wrong CM.
1630        $this->expectException('coding_exception');
1631        forum_get_discussion_neighbours($cm2, $disc11, $forum2);
1632    }
1633
1634    public function test_count_discussion_replies_basic() {
1635        list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5);
1636
1637        // Count the discussion replies in the forum.
1638        $result = forum_count_discussion_replies($forum->id);
1639        $this->assertCount(10, $result);
1640    }
1641
1642    public function test_count_discussion_replies_limited() {
1643        list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5);
1644        // Adding limits shouldn't make a difference.
1645        $result = forum_count_discussion_replies($forum->id, "", 20);
1646        $this->assertCount(10, $result);
1647    }
1648
1649    public function test_count_discussion_replies_paginated() {
1650        list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5);
1651        // Adding paging shouldn't make any difference.
1652        $result = forum_count_discussion_replies($forum->id, "", -1, 0, 100);
1653        $this->assertCount(10, $result);
1654    }
1655
1656    public function test_count_discussion_replies_paginated_sorted() {
1657        list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5);
1658        // Specifying the forumsort should also give a good result. This follows a different path.
1659        $result = forum_count_discussion_replies($forum->id, "d.id asc", -1, 0, 100);
1660        $this->assertCount(10, $result);
1661        foreach ($result as $row) {
1662            // Grab the first discussionid.
1663            $discussionid = array_shift($discussionids);
1664            $this->assertEquals($discussionid, $row->discussion);
1665        }
1666    }
1667
1668    public function test_count_discussion_replies_limited_sorted() {
1669        list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5);
1670        // Adding limits, and a forumsort shouldn't make a difference.
1671        $result = forum_count_discussion_replies($forum->id, "d.id asc", 20);
1672        $this->assertCount(10, $result);
1673        foreach ($result as $row) {
1674            // Grab the first discussionid.
1675            $discussionid = array_shift($discussionids);
1676            $this->assertEquals($discussionid, $row->discussion);
1677        }
1678    }
1679
1680    public function test_count_discussion_replies_paginated_sorted_small() {
1681        list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5);
1682        // Grabbing a smaller subset and they should be ordered as expected.
1683        $result = forum_count_discussion_replies($forum->id, "d.id asc", -1, 0, 5);
1684        $this->assertCount(5, $result);
1685        foreach ($result as $row) {
1686            // Grab the first discussionid.
1687            $discussionid = array_shift($discussionids);
1688            $this->assertEquals($discussionid, $row->discussion);
1689        }
1690    }
1691
1692    public function test_count_discussion_replies_paginated_sorted_small_reverse() {
1693        list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5);
1694        // Grabbing a smaller subset and they should be ordered as expected.
1695        $result = forum_count_discussion_replies($forum->id, "d.id desc", -1, 0, 5);
1696        $this->assertCount(5, $result);
1697        foreach ($result as $row) {
1698            // Grab the last discussionid.
1699            $discussionid = array_pop($discussionids);
1700            $this->assertEquals($discussionid, $row->discussion);
1701        }
1702    }
1703
1704    public function test_count_discussion_replies_limited_sorted_small_reverse() {
1705        list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5);
1706        // Adding limits, and a forumsort shouldn't make a difference.
1707        $result = forum_count_discussion_replies($forum->id, "d.id desc", 5);
1708        $this->assertCount(5, $result);
1709        foreach ($result as $row) {
1710            // Grab the last discussionid.
1711            $discussionid = array_pop($discussionids);
1712            $this->assertEquals($discussionid, $row->discussion);
1713        }
1714    }
1715
1716    /**
1717     * Test the reply count when used with private replies.
1718     */
1719    public function test_forum_count_discussion_replies_private() {
1720        global $DB;
1721        $this->resetAfterTest();
1722
1723        $course = $this->getDataGenerator()->create_course();
1724        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
1725        $context = context_module::instance($forum->cmid);
1726        $cm = get_coursemodule_from_instance('forum', $forum->id);
1727
1728        $student = $this->getDataGenerator()->create_user();
1729        $this->getDataGenerator()->enrol_user($student->id, $course->id);
1730
1731        $teacher = $this->getDataGenerator()->create_user();
1732        $this->getDataGenerator()->enrol_user($teacher->id, $course->id);
1733
1734        $privilegeduser = $this->getDataGenerator()->create_user();
1735        $this->getDataGenerator()->enrol_user($privilegeduser->id, $course->id, 'editingteacher');
1736
1737        $otheruser = $this->getDataGenerator()->create_user();
1738        $this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
1739
1740        $generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
1741
1742        // Create a discussion with some replies.
1743        $record = new stdClass();
1744        $record->course = $forum->course;
1745        $record->forum = $forum->id;
1746        $record->userid = $student->id;
1747        $discussion = $generator->create_discussion($record);
1748        $replycount = 5;
1749        $replyto = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
1750
1751        // Create a couple of standard replies.
1752        $post = new stdClass();
1753        $post->userid = $student->id;
1754        $post->discussion = $discussion->id;
1755        $post->parent = $replyto->id;
1756
1757        for ($i = 0; $i < $replycount; $i++) {
1758            $post = $generator->create_post($post);
1759        }
1760
1761        // Create a private reply post from the teacher back to the student.
1762        $reply = new stdClass();
1763        $reply->userid = $teacher->id;
1764        $reply->discussion = $discussion->id;
1765        $reply->parent = $replyto->id;
1766        $reply->privatereplyto = $replyto->userid;
1767        $generator->create_post($reply);
1768
1769        // The user is the author of the private reply.
1770        $this->setUser($teacher->id);
1771        $counts = forum_count_discussion_replies($forum->id);
1772        $this->assertEquals($replycount + 1, $counts[$discussion->id]->replies);
1773
1774        // The user is the intended recipient.
1775        $this->setUser($student->id);
1776        $counts = forum_count_discussion_replies($forum->id);
1777        $this->assertEquals($replycount + 1, $counts[$discussion->id]->replies);
1778
1779        // The user is not the author or recipient, but does have the readprivatereplies capability.
1780        $this->setUser($privilegeduser->id);
1781        $counts = forum_count_discussion_replies($forum->id, "", -1, -1, 0, true);
1782        $this->assertEquals($replycount + 1, $counts[$discussion->id]->replies);
1783
1784        // The user is not allowed to view this post.
1785        $this->setUser($otheruser->id);
1786        $counts = forum_count_discussion_replies($forum->id);
1787        $this->assertEquals($replycount, $counts[$discussion->id]->replies);
1788    }
1789
1790    public function test_discussion_pinned_sort() {
1791        list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5);
1792        $cm = get_coursemodule_from_instance('forum', $forum->id);
1793        $discussions = forum_get_discussions($cm);
1794        // First discussion should be pinned.
1795        $first = reset($discussions);
1796        $this->assertEquals(1, $first->pinned, "First discussion should be pinned discussion");
1797    }
1798    public function test_forum_view() {
1799        global $CFG;
1800
1801        $CFG->enablecompletion = 1;
1802        $this->resetAfterTest();
1803
1804        // Setup test data.
1805        $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
1806        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
1807                                                            array('completion' => 2, 'completionview' => 1));
1808        $context = context_module::instance($forum->cmid);
1809        $cm = get_coursemodule_from_instance('forum', $forum->id);
1810
1811        // Trigger and capture the event.
1812        $sink = $this->redirectEvents();
1813
1814        $this->setAdminUser();
1815        forum_view($forum, $course, $cm, $context);
1816
1817        $events = $sink->get_events();
1818        // 2 additional events thanks to completion.
1819        $this->assertCount(3, $events);
1820        $event = array_pop($events);
1821
1822        // Checking that the event contains the expected values.
1823        $this->assertInstanceOf('\mod_forum\event\course_module_viewed', $event);
1824        $this->assertEquals($context, $event->get_context());
1825        $url = new \moodle_url('/mod/forum/view.php', array('f' => $forum->id));
1826        $this->assertEquals($url, $event->get_url());
1827        $this->assertEventContextNotUsed($event);
1828        $this->assertNotEmpty($event->get_name());
1829
1830        // Check completion status.
1831        $completion = new completion_info($course);
1832        $completiondata = $completion->get_data($cm);
1833        $this->assertEquals(1, $completiondata->completionstate);
1834
1835    }
1836
1837    /**
1838     * Test forum_discussion_view.
1839     */
1840    public function test_forum_discussion_view() {
1841        global $CFG, $USER;
1842
1843        $this->resetAfterTest();
1844
1845        // Setup test data.
1846        $course = $this->getDataGenerator()->create_course();
1847        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
1848        $discussion = $this->create_single_discussion_with_replies($forum, $USER, 2);
1849
1850        $context = context_module::instance($forum->cmid);
1851        $cm = get_coursemodule_from_instance('forum', $forum->id);
1852
1853        // Trigger and capture the event.
1854        $sink = $this->redirectEvents();
1855
1856        $this->setAdminUser();
1857        forum_discussion_view($context, $forum, $discussion);
1858
1859        $events = $sink->get_events();
1860        $this->assertCount(1, $events);
1861        $event = array_pop($events);
1862
1863        // Checking that the event contains the expected values.
1864        $this->assertInstanceOf('\mod_forum\event\discussion_viewed', $event);
1865        $this->assertEquals($context, $event->get_context());
1866        $expected = array($course->id, 'forum', 'view discussion', "discuss.php?d={$discussion->id}",
1867            $discussion->id, $forum->cmid);
1868        $this->assertEventLegacyLogData($expected, $event);
1869        $this->assertEventContextNotUsed($event);
1870
1871        $this->assertNotEmpty($event->get_name());
1872
1873    }
1874
1875    /**
1876     * Create a new course, forum, and user with a number of discussions and replies.
1877     *
1878     * @param int $discussioncount The number of discussions to create
1879     * @param int $replycount The number of replies to create in each discussion
1880     * @return array Containing the created forum object, and the ids of the created discussions.
1881     */
1882    protected function create_multiple_discussions_with_replies($discussioncount, $replycount) {
1883        $this->resetAfterTest();
1884
1885        // Setup the content.
1886        $user = $this->getDataGenerator()->create_user();
1887        $course = $this->getDataGenerator()->create_course();
1888        $record = new stdClass();
1889        $record->course = $course->id;
1890        $forum = $this->getDataGenerator()->create_module('forum', $record);
1891
1892        // Create 10 discussions with replies.
1893        $discussionids = array();
1894        for ($i = 0; $i < $discussioncount; $i++) {
1895            // Pin 3rd discussion.
1896            if ($i == 3) {
1897                $discussion = $this->create_single_discussion_pinned_with_replies($forum, $user, $replycount);
1898            } else {
1899                $discussion = $this->create_single_discussion_with_replies($forum, $user, $replycount);
1900            }
1901
1902            $discussionids[] = $discussion->id;
1903        }
1904        return array($forum, $discussionids);
1905    }
1906
1907    /**
1908     * Create a discussion with a number of replies.
1909     *
1910     * @param object $forum The forum which has been created
1911     * @param object $user The user making the discussion and replies
1912     * @param int $replycount The number of replies
1913     * @return object $discussion
1914     */
1915    protected function create_single_discussion_with_replies($forum, $user, $replycount) {
1916        global $DB;
1917
1918        $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
1919
1920        $record = new stdClass();
1921        $record->course = $forum->course;
1922        $record->forum = $forum->id;
1923        $record->userid = $user->id;
1924        $discussion = $generator->create_discussion($record);
1925
1926        // Retrieve the first post.
1927        $replyto = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
1928
1929        // Create the replies.
1930        $post = new stdClass();
1931        $post->userid = $user->id;
1932        $post->discussion = $discussion->id;
1933        $post->parent = $replyto->id;
1934
1935        for ($i = 0; $i < $replycount; $i++) {
1936            $generator->create_post($post);
1937        }
1938
1939        return $discussion;
1940    }
1941    /**
1942     * Create a discussion with a number of replies.
1943     *
1944     * @param object $forum The forum which has been created
1945     * @param object $user The user making the discussion and replies
1946     * @param int $replycount The number of replies
1947     * @return object $discussion
1948     */
1949    protected function create_single_discussion_pinned_with_replies($forum, $user, $replycount) {
1950        global $DB;
1951
1952        $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
1953
1954        $record = new stdClass();
1955        $record->course = $forum->course;
1956        $record->forum = $forum->id;
1957        $record->userid = $user->id;
1958        $record->pinned = FORUM_DISCUSSION_PINNED;
1959        $discussion = $generator->create_discussion($record);
1960
1961        // Retrieve the first post.
1962        $replyto = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
1963
1964        // Create the replies.
1965        $post = new stdClass();
1966        $post->userid = $user->id;
1967        $post->discussion = $discussion->id;
1968        $post->parent = $replyto->id;
1969
1970        for ($i = 0; $i < $replycount; $i++) {
1971            $generator->create_post($post);
1972        }
1973
1974        return $discussion;
1975    }
1976
1977    /**
1978     * Tests for mod_forum_rating_can_see_item_ratings().
1979     *
1980     * @throws coding_exception
1981     * @throws rating_exception
1982     */
1983    public function test_mod_forum_rating_can_see_item_ratings() {
1984        global $DB;
1985
1986        $this->resetAfterTest();
1987
1988        // Setup test data.
1989        $course = new stdClass();
1990        $course->groupmode = SEPARATEGROUPS;
1991        $course->groupmodeforce = true;
1992        $course = $this->getDataGenerator()->create_course($course);
1993        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
1994        $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
1995        $cm = get_coursemodule_from_instance('forum', $forum->id);
1996        $context = context_module::instance($cm->id);
1997
1998        // Create users.
1999        $user1 = $this->getDataGenerator()->create_user();
2000        $user2 = $this->getDataGenerator()->create_user();
2001        $user3 = $this->getDataGenerator()->create_user();
2002        $user4 = $this->getDataGenerator()->create_user();
2003
2004        // Groups and stuff.
2005        $role = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
2006        $this->getDataGenerator()->enrol_user($user1->id, $course->id, $role->id);
2007        $this->getDataGenerator()->enrol_user($user2->id, $course->id, $role->id);
2008        $this->getDataGenerator()->enrol_user($user3->id, $course->id, $role->id);
2009        $this->getDataGenerator()->enrol_user($user4->id, $course->id, $role->id);
2010
2011        $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
2012        $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
2013        groups_add_member($group1, $user1);
2014        groups_add_member($group1, $user2);
2015        groups_add_member($group2, $user3);
2016        groups_add_member($group2, $user4);
2017
2018        $record = new stdClass();
2019        $record->course = $forum->course;
2020        $record->forum = $forum->id;
2021        $record->userid = $user1->id;
2022        $record->groupid = $group1->id;
2023        $discussion = $generator->create_discussion($record);
2024
2025        // Retrieve the first post.
2026        $post = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
2027
2028        $ratingoptions = new stdClass;
2029        $ratingoptions->context = $context;
2030        $ratingoptions->ratingarea = 'post';
2031        $ratingoptions->component = 'mod_forum';
2032        $ratingoptions->itemid  = $post->id;
2033        $ratingoptions->scaleid = 2;
2034        $ratingoptions->userid  = $user2->id;
2035        $rating = new rating($ratingoptions);
2036        $rating->update_rating(2);
2037
2038        // Now try to access it as various users.
2039        unassign_capability('moodle/site:accessallgroups', $role->id);
2040        $params = array('contextid' => 2,
2041                        'component' => 'mod_forum',
2042                        'ratingarea' => 'post',
2043                        'itemid' => $post->id,
2044                        'scaleid' => 2);
2045        $this->setUser($user1);
2046        $this->assertTrue(mod_forum_rating_can_see_item_ratings($params));
2047        $this->setUser($user2);
2048        $this->assertTrue(mod_forum_rating_can_see_item_ratings($params));
2049        $this->setUser($user3);
2050        $this->assertFalse(mod_forum_rating_can_see_item_ratings($params));
2051        $this->setUser($user4);
2052        $this->assertFalse(mod_forum_rating_can_see_item_ratings($params));
2053
2054        // Now try with accessallgroups cap and make sure everything is visible.
2055        assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $role->id, $context->id);
2056        $this->setUser($user1);
2057        $this->assertTrue(mod_forum_rating_can_see_item_ratings($params));
2058        $this->setUser($user2);
2059        $this->assertTrue(mod_forum_rating_can_see_item_ratings($params));
2060        $this->setUser($user3);
2061        $this->assertTrue(mod_forum_rating_can_see_item_ratings($params));
2062        $this->setUser($user4);
2063        $this->assertTrue(mod_forum_rating_can_see_item_ratings($params));
2064
2065        // Change group mode and verify visibility.
2066        $course->groupmode = VISIBLEGROUPS;
2067        $DB->update_record('course', $course);
2068        unassign_capability('moodle/site:accessallgroups', $role->id);
2069        $this->setUser($user1);
2070        $this->assertTrue(mod_forum_rating_can_see_item_ratings($params));
2071        $this->setUser($user2);
2072        $this->assertTrue(mod_forum_rating_can_see_item_ratings($params));
2073        $this->setUser($user3);
2074        $this->assertTrue(mod_forum_rating_can_see_item_ratings($params));
2075        $this->setUser($user4);
2076        $this->assertTrue(mod_forum_rating_can_see_item_ratings($params));
2077
2078    }
2079
2080    /**
2081     * Test forum_get_discussions
2082     */
2083    public function test_forum_get_discussions_with_groups() {
2084        global $DB;
2085
2086        $this->resetAfterTest(true);
2087
2088        // Create course to add the module.
2089        $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
2090        $user1 = self::getDataGenerator()->create_user();
2091        $user2 = self::getDataGenerator()->create_user();
2092        $user3 = self::getDataGenerator()->create_user();
2093
2094        $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
2095        self::getDataGenerator()->enrol_user($user1->id, $course->id, $role->id);
2096        self::getDataGenerator()->enrol_user($user2->id, $course->id, $role->id);
2097        self::getDataGenerator()->enrol_user($user3->id, $course->id, $role->id);
2098
2099        // Forum forcing separate gropus.
2100        $record = new stdClass();
2101        $record->course = $course->id;
2102        $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
2103        $cm = get_coursemodule_from_instance('forum', $forum->id);
2104
2105        // Create groups.
2106        $group1 = self::getDataGenerator()->create_group(array('courseid' => $course->id, 'name' => 'group1'));
2107        $group2 = self::getDataGenerator()->create_group(array('courseid' => $course->id, 'name' => 'group2'));
2108        $group3 = self::getDataGenerator()->create_group(array('courseid' => $course->id, 'name' => 'group3'));
2109
2110        // Add the user1 to g1 and g2 groups.
2111        groups_add_member($group1->id, $user1->id);
2112        groups_add_member($group2->id, $user1->id);
2113
2114        // Add the user 2 and 3 to only one group.
2115        groups_add_member($group1->id, $user2->id);
2116        groups_add_member($group3->id, $user3->id);
2117
2118        // Add a few discussions.
2119        $record = array();
2120        $record['course'] = $course->id;
2121        $record['forum'] = $forum->id;
2122        $record['userid'] = $user1->id;
2123        $record['groupid'] = $group1->id;
2124        $discussiong1u1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
2125
2126        $record['groupid'] = $group2->id;
2127        $discussiong2u1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
2128
2129        $record['userid'] = $user2->id;
2130        $record['groupid'] = $group1->id;
2131        $discussiong1u2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
2132
2133        $record['userid'] = $user3->id;
2134        $record['groupid'] = $group3->id;
2135        $discussiong3u3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
2136
2137        self::setUser($user1);
2138
2139        // Test retrieve discussions not passing the groupid parameter. We will receive only first group discussions.
2140        $discussions = forum_get_discussions($cm);
2141        self::assertCount(2, $discussions);
2142        foreach ($discussions as $discussion) {
2143            self::assertEquals($group1->id, $discussion->groupid);
2144        }
2145
2146        // Get all my discussions.
2147        $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, 0);
2148        self::assertCount(3, $discussions);
2149
2150        // Get all my g1 discussions.
2151        $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, $group1->id);
2152        self::assertCount(2, $discussions);
2153        foreach ($discussions as $discussion) {
2154            self::assertEquals($group1->id, $discussion->groupid);
2155        }
2156
2157        // Get all my g2 discussions.
2158        $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, $group2->id);
2159        self::assertCount(1, $discussions);
2160        $discussion = array_shift($discussions);
2161        self::assertEquals($group2->id, $discussion->groupid);
2162        self::assertEquals($user1->id, $discussion->userid);
2163        self::assertEquals($discussiong2u1->id, $discussion->discussion);
2164
2165        // Get all my g3 discussions (I'm not enrolled in that group).
2166        $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, $group3->id);
2167        self::assertCount(0, $discussions);
2168
2169        // This group does not exist.
2170        $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, $group3->id + 1000);
2171        self::assertCount(0, $discussions);
2172
2173        self::setUser($user2);
2174
2175        // Test retrieve discussions not passing the groupid parameter. We will receive only first group discussions.
2176        $discussions = forum_get_discussions($cm);
2177        self::assertCount(2, $discussions);
2178        foreach ($discussions as $discussion) {
2179            self::assertEquals($group1->id, $discussion->groupid);
2180        }
2181
2182        // Get all my viewable discussions.
2183        $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, 0);
2184        self::assertCount(2, $discussions);
2185        foreach ($discussions as $discussion) {
2186            self::assertEquals($group1->id, $discussion->groupid);
2187        }
2188
2189        // Get all my g2 discussions (I'm not enrolled in that group).
2190        $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, $group2->id);
2191        self::assertCount(0, $discussions);
2192
2193        // Get all my g3 discussions (I'm not enrolled in that group).
2194        $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, $group3->id);
2195        self::assertCount(0, $discussions);
2196
2197    }
2198
2199    /**
2200     * Test forum_user_can_post_discussion
2201     */
2202    public function test_forum_user_can_post_discussion() {
2203        global $DB;
2204
2205        $this->resetAfterTest(true);
2206
2207        // Create course to add the module.
2208        $course = self::getDataGenerator()->create_course(array('groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1));
2209        $user = self::getDataGenerator()->create_user();
2210        $this->getDataGenerator()->enrol_user($user->id, $course->id);
2211
2212        // Forum forcing separate gropus.
2213        $record = new stdClass();
2214        $record->course = $course->id;
2215        $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
2216        $cm = get_coursemodule_from_instance('forum', $forum->id);
2217        $context = context_module::instance($cm->id);
2218
2219        self::setUser($user);
2220
2221        // The user is not enroled in any group, try to post in a forum with separate groups.
2222        $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
2223        $this->assertFalse($can);
2224
2225        // Create a group.
2226        $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
2227
2228        // Try to post in a group the user is not enrolled.
2229        $can = forum_user_can_post_discussion($forum, $group->id, -1, $cm, $context);
2230        $this->assertFalse($can);
2231
2232        // Add the user to a group.
2233        groups_add_member($group->id, $user->id);
2234
2235        // Try to post in a group the user is not enrolled.
2236        $can = forum_user_can_post_discussion($forum, $group->id + 1, -1, $cm, $context);
2237        $this->assertFalse($can);
2238
2239        // Now try to post in the user group. (null means it will guess the group).
2240        $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
2241        $this->assertTrue($can);
2242
2243        $can = forum_user_can_post_discussion($forum, $group->id, -1, $cm, $context);
2244        $this->assertTrue($can);
2245
2246        // Test all groups.
2247        $can = forum_user_can_post_discussion($forum, -1, -1, $cm, $context);
2248        $this->assertFalse($can);
2249
2250        $this->setAdminUser();
2251        $can = forum_user_can_post_discussion($forum, -1, -1, $cm, $context);
2252        $this->assertTrue($can);
2253
2254        // Change forum type.
2255        $forum->type = 'news';
2256        $DB->update_record('forum', $forum);
2257
2258        // Admin can post news.
2259        $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
2260        $this->assertTrue($can);
2261
2262        // Normal users don't.
2263        self::setUser($user);
2264        $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
2265        $this->assertFalse($can);
2266
2267        // Change forum type.
2268        $forum->type = 'eachuser';
2269        $DB->update_record('forum', $forum);
2270
2271        // I didn't post yet, so I should be able to post.
2272        $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
2273        $this->assertTrue($can);
2274
2275        // Post now.
2276        $record = new stdClass();
2277        $record->course = $course->id;
2278        $record->userid = $user->id;
2279        $record->forum = $forum->id;
2280        $record->groupid = $group->id;
2281        $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
2282
2283        // I already posted, I shouldn't be able to post.
2284        $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
2285        $this->assertFalse($can);
2286
2287        // Last check with no groups, normal forum and course.
2288        $course->groupmode = NOGROUPS;
2289        $course->groupmodeforce = 0;
2290        $DB->update_record('course', $course);
2291
2292        $forum->type = 'general';
2293        $forum->groupmode = NOGROUPS;
2294        $DB->update_record('forum', $forum);
2295
2296        $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
2297        $this->assertTrue($can);
2298    }
2299
2300    /**
2301     * Test forum_user_can_post_discussion_after_cutoff
2302     */
2303    public function test_forum_user_can_post_discussion_after_cutoff() {
2304        $this->resetAfterTest(true);
2305
2306        // Create course to add the module.
2307        $course = self::getDataGenerator()->create_course(array('groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1));
2308        $student = self::getDataGenerator()->create_user();
2309        $teacher = self::getDataGenerator()->create_user();
2310        $this->getDataGenerator()->enrol_user($student->id, $course->id);
2311        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
2312
2313        // Forum forcing separate gropus.
2314        $record = new stdClass();
2315        $record->course = $course->id;
2316        $record->cutoffdate = time() - 1;
2317        $forum = self::getDataGenerator()->create_module('forum', $record);
2318        $cm = get_coursemodule_from_instance('forum', $forum->id);
2319        $context = context_module::instance($cm->id);
2320
2321        self::setUser($student);
2322
2323        // Students usually don't have the mod/forum:canoverridecutoff capability.
2324        $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
2325        $this->assertFalse($can);
2326
2327        self::setUser($teacher);
2328
2329        // Teachers usually have the mod/forum:canoverridecutoff capability.
2330        $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
2331        $this->assertTrue($can);
2332    }
2333
2334    /**
2335     * Test forum_user_has_posted_discussion with no groups.
2336     */
2337    public function test_forum_user_has_posted_discussion_no_groups() {
2338        global $CFG;
2339
2340        $this->resetAfterTest(true);
2341
2342        $course = self::getDataGenerator()->create_course();
2343        $author = self::getDataGenerator()->create_user();
2344        $other = self::getDataGenerator()->create_user();
2345        $this->getDataGenerator()->enrol_user($author->id, $course->id);
2346        $forum = self::getDataGenerator()->create_module('forum', (object) ['course' => $course->id ]);
2347
2348        self::setUser($author);
2349
2350        // Neither user has posted.
2351        $this->assertFalse(forum_user_has_posted_discussion($forum->id, $author->id));
2352        $this->assertFalse(forum_user_has_posted_discussion($forum->id, $other->id));
2353
2354        // Post in the forum.
2355        $record = new stdClass();
2356        $record->course = $course->id;
2357        $record->userid = $author->id;
2358        $record->forum = $forum->id;
2359        $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
2360
2361        // The author has now posted, but the other user has not.
2362        $this->assertTrue(forum_user_has_posted_discussion($forum->id, $author->id));
2363        $this->assertFalse(forum_user_has_posted_discussion($forum->id, $other->id));
2364    }
2365
2366    /**
2367     * Test forum_user_has_posted_discussion with multiple forums
2368     */
2369    public function test_forum_user_has_posted_discussion_multiple_forums() {
2370        global $CFG;
2371
2372        $this->resetAfterTest(true);
2373
2374        $course = self::getDataGenerator()->create_course();
2375        $author = self::getDataGenerator()->create_user();
2376        $this->getDataGenerator()->enrol_user($author->id, $course->id);
2377        $forum1 = self::getDataGenerator()->create_module('forum', (object) ['course' => $course->id ]);
2378        $forum2 = self::getDataGenerator()->create_module('forum', (object) ['course' => $course->id ]);
2379
2380        self::setUser($author);
2381
2382        // No post in either forum.
2383        $this->assertFalse(forum_user_has_posted_discussion($forum1->id, $author->id));
2384        $this->assertFalse(forum_user_has_posted_discussion($forum2->id, $author->id));
2385
2386        // Post in the forum.
2387        $record = new stdClass();
2388        $record->course = $course->id;
2389        $record->userid = $author->id;
2390        $record->forum = $forum1->id;
2391        $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
2392
2393        // The author has now posted in forum1, but not forum2.
2394        $this->assertTrue(forum_user_has_posted_discussion($forum1->id, $author->id));
2395        $this->assertFalse(forum_user_has_posted_discussion($forum2->id, $author->id));
2396    }
2397
2398    /**
2399     * Test forum_user_has_posted_discussion with multiple groups.
2400     */
2401    public function test_forum_user_has_posted_discussion_multiple_groups() {
2402        global $CFG;
2403
2404        $this->resetAfterTest(true);
2405
2406        $course = self::getDataGenerator()->create_course();
2407        $author = self::getDataGenerator()->create_user();
2408        $this->getDataGenerator()->enrol_user($author->id, $course->id);
2409
2410        $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
2411        $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
2412        groups_add_member($group1->id, $author->id);
2413        groups_add_member($group2->id, $author->id);
2414
2415        $forum = self::getDataGenerator()->create_module('forum', (object) ['course' => $course->id ], [
2416                    'groupmode' => SEPARATEGROUPS,
2417                ]);
2418
2419        self::setUser($author);
2420
2421        // The user has not posted in either group.
2422        $this->assertFalse(forum_user_has_posted_discussion($forum->id, $author->id));
2423        $this->assertFalse(forum_user_has_posted_discussion($forum->id, $author->id, $group1->id));
2424        $this->assertFalse(forum_user_has_posted_discussion($forum->id, $author->id, $group2->id));
2425
2426        // Post in one group.
2427        $record = new stdClass();
2428        $record->course = $course->id;
2429        $record->userid = $author->id;
2430        $record->forum = $forum->id;
2431        $record->groupid = $group1->id;
2432        $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
2433
2434        // The author has now posted in one group, but the other user has not.
2435        $this->assertTrue(forum_user_has_posted_discussion($forum->id, $author->id));
2436        $this->assertTrue(forum_user_has_posted_discussion($forum->id, $author->id, $group1->id));
2437        $this->assertFalse(forum_user_has_posted_discussion($forum->id, $author->id, $group2->id));
2438
2439        // Post in the other group.
2440        $record = new stdClass();
2441        $record->course = $course->id;
2442        $record->userid = $author->id;
2443        $record->forum = $forum->id;
2444        $record->groupid = $group2->id;
2445        $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
2446
2447        // The author has now posted in one group, but the other user has not.
2448        $this->assertTrue(forum_user_has_posted_discussion($forum->id, $author->id));
2449        $this->assertTrue(forum_user_has_posted_discussion($forum->id, $author->id, $group1->id));
2450        $this->assertTrue(forum_user_has_posted_discussion($forum->id, $author->id, $group2->id));
2451    }
2452
2453    /**
2454     * Tests the mod_forum_myprofile_navigation() function.
2455     */
2456    public function test_mod_forum_myprofile_navigation() {
2457        $this->resetAfterTest(true);
2458
2459        // Set up the test.
2460        $tree = new \core_user\output\myprofile\tree();
2461        $user = $this->getDataGenerator()->create_user();
2462        $course = $this->getDataGenerator()->create_course();
2463        $iscurrentuser = true;
2464
2465        // Set as the current user.
2466        $this->setUser($user);
2467
2468        // Check the node tree is correct.
2469        mod_forum_myprofile_navigation($tree, $user, $iscurrentuser, $course);
2470        $reflector = new ReflectionObject($tree);
2471        $nodes = $reflector->getProperty('nodes');
2472        $nodes->setAccessible(true);
2473        $this->assertArrayHasKey('forumposts', $nodes->getValue($tree));
2474        $this->assertArrayHasKey('forumdiscussions', $nodes->getValue($tree));
2475    }
2476
2477    /**
2478     * Tests the mod_forum_myprofile_navigation() function as a guest.
2479     */
2480    public function test_mod_forum_myprofile_navigation_as_guest() {
2481        global $USER;
2482
2483        $this->resetAfterTest(true);
2484
2485        // Set up the test.
2486        $tree = new \core_user\output\myprofile\tree();
2487        $course = $this->getDataGenerator()->create_course();
2488        $iscurrentuser = true;
2489
2490        // Set user as guest.
2491        $this->setGuestUser();
2492
2493        // Check the node tree is correct.
2494        mod_forum_myprofile_navigation($tree, $USER, $iscurrentuser, $course);
2495        $reflector = new ReflectionObject($tree);
2496        $nodes = $reflector->getProperty('nodes');
2497        $nodes->setAccessible(true);
2498        $this->assertArrayNotHasKey('forumposts', $nodes->getValue($tree));
2499        $this->assertArrayNotHasKey('forumdiscussions', $nodes->getValue($tree));
2500    }
2501
2502    /**
2503     * Tests the mod_forum_myprofile_navigation() function as a user viewing another user's profile.
2504     */
2505    public function test_mod_forum_myprofile_navigation_different_user() {
2506        $this->resetAfterTest(true);
2507
2508        // Set up the test.
2509        $tree = new \core_user\output\myprofile\tree();
2510        $user = $this->getDataGenerator()->create_user();
2511        $user2 = $this->getDataGenerator()->create_user();
2512        $course = $this->getDataGenerator()->create_course();
2513        $iscurrentuser = true;
2514
2515        // Set to different user's profile.
2516        $this->setUser($user2);
2517
2518        // Check the node tree is correct.
2519        mod_forum_myprofile_navigation($tree, $user, $iscurrentuser, $course);
2520        $reflector = new ReflectionObject($tree);
2521        $nodes = $reflector->getProperty('nodes');
2522        $nodes->setAccessible(true);
2523        $this->assertArrayHasKey('forumposts', $nodes->getValue($tree));
2524        $this->assertArrayHasKey('forumdiscussions', $nodes->getValue($tree));
2525    }
2526
2527    /**
2528     * Test test_pinned_discussion_with_group.
2529     */
2530    public function test_pinned_discussion_with_group() {
2531        global $SESSION;
2532
2533        $this->resetAfterTest();
2534        $course1 = $this->getDataGenerator()->create_course();
2535        $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
2536
2537        // Create an author user.
2538        $author = $this->getDataGenerator()->create_user();
2539        $this->getDataGenerator()->enrol_user($author->id, $course1->id);
2540
2541        // Create two viewer users - one in a group, one not.
2542        $viewer1 = $this->getDataGenerator()->create_user((object) array('trackforums' => 1));
2543        $this->getDataGenerator()->enrol_user($viewer1->id, $course1->id);
2544
2545        $viewer2 = $this->getDataGenerator()->create_user((object) array('trackforums' => 1));
2546        $this->getDataGenerator()->enrol_user($viewer2->id, $course1->id);
2547        $this->getDataGenerator()->create_group_member(array('userid' => $viewer2->id, 'groupid' => $group1->id));
2548
2549        $forum1 = $this->getDataGenerator()->create_module('forum', (object) array(
2550            'course' => $course1->id,
2551            'groupmode' => SEPARATEGROUPS,
2552        ));
2553
2554        $coursemodule = get_coursemodule_from_instance('forum', $forum1->id);
2555
2556        $alldiscussions = array();
2557        $group1discussions = array();
2558
2559        // Create 4 discussions in all participants group and group1, where the first
2560        // discussion is pinned in each group.
2561        $allrecord = new stdClass();
2562        $allrecord->course = $course1->id;
2563        $allrecord->userid = $author->id;
2564        $allrecord->forum = $forum1->id;
2565        $allrecord->pinned = FORUM_DISCUSSION_PINNED;
2566
2567        $group1record = new stdClass();
2568        $group1record->course = $course1->id;
2569        $group1record->userid = $author->id;
2570        $group1record->forum = $forum1->id;
2571        $group1record->groupid = $group1->id;
2572        $group1record->pinned = FORUM_DISCUSSION_PINNED;
2573
2574        $alldiscussions[] = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($allrecord);
2575        $group1discussions[] = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($group1record);
2576
2577        // Create unpinned discussions.
2578        $allrecord->pinned = FORUM_DISCUSSION_UNPINNED;
2579        $group1record->pinned = FORUM_DISCUSSION_UNPINNED;
2580        for ($i = 0; $i < 3; $i++) {
2581            $alldiscussions[] = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($allrecord);
2582            $group1discussions[] = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($group1record);
2583        }
2584
2585        // As viewer1 (no group). This user shouldn't see any of group1's discussions
2586        // so their expected discussion order is (where rightmost is highest priority):
2587        // Ad1, ad2, ad3, ad0.
2588        $this->setUser($viewer1->id);
2589
2590        // CHECK 1.
2591        // Take the neighbours of ad3, which should be prev: ad2 and next: ad0.
2592        $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[3], $forum1);
2593        // Ad2 check.
2594        $this->assertEquals($alldiscussions[2]->id, $neighbours['prev']->id);
2595        // Ad0 check.
2596        $this->assertEquals($alldiscussions[0]->id, $neighbours['next']->id);
2597
2598        // CHECK 2.
2599        // Take the neighbours of ad0, which should be prev: ad3 and next: null.
2600        $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[0], $forum1);
2601        // Ad3 check.
2602        $this->assertEquals($alldiscussions[3]->id, $neighbours['prev']->id);
2603        // Null check.
2604        $this->assertEmpty($neighbours['next']);
2605
2606        // CHECK 3.
2607        // Take the neighbours of ad1, which should be prev: null and next: ad2.
2608        $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[1], $forum1);
2609        // Null check.
2610        $this->assertEmpty($neighbours['prev']);
2611        // Ad2 check.
2612        $this->assertEquals($alldiscussions[2]->id, $neighbours['next']->id);
2613
2614        // Temporary hack to workaround for MDL-52656.
2615        $SESSION->currentgroup = null;
2616
2617        // As viewer2 (group1). This user should see all of group1's posts and the all participants group.
2618        // The expected discussion order is (rightmost is highest priority):
2619        // Ad1, gd1, ad2, gd2, ad3, gd3, ad0, gd0.
2620        $this->setUser($viewer2->id);
2621
2622        // CHECK 1.
2623        // Take the neighbours of ad1, which should be prev: null and next: gd1.
2624        $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[1], $forum1);
2625        // Null check.
2626        $this->assertEmpty($neighbours['prev']);
2627        // Gd1 check.
2628        $this->assertEquals($group1discussions[1]->id, $neighbours['next']->id);
2629
2630        // CHECK 2.
2631        // Take the neighbours of ad3, which should be prev: gd2 and next: gd3.
2632        $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[3], $forum1);
2633        // Gd2 check.
2634        $this->assertEquals($group1discussions[2]->id, $neighbours['prev']->id);
2635        // Gd3 check.
2636        $this->assertEquals($group1discussions[3]->id, $neighbours['next']->id);
2637
2638        // CHECK 3.
2639        // Take the neighbours of gd3, which should be prev: ad3 and next: ad0.
2640        $neighbours = forum_get_discussion_neighbours($coursemodule, $group1discussions[3], $forum1);
2641        // Ad3 check.
2642        $this->assertEquals($alldiscussions[3]->id, $neighbours['prev']->id);
2643        // Ad0 check.
2644        $this->assertEquals($alldiscussions[0]->id, $neighbours['next']->id);
2645
2646        // CHECK 4.
2647        // Take the neighbours of gd0, which should be prev: ad0 and next: null.
2648        $neighbours = forum_get_discussion_neighbours($coursemodule, $group1discussions[0], $forum1);
2649        // Ad0 check.
2650        $this->assertEquals($alldiscussions[0]->id, $neighbours['prev']->id);
2651        // Null check.
2652        $this->assertEmpty($neighbours['next']);
2653    }
2654
2655    /**
2656     * Test test_pinned_with_timed_discussions.
2657     */
2658    public function test_pinned_with_timed_discussions() {
2659        global $CFG;
2660
2661        $CFG->forum_enabletimedposts = true;
2662
2663        $this->resetAfterTest();
2664        $course = $this->getDataGenerator()->create_course();
2665
2666        // Create an user.
2667        $user = $this->getDataGenerator()->create_user();
2668        $this->getDataGenerator()->enrol_user($user->id, $course->id);
2669
2670        // Create a forum.
2671        $record = new stdClass();
2672        $record->course = $course->id;
2673        $forum = $this->getDataGenerator()->create_module('forum', (object) array(
2674            'course' => $course->id,
2675            'groupmode' => SEPARATEGROUPS,
2676        ));
2677
2678        $coursemodule = get_coursemodule_from_instance('forum', $forum->id);
2679        $now = time();
2680        $discussions = array();
2681        $discussiongenerator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
2682
2683        $record = new stdClass();
2684        $record->course = $course->id;
2685        $record->userid = $user->id;
2686        $record->forum = $forum->id;
2687        $record->pinned = FORUM_DISCUSSION_PINNED;
2688        $record->timemodified = $now;
2689
2690        $discussions[] = $discussiongenerator->create_discussion($record);
2691
2692        $record->pinned = FORUM_DISCUSSION_UNPINNED;
2693        $record->timestart = $now + 10;
2694
2695        $discussions[] = $discussiongenerator->create_discussion($record);
2696
2697        $record->timestart = $now;
2698
2699        $discussions[] = $discussiongenerator->create_discussion($record);
2700
2701        // Expected order of discussions:
2702        // D2, d1, d0.
2703        $this->setUser($user->id);
2704
2705        // CHECK 1.
2706        $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[2], $forum);
2707        // Null check.
2708        $this->assertEmpty($neighbours['prev']);
2709        // D1 check.
2710        $this->assertEquals($discussions[1]->id, $neighbours['next']->id);
2711
2712        // CHECK 2.
2713        $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[1], $forum);
2714        // D2 check.
2715        $this->assertEquals($discussions[2]->id, $neighbours['prev']->id);
2716        // D0 check.
2717        $this->assertEquals($discussions[0]->id, $neighbours['next']->id);
2718
2719        // CHECK 3.
2720        $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[0], $forum);
2721        // D2 check.
2722        $this->assertEquals($discussions[1]->id, $neighbours['prev']->id);
2723        // Null check.
2724        $this->assertEmpty($neighbours['next']);
2725    }
2726
2727    /**
2728     * Test test_pinned_timed_discussions_with_timed_discussions.
2729     */
2730    public function test_pinned_timed_discussions_with_timed_discussions() {
2731        global $CFG;
2732
2733        $CFG->forum_enabletimedposts = true;
2734
2735        $this->resetAfterTest();
2736        $course = $this->getDataGenerator()->create_course();
2737
2738        // Create an user.
2739        $user = $this->getDataGenerator()->create_user();
2740        $this->getDataGenerator()->enrol_user($user->id, $course->id);
2741
2742        // Create a forum.
2743        $record = new stdClass();
2744        $record->course = $course->id;
2745        $forum = $this->getDataGenerator()->create_module('forum', (object) array(
2746            'course' => $course->id,
2747            'groupmode' => SEPARATEGROUPS,
2748        ));
2749
2750        $coursemodule = get_coursemodule_from_instance('forum', $forum->id);
2751        $now = time();
2752        $discussions = array();
2753        $discussiongenerator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
2754
2755        $record = new stdClass();
2756        $record->course = $course->id;
2757        $record->userid = $user->id;
2758        $record->forum = $forum->id;
2759        $record->pinned = FORUM_DISCUSSION_PINNED;
2760        $record->timemodified = $now;
2761        $record->timestart = $now + 10;
2762
2763        $discussions[] = $discussiongenerator->create_discussion($record);
2764
2765        $record->pinned = FORUM_DISCUSSION_UNPINNED;
2766
2767        $discussions[] = $discussiongenerator->create_discussion($record);
2768
2769        $record->timestart = $now;
2770
2771        $discussions[] = $discussiongenerator->create_discussion($record);
2772
2773        $record->pinned = FORUM_DISCUSSION_PINNED;
2774
2775        $discussions[] = $discussiongenerator->create_discussion($record);
2776
2777        // Expected order of discussions:
2778        // D2, d1, d3, d0.
2779        $this->setUser($user->id);
2780
2781        // CHECK 1.
2782        $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[2], $forum);
2783        // Null check.
2784        $this->assertEmpty($neighbours['prev']);
2785        // D1 check.
2786        $this->assertEquals($discussions[1]->id, $neighbours['next']->id);
2787
2788        // CHECK 2.
2789        $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[1], $forum);
2790        // D2 check.
2791        $this->assertEquals($discussions[2]->id, $neighbours['prev']->id);
2792        // D3 check.
2793        $this->assertEquals($discussions[3]->id, $neighbours['next']->id);
2794
2795        // CHECK 3.
2796        $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[3], $forum);
2797        // D1 check.
2798        $this->assertEquals($discussions[1]->id, $neighbours['prev']->id);
2799        // D0 check.
2800        $this->assertEquals($discussions[0]->id, $neighbours['next']->id);
2801
2802        // CHECK 4.
2803        $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[0], $forum);
2804        // D3 check.
2805        $this->assertEquals($discussions[3]->id, $neighbours['prev']->id);
2806        // Null check.
2807        $this->assertEmpty($neighbours['next']);
2808    }
2809
2810    /**
2811     * Test for forum_is_author_hidden.
2812     */
2813    public function test_forum_is_author_hidden() {
2814        // First post, different forum type.
2815        $post = (object) ['parent' => 0];
2816        $forum = (object) ['type' => 'standard'];
2817        $this->assertFalse(forum_is_author_hidden($post, $forum));
2818
2819        // Child post, different forum type.
2820        $post->parent = 1;
2821        $this->assertFalse(forum_is_author_hidden($post, $forum));
2822
2823        // First post, single simple discussion forum type.
2824        $post->parent = 0;
2825        $forum->type = 'single';
2826        $this->assertTrue(forum_is_author_hidden($post, $forum));
2827
2828        // Child post, single simple discussion forum type.
2829        $post->parent = 1;
2830        $this->assertFalse(forum_is_author_hidden($post, $forum));
2831
2832        // Incorrect parameters: $post.
2833        $this->expectException('coding_exception');
2834        $this->expectExceptionMessage('$post->parent must be set.');
2835        unset($post->parent);
2836        forum_is_author_hidden($post, $forum);
2837
2838        // Incorrect parameters: $forum.
2839        $this->expectException('coding_exception');
2840        $this->expectExceptionMessage('$forum->type must be set.');
2841        unset($forum->type);
2842        forum_is_author_hidden($post, $forum);
2843    }
2844
2845    /**
2846     * Test the forum_discussion_is_locked function.
2847     *
2848     * @dataProvider forum_discussion_is_locked_provider
2849     * @param   stdClass    $forum
2850     * @param   stdClass    $discussion
2851     * @param   bool        $expect
2852     */
2853    public function test_forum_discussion_is_locked($forum, $discussion, $expect) {
2854        $this->resetAfterTest();
2855
2856        $datagenerator = $this->getDataGenerator();
2857        $plugingenerator = $datagenerator->get_plugin_generator('mod_forum');
2858
2859        $course = $datagenerator->create_course();
2860        $user = $datagenerator->create_user();
2861        $forum = $datagenerator->create_module('forum', (object) array_merge([
2862            'course' => $course->id
2863        ], $forum));
2864        $discussion = $plugingenerator->create_discussion((object) array_merge([
2865            'course' => $course->id,
2866            'userid' => $user->id,
2867            'forum' => $forum->id,
2868        ], $discussion));
2869
2870        $this->assertEquals($expect, forum_discussion_is_locked($forum, $discussion));
2871    }
2872
2873    /**
2874     * Dataprovider for forum_discussion_is_locked tests.
2875     *
2876     * @return  array
2877     */
2878    public function forum_discussion_is_locked_provider() {
2879        return [
2880            'Unlocked: lockdiscussionafter is false' => [
2881                ['lockdiscussionafter' => false],
2882                [],
2883                false
2884            ],
2885            'Unlocked: lockdiscussionafter is set; forum is of type single; post is recent' => [
2886                ['lockdiscussionafter' => DAYSECS, 'type' => 'single'],
2887                ['timemodified' => time()],
2888                false
2889            ],
2890            'Unlocked: lockdiscussionafter is set; forum is of type single; post is old' => [
2891                ['lockdiscussionafter' => MINSECS, 'type' => 'single'],
2892                ['timemodified' => time() - DAYSECS],
2893                false
2894            ],
2895            'Unlocked: lockdiscussionafter is set; forum is of type eachuser; post is recent' => [
2896                ['lockdiscussionafter' => DAYSECS, 'type' => 'eachuser'],
2897                ['timemodified' => time()],
2898                false
2899            ],
2900            'Locked: lockdiscussionafter is set; forum is of type eachuser; post is old' => [
2901                ['lockdiscussionafter' => MINSECS, 'type' => 'eachuser'],
2902                ['timemodified' => time() - DAYSECS],
2903                true
2904            ],
2905        ];
2906    }
2907
2908    /**
2909     * Test the forum_is_cutoff_date_reached function.
2910     *
2911     * @dataProvider forum_is_cutoff_date_reached_provider
2912     * @param   array   $forum
2913     * @param   bool    $expect
2914     */
2915    public function test_forum_is_cutoff_date_reached($forum, $expect) {
2916        $this->resetAfterTest();
2917
2918        $datagenerator = $this->getDataGenerator();
2919        $course = $datagenerator->create_course();
2920        $forum = $datagenerator->create_module('forum', (object) array_merge([
2921            'course' => $course->id
2922        ], $forum));
2923
2924        $this->assertEquals($expect, forum_is_cutoff_date_reached($forum));
2925    }
2926
2927    /**
2928     * Dataprovider for forum_is_cutoff_date_reached tests.
2929     *
2930     * @return  array
2931     */
2932    public function forum_is_cutoff_date_reached_provider() {
2933        $now = time();
2934        return [
2935            'cutoffdate is unset' => [
2936                [],
2937                false
2938            ],
2939            'cutoffdate is 0' => [
2940                ['cutoffdate' => 0],
2941                false
2942            ],
2943            'cutoffdate is set and is in future' => [
2944                ['cutoffdate' => $now + 86400],
2945                false
2946            ],
2947            'cutoffdate is set and is in past' => [
2948                ['cutoffdate' => $now - 86400],
2949                true
2950            ],
2951        ];
2952    }
2953
2954    /**
2955     * Test the forum_is_due_date_reached function.
2956     *
2957     * @dataProvider forum_is_due_date_reached_provider
2958     * @param   stdClass    $forum
2959     * @param   bool        $expect
2960     */
2961    public function test_forum_is_due_date_reached($forum, $expect) {
2962        $this->resetAfterTest();
2963
2964        $this->setAdminUser();
2965
2966        $datagenerator = $this->getDataGenerator();
2967        $course = $datagenerator->create_course();
2968        $forum = $datagenerator->create_module('forum', (object) array_merge([
2969            'course' => $course->id
2970        ], $forum));
2971
2972        $this->assertEquals($expect, forum_is_due_date_reached($forum));
2973    }
2974
2975    /**
2976     * Dataprovider for forum_is_due_date_reached tests.
2977     *
2978     * @return  array
2979     */
2980    public function forum_is_due_date_reached_provider() {
2981        $now = time();
2982        return [
2983            'duedate is unset' => [
2984                [],
2985                false
2986            ],
2987            'duedate is 0' => [
2988                ['duedate' => 0],
2989                false
2990            ],
2991            'duedate is set and is in future' => [
2992                ['duedate' => $now + 86400],
2993                false
2994            ],
2995            'duedate is set and is in past' => [
2996                ['duedate' => $now - 86400],
2997                true
2998            ],
2999        ];
3000    }
3001
3002    /**
3003     * Test that {@link forum_update_post()} keeps correct forum_discussions usermodified.
3004     */
3005    public function test_forum_update_post_keeps_discussions_usermodified() {
3006        global $DB;
3007
3008        $this->resetAfterTest();
3009
3010        // Let there be light.
3011        $teacher = self::getDataGenerator()->create_user();
3012        $student = self::getDataGenerator()->create_user();
3013        $course = self::getDataGenerator()->create_course();
3014
3015        $forum = self::getDataGenerator()->create_module('forum', (object)[
3016            'course' => $course->id,
3017        ]);
3018
3019        $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
3020
3021        // Let the teacher start a discussion.
3022        $discussion = $generator->create_discussion((object)[
3023            'course' => $course->id,
3024            'userid' => $teacher->id,
3025            'forum' => $forum->id,
3026        ]);
3027
3028        // On this freshly created discussion, the teacher is the author of the last post.
3029        $this->assertEquals($teacher->id, $DB->get_field('forum_discussions', 'usermodified', ['id' => $discussion->id]));
3030
3031        // Fetch modified timestamp of the discussion.
3032        $discussionmodified = $DB->get_field('forum_discussions', 'timemodified', ['id' => $discussion->id]);
3033        $pasttime = $discussionmodified - 3600;
3034
3035        // Adjust the discussion modified timestamp back an hour, so it's in the past.
3036        $adjustment = (object)[
3037            'id' => $discussion->id,
3038            'timemodified' => $pasttime,
3039        ];
3040        $DB->update_record('forum_discussions', $adjustment);
3041
3042        // Let the student reply to the teacher's post.
3043        $reply = $generator->create_post((object)[
3044            'course' => $course->id,
3045            'userid' => $student->id,
3046            'forum' => $forum->id,
3047            'discussion' => $discussion->id,
3048            'parent' => $discussion->firstpost,
3049        ]);
3050
3051        // The student should now be the last post's author.
3052        $this->assertEquals($student->id, $DB->get_field('forum_discussions', 'usermodified', ['id' => $discussion->id]));
3053
3054        // Fetch modified timestamp of the discussion and student's post.
3055        $discussionmodified = $DB->get_field('forum_discussions', 'timemodified', ['id' => $discussion->id]);
3056        $postmodified = $DB->get_field('forum_posts', 'modified', ['id' => $reply->id]);
3057
3058        // Discussion modified time should be updated to be equal to the newly created post's time.
3059        $this->assertEquals($discussionmodified, $postmodified);
3060
3061        // Adjust the discussion and post timestamps, so they are in the past.
3062        $adjustment = (object)[
3063            'id' => $discussion->id,
3064            'timemodified' => $pasttime,
3065        ];
3066        $DB->update_record('forum_discussions', $adjustment);
3067
3068        $adjustment = (object)[
3069            'id' => $reply->id,
3070            'modified' => $pasttime,
3071        ];
3072        $DB->update_record('forum_posts', $adjustment);
3073
3074        // The discussion and student's post time should now be an hour in the past.
3075        $this->assertEquals($pasttime, $DB->get_field('forum_discussions', 'timemodified', ['id' => $discussion->id]));
3076        $this->assertEquals($pasttime, $DB->get_field('forum_posts', 'modified', ['id' => $reply->id]));
3077
3078        // Let the teacher edit the student's reply.
3079        $this->setUser($teacher->id);
3080        $newpost = (object)[
3081            'id' => $reply->id,
3082            'itemid' => 0,
3083            'subject' => 'Amended subject',
3084        ];
3085        forum_update_post($newpost, null);
3086
3087        // The student should still be the last post's author.
3088        $this->assertEquals($student->id, $DB->get_field('forum_discussions', 'usermodified', ['id' => $discussion->id]));
3089
3090        // The discussion modified time should not have changed.
3091        $this->assertEquals($pasttime, $DB->get_field('forum_discussions', 'timemodified', ['id' => $discussion->id]));
3092
3093        // The post time should be updated.
3094        $this->assertGreaterThan($pasttime, $DB->get_field('forum_posts', 'modified', ['id' => $reply->id]));
3095    }
3096
3097    public function test_forum_core_calendar_provide_event_action() {
3098        $this->resetAfterTest();
3099        $this->setAdminUser();
3100
3101        // Create the activity.
3102        $course = $this->getDataGenerator()->create_course();
3103        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id,
3104            'completionreplies' => 5, 'completiondiscussions' => 2));
3105
3106        // Create a calendar event.
3107        $event = $this->create_action_event($course->id, $forum->id,
3108            \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
3109
3110        // Create an action factory.
3111        $factory = new \core_calendar\action_factory();
3112
3113        // Decorate action event.
3114        $actionevent = mod_forum_core_calendar_provide_event_action($event, $factory);
3115
3116        // Confirm the event was decorated.
3117        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
3118        $this->assertEquals(get_string('view'), $actionevent->get_name());
3119        $this->assertInstanceOf('moodle_url', $actionevent->get_url());
3120        $this->assertEquals(7, $actionevent->get_item_count());
3121        $this->assertTrue($actionevent->is_actionable());
3122    }
3123
3124    public function test_forum_core_calendar_provide_event_action_in_hidden_section() {
3125        global $CFG;
3126
3127        $this->resetAfterTest();
3128        $this->setAdminUser();
3129
3130        // Create a course.
3131        $course = $this->getDataGenerator()->create_course();
3132
3133        // Create a student.
3134        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3135
3136        // Create the activity.
3137        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id,
3138                'completionreplies' => 5, 'completiondiscussions' => 2));
3139
3140        // Create a calendar event.
3141        $event = $this->create_action_event($course->id, $forum->id,
3142                \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
3143
3144        // Set sections 0 as hidden.
3145        set_section_visible($course->id, 0, 0);
3146
3147        // Now, log out.
3148        $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
3149        $this->setUser();
3150
3151        // Create an action factory.
3152        $factory = new \core_calendar\action_factory();
3153
3154        // Decorate action event for the student.
3155        $actionevent = mod_forum_core_calendar_provide_event_action($event, $factory, $student->id);
3156
3157        // Confirm the event is not shown at all.
3158        $this->assertNull($actionevent);
3159    }
3160
3161    public function test_forum_core_calendar_provide_event_action_for_user() {
3162        global $CFG;
3163
3164        $this->resetAfterTest();
3165        $this->setAdminUser();
3166
3167        // Create a course.
3168        $course = $this->getDataGenerator()->create_course();
3169
3170        // Create a student.
3171        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3172
3173        // Create the activity.
3174        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id,
3175                'completionreplies' => 5, 'completiondiscussions' => 2));
3176
3177        // Create a calendar event.
3178        $event = $this->create_action_event($course->id, $forum->id,
3179                \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
3180
3181        // Now log out.
3182        $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
3183        $this->setUser();
3184
3185        // Create an action factory.
3186        $factory = new \core_calendar\action_factory();
3187
3188        // Decorate action event for the student.
3189        $actionevent = mod_forum_core_calendar_provide_event_action($event, $factory, $student->id);
3190
3191        // Confirm the event was decorated.
3192        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
3193        $this->assertEquals(get_string('view'), $actionevent->get_name());
3194        $this->assertInstanceOf('moodle_url', $actionevent->get_url());
3195        $this->assertEquals(7, $actionevent->get_item_count());
3196        $this->assertTrue($actionevent->is_actionable());
3197    }
3198
3199    public function test_forum_core_calendar_provide_event_action_as_non_user() {
3200        global $CFG;
3201
3202        $this->resetAfterTest();
3203        $this->setAdminUser();
3204
3205        // Create the activity.
3206        $course = $this->getDataGenerator()->create_course();
3207        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
3208
3209        // Create a calendar event.
3210        $event = $this->create_action_event($course->id, $forum->id,
3211            \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
3212
3213        // Log out the user and set force login to true.
3214        \core\session\manager::init_empty_session();
3215        $CFG->forcelogin = true;
3216
3217        // Create an action factory.
3218        $factory = new \core_calendar\action_factory();
3219
3220        // Decorate action event.
3221        $actionevent = mod_forum_core_calendar_provide_event_action($event, $factory);
3222
3223        // Ensure result was null.
3224        $this->assertNull($actionevent);
3225    }
3226
3227    public function test_forum_core_calendar_provide_event_action_already_completed() {
3228        global $CFG;
3229
3230        $this->resetAfterTest();
3231        $this->setAdminUser();
3232
3233        $CFG->enablecompletion = 1;
3234
3235        // Create the activity.
3236        $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
3237        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
3238            array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
3239
3240        // Get some additional data.
3241        $cm = get_coursemodule_from_instance('forum', $forum->id);
3242
3243        // Create a calendar event.
3244        $event = $this->create_action_event($course->id, $forum->id,
3245            \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
3246
3247        // Mark the activity as completed.
3248        $completion = new completion_info($course);
3249        $completion->set_module_viewed($cm);
3250
3251        // Create an action factory.
3252        $factory = new \core_calendar\action_factory();
3253
3254        // Decorate action event.
3255        $actionevent = mod_forum_core_calendar_provide_event_action($event, $factory);
3256
3257        // Ensure result was null.
3258        $this->assertNull($actionevent);
3259    }
3260
3261    public function test_forum_core_calendar_provide_event_action_already_completed_for_user() {
3262        global $CFG;
3263
3264        $this->resetAfterTest();
3265        $this->setAdminUser();
3266
3267        $CFG->enablecompletion = 1;
3268
3269        // Create a course.
3270        $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
3271
3272        // Create a student.
3273        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3274
3275        // Create the activity.
3276        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
3277            array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
3278
3279        // Get some additional data.
3280        $cm = get_coursemodule_from_instance('forum', $forum->id);
3281
3282        // Create a calendar event.
3283        $event = $this->create_action_event($course->id, $forum->id,
3284            \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
3285
3286        // Mark the activity as completed for the student.
3287        $completion = new completion_info($course);
3288        $completion->set_module_viewed($cm, $student->id);
3289
3290        // Create an action factory.
3291        $factory = new \core_calendar\action_factory();
3292
3293        // Decorate action event.
3294        $actionevent = mod_forum_core_calendar_provide_event_action($event, $factory, $student->id);
3295
3296        // Ensure result was null.
3297        $this->assertNull($actionevent);
3298    }
3299
3300    public function test_mod_forum_get_tagged_posts() {
3301        global $DB;
3302
3303        $this->resetAfterTest();
3304        $this->setAdminUser();
3305
3306        // Setup test data.
3307        $forumgenerator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
3308        $course3 = $this->getDataGenerator()->create_course();
3309        $course2 = $this->getDataGenerator()->create_course();
3310        $course1 = $this->getDataGenerator()->create_course();
3311        $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id));
3312        $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id));
3313        $forum3 = $this->getDataGenerator()->create_module('forum', array('course' => $course3->id));
3314        $post11 = $forumgenerator->create_content($forum1, array('tags' => array('Cats', 'Dogs')));
3315        $post12 = $forumgenerator->create_content($forum1, array('tags' => array('Cats', 'mice')));
3316        $post13 = $forumgenerator->create_content($forum1, array('tags' => array('Cats')));
3317        $post14 = $forumgenerator->create_content($forum1);
3318        $post15 = $forumgenerator->create_content($forum1, array('tags' => array('Cats')));
3319        $post16 = $forumgenerator->create_content($forum1, array('tags' => array('Cats'), 'hidden' => true));
3320        $post21 = $forumgenerator->create_content($forum2, array('tags' => array('Cats')));
3321        $post22 = $forumgenerator->create_content($forum2, array('tags' => array('Cats', 'Dogs')));
3322        $post23 = $forumgenerator->create_content($forum2, array('tags' => array('mice', 'Cats')));
3323        $post31 = $forumgenerator->create_content($forum3, array('tags' => array('mice', 'Cats')));
3324
3325        $tag = core_tag_tag::get_by_name(0, 'Cats');
3326
3327        // Admin can see everything.
3328        $res = mod_forum_get_tagged_posts($tag, /*$exclusivemode = */false,
3329            /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$post = */0);
3330        $this->assertRegExp('/'.$post11->subject.'</', $res->content);
3331        $this->assertRegExp('/'.$post12->subject.'</', $res->content);
3332        $this->assertRegExp('/'.$post13->subject.'</', $res->content);
3333        $this->assertNotRegExp('/'.$post14->subject.'</', $res->content);
3334        $this->assertRegExp('/'.$post15->subject.'</', $res->content);
3335        $this->assertRegExp('/'.$post16->subject.'</', $res->content);
3336        $this->assertNotRegExp('/'.$post21->subject.'</', $res->content);
3337        $this->assertNotRegExp('/'.$post22->subject.'</', $res->content);
3338        $this->assertNotRegExp('/'.$post23->subject.'</', $res->content);
3339        $this->assertNotRegExp('/'.$post31->subject.'</', $res->content);
3340        $this->assertEmpty($res->prevpageurl);
3341        $this->assertNotEmpty($res->nextpageurl);
3342        $res = mod_forum_get_tagged_posts($tag, /*$exclusivemode = */false,
3343            /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$post = */1);
3344        $this->assertNotRegExp('/'.$post11->subject.'</', $res->content);
3345        $this->assertNotRegExp('/'.$post12->subject.'</', $res->content);
3346        $this->assertNotRegExp('/'.$post13->subject.'</', $res->content);
3347        $this->assertNotRegExp('/'.$post14->subject.'</', $res->content);
3348        $this->assertNotRegExp('/'.$post15->subject.'</', $res->content);
3349        $this->assertNotRegExp('/'.$post16->subject.'</', $res->content);
3350        $this->assertRegExp('/'.$post21->subject.'</', $res->content);
3351        $this->assertRegExp('/'.$post22->subject.'</', $res->content);
3352        $this->assertRegExp('/'.$post23->subject.'</', $res->content);
3353        $this->assertRegExp('/'.$post31->subject.'</', $res->content);
3354        $this->assertNotEmpty($res->prevpageurl);
3355        $this->assertEmpty($res->nextpageurl);
3356
3357        // Create and enrol a user.
3358        $student = self::getDataGenerator()->create_user();
3359        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
3360        $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual');
3361        $this->getDataGenerator()->enrol_user($student->id, $course2->id, $studentrole->id, 'manual');
3362        $this->setUser($student);
3363        core_tag_index_builder::reset_caches();
3364
3365        // User can not see posts in course 3 because he is not enrolled.
3366        $res = mod_forum_get_tagged_posts($tag, /*$exclusivemode = */false,
3367            /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$post = */1);
3368        $this->assertRegExp('/'.$post22->subject.'/', $res->content);
3369        $this->assertRegExp('/'.$post23->subject.'/', $res->content);
3370        $this->assertNotRegExp('/'.$post31->subject.'/', $res->content);
3371
3372        // User can search forum posts inside a course.
3373        $coursecontext = context_course::instance($course1->id);
3374        $res = mod_forum_get_tagged_posts($tag, /*$exclusivemode = */false,
3375            /*$fromctx = */0, /*$ctx = */$coursecontext->id, /*$rec = */1, /*$post = */0);
3376        $this->assertRegExp('/'.$post11->subject.'/', $res->content);
3377        $this->assertRegExp('/'.$post12->subject.'/', $res->content);
3378        $this->assertRegExp('/'.$post13->subject.'/', $res->content);
3379        $this->assertNotRegExp('/'.$post14->subject.'/', $res->content);
3380        $this->assertRegExp('/'.$post15->subject.'/', $res->content);
3381        $this->assertRegExp('/'.$post16->subject.'/', $res->content);
3382        $this->assertNotRegExp('/'.$post21->subject.'/', $res->content);
3383        $this->assertNotRegExp('/'.$post22->subject.'/', $res->content);
3384        $this->assertNotRegExp('/'.$post23->subject.'/', $res->content);
3385        $this->assertEmpty($res->nextpageurl);
3386    }
3387
3388    /**
3389     * Creates an action event.
3390     *
3391     * @param int $courseid The course id.
3392     * @param int $instanceid The instance id.
3393     * @param string $eventtype The event type.
3394     * @return bool|calendar_event
3395     */
3396    private function create_action_event($courseid, $instanceid, $eventtype) {
3397        $event = new stdClass();
3398        $event->name = 'Calendar event';
3399        $event->modulename  = 'forum';
3400        $event->courseid = $courseid;
3401        $event->instance = $instanceid;
3402        $event->type = CALENDAR_EVENT_TYPE_ACTION;
3403        $event->eventtype = $eventtype;
3404        $event->timestart = time();
3405
3406        return calendar_event::create($event);
3407    }
3408
3409    /**
3410     * Test the callback responsible for returning the completion rule descriptions.
3411     * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
3412     * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
3413     */
3414    public function test_mod_forum_completion_get_active_rule_descriptions() {
3415        $this->resetAfterTest();
3416        $this->setAdminUser();
3417
3418        // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
3419        $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]);
3420        $forum1 = $this->getDataGenerator()->create_module('forum', [
3421            'course' => $course->id,
3422            'completion' => 2,
3423            'completiondiscussions' => 3,
3424            'completionreplies' => 3,
3425            'completionposts' => 3
3426        ]);
3427        $forum2 = $this->getDataGenerator()->create_module('forum', [
3428            'course' => $course->id,
3429            'completion' => 2,
3430            'completiondiscussions' => 0,
3431            'completionreplies' => 0,
3432            'completionposts' => 0
3433        ]);
3434        $cm1 = cm_info::create(get_coursemodule_from_instance('forum', $forum1->id));
3435        $cm2 = cm_info::create(get_coursemodule_from_instance('forum', $forum2->id));
3436
3437        // Data for the stdClass input type.
3438        // This type of input would occur when checking the default completion rules for an activity type, where we don't have
3439        // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
3440        $moddefaults = new stdClass();
3441        $moddefaults->customdata = ['customcompletionrules' => [
3442            'completiondiscussions' => 3,
3443            'completionreplies' => 3,
3444            'completionposts' => 3
3445        ]];
3446        $moddefaults->completion = 2;
3447
3448        $activeruledescriptions = [
3449            get_string('completiondiscussionsdesc', 'forum', 3),
3450            get_string('completionrepliesdesc', 'forum', 3),
3451            get_string('completionpostsdesc', 'forum', 3)
3452        ];
3453        $this->assertEquals(mod_forum_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
3454        $this->assertEquals(mod_forum_get_completion_active_rule_descriptions($cm2), []);
3455        $this->assertEquals(mod_forum_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
3456        $this->assertEquals(mod_forum_get_completion_active_rule_descriptions(new stdClass()), []);
3457    }
3458
3459    /**
3460     * Test the forum_post_is_visible_privately function used in private replies.
3461     */
3462    public function test_forum_post_is_visible_privately() {
3463        $this->resetAfterTest();
3464
3465        $course = $this->getDataGenerator()->create_course();
3466        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
3467        $context = context_module::instance($forum->cmid);
3468        $cm = get_coursemodule_from_instance('forum', $forum->id);
3469
3470        $author = $this->getDataGenerator()->create_user();
3471        $this->getDataGenerator()->enrol_user($author->id, $course->id);
3472
3473        $recipient = $this->getDataGenerator()->create_user();
3474        $this->getDataGenerator()->enrol_user($recipient->id, $course->id);
3475
3476        $privilegeduser = $this->getDataGenerator()->create_user();
3477        $this->getDataGenerator()->enrol_user($privilegeduser->id, $course->id, 'editingteacher');
3478
3479        $otheruser = $this->getDataGenerator()->create_user();
3480        $this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
3481
3482        // Fake a post - this does not need to be persisted to the DB.
3483        $post = new \stdClass();
3484        $post->userid = $author->id;
3485        $post->privatereplyto = $recipient->id;
3486
3487        // The user is the author.
3488        $this->setUser($author->id);
3489        $this->assertTrue(forum_post_is_visible_privately($post, $cm));
3490
3491        // The user is the intended recipient.
3492        $this->setUser($recipient->id);
3493        $this->assertTrue(forum_post_is_visible_privately($post, $cm));
3494
3495        // The user is not the author or recipient, but does have the readprivatereplies capability.
3496        $this->setUser($privilegeduser->id);
3497        $this->assertTrue(forum_post_is_visible_privately($post, $cm));
3498
3499        // The user is not allowed to view this post.
3500        $this->setUser($otheruser->id);
3501        $this->assertFalse(forum_post_is_visible_privately($post, $cm));
3502    }
3503
3504    /**
3505     * An unkown event type should not have any limits
3506     */
3507    public function test_mod_forum_core_calendar_get_valid_event_timestart_range_unknown_event() {
3508        global $CFG;
3509        require_once($CFG->dirroot . "/calendar/lib.php");
3510
3511        $this->resetAfterTest(true);
3512        $this->setAdminUser();
3513        $generator = $this->getDataGenerator();
3514        $course = $generator->create_course();
3515        $duedate = time() + DAYSECS;
3516        $forum = new \stdClass();
3517        $forum->duedate = $duedate;
3518
3519        // Create a valid event.
3520        $event = new \calendar_event([
3521            'name' => 'Test event',
3522            'description' => '',
3523            'format' => 1,
3524            'courseid' => $course->id,
3525            'groupid' => 0,
3526            'userid' => 2,
3527            'modulename' => 'forum',
3528            'instance' => 1,
3529            'eventtype' => FORUM_EVENT_TYPE_DUE . "SOMETHING ELSE",
3530            'timestart' => 1,
3531            'timeduration' => 86400,
3532            'visible' => 1
3533        ]);
3534
3535        list ($min, $max) = mod_forum_core_calendar_get_valid_event_timestart_range($event, $forum);
3536        $this->assertNull($min);
3537        $this->assertNull($max);
3538    }
3539
3540    /**
3541     * Forums configured without a cutoff date should not have any limits applied.
3542     */
3543    public function test_mod_forum_core_calendar_get_valid_event_timestart_range_due_no_limit() {
3544        global $CFG;
3545        require_once($CFG->dirroot . '/calendar/lib.php');
3546
3547        $this->resetAfterTest(true);
3548        $this->setAdminUser();
3549        $generator = $this->getDataGenerator();
3550        $course = $generator->create_course();
3551        $duedate = time() + DAYSECS;
3552        $forum = new \stdClass();
3553        $forum->duedate = $duedate;
3554
3555        // Create a valid event.
3556        $event = new \calendar_event([
3557            'name' => 'Test event',
3558            'description' => '',
3559            'format' => 1,
3560            'courseid' => $course->id,
3561            'groupid' => 0,
3562            'userid' => 2,
3563            'modulename' => 'forum',
3564            'instance' => 1,
3565            'eventtype' => FORUM_EVENT_TYPE_DUE,
3566            'timestart' => 1,
3567            'timeduration' => 86400,
3568            'visible' => 1
3569        ]);
3570
3571        list($min, $max) = mod_forum_core_calendar_get_valid_event_timestart_range($event, $forum);
3572        $this->assertNull($min);
3573        $this->assertNull($max);
3574    }
3575
3576    /**
3577     * Forums should be top bound by the cutoff date.
3578     */
3579    public function test_mod_forum_core_calendar_get_valid_event_timestart_range_due_with_limits() {
3580        global $CFG;
3581        require_once($CFG->dirroot . '/calendar/lib.php');
3582
3583        $this->resetAfterTest(true);
3584        $this->setAdminUser();
3585        $generator = $this->getDataGenerator();
3586        $course = $generator->create_course();
3587        $duedate = time() + DAYSECS;
3588        $cutoffdate = $duedate + DAYSECS;
3589        $forum = new \stdClass();
3590        $forum->duedate = $duedate;
3591        $forum->cutoffdate = $cutoffdate;
3592
3593        // Create a valid event.
3594        $event = new \calendar_event([
3595            'name' => 'Test event',
3596            'description' => '',
3597            'format' => 1,
3598            'courseid' => $course->id,
3599            'groupid' => 0,
3600            'userid' => 2,
3601            'modulename' => 'forum',
3602            'instance' => 1,
3603            'eventtype' => FORUM_EVENT_TYPE_DUE,
3604            'timestart' => 1,
3605            'timeduration' => 86400,
3606            'visible' => 1
3607        ]);
3608
3609        list($min, $max) = mod_forum_core_calendar_get_valid_event_timestart_range($event, $forum);
3610        $this->assertNull($min);
3611        $this->assertEquals($cutoffdate, $max[0]);
3612        $this->assertNotEmpty($max[1]);
3613    }
3614
3615    /**
3616     * An unknown event type should not change the forum instance.
3617     */
3618    public function test_mod_forum_core_calendar_event_timestart_updated_unknown_event() {
3619        global $CFG, $DB;
3620        require_once($CFG->dirroot . "/calendar/lib.php");
3621
3622        $this->resetAfterTest(true);
3623        $this->setAdminUser();
3624        $generator = $this->getDataGenerator();
3625        $course = $generator->create_course();
3626        $forumgenerator = $generator->get_plugin_generator('mod_forum');
3627        $duedate = time() + DAYSECS;
3628        $cutoffdate = $duedate + DAYSECS;
3629        $forum = $forumgenerator->create_instance(['course' => $course->id]);
3630        $forum->duedate = $duedate;
3631        $forum->cutoffdate = $cutoffdate;
3632        $DB->update_record('forum', $forum);
3633
3634        // Create a valid event.
3635        $event = new \calendar_event([
3636            'name' => 'Test event',
3637            'description' => '',
3638            'format' => 1,
3639            'courseid' => $course->id,
3640            'groupid' => 0,
3641            'userid' => 2,
3642            'modulename' => 'forum',
3643            'instance' => $forum->id,
3644            'eventtype' => FORUM_EVENT_TYPE_DUE . "SOMETHING ELSE",
3645            'timestart' => 1,
3646            'timeduration' => 86400,
3647            'visible' => 1
3648        ]);
3649
3650        mod_forum_core_calendar_event_timestart_updated($event, $forum);
3651
3652        $forum = $DB->get_record('forum', ['id' => $forum->id]);
3653        $this->assertEquals($duedate, $forum->duedate);
3654        $this->assertEquals($cutoffdate, $forum->cutoffdate);
3655    }
3656
3657    /**
3658     * Due date events should update the forum due date.
3659     */
3660    public function test_mod_forum_core_calendar_event_timestart_updated_due_event() {
3661        global $CFG, $DB;
3662        require_once($CFG->dirroot . "/calendar/lib.php");
3663
3664        $this->resetAfterTest(true);
3665        $this->setAdminUser();
3666        $generator = $this->getDataGenerator();
3667        $course = $generator->create_course();
3668        $forumgenerator = $generator->get_plugin_generator('mod_forum');
3669        $duedate = time() + DAYSECS;
3670        $cutoffdate = $duedate + DAYSECS;
3671        $newduedate = $duedate + 1;
3672        $forum = $forumgenerator->create_instance(['course' => $course->id]);
3673        $forum->duedate = $duedate;
3674        $forum->cutoffdate = $cutoffdate;
3675        $DB->update_record('forum', $forum);
3676
3677        // Create a valid event.
3678        $event = new \calendar_event([
3679            'name' => 'Test event',
3680            'description' => '',
3681            'format' => 1,
3682            'courseid' => $course->id,
3683            'groupid' => 0,
3684            'userid' => 2,
3685            'modulename' => 'forum',
3686            'instance' => $forum->id,
3687            'eventtype' => FORUM_EVENT_TYPE_DUE,
3688            'timestart' => $newduedate,
3689            'timeduration' => 86400,
3690            'visible' => 1
3691        ]);
3692
3693        mod_forum_core_calendar_event_timestart_updated($event, $forum);
3694
3695        $forum = $DB->get_record('forum', ['id' => $forum->id]);
3696        $this->assertEquals($newduedate, $forum->duedate);
3697        $this->assertEquals($cutoffdate, $forum->cutoffdate);
3698    }
3699
3700    /**
3701     * Test forum_get_layout_modes function.
3702     */
3703    public function test_forum_get_layout_modes() {
3704        $expectednormal = [
3705            FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'),
3706            FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'),
3707            FORUM_MODE_THREADED   => get_string('modethreaded', 'forum'),
3708            FORUM_MODE_NESTED => get_string('modenested', 'forum')
3709        ];
3710        $expectedexperimental = [
3711            FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'),
3712            FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'),
3713            FORUM_MODE_THREADED   => get_string('modethreaded', 'forum'),
3714            FORUM_MODE_NESTED_V2 => get_string('modenestedv2', 'forum')
3715        ];
3716
3717        $this->assertEquals($expectednormal, forum_get_layout_modes());
3718        $this->assertEquals($expectednormal, forum_get_layout_modes(false));
3719        $this->assertEquals($expectedexperimental, forum_get_layout_modes(true));
3720    }
3721}
3722