Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

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