Search moodle.org's
Developer Documentation

See Release Notes

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

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

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