MDL-65069 mod_forum: Remove unused code and additional dev docs.
[moodle.git] / mod / forum / classes / subscriptions.php
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/>.
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  */
25 namespace mod_forum;
27 defined('MOODLE_INTERNAL') || die();
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 {
37     /**
38      * The status value for an unsubscribed discussion.
39      *
40      * @var int
41      */
42     const FORUM_DISCUSSION_UNSUBSCRIBED = -1;
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();
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();
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();
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();
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         }
114         if ($discussionid === null) {
115             return self::is_subscribed_to_forum($userid, $forum);
116         }
118         $subscriptions = self::fetch_discussion_subscription($forum->id, $userid);
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         }
125         return self::is_subscribed_to_forum($userid, $forum);
126     }
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     }
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     }
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     }
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     }
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     }
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     }
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;
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         }
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');
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);
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             }
246             $unsubscribableforums[] = $forum;
247         }
248         $forums->close();
250         return $unsubscribableforums;
251     }
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;
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         }
272         $sql = "SELECT $fields
273                 FROM {user} u
274                 JOIN ($esql) je ON je.id = u.id
275             ORDER BY $sort";
277         return $DB->get_records_sql($sql, $params);
278     }
280     /**
281      * Fetch the forum subscription data for the specified userid and forum.
282      *
283      * @param int $forumid The forum to retrieve a cache for
284      * @param int $userid The user ID
285      * @return boolean
286      */
287     public static function fetch_subscription_cache($forumid, $userid) {
288         if (isset(self::$forumcache[$userid]) && isset(self::$forumcache[$userid][$forumid])) {
289             return self::$forumcache[$userid][$forumid];
290         }
291         self::fill_subscription_cache($forumid, $userid);
293         if (!isset(self::$forumcache[$userid]) || !isset(self::$forumcache[$userid][$forumid])) {
294             return false;
295         }
297         return self::$forumcache[$userid][$forumid];
298     }
300     /**
301      * Fill the forum subscription data for the specified userid and forum.
302      *
303      * If the userid is not specified, then all subscription data for that forum is fetched in a single query and used
304      * for subsequent lookups without requiring further database queries.
305      *
306      * @param int $forumid The forum to retrieve a cache for
307      * @param int $userid The user ID
308      * @return void
309      */
310     public static function fill_subscription_cache($forumid, $userid = null) {
311         global $DB;
313         if (!isset(self::$fetchedforums[$forumid])) {
314             // This forum has not been fetched as a whole.
315             if (isset($userid)) {
316                 if (!isset(self::$forumcache[$userid])) {
317                     self::$forumcache[$userid] = array();
318                 }
320                 if (!isset(self::$forumcache[$userid][$forumid])) {
321                     if ($DB->record_exists('forum_subscriptions', array(
322                         'userid' => $userid,
323                         'forum' => $forumid,
324                     ))) {
325                         self::$forumcache[$userid][$forumid] = true;
326                     } else {
327                         self::$forumcache[$userid][$forumid] = false;
328                     }
329                 }
330             } else {
331                 $subscriptions = $DB->get_recordset('forum_subscriptions', array(
332                     'forum' => $forumid,
333                 ), '', 'id, userid');
334                 foreach ($subscriptions as $id => $data) {
335                     if (!isset(self::$forumcache[$data->userid])) {
336                         self::$forumcache[$data->userid] = array();
337                     }
338                     self::$forumcache[$data->userid][$forumid] = true;
339                 }
340                 self::$fetchedforums[$forumid] = true;
341                 $subscriptions->close();
342             }
343         }
344     }
346     /**
347      * Fill the forum subscription data for all forums that the specified userid can subscribe to in the specified course.
348      *
349      * @param int $courseid The course to retrieve a cache for
350      * @param int $userid The user ID
351      * @return void
352      */
353     public static function fill_subscription_cache_for_course($courseid, $userid) {
354         global $DB;
356         if (!isset(self::$forumcache[$userid])) {
357             self::$forumcache[$userid] = array();
358         }
360         $sql = "SELECT
361                     f.id AS forumid,
362                     s.id AS subscriptionid
363                 FROM {forum} f
364                 LEFT JOIN {forum_subscriptions} s ON (s.forum = f.id AND s.userid = :userid)
365                 WHERE f.course = :course
366                 AND f.forcesubscribe <> :subscriptionforced";
368         $subscriptions = $DB->get_recordset_sql($sql, array(
369             'course' => $courseid,
370             'userid' => $userid,
371             'subscriptionforced' => FORUM_FORCESUBSCRIBE,
372         ));
374         foreach ($subscriptions as $id => $data) {
375             self::$forumcache[$userid][$id] = !empty($data->subscriptionid);
376         }
377         $subscriptions->close();
378     }
380     /**
381      * Returns a list of user objects who are subscribed to this forum.
382      *
383      * @param stdClass $forum The forum record.
384      * @param int $groupid The group id if restricting subscriptions to a group of users, or 0 for all.
385      * @param context_module $context the forum context, to save re-fetching it where possible.
386      * @param string $fields requested user fields (with "u." table prefix).
387      * @param boolean $includediscussionsubscriptions Whether to take discussion subscriptions and unsubscriptions into consideration.
388      * @return array list of users.
389      */
390     public static function fetch_subscribed_users($forum, $groupid = 0, $context = null, $fields = null,
391             $includediscussionsubscriptions = false) {
392         global $CFG, $DB;
394         if (empty($fields)) {
395             $allnames = get_all_user_name_fields(true, 'u');
396             $fields ="u.id,
397                       u.username,
398                       $allnames,
399                       u.maildisplay,
400                       u.mailformat,
401                       u.maildigest,
402                       u.imagealt,
403                       u.email,
404                       u.emailstop,
405                       u.city,
406                       u.country,
407                       u.lastaccess,
408                       u.lastlogin,
409                       u.picture,
410                       u.timezone,
411                       u.theme,
412                       u.lang,
413                       u.trackforums,
414                       u.mnethostid";
415         }
417         // Retrieve the forum context if it wasn't specified.
418         $context = forum_get_context($forum->id, $context);
420         if (self::is_forcesubscribed($forum)) {
421             $results = \mod_forum\subscriptions::get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
423         } else {
424             // Only active enrolled users or everybody on the frontpage.
425             list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
426             $params['forumid'] = $forum->id;
428             if ($includediscussionsubscriptions) {
429                 $params['sforumid'] = $forum->id;
430                 $params['dsforumid'] = $forum->id;
431                 $params['unsubscribed'] = self::FORUM_DISCUSSION_UNSUBSCRIBED;
433                 $sql = "SELECT $fields
434                         FROM (
435                             SELECT userid FROM {forum_subscriptions} s
436                             WHERE
437                                 s.forum = :sforumid
438                                 UNION
439                             SELECT userid FROM {forum_discussion_subs} ds
440                             WHERE
441                                 ds.forum = :dsforumid AND ds.preference <> :unsubscribed
442                         ) subscriptions
443                         JOIN {user} u ON u.id = subscriptions.userid
444                         JOIN ($esql) je ON je.id = u.id
445                         ORDER BY u.email ASC";
447             } else {
448                 $sql = "SELECT $fields
449                         FROM {user} u
450                         JOIN ($esql) je ON je.id = u.id
451                         JOIN {forum_subscriptions} s ON s.userid = u.id
452                         WHERE
453                           s.forum = :forumid
454                         ORDER BY u.email ASC";
455             }
456             $results = $DB->get_records_sql($sql, $params);
457         }
459         // Guest user should never be subscribed to a forum.
460         unset($results[$CFG->siteguest]);
462         // Apply the activity module availability resetrictions.
463         $cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course);
464         $modinfo = get_fast_modinfo($forum->course);
465         $info = new \core_availability\info_module($modinfo->get_cm($cm->id));
466         $results = $info->filter_user_list($results);
468         return $results;
469     }
471     /**
472      * Retrieve the discussion subscription data for the specified userid and forum.
473      *
474      * This is returned as an array of discussions for that forum which contain the preference in a stdClass.
475      *
476      * @param int $forumid The forum to retrieve a cache for
477      * @param int $userid The user ID
478      * @return array of stdClass objects with one per discussion in the forum.
479      */
480     public static function fetch_discussion_subscription($forumid, $userid = null) {
481         self::fill_discussion_subscription_cache($forumid, $userid);
483         if (!isset(self::$forumdiscussioncache[$userid]) || !isset(self::$forumdiscussioncache[$userid][$forumid])) {
484             return array();
485         }
487         return self::$forumdiscussioncache[$userid][$forumid];
488     }
490     /**
491      * Fill the discussion subscription data for the specified userid and forum.
492      *
493      * If the userid is not specified, then all discussion subscription data for that forum is fetched in a single query
494      * and used for subsequent lookups without requiring further database queries.
495      *
496      * @param int $forumid The forum to retrieve a cache for
497      * @param int $userid The user ID
498      * @return void
499      */
500     public static function fill_discussion_subscription_cache($forumid, $userid = null) {
501         global $DB;
503         if (!isset(self::$discussionfetchedforums[$forumid])) {
504             // This forum hasn't been fetched as a whole yet.
505             if (isset($userid)) {
506                 if (!isset(self::$forumdiscussioncache[$userid])) {
507                     self::$forumdiscussioncache[$userid] = array();
508                 }
510                 if (!isset(self::$forumdiscussioncache[$userid][$forumid])) {
511                     $subscriptions = $DB->get_recordset('forum_discussion_subs', array(
512                         'userid' => $userid,
513                         'forum' => $forumid,
514                     ), null, 'id, discussion, preference');
515                     foreach ($subscriptions as $id => $data) {
516                         self::add_to_discussion_cache($forumid, $userid, $data->discussion, $data->preference);
517                     }
518                     $subscriptions->close();
519                 }
520             } else {
521                 $subscriptions = $DB->get_recordset('forum_discussion_subs', array(
522                     'forum' => $forumid,
523                 ), null, 'id, userid, discussion, preference');
524                 foreach ($subscriptions as $id => $data) {
525                     self::add_to_discussion_cache($forumid, $data->userid, $data->discussion, $data->preference);
526                 }
527                 self::$discussionfetchedforums[$forumid] = true;
528                 $subscriptions->close();
529             }
530         }
531     }
533     /**
534      * Add the specified discussion and user preference to the discussion
535      * subscription cache.
536      *
537      * @param int $forumid The ID of the forum that this preference belongs to
538      * @param int $userid The ID of the user that this preference belongs to
539      * @param int $discussion The ID of the discussion that this preference relates to
540      * @param int $preference The preference to store
541      */
542     protected static function add_to_discussion_cache($forumid, $userid, $discussion, $preference) {
543         if (!isset(self::$forumdiscussioncache[$userid])) {
544             self::$forumdiscussioncache[$userid] = array();
545         }
547         if (!isset(self::$forumdiscussioncache[$userid][$forumid])) {
548             self::$forumdiscussioncache[$userid][$forumid] = array();
549         }
551         self::$forumdiscussioncache[$userid][$forumid][$discussion] = $preference;
552     }
554     /**
555      * Reset the discussion cache.
556      *
557      * This cache is used to reduce the number of database queries when
558      * checking forum discussion subscription states.
559      */
560     public static function reset_discussion_cache() {
561         self::$forumdiscussioncache = array();
562         self::$discussionfetchedforums = array();
563     }
565     /**
566      * Reset the forum cache.
567      *
568      * This cache is used to reduce the number of database queries when
569      * checking forum subscription states.
570      */
571     public static function reset_forum_cache() {
572         self::$forumcache = array();
573         self::$fetchedforums = array();
574     }
576     /**
577      * Adds user to the subscriber list.
578      *
579      * @param int $userid The ID of the user to subscribe
580      * @param \stdClass $forum The forum record for this forum.
581      * @param \context_module|null $context Module context, may be omitted if not known or if called for the current
582      *      module set in page.
583      * @param boolean $userrequest Whether the user requested this change themselves. This has an effect on whether
584      *     discussion subscriptions are removed too.
585      * @return bool|int Returns true if the user is already subscribed, or the forum_subscriptions ID if the user was
586      *     successfully subscribed.
587      */
588     public static function subscribe_user($userid, $forum, $context = null, $userrequest = false) {
589         global $DB;
591         if (self::is_subscribed($userid, $forum)) {
592             return true;
593         }
595         $sub = new \stdClass();
596         $sub->userid  = $userid;
597         $sub->forum = $forum->id;
599         $result = $DB->insert_record("forum_subscriptions", $sub);
601         if ($userrequest) {
602             $discussionsubscriptions = $DB->get_recordset('forum_discussion_subs', array('userid' => $userid, 'forum' => $forum->id));
603             $DB->delete_records_select('forum_discussion_subs',
604                     'userid = :userid AND forum = :forumid AND preference <> :preference', array(
605                         'userid' => $userid,
606                         'forumid' => $forum->id,
607                         'preference' => self::FORUM_DISCUSSION_UNSUBSCRIBED,
608                     ));
610             // Reset the subscription caches for this forum.
611             // We know that the there were previously entries and there aren't any more.
612             if (isset(self::$forumdiscussioncache[$userid]) && isset(self::$forumdiscussioncache[$userid][$forum->id])) {
613                 foreach (self::$forumdiscussioncache[$userid][$forum->id] as $discussionid => $preference) {
614                     if ($preference != self::FORUM_DISCUSSION_UNSUBSCRIBED) {
615                         unset(self::$forumdiscussioncache[$userid][$forum->id][$discussionid]);
616                     }
617                 }
618             }
619         }
621         // Reset the cache for this forum.
622         self::$forumcache[$userid][$forum->id] = true;
624         $context = forum_get_context($forum->id, $context);
625         $params = array(
626             'context' => $context,
627             'objectid' => $result,
628             'relateduserid' => $userid,
629             'other' => array('forumid' => $forum->id),
631         );
632         $event  = event\subscription_created::create($params);
633         if ($userrequest && $discussionsubscriptions) {
634             foreach ($discussionsubscriptions as $subscription) {
635                 $event->add_record_snapshot('forum_discussion_subs', $subscription);
636             }
637             $discussionsubscriptions->close();
638         }
639         $event->trigger();
641         return $result;
642     }
644     /**
645      * Removes user from the subscriber list
646      *
647      * @param int $userid The ID of the user to unsubscribe
648      * @param \stdClass $forum The forum record for this forum.
649      * @param \context_module|null $context Module context, may be omitted if not known or if called for the current
650      *     module set in page.
651      * @param boolean $userrequest Whether the user requested this change themselves. This has an effect on whether
652      *     discussion subscriptions are removed too.
653      * @return boolean Always returns true.
654      */
655     public static function unsubscribe_user($userid, $forum, $context = null, $userrequest = false) {
656         global $DB;
658         $sqlparams = array(
659             'userid' => $userid,
660             'forum' => $forum->id,
661         );
662         $DB->delete_records('forum_digests', $sqlparams);
664         if ($forumsubscription = $DB->get_record('forum_subscriptions', $sqlparams)) {
665             $DB->delete_records('forum_subscriptions', array('id' => $forumsubscription->id));
667             if ($userrequest) {
668                 $discussionsubscriptions = $DB->get_recordset('forum_discussion_subs', $sqlparams);
669                 $DB->delete_records('forum_discussion_subs',
670                         array('userid' => $userid, 'forum' => $forum->id, 'preference' => self::FORUM_DISCUSSION_UNSUBSCRIBED));
672                 // We know that the there were previously entries and there aren't any more.
673                 if (isset(self::$forumdiscussioncache[$userid]) && isset(self::$forumdiscussioncache[$userid][$forum->id])) {
674                     self::$forumdiscussioncache[$userid][$forum->id] = array();
675                 }
676             }
678             // Reset the cache for this forum.
679             self::$forumcache[$userid][$forum->id] = false;
681             $context = forum_get_context($forum->id, $context);
682             $params = array(
683                 'context' => $context,
684                 'objectid' => $forumsubscription->id,
685                 'relateduserid' => $userid,
686                 'other' => array('forumid' => $forum->id),
688             );
689             $event = event\subscription_deleted::create($params);
690             $event->add_record_snapshot('forum_subscriptions', $forumsubscription);
691             if ($userrequest && $discussionsubscriptions) {
692                 foreach ($discussionsubscriptions as $subscription) {
693                     $event->add_record_snapshot('forum_discussion_subs', $subscription);
694                 }
695                 $discussionsubscriptions->close();
696             }
697             $event->trigger();
698         }
700         return true;
701     }
703     /**
704      * Subscribes the user to the specified discussion.
705      *
706      * @param int $userid The userid of the user being subscribed
707      * @param \stdClass $discussion The discussion to subscribe to
708      * @param \context_module|null $context Module context, may be omitted if not known or if called for the current
709      *     module set in page.
710      * @return boolean Whether a change was made
711      */
712     public static function subscribe_user_to_discussion($userid, $discussion, $context = null) {
713         global $DB;
715         // First check whether the user is subscribed to the discussion already.
716         $subscription = $DB->get_record('forum_discussion_subs', array('userid' => $userid, 'discussion' => $discussion->id));
717         if ($subscription) {
718             if ($subscription->preference != self::FORUM_DISCUSSION_UNSUBSCRIBED) {
719                 // The user is already subscribed to the discussion. Ignore.
720                 return false;
721             }
722         }
723         // No discussion-level subscription. Check for a forum level subscription.
724         if ($DB->record_exists('forum_subscriptions', array('userid' => $userid, 'forum' => $discussion->forum))) {
725             if ($subscription && $subscription->preference == self::FORUM_DISCUSSION_UNSUBSCRIBED) {
726                 // The user is subscribed to the forum, but unsubscribed from the discussion, delete the discussion preference.
727                 $DB->delete_records('forum_discussion_subs', array('id' => $subscription->id));
728                 unset(self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id]);
729             } else {
730                 // The user is already subscribed to the forum. Ignore.
731                 return false;
732             }
733         } else {
734             if ($subscription) {
735                 $subscription->preference = time();
736                 $DB->update_record('forum_discussion_subs', $subscription);
737             } else {
738                 $subscription = new \stdClass();
739                 $subscription->userid  = $userid;
740                 $subscription->forum = $discussion->forum;
741                 $subscription->discussion = $discussion->id;
742                 $subscription->preference = time();
744                 $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription);
745                 self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id] = $subscription->preference;
746             }
747         }
749         $context = forum_get_context($discussion->forum, $context);
750         $params = array(
751             'context' => $context,
752             'objectid' => $subscription->id,
753             'relateduserid' => $userid,
754             'other' => array(
755                 'forumid' => $discussion->forum,
756                 'discussion' => $discussion->id,
757             ),
759         );
760         $event  = event\discussion_subscription_created::create($params);
761         $event->trigger();
763         return true;
764     }
765     /**
766      * Unsubscribes the user from the specified discussion.
767      *
768      * @param int $userid The userid of the user being unsubscribed
769      * @param \stdClass $discussion The discussion to unsubscribe from
770      * @param \context_module|null $context Module context, may be omitted if not known or if called for the current
771      *     module set in page.
772      * @return boolean Whether a change was made
773      */
774     public static function unsubscribe_user_from_discussion($userid, $discussion, $context = null) {
775         global $DB;
777         // First check whether the user's subscription preference for this discussion.
778         $subscription = $DB->get_record('forum_discussion_subs', array('userid' => $userid, 'discussion' => $discussion->id));
779         if ($subscription) {
780             if ($subscription->preference == self::FORUM_DISCUSSION_UNSUBSCRIBED) {
781                 // The user is already unsubscribed from the discussion. Ignore.
782                 return false;
783             }
784         }
785         // No discussion-level preference. Check for a forum level subscription.
786         if (!$DB->record_exists('forum_subscriptions', array('userid' => $userid, 'forum' => $discussion->forum))) {
787             if ($subscription && $subscription->preference != self::FORUM_DISCUSSION_UNSUBSCRIBED) {
788                 // The user is not subscribed to the forum, but subscribed from the discussion, delete the discussion subscription.
789                 $DB->delete_records('forum_discussion_subs', array('id' => $subscription->id));
790                 unset(self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id]);
791             } else {
792                 // The user is not subscribed from the forum. Ignore.
793                 return false;
794             }
795         } else {
796             if ($subscription) {
797                 $subscription->preference = self::FORUM_DISCUSSION_UNSUBSCRIBED;
798                 $DB->update_record('forum_discussion_subs', $subscription);
799             } else {
800                 $subscription = new \stdClass();
801                 $subscription->userid  = $userid;
802                 $subscription->forum = $discussion->forum;
803                 $subscription->discussion = $discussion->id;
804                 $subscription->preference = self::FORUM_DISCUSSION_UNSUBSCRIBED;
806                 $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription);
807             }
808             self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id] = $subscription->preference;
809         }
811         $context = forum_get_context($discussion->forum, $context);
812         $params = array(
813             'context' => $context,
814             'objectid' => $subscription->id,
815             'relateduserid' => $userid,
816             'other' => array(
817                 'forumid' => $discussion->forum,
818                 'discussion' => $discussion->id,
819             ),
821         );
822         $event  = event\discussion_subscription_deleted::create($params);
823         $event->trigger();
825         return true;
826     }
828     /**
829      * Gets the default subscription value for the logged in user.
830      *
831      * @param \stdClass $forum The forum record
832      * @param \context $context The course context
833      * @param \cm_info $cm cm_info
834      * @param int|null $discussionid The discussion we are checking against
835      * @return bool Default subscription
836      * @throws coding_exception
837      */
838     public static function get_user_default_subscription($forum, $context, $cm, ?int $discussionid) {
839         global $USER;
840         $manageactivities = has_capability('moodle/course:manageactivities', $context);
841         if (\mod_forum\subscriptions::subscription_disabled($forum) && !$manageactivities) {
842             // User does not have permission to subscribe to this discussion at all.
843             $discussionsubscribe = false;
844         } else if (\mod_forum\subscriptions::is_forcesubscribed($forum)) {
845             // User does not have permission to unsubscribe from this discussion at all.
846             $discussionsubscribe = true;
847         } else {
848             if (isset($discussion) && \mod_forum\subscriptions::is_subscribed($USER->id, $forum, $discussionid, $cm)) {
849                 // User is subscribed to the discussion - continue the subscription.
850                 $discussionsubscribe = true;
851             } else if (!isset($discussionid) && \mod_forum\subscriptions::is_subscribed($USER->id, $forum, null, $cm)) {
852                 // Starting a new discussion, and the user is subscribed to the forum - subscribe to the discussion.
853                 $discussionsubscribe = true;
854             } else {
855                 // User is not subscribed to either forum or discussion. Follow user preference.
856                 $discussionsubscribe = $USER->autosubscribe ?? false;
857             }
858         }
860         return $discussionsubscribe;
861     }