From e3bbfb52c4b5d951804934f4eacbcb682141fc14 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Thu, 5 Jun 2014 21:27:51 +0800 Subject: [PATCH] MDL-1626 mod_forum: Add functions to change user discussion subscription state --- .../event/discussion_subscription_created.php | 112 ++ .../event/discussion_subscription_deleted.php | 112 ++ mod/forum/classes/subscriptions.php | 397 +++++- mod/forum/index.php | 4 +- mod/forum/lang/en/forum.php | 2 + mod/forum/lib.php | 2 +- mod/forum/subscribe.php | 6 +- mod/forum/tests/events_test.php | 869 +++++++++++++ mod/forum/tests/lib_test.php | 3 + mod/forum/tests/subscriptions_test.php | 1101 +++++++++++++++++ mod/forum/unsubscribeall.php | 2 +- 11 files changed, 2582 insertions(+), 28 deletions(-) create mode 100644 mod/forum/classes/event/discussion_subscription_created.php create mode 100644 mod/forum/classes/event/discussion_subscription_deleted.php diff --git a/mod/forum/classes/event/discussion_subscription_created.php b/mod/forum/classes/event/discussion_subscription_created.php new file mode 100644 index 00000000000..d430292fc6b --- /dev/null +++ b/mod/forum/classes/event/discussion_subscription_created.php @@ -0,0 +1,112 @@ +. + +/** + * The mod_forum discussion_subscription created event. + * + * @package mod_forum + * @copyright 2014 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_forum\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_forum discussion_subscription created event class. + * + * @property-read array $other { + * Extra information about the event. + * + * - int forumid: The id of the forum which the discussion is in. + * - int discussion: The id of the discussion which has been subscribed to. + * } + * + * @package mod_forum + * @since Moodle 2.8 + * @copyright 2014 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class discussion_subscription_created extends \core\event\base { + /** + * Init method. + * + * @return void + */ + protected function init() { + $this->data['crud'] = 'c'; + $this->data['edulevel'] = self::LEVEL_PARTICIPATING; + $this->data['objecttable'] = 'forum_discussion_subs'; + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' subscribed the user with id '$this->relateduserid' to the discussion " . + " with id '{$this->other['discussion']}' in the forum with the course module id '$this->contextinstanceid'."; + } + + /** + * Return localised event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventdiscussionsubscriptioncreated', 'mod_forum'); + } + + /** + * Get URL related to the action. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/mod/forum/subscribe.php', array( + 'id' => $this->other['forumid'], + 'd' => $this->other['discussion'], + )); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->relateduserid)) { + throw new \coding_exception('The \'relateduserid\' must be set.'); + } + + if (!isset($this->other['forumid'])) { + throw new \coding_exception('The \'forumid\' value must be set in other.'); + } + + if (!isset($this->other['discussion'])) { + throw new \coding_exception('The \'discussion\' value must be set in other.'); + } + + if ($this->contextlevel != CONTEXT_MODULE) { + throw new \coding_exception('Context level must be CONTEXT_MODULE.'); + } + } +} diff --git a/mod/forum/classes/event/discussion_subscription_deleted.php b/mod/forum/classes/event/discussion_subscription_deleted.php new file mode 100644 index 00000000000..7a773912eba --- /dev/null +++ b/mod/forum/classes/event/discussion_subscription_deleted.php @@ -0,0 +1,112 @@ +. + +/** + * The mod_forum discussion_subscription deleted event. + * + * @package mod_forum + * @copyright 2014 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_forum\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_forum discussion_subscription deleted event class. + * + * @property-read array $other { + * Extra information about the event. + * + * - int forumid: The id of the forum which the discussion is in. + * - int discussion: The id of the discussion which has been unsubscribed from. + * } + * + * @package mod_forum + * @since Moodle 2.8 + * @copyright 2014 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class discussion_subscription_deleted extends \core\event\base { + /** + * Init method. + * + * @return void + */ + protected function init() { + $this->data['crud'] = 'd'; + $this->data['edulevel'] = self::LEVEL_PARTICIPATING; + $this->data['objecttable'] = 'forum_discussion_subs'; + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' unsubscribed the user with id '$this->relateduserid' from the discussion " . + " with id '{$this->other['discussion']}' in the forum with the course module id '$this->contextinstanceid'."; + } + + /** + * Return localised event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventdiscussionsubscriptiondeleted', 'mod_forum'); + } + + /** + * Get URL related to the action. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/mod/forum/subscribe.php', array( + 'id' => $this->other['forumid'], + 'd' => $this->other['discussion'], + )); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->relateduserid)) { + throw new \coding_exception('The \'relateduserid\' must be set.'); + } + + if (!isset($this->other['forumid'])) { + throw new \coding_exception('The \'forumid\' value must be set in other.'); + } + + if (!isset($this->other['discussion'])) { + throw new \coding_exception('The \'discussion\' value must be set in other.'); + } + + if ($this->contextlevel != CONTEXT_MODULE) { + throw new \coding_exception('Context level must be CONTEXT_MODULE.'); + } + } +} diff --git a/mod/forum/classes/subscriptions.php b/mod/forum/classes/subscriptions.php index 68c32c5f97e..60349e050d7 100644 --- a/mod/forum/classes/subscriptions.php +++ b/mod/forum/classes/subscriptions.php @@ -33,6 +33,21 @@ defined('MOODLE_INTERNAL') || die(); * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class subscriptions { + + /** + * The status value for a subscribed discussion. + * + * @const int + */ + const FORUM_DISCUSSION_SUBSCRIBED = 1; + + /** + * The status value for an unsubscribed discussion. + * + * @var int + */ + const FORUM_DISCUSSION_UNSUBSCRIBED = -1; + /** * The subscription cache for forums. * @@ -55,13 +70,43 @@ class subscriptions { protected static $fetchedforums = array(); /** - * Whether a user is subscribed to this forum. + * The subscription cache for forum discussions. + * + * The first level key is the user ID + * The second level is the forum ID + * The third level key is the discussion ID + * The value is then the users preference (int) + * + * @var array[] + */ + protected static $forumdiscussioncache = array(); + + /** + * The list of forums which have been wholly retrieved for the forum discussion subscription cache. + * + * This allows for prior caching of an entire forum to reduce the + * number of DB queries in a subscription check loop. + * + * @var bool[] + */ + protected static $discussionfetchedforums = array(); + + /** + * Whether a user is subscribed to this forum, or a discussion within + * the forum. + * + * If a discussion is specified, then report whether the user is + * subscribed to posts to this particular discussion, taking into + * account the forum preference. + * + * If it is not specified then only the forum preference is considered. * * @param int $userid The user ID * @param \stdClass $forum The record of the forum to test + * @param int $discussionid The ID of the discussion to check * @return boolean */ - private static function is_subscribed($userid, $forum) { + public static function is_subscribed($userid, $forum, $discussionid = null) { // If forum is force subscribed and has allowforcesubscribe, then user is subscribed. $cm = get_coursemodule_from_instance('forum', $forum->id); if ($cm && self::is_forcesubscribed($forum) && @@ -69,6 +114,28 @@ class subscriptions { return true; } + if ($discussionid === null) { + return self::is_subscribed_to_forum($userid, $forum); + } + + $subscriptions = self::fetch_discussion_subscription($forum->id, $userid); + + // Check whether there is a record for this discussion subscription. + if (isset($subscriptions[$discussionid])) { + return ($subscriptions[$discussionid] == self::FORUM_DISCUSSION_SUBSCRIBED); + } + + return self::is_subscribed_to_forum($userid, $forum); + } + + /** + * Whether a user is subscribed to this forum. + * + * @param int $userid The user ID + * @param \stdClass $forum The record of the forum to test + * @return boolean + */ + protected static function is_subscribed_to_forum($userid, $forum) { return self::fetch_subscription_cache($forum->id, $userid); } @@ -148,14 +215,18 @@ class subscriptions { } list($coursesql, $courseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'c'); - // get all forums from the user's courses that they are subscribed to and which are not set to forced - $sql = "SELECT f.id, cm.id as cm, cm.visible + // Get all forums from the user's courses that they are subscribed to and which are not set to forced. + // It is possible for users to be subscribed to a forum in subscription disallowed mode so they must be listed + // here so that that can be unsubscribed from. + $sql = "SELECT f.id, cm.id as cm, cm.visible, f.course FROM {forum} f - JOIN {course_modules} cm ON cm.instance = f.id - JOIN {modules} m ON m.name = :modulename AND m.id = cm.module - LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid) - WHERE f.forcesubscribe <> :forcesubscribe AND fs.id IS NOT NULL - AND cm.course $coursesql"; + JOIN {course_modules} cm ON cm.instance = f.id + JOIN {modules} m ON m.name = :modulename AND m.id = cm.module + LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid) + WHERE f.forcesubscribe <> :forcesubscribe + AND fs.id IS NOT NULL + AND cm.course + $coursesql"; $params = array_merge($courseparams, array( 'modulename'=>'forum', 'userid' => $USER->id, @@ -163,11 +234,11 @@ class subscriptions { )); $forums = $DB->get_recordset_sql($sql, $params); - $unsubscribableforums = array(); // Array to return + $unsubscribableforums = array(); foreach($forums as $forum) { if (empty($forum->visible)) { // The forum is hidden - check if the user can view the forum. - $context = context_module::instance($forum->cm); + $context = \context_module::instance($forum->cm); if (!has_capability('moodle/course:viewhiddenactivities', $context)) { // The user can't see the hidden forum to cannot unsubscribe. continue; @@ -315,9 +386,11 @@ class subscriptions { * @param int $groupid The group id if restricting subscriptions to a group of users, or 0 for all. * @param context_module $context the forum context, to save re-fetching it where possible. * @param string $fields requested user fields (with "u." table prefix). + * @param boolean $includediscussionsubscriptions Whether to take discussion subscriptions and unsubscriptions into consideration. * @return array list of users. */ - public static function fetch_subscribed_users($forum, $groupid = 0, $context = null, $fields = null) { + public static function fetch_subscribed_users($forum, $groupid = 0, $context = null, $fields = null, + $includediscussionsubscriptions = false) { global $CFG, $DB; if (empty($fields)) { @@ -354,13 +427,33 @@ class subscriptions { list($esql, $params) = get_enrolled_sql($context, '', $groupid, true); $params['forumid'] = $forum->id; - $sql = "SELECT $fields - FROM {user} u - JOIN ($esql) je ON je.id = u.id - JOIN {forum_subscriptions} s ON s.userid = u.id - WHERE - s.forum = :forumid - ORDER BY u.email ASC"; + if ($includediscussionsubscriptions) { + $params['dforumid'] = $forum->id; + $params['unsubscribed'] = self::FORUM_DISCUSSION_UNSUBSCRIBED; + + $sql = "SELECT $fields + FROM {user} u + WHERE u.id IN ( + SELECT u2.id FROM + {user} u2 + JOIN ($esql) je ON je.id = u.id + LEFT JOIN {forum_subscriptions} s ON s.userid = u2.id + LEFT JOIN {forum_discussion_subs} ds ON ds.userid = u2.id + WHERE + s.forum = :forumid + OR + (ds.forum = :dforumid AND ds.preference <> :unsubscribed) + ) + ORDER BY u.email ASC"; + } else { + $sql = "SELECT $fields + FROM {user} u + JOIN ($esql) je ON je.id = u.id + JOIN {forum_subscriptions} s ON s.userid = u.id + WHERE + s.forum = :forumid + ORDER BY u.email ASC"; + } $results = $DB->get_records_sql($sql, $params); } @@ -370,6 +463,100 @@ class subscriptions { return $results; } + /** + * Retrieve the discussion subscription data for the specified userid and forum. + * + * This is returned as an array of discussions for that forum which contain the preference in a stdClass. + * + * @param int $forumid The forum to retrieve a cache for + * @param int $userid The user ID + * @return array of stdClass objects with one per discussion in the forum. + */ + public static function fetch_discussion_subscription($forumid, $userid = null) { + self::fill_discussion_subscription_cache($forumid, $userid); + + if (!isset(self::$forumdiscussioncache[$userid]) || !isset(self::$forumdiscussioncache[$userid][$forumid])) { + return array(); + } + + return self::$forumdiscussioncache[$userid][$forumid]; + } + + /** + * Fill the discussion subscription data for the specified userid and forum. + * + * If the userid is not specified, then all discussion subscription data for that forum is fetched in a single query + * and used for subsequent lookups without requiring further database queries. + * + * @param int $forumid The forum to retrieve a cache for + * @param int $userid The user ID + * @return void + */ + public static function fill_discussion_subscription_cache($forumid, $userid = null) { + global $DB; + + if (!isset(self::$discussionfetchedforums[$forumid])) { + // This forum hasn't been fetched as a whole yet. + if (isset($userid)) { + if (!isset(self::$forumdiscussioncache[$userid])) { + self::$forumdiscussioncache[$userid] = array(); + } + + if (!isset(self::$forumdiscussioncache[$userid][$forumid])) { + $subscriptions = $DB->get_recordset('forum_discussion_subs', array( + 'userid' => $userid, + 'forum' => $forumid, + ), null, 'id, discussion, preference'); + foreach ($subscriptions as $id => $data) { + self::add_to_discussion_cache($forumid, $userid, $data->discussion, $data->preference); + } + $subscriptions->close(); + } + } else { + $subscriptions = $DB->get_recordset('forum_discussion_subs', array( + 'forum' => $forumid, + ), null, 'id, userid, discussion, preference'); + foreach ($subscriptions as $id => $data) { + self::add_to_discussion_cache($forumid, $data->userid, $data->discussion, $data->preference); + } + self::$discussionfetchedforums[$forumid] = true; + $subscriptions->close(); + } + } + } + + /** + * Add the specified discussion and user preference to the discussion + * subscription cache. + * + * @param int $forumid The ID of the forum that this preference belongs to + * @param int $userid The ID of the user that this preference belongs to + * @param int $discussion The ID of the discussion that this preference relates to + * @param int $preference The preference to store + */ + protected static function add_to_discussion_cache($forumid, $userid, $discussion, $preference) { + if (!isset(self::$forumdiscussioncache[$userid])) { + self::$forumdiscussioncache[$userid] = array(); + } + + if (!isset(self::$forumdiscussioncache[$userid][$forumid])) { + self::$forumdiscussioncache[$userid][$forumid] = array(); + } + + self::$forumdiscussioncache[$userid][$forumid][$discussion] = $preference; + } + + /** + * Reset the discussion cache. + * + * This cache is used to reduce the number of database queries when + * checking forum discussion subscription states. + */ + public static function reset_discussion_cache() { + self::$forumdiscussioncache = array(); + self::$discussionfetchedforums = array(); + } + /** * Reset the forum cache. * @@ -388,10 +575,12 @@ class subscriptions { * @param \stdClass $forum The forum record for this forum. * @param \context_module|null $context Module context, may be omitted if not known or if called for the current * module set in page. + * @param boolean $userrequest Whether the user requested this change themselves. This has an effect on whether + * discussion subscriptions are removed too. * @return bool|int Returns true if the user is already subscribed, or the forum_subscriptions ID if the user was * successfully subscribed. */ - public static function subscribe_user($userid, $forum, $context = null) { + public static function subscribe_user($userid, $forum, $context = null, $userrequest = false) { global $DB; if (self::is_subscribed($userid, $forum)) { @@ -404,6 +593,22 @@ class subscriptions { $result = $DB->insert_record("forum_subscriptions", $sub); + if ($userrequest) { + $discussionsubscriptions = $DB->get_recordset('forum_discussion_subs', array('userid' => $userid, 'forum' => $forum->id)); + $DB->delete_records('forum_discussion_subs', + array('userid' => $userid, 'forum' => $forum->id, 'preference' => self::FORUM_DISCUSSION_SUBSCRIBED)); + + // Reset the subscription caches for this forum. + // We know that the there were previously entries and there aren't any more. + if (isset(self::$forumdiscussioncache[$userid]) && isset(self::$forumdiscussioncache[$userid][$forum->id])) { + foreach (self::$forumdiscussioncache[$userid][$forum->id] as $discussionid => $preference) { + if ($preference == self::FORUM_DISCUSSION_SUBSCRIBED) { + unset(self::$forumdiscussioncache[$userid][$forum->id][$discussionid]); + } + } + } + } + // Reset the cache for this forum. self::$forumcache[$userid][$forum->id] = true; @@ -416,6 +621,12 @@ class subscriptions { ); $event = event\subscription_created::create($params); + if ($userrequest && $discussionsubscriptions) { + foreach ($discussionsubscriptions as $subscription) { + $event->add_record_snapshot('forum_discussion_subs', $subscription); + } + $discussionsubscriptions->close(); + } $event->trigger(); return $result; @@ -428,9 +639,11 @@ class subscriptions { * @param \stdClass $forum The forum record for this forum. * @param \context_module|null $context Module context, may be omitted if not known or if called for the current * module set in page. + * @param boolean $userrequest Whether the user requested this change themselves. This has an effect on whether + * discussion subscriptions are removed too. * @return boolean Always returns true. */ - public static function unsubscribe_user($userid, $forum, $context = null) { + public static function unsubscribe_user($userid, $forum, $context = null, $userrequest = false) { global $DB; $sqlparams = array( @@ -442,6 +655,17 @@ class subscriptions { if ($forumsubscription = $DB->get_record('forum_subscriptions', $sqlparams)) { $DB->delete_records('forum_subscriptions', array('id' => $forumsubscription->id)); + if ($userrequest) { + $discussionsubscriptions = $DB->get_recordset('forum_discussion_subs', $sqlparams); + $DB->delete_records('forum_discussion_subs', + array('userid' => $userid, 'forum' => $forum->id, 'preference' => self::FORUM_DISCUSSION_UNSUBSCRIBED)); + + // We know that the there were previously entries and there aren't any more. + if (isset(self::$forumdiscussioncache[$userid]) && isset(self::$forumdiscussioncache[$userid][$forum->id])) { + self::$forumdiscussioncache[$userid][$forum->id] = array(); + } + } + // Reset the cache for this forum. self::$forumcache[$userid][$forum->id] = false; @@ -455,10 +679,141 @@ class subscriptions { ); $event = event\subscription_deleted::create($params); $event->add_record_snapshot('forum_subscriptions', $forumsubscription); + if ($userrequest && $discussionsubscriptions) { + foreach ($discussionsubscriptions as $subscription) { + $event->add_record_snapshot('forum_discussion_subs', $subscription); + } + $discussionsubscriptions->close(); + } $event->trigger(); } return true; } + /** + * Subscribes the user to the specified discussion. + * + * @param int $userid The userid of the user being subscribed + * @param \stdClass $discussion The discussion to subscribe to + * @param \context_module|null $context Module context, may be omitted if not known or if called for the current + * module set in page. + * @return boolean Whether a change was made + */ + public static function subscribe_user_to_discussion($userid, $discussion, $context = null) { + global $DB; + + // First check whether the user is subscribed to the discussion already. + $subscription = $DB->get_record('forum_discussion_subs', array('userid' => $userid, 'discussion' => $discussion->id)); + if ($subscription) { + if ($subscription->preference == self::FORUM_DISCUSSION_SUBSCRIBED) { + // The user is already subscribed to the discussion. Ignore. + return false; + } + } + // No discussion-level subscription. Check for a forum level subscription. + if ($DB->record_exists('forum_subscriptions', array('userid' => $userid, 'forum' => $discussion->forum))) { + if ($subscription && $subscription->preference == self::FORUM_DISCUSSION_UNSUBSCRIBED) { + // The user is subscribed to the forum, but unsubscribed from the discussion, delete the discussion preference. + $DB->delete_records('forum_discussion_subs', array('id' => $subscription->id)); + unset(self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id]); + } else { + // The user is already subscribed to the forum. Ignore. + return false; + } + } else { + if ($subscription) { + $subscription->preference = self::FORUM_DISCUSSION_SUBSCRIBED; + $DB->update_record('forum_discussion_subs', $subscription); + } else { + $subscription = new \stdClass(); + $subscription->userid = $userid; + $subscription->forum = $discussion->forum; + $subscription->discussion = $discussion->id; + $subscription->preference = self::FORUM_DISCUSSION_SUBSCRIBED; + + $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription); + self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id] = true; + } + } + + $context = forum_get_context($discussion->forum, $context); + $params = array( + 'context' => $context, + 'objectid' => $subscription->id, + 'relateduserid' => $userid, + 'other' => array( + 'forumid' => $discussion->forum, + 'discussion' => $discussion->id, + ), + + ); + $event = event\discussion_subscription_created::create($params); + $event->trigger(); + + return true; + } + /** + * Unsubscribes the user from the specified discussion. + * + * @param int $userid The userid of the user being unsubscribed + * @param \stdClass $discussion The discussion to unsubscribe from + * @param \context_module|null $context Module context, may be omitted if not known or if called for the current + * module set in page. + * @return boolean Whether a change was made + */ + public static function unsubscribe_user_from_discussion($userid, $discussion, $context = null) { + global $DB; + + // First check whether the user's subscription preference for this discussion. + $subscription = $DB->get_record('forum_discussion_subs', array('userid' => $userid, 'discussion' => $discussion->id)); + if ($subscription) { + if ($subscription->preference == self::FORUM_DISCUSSION_UNSUBSCRIBED) { + // The user is already unsubscribed from the discussion. Ignore. + return false; + } + } + // No discussion-level preference. Check for a forum level subscription. + if (!$DB->record_exists('forum_subscriptions', array('userid' => $userid, 'forum' => $discussion->forum))) { + if ($subscription && $subscription->preference == self::FORUM_DISCUSSION_SUBSCRIBED) { + // The user is not subscribed to the forum, but subscribed from the discussion, delete the discussion subscription. + $DB->delete_records('forum_discussion_subs', array('id' => $subscription->id)); + unset(self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id]); + } else { + // The user is not subscribed from the forum. Ignore. + return false; + } + } else { + if ($subscription) { + $subscription->preference = self::FORUM_DISCUSSION_UNSUBSCRIBED; + $DB->update_record('forum_discussion_subs', $subscription); + } else { + $subscription = new \stdClass(); + $subscription->userid = $userid; + $subscription->forum = $discussion->forum; + $subscription->discussion = $discussion->id; + $subscription->preference = self::FORUM_DISCUSSION_UNSUBSCRIBED; + + $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription); + } + self::$forumdiscussioncache[$userid][$discussion->forum][$discussion->id] = false; + } + + $context = forum_get_context($discussion->forum, $context); + $params = array( + 'context' => $context, + 'objectid' => $subscription->id, + 'relateduserid' => $userid, + 'other' => array( + 'forumid' => $discussion->forum, + 'discussion' => $discussion->id, + ), + + ); + $event = event\discussion_subscription_deleted::create($params); + $event->trigger(); + + return true; + } + } diff --git a/mod/forum/index.php b/mod/forum/index.php index edfd496a005..1d4ac71b640 100644 --- a/mod/forum/index.php +++ b/mod/forum/index.php @@ -195,9 +195,9 @@ if (!is_null($subscribe)) { $subscribed = \mod_forum\subscriptions::is_subscribed($USER->id, $forum); $canmanageactivities = has_capability('moodle/course:manageactivities', $coursecontext, $USER->id); if (($canmanageactivities || \mod_forum\subscriptions::is_subscribable($forum)) && $subscribe && !$subscribed && $cansub) { - \mod_forum\subscriptions::subscribe_user($USER->id, $forum, $modcontext); + \mod_forum\subscriptions::subscribe_user($USER->id, $forum, $modcontext, true); } else if (!$subscribe && $subscribed) { - \mod_forum\subscriptions::unsubscribe_user($USER->id, $forum, $modcontext); + \mod_forum\subscriptions::unsubscribe_user($USER->id, $forum, $modcontext, true); } } } diff --git a/mod/forum/lang/en/forum.php b/mod/forum/lang/en/forum.php index ffbcac4330a..e1a7b57c4f4 100644 --- a/mod/forum/lang/en/forum.php +++ b/mod/forum/lang/en/forum.php @@ -150,6 +150,8 @@ $string['eventdiscussionupdated'] = 'Discussion updated'; $string['eventdiscussiondeleted'] = 'Discussion deleted'; $string['eventdiscussionmoved'] = 'Discussion moved'; $string['eventdiscussionviewed'] = 'Discussion viewed'; +$string['eventdiscussionsubscriptioncreated'] = 'Discussion subscription created'; +$string['eventdiscussionsubscriptiondeleted'] = 'Discussion subscription deleted'; $string['eventuserreportviewed'] = 'User report viewed'; $string['eventpostcreated'] = 'Post created'; $string['eventpostdeleted'] = 'Post deleted'; diff --git a/mod/forum/lib.php b/mod/forum/lib.php index f6f19e4856b..bd04986c288 100644 --- a/mod/forum/lib.php +++ b/mod/forum/lib.php @@ -546,7 +546,7 @@ function forum_cron() { // caching subscribed users of each forum if (!isset($subscribedusers[$forumid])) { $modcontext = context_module::instance($coursemodules[$forumid]->id); - if ($subusers = \mod_forum\subscriptions::fetch_subscribed_users($forums[$forumid], 0, $modcontext, 'u.*')) { + if ($subusers = \mod_forum\subscriptions::fetch_subscribed_users($forums[$forumid], 0, $modcontext, 'u.*', true)) { foreach ($subusers as $postuser) { // this user is subscribed to this forum $subscribedusers[$forumid][$postuser->id] = $postuser->id; diff --git a/mod/forum/subscribe.php b/mod/forum/subscribe.php index 8ab67a8e403..014b93bfe35 100644 --- a/mod/forum/subscribe.php +++ b/mod/forum/subscribe.php @@ -113,7 +113,7 @@ if (!is_null($mode) and has_capability('mod/forum:managesubscriptions', $context if ($forum->forcesubscribe <> FORUM_INITIALSUBSCRIBE) { $users = \mod_forum\subscriptions::get_potential_subscribers($context, 0, 'u.id, u.email', ''); foreach ($users as $user) { - \mod_forum\subscriptions::subscribe_user($user->id, $forum); + \mod_forum\subscriptions::subscribe_user($user->id, $forum, $context); } } \mod_forum\subscriptions::set_subscription_mode($forum->id, FORUM_INITIALSUBSCRIBE); @@ -147,7 +147,7 @@ if (\mod_forum\subscriptions::is_subscribed($user->id, $forum)) { exit; } require_sesskey(); - if (\mod_forum\subscriptions::unsubscribe_user($user->id, $forum)) { + if (\mod_forum\subscriptions::unsubscribe_user($user->id, $forum, $context, true)) { redirect($returnto, get_string("nownotsubscribed", "forum", $info), 1); } else { print_error('cannotunsubscribe', 'forum', $_SERVER["HTTP_REFERER"]); @@ -170,6 +170,6 @@ if (\mod_forum\subscriptions::is_subscribed($user->id, $forum)) { exit; } require_sesskey(); - \mod_forum\subscriptions::subscribe_user($user->id, $forum); + \mod_forum\subscriptions::subscribe_user($user->id, $forum, $context, true); redirect($returnto, get_string("nowsubscribed", "forum", $info), 1); } diff --git a/mod/forum/tests/events_test.php b/mod/forum/tests/events_test.php index de028d22887..4c284dafb97 100644 --- a/mod/forum/tests/events_test.php +++ b/mod/forum/tests/events_test.php @@ -1824,4 +1824,873 @@ class mod_forum_events_testcase extends advanced_testcase { $this->assertNotEmpty($event->get_name()); } + + /** + * Test discussion_subscription_created event. + */ + public function test_discussion_subscription_created() { + global $CFG; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // Trigger and capturing the event. + $sink = $this->redirectEvents(); + + // Trigger the event by subscribing the user to the forum discussion. + \mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $discussion); + + $events = $sink->get_events(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\discussion_subscription_created', $event); + + + $cm = get_coursemodule_from_instance('forum', $discussion->forum); + $context = \context_module::instance($cm->id); + $this->assertEquals($context, $event->get_context()); + + $url = new \moodle_url('/mod/forum/subscribe.php', array( + 'id' => $forum->id, + 'd' => $discussion->id + )); + + $this->assertEquals($url, $event->get_url()); + $this->assertEventContextNotUsed($event); + $this->assertNotEmpty($event->get_name()); + } + + /** + * Test validation of discussion_subscription_created event. + */ + public function test_discussion_subscription_created_validation() { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // The user is not subscribed to the forum. Insert a new discussion subscription. + $subscription = new \stdClass(); + $subscription->userid = $user->id; + $subscription->forum = $forum->id; + $subscription->discussion = $discussion->id; + $subscription->preference = \mod_forum\subscriptions::FORUM_DISCUSSION_SUBSCRIBED; + + $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription); + + $context = context_module::instance($forum->cmid); + + $params = array( + 'context' => $context, + 'objectid' => $subscription->id, + 'relateduserid' => $user->id, + 'other' => array( + 'forumid' => $forum->id, + 'discussion' => $discussion->id, + ) + ); + + $event = \mod_forum\event\discussion_subscription_created::create($params); + + // Trigger and capturing the event. + $sink = $this->redirectEvents(); + $event->trigger(); + $events = $sink->get_events(); + $this->assertCount(1, $events); + $event = reset($events); + } + + /** + * Test contextlevel validation of discussion_subscription_created event. + */ + public function test_discussion_subscription_created_validation_contextlevel() { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // The user is not subscribed to the forum. Insert a new discussion subscription. + $subscription = new \stdClass(); + $subscription->userid = $user->id; + $subscription->forum = $forum->id; + $subscription->discussion = $discussion->id; + $subscription->preference = \mod_forum\subscriptions::FORUM_DISCUSSION_SUBSCRIBED; + + $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription); + + $context = context_module::instance($forum->cmid); + + $params = array( + 'context' => \context_course::instance($course->id), + 'objectid' => $subscription->id, + 'relateduserid' => $user->id, + 'other' => array( + 'forumid' => $forum->id, + 'discussion' => $discussion->id, + ) + ); + + // Without an invalid context. + $this->setExpectedException('coding_exception', 'Context level must be CONTEXT_MODULE.'); + \mod_forum\event\discussion_subscription_created::create($params); + } + + /** + * Test discussion validation of discussion_subscription_created event. + */ + public function test_discussion_subscription_created_validation_discussion() { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // The user is not subscribed to the forum. Insert a new discussion subscription. + $subscription = new \stdClass(); + $subscription->userid = $user->id; + $subscription->forum = $forum->id; + $subscription->discussion = $discussion->id; + $subscription->preference = \mod_forum\subscriptions::FORUM_DISCUSSION_SUBSCRIBED; + + $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription); + + // Without the discussion. + $params = array( + 'context' => context_module::instance($forum->cmid), + 'objectid' => $subscription->id, + 'relateduserid' => $user->id, + 'other' => array( + 'forumid' => $forum->id, + ) + ); + + $this->setExpectedException('coding_exception', "The 'discussion' value must be set in other."); + \mod_forum\event\discussion_subscription_created::create($params); + } + + /** + * Test forumid validation of discussion_subscription_created event. + */ + public function test_discussion_subscription_created_validation_forumid() { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // The user is not subscribed to the forum. Insert a new discussion subscription. + $subscription = new \stdClass(); + $subscription->userid = $user->id; + $subscription->forum = $forum->id; + $subscription->discussion = $discussion->id; + $subscription->preference = \mod_forum\subscriptions::FORUM_DISCUSSION_SUBSCRIBED; + + $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription); + + // Without the forumid. + $params = array( + 'context' => context_module::instance($forum->cmid), + 'objectid' => $subscription->id, + 'relateduserid' => $user->id, + 'other' => array( + 'discussion' => $discussion->id, + ) + ); + + $this->setExpectedException('coding_exception', "The 'forumid' value must be set in other."); + \mod_forum\event\discussion_subscription_created::create($params); + } + + /** + * Test relateduserid validation of discussion_subscription_created event. + */ + public function test_discussion_subscription_created_validation_relateduserid() { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // The user is not subscribed to the forum. Insert a new discussion subscription. + $subscription = new \stdClass(); + $subscription->userid = $user->id; + $subscription->forum = $forum->id; + $subscription->discussion = $discussion->id; + $subscription->preference = \mod_forum\subscriptions::FORUM_DISCUSSION_SUBSCRIBED; + + $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription); + + $context = context_module::instance($forum->cmid); + + // Without the relateduserid. + $params = array( + 'context' => context_module::instance($forum->cmid), + 'objectid' => $subscription->id, + 'other' => array( + 'forumid' => $forum->id, + 'discussion' => $discussion->id, + ) + ); + + $this->setExpectedException('coding_exception', "The 'relateduserid' must be set."); + \mod_forum\event\discussion_subscription_created::create($params); + } + + /** + * Test discussion_subscription_deleted event. + */ + public function test_discussion_subscription_deleted() { + global $CFG; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // Trigger and capturing the event. + $sink = $this->redirectEvents(); + + // Trigger the event by unsubscribing the user to the forum discussion. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion); + + $events = $sink->get_events(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\discussion_subscription_deleted', $event); + + + $cm = get_coursemodule_from_instance('forum', $discussion->forum); + $context = \context_module::instance($cm->id); + $this->assertEquals($context, $event->get_context()); + + $url = new \moodle_url('/mod/forum/subscribe.php', array( + 'id' => $forum->id, + 'd' => $discussion->id + )); + + $this->assertEquals($url, $event->get_url()); + $this->assertEventContextNotUsed($event); + $this->assertNotEmpty($event->get_name()); + } + + /** + * Test validation of discussion_subscription_deleted event. + */ + public function test_discussion_subscription_deleted_validation() { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // The user is not subscribed to the forum. Insert a new discussion subscription. + $subscription = new \stdClass(); + $subscription->userid = $user->id; + $subscription->forum = $forum->id; + $subscription->discussion = $discussion->id; + $subscription->preference = \mod_forum\subscriptions::FORUM_DISCUSSION_UNSUBSCRIBED; + + $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription); + + $context = context_module::instance($forum->cmid); + + $params = array( + 'context' => $context, + 'objectid' => $subscription->id, + 'relateduserid' => $user->id, + 'other' => array( + 'forumid' => $forum->id, + 'discussion' => $discussion->id, + ) + ); + + $event = \mod_forum\event\discussion_subscription_deleted::create($params); + + // Trigger and capturing the event. + $sink = $this->redirectEvents(); + $event->trigger(); + $events = $sink->get_events(); + $this->assertCount(1, $events); + $event = reset($events); + + // Without an invalid context. + $params['context'] = \context_course::instance($course->id); + $this->setExpectedException('coding_exception', 'Context level must be CONTEXT_MODULE.'); + \mod_forum\event\discussion_deleted::create($params); + + // Without the discussion. + unset($params['discussion']); + $this->setExpectedException('coding_exception', 'The \'discussion\' value must be set in other.'); + \mod_forum\event\discussion_deleted::create($params); + + // Without the forumid. + unset($params['forumid']); + $this->setExpectedException('coding_exception', 'The \'forumid\' value must be set in other.'); + \mod_forum\event\discussion_deleted::create($params); + + // Without the relateduserid. + unset($params['relateduserid']); + $this->setExpectedException('coding_exception', 'The \'relateduserid\' value must be set in other.'); + \mod_forum\event\discussion_deleted::create($params); + } + + /** + * Test contextlevel validation of discussion_subscription_deleted event. + */ + public function test_discussion_subscription_deleted_validation_contextlevel() { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // The user is not subscribed to the forum. Insert a new discussion subscription. + $subscription = new \stdClass(); + $subscription->userid = $user->id; + $subscription->forum = $forum->id; + $subscription->discussion = $discussion->id; + $subscription->preference = \mod_forum\subscriptions::FORUM_DISCUSSION_SUBSCRIBED; + + $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription); + + $context = context_module::instance($forum->cmid); + + $params = array( + 'context' => \context_course::instance($course->id), + 'objectid' => $subscription->id, + 'relateduserid' => $user->id, + 'other' => array( + 'forumid' => $forum->id, + 'discussion' => $discussion->id, + ) + ); + + // Without an invalid context. + $this->setExpectedException('coding_exception', 'Context level must be CONTEXT_MODULE.'); + \mod_forum\event\discussion_subscription_deleted::create($params); + } + + /** + * Test discussion validation of discussion_subscription_deleted event. + */ + public function test_discussion_subscription_deleted_validation_discussion() { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // The user is not subscribed to the forum. Insert a new discussion subscription. + $subscription = new \stdClass(); + $subscription->userid = $user->id; + $subscription->forum = $forum->id; + $subscription->discussion = $discussion->id; + $subscription->preference = \mod_forum\subscriptions::FORUM_DISCUSSION_SUBSCRIBED; + + $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription); + + // Without the discussion. + $params = array( + 'context' => context_module::instance($forum->cmid), + 'objectid' => $subscription->id, + 'relateduserid' => $user->id, + 'other' => array( + 'forumid' => $forum->id, + ) + ); + + $this->setExpectedException('coding_exception', "The 'discussion' value must be set in other."); + \mod_forum\event\discussion_subscription_deleted::create($params); + } + + /** + * Test forumid validation of discussion_subscription_deleted event. + */ + public function test_discussion_subscription_deleted_validation_forumid() { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // The user is not subscribed to the forum. Insert a new discussion subscription. + $subscription = new \stdClass(); + $subscription->userid = $user->id; + $subscription->forum = $forum->id; + $subscription->discussion = $discussion->id; + $subscription->preference = \mod_forum\subscriptions::FORUM_DISCUSSION_SUBSCRIBED; + + $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription); + + // Without the forumid. + $params = array( + 'context' => context_module::instance($forum->cmid), + 'objectid' => $subscription->id, + 'relateduserid' => $user->id, + 'other' => array( + 'discussion' => $discussion->id, + ) + ); + + $this->setExpectedException('coding_exception', "The 'forumid' value must be set in other."); + \mod_forum\event\discussion_subscription_deleted::create($params); + } + + /** + * Test relateduserid validation of discussion_subscription_deleted event. + */ + public function test_discussion_subscription_deleted_validation_relateduserid() { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // The user is not subscribed to the forum. Insert a new discussion subscription. + $subscription = new \stdClass(); + $subscription->userid = $user->id; + $subscription->forum = $forum->id; + $subscription->discussion = $discussion->id; + $subscription->preference = \mod_forum\subscriptions::FORUM_DISCUSSION_SUBSCRIBED; + + $subscription->id = $DB->insert_record('forum_discussion_subs', $subscription); + + $context = context_module::instance($forum->cmid); + + // Without the relateduserid. + $params = array( + 'context' => context_module::instance($forum->cmid), + 'objectid' => $subscription->id, + 'other' => array( + 'forumid' => $forum->id, + 'discussion' => $discussion->id, + ) + ); + + $this->setExpectedException('coding_exception', "The 'relateduserid' must be set."); + \mod_forum\event\discussion_subscription_deleted::create($params); + } + + /** + * Test that the correct context is used in the events when subscribing + * users. + */ + public function test_forum_subscription_page_context_valid() { + global $CFG, $PAGE; + require_once($CFG->dirroot . '/mod/forum/lib.php'); + + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + $quiz = $this->getDataGenerator()->create_module('quiz', $options); + + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = array(); + $record['discussion'] = $discussion->id; + $record['userid'] = $user->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // Set up the default page event to use this forum. + $PAGE = new moodle_page(); + $cm = get_coursemodule_from_instance('forum', $discussion->forum); + $context = \context_module::instance($cm->id); + $PAGE->set_context($context); + $PAGE->set_cm($cm, $course, $forum); + + // Trigger and capturing the event. + $sink = $this->redirectEvents(); + + // Trigger the event by subscribing the user to the forum. + \mod_forum\subscriptions::subscribe_user($user->id, $forum); + + $events = $sink->get_events(); + $sink->clear(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\subscription_created', $event); + $this->assertEquals($context, $event->get_context()); + + // Trigger the event by unsubscribing the user to the forum. + \mod_forum\subscriptions::unsubscribe_user($user->id, $forum); + + $events = $sink->get_events(); + $sink->clear(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\subscription_deleted', $event); + $this->assertEquals($context, $event->get_context()); + + // Trigger the event by subscribing the user to the discussion. + \mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $discussion); + + $events = $sink->get_events(); + $sink->clear(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\discussion_subscription_created', $event); + $this->assertEquals($context, $event->get_context()); + + // Trigger the event by unsubscribing the user from the discussion. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion); + + $events = $sink->get_events(); + $sink->clear(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\discussion_subscription_deleted', $event); + $this->assertEquals($context, $event->get_context()); + + // Now try with the context for a different module (quiz). + $PAGE = new moodle_page(); + $cm = get_coursemodule_from_instance('quiz', $quiz->id); + $quizcontext = \context_module::instance($cm->id); + $PAGE->set_context($quizcontext); + $PAGE->set_cm($cm, $course, $quiz); + + // Trigger and capturing the event. + $sink = $this->redirectEvents(); + + // Trigger the event by subscribing the user to the forum. + \mod_forum\subscriptions::subscribe_user($user->id, $forum); + + $events = $sink->get_events(); + $sink->clear(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\subscription_created', $event); + $this->assertEquals($context, $event->get_context()); + + // Trigger the event by unsubscribing the user to the forum. + \mod_forum\subscriptions::unsubscribe_user($user->id, $forum); + + $events = $sink->get_events(); + $sink->clear(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\subscription_deleted', $event); + $this->assertEquals($context, $event->get_context()); + + // Trigger the event by subscribing the user to the discussion. + \mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $discussion); + + $events = $sink->get_events(); + $sink->clear(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\discussion_subscription_created', $event); + $this->assertEquals($context, $event->get_context()); + + // Trigger the event by unsubscribing the user from the discussion. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion); + + $events = $sink->get_events(); + $sink->clear(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\discussion_subscription_deleted', $event); + $this->assertEquals($context, $event->get_context()); + + // Now try with the course context - the module context should still be used. + $PAGE = new moodle_page(); + $coursecontext = \context_course::instance($course->id); + $PAGE->set_context($coursecontext); + + // Trigger the event by subscribing the user to the forum. + \mod_forum\subscriptions::subscribe_user($user->id, $forum); + + $events = $sink->get_events(); + $sink->clear(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\subscription_created', $event); + $this->assertEquals($context, $event->get_context()); + + // Trigger the event by unsubscribing the user to the forum. + \mod_forum\subscriptions::unsubscribe_user($user->id, $forum); + + $events = $sink->get_events(); + $sink->clear(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\subscription_deleted', $event); + $this->assertEquals($context, $event->get_context()); + + // Trigger the event by subscribing the user to the discussion. + \mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $discussion); + + $events = $sink->get_events(); + $sink->clear(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\discussion_subscription_created', $event); + $this->assertEquals($context, $event->get_context()); + + // Trigger the event by unsubscribing the user from the discussion. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion); + + $events = $sink->get_events(); + $sink->clear(); + $this->assertCount(1, $events); + $event = reset($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_forum\event\discussion_subscription_deleted', $event); + $this->assertEquals($context, $event->get_context()); + + } } diff --git a/mod/forum/tests/lib_test.php b/mod/forum/tests/lib_test.php index 25edf528d77..b955a2cdf5f 100644 --- a/mod/forum/tests/lib_test.php +++ b/mod/forum/tests/lib_test.php @@ -24,6 +24,9 @@ defined('MOODLE_INTERNAL') || die(); +global $CFG; +require_once($CFG->dirroot . '/mod/forum/lib.php'); + class mod_forum_lib_testcase extends advanced_testcase { public function setUp() { diff --git a/mod/forum/tests/subscriptions_test.php b/mod/forum/tests/subscriptions_test.php index 06711a16ec9..0d358fe5973 100644 --- a/mod/forum/tests/subscriptions_test.php +++ b/mod/forum/tests/subscriptions_test.php @@ -36,6 +36,7 @@ class mod_forum_subscriptions_testcase extends advanced_testcase { // We must clear the subscription caches. This has to be done both before each test, and after in case of other // tests using these functions. \mod_forum\subscriptions::reset_forum_cache(); + \mod_forum\subscriptions::reset_discussion_cache(); } /** @@ -45,6 +46,7 @@ class mod_forum_subscriptions_testcase extends advanced_testcase { // We must clear the subscription caches. This has to be done both before each test, and after in case of other // tests using these functions. \mod_forum\subscriptions::reset_forum_cache(); + \mod_forum\subscriptions::reset_discussion_cache(); } /** @@ -198,6 +200,711 @@ class mod_forum_subscriptions_testcase extends advanced_testcase { $this->assertEquals(3, count($result)); } + /** + * Test that toggling the forum-level subscription for a different user does not affect their discussion-level + * subscriptions. + */ + public function test_forum_subscribe_toggle_as_other() { + global $DB; + + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create a user enrolled in the course as a student. + list($author) = $this->helper_create_users($course, 1); + + // Post a discussion to the forum. + list($discussion, $post) = $this->helper_post_to_forum($forum, $author); + + // Check that the user is currently not subscribed to the forum. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertFalse(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // Check that the user is unsubscribed from the discussion too. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + + // Check that we have no records in either of the subscription tables. + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Subscribing to the forum should create a record in the subscriptions table, but not the forum discussion + // subscriptions table. + \mod_forum\subscriptions::subscribe_user($author->id, $forum); + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Unsubscribing should remove the record from the forum subscriptions table, and not modify the forum + // discussion subscriptions table. + \mod_forum\subscriptions::unsubscribe_user($author->id, $forum); + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // The same thing should happen calling the deprecated versions of + // these functions. + // Subscribing to the forum should create a record in the subscriptions table, but not the forum discussion + // subscriptions table. + forum_subscribe($author->id, $forum->id); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Unsubscribing should remove the record from the forum subscriptions table, and not modify the forum + // discussion subscriptions table. + forum_unsubscribe($author->id, $forum->id); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Enroling the user in the discussion should add one record to the forum discussion table without modifying the + // form subscriptions. + \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion); + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Unsubscribing should remove the record from the forum subscriptions table, and not modify the forum + // discussion subscriptions table. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion); + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Re-subscribe to the discussion so that we can check the effect of forum-level subscriptions. + \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion); + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Subscribing to the forum should have no effect on the forum discussion subscriptions table if the user did + // not request the change themself. + \mod_forum\subscriptions::subscribe_user($author->id, $forum); + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Unsubscribing from the forum should have no effect on the forum discussion subscriptions table if the user + // did not request the change themself. + \mod_forum\subscriptions::unsubscribe_user($author->id, $forum); + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Subscribing to the forum should remove the per-discussion subscription preference if the user requested the + // change themself. + \mod_forum\subscriptions::subscribe_user($author->id, $forum, null, true); + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Now unsubscribe from the current discussion whilst being subscribed to the forum as a whole. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion); + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Unsubscribing from the forum should remove the per-discussion subscription preference if the user requested the + // change themself. + \mod_forum\subscriptions::unsubscribe_user($author->id, $forum, null, true); + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Subscribe to the discussion. + \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion); + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Subscribe to the forum without removing the discussion preferences. + \mod_forum\subscriptions::subscribe_user($author->id, $forum); + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Unsubscribing from the discussion should result in a change. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion); + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + } + + /** + * Test that a user unsubscribed from a forum is not subscribed to it's discussions by default. + */ + public function test_forum_discussion_subscription_forum_unsubscribed() { + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create users enrolled in the course as students. + list($author) = $this->helper_create_users($course, 1); + + // Check that the user is currently not subscribed to the forum. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertFalse(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // Post a discussion to the forum. + list($discussion, $post) = $this->helper_post_to_forum($forum, $author); + + // Check that the user is unsubscribed from the discussion too. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + } + + /** + * Test that the act of subscribing to a forum subscribes the user to it's discussions by default. + */ + public function test_forum_discussion_subscription_forum_subscribed() { + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create users enrolled in the course as students. + list($author) = $this->helper_create_users($course, 1); + + // Enrol the user in the forum. + // If a subscription was added, we get the record ID. + $this->assertInternalType('int', \mod_forum\subscriptions::subscribe_user($author->id, $forum)); + + // If we already have a subscription when subscribing the user, we get a boolean (true). + $this->assertTrue(\mod_forum\subscriptions::subscribe_user($author->id, $forum)); + + // Check that the user is currently subscribed to the forum. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertTrue(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // Post a discussion to the forum. + list($discussion, $post) = $this->helper_post_to_forum($forum, $author); + + // Check that the user is subscribed to the discussion too. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + } + + /** + * Test that a user unsubscribed from a forum can be subscribed to a discussion. + */ + public function test_forum_discussion_subscription_forum_unsubscribed_discussion_subscribed() { + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create a user enrolled in the course as a student. + list($author) = $this->helper_create_users($course, 1); + + // Check that the user is currently not subscribed to the forum. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertFalse(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // Post a discussion to the forum. + list($discussion, $post) = $this->helper_post_to_forum($forum, $author); + + // Attempting to unsubscribe from the discussion should not make a change. + $this->assertFalse(\mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion)); + + // Then subscribe them to the discussion. + $this->assertTrue(\mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion)); + + // Check that the user is still unsubscribed from the forum. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertFalse(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // But subscribed to the discussion. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + } + + /** + * Test that a user subscribed to a forum can be unsubscribed from a discussion. + */ + public function test_forum_discussion_subscription_forum_subscribed_discussion_unsubscribed() { + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create two users enrolled in the course as students. + list($author) = $this->helper_create_users($course, 2); + + // Enrol the student in the forum. + \mod_forum\subscriptions::subscribe_user($author->id, $forum); + + // Check that the user is currently subscribed to the forum. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertTrue(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // Post a discussion to the forum. + list($discussion, $post) = $this->helper_post_to_forum($forum, $author); + + // Then unsubscribe them from the discussion. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion); + + // Check that the user is still subscribed to the forum. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertTrue(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // But unsubscribed from the discussion. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + } + + /** + * Test the effect of toggling the discussion subscription status when subscribed to the forum. + */ + public function test_forum_discussion_toggle_forum_subscribed() { + global $DB; + + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create two users enrolled in the course as students. + list($author) = $this->helper_create_users($course, 2); + + // Enrol the student in the forum. + \mod_forum\subscriptions::subscribe_user($author->id, $forum); + + // Check that the user is currently subscribed to the forum. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertTrue(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // Post a discussion to the forum. + list($discussion, $post) = $this->helper_post_to_forum($forum, $author); + + // Check that the user is initially subscribed to that discussion. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + + // An attempt to subscribe again should result in a falsey return to indicate that no change was made. + $this->assertFalse(\mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion)); + + // And there should be no discussion subscriptions (and one forum subscription). + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + + // Then unsubscribe them from the discussion. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion); + + // Check that the user is still subscribed to the forum. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertTrue(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // An attempt to unsubscribe again should result in a falsey return to indicate that no change was made. + $this->assertFalse(\mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion)); + + // And there should be a discussion subscriptions (and one forum subscription). + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + + // But unsubscribed from the discussion. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + + // There should be a record in the discussion subscription tracking table. + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // And one in the forum subscription tracking table. + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + + // Now subscribe the user again to the discussion. + \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion); + + // Check that the user is still subscribed to the forum. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertTrue(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // Check the deprecated function too. + $this->assertTrue(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // And is subscribed to the discussion again. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + + // There should be no record in the discussion subscription tracking table. + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // And one in the forum subscription tracking table. + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + + // And unsubscribe again. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion); + + // Check that the user is still subscribed to the forum. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertTrue(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // But unsubscribed from the discussion. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + + // There should be a record in the discussion subscription tracking table. + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // And one in the forum subscription tracking table. + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + + // And subscribe the user again to the discussion. + \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion); + + // Check that the user is still subscribed to the forum. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // And is subscribed to the discussion again. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + + // There should be no record in the discussion subscription tracking table. + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // And one in the forum subscription tracking table. + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + + // And unsubscribe again. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion); + + // Check that the user is still subscribed to the forum. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertTrue(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // But unsubscribed from the discussion. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + + // There should be a record in the discussion subscription tracking table. + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // And one in the forum subscription tracking table. + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + + // Now unsubscribe the user from the forum. + $this->assertTrue(\mod_forum\subscriptions::unsubscribe_user($author->id, $forum, null, true)); + + // This removes both the forum_subscriptions, and the forum_discussion_subs records. + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $author->id, + 'forum' => $forum->id, + ))); + + // And should have reset the discussion cache value. + $result = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $author->id); + $this->assertInternalType('array', $result); + $this->assertFalse(isset($result[$discussion->id])); + } + + /** + * Test the effect of toggling the discussion subscription status when unsubscribed from the forum. + */ + public function test_forum_discussion_toggle_forum_unsubscribed() { + global $DB; + + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create two users enrolled in the course as students. + list($author) = $this->helper_create_users($course, 2); + + // Check that the user is currently unsubscribed to the forum. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertFalse(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // Post a discussion to the forum. + list($discussion, $post) = $this->helper_post_to_forum($forum, $author); + + // Check that the user is initially unsubscribed to that discussion. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + + // Then subscribe them to the discussion. + $this->assertTrue(\mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion)); + + // An attempt to subscribe again should result in a falsey return to indicate that no change was made. + $this->assertFalse(\mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion)); + + // Check that the user is still unsubscribed from the forum. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertFalse(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // But subscribed to the discussion. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + + // There should be a record in the discussion subscription tracking table. + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // Now unsubscribe the user again from the discussion. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion); + + // Check that the user is still unsubscribed from the forum. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertFalse(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // And is unsubscribed from the discussion again. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + + // There should be no record in the discussion subscription tracking table. + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // And subscribe the user again to the discussion. + \mod_forum\subscriptions::subscribe_user_to_discussion($author->id, $discussion); + + // Check that the user is still unsubscribed from the forum. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertFalse(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // And is subscribed to the discussion again. + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + + // There should be a record in the discussion subscription tracking table. + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + + // And unsubscribe again. + \mod_forum\subscriptions::unsubscribe_user_from_discussion($author->id, $discussion); + + // Check that the user is still unsubscribed from the forum. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum)); + + // Check the deprecated function too. + $this->assertFalse(forum_is_subscribed($author->id, $forum)); + $this->assertEquals(1, count(phpunit_util::get_debugging_messages())); + phpunit_util::reset_debugging(); + + // But unsubscribed from the discussion. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($author->id, $forum, $discussion->id)); + + // There should be no record in the discussion subscription tracking table. + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $author->id, + 'discussion' => $discussion->id, + ))); + } + /** * Test that the deprecated forum_is_subscribed accepts numeric forum IDs. */ @@ -299,6 +1006,78 @@ class mod_forum_subscriptions_testcase extends advanced_testcase { $this->assertEquals($usercount, count($subscribers)); } + /** + * Test that unusual combinations of discussion subscriptions do not affect the subscribed user list. + */ + public function test_fetch_subscribed_users_discussion_subscriptions() { + global $DB; + + $this->resetAfterTest(true); + + // Create a course, with a forum. where users are initially subscribed. + $course = $this->getDataGenerator()->create_course(); + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create some user enrolled in the course as a student. + $usercount = 5; + $users = $this->helper_create_users($course, $usercount); + + list($discussion, $post) = $this->helper_post_to_forum($forum, $users[0]); + + // All users should be subscribed. + $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum); + $this->assertEquals($usercount, count($subscribers)); + $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, null, null, true); + $this->assertEquals($usercount, count($subscribers)); + + \mod_forum\subscriptions::unsubscribe_user_from_discussion($users[0]->id, $discussion); + + // All users should be subscribed. + $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum); + $this->assertEquals($usercount, count($subscribers)); + + // All users should be subscribed. + $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, null, null, true); + $this->assertEquals($usercount, count($subscribers)); + + // Manually insert an extra subscription for one of the users. + $record = new stdClass(); + $record->userid = $users[2]->id; + $record->forum = $forum->id; + $record->discussion = $discussion->id; + $record->preference = \mod_forum\subscriptions::FORUM_DISCUSSION_SUBSCRIBED; + $DB->insert_record('forum_discussion_subs', $record); + + // The discussion count should not have changed. + $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum); + $this->assertEquals($usercount, count($subscribers)); + $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, null, null, true); + $this->assertEquals($usercount, count($subscribers)); + + // Unsubscribe 2 users. + $unsubscribedcount = 2; + for ($i = 0; $i < $unsubscribedcount; $i++) { + \mod_forum\subscriptions::unsubscribe_user($users[$i]->id, $forum); + } + + // The subscription count should now take into account those users who have been unsubscribed. + $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum); + $this->assertEquals($usercount - $unsubscribedcount, count($subscribers)); + $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, null, null, true); + $this->assertEquals($usercount - $unsubscribedcount, count($subscribers)); + + // Now subscribe one of those users back to the discussion. + $subscribeddiscussionusers = 1; + for ($i = 0; $i < $subscribeddiscussionusers; $i++) { + \mod_forum\subscriptions::subscribe_user_to_discussion($users[$i]->id, $discussion); + } + $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum); + $this->assertEquals($usercount - $unsubscribedcount, count($subscribers)); + $subscribers = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, null, null, true); + $this->assertEquals($usercount - $unsubscribedcount + $subscribeddiscussionusers, count($subscribers)); + } + /** * Test whether a user is force-subscribed to a forum. */ @@ -332,4 +1111,326 @@ class mod_forum_subscriptions_testcase extends advanced_testcase { $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum)); } + /** + * Test that the subscription cache can be pre-filled. + */ + public function test_subscription_cache_prefill() { + global $DB; + + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create some users. + $users = $this->helper_create_users($course, 20); + + // Reset the subscription cache. + \mod_forum\subscriptions::reset_forum_cache(); + + // Filling the subscription cache should only use a single query. + $startcount = $DB->perf_get_reads(); + $this->assertNull(\mod_forum\subscriptions::fill_subscription_cache($forum->id)); + $postfillcount = $DB->perf_get_reads(); + $this->assertEquals(1, $postfillcount - $startcount); + + // Now fetch some subscriptions from that forum - these should use + // the cache and not perform additional queries. + foreach ($users as $user) { + $this->assertTrue(\mod_forum\subscriptions::fetch_subscription_cache($forum->id, $user->id)); + } + $finalcount = $DB->perf_get_reads(); + $this->assertEquals(0, $finalcount - $postfillcount); + } + + /** + * Test that the subscription cache can filled user-at-a-time. + */ + public function test_subscription_cache_fill() { + global $DB; + + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create some users. + $users = $this->helper_create_users($course, 20); + + // Reset the subscription cache. + \mod_forum\subscriptions::reset_forum_cache(); + + // Filling the subscription cache should only use a single query. + $startcount = $DB->perf_get_reads(); + + // Fetch some subscriptions from that forum - these should not use the cache and will perform additional queries. + foreach ($users as $user) { + $this->assertTrue(\mod_forum\subscriptions::fetch_subscription_cache($forum->id, $user->id)); + } + $finalcount = $DB->perf_get_reads(); + $this->assertEquals(20, $finalcount - $startcount); + } + + /** + * Test that the discussion subscription cache can filled course-at-a-time. + */ + public function test_discussion_subscription_cache_fill_for_course() { + global $DB; + + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + // Create the forums. + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_DISALLOWSUBSCRIBE); + $disallowforum = $this->getDataGenerator()->create_module('forum', $options); + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $chooseforum = $this->getDataGenerator()->create_module('forum', $options); + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE); + $initialforum = $this->getDataGenerator()->create_module('forum', $options); + + // Create some users and keep a reference to the first user. + $users = $this->helper_create_users($course, 20); + $user = reset($users); + + // Reset the subscription caches. + \mod_forum\subscriptions::reset_forum_cache(); + + $startcount = $DB->perf_get_reads(); + $result = \mod_forum\subscriptions::fill_subscription_cache_for_course($course->id, $user->id); + $this->assertNull($result); + $postfillcount = $DB->perf_get_reads(); + $this->assertEquals(1, $postfillcount - $startcount); + $this->assertFalse(\mod_forum\subscriptions::fetch_subscription_cache($disallowforum->id, $user->id)); + $this->assertFalse(\mod_forum\subscriptions::fetch_subscription_cache($chooseforum->id, $user->id)); + $this->assertTrue(\mod_forum\subscriptions::fetch_subscription_cache($initialforum->id, $user->id)); + $finalcount = $DB->perf_get_reads(); + $this->assertEquals(0, $finalcount - $postfillcount); + + // Test for all users. + foreach ($users as $user) { + $result = \mod_forum\subscriptions::fill_subscription_cache_for_course($course->id, $user->id); + $this->assertFalse(\mod_forum\subscriptions::fetch_subscription_cache($disallowforum->id, $user->id)); + $this->assertFalse(\mod_forum\subscriptions::fetch_subscription_cache($chooseforum->id, $user->id)); + $this->assertTrue(\mod_forum\subscriptions::fetch_subscription_cache($initialforum->id, $user->id)); + } + $finalcount = $DB->perf_get_reads(); + $this->assertEquals(count($users), $finalcount - $postfillcount); + } + + /** + * Test that the discussion subscription cache can be forcibly updated for a user. + */ + public function test_discussion_subscription_cache_prefill() { + global $DB; + + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create some users. + $users = $this->helper_create_users($course, 20); + + // Post some discussions to the forum. + $discussions = array(); + $author = $users[0]; + for ($i = 0; $i < 20; $i++) { + list($discussion, $post) = $this->helper_post_to_forum($forum, $author); + $discussions[] = $discussion; + } + + // Unsubscribe half the users from the half the discussions. + $forumcount = 0; + $usercount = 0; + foreach ($discussions as $data) { + if ($forumcount % 2) { + continue; + } + foreach ($users as $user) { + if ($usercount % 2) { + continue; + } + \mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion); + $usercount++; + } + $forumcount++; + } + + // Reset the subscription caches. + \mod_forum\subscriptions::reset_forum_cache(); + \mod_forum\subscriptions::reset_discussion_cache(); + + // Filling the discussion subscription cache should only use a single query. + $startcount = $DB->perf_get_reads(); + $this->assertNull(\mod_forum\subscriptions::fill_discussion_subscription_cache($forum->id)); + $postfillcount = $DB->perf_get_reads(); + $this->assertEquals(1, $postfillcount - $startcount); + + // Now fetch some subscriptions from that forum - these should use + // the cache and not perform additional queries. + foreach ($users as $user) { + $result = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $user->id); + $this->assertInternalType('array', $result); + } + $finalcount = $DB->perf_get_reads(); + $this->assertEquals(0, $finalcount - $postfillcount); + } + + /** + * Test that the discussion subscription cache can filled user-at-a-time. + */ + public function test_discussion_subscription_cache_fill() { + global $DB; + + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create some users. + $users = $this->helper_create_users($course, 20); + + // Post some discussions to the forum. + $discussions = array(); + $author = $users[0]; + for ($i = 0; $i < 20; $i++) { + list($discussion, $post) = $this->helper_post_to_forum($forum, $author); + $discussions[] = $discussion; + } + + // Unsubscribe half the users from the half the discussions. + $forumcount = 0; + $usercount = 0; + foreach ($discussions as $data) { + if ($forumcount % 2) { + continue; + } + foreach ($users as $user) { + if ($usercount % 2) { + continue; + } + \mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion); + $usercount++; + } + $forumcount++; + } + + // Reset the subscription caches. + \mod_forum\subscriptions::reset_forum_cache(); + \mod_forum\subscriptions::reset_discussion_cache(); + + $startcount = $DB->perf_get_reads(); + + // Now fetch some subscriptions from that forum - these should use + // the cache and not perform additional queries. + foreach ($users as $user) { + $result = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $user->id); + $this->assertInternalType('array', $result); + } + $finalcount = $DB->perf_get_reads(); + $this->assertEquals(20, $finalcount - $startcount); + } + + /** + * Test that after toggling the forum subscription as another user, + * the discussion subscription functionality works as expected. + */ + public function test_forum_subscribe_toggle_as_other_repeat_subscriptions() { + global $DB; + + $this->resetAfterTest(true); + + // Create a course, with a forum. + $course = $this->getDataGenerator()->create_course(); + + $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); + $forum = $this->getDataGenerator()->create_module('forum', $options); + + // Create a user enrolled in the course as a student. + list($user) = $this->helper_create_users($course, 1); + + // Post a discussion to the forum. + list($discussion, $post) = $this->helper_post_to_forum($forum, $user); + + // Confirm that the user is currently not subscribed to the forum. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum)); + + // Confirm that the user is unsubscribed from the discussion too. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion->id)); + + // Confirm that we have no records in either of the subscription tables. + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $user->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $user->id, + 'discussion' => $discussion->id, + ))); + + // Subscribing to the forum should create a record in the subscriptions table, but not the forum discussion + // subscriptions table. + \mod_forum\subscriptions::subscribe_user($user->id, $forum); + $this->assertEquals(1, $DB->count_records('forum_subscriptions', array( + 'userid' => $user->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(0, $DB->count_records('forum_discussion_subs', array( + 'userid' => $user->id, + 'discussion' => $discussion->id, + ))); + + // Now unsubscribe from the discussion. This should return true. + $this->assertTrue(\mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion)); + + // Attempting to unsubscribe again should return false because no change was made. + $this->assertFalse(\mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion)); + + // Subscribing to the discussion again should return truthfully as the subscription preference was removed. + $this->assertTrue(\mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $discussion)); + + // Attempting to subscribe again should return false because no change was made. + $this->assertFalse(\mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $discussion)); + + // Now unsubscribe from the discussion. This should return true once more. + $this->assertTrue(\mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion)); + + // And unsubscribing from the forum but not as a request from the user should maintain their preference. + \mod_forum\subscriptions::unsubscribe_user($user->id, $forum); + + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $user->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $user->id, + 'discussion' => $discussion->id, + ))); + + // Subscribing to the discussion should return truthfully because a change was made. + $this->assertTrue(\mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $discussion)); + $this->assertEquals(0, $DB->count_records('forum_subscriptions', array( + 'userid' => $user->id, + 'forum' => $forum->id, + ))); + $this->assertEquals(1, $DB->count_records('forum_discussion_subs', array( + 'userid' => $user->id, + 'discussion' => $discussion->id, + ))); + } + } diff --git a/mod/forum/unsubscribeall.php b/mod/forum/unsubscribeall.php index bfedd1aa133..acecc236d6c 100644 --- a/mod/forum/unsubscribeall.php +++ b/mod/forum/unsubscribeall.php @@ -50,7 +50,7 @@ if (data_submitted() and $confirm and confirm_sesskey()) { $forums = \mod_forum\subscriptions::get_unsubscribable_forums(); foreach($forums as $forum) { - \mod_forum\subscriptions::unsubscribe_user($USER->id, $forum, context_module::instance($forum->cm)); + \mod_forum\subscriptions::unsubscribe_user($USER->id, $forum, context_module::instance($forum->cm), true); } $DB->set_field('user', 'autosubscribe', 0, array('id'=>$USER->id));