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
25namespace mod_forum;
26
27defined('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 */
35class 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            $allnames = get_all_user_name_fields(true, 'u');
397            $fields ="u.id,
398                      u.username,
399                      $allnames,
400                      u.maildisplay,
401                      u.mailformat,
402                      u.maildigest,
403                      u.imagealt,
404                      u.email,
405                      u.emailstop,
406                      u.city,
407                      u.country,
408                      u.lastaccess,
409                      u.lastlogin,
410                      u.picture,
411                      u.timezone,
412                      u.theme,
413                      u.lang,
414                      u.trackforums,
415                      u.mnethostid";
416        }
417
418        // Retrieve the forum context if it wasn't specified.
419        $context = forum_get_context($forum->id, $context);
420
421        if (self::is_forcesubscribed($forum)) {
422            $results = \mod_forum\subscriptions::get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
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            if ($includediscussionsubscriptions) {
430                $params['sforumid'] = $forum->id;
431                $params['dsforumid'] = $forum->id;
432                $params['unsubscribed'] = self::FORUM_DISCUSSION_UNSUBSCRIBED;
433
434                $sql = "SELECT $fields
435                        FROM (
436                            SELECT userid FROM {forum_subscriptions} s
437                            WHERE
438                                s.forum = :sforumid
439                                UNION
440                            SELECT userid FROM {forum_discussion_subs} ds
441                            WHERE
442                                ds.forum = :dsforumid AND ds.preference <> :unsubscribed
443                        ) subscriptions
444                        JOIN {user} u ON u.id = subscriptions.userid
445                        JOIN ($esql) je ON je.id = u.id
446                        WHERE u.auth <> 'nologin' AND u.suspended = 0 AND u.confirmed = 1
447                        ORDER BY u.email ASC";
448
449            } else {
450                $sql = "SELECT $fields
451                        FROM {user} u
452                        JOIN ($esql) je ON je.id = u.id
453                        JOIN {forum_subscriptions} s ON s.userid = u.id
454                        WHERE
455                          s.forum = :forumid AND u.auth <> 'nologin' AND u.suspended = 0 AND u.confirmed = 1
456                        ORDER BY u.email ASC";
457            }
458            $results = $DB->get_records_sql($sql, $params);
459        }
460
461        // Guest user should never be subscribed to a forum.
462        unset($results[$CFG->siteguest]);
463
464        // Apply the activity module availability resetrictions.
465        $cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course);
466        $modinfo = get_fast_modinfo($forum->course);
467        $info = new \core_availability\info_module($modinfo->get_cm($cm->id));
468        $results = $info->filter_user_list($results);
469
470        return $results;
471    }
472
473    /**
474     * Retrieve the discussion subscription data for the specified userid and forum.
475     *
476     * This is returned as an array of discussions for that forum which contain the preference in a stdClass.
477     *
478     * @param int $forumid The forum to retrieve a cache for
479     * @param int $userid The user ID
480     * @return array of stdClass objects with one per discussion in the forum.
481     */
482    public static function fetch_discussion_subscription($forumid, $userid = null) {
483        self::fill_discussion_subscription_cache($forumid, $userid);
484
485        if (!isset(self::$forumdiscussioncache[$userid]) || !isset(self::$forumdiscussioncache[$userid][$forumid])) {
486            return array();
487        }
488
489        return self::$forumdiscussioncache[$userid][$forumid];
490    }
491
492    /**
493     * Fill the discussion subscription data for the specified userid and forum.
494     *
495     * If the userid is not specified, then all discussion subscription data for that forum is fetched in a single query
496     * and used for subsequent lookups without requiring further database queries.
497     *
498     * @param int $forumid The forum to retrieve a cache for
499     * @param int $userid The user ID
500     * @return void
501     */
502    public static function fill_discussion_subscription_cache($forumid, $userid = null) {
503        global $DB;
504
505        if (!isset(self::$discussionfetchedforums[$forumid])) {
506            // This forum hasn't been fetched as a whole yet.
507            if (isset($userid)) {
508                if (!isset(self::$forumdiscussioncache[$userid])) {
509                    self::$forumdiscussioncache[$userid] = array();
510                }
511
512                if (!isset(self::$forumdiscussioncache[$userid][$forumid])) {
513                    $subscriptions = $DB->get_recordset('forum_discussion_subs', array(
514                        'userid' => $userid,
515                        'forum' => $forumid,
516                    ), null, 'id, discussion, preference');
517
518                    self::$forumdiscussioncache[$userid][$forumid] = array();
519                    foreach ($subscriptions as $id => $data) {
520                        self::add_to_discussion_cache($forumid, $userid, $data->discussion, $data->preference);
521                    }
522
523                    $subscriptions->close();
524                }
525            } else {
526                $subscriptions = $DB->get_recordset('forum_discussion_subs', array(
527                    'forum' => $forumid,
528                ), null, 'id, userid, discussion, preference');
529                foreach ($subscriptions as $id => $data) {
530                    self::add_to_discussion_cache($forumid, $data->userid, $data->discussion, $data->preference);
531                }
532                self::$discussionfetchedforums[$forumid] = true;
533                $subscriptions->close();
534            }
535        }
536    }
537
538    /**
539     * Add the specified discussion and user preference to the discussion
540     * subscription cache.
541     *
542     * @param int $forumid The ID of the forum that this preference belongs to
543     * @param int $userid The ID of the user that this preference belongs to
544     * @param int $discussion The ID of the discussion that this preference relates to
545     * @param int $preference The preference to store
546     */
547    protected static function add_to_discussion_cache($forumid, $userid, $discussion, $preference) {
548        if (!isset(self::$forumdiscussioncache[$userid])) {
549            self::$forumdiscussioncache[$userid] = array();
550        }
551
552        if (!isset(self::$forumdiscussioncache[$userid][$forumid])) {
553            self::$forumdiscussioncache[$userid][$forumid] = array();
554        }
555
556        self::$forumdiscussioncache[$userid][$forumid][$discussion] = $preference;
557    }
558
559    /**
560     * Reset the discussion cache.
561     *
562     * This cache is used to reduce the number of database queries when
563     * checking forum discussion subscription states.
564     */
565    public static function reset_discussion_cache() {
566        self::$forumdiscussioncache = array();
567        self::$discussionfetchedforums = array();
568    }
569
570    /**
571     * Reset the forum cache.
572     *
573     * This cache is used to reduce the number of database queries when
574     * checking forum subscription states.
575     */
576    public static function reset_forum_cache() {
577        self::$forumcache = array();
578        self::$fetchedforums = array();
579    }
580
581    /**
582     * Adds user to the subscriber list.
583     *
584     * @param int $userid The ID of the user to subscribe
585     * @param \stdClass $forum The forum record for this forum.
586     * @param \context_module|null $context Module context, may be omitted if not known or if called for the current
587     *      module set in page.
588     * @param boolean $userrequest Whether the user requested this change themselves. This has an effect on whether
589     *     discussion subscriptions are removed too.
590     * @return bool|int Returns true if the user is already subscribed, or the forum_subscriptions ID if the user was
591     *     successfully subscribed.
592     */
593    public static function subscribe_user($userid, $forum, $context = null, $userrequest = false) {
594        global $DB;
595
596        if (self::is_subscribed($userid, $forum)) {
597            return true;
598        }
599
600        $sub = new \stdClass();
601        $sub->userid  = $userid;
602        $sub->forum = $forum->id;
603
604        $result = $DB->insert_record("forum_subscriptions", $sub);
605
606        if ($userrequest) {
607            $discussionsubscriptions = $DB->get_recordset('forum_discussion_subs', array('userid' => $userid, 'forum' => $forum->id));
608            $DB->delete_records_select('forum_discussion_subs',
609                    'userid = :userid AND forum = :forumid AND preference <> :preference', array(
610                        'userid' => $userid,
611                        'forumid' => $forum->id,
612                        'preference' => self::FORUM_DISCUSSION_UNSUBSCRIBED,
613                    ));
614
615            // Reset the subscription caches for this forum.
616            // We know that the there were previously entries and there aren't any more.
617            if (isset(self::$forumdiscussioncache[$userid]) && isset(self::$forumdiscussioncache[$userid][$forum->id])) {
618                foreach (self::$forumdiscussioncache[$userid][$forum->id] as $discussionid => $preference) {
619                    if ($preference != self::FORUM_DISCUSSION_UNSUBSCRIBED) {
620                        unset(self::$forumdiscussioncache[$userid][$forum->id][$discussionid]);
621                    }
622                }
623            }
624        }
625
626        // Reset the cache for this forum.
627        self::$forumcache[$userid][$forum->id] = true;
628
629        $context = forum_get_context($forum->id, $context);
630        $params = array(
631            'context' => $context,
632            'objectid' => $result,
633            'relateduserid' => $userid,
634            'other' => array('forumid' => $forum->id),
635
636        );
637        $event  = event\subscription_created::create($params);
638        if ($userrequest && $discussionsubscriptions) {
639            foreach ($discussionsubscriptions as $subscription) {
640                $event->add_record_snapshot('forum_discussion_subs', $subscription);
641            }
642            $discussionsubscriptions->close();
643        }
644        $event->trigger();
645
646        return $result;
647    }
648
649    /**
650     * Removes user from the subscriber list
651     *
652     * @param int $userid The ID of the user to unsubscribe
653     * @param \stdClass $forum The forum record for this forum.
654     * @param \context_module|null $context Module context, may be omitted if not known or if called for the current
655     *     module set in page.
656     * @param boolean $userrequest Whether the user requested this change themselves. This has an effect on whether
657     *     discussion subscriptions are removed too.
658     * @return boolean Always returns true.
659     */
660    public static function unsubscribe_user($userid, $forum, $context = null, $userrequest = false) {
661        global $DB;
662
663        $sqlparams = array(
664            'userid' => $userid,
665            'forum' => $forum->id,
666        );
667        $DB->delete_records('forum_digests', $sqlparams);
668
669        if ($forumsubscription = $DB->get_record('forum_subscriptions', $sqlparams)) {
670            $DB->delete_records('forum_subscriptions', array('id' => $forumsubscription->id));
671
672            if ($userrequest) {
673                $discussionsubscriptions = $DB->get_recordset('forum_discussion_subs', $sqlparams);
674                $DB->delete_records('forum_discussion_subs',
675                        array('userid' => $userid, 'forum' => $forum->id, 'preference' => self::FORUM_DISCUSSION_UNSUBSCRIBED));
676
677                // We know that the there were previously entries and there aren't any more.
678                if (isset(self::$forumdiscussioncache[$userid]) && isset(self::$forumdiscussioncache[$userid][$forum->id])) {
679                    self::$forumdiscussioncache[$userid][$forum->id] = array();
680                }
681            }
682
683            // Reset the cache for this forum.
684            self::$forumcache[$userid][$forum->id] = false;
685
686            $context = forum_get_context($forum->id, $context);
687            $params = array(
688                'context' => $context,
689                'objectid' => $forumsubscription->id,
690                'relateduserid' => $userid,
691                'other' => array('forumid' => $forum->id),
692
693            );
694            $event = event\subscription_deleted::create($params);
695            $event->add_record_snapshot('forum_subscriptions', $forumsubscription);
696            if ($userrequest && $discussionsubscriptions) {
697                foreach ($discussionsubscriptions as $subscription) {
698                    $event->add_record_snapshot('forum_discussion_subs', $subscription);
699                }
700                $discussionsubscriptions->close();
701            }
702            $event->trigger();
703        }
704
705        return true;
706    }
707
708    /**
709     * Subscribes the user to the specified discussion.
710     *
711     * @param int $userid The userid of the user being subscribed
712     * @param \stdClass $discussion The discussion to subscribe to
713     * @param \context_module|null $context Module context, may be omitted if not known or if called for the current
714     *     module set in page.
715     * @return boolean Whether a change was made
716     */
717    public static function subscribe_user_to_discussion($userid, $discussion, $context = null) {
718        global $DB;
719
720        // First check whether the user is subscribed to the discussion already.
721        $subscription = $DB->get_record('forum_discussion_subs', array('userid' => $userid, 'discussion' => $discussion->id));
722        if ($subscription) {
723            if ($subscription->preference != self::FORUM_DISCUSSION_UNSUBSCRIBED) {
724                // The user is already subscribed to the discussion. Ignore.
725                return false;
726            }
727        }
728        // No discussion-level subscription. Check for a forum level subscription.
729        if ($DB->record_exists('forum_subscriptions', array('userid' => $userid, 'forum' => $discussion->forum))) {
730            if ($subscription && $subscription->preference == self::FORUM_DISCUSSION_UNSUBSCRIBED) {
731                // The user is subscribed to the forum, but unsubscribed from the discussion, delete the discussion preference.
732                $DB->delete_records('forum_discussion_subs', array('id' => $subscription->id));
733                unset(self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id]);
734            } else {
735                // The user is already subscribed to the forum. Ignore.
736                return false;
737            }
738        } else {
739            if ($subscription) {
740                $subscription->preference = time();
741                $DB->update_record('forum_discussion_subs', $subscription);
742            } else {
743                $subscription = new \stdClass();
744                $subscription->userid  = $userid;
745                $subscription->forum = $discussion->forum;
746                $subscription->discussion = $discussion->id;
747                $subscription->preference = time();
748
749                $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription);
750                self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id] = $subscription->preference;
751            }
752        }
753
754        $context = forum_get_context($discussion->forum, $context);
755        $params = array(
756            'context' => $context,
757            'objectid' => $subscription->id,
758            'relateduserid' => $userid,
759            'other' => array(
760                'forumid' => $discussion->forum,
761                'discussion' => $discussion->id,
762            ),
763
764        );
765        $event  = event\discussion_subscription_created::create($params);
766        $event->trigger();
767
768        return true;
769    }
770    /**
771     * Unsubscribes the user from the specified discussion.
772     *
773     * @param int $userid The userid of the user being unsubscribed
774     * @param \stdClass $discussion The discussion to unsubscribe from
775     * @param \context_module|null $context Module context, may be omitted if not known or if called for the current
776     *     module set in page.
777     * @return boolean Whether a change was made
778     */
779    public static function unsubscribe_user_from_discussion($userid, $discussion, $context = null) {
780        global $DB;
781
782        // First check whether the user's subscription preference for this discussion.
783        $subscription = $DB->get_record('forum_discussion_subs', array('userid' => $userid, 'discussion' => $discussion->id));
784        if ($subscription) {
785            if ($subscription->preference == self::FORUM_DISCUSSION_UNSUBSCRIBED) {
786                // The user is already unsubscribed from the discussion. Ignore.
787                return false;
788            }
789        }
790        // No discussion-level preference. Check for a forum level subscription.
791        if (!$DB->record_exists('forum_subscriptions', array('userid' => $userid, 'forum' => $discussion->forum))) {
792            if ($subscription && $subscription->preference != self::FORUM_DISCUSSION_UNSUBSCRIBED) {
793                // The user is not subscribed to the forum, but subscribed from the discussion, delete the discussion subscription.
794                $DB->delete_records('forum_discussion_subs', array('id' => $subscription->id));
795                unset(self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id]);
796            } else {
797                // The user is not subscribed from the forum. Ignore.
798                return false;
799            }
800        } else {
801            if ($subscription) {
802                $subscription->preference = self::FORUM_DISCUSSION_UNSUBSCRIBED;
803                $DB->update_record('forum_discussion_subs', $subscription);
804            } else {
805                $subscription = new \stdClass();
806                $subscription->userid  = $userid;
807                $subscription->forum = $discussion->forum;
808                $subscription->discussion = $discussion->id;
809                $subscription->preference = self::FORUM_DISCUSSION_UNSUBSCRIBED;
810
811                $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription);
812            }
813            self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id] = $subscription->preference;
814        }
815
816        $context = forum_get_context($discussion->forum, $context);
817        $params = array(
818            'context' => $context,
819            'objectid' => $subscription->id,
820            'relateduserid' => $userid,
821            'other' => array(
822                'forumid' => $discussion->forum,
823                'discussion' => $discussion->id,
824            ),
825
826        );
827        $event  = event\discussion_subscription_deleted::create($params);
828        $event->trigger();
829
830        return true;
831    }
832
833    /**
834     * Gets the default subscription value for the logged in user.
835     *
836     * @param \stdClass $forum The forum record
837     * @param \context $context The course context
838     * @param \cm_info $cm cm_info
839     * @param int|null $discussionid The discussion we are checking against
840     * @return bool Default subscription
841     * @throws coding_exception
842     */
843    public static function get_user_default_subscription($forum, $context, $cm, ?int $discussionid) {
844        global $USER;
845        $manageactivities = has_capability('moodle/course:manageactivities', $context);
846        if (\mod_forum\subscriptions::subscription_disabled($forum) && !$manageactivities) {
847            // User does not have permission to subscribe to this discussion at all.
848            $discussionsubscribe = false;
849        } else if (\mod_forum\subscriptions::is_forcesubscribed($forum)) {
850            // User does not have permission to unsubscribe from this discussion at all.
851            $discussionsubscribe = true;
852        } else {
853            if (isset($discussionid) && self::is_subscribed($USER->id, $forum, $discussionid, $cm)) {
854                // User is subscribed to the discussion - continue the subscription.
855                $discussionsubscribe = true;
856            } else if (!isset($discussionid) && \mod_forum\subscriptions::is_subscribed($USER->id, $forum, null, $cm)) {
857                // Starting a new discussion, and the user is subscribed to the forum - subscribe to the discussion.
858                $discussionsubscribe = true;
859            } else {
860                // User is not subscribed to either forum or discussion. Follow user preference.
861                $discussionsubscribe = $USER->autosubscribe ?? false;
862            }
863        }
864
865        return $discussionsubscribe;
866    }
867}
868