Search moodle.org's
Developer Documentation

See Release Notes

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

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace mod_forum;
  18  
  19  use mod_forum_tests_generator_trait;
  20  
  21  defined('MOODLE_INTERNAL') || die();
  22  
  23  global $CFG;
  24  require_once (__DIR__ . '/generator_trait.php');
  25  require_once("{$CFG->dirroot}/mod/forum/lib.php");
  26  
  27  /**
  28   * The module forums tests
  29   *
  30   * @package    mod_forum
  31   * @copyright  2013 Frédéric Massart
  32   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  33   */
  34  class subscriptions_test extends \advanced_testcase {
  35      // Include the mod_forum test helpers.
  36      // This includes functions to create forums, users, discussions, and posts.
  37      use mod_forum_tests_generator_trait;
  38  
  39      /**
  40       * Test setUp.
  41       */
  42      public function setUp(): void {
  43          global $DB;
  44  
  45          // We must clear the subscription caches. This has to be done both before each test, and after in case of other
  46          // tests using these functions.
  47          \mod_forum\subscriptions::reset_forum_cache();
  48          \mod_forum\subscriptions::reset_discussion_cache();
  49      }
  50  
  51      /**
  52       * Test tearDown.
  53       */
  54      public function tearDown(): void {
  55          // We must clear the subscription caches. This has to be done both before each test, and after in case of other
  56          // tests using these functions.
  57          \mod_forum\subscriptions::reset_forum_cache();
  58          \mod_forum\subscriptions::reset_discussion_cache();
  59      }
  60  
  61      public function test_subscription_modes() {
  62          global $DB;
  63  
  64          $this->resetAfterTest(true);
  65  
  66          // Create a course, with a forum.
  67          $course = $this->getDataGenerator()->create_course();
  68  
  69          $options = array('course' => $course->id);
  70          $forum = $this->getDataGenerator()->create_module('forum', $options);
  71  
  72          // Create a user enrolled in the course as a student.
  73          list($user) = $this->helper_create_users($course, 1);
  74  
  75          // Must be logged in as the current user.
  76          $this->setUser($user);
  77  
  78          \mod_forum\subscriptions::set_subscription_mode($forum->id, FORUM_FORCESUBSCRIBE);
  79          $forum = $DB->get_record('forum', array('id' => $forum->id));
  80          $this->assertEquals(FORUM_FORCESUBSCRIBE, \mod_forum\subscriptions::get_subscription_mode($forum));
  81          $this->assertTrue(\mod_forum\subscriptions::is_forcesubscribed($forum));
  82          $this->assertFalse(\mod_forum\subscriptions::is_subscribable($forum));
  83          $this->assertFalse(\mod_forum\subscriptions::subscription_disabled($forum));
  84  
  85          \mod_forum\subscriptions::set_subscription_mode($forum->id, FORUM_DISALLOWSUBSCRIBE);
  86          $forum = $DB->get_record('forum', array('id' => $forum->id));
  87          $this->assertEquals(FORUM_DISALLOWSUBSCRIBE, \mod_forum\subscriptions::get_subscription_mode($forum));
  88          $this->assertTrue(\mod_forum\subscriptions::subscription_disabled($forum));
  89          $this->assertFalse(\mod_forum\subscriptions::is_subscribable($forum));
  90          $this->assertFalse(\mod_forum\subscriptions::is_forcesubscribed($forum));
  91  
  92          \mod_forum\subscriptions::set_subscription_mode($forum->id, FORUM_INITIALSUBSCRIBE);
  93          $forum = $DB->get_record('forum', array('id' => $forum->id));
  94          $this->assertEquals(FORUM_INITIALSUBSCRIBE, \mod_forum\subscriptions::get_subscription_mode($forum));
  95          $this->assertTrue(\mod_forum\subscriptions::is_subscribable($forum));
  96          $this->assertFalse(\mod_forum\subscriptions::subscription_disabled($forum));
  97          $this->assertFalse(\mod_forum\subscriptions::is_forcesubscribed($forum));
  98  
  99          \mod_forum\subscriptions::set_subscription_mode($forum->id, FORUM_CHOOSESUBSCRIBE);
 100          $forum = $DB->get_record('forum', array('id' => $forum->id));
 101          $this->assertEquals(FORUM_CHOOSESUBSCRIBE, \mod_forum\subscriptions::get_subscription_mode($forum));
 102          $this->assertTrue(\mod_forum\subscriptions::is_subscribable($forum));
 103          $this->assertFalse(\mod_forum\subscriptions::subscription_disabled($forum));
 104          $this->assertFalse(\mod_forum\subscriptions::is_forcesubscribed($forum));
 105      }
 106  
 107      /**
 108       * Test fetching unsubscribable forums.
 109       */
 110      public function test_unsubscribable_forums() {
 111          global $DB;
 112  
 113          $this->resetAfterTest(true);
 114  
 115          // Create a course, with a forum.
 116          $course = $this->getDataGenerator()->create_course();
 117  
 118          // Create a user enrolled in the course as a student.
 119          list($user) = $this->helper_create_users($course, 1);
 120  
 121          // Must be logged in as the current user.
 122          $this->setUser($user);
 123  
 124          // Without any subscriptions, there should be nothing returned.
 125          $result = \mod_forum\subscriptions::get_unsubscribable_forums();
 126          $this->assertEquals(0, count($result));
 127  
 128          // Create the forums.
 129          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
 130          $forceforum = $this->getDataGenerator()->create_module('forum', $options);
 131          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_DISALLOWSUBSCRIBE);
 132          $disallowforum = $this->getDataGenerator()->create_module('forum', $options);
 133          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 134          $chooseforum = $this->getDataGenerator()->create_module('forum', $options);
 135          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
 136          $initialforum = $this->getDataGenerator()->create_module('forum', $options);
 137  
 138          // At present the user is only subscribed to the initial forum.
 139          $result = \mod_forum\subscriptions::get_unsubscribable_forums();
 140          $this->assertEquals(1, count($result));
 141  
 142          // Ensure that the user is enrolled in all of the forums except force subscribed.
 143          \mod_forum\subscriptions::subscribe_user($user->id, $disallowforum);
 144          \mod_forum\subscriptions::subscribe_user($user->id, $chooseforum);
 145  
 146          $result = \mod_forum\subscriptions::get_unsubscribable_forums();
 147          $this->assertEquals(3, count($result));
 148  
 149          // Hide the forums.
 150          set_coursemodule_visible($forceforum->cmid, 0);
 151          set_coursemodule_visible($disallowforum->cmid, 0);
 152          set_coursemodule_visible($chooseforum->cmid, 0);
 153          set_coursemodule_visible($initialforum->cmid, 0);
 154          $result = \mod_forum\subscriptions::get_unsubscribable_forums();
 155          $this->assertEquals(0, count($result));
 156  
 157          // Add the moodle/course:viewhiddenactivities capability to the student user.
 158          $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
 159          $context = \context_course::instance($course->id);
 160          assign_capability('moodle/course:viewhiddenactivities', CAP_ALLOW, $roleids['student'], $context);
 161  
 162          // All of the unsubscribable forums should now be listed.
 163          $result = \mod_forum\subscriptions::get_unsubscribable_forums();
 164          $this->assertEquals(3, count($result));
 165      }
 166  
 167      /**
 168       * Test that toggling the forum-level subscription for a different user does not affect their discussion-level
 169       * subscriptions.
 170       */
 171      public function test_forum_subscribe_toggle_as_other() {
 172          global $DB;
 173  
 174          $this->resetAfterTest(true);
 175  
 176          // Create a course, with a forum.
 177          $course = $this->getDataGenerator()->create_course();
 178  
 179          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 180          $forum = $this->getDataGenerator()->create_module('forum', $options);
 181  
 182          // Create a user enrolled in the course as a student.
 183          list($author) = $this->helper_create_users($course, 1);
 184  
 185          // Post a discussion to the forum.
 186          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 187  
 188          // Check that the user is currently not subscribed to the forum.
 189          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 190  
 191          // Check that the user is unsubscribed from the discussion too.
 192          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 193  
 194          // Check that we have no records in either of the subscription tables.
 195          $this->assertEquals(0, $DB->count_records('forum_subscriptions', array(
 196              'userid'        => $author->id,
 197              'forum'         => $forum->id,
 198          )));
 199          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
 200              'userid'        => $author->id,
 201              'discussion'    => $discussion->id,
 202          )));
 203  
 204          // Subscribing to the forum should create a record in the subscriptions table, but not the forum discussion
 205          // subscriptions table.
 206          \mod_forum\subscriptions::subscribe_user($author->id, $forum);
 207          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 208              'userid'        => $author->id,
 209              'forum'         => $forum->id,
 210          )));
 211          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
 212              'userid'        => $author->id,
 213              'discussion'    => $discussion->id,
 214          )));
 215  
 216          // Unsubscribing should remove the record from the forum subscriptions table, and not modify the forum
 217          // discussion subscriptions table.
 218          \mod_forum\subscriptions::unsubscribe_user($author->id, $forum);
 219          $this->assertEquals(0, $DB->count_records('forum_subscriptions', array(
 220              'userid'        => $author->id,
 221              'forum'         => $forum->id,
 222          )));
 223          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
 224              'userid'        => $author->id,
 225              'discussion'    => $discussion->id,
 226          )));
 227  
 228          // Enroling the user in the discussion should add one record to the forum discussion table without modifying the
 229          // form subscriptions.
 230          \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion);
 231          $this->assertEquals(0, $DB->count_records('forum_subscriptions', array(
 232              'userid'        => $author->id,
 233              'forum'         => $forum->id,
 234          )));
 235          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 236              'userid'        => $author->id,
 237              'discussion'    => $discussion->id,
 238          )));
 239  
 240          // Unsubscribing should remove the record from the forum subscriptions table, and not modify the forum
 241          // discussion subscriptions table.
 242          \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion);
 243          $this->assertEquals(0, $DB->count_records('forum_subscriptions', array(
 244              'userid'        => $author->id,
 245              'forum'         => $forum->id,
 246          )));
 247          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
 248              'userid'        => $author->id,
 249              'discussion'    => $discussion->id,
 250          )));
 251  
 252          // Re-subscribe to the discussion so that we can check the effect of forum-level subscriptions.
 253          \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion);
 254          $this->assertEquals(0, $DB->count_records('forum_subscriptions', array(
 255              'userid'        => $author->id,
 256              'forum'         => $forum->id,
 257          )));
 258          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 259              'userid'        => $author->id,
 260              'discussion'    => $discussion->id,
 261          )));
 262  
 263          // Subscribing to the forum should have no effect on the forum discussion subscriptions table if the user did
 264          // not request the change themself.
 265          \mod_forum\subscriptions::subscribe_user($author->id, $forum);
 266          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 267              'userid'        => $author->id,
 268              'forum'         => $forum->id,
 269          )));
 270          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 271              'userid'        => $author->id,
 272              'discussion'    => $discussion->id,
 273          )));
 274  
 275          // Unsubscribing from the forum should have no effect on the forum discussion subscriptions table if the user
 276          // did not request the change themself.
 277          \mod_forum\subscriptions::unsubscribe_user($author->id, $forum);
 278          $this->assertEquals(0, $DB->count_records('forum_subscriptions', array(
 279              'userid'        => $author->id,
 280              'forum'         => $forum->id,
 281          )));
 282          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 283              'userid'        => $author->id,
 284              'discussion'    => $discussion->id,
 285          )));
 286  
 287          // Subscribing to the forum should remove the per-discussion subscription preference if the user requested the
 288          // change themself.
 289          \mod_forum\subscriptions::subscribe_user($author->id, $forum, null, true);
 290          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 291              'userid'        => $author->id,
 292              'forum'         => $forum->id,
 293          )));
 294          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
 295              'userid'        => $author->id,
 296              'discussion'    => $discussion->id,
 297          )));
 298  
 299          // Now unsubscribe from the current discussion whilst being subscribed to the forum as a whole.
 300          \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion);
 301          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 302              'userid'        => $author->id,
 303              'forum'         => $forum->id,
 304          )));
 305          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 306              'userid'        => $author->id,
 307              'discussion'    => $discussion->id,
 308          )));
 309  
 310          // Unsubscribing from the forum should remove the per-discussion subscription preference if the user requested the
 311          // change themself.
 312          \mod_forum\subscriptions::unsubscribe_user($author->id, $forum, null, true);
 313          $this->assertEquals(0, $DB->count_records('forum_subscriptions', array(
 314              'userid'        => $author->id,
 315              'forum'         => $forum->id,
 316          )));
 317          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
 318              'userid'        => $author->id,
 319              'discussion'    => $discussion->id,
 320          )));
 321  
 322          // Subscribe to the discussion.
 323          \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion);
 324          $this->assertEquals(0, $DB->count_records('forum_subscriptions', array(
 325              'userid'        => $author->id,
 326              'forum'         => $forum->id,
 327          )));
 328          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 329              'userid'        => $author->id,
 330              'discussion'    => $discussion->id,
 331          )));
 332  
 333          // Subscribe to the forum without removing the discussion preferences.
 334          \mod_forum\subscriptions::subscribe_user($author->id, $forum);
 335          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 336              'userid'        => $author->id,
 337              'forum'         => $forum->id,
 338          )));
 339          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 340              'userid'        => $author->id,
 341              'discussion'    => $discussion->id,
 342          )));
 343  
 344          // Unsubscribing from the discussion should result in a change.
 345          \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion);
 346          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 347              'userid'        => $author->id,
 348              'forum'         => $forum->id,
 349          )));
 350          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 351              'userid'        => $author->id,
 352              'discussion'    => $discussion->id,
 353          )));
 354  
 355      }
 356  
 357      /**
 358       * Test that a user unsubscribed from a forum is not subscribed to it's discussions by default.
 359       */
 360      public function test_forum_discussion_subscription_forum_unsubscribed() {
 361          $this->resetAfterTest(true);
 362  
 363          // Create a course, with a forum.
 364          $course = $this->getDataGenerator()->create_course();
 365  
 366          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 367          $forum = $this->getDataGenerator()->create_module('forum', $options);
 368  
 369          // Create users enrolled in the course as students.
 370          list($author) = $this->helper_create_users($course, 1);
 371  
 372          // Check that the user is currently not subscribed to the forum.
 373          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 374  
 375          // Post a discussion to the forum.
 376          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 377  
 378          // Check that the user is unsubscribed from the discussion too.
 379          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 380      }
 381  
 382      /**
 383       * Test that the act of subscribing to a forum subscribes the user to it's discussions by default.
 384       */
 385      public function test_forum_discussion_subscription_forum_subscribed() {
 386          $this->resetAfterTest(true);
 387  
 388          // Create a course, with a forum.
 389          $course = $this->getDataGenerator()->create_course();
 390  
 391          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 392          $forum = $this->getDataGenerator()->create_module('forum', $options);
 393  
 394          // Create users enrolled in the course as students.
 395          list($author) = $this->helper_create_users($course, 1);
 396  
 397          // Enrol the user in the forum.
 398          // If a subscription was added, we get the record ID.
 399          $this->assertIsInt(\mod_forum\subscriptions::subscribe_user($author->id, $forum));
 400  
 401          // If we already have a subscription when subscribing the user, we get a boolean (true).
 402          $this->assertTrue(\mod_forum\subscriptions::subscribe_user($author->id, $forum));
 403  
 404          // Check that the user is currently subscribed to the forum.
 405          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 406  
 407          // Post a discussion to the forum.
 408          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 409  
 410          // Check that the user is subscribed to the discussion too.
 411          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 412      }
 413  
 414      /**
 415       * Test that a user unsubscribed from a forum can be subscribed to a discussion.
 416       */
 417      public function test_forum_discussion_subscription_forum_unsubscribed_discussion_subscribed() {
 418          $this->resetAfterTest(true);
 419  
 420          // Create a course, with a forum.
 421          $course = $this->getDataGenerator()->create_course();
 422  
 423          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 424          $forum = $this->getDataGenerator()->create_module('forum', $options);
 425  
 426          // Create a user enrolled in the course as a student.
 427          list($author) = $this->helper_create_users($course, 1);
 428  
 429          // Check that the user is currently not subscribed to the forum.
 430          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 431  
 432          // Post a discussion to the forum.
 433          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 434  
 435          // Attempting to unsubscribe from the discussion should not make a change.
 436          $this->assertFalse(\mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion));
 437  
 438          // Then subscribe them to the discussion.
 439          $this->assertTrue(\mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion));
 440  
 441          // Check that the user is still unsubscribed from the forum.
 442          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 443  
 444          // But subscribed to the discussion.
 445          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 446      }
 447  
 448      /**
 449       * Test that a user subscribed to a forum can be unsubscribed from a discussion.
 450       */
 451      public function test_forum_discussion_subscription_forum_subscribed_discussion_unsubscribed() {
 452          $this->resetAfterTest(true);
 453  
 454          // Create a course, with a forum.
 455          $course = $this->getDataGenerator()->create_course();
 456  
 457          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 458          $forum = $this->getDataGenerator()->create_module('forum', $options);
 459  
 460          // Create two users enrolled in the course as students.
 461          list($author) = $this->helper_create_users($course, 2);
 462  
 463          // Enrol the student in the forum.
 464          \mod_forum\subscriptions::subscribe_user($author->id, $forum);
 465  
 466          // Check that the user is currently subscribed to the forum.
 467          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 468  
 469          // Post a discussion to the forum.
 470          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 471  
 472          // Then unsubscribe them from the discussion.
 473          \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion);
 474  
 475          // Check that the user is still subscribed to the forum.
 476          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 477  
 478          // But unsubscribed from the discussion.
 479          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 480      }
 481  
 482      /**
 483       * Test the effect of toggling the discussion subscription status when subscribed to the forum.
 484       */
 485      public function test_forum_discussion_toggle_forum_subscribed() {
 486          global $DB;
 487  
 488          $this->resetAfterTest(true);
 489  
 490          // Create a course, with a forum.
 491          $course = $this->getDataGenerator()->create_course();
 492  
 493          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 494          $forum = $this->getDataGenerator()->create_module('forum', $options);
 495  
 496          // Create two users enrolled in the course as students.
 497          list($author) = $this->helper_create_users($course, 2);
 498  
 499          // Enrol the student in the forum.
 500          \mod_forum\subscriptions::subscribe_user($author->id, $forum);
 501  
 502          // Check that the user is currently subscribed to the forum.
 503          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 504  
 505          // Post a discussion to the forum.
 506          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 507  
 508          // Check that the user is initially subscribed to that discussion.
 509          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 510  
 511          // An attempt to subscribe again should result in a falsey return to indicate that no change was made.
 512          $this->assertFalse(\mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion));
 513  
 514          // And there should be no discussion subscriptions (and one forum subscription).
 515          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
 516              'userid'        => $author->id,
 517              'discussion'    => $discussion->id,
 518          )));
 519          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 520              'userid'        => $author->id,
 521              'forum'         => $forum->id,
 522          )));
 523  
 524          // Then unsubscribe them from the discussion.
 525          \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion);
 526  
 527          // Check that the user is still subscribed to the forum.
 528          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 529  
 530          // An attempt to unsubscribe again should result in a falsey return to indicate that no change was made.
 531          $this->assertFalse(\mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion));
 532  
 533          // And there should be a discussion subscriptions (and one forum subscription).
 534          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 535              'userid'        => $author->id,
 536              'discussion'    => $discussion->id,
 537          )));
 538          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 539              'userid'        => $author->id,
 540              'forum'         => $forum->id,
 541          )));
 542  
 543          // But unsubscribed from the discussion.
 544          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 545  
 546          // There should be a record in the discussion subscription tracking table.
 547          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 548              'userid'        => $author->id,
 549              'discussion'    => $discussion->id,
 550          )));
 551  
 552          // And one in the forum subscription tracking table.
 553          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 554              'userid'        => $author->id,
 555              'forum'         => $forum->id,
 556          )));
 557  
 558          // Now subscribe the user again to the discussion.
 559          \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion);
 560  
 561          // Check that the user is still subscribed to the forum.
 562          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 563  
 564          // And is subscribed to the discussion again.
 565          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 566  
 567          // There should be no record in the discussion subscription tracking table.
 568          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
 569              'userid'        => $author->id,
 570              'discussion'    => $discussion->id,
 571          )));
 572  
 573          // And one in the forum subscription tracking table.
 574          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 575              'userid'        => $author->id,
 576              'forum'         => $forum->id,
 577          )));
 578  
 579          // And unsubscribe again.
 580          \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion);
 581  
 582          // Check that the user is still subscribed to the forum.
 583          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 584  
 585          // But unsubscribed from the discussion.
 586          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 587  
 588          // There should be a record in the discussion subscription tracking table.
 589          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 590              'userid'        => $author->id,
 591              'discussion'    => $discussion->id,
 592          )));
 593  
 594          // And one in the forum subscription tracking table.
 595          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 596              'userid'        => $author->id,
 597              'forum'         => $forum->id,
 598          )));
 599  
 600          // And subscribe the user again to the discussion.
 601          \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion);
 602  
 603          // Check that the user is still subscribed to the forum.
 604          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 605          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 606  
 607          // And is subscribed to the discussion again.
 608          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 609  
 610          // There should be no record in the discussion subscription tracking table.
 611          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
 612              'userid'        => $author->id,
 613              'discussion'    => $discussion->id,
 614          )));
 615  
 616          // And one in the forum subscription tracking table.
 617          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 618              'userid'        => $author->id,
 619              'forum'         => $forum->id,
 620          )));
 621  
 622          // And unsubscribe again.
 623          \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion);
 624  
 625          // Check that the user is still subscribed to the forum.
 626          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 627  
 628          // But unsubscribed from the discussion.
 629          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 630  
 631          // There should be a record in the discussion subscription tracking table.
 632          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 633              'userid'        => $author->id,
 634              'discussion'    => $discussion->id,
 635          )));
 636  
 637          // And one in the forum subscription tracking table.
 638          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
 639              'userid'        => $author->id,
 640              'forum'         => $forum->id,
 641          )));
 642  
 643          // Now unsubscribe the user from the forum.
 644          $this->assertTrue(\mod_forum\subscriptions::unsubscribe_user($author->id, $forum, null, true));
 645  
 646          // This removes both the forum_subscriptions, and the forum_discussion_subs records.
 647          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
 648              'userid'        => $author->id,
 649              'discussion'    => $discussion->id,
 650          )));
 651          $this->assertEquals(0, $DB->count_records('forum_subscriptions', array(
 652              'userid'        => $author->id,
 653              'forum'         => $forum->id,
 654          )));
 655  
 656          // And should have reset the discussion cache value.
 657          $result = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $author->id);
 658          $this->assertIsArray($result);
 659          $this->assertFalse(isset($result[$discussion->id]));
 660      }
 661  
 662      /**
 663       * Test the effect of toggling the discussion subscription status when unsubscribed from the forum.
 664       */
 665      public function test_forum_discussion_toggle_forum_unsubscribed() {
 666          global $DB;
 667  
 668          $this->resetAfterTest(true);
 669  
 670          // Create a course, with a forum.
 671          $course = $this->getDataGenerator()->create_course();
 672  
 673          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 674          $forum = $this->getDataGenerator()->create_module('forum', $options);
 675  
 676          // Create two users enrolled in the course as students.
 677          list($author) = $this->helper_create_users($course, 2);
 678  
 679          // Check that the user is currently unsubscribed to the forum.
 680          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 681  
 682          // Post a discussion to the forum.
 683          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
 684  
 685          // Check that the user is initially unsubscribed to that discussion.
 686          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 687  
 688          // Then subscribe them to the discussion.
 689          $this->assertTrue(\mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion));
 690  
 691          // An attempt to subscribe again should result in a falsey return to indicate that no change was made.
 692          $this->assertFalse(\mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion));
 693  
 694          // Check that the user is still unsubscribed from the forum.
 695          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 696  
 697          // But subscribed to the discussion.
 698          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 699  
 700          // There should be a record in the discussion subscription tracking table.
 701          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 702              'userid'        => $author->id,
 703              'discussion'    => $discussion->id,
 704          )));
 705  
 706          // Now unsubscribe the user again from the discussion.
 707          \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion);
 708  
 709          // Check that the user is still unsubscribed from the forum.
 710          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 711  
 712          // And is unsubscribed from the discussion again.
 713          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 714  
 715          // There should be no record in the discussion subscription tracking table.
 716          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
 717              'userid'        => $author->id,
 718              'discussion'    => $discussion->id,
 719          )));
 720  
 721          // And subscribe the user again to the discussion.
 722          \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion);
 723  
 724          // Check that the user is still unsubscribed from the forum.
 725          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 726  
 727          // And is subscribed to the discussion again.
 728          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 729  
 730          // There should be a record in the discussion subscription tracking table.
 731          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
 732              'userid'        => $author->id,
 733              'discussion'    => $discussion->id,
 734          )));
 735  
 736          // And unsubscribe again.
 737          \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion);
 738  
 739          // Check that the user is still unsubscribed from the forum.
 740          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum));
 741  
 742          // But unsubscribed from the discussion.
 743          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id));
 744  
 745          // There should be no record in the discussion subscription tracking table.
 746          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
 747              'userid'        => $author->id,
 748              'discussion'    => $discussion->id,
 749          )));
 750      }
 751  
 752      /**
 753       * Test that the correct users are returned when fetching subscribed users from a forum where users can choose to
 754       * subscribe and unsubscribe.
 755       */
 756      public function test_fetch_subscribed_users_subscriptions() {
 757          global $DB, $CFG;
 758  
 759          $this->resetAfterTest(true);
 760  
 761          // Create a course, with a forum. where users are initially subscribed.
 762          $course = $this->getDataGenerator()->create_course();
 763          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
 764          $forum = $this->getDataGenerator()->create_module('forum', $options);
 765  
 766          // Create some user enrolled in the course as a student.
 767          $usercount = 5;
 768          $users = $this->helper_create_users($course, $usercount);
 769  
 770          // All users should be subscribed.
 771          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum);
 772          $this->assertEquals($usercount, count($subscribers));
 773  
 774          // Subscribe the guest user too to the forum - they should never be returned by this function.
 775          $this->getDataGenerator()->enrol_user($CFG->siteguest, $course->id);
 776          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum);
 777          $this->assertEquals($usercount, count($subscribers));
 778  
 779          // Unsubscribe 2 users.
 780          $unsubscribedcount = 2;
 781          for ($i = 0; $i < $unsubscribedcount; $i++) {
 782              \mod_forum\subscriptions::unsubscribe_user($users[$i]->id, $forum);
 783          }
 784  
 785          // The subscription count should now take into account those users who have been unsubscribed.
 786          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum);
 787          $this->assertEquals($usercount - $unsubscribedcount, count($subscribers));
 788      }
 789  
 790      /**
 791       * Test that the correct users are returned hwen fetching subscribed users from a forum where users are forcibly
 792       * subscribed.
 793       */
 794      public function test_fetch_subscribed_users_forced() {
 795          global $DB;
 796  
 797          $this->resetAfterTest(true);
 798  
 799          // Create a course, with a forum. where users are initially subscribed.
 800          $course = $this->getDataGenerator()->create_course();
 801          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
 802          $forum = $this->getDataGenerator()->create_module('forum', $options);
 803  
 804          // Create some user enrolled in the course as a student.
 805          $usercount = 5;
 806          $users = $this->helper_create_users($course, $usercount);
 807  
 808          // All users should be subscribed.
 809          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum);
 810          $this->assertEquals($usercount, count($subscribers));
 811      }
 812  
 813      /**
 814       * Test that unusual combinations of discussion subscriptions do not affect the subscribed user list.
 815       */
 816      public function test_fetch_subscribed_users_discussion_subscriptions() {
 817          global $DB;
 818  
 819          $this->resetAfterTest(true);
 820  
 821          // Create a course, with a forum. where users are initially subscribed.
 822          $course = $this->getDataGenerator()->create_course();
 823          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
 824          $forum = $this->getDataGenerator()->create_module('forum', $options);
 825  
 826          // Create some user enrolled in the course as a student.
 827          $usercount = 5;
 828          $users = $this->helper_create_users($course, $usercount);
 829  
 830          list($discussion, $post) = $this->helper_post_to_forum($forum, $users[0]);
 831  
 832          // All users should be subscribed.
 833          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum);
 834          $this->assertEquals($usercount, count($subscribers));
 835          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, null, null, true);
 836          $this->assertEquals($usercount, count($subscribers));
 837  
 838          \mod_forum\subscriptions::unsubscribe_user_from_discussion($users[0]->id, $discussion);
 839  
 840          // All users should be subscribed.
 841          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum);
 842          $this->assertEquals($usercount, count($subscribers));
 843  
 844          // All users should be subscribed.
 845          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, null, null, true);
 846          $this->assertEquals($usercount, count($subscribers));
 847  
 848          // Manually insert an extra subscription for one of the users.
 849          $record = new \stdClass();
 850          $record->userid = $users[2]->id;
 851          $record->forum = $forum->id;
 852          $record->discussion = $discussion->id;
 853          $record->preference = time();
 854          $DB->insert_record('forum_discussion_subs', $record);
 855  
 856          // The discussion count should not have changed.
 857          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum);
 858          $this->assertEquals($usercount, count($subscribers));
 859          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, null, null, true);
 860          $this->assertEquals($usercount, count($subscribers));
 861  
 862          // Unsubscribe 2 users.
 863          $unsubscribedcount = 2;
 864          for ($i = 0; $i < $unsubscribedcount; $i++) {
 865              \mod_forum\subscriptions::unsubscribe_user($users[$i]->id, $forum);
 866          }
 867  
 868          // The subscription count should now take into account those users who have been unsubscribed.
 869          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum);
 870          $this->assertEquals($usercount - $unsubscribedcount, count($subscribers));
 871          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, null, null, true);
 872          $this->assertEquals($usercount - $unsubscribedcount, count($subscribers));
 873  
 874          // Now subscribe one of those users back to the discussion.
 875          $subscribeddiscussionusers = 1;
 876          for ($i = 0; $i < $subscribeddiscussionusers; $i++) {
 877              \mod_forum\subscriptions::subscribe_user_to_discussion($users[$i]->id, $discussion);
 878          }
 879          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum);
 880          $this->assertEquals($usercount - $unsubscribedcount, count($subscribers));
 881          $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, null, null, true);
 882          $this->assertEquals($usercount - $unsubscribedcount + $subscribeddiscussionusers, count($subscribers));
 883      }
 884  
 885      /**
 886       * Test whether a user is force-subscribed to a forum.
 887       */
 888      public function test_force_subscribed_to_forum() {
 889          global $DB;
 890  
 891          $this->resetAfterTest(true);
 892  
 893          // Create a course, with a forum.
 894          $course = $this->getDataGenerator()->create_course();
 895  
 896          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
 897          $forum = $this->getDataGenerator()->create_module('forum', $options);
 898  
 899          // Create a user enrolled in the course as a student.
 900          $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
 901          $user = $this->getDataGenerator()->create_user();
 902          $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleids['student']);
 903  
 904          // Check that the user is currently subscribed to the forum.
 905          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum));
 906  
 907          // Remove the allowforcesubscribe capability from the user.
 908          $cm = get_coursemodule_from_instance('forum', $forum->id);
 909          $context = \context_module::instance($cm->id);
 910          assign_capability('mod/forum:allowforcesubscribe', CAP_PROHIBIT, $roleids['student'], $context);
 911          $this->assertFalse(has_capability('mod/forum:allowforcesubscribe', $context, $user->id));
 912  
 913          // Check that the user is no longer subscribed to the forum.
 914          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum));
 915      }
 916  
 917      /**
 918       * Test that the subscription cache can be pre-filled.
 919       */
 920      public function test_subscription_cache_prefill() {
 921          global $DB;
 922  
 923          $this->resetAfterTest(true);
 924  
 925          // Create a course, with a forum.
 926          $course = $this->getDataGenerator()->create_course();
 927  
 928          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
 929          $forum = $this->getDataGenerator()->create_module('forum', $options);
 930  
 931          // Create some users.
 932          $users = $this->helper_create_users($course, 20);
 933  
 934          // Reset the subscription cache.
 935          \mod_forum\subscriptions::reset_forum_cache();
 936  
 937          // Filling the subscription cache should use a query.
 938          $startcount = $DB->perf_get_reads();
 939          $this->assertNull(\mod_forum\subscriptions::fill_subscription_cache($forum->id));
 940          $postfillcount = $DB->perf_get_reads();
 941          $this->assertNotEquals($postfillcount, $startcount);
 942  
 943          // Now fetch some subscriptions from that forum - these should use
 944          // the cache and not perform additional queries.
 945          foreach ($users as $user) {
 946              $this->assertTrue(\mod_forum\subscriptions::fetch_subscription_cache($forum->id, $user->id));
 947          }
 948          $finalcount = $DB->perf_get_reads();
 949          $this->assertEquals(0, $finalcount - $postfillcount);
 950      }
 951  
 952      /**
 953       * Test that the subscription cache can filled user-at-a-time.
 954       */
 955      public function test_subscription_cache_fill() {
 956          global $DB;
 957  
 958          $this->resetAfterTest(true);
 959  
 960          // Create a course, with a forum.
 961          $course = $this->getDataGenerator()->create_course();
 962  
 963          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
 964          $forum = $this->getDataGenerator()->create_module('forum', $options);
 965  
 966          // Create some users.
 967          $users = $this->helper_create_users($course, 20);
 968  
 969          // Reset the subscription cache.
 970          \mod_forum\subscriptions::reset_forum_cache();
 971  
 972          // Filling the subscription cache should only use a single query.
 973          $startcount = $DB->perf_get_reads();
 974  
 975          // Fetch some subscriptions from that forum - these should not use the cache and will perform additional queries.
 976          foreach ($users as $user) {
 977              $this->assertTrue(\mod_forum\subscriptions::fetch_subscription_cache($forum->id, $user->id));
 978          }
 979          $finalcount = $DB->perf_get_reads();
 980          $this->assertEquals(20, $finalcount - $startcount);
 981      }
 982  
 983      /**
 984       * Test that the discussion subscription cache can filled course-at-a-time.
 985       */
 986      public function test_discussion_subscription_cache_fill_for_course() {
 987          global $DB;
 988  
 989          $this->resetAfterTest(true);
 990  
 991          // Create a course, with a forum.
 992          $course = $this->getDataGenerator()->create_course();
 993  
 994          // Create the forums.
 995          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_DISALLOWSUBSCRIBE);
 996          $disallowforum = $this->getDataGenerator()->create_module('forum', $options);
 997          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
 998          $chooseforum = $this->getDataGenerator()->create_module('forum', $options);
 999          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
1000          $initialforum = $this->getDataGenerator()->create_module('forum', $options);
1001  
1002          // Create some users and keep a reference to the first user.
1003          $users = $this->helper_create_users($course, 20);
1004          $user = reset($users);
1005  
1006          // Reset the subscription caches.
1007          \mod_forum\subscriptions::reset_forum_cache();
1008  
1009          $startcount = $DB->perf_get_reads();
1010          $result = \mod_forum\subscriptions::fill_subscription_cache_for_course($course->id, $user->id);
1011          $this->assertNull($result);
1012          $postfillcount = $DB->perf_get_reads();
1013          $this->assertNotEquals($postfillcount, $startcount);
1014          $this->assertFalse(\mod_forum\subscriptions::fetch_subscription_cache($disallowforum->id, $user->id));
1015          $this->assertFalse(\mod_forum\subscriptions::fetch_subscription_cache($chooseforum->id, $user->id));
1016          $this->assertTrue(\mod_forum\subscriptions::fetch_subscription_cache($initialforum->id, $user->id));
1017          $finalcount = $DB->perf_get_reads();
1018          $this->assertEquals(0, $finalcount - $postfillcount);
1019  
1020          // Test for all users.
1021          foreach ($users as $user) {
1022              $result = \mod_forum\subscriptions::fill_subscription_cache_for_course($course->id, $user->id);
1023              $this->assertFalse(\mod_forum\subscriptions::fetch_subscription_cache($disallowforum->id, $user->id));
1024              $this->assertFalse(\mod_forum\subscriptions::fetch_subscription_cache($chooseforum->id, $user->id));
1025              $this->assertTrue(\mod_forum\subscriptions::fetch_subscription_cache($initialforum->id, $user->id));
1026          }
1027          $finalcount = $DB->perf_get_reads();
1028          $this->assertNotEquals($finalcount, $postfillcount);
1029      }
1030  
1031      /**
1032       * Test that the discussion subscription cache can be forcibly updated for a user.
1033       */
1034      public function test_discussion_subscription_cache_prefill() {
1035          global $DB;
1036  
1037          $this->resetAfterTest(true);
1038  
1039          // Create a course, with a forum.
1040          $course = $this->getDataGenerator()->create_course();
1041  
1042          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
1043          $forum = $this->getDataGenerator()->create_module('forum', $options);
1044  
1045          // Create some users.
1046          $users = $this->helper_create_users($course, 20);
1047  
1048          // Post some discussions to the forum.
1049          $discussions = array();
1050          $author = $users[0];
1051          $userwithnosubs = $users[1];
1052  
1053          for ($i = 0; $i < 20; $i++) {
1054              list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
1055              $discussions[] = $discussion;
1056          }
1057  
1058          // Unsubscribe half the users from the half the discussions.
1059          $forumcount = 0;
1060          $usercount = 0;
1061          $userwithsubs = null;
1062          foreach ($discussions as $data) {
1063              // Unsubscribe user from all discussions.
1064              \mod_forum\subscriptions::unsubscribe_user_from_discussion($userwithnosubs->id, $data);
1065  
1066              if ($forumcount % 2) {
1067                  continue;
1068              }
1069              foreach ($users as $user) {
1070                  if ($usercount % 2) {
1071                      $userwithsubs = $user;
1072                      continue;
1073                  }
1074                  \mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $data);
1075                  $usercount++;
1076              }
1077              $forumcount++;
1078          }
1079  
1080          // Reset the subscription caches.
1081          \mod_forum\subscriptions::reset_forum_cache();
1082          \mod_forum\subscriptions::reset_discussion_cache();
1083  
1084          // A user with no subscriptions should only be fetched once.
1085          $this->assertNull(\mod_forum\subscriptions::fill_discussion_subscription_cache($forum->id, $userwithnosubs->id));
1086          $startcount = $DB->perf_get_reads();
1087          $this->assertNull(\mod_forum\subscriptions::fill_discussion_subscription_cache($forum->id, $userwithnosubs->id));
1088          $this->assertEquals($startcount, $DB->perf_get_reads());
1089  
1090          // Confirm subsequent calls properly tries to fetch subs.
1091          $this->assertNull(\mod_forum\subscriptions::fill_discussion_subscription_cache($forum->id, $userwithsubs->id));
1092          $this->assertNotEquals($startcount, $DB->perf_get_reads());
1093  
1094          // Another read should be performed to get all subscriptions for the forum.
1095          $startcount = $DB->perf_get_reads();
1096          $this->assertNull(\mod_forum\subscriptions::fill_discussion_subscription_cache($forum->id));
1097          $this->assertNotEquals($startcount, $DB->perf_get_reads());
1098  
1099          // Reset the subscription caches.
1100          \mod_forum\subscriptions::reset_forum_cache();
1101          \mod_forum\subscriptions::reset_discussion_cache();
1102  
1103          // Filling the discussion subscription cache should only use a single query.
1104          $startcount = $DB->perf_get_reads();
1105          $this->assertNull(\mod_forum\subscriptions::fill_discussion_subscription_cache($forum->id));
1106          $postfillcount = $DB->perf_get_reads();
1107          $this->assertNotEquals($postfillcount, $startcount);
1108  
1109          // Now fetch some subscriptions from that forum - these should use
1110          // the cache and not perform additional queries.
1111          foreach ($users as $user) {
1112              $result = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $user->id);
1113              $this->assertIsArray($result);
1114          }
1115          $finalcount = $DB->perf_get_reads();
1116          $this->assertEquals(0, $finalcount - $postfillcount);
1117      }
1118  
1119      /**
1120       * Test that the discussion subscription cache can filled user-at-a-time.
1121       */
1122      public function test_discussion_subscription_cache_fill() {
1123          global $DB;
1124  
1125          $this->resetAfterTest(true);
1126  
1127          // Create a course, with a forum.
1128          $course = $this->getDataGenerator()->create_course();
1129  
1130          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
1131          $forum = $this->getDataGenerator()->create_module('forum', $options);
1132  
1133          // Create some users.
1134          $users = $this->helper_create_users($course, 20);
1135  
1136          // Post some discussions to the forum.
1137          $discussions = array();
1138          $author = $users[0];
1139          for ($i = 0; $i < 20; $i++) {
1140              list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
1141              $discussions[] = $discussion;
1142          }
1143  
1144          // Unsubscribe half the users from the half the discussions.
1145          $forumcount = 0;
1146          $usercount = 0;
1147          foreach ($discussions as $data) {
1148              if ($forumcount % 2) {
1149                  continue;
1150              }
1151              foreach ($users as $user) {
1152                  if ($usercount % 2) {
1153                      continue;
1154                  }
1155                  \mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion);
1156                  $usercount++;
1157              }
1158              $forumcount++;
1159          }
1160  
1161          // Reset the subscription caches.
1162          \mod_forum\subscriptions::reset_forum_cache();
1163          \mod_forum\subscriptions::reset_discussion_cache();
1164  
1165          $startcount = $DB->perf_get_reads();
1166  
1167          // Now fetch some subscriptions from that forum - these should use
1168          // the cache and not perform additional queries.
1169          foreach ($users as $user) {
1170              $result = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $user->id);
1171              $this->assertIsArray($result);
1172          }
1173          $finalcount = $DB->perf_get_reads();
1174          $this->assertNotEquals($finalcount, $startcount);
1175      }
1176  
1177      /**
1178       * Test that after toggling the forum subscription as another user,
1179       * the discussion subscription functionality works as expected.
1180       */
1181      public function test_forum_subscribe_toggle_as_other_repeat_subscriptions() {
1182          global $DB;
1183  
1184          $this->resetAfterTest(true);
1185  
1186          // Create a course, with a forum.
1187          $course = $this->getDataGenerator()->create_course();
1188  
1189          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE);
1190          $forum = $this->getDataGenerator()->create_module('forum', $options);
1191  
1192          // Create a user enrolled in the course as a student.
1193          list($user) = $this->helper_create_users($course, 1);
1194  
1195          // Post a discussion to the forum.
1196          list($discussion, $post) = $this->helper_post_to_forum($forum, $user);
1197  
1198          // Confirm that the user is currently not subscribed to the forum.
1199          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum));
1200  
1201          // Confirm that the user is unsubscribed from the discussion too.
1202          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion->id));
1203  
1204          // Confirm that we have no records in either of the subscription tables.
1205          $this->assertEquals(0, $DB->count_records('forum_subscriptions', array(
1206              'userid'        => $user->id,
1207              'forum'         => $forum->id,
1208          )));
1209          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
1210              'userid'        => $user->id,
1211              'discussion'    => $discussion->id,
1212          )));
1213  
1214          // Subscribing to the forum should create a record in the subscriptions table, but not the forum discussion
1215          // subscriptions table.
1216          \mod_forum\subscriptions::subscribe_user($user->id, $forum);
1217          $this->assertEquals(1, $DB->count_records('forum_subscriptions', array(
1218              'userid'        => $user->id,
1219              'forum'         => $forum->id,
1220          )));
1221          $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array(
1222              'userid'        => $user->id,
1223              'discussion'    => $discussion->id,
1224          )));
1225  
1226          // Now unsubscribe from the discussion. This should return true.
1227          $this->assertTrue(\mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion));
1228  
1229          // Attempting to unsubscribe again should return false because no change was made.
1230          $this->assertFalse(\mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion));
1231  
1232          // Subscribing to the discussion again should return truthfully as the subscription preference was removed.
1233          $this->assertTrue(\mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $discussion));
1234  
1235          // Attempting to subscribe again should return false because no change was made.
1236          $this->assertFalse(\mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $discussion));
1237  
1238          // Now unsubscribe from the discussion. This should return true once more.
1239          $this->assertTrue(\mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion));
1240  
1241          // And unsubscribing from the forum but not as a request from the user should maintain their preference.
1242          \mod_forum\subscriptions::unsubscribe_user($user->id, $forum);
1243  
1244          $this->assertEquals(0, $DB->count_records('forum_subscriptions', array(
1245              'userid'        => $user->id,
1246              'forum'         => $forum->id,
1247          )));
1248          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
1249              'userid'        => $user->id,
1250              'discussion'    => $discussion->id,
1251          )));
1252  
1253          // Subscribing to the discussion should return truthfully because a change was made.
1254          $this->assertTrue(\mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $discussion));
1255          $this->assertEquals(0, $DB->count_records('forum_subscriptions', array(
1256              'userid'        => $user->id,
1257              'forum'         => $forum->id,
1258          )));
1259          $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array(
1260              'userid'        => $user->id,
1261              'discussion'    => $discussion->id,
1262          )));
1263      }
1264  
1265      /**
1266       * Test that providing a context_module instance to is_subscribed does not result in additional lookups to retrieve
1267       * the context_module.
1268       */
1269      public function test_is_subscribed_cm() {
1270          global $DB;
1271  
1272          $this->resetAfterTest(true);
1273  
1274          // Create a course, with a forum.
1275          $course = $this->getDataGenerator()->create_course();
1276  
1277          $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
1278          $forum = $this->getDataGenerator()->create_module('forum', $options);
1279  
1280          // Create a user enrolled in the course as a student.
1281          list($user) = $this->helper_create_users($course, 1);
1282  
1283          // Retrieve the $cm now.
1284          $cm = get_fast_modinfo($forum->course)->instances['forum'][$forum->id];
1285  
1286          // Reset get_fast_modinfo.
1287          get_fast_modinfo(0, 0, true);
1288  
1289          // Call is_subscribed without passing the $cmid - this should result in a lookup and filling of some of the
1290          // caches. This provides us with consistent data to start from.
1291          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum));
1292          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum));
1293  
1294          // Make a note of the number of DB calls.
1295          $basecount = $DB->perf_get_reads();
1296  
1297          // Call is_subscribed - it should give return the correct result (False), and result in no additional queries.
1298          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, null, $cm));
1299  
1300          // The capability check does require some queries, so we don't test it directly.
1301          // We don't assert here because this is dependant upon linked code which could change at any time.
1302          $suppliedcmcount = $DB->perf_get_reads() - $basecount;
1303  
1304          // Call is_subscribed without passing the $cmid now - this should result in a lookup.
1305          get_fast_modinfo(0, 0, true);
1306          $basecount = $DB->perf_get_reads();
1307          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum));
1308          $calculatedcmcount = $DB->perf_get_reads() - $basecount;
1309  
1310          // There should be more queries than when we performed the same check a moment ago.
1311          $this->assertGreaterThan($suppliedcmcount, $calculatedcmcount);
1312      }
1313  
1314      public function is_subscribable_forums() {
1315          return [
1316              [
1317                  'forcesubscribe' => FORUM_DISALLOWSUBSCRIBE,
1318              ],
1319              [
1320                  'forcesubscribe' => FORUM_CHOOSESUBSCRIBE,
1321              ],
1322              [
1323                  'forcesubscribe' => FORUM_INITIALSUBSCRIBE,
1324              ],
1325              [
1326                  'forcesubscribe' => FORUM_FORCESUBSCRIBE,
1327              ],
1328          ];
1329      }
1330  
1331      public function is_subscribable_provider() {
1332          $data = [];
1333          foreach ($this->is_subscribable_forums() as $forum) {
1334              $data[] = [$forum];
1335          }
1336  
1337          return $data;
1338      }
1339  
1340      /**
1341       * @dataProvider is_subscribable_provider
1342       */
1343      public function test_is_subscribable_logged_out($options) {
1344          $this->resetAfterTest(true);
1345  
1346          // Create a course, with a forum.
1347          $course = $this->getDataGenerator()->create_course();
1348          $options['course'] = $course->id;
1349          $forum = $this->getDataGenerator()->create_module('forum', $options);
1350  
1351          $this->assertFalse(\mod_forum\subscriptions::is_subscribable($forum));
1352      }
1353  
1354      /**
1355       * @dataProvider is_subscribable_provider
1356       */
1357      public function test_is_subscribable_is_guest($options) {
1358          global $DB;
1359          $this->resetAfterTest(true);
1360  
1361          $guest = $DB->get_record('user', array('username'=>'guest'));
1362          $this->setUser($guest);
1363  
1364          // Create a course, with a forum.
1365          $course = $this->getDataGenerator()->create_course();
1366          $options['course'] = $course->id;
1367          $forum = $this->getDataGenerator()->create_module('forum', $options);
1368  
1369          $this->assertFalse(\mod_forum\subscriptions::is_subscribable($forum));
1370      }
1371  
1372      public function is_subscribable_loggedin_provider() {
1373          return [
1374              [
1375                  ['forcesubscribe' => FORUM_DISALLOWSUBSCRIBE],
1376                  false,
1377              ],
1378              [
1379                  ['forcesubscribe' => FORUM_CHOOSESUBSCRIBE],
1380                  true,
1381              ],
1382              [
1383                  ['forcesubscribe' => FORUM_INITIALSUBSCRIBE],
1384                  true,
1385              ],
1386              [
1387                  ['forcesubscribe' => FORUM_FORCESUBSCRIBE],
1388                  false,
1389              ],
1390          ];
1391      }
1392  
1393      /**
1394       * @dataProvider is_subscribable_loggedin_provider
1395       */
1396      public function test_is_subscribable_loggedin($options, $expect) {
1397          $this->resetAfterTest(true);
1398  
1399          // Create a course, with a forum.
1400          $course = $this->getDataGenerator()->create_course();
1401          $options['course'] = $course->id;
1402          $forum = $this->getDataGenerator()->create_module('forum', $options);
1403  
1404          $user = $this->getDataGenerator()->create_user();
1405          $this->getDataGenerator()->enrol_user($user->id, $course->id);
1406          $this->setUser($user);
1407  
1408          $this->assertEquals($expect, \mod_forum\subscriptions::is_subscribable($forum));
1409      }
1410  
1411      public function test_get_user_default_subscription() {
1412          global $DB;
1413          $this->resetAfterTest(true);
1414  
1415          // Create a course, with a forum.
1416          $course = $this->getDataGenerator()->create_course();
1417          $context = \context_course::instance($course->id);
1418          $options['course'] = $course->id;
1419          $forum = $this->getDataGenerator()->create_module('forum', $options);
1420          $cm = get_coursemodule_from_instance("forum", $forum->id, $course->id);
1421  
1422          // Create a user enrolled in the course as a student.
1423          list($author, $student) = $this->helper_create_users($course, 2, 'student');
1424          // Post a discussion to the forum.
1425          list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
1426  
1427          // A guest user.
1428          $this->setUser(0);
1429          $this->assertFalse((boolean)\mod_forum\subscriptions::get_user_default_subscription($forum, $context, $cm, $discussion->id));
1430          $this->assertFalse((boolean)\mod_forum\subscriptions::get_user_default_subscription($forum, $context, $cm, null));
1431  
1432          // A user enrolled in the course.
1433          $this->setUser($author->id);
1434          $this->assertTrue((boolean)\mod_forum\subscriptions::get_user_default_subscription($forum, $context, $cm, $discussion->id));
1435          $this->assertTrue((boolean)\mod_forum\subscriptions::get_user_default_subscription($forum, $context, $cm, null));
1436  
1437          // Subscribption disabled.
1438          $this->setUser($student->id);
1439          \mod_forum\subscriptions::set_subscription_mode($forum->id, FORUM_DISALLOWSUBSCRIBE);
1440          $forum = $DB->get_record('forum', array('id' => $forum->id));
1441          $this->assertFalse((boolean)\mod_forum\subscriptions::get_user_default_subscription($forum, $context, $cm, $discussion->id));
1442          $this->assertFalse((boolean)\mod_forum\subscriptions::get_user_default_subscription($forum, $context, $cm, null));
1443  
1444          \mod_forum\subscriptions::set_subscription_mode($forum->id, FORUM_FORCESUBSCRIBE);
1445          $forum = $DB->get_record('forum', array('id' => $forum->id));
1446          $this->assertTrue((boolean)\mod_forum\subscriptions::get_user_default_subscription($forum, $context, $cm, $discussion->id));
1447          $this->assertTrue((boolean)\mod_forum\subscriptions::get_user_default_subscription($forum, $context, $cm, null));
1448  
1449          // Admin user.
1450          $this->setAdminUser();
1451          $this->assertTrue((boolean)\mod_forum\subscriptions::get_user_default_subscription($forum, $context, $cm, $discussion->id));
1452          $this->assertTrue((boolean)\mod_forum\subscriptions::get_user_default_subscription($forum, $context, $cm, null));
1453      }
1454  }