Search moodle.org's
Developer Documentation

See Release Notes

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

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Forum subscription manager.
  19   *
  20   * @package    mod_forum
  21   * @copyright  2014 Andrew Nicols <andrew@nicols.co.uk>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace mod_forum;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * Forum subscription manager.
  31   *
  32   * @copyright  2014 Andrew Nicols <andrew@nicols.co.uk>
  33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  class subscriptions {
  36  
  37      /**
  38       * The status value for an unsubscribed discussion.
  39       *
  40       * @var int
  41       */
  42      const FORUM_DISCUSSION_UNSUBSCRIBED = -1;
  43  
  44      /**
  45       * The subscription cache for forums.
  46       *
  47       * The first level key is the user ID
  48       * The second level is the forum ID
  49       * The Value then is bool for subscribed of not.
  50       *
  51       * @var array[] An array of arrays.
  52       */
  53      protected static $forumcache = array();
  54  
  55      /**
  56       * The list of forums which have been wholly retrieved for the forum subscription cache.
  57       *
  58       * This allows for prior caching of an entire forum to reduce the
  59       * number of DB queries in a subscription check loop.
  60       *
  61       * @var bool[]
  62       */
  63      protected static $fetchedforums = array();
  64  
  65      /**
  66       * The subscription cache for forum discussions.
  67       *
  68       * The first level key is the user ID
  69       * The second level is the forum ID
  70       * The third level key is the discussion ID
  71       * The value is then the users preference (int)
  72       *
  73       * @var array[]
  74       */
  75      protected static $forumdiscussioncache = array();
  76  
  77      /**
  78       * The list of forums which have been wholly retrieved for the forum discussion subscription cache.
  79       *
  80       * This allows for prior caching of an entire forum to reduce the
  81       * number of DB queries in a subscription check loop.
  82       *
  83       * @var bool[]
  84       */
  85      protected static $discussionfetchedforums = array();
  86  
  87      /**
  88       * Whether a user is subscribed to this forum, or a discussion within
  89       * the forum.
  90       *
  91       * If a discussion is specified, then report whether the user is
  92       * subscribed to posts to this particular discussion, taking into
  93       * account the forum preference.
  94       *
  95       * If it is not specified then only the forum preference is considered.
  96       *
  97       * @param int $userid The user ID
  98       * @param \stdClass $forum The record of the forum to test
  99       * @param int $discussionid The ID of the discussion to check
 100       * @param $cm The coursemodule record. If not supplied, this will be calculated using get_fast_modinfo instead.
 101       * @return boolean
 102       */
 103      public static function is_subscribed($userid, $forum, $discussionid = null, $cm = null) {
 104          // If forum is force subscribed and has allowforcesubscribe, then user is subscribed.
 105          if (self::is_forcesubscribed($forum)) {
 106              if (!$cm) {
 107                  $cm = get_fast_modinfo($forum->course)->instances['forum'][$forum->id];
 108              }
 109              if (has_capability('mod/forum:allowforcesubscribe', \context_module::instance($cm->id), $userid)) {
 110                  return true;
 111              }
 112          }
 113  
 114          if ($discussionid === null) {
 115              return self::is_subscribed_to_forum($userid, $forum);
 116          }
 117  
 118          $subscriptions = self::fetch_discussion_subscription($forum->id, $userid);
 119  
 120          // Check whether there is a record for this discussion subscription.
 121          if (isset($subscriptions[$discussionid])) {
 122              return ($subscriptions[$discussionid] != self::FORUM_DISCUSSION_UNSUBSCRIBED);
 123          }
 124  
 125          return self::is_subscribed_to_forum($userid, $forum);
 126      }
 127  
 128      /**
 129       * Whether a user is subscribed to this forum.
 130       *
 131       * @param int $userid The user ID
 132       * @param \stdClass $forum The record of the forum to test
 133       * @return boolean
 134       */
 135      protected static function is_subscribed_to_forum($userid, $forum) {
 136          return self::fetch_subscription_cache($forum->id, $userid);
 137      }
 138  
 139      /**
 140       * Helper to determine whether a forum has it's subscription mode set
 141       * to forced subscription.
 142       *
 143       * @param \stdClass $forum The record of the forum to test
 144       * @return bool
 145       */
 146      public static function is_forcesubscribed($forum) {
 147          return ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE);
 148      }
 149  
 150      /**
 151       * Helper to determine whether a forum has it's subscription mode set to disabled.
 152       *
 153       * @param \stdClass $forum The record of the forum to test
 154       * @return bool
 155       */
 156      public static function subscription_disabled($forum) {
 157          return ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE);
 158      }
 159  
 160      /**
 161       * Helper to determine whether the specified forum can be subscribed to.
 162       *
 163       * @param \stdClass $forum The record of the forum to test
 164       * @return bool
 165       */
 166      public static function is_subscribable($forum) {
 167          return (isloggedin() && !isguestuser() &&
 168                  !\mod_forum\subscriptions::is_forcesubscribed($forum) &&
 169                  !\mod_forum\subscriptions::subscription_disabled($forum));
 170      }
 171  
 172      /**
 173       * Set the forum subscription mode.
 174       *
 175       * By default when called without options, this is set to FORUM_FORCESUBSCRIBE.
 176       *
 177       * @param \stdClass $forum The record of the forum to set
 178       * @param int $status The new subscription state
 179       * @return bool
 180       */
 181      public static function set_subscription_mode($forumid, $status = 1) {
 182          global $DB;
 183          return $DB->set_field("forum", "forcesubscribe", $status, array("id" => $forumid));
 184      }
 185  
 186      /**
 187       * Returns the current subscription mode for the forum.
 188       *
 189       * @param \stdClass $forum The record of the forum to set
 190       * @return int The forum subscription mode
 191       */
 192      public static function get_subscription_mode($forum) {
 193          return $forum->forcesubscribe;
 194      }
 195  
 196      /**
 197       * Returns an array of forums that the current user is subscribed to and is allowed to unsubscribe from
 198       *
 199       * @return array An array of unsubscribable forums
 200       */
 201      public static function get_unsubscribable_forums() {
 202          global $USER, $DB;
 203  
 204          // Get courses that $USER is enrolled in and can see.
 205          $courses = enrol_get_my_courses();
 206          if (empty($courses)) {
 207              return array();
 208          }
 209  
 210          $courseids = array();
 211          foreach($courses as $course) {
 212              $courseids[] = $course->id;
 213          }
 214          list($coursesql, $courseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'c');
 215  
 216          // Get all forums from the user's courses that they are subscribed to and which are not set to forced.
 217          // It is possible for users to be subscribed to a forum in subscription disallowed mode so they must be listed
 218          // here so that that can be unsubscribed from.
 219          $sql = "SELECT f.id, cm.id as cm, cm.visible, f.course
 220                  FROM {forum} f
 221                  JOIN {course_modules} cm ON cm.instance = f.id
 222                  JOIN {modules} m ON m.name = :modulename AND m.id = cm.module
 223                  LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
 224                  WHERE f.forcesubscribe <> :forcesubscribe
 225                  AND fs.id IS NOT NULL
 226                  AND cm.course
 227                  $coursesql";
 228          $params = array_merge($courseparams, array(
 229              'modulename'=>'forum',
 230              'userid' => $USER->id,
 231              'forcesubscribe' => FORUM_FORCESUBSCRIBE,
 232          ));
 233          $forums = $DB->get_recordset_sql($sql, $params);
 234  
 235          $unsubscribableforums = array();
 236          foreach($forums as $forum) {
 237              if (empty($forum->visible)) {
 238                  // The forum is hidden - check if the user can view the forum.
 239                  $context = \context_module::instance($forum->cm);
 240                  if (!has_capability('moodle/course:viewhiddenactivities', $context)) {
 241                      // The user can't see the hidden forum to cannot unsubscribe.
 242                      continue;
 243                  }
 244              }
 245  
 246              $unsubscribableforums[] = $forum;
 247          }
 248          $forums->close();
 249  
 250          return $unsubscribableforums;
 251      }
 252  
 253      /**
 254       * Get the list of potential subscribers to a forum.
 255       *
 256       * @param context_module $context the forum context.
 257       * @param integer $groupid the id of a group, or 0 for all groups.
 258       * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
 259       * @param string $sort sort order. As for get_users_by_capability.
 260       * @return array list of users.
 261       */
 262      public static function get_potential_subscribers($context, $groupid, $fields, $sort = '') {
 263          global $DB;
 264  
 265          // Only active enrolled users or everybody on the frontpage.
 266          list($esql, $params) = get_enrolled_sql($context, 'mod/forum:allowforcesubscribe', $groupid, true);
 267          if (!$sort) {
 268              list($sort, $sortparams) = users_order_by_sql('u');
 269              $params = array_merge($params, $sortparams);
 270          }
 271  
 272          $sql = "SELECT $fields
 273                  FROM {user} u
 274                  JOIN ($esql) je ON je.id = u.id
 275                 WHERE u.auth <> 'nologin' AND u.suspended = 0 AND u.confirmed = 1
 276              ORDER BY $sort";
 277  
 278          return $DB->get_records_sql($sql, $params);
 279      }
 280  
 281      /**
 282       * Fetch the forum subscription data for the specified userid and forum.
 283       *
 284       * @param int $forumid The forum to retrieve a cache for
 285       * @param int $userid The user ID
 286       * @return boolean
 287       */
 288      public static function fetch_subscription_cache($forumid, $userid) {
 289          if (isset(self::$forumcache[$userid]) && isset(self::$forumcache[$userid][$forumid])) {
 290              return self::$forumcache[$userid][$forumid];
 291          }
 292          self::fill_subscription_cache($forumid, $userid);
 293  
 294          if (!isset(self::$forumcache[$userid]) || !isset(self::$forumcache[$userid][$forumid])) {
 295              return false;
 296          }
 297  
 298          return self::$forumcache[$userid][$forumid];
 299      }
 300  
 301      /**
 302       * Fill the forum subscription data for the specified userid and forum.
 303       *
 304       * If the userid is not specified, then all subscription data for that forum is fetched in a single query and used
 305       * for subsequent lookups without requiring further database queries.
 306       *
 307       * @param int $forumid The forum to retrieve a cache for
 308       * @param int $userid The user ID
 309       * @return void
 310       */
 311      public static function fill_subscription_cache($forumid, $userid = null) {
 312          global $DB;
 313  
 314          if (!isset(self::$fetchedforums[$forumid])) {
 315              // This forum has not been fetched as a whole.
 316              if (isset($userid)) {
 317                  if (!isset(self::$forumcache[$userid])) {
 318                      self::$forumcache[$userid] = array();
 319                  }
 320  
 321                  if (!isset(self::$forumcache[$userid][$forumid])) {
 322                      if ($DB->record_exists('forum_subscriptions', array(
 323                          'userid' => $userid,
 324                          'forum' => $forumid,
 325                      ))) {
 326                          self::$forumcache[$userid][$forumid] = true;
 327                      } else {
 328                          self::$forumcache[$userid][$forumid] = false;
 329                      }
 330                  }
 331              } else {
 332                  $subscriptions = $DB->get_recordset('forum_subscriptions', array(
 333                      'forum' => $forumid,
 334                  ), '', 'id, userid');
 335                  foreach ($subscriptions as $id => $data) {
 336                      if (!isset(self::$forumcache[$data->userid])) {
 337                          self::$forumcache[$data->userid] = array();
 338                      }
 339                      self::$forumcache[$data->userid][$forumid] = true;
 340                  }
 341                  self::$fetchedforums[$forumid] = true;
 342                  $subscriptions->close();
 343              }
 344          }
 345      }
 346  
 347      /**
 348       * Fill the forum subscription data for all forums that the specified userid can subscribe to in the specified course.
 349       *
 350       * @param int $courseid The course to retrieve a cache for
 351       * @param int $userid The user ID
 352       * @return void
 353       */
 354      public static function fill_subscription_cache_for_course($courseid, $userid) {
 355          global $DB;
 356  
 357          if (!isset(self::$forumcache[$userid])) {
 358              self::$forumcache[$userid] = array();
 359          }
 360  
 361          $sql = "SELECT
 362                      f.id AS forumid,
 363                      s.id AS subscriptionid
 364                  FROM {forum} f
 365                  LEFT JOIN {forum_subscriptions} s ON (s.forum = f.id AND s.userid = :userid)
 366                  WHERE f.course = :course
 367                  AND f.forcesubscribe <> :subscriptionforced";
 368  
 369          $subscriptions = $DB->get_recordset_sql($sql, array(
 370              'course' => $courseid,
 371              'userid' => $userid,
 372              'subscriptionforced' => FORUM_FORCESUBSCRIBE,
 373          ));
 374  
 375          foreach ($subscriptions as $id => $data) {
 376              self::$forumcache[$userid][$id] = !empty($data->subscriptionid);
 377          }
 378          $subscriptions->close();
 379      }
 380  
 381      /**
 382       * Returns a list of user objects who are subscribed to this forum.
 383       *
 384       * @param stdClass $forum The forum record.
 385       * @param int $groupid The group id if restricting subscriptions to a group of users, or 0 for all.
 386       * @param context_module $context the forum context, to save re-fetching it where possible.
 387       * @param string $fields requested user fields (with "u." table prefix).
 388       * @param boolean $includediscussionsubscriptions Whether to take discussion subscriptions and unsubscriptions into consideration.
 389       * @return array list of users.
 390       */
 391      public static function fetch_subscribed_users($forum, $groupid = 0, $context = null, $fields = null,
 392              $includediscussionsubscriptions = false) {
 393          global $CFG, $DB;
 394  
 395          if (empty($fields)) {
 396              $userfieldsapi = \core_user\fields::for_name();
 397              $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
 398              $fields ="u.id,
 399                        u.username,
 400                        $allnames,
 401                        u.maildisplay,
 402                        u.mailformat,
 403                        u.maildigest,
 404                        u.imagealt,
 405                        u.email,
 406                        u.emailstop,
 407                        u.city,
 408                        u.country,
 409                        u.lastaccess,
 410                        u.lastlogin,
 411                        u.picture,
 412                        u.timezone,
 413                        u.theme,
 414                        u.lang,
 415                        u.trackforums,
 416                        u.mnethostid";
 417          }
 418  
 419          // Retrieve the forum context if it wasn't specified.
 420          $context = forum_get_context($forum->id, $context);
 421          if (self::is_forcesubscribed($forum)) {
 422              $results = self::get_potential_subscribers($context, $groupid, $fields);
 423  
 424          } else {
 425              // Only active enrolled users or everybody on the frontpage.
 426              list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
 427              $params['forumid'] = $forum->id;
 428  
 429              list($sort, $sortparams) = users_order_by_sql('u');
 430              $params = array_merge($params, $sortparams);
 431  
 432              if ($includediscussionsubscriptions) {
 433                  $params['sforumid'] = $forum->id;
 434                  $params['dsforumid'] = $forum->id;
 435                  $params['unsubscribed'] = self::FORUM_DISCUSSION_UNSUBSCRIBED;
 436  
 437                  $sql = "SELECT $fields
 438                          FROM (
 439                              SELECT userid FROM {forum_subscriptions} s
 440                              WHERE
 441                                  s.forum = :sforumid
 442                                  UNION
 443                              SELECT userid FROM {forum_discussion_subs} ds
 444                              WHERE
 445                                  ds.forum = :dsforumid AND ds.preference <> :unsubscribed
 446                          ) subscriptions
 447                          JOIN {user} u ON u.id = subscriptions.userid
 448                          JOIN ($esql) je ON je.id = u.id
 449                          WHERE u.auth <> 'nologin' AND u.suspended = 0 AND u.confirmed = 1
 450                          ORDER BY $sort";
 451  
 452              } else {
 453                  $sql = "SELECT $fields
 454                          FROM {user} u
 455                          JOIN ($esql) je ON je.id = u.id
 456                          JOIN {forum_subscriptions} s ON s.userid = u.id
 457                          WHERE
 458                            s.forum = :forumid AND u.auth <> 'nologin' AND u.suspended = 0 AND u.confirmed = 1
 459                          ORDER BY $sort";
 460              }
 461              $results = $DB->get_records_sql($sql, $params);
 462          }
 463  
 464          // Guest user should never be subscribed to a forum.
 465          unset($results[$CFG->siteguest]);
 466  
 467          // Apply the activity module availability resetrictions.
 468          $cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course);
 469          $modinfo = get_fast_modinfo($forum->course);
 470          $info = new \core_availability\info_module($modinfo->get_cm($cm->id));
 471          $results = $info->filter_user_list($results);
 472  
 473          return $results;
 474      }
 475  
 476      /**
 477       * Retrieve the discussion subscription data for the specified userid and forum.
 478       *
 479       * This is returned as an array of discussions for that forum which contain the preference in a stdClass.
 480       *
 481       * @param int $forumid The forum to retrieve a cache for
 482       * @param int $userid The user ID
 483       * @return array of stdClass objects with one per discussion in the forum.
 484       */
 485      public static function fetch_discussion_subscription($forumid, $userid = null) {
 486          self::fill_discussion_subscription_cache($forumid, $userid);
 487  
 488          if (!isset(self::$forumdiscussioncache[$userid]) || !isset(self::$forumdiscussioncache[$userid][$forumid])) {
 489              return array();
 490          }
 491  
 492          return self::$forumdiscussioncache[$userid][$forumid];
 493      }
 494  
 495      /**
 496       * Fill the discussion subscription data for the specified userid and forum.
 497       *
 498       * If the userid is not specified, then all discussion subscription data for that forum is fetched in a single query
 499       * and used for subsequent lookups without requiring further database queries.
 500       *
 501       * @param int $forumid The forum to retrieve a cache for
 502       * @param int $userid The user ID
 503       * @return void
 504       */
 505      public static function fill_discussion_subscription_cache($forumid, $userid = null) {
 506          global $DB;
 507  
 508          if (!isset(self::$discussionfetchedforums[$forumid])) {
 509              // This forum hasn't been fetched as a whole yet.
 510              if (isset($userid)) {
 511                  if (!isset(self::$forumdiscussioncache[$userid])) {
 512                      self::$forumdiscussioncache[$userid] = array();
 513                  }
 514  
 515                  if (!isset(self::$forumdiscussioncache[$userid][$forumid])) {
 516                      $subscriptions = $DB->get_recordset('forum_discussion_subs', array(
 517                          'userid' => $userid,
 518                          'forum' => $forumid,
 519                      ), null, 'id, discussion, preference');
 520  
 521                      self::$forumdiscussioncache[$userid][$forumid] = array();
 522                      foreach ($subscriptions as $id => $data) {
 523                          self::add_to_discussion_cache($forumid, $userid, $data->discussion, $data->preference);
 524                      }
 525  
 526                      $subscriptions->close();
 527                  }
 528              } else {
 529                  $subscriptions = $DB->get_recordset('forum_discussion_subs', array(
 530                      'forum' => $forumid,
 531                  ), null, 'id, userid, discussion, preference');
 532                  foreach ($subscriptions as $id => $data) {
 533                      self::add_to_discussion_cache($forumid, $data->userid, $data->discussion, $data->preference);
 534                  }
 535                  self::$discussionfetchedforums[$forumid] = true;
 536                  $subscriptions->close();
 537              }
 538          }
 539      }
 540  
 541      /**
 542       * Add the specified discussion and user preference to the discussion
 543       * subscription cache.
 544       *
 545       * @param int $forumid The ID of the forum that this preference belongs to
 546       * @param int $userid The ID of the user that this preference belongs to
 547       * @param int $discussion The ID of the discussion that this preference relates to
 548       * @param int $preference The preference to store
 549       */
 550      protected static function add_to_discussion_cache($forumid, $userid, $discussion, $preference) {
 551          if (!isset(self::$forumdiscussioncache[$userid])) {
 552              self::$forumdiscussioncache[$userid] = array();
 553          }
 554  
 555          if (!isset(self::$forumdiscussioncache[$userid][$forumid])) {
 556              self::$forumdiscussioncache[$userid][$forumid] = array();
 557          }
 558  
 559          self::$forumdiscussioncache[$userid][$forumid][$discussion] = $preference;
 560      }
 561  
 562      /**
 563       * Reset the discussion cache.
 564       *
 565       * This cache is used to reduce the number of database queries when
 566       * checking forum discussion subscription states.
 567       */
 568      public static function reset_discussion_cache() {
 569          self::$forumdiscussioncache = array();
 570          self::$discussionfetchedforums = array();
 571      }
 572  
 573      /**
 574       * Reset the forum cache.
 575       *
 576       * This cache is used to reduce the number of database queries when
 577       * checking forum subscription states.
 578       */
 579      public static function reset_forum_cache() {
 580          self::$forumcache = array();
 581          self::$fetchedforums = array();
 582      }
 583  
 584      /**
 585       * Adds user to the subscriber list.
 586       *
 587       * @param int $userid The ID of the user to subscribe
 588       * @param \stdClass $forum The forum record for this forum.
 589       * @param \context_module|null $context Module context, may be omitted if not known or if called for the current
 590       *      module set in page.
 591       * @param boolean $userrequest Whether the user requested this change themselves. This has an effect on whether
 592       *     discussion subscriptions are removed too.
 593       * @return bool|int Returns true if the user is already subscribed, or the forum_subscriptions ID if the user was
 594       *     successfully subscribed.
 595       */
 596      public static function subscribe_user($userid, $forum, $context = null, $userrequest = false) {
 597          global $DB;
 598  
 599          if (self::is_subscribed($userid, $forum)) {
 600              return true;
 601          }
 602  
 603          $sub = new \stdClass();
 604          $sub->userid  = $userid;
 605          $sub->forum = $forum->id;
 606  
 607          $result = $DB->insert_record("forum_subscriptions", $sub);
 608  
 609          if ($userrequest) {
 610              $discussionsubscriptions = $DB->get_recordset('forum_discussion_subs', array('userid' => $userid, 'forum' => $forum->id));
 611              $DB->delete_records_select('forum_discussion_subs',
 612                      'userid = :userid AND forum = :forumid AND preference <> :preference', array(
 613                          'userid' => $userid,
 614                          'forumid' => $forum->id,
 615                          'preference' => self::FORUM_DISCUSSION_UNSUBSCRIBED,
 616                      ));
 617  
 618              // Reset the subscription caches for this forum.
 619              // We know that the there were previously entries and there aren't any more.
 620              if (isset(self::$forumdiscussioncache[$userid]) && isset(self::$forumdiscussioncache[$userid][$forum->id])) {
 621                  foreach (self::$forumdiscussioncache[$userid][$forum->id] as $discussionid => $preference) {
 622                      if ($preference != self::FORUM_DISCUSSION_UNSUBSCRIBED) {
 623                          unset(self::$forumdiscussioncache[$userid][$forum->id][$discussionid]);
 624                      }
 625                  }
 626              }
 627          }
 628  
 629          // Reset the cache for this forum.
 630          self::$forumcache[$userid][$forum->id] = true;
 631  
 632          $context = forum_get_context($forum->id, $context);
 633          $params = array(
 634              'context' => $context,
 635              'objectid' => $result,
 636              'relateduserid' => $userid,
 637              'other' => array('forumid' => $forum->id),
 638  
 639          );
 640          $event  = event\subscription_created::create($params);
 641          if ($userrequest && $discussionsubscriptions) {
 642              foreach ($discussionsubscriptions as $subscription) {
 643                  $event->add_record_snapshot('forum_discussion_subs', $subscription);
 644              }
 645              $discussionsubscriptions->close();
 646          }
 647          $event->trigger();
 648  
 649          return $result;
 650      }
 651  
 652      /**
 653       * Removes user from the subscriber list
 654       *
 655       * @param int $userid The ID of the user to unsubscribe
 656       * @param \stdClass $forum The forum record for this forum.
 657       * @param \context_module|null $context Module context, may be omitted if not known or if called for the current
 658       *     module set in page.
 659       * @param boolean $userrequest Whether the user requested this change themselves. This has an effect on whether
 660       *     discussion subscriptions are removed too.
 661       * @return boolean Always returns true.
 662       */
 663      public static function unsubscribe_user($userid, $forum, $context = null, $userrequest = false) {
 664          global $DB;
 665  
 666          $sqlparams = array(
 667              'userid' => $userid,
 668              'forum' => $forum->id,
 669          );
 670          $DB->delete_records('forum_digests', $sqlparams);
 671  
 672          if ($forumsubscription = $DB->get_record('forum_subscriptions', $sqlparams)) {
 673              $DB->delete_records('forum_subscriptions', array('id' => $forumsubscription->id));
 674  
 675              if ($userrequest) {
 676                  $discussionsubscriptions = $DB->get_recordset('forum_discussion_subs', $sqlparams);
 677                  $DB->delete_records('forum_discussion_subs',
 678                          array('userid' => $userid, 'forum' => $forum->id, 'preference' => self::FORUM_DISCUSSION_UNSUBSCRIBED));
 679  
 680                  // We know that the there were previously entries and there aren't any more.
 681                  if (isset(self::$forumdiscussioncache[$userid]) && isset(self::$forumdiscussioncache[$userid][$forum->id])) {
 682                      self::$forumdiscussioncache[$userid][$forum->id] = array();
 683                  }
 684              }
 685  
 686              // Reset the cache for this forum.
 687              self::$forumcache[$userid][$forum->id] = false;
 688  
 689              $context = forum_get_context($forum->id, $context);
 690              $params = array(
 691                  'context' => $context,
 692                  'objectid' => $forumsubscription->id,
 693                  'relateduserid' => $userid,
 694                  'other' => array('forumid' => $forum->id),
 695  
 696              );
 697              $event = event\subscription_deleted::create($params);
 698              $event->add_record_snapshot('forum_subscriptions', $forumsubscription);
 699              if ($userrequest && $discussionsubscriptions) {
 700                  foreach ($discussionsubscriptions as $subscription) {
 701                      $event->add_record_snapshot('forum_discussion_subs', $subscription);
 702                  }
 703                  $discussionsubscriptions->close();
 704              }
 705              $event->trigger();
 706          }
 707  
 708          return true;
 709      }
 710  
 711      /**
 712       * Subscribes the user to the specified discussion.
 713       *
 714       * @param int $userid The userid of the user being subscribed
 715       * @param \stdClass $discussion The discussion to subscribe to
 716       * @param \context_module|null $context Module context, may be omitted if not known or if called for the current
 717       *     module set in page.
 718       * @return boolean Whether a change was made
 719       */
 720      public static function subscribe_user_to_discussion($userid, $discussion, $context = null) {
 721          global $DB;
 722  
 723          // First check whether the user is subscribed to the discussion already.
 724          $subscription = $DB->get_record('forum_discussion_subs', array('userid' => $userid, 'discussion' => $discussion->id));
 725          if ($subscription) {
 726              if ($subscription->preference != self::FORUM_DISCUSSION_UNSUBSCRIBED) {
 727                  // The user is already subscribed to the discussion. Ignore.
 728                  return false;
 729              }
 730          }
 731          // No discussion-level subscription. Check for a forum level subscription.
 732          if ($DB->record_exists('forum_subscriptions', array('userid' => $userid, 'forum' => $discussion->forum))) {
 733              if ($subscription && $subscription->preference == self::FORUM_DISCUSSION_UNSUBSCRIBED) {
 734                  // The user is subscribed to the forum, but unsubscribed from the discussion, delete the discussion preference.
 735                  $DB->delete_records('forum_discussion_subs', array('id' => $subscription->id));
 736                  unset(self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id]);
 737              } else {
 738                  // The user is already subscribed to the forum. Ignore.
 739                  return false;
 740              }
 741          } else {
 742              if ($subscription) {
 743                  $subscription->preference = time();
 744                  $DB->update_record('forum_discussion_subs', $subscription);
 745              } else {
 746                  $subscription = new \stdClass();
 747                  $subscription->userid  = $userid;
 748                  $subscription->forum = $discussion->forum;
 749                  $subscription->discussion = $discussion->id;
 750                  $subscription->preference = time();
 751  
 752                  $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription);
 753                  self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id] = $subscription->preference;
 754              }
 755          }
 756  
 757          $context = forum_get_context($discussion->forum, $context);
 758          $params = array(
 759              'context' => $context,
 760              'objectid' => $subscription->id,
 761              'relateduserid' => $userid,
 762              'other' => array(
 763                  'forumid' => $discussion->forum,
 764                  'discussion' => $discussion->id,
 765              ),
 766  
 767          );
 768          $event  = event\discussion_subscription_created::create($params);
 769          $event->trigger();
 770  
 771          return true;
 772      }
 773      /**
 774       * Unsubscribes the user from the specified discussion.
 775       *
 776       * @param int $userid The userid of the user being unsubscribed
 777       * @param \stdClass $discussion The discussion to unsubscribe from
 778       * @param \context_module|null $context Module context, may be omitted if not known or if called for the current
 779       *     module set in page.
 780       * @return boolean Whether a change was made
 781       */
 782      public static function unsubscribe_user_from_discussion($userid, $discussion, $context = null) {
 783          global $DB;
 784  
 785          // First check whether the user's subscription preference for this discussion.
 786          $subscription = $DB->get_record('forum_discussion_subs', array('userid' => $userid, 'discussion' => $discussion->id));
 787          if ($subscription) {
 788              if ($subscription->preference == self::FORUM_DISCUSSION_UNSUBSCRIBED) {
 789                  // The user is already unsubscribed from the discussion. Ignore.
 790                  return false;
 791              }
 792          }
 793          // No discussion-level preference. Check for a forum level subscription.
 794          if (!$DB->record_exists('forum_subscriptions', array('userid' => $userid, 'forum' => $discussion->forum))) {
 795              if ($subscription && $subscription->preference != self::FORUM_DISCUSSION_UNSUBSCRIBED) {
 796                  // The user is not subscribed to the forum, but subscribed from the discussion, delete the discussion subscription.
 797                  $DB->delete_records('forum_discussion_subs', array('id' => $subscription->id));
 798                  unset(self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id]);
 799              } else {
 800                  // The user is not subscribed from the forum. Ignore.
 801                  return false;
 802              }
 803          } else {
 804              if ($subscription) {
 805                  $subscription->preference = self::FORUM_DISCUSSION_UNSUBSCRIBED;
 806                  $DB->update_record('forum_discussion_subs', $subscription);
 807              } else {
 808                  $subscription = new \stdClass();
 809                  $subscription->userid  = $userid;
 810                  $subscription->forum = $discussion->forum;
 811                  $subscription->discussion = $discussion->id;
 812                  $subscription->preference = self::FORUM_DISCUSSION_UNSUBSCRIBED;
 813  
 814                  $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription);
 815              }
 816              self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id] = $subscription->preference;
 817          }
 818  
 819          $context = forum_get_context($discussion->forum, $context);
 820          $params = array(
 821              'context' => $context,
 822              'objectid' => $subscription->id,
 823              'relateduserid' => $userid,
 824              'other' => array(
 825                  'forumid' => $discussion->forum,
 826                  'discussion' => $discussion->id,
 827              ),
 828  
 829          );
 830          $event  = event\discussion_subscription_deleted::create($params);
 831          $event->trigger();
 832  
 833          return true;
 834      }
 835  
 836      /**
 837       * Gets the default subscription value for the logged in user.
 838       *
 839       * @param \stdClass $forum The forum record
 840       * @param \context $context The course context
 841       * @param \cm_info $cm cm_info
 842       * @param int|null $discussionid The discussion we are checking against
 843       * @return bool Default subscription
 844       * @throws coding_exception
 845       */
 846      public static function get_user_default_subscription($forum, $context, $cm, ?int $discussionid) {
 847          global $USER;
 848          $manageactivities = has_capability('moodle/course:manageactivities', $context);
 849          if (\mod_forum\subscriptions::subscription_disabled($forum) && !$manageactivities) {
 850              // User does not have permission to subscribe to this discussion at all.
 851              $discussionsubscribe = false;
 852          } else if (\mod_forum\subscriptions::is_forcesubscribed($forum)) {
 853              // User does not have permission to unsubscribe from this discussion at all.
 854              $discussionsubscribe = true;
 855          } else {
 856              if (isset($discussionid) && self::is_subscribed($USER->id, $forum, $discussionid, $cm)) {
 857                  // User is subscribed to the discussion - continue the subscription.
 858                  $discussionsubscribe = true;
 859              } else if (!isset($discussionid) && \mod_forum\subscriptions::is_subscribed($USER->id, $forum, null, $cm)) {
 860                  // Starting a new discussion, and the user is subscribed to the forum - subscribe to the discussion.
 861                  $discussionsubscribe = true;
 862              } else {
 863                  // User is not subscribed to either forum or discussion. Follow user preference.
 864                  $discussionsubscribe = $USER->autosubscribe ?? false;
 865              }
 866          }
 867  
 868          return $discussionsubscribe;
 869      }
 870  }