MDL-29663 forum: Option to allow Forced for read tracking.
authorEric Merrill <merrill@oakland.edu>
Thu, 26 Sep 2013 14:47:46 +0000 (10:47 -0400)
committerEric Merrill <merrill@oakland.edu>
Tue, 8 Oct 2013 12:03:54 +0000 (08:03 -0400)
Converts the use of Read Tracking On to Forced. Adds new admin setting
forum_allowforcedreadtracking. If set, Forced will be available when
adding or editing forums. In this state the forced forum overrides the
user preference of off.

With forum_allowforcedreadtracking not set (default) behavior is the
same as now, except forums previously set in the On state are treated
as Optional.

The side effect of forum_allowforcedreadtracking being on, is that users
cannot speed up the loading of course pages with lots of forums by
setting their personal forum read tracking option to off.

mod/forum/index.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/mod_form.php
mod/forum/settings.php
mod/forum/tests/behat/track_read_posts.feature
mod/forum/tests/lib_test.php

index 96e1363..87666b9 100644 (file)
@@ -230,9 +230,10 @@ if ($generalforums) {
                     $unreadlink = '<span class="read">0</span>';
                 }
 
-                if ($forum->trackingtype == FORUM_TRACKING_ON) {
+                if (($forum->trackingtype == FORUM_TRACKING_ON) && ($CFG->forum_allowforcedreadtracking)) {
                     $trackedlink = $stryes;
-
+                } else if ($forum->trackingtype === FORUM_TRACKING_OFF || ($USER->trackforums == 0)) {
+                    $trackedlink = '-';
                 } else {
                     $aurl = new moodle_url('/mod/forum/settracking.php', array('id'=>$forum->id));
                     if (!isset($untracked[$forum->id])) {
@@ -368,9 +369,10 @@ if ($course->id != SITEID) {    // Only real courses have learning forums
                         $unreadlink = '<span class="read">0</span>';
                     }
 
-                    if ($forum->trackingtype == FORUM_TRACKING_ON) {
+                    if (($forum->trackingtype == FORUM_TRACKING_ON) && ($CFG->forum_allowforcedreadtracking)) {
                         $trackedlink = $stryes;
-
+                    } else if ($forum->trackingtype === FORUM_TRACKING_OFF || ($USER->trackforums == 0)) {
+                        $trackedlink = '-';
                     } else {
                         $aurl = new moodle_url('/mod/forum/settracking.php', array('id'=>$forum->id));
                         if (!isset($untracked[$forum->id])) {
index 0ab864f..af18efa 100644 (file)
@@ -173,6 +173,8 @@ $string['everyoneisnowsubscribed'] = 'Everyone is now subscribed to this forum';
 $string['everyoneissubscribed'] = 'Everyone is subscribed to this forum';
 $string['existingsubscribers'] = 'Existing subscribers';
 $string['exportdiscussion'] = 'Export whole discussion';
+$string['forcedreadtracking'] = 'Allow forced read tracking';
+$string['forcedreadtracking_desc'] = 'Allows forums to be set to forced read tracking. Will result in decreased performance for some users, particularly on courses with many forums and posts. When off, any forums previously set to Forced are treated as optional.';
 $string['forcessubscribe'] = 'This forum forces everyone to be subscribed';
 $string['forum'] = 'Forum';
 $string['forum:addinstance'] = 'Add a new forum';
@@ -430,16 +432,14 @@ $string['timestartenderror'] = 'Display end date cannot be earlier than the star
 $string['trackforum'] = 'Track unread posts';
 $string['tracking'] = 'Track';
 $string['trackingoff'] = 'Off';
-$string['trackingon'] = 'On';
+$string['trackingon'] = 'Forced';
 $string['trackingoptional'] = 'Optional';
 $string['trackingtype'] = 'Read tracking';
 $string['trackingtype_help'] = 'If enabled, participants can track read and unread posts in the forum and in discussions. There are three options:
 
-* Optional - Participants can choose whether to turn tracking on or off via a link in the administration block
-* On - Tracking is always on
-* Off - Read and unread posts are not tracked
-
-Note: Forum tracking must also be enabled in the user\'s profile settings.';
+* Optional - Participants can choose whether to turn tracking on or off via a link in the administration block. Forum tracking must also be enabled in the user\'s profile settings.
+* Forced - Tracking is always on, regardless of user setting. Available depending on administrative setting.
+* Off - Read and unread posts are not tracked.';
 $string['unread'] = 'Unread';
 $string['unreadposts'] = 'Unread posts';
 $string['unreadpostsnumber'] = '{$a} unread posts';
index a63bd09..086df75 100644 (file)
@@ -6269,6 +6269,14 @@ function forum_tp_mark_posts_read($user, $postids) {
         $params = array_merge($params, $new_params);
         $params[] = $cutoffdate;
 
+        if ($CFG->forum_allowforcedreadtracking) {
+            $trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_ON."
+                            OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))";
+        } else {
+            $trackingsql = "AND ((f.trackingtype = ".FORUM_TRACKING_OPTIONAL."  OR f.trackingtype = ".FORUM_TRACKING_ON.")
+                                AND tf.id IS NULL)";
+        }
+
         $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
 
                 SELECT ?, p.id, p.discussion, d.forum, ?, ?
@@ -6278,8 +6286,7 @@ function forum_tp_mark_posts_read($user, $postids) {
                        LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
                  WHERE p.id $usql
                        AND p.modified >= ?
-                       AND (f.trackingtype = ".FORUM_TRACKING_ON."
-                            OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))";
+                       $trackingsql";
         $status = $DB->execute($sql, $params) && $status;
     }
 
@@ -6602,9 +6609,9 @@ function forum_tp_count_forum_read_records($userid, $forumid, $groupid=false) {
 function forum_tp_get_course_unread_posts($userid, $courseid) {
     global $CFG, $DB;
 
-    $now = round(time(), -2); // db cache friendliness
-    $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
-    $params = array($userid, $userid, $courseid, $cutoffdate);
+    $now = round(time(), -2); // DB cache friendliness.
+    $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 60 * 60);
+    $params = array($userid, $userid, $courseid, $cutoffdate, $userid);
 
     if (!empty($CFG->forum_enabletimedposts)) {
         $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
@@ -6614,6 +6621,16 @@ function forum_tp_get_course_unread_posts($userid, $courseid) {
         $timedsql = "";
     }
 
+    if ($CFG->forum_allowforcedreadtracking) {
+        $trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_ON."
+                            OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL
+                                AND (SELECT trackforums FROM {user} WHERE id = ?) = 1))";
+    } else {
+        $trackingsql = "AND ((f.trackingtype = ".FORUM_TRACKING_OPTIONAL." OR f.trackingtype = ".FORUM_TRACKING_ON.")
+                            AND tf.id IS NULL
+                            AND (SELECT trackforums FROM {user} WHERE id = ?) = 1)";
+    }
+
     $sql = "SELECT f.id, COUNT(p.id) AS unread
               FROM {forum_posts} p
                    JOIN {forum_discussions} d       ON d.id = p.discussion
@@ -6623,8 +6640,7 @@ function forum_tp_get_course_unread_posts($userid, $courseid) {
                    LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
              WHERE f.course = ?
                    AND p.modified >= ? AND r.id is NULL
-                   AND (f.trackingtype = ".FORUM_TRACKING_ON."
-                        OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))
+                   $trackingsql
                    $timedsql
           GROUP BY f.id";
 
@@ -6767,14 +6783,24 @@ function forum_tp_delete_read_records($userid=-1, $postid=-1, $discussionid=-1,
 function forum_tp_get_untracked_forums($userid, $courseid) {
     global $CFG, $DB;
 
+    if ($CFG->forum_allowforcedreadtracking) {
+        $trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_OFF."
+                            OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND (ft.id IS NOT NULL
+                                OR (SELECT trackforums FROM {user} WHERE id = ?) = 0)))";
+    } else {
+        $trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_OFF."
+                            OR ((f.trackingtype = ".FORUM_TRACKING_OPTIONAL." OR f.trackingtype = ".FORUM_TRACKING_ON.")
+                                AND (ft.id IS NOT NULL
+                                    OR (SELECT trackforums FROM {user} WHERE id = ?) = 0)))";
+    }
+
     $sql = "SELECT f.id
               FROM {forum} f
                    LEFT JOIN {forum_track_prefs} ft ON (ft.forumid = f.id AND ft.userid = ?)
              WHERE f.course = ?
-                   AND (f.trackingtype = ".FORUM_TRACKING_OFF."
-                        OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND ft.id IS NOT NULL))";
+                   $trackingsql";
 
-    if ($forums = $DB->get_records_sql($sql, array($userid, $courseid))) {
+    if ($forums = $DB->get_records_sql($sql, array($userid, $courseid, $userid))) {
         foreach ($forums as $forum) {
             $forums[$forum->id] = $forum;
         }
@@ -6815,11 +6841,14 @@ function forum_tp_can_track_forums($forum=false, $user=false) {
     }
 
     if ($forum === false) {
-        // general abitily to track forums
-        return (bool)$user->trackforums;
+        if ($CFG->forum_allowforcedreadtracking) {
+            // Since we can force tracking, assume yes without a specific forum.
+            return true;
+        } else {
+            return (bool)$user->trackforums;
+        }
     }
 
-
     // Work toward always passing an object...
     if (is_numeric($forum)) {
         debugging('Better use proper forum object.', DEBUG_DEVELOPER);
@@ -6829,7 +6858,13 @@ function forum_tp_can_track_forums($forum=false, $user=false) {
     $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
     $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
 
-    return ($forumforced || $forumallows)  && !empty($user->trackforums);
+    if ($CFG->forum_allowforcedreadtracking) {
+        // If we allow forcing, then forced forums takes procidence over user setting.
+        return ($forumforced || ($forumallows  && (!empty($user->trackforums) && (bool)$user->trackforums)));
+    } else {
+        // If we don't allow forcing, user setting trumps.
+        return ($forumforced || $forumallows)  && !empty($user->trackforums);
+    }
 }
 
 /**
@@ -6866,9 +6901,13 @@ function forum_tp_is_tracked($forum, $user=false) {
 
     $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
     $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
+    $userpref = $DB->get_record('forum_track_prefs', array('userid' => $user->id, 'forumid' => $forum->id));
 
-    return $forumforced ||
-           ($forumallows && $DB->get_record('forum_track_prefs', array('userid' => $user->id, 'forumid' => $forum->id)) === false);
+    if ($CFG->forum_allowforcedreadtracking) {
+        return $forumforced || ($forumallows && $userpref === false);
+    } else {
+        return  ($forumallows || $forumforced) && $userpref === false;
+    }
 }
 
 /**
@@ -7678,10 +7717,8 @@ function forum_extend_settings_navigation(settings_navigation $settingsnav, navi
     }
 
     if ($enrolled && forum_tp_can_track_forums($forumobject)) { // keep tracking info for users with suspended enrolments
-        if ($forumobject->trackingtype != FORUM_TRACKING_OPTIONAL) {
-            //tracking forced on or off in forum settings so dont provide a link here to change it
-            //could add unclickable text like for forced subscription but not sure this justifies adding another menu item
-        } else {
+        if ($forumobject->trackingtype == FORUM_TRACKING_OPTIONAL
+                || ((!$CFG->forum_allowforcedreadtracking) && $forumobject->trackingtype == FORUM_TRACKING_ON)) {
             if (forum_tp_is_tracked($forumobject)) {
                 $linktext = get_string('notrackforum', 'forum');
             } else {
@@ -7952,21 +7989,11 @@ class forum_existing_subscriber_selector extends forum_subscriber_selector_base
 function forum_cm_info_view(cm_info $cm) {
     global $CFG;
 
-    // Get tracking status (once per request)
-    static $initialised;
-    static $usetracking, $strunreadpostsone;
-    if (!isset($initialised)) {
-        if ($usetracking = forum_tp_can_track_forums()) {
-            $strunreadpostsone = get_string('unreadpostsone', 'forum');
-        }
-        $initialised = true;
-    }
-
-    if ($usetracking) {
+    if (forum_tp_can_track_forums()) {
         if ($unread = forum_tp_count_forum_unread_posts($cm, $cm->get_course())) {
             $out = '<span class="unread"> <a href="' . $cm->get_url() . '">';
             if ($unread == 1) {
-                $out .= $strunreadpostsone;
+                $out .= get_string('unreadpostsone', 'forum');
             } else {
                 $out .= get_string('unreadpostsnumber', 'forum', $unread);
             }
index 70c8f62..a702cdb 100644 (file)
@@ -86,10 +86,16 @@ class mod_forum_mod_form extends moodleform_mod {
         $options = array();
         $options[FORUM_TRACKING_OPTIONAL] = get_string('trackingoptional', 'forum');
         $options[FORUM_TRACKING_OFF] = get_string('trackingoff', 'forum');
-        $options[FORUM_TRACKING_ON] = get_string('trackingon', 'forum');
+        if ($CFG->forum_allowforcedreadtracking) {
+            $options[FORUM_TRACKING_ON] = get_string('trackingon', 'forum');
+        }
         $mform->addElement('select', 'trackingtype', get_string('trackingtype', 'forum'), $options);
         $mform->addHelpButton('trackingtype', 'trackingtype', 'forum');
-        $mform->setDefault('trackingtype', $CFG->forum_trackingtype);
+        $default = $CFG->forum_trackingtype;
+        if ((!$CFG->forum_allowforcedreadtracking) && ($default == FORUM_TRACKING_ON)) {
+            $default = FORUM_TRACKING_OPTIONAL;
+        }
+        $mform->setDefault('trackingtype', $default);
 
         if ($CFG->enablerssfeeds && isset($CFG->forum_enablerssfeeds) && $CFG->forum_enablerssfeeds) {
 //-------------------------------------------------------------------------------
index b6bf51d..adc80ce 100644 (file)
@@ -69,6 +69,10 @@ if ($ADMIN->fulltree) {
     $settings->add(new admin_setting_configcheckbox('forum_trackreadposts', get_string('trackforum', 'forum'),
                        get_string('configtrackreadposts', 'forum'), 1));
 
+    // Default whether user needs to mark a post as read.
+    $settings->add(new admin_setting_configcheckbox('forum_allowforcedreadtracking', get_string('forcedreadtracking', 'forum'),
+                       get_string('forcedreadtracking_desc', 'forum'), 0));
+
     // Default number of days that a post is considered old
     $settings->add(new admin_setting_configtext('forum_oldpostdays', get_string('oldpostdays', 'forum'),
                        get_string('configoldpostdays', 'forum'), 14, PARAM_INT));
index 2b9c334..d08ce1d 100644 (file)
@@ -8,23 +8,43 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
     Given the following "users" exists:
       | username | firstname | lastname | email | trackforums |
       | student1 | Student | 1 | student1@asd.com | 1 |
+      | student2 | Student | 2 | student2@asd.com | 0 |
     And the following "courses" exists:
       | fullname | shortname | category |
       | Course 1 | C1 | 0 |
     And the following "course enrolments" exists:
       | user | course | role |
       | student1 | C1 | student |
+      | student2 | C1 | student |
     And I log in as "admin"
     And I follow "Course 1"
     And I turn editing mode on
 
   @javascript
-  Scenario: Tracking forum posts on
+  Scenario: Tracking forum posts off
     Given I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name |
       | Forum type | Standard forum for general use |
       | Description | Test forum description |
-      | Read tracking | On |
+      | Read tracking | Off |
+    And I add a new discussion to "Test forum name" forum with:
+      | Subject | Test post subject |
+      | Message | Test post message |
+    And I wait "6" seconds
+    And I log out
+    When I log in as "student1"
+    And I follow "Course 1"
+    Then I should not see "1 unread post"
+    And I follow "Test forum name"
+    And I should not see "Track unread posts"
+
+  @javascript
+  Scenario: Tracking forum posts optional with user tracking on
+    Given I add a "Forum" to section "1" and I fill the form with:
+      | Forum name | Test forum name |
+      | Forum type | Standard forum for general use |
+      | Description | Test forum description |
+      | Read tracking | Optional |
     And I add a new discussion to "Test forum name" forum with:
       | Subject | Test post subject |
       | Message | Test post message |
@@ -33,41 +53,103 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
     When I log in as "student1"
     And I follow "Course 1"
     Then I should see "1 unread post"
-    And I follow "1 unread post"
-    And I should not see "Don't track unread posts"
-    And I follow "Test post subject"
+    And I follow "Test forum name"
+    And I follow "Don't track unread posts"
+    And I wait "4" seconds
+    And I follow "Course 1"
+    And I should not see "1 unread post"
+    And I follow "Test forum name"
+    And I follow "Track unread posts"
+    And I wait "4" seconds
+    And I follow "1"
     And I follow "Course 1"
     And I should not see "1 unread post"
 
   @javascript
-  Scenario: Tracking forum posts off
+  Scenario: Tracking forum posts optional with user tracking off
     Given I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name |
       | Forum type | Standard forum for general use |
       | Description | Test forum description |
-      | Read tracking | Off |
+      | Read tracking | Optional |
     And I add a new discussion to "Test forum name" forum with:
       | Subject | Test post subject |
       | Message | Test post message |
     And I wait "6" seconds
     And I log out
-    When I log in as "student1"
+    When I log in as "student2"
     And I follow "Course 1"
     Then I should not see "1 unread post"
     And I follow "Test forum name"
     And I should not see "Track unread posts"
 
   @javascript
-  Scenario: Tracking forum posts optional
+  Scenario: Tracking forum posts forced with user tracking on
+    And I set the following administration settings values:
+      | Allow forced read tracking | 1 |
+    And I follow "Home"
+    And I follow "Course 1"
     Given I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name |
       | Forum type | Standard forum for general use |
       | Description | Test forum description |
-      | Read tracking | Optional |
+      | Read tracking | Force |
+    And I add a new discussion to "Test forum name" forum with:
+      | Subject | Test post subject |
+      | Message | Test post message |
+    And I wait "6" seconds
+    And I log out
+    When I log in as "student1"
+    And I follow "Course 1"
+    Then I should see "1 unread post"
+    And I follow "1 unread post"
+    And I should not see "Don't track unread posts"
+    And I follow "Test post subject"
+    And I follow "Course 1"
+    And I should not see "1 unread post"
+
+  @javascript
+  Scenario: Tracking forum posts forced with user tracking off
+    And I set the following administration settings values:
+      | Allow forced read tracking | 1 |
+    And I follow "Home"
+    And I follow "Course 1"
+    Given I add a "Forum" to section "1" and I fill the form with:
+      | Forum name | Test forum name |
+      | Forum type | Standard forum for general use |
+      | Description | Test forum description |
+      | Read tracking | Force |
+    And I add a new discussion to "Test forum name" forum with:
+      | Subject | Test post subject |
+      | Message | Test post message |
+    And I wait "6" seconds
+    And I log out
+    When I log in as "student2"
+    And I follow "Course 1"
+    Then I should see "1 unread post"
+    And I follow "1 unread post"
+    And I should not see "Don't track unread posts"
+    And I follow "Test post subject"
+    And I follow "Course 1"
+    And I should not see "1 unread post"
+
+  @javascript
+  Scenario: Tracking forum posts forced (with force disabled) with user tracking on
+    And I set the following administration settings values:
+      | Allow forced read tracking | 1 |
+    And I follow "Home"
+    And I follow "Course 1"
+    Given I add a "Forum" to section "1" and I fill the form with:
+      | Forum name | Test forum name |
+      | Forum type | Standard forum for general use |
+      | Description | Test forum description |
+      | Read tracking | Force |
     And I add a new discussion to "Test forum name" forum with:
       | Subject | Test post subject |
       | Message | Test post message |
     And I wait "6" seconds
+    And I set the following administration settings values:
+      | Allow forced read tracking | 0 |
     And I log out
     When I log in as "student1"
     And I follow "Course 1"
@@ -83,3 +165,27 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
     And I follow "1"
     And I follow "Course 1"
     And I should not see "1 unread post"
+
+  @javascript
+  Scenario: Tracking forum posts forced (with force disabled) with user tracking off
+    And I set the following administration settings values:
+      | Allow forced read tracking | 1 |
+    And I follow "Home"
+    And I follow "Course 1"
+    Given I add a "Forum" to section "1" and I fill the form with:
+      | Forum name | Test forum name |
+      | Forum type | Standard forum for general use |
+      | Description | Test forum description |
+      | Read tracking | Force |
+    And I add a new discussion to "Test forum name" forum with:
+      | Subject | Test post subject |
+      | Message | Test post message |
+    And I wait "6" seconds
+    And I set the following administration settings values:
+      | Allow forced read tracking | 0 |
+    And I log out
+    When I log in as "student2"
+    And I follow "Course 1"
+    Then I should not see "1 unread post"
+    And I follow "Test forum name"
+    And I should not see "Track unread posts"
index d5bfb7a..8806af1 100644 (file)
@@ -181,7 +181,7 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $user1 = $this->getDataGenerator()->create_user();
         $course1 = $this->getDataGenerator()->create_course();
         $course2 = $this->getDataGenerator()->create_course();
-        $student = $DB->get_record('role', array('shortname'=>'student'));
+        $student = $DB->get_record('role', array('shortname' => 'student'));
 
         $e1 = $metaplugin->add_instance($course2, array('customint1' => $course1->id));
         $enrol1 = $DB->get_record('enrol', array('id' => $e1));
@@ -201,4 +201,410 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $this->assertInstanceOf('\core\event\user_enrolment_deleted', $event);
         $this->assertEquals('user_unenrolled', $event->get_legacy_eventname());
     }
+
+    /**
+     * Test the logic in the forum_tp_can_track_forums() function.
+     */
+    public function test_forum_tp_can_track_forums() {
+        global $CFG;
+
+        $this->resetAfterTest();
+
+        $useron = $this->getDataGenerator()->create_user(array('trackforums' => 1));
+        $useroff = $this->getDataGenerator()->create_user(array('trackforums' => 0));
+        $course = $this->getDataGenerator()->create_course();
+        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OFF); // Off.
+        $forumoff = $this->getDataGenerator()->create_module('forum', $options);
+
+        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_ON); // On.
+        $forumforce = $this->getDataGenerator()->create_module('forum', $options);
+
+        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OPTIONAL); // Optional.
+        $forumoptional = $this->getDataGenerator()->create_module('forum', $options);
+
+        // Allow force.
+        $CFG->forum_allowforcedreadtracking = 1;
+
+        // User on, forum off, should be off.
+        $result = forum_tp_can_track_forums($forumoff, $useron);
+        $this->assertEquals(false, $result);
+
+        // User on, forum on, should be on.
+        $result = forum_tp_can_track_forums($forumforce, $useron);
+        $this->assertEquals(true, $result);
+
+        // User on, forum optional, should be on.
+        $result = forum_tp_can_track_forums($forumoptional, $useron);
+        $this->assertEquals(true, $result);
+
+        // User off, forum off, should be off.
+        $result = forum_tp_can_track_forums($forumoff, $useroff);
+        $this->assertEquals(false, $result);
+
+        // User off, forum force, should be on.
+        $result = forum_tp_can_track_forums($forumforce, $useroff);
+        $this->assertEquals(true, $result);
+
+        // User off, forum optional, should be off.
+        $result = forum_tp_can_track_forums($forumoptional, $useroff);
+        $this->assertEquals(false, $result);
+
+        // Don't allow force.
+        $CFG->forum_allowforcedreadtracking = 0;
+
+        // User on, forum off, should be off.
+        $result = forum_tp_can_track_forums($forumoff, $useron);
+        $this->assertEquals(false, $result);
+
+        // User on, forum on, should be on.
+        $result = forum_tp_can_track_forums($forumforce, $useron);
+        $this->assertEquals(true, $result);
+
+        // User on, forum optional, should be on.
+        $result = forum_tp_can_track_forums($forumoptional, $useron);
+        $this->assertEquals(true, $result);
+
+        // User off, forum off, should be off.
+        $result = forum_tp_can_track_forums($forumoff, $useroff);
+        $this->assertEquals(false, $result);
+
+        // User off, forum force, should be off.
+        $result = forum_tp_can_track_forums($forumforce, $useroff);
+        $this->assertEquals(false, $result);
+
+        // User off, forum optional, should be off.
+        $result = forum_tp_can_track_forums($forumoptional, $useroff);
+        $this->assertEquals(false, $result);
+
+    }
+
+    /**
+     * Test the logic in the test_forum_tp_is_tracked() function.
+     */
+    public function test_forum_tp_is_tracked() {
+        global $CFG;
+
+        $this->resetAfterTest();
+
+        $useron = $this->getDataGenerator()->create_user(array('trackforums' => 1));
+        $useroff = $this->getDataGenerator()->create_user(array('trackforums' => 0));
+        $course = $this->getDataGenerator()->create_course();
+        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OFF); // Off.
+        $forumoff = $this->getDataGenerator()->create_module('forum', $options);
+
+        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_ON); // On.
+        $forumforce = $this->getDataGenerator()->create_module('forum', $options);
+
+        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OPTIONAL); // Optional.
+        $forumoptional = $this->getDataGenerator()->create_module('forum', $options);
+
+        // Allow force.
+        $CFG->forum_allowforcedreadtracking = 1;
+
+        // User on, forum off, should be off.
+        $result = forum_tp_is_tracked($forumoff, $useron);
+        $this->assertEquals(false, $result);
+
+        // User on, forum force, should be on.
+        $result = forum_tp_is_tracked($forumforce, $useron);
+        $this->assertEquals(true, $result);
+
+        // User on, forum optional, should be on.
+        $result = forum_tp_is_tracked($forumoptional, $useron);
+        $this->assertEquals(true, $result);
+
+        // User off, forum off, should be off.
+        $result = forum_tp_is_tracked($forumoff, $useroff);
+        $this->assertEquals(false, $result);
+
+        // User off, forum force, should be on.
+        $result = forum_tp_is_tracked($forumforce, $useroff);
+        $this->assertEquals(true, $result);
+
+        // User off, forum optional, should be off.
+        $result = forum_tp_is_tracked($forumoptional, $useroff);
+        $this->assertEquals(false, $result);
+
+        // Don't allow force.
+        $CFG->forum_allowforcedreadtracking = 0;
+
+        // User on, forum off, should be off.
+        $result = forum_tp_is_tracked($forumoff, $useron);
+        $this->assertEquals(false, $result);
+
+        // User on, forum force, should be on.
+        $result = forum_tp_is_tracked($forumforce, $useron);
+        $this->assertEquals(true, $result);
+
+        // User on, forum optional, should be on.
+        $result = forum_tp_is_tracked($forumoptional, $useron);
+        $this->assertEquals(true, $result);
+
+        // User off, forum off, should be off.
+        $result = forum_tp_is_tracked($forumoff, $useroff);
+        $this->assertEquals(false, $result);
+
+        // User off, forum force, should be off.
+        $result = forum_tp_is_tracked($forumforce, $useroff);
+        $this->assertEquals(false, $result);
+
+        // User off, forum optional, should be off.
+        $result = forum_tp_is_tracked($forumoptional, $useroff);
+        $this->assertEquals(false, $result);
+
+        // Stop tracking so we can test again.
+        forum_tp_stop_tracking($forumforce->id, $useron->id);
+        forum_tp_stop_tracking($forumoptional->id, $useron->id);
+        forum_tp_stop_tracking($forumforce->id, $useroff->id);
+        forum_tp_stop_tracking($forumoptional->id, $useroff->id);
+
+        // Allow force.
+        $CFG->forum_allowforcedreadtracking = 1;
+
+        // User on, preference off, forum force, should be on.
+        $result = forum_tp_is_tracked($forumforce, $useron);
+        $this->assertEquals(true, $result);
+
+        // User on, preference off, forum optional, should be on.
+        $result = forum_tp_is_tracked($forumoptional, $useron);
+        $this->assertEquals(false, $result);
+
+        // User off, preference off, forum force, should be on.
+        $result = forum_tp_is_tracked($forumforce, $useroff);
+        $this->assertEquals(true, $result);
+
+        // User off, preference off, forum optional, should be off.
+        $result = forum_tp_is_tracked($forumoptional, $useroff);
+        $this->assertEquals(false, $result);
+
+        // Don't allow force.
+        $CFG->forum_allowforcedreadtracking = 0;
+
+        // User on, preference off, forum force, should be on.
+        $result = forum_tp_is_tracked($forumforce, $useron);
+        $this->assertEquals(false, $result);
+
+        // User on, preference off, forum optional, should be on.
+        $result = forum_tp_is_tracked($forumoptional, $useron);
+        $this->assertEquals(false, $result);
+
+        // User off, preference off, forum force, should be off.
+        $result = forum_tp_is_tracked($forumforce, $useroff);
+        $this->assertEquals(false, $result);
+
+        // User off, preference off, forum optional, should be off.
+        $result = forum_tp_is_tracked($forumoptional, $useroff);
+        $this->assertEquals(false, $result);
+    }
+
+    /**
+     * Test the logic in the forum_tp_get_course_unread_posts() function.
+     */
+    public function test_forum_tp_get_course_unread_posts() {
+        global $CFG;
+
+        $this->resetAfterTest();
+
+        $useron = $this->getDataGenerator()->create_user(array('trackforums' => 1));
+        $useroff = $this->getDataGenerator()->create_user(array('trackforums' => 0));
+        $course = $this->getDataGenerator()->create_course();
+        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OFF); // Off.
+        $forumoff = $this->getDataGenerator()->create_module('forum', $options);
+
+        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_ON); // On.
+        $forumforce = $this->getDataGenerator()->create_module('forum', $options);
+
+        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OPTIONAL); // Optional.
+        $forumoptional = $this->getDataGenerator()->create_module('forum', $options);
+
+        // Add discussions to the tracking off forum.
+        $record = new stdClass();
+        $record->course = $course->id;
+        $record->userid = $useron->id;
+        $record->forum = $forumoff->id;
+        $discussionoff = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
+
+        // Add discussions to the tracking forced forum.
+        $record = new stdClass();
+        $record->course = $course->id;
+        $record->userid = $useron->id;
+        $record->forum = $forumforce->id;
+        $discussionforce = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
+
+        // Add post to the tracking forced discussion.
+        $record = new stdClass();
+        $record->course = $course->id;
+        $record->userid = $useroff->id;
+        $record->forum = $forumforce->id;
+        $record->discussion = $discussionforce->id;
+        $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
+
+        // Add discussions to the tracking optional forum.
+        $record = new stdClass();
+        $record->course = $course->id;
+        $record->userid = $useron->id;
+        $record->forum = $forumoptional->id;
+        $discussionoptional = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
+
+        // Allow force.
+        $CFG->forum_allowforcedreadtracking = 1;
+
+        $result = forum_tp_get_course_unread_posts($useron->id, $course->id);
+        $this->assertEquals(2, count($result));
+        $this->assertEquals(false, isset($result[$forumoff->id]));
+        $this->assertEquals(true, isset($result[$forumforce->id]));
+        $this->assertEquals(2, $result[$forumforce->id]->unread);
+        $this->assertEquals(true, isset($result[$forumoptional->id]));
+        $this->assertEquals(1, $result[$forumoptional->id]->unread);
+
+        $result = forum_tp_get_course_unread_posts($useroff->id, $course->id);
+        $this->assertEquals(1, count($result));
+        $this->assertEquals(false, isset($result[$forumoff->id]));
+        $this->assertEquals(true, isset($result[$forumforce->id]));
+        $this->assertEquals(2, $result[$forumforce->id]->unread);
+        $this->assertEquals(false, isset($result[$forumoptional->id]));
+
+        // Don't allow force.
+        $CFG->forum_allowforcedreadtracking = 0;
+
+        $result = forum_tp_get_course_unread_posts($useron->id, $course->id);
+        $this->assertEquals(2, count($result));
+        $this->assertEquals(false, isset($result[$forumoff->id]));
+        $this->assertEquals(true, isset($result[$forumforce->id]));
+        $this->assertEquals(2, $result[$forumforce->id]->unread);
+        $this->assertEquals(true, isset($result[$forumoptional->id]));
+        $this->assertEquals(1, $result[$forumoptional->id]->unread);
+
+        $result = forum_tp_get_course_unread_posts($useroff->id, $course->id);
+        $this->assertEquals(0, count($result));
+        $this->assertEquals(false, isset($result[$forumoff->id]));
+        $this->assertEquals(false, isset($result[$forumforce->id]));
+        $this->assertEquals(false, isset($result[$forumoptional->id]));
+
+        // Stop tracking so we can test again.
+        forum_tp_stop_tracking($forumforce->id, $useron->id);
+        forum_tp_stop_tracking($forumoptional->id, $useron->id);
+        forum_tp_stop_tracking($forumforce->id, $useroff->id);
+        forum_tp_stop_tracking($forumoptional->id, $useroff->id);
+
+        // Allow force.
+        $CFG->forum_allowforcedreadtracking = 1;
+
+        $result = forum_tp_get_course_unread_posts($useron->id, $course->id);
+        $this->assertEquals(1, count($result));
+        $this->assertEquals(false, isset($result[$forumoff->id]));
+        $this->assertEquals(true, isset($result[$forumforce->id]));
+        $this->assertEquals(2, $result[$forumforce->id]->unread);
+        $this->assertEquals(false, isset($result[$forumoptional->id]));
+
+        $result = forum_tp_get_course_unread_posts($useroff->id, $course->id);
+        $this->assertEquals(1, count($result));
+        $this->assertEquals(false, isset($result[$forumoff->id]));
+        $this->assertEquals(true, isset($result[$forumforce->id]));
+        $this->assertEquals(2, $result[$forumforce->id]->unread);
+        $this->assertEquals(false, isset($result[$forumoptional->id]));
+
+        // Don't allow force.
+        $CFG->forum_allowforcedreadtracking = 0;
+
+        $result = forum_tp_get_course_unread_posts($useron->id, $course->id);
+        $this->assertEquals(0, count($result));
+        $this->assertEquals(false, isset($result[$forumoff->id]));
+        $this->assertEquals(false, isset($result[$forumforce->id]));
+        $this->assertEquals(false, isset($result[$forumoptional->id]));
+
+        $result = forum_tp_get_course_unread_posts($useroff->id, $course->id);
+        $this->assertEquals(0, count($result));
+        $this->assertEquals(false, isset($result[$forumoff->id]));
+        $this->assertEquals(false, isset($result[$forumforce->id]));
+        $this->assertEquals(false, isset($result[$forumoptional->id]));
+    }
+
+    /**
+     * Test the logic in the test_forum_tp_get_untracked_forums() function.
+     */
+    public function test_forum_tp_get_untracked_forums() {
+        global $CFG;
+
+        $this->resetAfterTest();
+
+        $useron = $this->getDataGenerator()->create_user(array('trackforums' => 1));
+        $useroff = $this->getDataGenerator()->create_user(array('trackforums' => 0));
+        $course = $this->getDataGenerator()->create_course();
+        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OFF); // Off.
+        $forumoff = $this->getDataGenerator()->create_module('forum', $options);
+
+        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_ON); // On.
+        $forumforce = $this->getDataGenerator()->create_module('forum', $options);
+
+        $options = array('course' => $course->id, 'trackingtype' => FORUM_TRACKING_OPTIONAL); // Optional.
+        $forumoptional = $this->getDataGenerator()->create_module('forum', $options);
+
+        // Allow force.
+        $CFG->forum_allowforcedreadtracking = 1;
+
+        // On user with force on.
+        $result = forum_tp_get_untracked_forums($useron->id, $course->id);
+        $this->assertEquals(1, count($result));
+        $this->assertEquals(true, isset($result[$forumoff->id]));
+
+        // Off user with force on.
+        $result = forum_tp_get_untracked_forums($useroff->id, $course->id);
+        $this->assertEquals(2, count($result));
+        $this->assertEquals(true, isset($result[$forumoff->id]));
+        $this->assertEquals(true, isset($result[$forumoptional->id]));
+
+        // Don't allow force.
+        $CFG->forum_allowforcedreadtracking = 0;
+
+        // On user with force off.
+        $result = forum_tp_get_untracked_forums($useron->id, $course->id);
+        $this->assertEquals(1, count($result));
+        $this->assertEquals(true, isset($result[$forumoff->id]));
+
+        // Off user with force off.
+        $result = forum_tp_get_untracked_forums($useroff->id, $course->id);
+        $this->assertEquals(3, count($result));
+        $this->assertEquals(true, isset($result[$forumoff->id]));
+        $this->assertEquals(true, isset($result[$forumoptional->id]));
+        $this->assertEquals(true, isset($result[$forumforce->id]));
+
+        // Stop tracking so we can test again.
+        forum_tp_stop_tracking($forumforce->id, $useron->id);
+        forum_tp_stop_tracking($forumoptional->id, $useron->id);
+        forum_tp_stop_tracking($forumforce->id, $useroff->id);
+        forum_tp_stop_tracking($forumoptional->id, $useroff->id);
+
+        // Allow force.
+        $CFG->forum_allowforcedreadtracking = 1;
+
+        // On user with force on.
+        $result = forum_tp_get_untracked_forums($useron->id, $course->id);
+        $this->assertEquals(2, count($result));
+        $this->assertEquals(true, isset($result[$forumoff->id]));
+        $this->assertEquals(true, isset($result[$forumoptional->id]));
+
+        // Off user with force on.
+        $result = forum_tp_get_untracked_forums($useroff->id, $course->id);
+        $this->assertEquals(2, count($result));
+        $this->assertEquals(true, isset($result[$forumoff->id]));
+        $this->assertEquals(true, isset($result[$forumoptional->id]));
+
+        // Don't allow force.
+        $CFG->forum_allowforcedreadtracking = 0;
+
+        // On user with force off.
+        $result = forum_tp_get_untracked_forums($useron->id, $course->id);
+        $this->assertEquals(3, count($result));
+        $this->assertEquals(true, isset($result[$forumoff->id]));
+        $this->assertEquals(true, isset($result[$forumoptional->id]));
+        $this->assertEquals(true, isset($result[$forumforce->id]));
+
+        // Off user with force off.
+        $result = forum_tp_get_untracked_forums($useroff->id, $course->id);
+        $this->assertEquals(3, count($result));
+        $this->assertEquals(true, isset($result[$forumoff->id]));
+        $this->assertEquals(true, isset($result[$forumoptional->id]));
+        $this->assertEquals(true, isset($result[$forumforce->id]));
+    }
 }