Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

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