Search moodle.org's
Developer Documentation

See Release Notes

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

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

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