Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

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