See Release Notes
Long Term Support Release
<?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace mod_forum; use mod_forum_generator; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/mod/forum/lib.php'); require_once($CFG->dirroot . '/mod/forum/locallib.php'); require_once($CFG->dirroot . '/rating/lib.php'); /** * The mod_forum lib.php tests. * * @package mod_forum * @copyright 2013 Frédéric Massart * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class lib_test extends \advanced_testcase { public function setUp(): void { // 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(); } public function tearDown(): void { // 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(); } public function test_forum_trigger_content_uploaded_event() { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); $context = \context_module::instance($forum->cmid); $this->setUser($user->id); $fakepost = (object) array('id' => 123, 'message' => 'Yay!', 'discussion' => 100); $cm = get_coursemodule_from_instance('forum', $forum->id); $fs = get_file_storage(); $dummy = (object) array( 'contextid' => $context->id, 'component' => 'mod_forum', 'filearea' => 'attachment', 'itemid' => $fakepost->id, 'filepath' => '/', 'filename' => 'myassignmnent.pdf' ); $fi = $fs->create_file_from_string($dummy, 'Content of ' . $dummy->filename); $data = new \stdClass(); $sink = $this->redirectEvents(); forum_trigger_content_uploaded_event($fakepost, $cm, 'some triggered from value'); $events = $sink->get_events(); $this->assertCount(1, $events); $event = reset($events); $this->assertInstanceOf('\mod_forum\event\assessable_uploaded', $event); $this->assertEquals($context->id, $event->contextid); $this->assertEquals($fakepost->id, $event->objectid); $this->assertEquals($fakepost->message, $event->other['content']); $this->assertEquals($fakepost->discussion, $event->other['discussionid']); $this->assertCount(1, $event->other['pathnamehashes']); $this->assertEquals($fi->get_pathnamehash(), $event->other['pathnamehashes'][0]); $expected = new \stdClass(); $expected->modulename = 'forum'; $expected->name = 'some triggered from value'; $expected->cmid = $forum->cmid; $expected->itemid = $fakepost->id; $expected->courseid = $course->id; $expected->userid = $user->id; $expected->content = $fakepost->message; $expected->pathnamehashes = array($fi->get_pathnamehash());< $this->assertEventLegacyData($expected, $event);$this->assertEventContextNotUsed($event); } public function test_forum_get_courses_user_posted_in() { $this->resetAfterTest(); $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $user3 = $this->getDataGenerator()->create_user(); $course1 = $this->getDataGenerator()->create_course(); $course2 = $this->getDataGenerator()->create_course(); $course3 = $this->getDataGenerator()->create_course(); // Create 3 forums, one in each course. $record = new \stdClass(); $record->course = $course1->id; $forum1 = $this->getDataGenerator()->create_module('forum', $record); $record = new \stdClass(); $record->course = $course2->id; $forum2 = $this->getDataGenerator()->create_module('forum', $record); $record = new \stdClass(); $record->course = $course3->id; $forum3 = $this->getDataGenerator()->create_module('forum', $record); // Add a second forum in course 1. $record = new \stdClass(); $record->course = $course1->id; $forum4 = $this->getDataGenerator()->create_module('forum', $record); // Add discussions to course 1 started by user1. $record = new \stdClass(); $record->course = $course1->id; $record->userid = $user1->id; $record->forum = $forum1->id; $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); $record = new \stdClass(); $record->course = $course1->id; $record->userid = $user1->id; $record->forum = $forum4->id; $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); // Add discussions to course2 started by user1. $record = new \stdClass(); $record->course = $course2->id; $record->userid = $user1->id; $record->forum = $forum2->id; $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); // Add discussions to course 3 started by user2. $record = new \stdClass(); $record->course = $course3->id; $record->userid = $user2->id; $record->forum = $forum3->id; $discussion3 = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); // Add post to course 3 by user1. $record = new \stdClass(); $record->course = $course3->id; $record->userid = $user1->id; $record->forum = $forum3->id; $record->discussion = $discussion3->id; $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); // User 3 hasn't posted anything, so shouldn't get any results. $user3courses = forum_get_courses_user_posted_in($user3); $this->assertEmpty($user3courses); // User 2 has only posted in course3. $user2courses = forum_get_courses_user_posted_in($user2); $this->assertCount(1, $user2courses); $user2course = array_shift($user2courses); $this->assertEquals($course3->id, $user2course->id); $this->assertEquals($course3->shortname, $user2course->shortname); // User 1 has posted in all 3 courses. $user1courses = forum_get_courses_user_posted_in($user1); $this->assertCount(3, $user1courses); foreach ($user1courses as $course) { $this->assertContains($course->id, array($course1->id, $course2->id, $course3->id)); $this->assertContains($course->shortname, array($course1->shortname, $course2->shortname, $course3->shortname)); } // User 1 has only started a discussion in course 1 and 2 though. $user1courses = forum_get_courses_user_posted_in($user1, true); $this->assertCount(2, $user1courses); foreach ($user1courses as $course) { $this->assertContains($course->id, array($course1->id, $course2->id)); $this->assertContains($course->shortname, array($course1->shortname, $course2->shortname)); } } /** * 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_FORCED); // 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(); $cache = \cache::make('mod_forum', 'forum_is_tracked'); $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_FORCED); // 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); $cache->purge(); // 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); $cache->purge(); // 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); $cache->purge(); // 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_FORCED); // 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 forum_tp_get_course_unread_posts() function when private replies are present. * * @covers ::forum_tp_get_course_unread_posts */ public function test_forum_tp_get_course_unread_posts_with_private_replies() { global $DB; $this->resetAfterTest(); $generator = $this->getDataGenerator(); // Create 3 students. $s1 = $generator->create_user(['trackforums' => 1]); $s2 = $generator->create_user(['trackforums' => 1]); $s3 = $generator->create_user(['trackforums' => 1]); // Editing teacher. $t1 = $generator->create_user(['trackforums' => 1]); // Non-editing teacher. $t2 = $generator->create_user(['trackforums' => 1]); // Create our course. $course = $generator->create_course(); // Enrol editing and non-editing teachers. $generator->enrol_user($t1->id, $course->id, 'editingteacher'); $generator->enrol_user($t2->id, $course->id, 'teacher'); // Create forums. $forum1 = $generator->create_module('forum', ['course' => $course->id]); $forum2 = $generator->create_module('forum', ['course' => $course->id]); $forumgenerator = $generator->get_plugin_generator('mod_forum'); // Prevent the non-editing teacher from reading private replies in forum 2. $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'teacher']); $forum2cm = get_coursemodule_from_instance('forum', $forum2->id); $forum2context = \context_module::instance($forum2cm->id); role_change_permission($teacherroleid, $forum2context, 'mod/forum:readprivatereplies', CAP_PREVENT); // Create discussion by s1. $discussiondata = (object)[ 'course' => $course->id, 'forum' => $forum1->id, 'userid' => $s1->id, ]; $discussion1 = $forumgenerator->create_discussion($discussiondata); // Create discussion by s2. $discussiondata->userid = $s2->id; $discussion2 = $forumgenerator->create_discussion($discussiondata); // Create discussion by s3. $discussiondata->userid = $s3->id; $discussion3 = $forumgenerator->create_discussion($discussiondata); // Post a normal reply to s1's discussion in forum 1 as the editing teacher. $replydata = (object)[ 'course' => $course->id, 'forum' => $forum1->id, 'discussion' => $discussion1->id, 'userid' => $t1->id, ]; $forumgenerator->create_post($replydata); // Post a normal reply to s2's discussion as the editing teacher. $replydata->discussion = $discussion2->id; $forumgenerator->create_post($replydata); // Post a normal reply to s3's discussion as the editing teacher. $replydata->discussion = $discussion3->id; $forumgenerator->create_post($replydata); // Post a private reply to s1's discussion in forum 1 as the editing teacher. $replydata->discussion = $discussion1->id; $replydata->userid = $t1->id; $replydata->privatereplyto = $s1->id; $forumgenerator->create_post($replydata); // Post another private reply to s1 as the teacher. $forumgenerator->create_post($replydata); // Post a private reply to s2's discussion as the editing teacher. $replydata->discussion = $discussion2->id; $replydata->privatereplyto = $s2->id; $forumgenerator->create_post($replydata); // Create discussion by s1 in forum 2. $discussiondata->forum = $forum2->id; $discussiondata->userid = $s1->id; $discussion21 = $forumgenerator->create_discussion($discussiondata); // Post a private reply to s1's discussion in forum 2 as the editing teacher. $replydata->discussion = $discussion21->id; $replydata->privatereplyto = $s1->id; $forumgenerator->create_post($replydata); // Let's count! // S1 should see 8 unread posts 3 discussions posts + 2 private replies + 3 normal replies. $result = forum_tp_get_course_unread_posts($s1->id, $course->id); $unreadcounts = $result[$forum1->id]; $this->assertEquals(8, $unreadcounts->unread); // S2 should see 7 unread posts 3 discussions posts + 1 private reply + 3 normal replies. $result = forum_tp_get_course_unread_posts($s2->id, $course->id); $unreadcounts = $result[$forum1->id]; $this->assertEquals(7, $unreadcounts->unread); // S3 should see 6 unread posts 3 discussions posts + 3 normal replies. No private replies. $result = forum_tp_get_course_unread_posts($s3->id, $course->id); $unreadcounts = $result[$forum1->id]; $this->assertEquals(6, $unreadcounts->unread); // The editing teacher should see 9 unread posts in forum 1: 3 discussions posts + 3 normal replies + 3 private replies. $result = forum_tp_get_course_unread_posts($t1->id, $course->id); $unreadcounts = $result[$forum1->id]; $this->assertEquals(9, $unreadcounts->unread); // Same with the non-editing teacher, since they can read private replies by default. $result = forum_tp_get_course_unread_posts($t2->id, $course->id); $unreadcounts = $result[$forum1->id]; $this->assertEquals(9, $unreadcounts->unread); // But for forum 2, the non-editing teacher should only see 1 unread which is s1's discussion post. $unreadcounts = $result[$forum2->id]; $this->assertEquals(1, $unreadcounts->unread); } /** * Test the logic in the forum_tp_count_forum_unread_posts() function when private replies are present but without * separate group mode. This should yield the same results returned by forum_tp_get_course_unread_posts(). * * @covers ::forum_tp_count_forum_unread_posts */ public function test_forum_tp_count_forum_unread_posts_with_private_replies() { global $DB; $this->resetAfterTest(); $generator = $this->getDataGenerator(); // Create 3 students. $s1 = $generator->create_user(['username' => 's1', 'trackforums' => 1]); $s2 = $generator->create_user(['username' => 's2', 'trackforums' => 1]); $s3 = $generator->create_user(['username' => 's3', 'trackforums' => 1]); // Editing teacher. $t1 = $generator->create_user(['username' => 't1', 'trackforums' => 1]); // Non-editing teacher. $t2 = $generator->create_user(['username' => 't2', 'trackforums' => 1]); // Create our course. $course = $generator->create_course(); // Enrol editing and non-editing teachers. $generator->enrol_user($t1->id, $course->id, 'editingteacher'); $generator->enrol_user($t2->id, $course->id, 'teacher'); // Create forums. $forum1 = $generator->create_module('forum', ['course' => $course->id]); $forum2 = $generator->create_module('forum', ['course' => $course->id]); $forumgenerator = $generator->get_plugin_generator('mod_forum'); // Prevent the non-editing teacher from reading private replies in forum 2. $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'teacher']); $forum2cm = get_coursemodule_from_instance('forum', $forum2->id); $forum2context = \context_module::instance($forum2cm->id); role_change_permission($teacherroleid, $forum2context, 'mod/forum:readprivatereplies', CAP_PREVENT); // Create discussion by s1. $discussiondata = (object)[ 'course' => $course->id, 'forum' => $forum1->id, 'userid' => $s1->id, ]; $discussion1 = $forumgenerator->create_discussion($discussiondata); // Create discussion by s2. $discussiondata->userid = $s2->id; $discussion2 = $forumgenerator->create_discussion($discussiondata); // Create discussion by s3. $discussiondata->userid = $s3->id; $discussion3 = $forumgenerator->create_discussion($discussiondata); // Post a normal reply to s1's discussion in forum 1 as the editing teacher. $replydata = (object)[ 'course' => $course->id, 'forum' => $forum1->id, 'discussion' => $discussion1->id, 'userid' => $t1->id, ]; $forumgenerator->create_post($replydata); // Post a normal reply to s2's discussion as the editing teacher. $replydata->discussion = $discussion2->id; $forumgenerator->create_post($replydata); // Post a normal reply to s3's discussion as the editing teacher. $replydata->discussion = $discussion3->id; $forumgenerator->create_post($replydata); // Post a private reply to s1's discussion in forum 1 as the editing teacher. $replydata->discussion = $discussion1->id; $replydata->userid = $t1->id; $replydata->privatereplyto = $s1->id; $forumgenerator->create_post($replydata); // Post another private reply to s1 as the teacher. $forumgenerator->create_post($replydata); // Post a private reply to s2's discussion as the editing teacher. $replydata->discussion = $discussion2->id; $replydata->privatereplyto = $s2->id; $forumgenerator->create_post($replydata); // Create discussion by s1 in forum 2. $discussiondata->forum = $forum2->id; $discussiondata->userid = $s1->id; $discussion11 = $forumgenerator->create_discussion($discussiondata); // Post a private reply to s1's discussion in forum 2 as the editing teacher. $replydata->discussion = $discussion11->id; $replydata->privatereplyto = $s1->id; $forumgenerator->create_post($replydata); // Let's count! // S1 should see 8 unread posts 3 discussions posts + 2 private replies + 3 normal replies. $this->setUser($s1); $forum1cm = get_coursemodule_from_instance('forum', $forum1->id); $result = forum_tp_count_forum_unread_posts($forum1cm, $course, true); $this->assertEquals(8, $result); // S2 should see 7 unread posts 3 discussions posts + 1 private reply + 3 normal replies. $this->setUser($s2); $result = forum_tp_count_forum_unread_posts($forum1cm, $course, true); $this->assertEquals(7, $result); // S3 should see 6 unread posts 3 discussions posts + 3 normal replies. No private replies. $this->setUser($s3); $result = forum_tp_count_forum_unread_posts($forum1cm, $course, true); $this->assertEquals(6, $result); // The editing teacher should see 9 unread posts in forum 1: 3 discussions posts + 3 normal replies + 3 private replies. $this->setUser($t1); $result = forum_tp_count_forum_unread_posts($forum1cm, $course, true); $this->assertEquals(9, $result); // Same with the non-editing teacher, since they can read private replies by default. $this->setUser($t2); $result = forum_tp_count_forum_unread_posts($forum1cm, $course, true); $this->assertEquals(9, $result); // But for forum 2, the non-editing teacher should only see 1 unread which is s1's discussion post. $result = forum_tp_count_forum_unread_posts($forum2cm, $course); $this->assertEquals(1, $result); } /** * Test the logic in the forum_tp_count_forum_unread_posts() function when private replies are present and group modes are set. * * @covers ::forum_tp_count_forum_unread_posts */ public function test_forum_tp_count_forum_unread_posts_with_private_replies_and_separate_groups() { $this->resetAfterTest(); $generator = $this->getDataGenerator(); // Create 3 students. $s1 = $generator->create_user(['username' => 's1', 'trackforums' => 1]); $s2 = $generator->create_user(['username' => 's2', 'trackforums' => 1]); // Editing teacher. $t1 = $generator->create_user(['username' => 't1', 'trackforums' => 1]); // Create our course. $course = $generator->create_course(); // Enrol students, editing and non-editing teachers. $generator->enrol_user($s1->id, $course->id, 'student'); $generator->enrol_user($s2->id, $course->id, 'student'); $generator->enrol_user($t1->id, $course->id, 'editingteacher'); // Create groups. $g1 = $generator->create_group(['courseid' => $course->id]); $g2 = $generator->create_group(['courseid' => $course->id]); $generator->create_group_member(['groupid' => $g1->id, 'userid' => $s1->id]); $generator->create_group_member(['groupid' => $g2->id, 'userid' => $s2->id]); // Create forums. $forum1 = $generator->create_module('forum', ['course' => $course->id, 'groupmode' => SEPARATEGROUPS]); $forum2 = $generator->create_module('forum', ['course' => $course->id, 'groupmode' => VISIBLEGROUPS]); $forumgenerator = $generator->get_plugin_generator('mod_forum'); // Create discussion by s1. $discussiondata = (object)[ 'course' => $course->id, 'forum' => $forum1->id, 'userid' => $s1->id, 'groupid' => $g1->id, ]; $discussion1 = $forumgenerator->create_discussion($discussiondata); // Create discussion by s2. $discussiondata->userid = $s2->id; $discussiondata->groupid = $g2->id; $discussion2 = $forumgenerator->create_discussion($discussiondata); // Post a normal reply to s1's discussion in forum 1 as the editing teacher. $replydata = (object)[ 'course' => $course->id, 'forum' => $forum1->id, 'discussion' => $discussion1->id, 'userid' => $t1->id, ]; $forumgenerator->create_post($replydata); // Post a normal reply to s2's discussion as the editing teacher. $replydata->discussion = $discussion2->id; $forumgenerator->create_post($replydata); // Post a private reply to s1's discussion in forum 1 as the editing teacher. $replydata->discussion = $discussion1->id; $replydata->userid = $t1->id; $replydata->privatereplyto = $s1->id; $forumgenerator->create_post($replydata); // Post another private reply to s1 as the teacher. $forumgenerator->create_post($replydata); // Post a private reply to s2's discussion as the editing teacher. $replydata->discussion = $discussion2->id; $replydata->privatereplyto = $s2->id; $forumgenerator->create_post($replydata); // Create discussion by s1 in forum 2. $discussiondata->forum = $forum2->id; $discussiondata->userid = $s1->id; $discussiondata->groupid = $g1->id; $discussion21 = $forumgenerator->create_discussion($discussiondata); // Post a private reply to s1's discussion in forum 2 as the editing teacher. $replydata->discussion = $discussion21->id; $replydata->privatereplyto = $s1->id; $forumgenerator->create_post($replydata); // Let's count! // S1 should see 4 unread posts in forum 1 (1 discussions post + 2 private replies + 1 normal reply). $this->setUser($s1); $forum1cm = get_coursemodule_from_instance('forum', $forum1->id); $result = forum_tp_count_forum_unread_posts($forum1cm, $course, true); $this->assertEquals(4, $result); // S2 should see 3 unread posts in forum 1 (1 discussions post + 1 private reply + 1 normal reply). $this->setUser($s2); $result = forum_tp_count_forum_unread_posts($forum1cm, $course, true); $this->assertEquals(3, $result); // S2 should see 1 unread posts in forum 2 (visible groups, 1 discussion post from s1). $forum2cm = get_coursemodule_from_instance('forum', $forum2->id); $result = forum_tp_count_forum_unread_posts($forum2cm, $course, true); $this->assertEquals(1, $result); // The editing teacher should still see 7 unread posts (2 discussions posts + 2 normal replies + 3 private replies) // in forum 1 since they have the capability to view all groups by default. $this->setUser($t1); $result = forum_tp_count_forum_unread_posts($forum1cm, $course, true); $this->assertEquals(7, $result); } /** * 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_FORCED); // 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])); } /** * Test subscription using automatic subscription on create. */ public function test_forum_auto_subscribe_on_create() { global $CFG; $this->resetAfterTest(); $usercount = 5; $course = $this->getDataGenerator()->create_course(); $users = array(); for ($i = 0; $i < $usercount; $i++) { $user = $this->getDataGenerator()->create_user(); $users[] = $user; $this->getDataGenerator()->enrol_user($user->id, $course->id); } $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE); // Automatic Subscription. $forum = $this->getDataGenerator()->create_module('forum', $options); $result = \mod_forum\subscriptions::fetch_subscribed_users($forum); $this->assertEquals($usercount, count($result)); foreach ($users as $user) { $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum)); } } /** * Test subscription using forced subscription on create. */ public function test_forum_forced_subscribe_on_create() { global $CFG; $this->resetAfterTest(); $usercount = 5; $course = $this->getDataGenerator()->create_course(); $users = array(); for ($i = 0; $i < $usercount; $i++) { $user = $this->getDataGenerator()->create_user(); $users[] = $user; $this->getDataGenerator()->enrol_user($user->id, $course->id); } $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE); // Forced subscription. $forum = $this->getDataGenerator()->create_module('forum', $options); $result = \mod_forum\subscriptions::fetch_subscribed_users($forum); $this->assertEquals($usercount, count($result)); foreach ($users as $user) { $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum)); } } /** * Test subscription using optional subscription on create. */ public function test_forum_optional_subscribe_on_create() { global $CFG; $this->resetAfterTest(); $usercount = 5; $course = $this->getDataGenerator()->create_course(); $users = array(); for ($i = 0; $i < $usercount; $i++) { $user = $this->getDataGenerator()->create_user(); $users[] = $user; $this->getDataGenerator()->enrol_user($user->id, $course->id); } $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); // Subscription optional. $forum = $this->getDataGenerator()->create_module('forum', $options); $result = \mod_forum\subscriptions::fetch_subscribed_users($forum); // No subscriptions by default. $this->assertEquals(0, count($result)); foreach ($users as $user) { $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum)); } } /** * Test subscription using disallow subscription on create. */ public function test_forum_disallow_subscribe_on_create() { global $CFG; $this->resetAfterTest(); $usercount = 5; $course = $this->getDataGenerator()->create_course(); $users = array(); for ($i = 0; $i < $usercount; $i++) { $user = $this->getDataGenerator()->create_user(); $users[] = $user; $this->getDataGenerator()->enrol_user($user->id, $course->id); } $options = array('course' => $course->id, 'forcesubscribe' => FORUM_DISALLOWSUBSCRIBE); // Subscription prevented. $forum = $this->getDataGenerator()->create_module('forum', $options); $result = \mod_forum\subscriptions::fetch_subscribed_users($forum); // No subscriptions by default. $this->assertEquals(0, count($result)); foreach ($users as $user) { $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum)); } } /** * Test that context fetching returns the appropriate context. */ public function test_forum_get_context() { global $DB, $PAGE; $this->resetAfterTest(); // Setup test data. $course = $this->getDataGenerator()->create_course(); $coursecontext = \context_course::instance($course->id); $options = array('course' => $course->id, 'forcesubscribe' => FORUM_CHOOSESUBSCRIBE); $forum = $this->getDataGenerator()->create_module('forum', $options); $forumcm = get_coursemodule_from_instance('forum', $forum->id); $forumcontext = \context_module::instance($forumcm->id); // First check that specifying the context results in the correct context being returned. // Do this before we set up the page object and we should return from the coursemodule record. // There should be no DB queries here because the context type was correct. $startcount = $DB->perf_get_reads(); $result = forum_get_context($forum->id, $forumcontext); $aftercount = $DB->perf_get_reads(); $this->assertEquals($forumcontext, $result); $this->assertEquals(0, $aftercount - $startcount); // And a context which is not the correct type. // This tests will result in a DB query to fetch the course_module. $startcount = $DB->perf_get_reads(); $result = forum_get_context($forum->id, $coursecontext); $aftercount = $DB->perf_get_reads(); $this->assertEquals($forumcontext, $result); $this->assertEquals(1, $aftercount - $startcount); // Now do not specify a context at all. // This tests will result in a DB query to fetch the course_module. $startcount = $DB->perf_get_reads(); $result = forum_get_context($forum->id); $aftercount = $DB->perf_get_reads(); $this->assertEquals($forumcontext, $result); $this->assertEquals(1, $aftercount - $startcount); // Set up the default page event to use the forum. $PAGE = new \moodle_page(); $PAGE->set_context($forumcontext); $PAGE->set_cm($forumcm, $course, $forum); // Now specify a context which is not a context_module. // There should be no DB queries here because we use the PAGE. $startcount = $DB->perf_get_reads(); $result = forum_get_context($forum->id, $coursecontext); $aftercount = $DB->perf_get_reads(); $this->assertEquals($forumcontext, $result); $this->assertEquals(0, $aftercount - $startcount); // Now do not specify a context at all. // There should be no DB queries here because we use the PAGE. $startcount = $DB->perf_get_reads(); $result = forum_get_context($forum->id); $aftercount = $DB->perf_get_reads(); $this->assertEquals($forumcontext, $result); $this->assertEquals(0, $aftercount - $startcount); // Now specify the page context of the course instead.. $PAGE = new \moodle_page(); $PAGE->set_context($coursecontext); // Now specify a context which is not a context_module. // This tests will result in a DB query to fetch the course_module. $startcount = $DB->perf_get_reads(); $result = forum_get_context($forum->id, $coursecontext); $aftercount = $DB->perf_get_reads(); $this->assertEquals($forumcontext, $result); $this->assertEquals(1, $aftercount - $startcount); // Now do not specify a context at all. // This tests will result in a DB query to fetch the course_module. $startcount = $DB->perf_get_reads(); $result = forum_get_context($forum->id); $aftercount = $DB->perf_get_reads(); $this->assertEquals($forumcontext, $result); $this->assertEquals(1, $aftercount - $startcount); } /** * Test getting the neighbour threads of a discussion. */ public function test_forum_get_neighbours() { global $CFG, $DB; $this->resetAfterTest(); $timenow = time(); $timenext = $timenow; // Setup test data. $forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum'); $course = $this->getDataGenerator()->create_course(); $user = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); $cm = get_coursemodule_from_instance('forum', $forum->id); $context = \context_module::instance($cm->id); $record = new \stdClass(); $record->course = $course->id; $record->userid = $user->id; $record->forum = $forum->id; $record->timemodified = time(); $disc1 = $forumgen->create_discussion($record); $record->timemodified++; $disc2 = $forumgen->create_discussion($record); $record->timemodified++; $disc3 = $forumgen->create_discussion($record); $record->timemodified++; $disc4 = $forumgen->create_discussion($record); $record->timemodified++; $disc5 = $forumgen->create_discussion($record); // Getting the neighbours. $neighbours = forum_get_discussion_neighbours($cm, $disc1, $forum); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc2->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum); $this->assertEquals($disc1->id, $neighbours['prev']->id); $this->assertEquals($disc3->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum); $this->assertEquals($disc2->id, $neighbours['prev']->id); $this->assertEquals($disc4->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc4, $forum); $this->assertEquals($disc3->id, $neighbours['prev']->id); $this->assertEquals($disc5->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc5, $forum); $this->assertEquals($disc4->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Post in some discussions. We manually update the discussion record because // the data generator plays with timemodified in a way that would break this test. $record->timemodified++; $disc1->timemodified = $record->timemodified; $DB->update_record('forum_discussions', $disc1); $neighbours = forum_get_discussion_neighbours($cm, $disc5, $forum); $this->assertEquals($disc4->id, $neighbours['prev']->id); $this->assertEquals($disc1->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc3->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc1, $forum); $this->assertEquals($disc5->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // After some discussions were created. $record->timemodified++; $disc6 = $forumgen->create_discussion($record); $neighbours = forum_get_discussion_neighbours($cm, $disc6, $forum); $this->assertEquals($disc1->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); $record->timemodified++; $disc7 = $forumgen->create_discussion($record); $neighbours = forum_get_discussion_neighbours($cm, $disc7, $forum); $this->assertEquals($disc6->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Adding timed discussions. $CFG->forum_enabletimedposts = true; $now = $record->timemodified; $past = $now - 600; $future = $now + 600; $record = new \stdClass(); $record->course = $course->id; $record->userid = $user->id; $record->forum = $forum->id; $record->timestart = $past; $record->timeend = $future; $record->timemodified = $now; $record->timemodified++; $disc8 = $forumgen->create_discussion($record); $record->timemodified++; $record->timestart = $future; $record->timeend = 0; $disc9 = $forumgen->create_discussion($record); $record->timemodified++; $record->timestart = 0; $record->timeend = 0; $disc10 = $forumgen->create_discussion($record); $record->timemodified++; $record->timestart = 0; $record->timeend = $past; $disc11 = $forumgen->create_discussion($record); $record->timemodified++; $record->timestart = $past; $record->timeend = $future; $disc12 = $forumgen->create_discussion($record); $record->timemodified++; $record->timestart = $future + 1; // Should be last post for those that can see it. $record->timeend = 0; $disc13 = $forumgen->create_discussion($record); // Admin user ignores the timed settings of discussions. // Post ordering taking into account timestart: // 8 = t // 10 = t+3 // 11 = t+4 // 12 = t+5 // 9 = t+60 // 13 = t+61. $this->setAdminUser(); $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum); $this->assertEquals($disc7->id, $neighbours['prev']->id); $this->assertEquals($disc10->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc9, $forum); $this->assertEquals($disc12->id, $neighbours['prev']->id); $this->assertEquals($disc13->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc10, $forum); $this->assertEquals($disc8->id, $neighbours['prev']->id); $this->assertEquals($disc11->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc11, $forum); $this->assertEquals($disc10->id, $neighbours['prev']->id); $this->assertEquals($disc12->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc12, $forum); $this->assertEquals($disc11->id, $neighbours['prev']->id); $this->assertEquals($disc9->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc13, $forum); $this->assertEquals($disc9->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Normal user can see their own timed discussions. $this->setUser($user); $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum); $this->assertEquals($disc7->id, $neighbours['prev']->id); $this->assertEquals($disc10->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc9, $forum); $this->assertEquals($disc12->id, $neighbours['prev']->id); $this->assertEquals($disc13->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc10, $forum); $this->assertEquals($disc8->id, $neighbours['prev']->id); $this->assertEquals($disc11->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc11, $forum); $this->assertEquals($disc10->id, $neighbours['prev']->id); $this->assertEquals($disc12->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc12, $forum); $this->assertEquals($disc11->id, $neighbours['prev']->id); $this->assertEquals($disc9->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc13, $forum); $this->assertEquals($disc9->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Normal user does not ignore timed settings. $this->setUser($user2); $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum); $this->assertEquals($disc7->id, $neighbours['prev']->id); $this->assertEquals($disc10->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc10, $forum); $this->assertEquals($disc8->id, $neighbours['prev']->id); $this->assertEquals($disc12->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc12, $forum); $this->assertEquals($disc10->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Reset to normal mode. $CFG->forum_enabletimedposts = false; $this->setAdminUser(); // Two discussions with identical timemodified will sort by id. $record->timemodified += 25; $DB->update_record('forum_discussions', (object) array('id' => $disc3->id, 'timemodified' => $record->timemodified)); $DB->update_record('forum_discussions', (object) array('id' => $disc2->id, 'timemodified' => $record->timemodified)); $DB->update_record('forum_discussions', (object) array('id' => $disc12->id, 'timemodified' => $record->timemodified - 5)); $disc2 = $DB->get_record('forum_discussions', array('id' => $disc2->id)); $disc3 = $DB->get_record('forum_discussions', array('id' => $disc3->id)); $neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum); $this->assertEquals($disc2->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum); $this->assertEquals($disc12->id, $neighbours['prev']->id); $this->assertEquals($disc3->id, $neighbours['next']->id); // Set timemodified to not be identical. $DB->update_record('forum_discussions', (object) array('id' => $disc2->id, 'timemodified' => $record->timemodified - 1)); // Test pinned posts behave correctly. $disc8->pinned = FORUM_DISCUSSION_PINNED; $DB->update_record('forum_discussions', (object) array('id' => $disc8->id, 'pinned' => $disc8->pinned)); $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum); $this->assertEquals($disc3->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); $neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum); $this->assertEquals($disc2->id, $neighbours['prev']->id); $this->assertEquals($disc8->id, $neighbours['next']->id); // Test 3 pinned posts. $disc6->pinned = FORUM_DISCUSSION_PINNED; $DB->update_record('forum_discussions', (object) array('id' => $disc6->id, 'pinned' => $disc6->pinned)); $disc4->pinned = FORUM_DISCUSSION_PINNED; $DB->update_record('forum_discussions', (object) array('id' => $disc4->id, 'pinned' => $disc4->pinned)); $neighbours = forum_get_discussion_neighbours($cm, $disc6, $forum); $this->assertEquals($disc4->id, $neighbours['prev']->id); $this->assertEquals($disc8->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc4, $forum); $this->assertEquals($disc3->id, $neighbours['prev']->id); $this->assertEquals($disc6->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum); $this->assertEquals($disc6->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); } /** * Test getting the neighbour threads of a blog-like forum. */ public function test_forum_get_neighbours_blog() { global $CFG, $DB; $this->resetAfterTest(); $timenow = time(); $timenext = $timenow; // Setup test data. $forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum'); $course = $this->getDataGenerator()->create_course(); $user = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'type' => 'blog')); $cm = get_coursemodule_from_instance('forum', $forum->id); $context = \context_module::instance($cm->id); $record = new \stdClass(); $record->course = $course->id; $record->userid = $user->id; $record->forum = $forum->id; $record->timemodified = time(); $disc1 = $forumgen->create_discussion($record); $record->timemodified++; $disc2 = $forumgen->create_discussion($record); $record->timemodified++; $disc3 = $forumgen->create_discussion($record); $record->timemodified++; $disc4 = $forumgen->create_discussion($record); $record->timemodified++; $disc5 = $forumgen->create_discussion($record); // Getting the neighbours. $neighbours = forum_get_discussion_neighbours($cm, $disc1, $forum); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc2->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum); $this->assertEquals($disc1->id, $neighbours['prev']->id); $this->assertEquals($disc3->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum); $this->assertEquals($disc2->id, $neighbours['prev']->id); $this->assertEquals($disc4->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc4, $forum); $this->assertEquals($disc3->id, $neighbours['prev']->id); $this->assertEquals($disc5->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc5, $forum); $this->assertEquals($disc4->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Make sure that the thread's timemodified does not affect the order. $record->timemodified++; $disc1->timemodified = $record->timemodified; $DB->update_record('forum_discussions', $disc1); $neighbours = forum_get_discussion_neighbours($cm, $disc1, $forum); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc2->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum); $this->assertEquals($disc1->id, $neighbours['prev']->id); $this->assertEquals($disc3->id, $neighbours['next']->id); // Add another blog post. $record->timemodified++; $disc6 = $forumgen->create_discussion($record); $neighbours = forum_get_discussion_neighbours($cm, $disc6, $forum); $this->assertEquals($disc5->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); $record->timemodified++; $disc7 = $forumgen->create_discussion($record); $neighbours = forum_get_discussion_neighbours($cm, $disc7, $forum); $this->assertEquals($disc6->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Adding timed discussions. $CFG->forum_enabletimedposts = true; $now = $record->timemodified; $past = $now - 600; $future = $now + 600; $record = new \stdClass(); $record->course = $course->id; $record->userid = $user->id; $record->forum = $forum->id; $record->timestart = $past; $record->timeend = $future; $record->timemodified = $now; $record->timemodified++; $disc8 = $forumgen->create_discussion($record); $record->timemodified++; $record->timestart = $future; $record->timeend = 0; $disc9 = $forumgen->create_discussion($record); $record->timemodified++; $record->timestart = 0; $record->timeend = 0; $disc10 = $forumgen->create_discussion($record); $record->timemodified++; $record->timestart = 0; $record->timeend = $past; $disc11 = $forumgen->create_discussion($record); $record->timemodified++; $record->timestart = $past; $record->timeend = $future; $disc12 = $forumgen->create_discussion($record); // Admin user ignores the timed settings of discussions. $this->setAdminUser(); $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum); $this->assertEquals($disc7->id, $neighbours['prev']->id); $this->assertEquals($disc9->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc9, $forum); $this->assertEquals($disc8->id, $neighbours['prev']->id); $this->assertEquals($disc10->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc10, $forum); $this->assertEquals($disc9->id, $neighbours['prev']->id); $this->assertEquals($disc11->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc11, $forum); $this->assertEquals($disc10->id, $neighbours['prev']->id); $this->assertEquals($disc12->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc12, $forum); $this->assertEquals($disc11->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Normal user can see their own timed discussions. $this->setUser($user); $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum); $this->assertEquals($disc7->id, $neighbours['prev']->id); $this->assertEquals($disc9->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc9, $forum); $this->assertEquals($disc8->id, $neighbours['prev']->id); $this->assertEquals($disc10->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc10, $forum); $this->assertEquals($disc9->id, $neighbours['prev']->id); $this->assertEquals($disc11->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc11, $forum); $this->assertEquals($disc10->id, $neighbours['prev']->id); $this->assertEquals($disc12->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc12, $forum); $this->assertEquals($disc11->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Normal user does not ignore timed settings. $this->setUser($user2); $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum); $this->assertEquals($disc7->id, $neighbours['prev']->id); $this->assertEquals($disc10->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc10, $forum); $this->assertEquals($disc8->id, $neighbours['prev']->id); $this->assertEquals($disc12->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc12, $forum); $this->assertEquals($disc10->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Reset to normal mode. $CFG->forum_enabletimedposts = false; $this->setAdminUser(); $record->timemodified++; // Two blog posts with identical creation time will sort by id. $DB->update_record('forum_posts', (object) array('id' => $disc2->firstpost, 'created' => $record->timemodified)); $DB->update_record('forum_posts', (object) array('id' => $disc3->firstpost, 'created' => $record->timemodified)); $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum); $this->assertEquals($disc12->id, $neighbours['prev']->id); $this->assertEquals($disc3->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum); $this->assertEquals($disc2->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); } /** * Test getting the neighbour threads of a discussion. */ public function test_forum_get_neighbours_with_groups() { $this->resetAfterTest(); $timenow = time(); $timenext = $timenow; // Setup test data. $forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum'); $course = $this->getDataGenerator()->create_course(); $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->create_group_member(array('userid' => $user1->id, 'groupid' => $group1->id)); $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'groupmode' => VISIBLEGROUPS)); $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'groupmode' => SEPARATEGROUPS)); $cm1 = get_coursemodule_from_instance('forum', $forum1->id); $cm2 = get_coursemodule_from_instance('forum', $forum2->id); $context1 = \context_module::instance($cm1->id); $context2 = \context_module::instance($cm2->id); // Creating discussions in both forums. $record = new \stdClass(); $record->course = $course->id; $record->userid = $user1->id; $record->forum = $forum1->id; $record->groupid = $group1->id; $record->timemodified = time(); $disc11 = $forumgen->create_discussion($record); $record->forum = $forum2->id; $record->timemodified++; $disc21 = $forumgen->create_discussion($record); $record->timemodified++; $record->userid = $user2->id; $record->forum = $forum1->id; $record->groupid = $group2->id; $disc12 = $forumgen->create_discussion($record); $record->forum = $forum2->id; $disc22 = $forumgen->create_discussion($record); $record->timemodified++; $record->userid = $user1->id; $record->forum = $forum1->id; $record->groupid = null; $disc13 = $forumgen->create_discussion($record); $record->forum = $forum2->id; $disc23 = $forumgen->create_discussion($record); $record->timemodified++; $record->userid = $user2->id; $record->forum = $forum1->id; $record->groupid = $group2->id; $disc14 = $forumgen->create_discussion($record); $record->forum = $forum2->id; $disc24 = $forumgen->create_discussion($record); $record->timemodified++; $record->userid = $user1->id; $record->forum = $forum1->id; $record->groupid = $group1->id; $disc15 = $forumgen->create_discussion($record); $record->forum = $forum2->id; $disc25 = $forumgen->create_discussion($record); // Admin user can see all groups. $this->setAdminUser(); $neighbours = forum_get_discussion_neighbours($cm1, $disc11, $forum1); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc12->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc21, $forum2); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc22->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc12, $forum1); $this->assertEquals($disc11->id, $neighbours['prev']->id); $this->assertEquals($disc13->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc22, $forum2); $this->assertEquals($disc21->id, $neighbours['prev']->id); $this->assertEquals($disc23->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1); $this->assertEquals($disc12->id, $neighbours['prev']->id); $this->assertEquals($disc14->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2); $this->assertEquals($disc22->id, $neighbours['prev']->id); $this->assertEquals($disc24->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc14, $forum1); $this->assertEquals($disc13->id, $neighbours['prev']->id); $this->assertEquals($disc15->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc24, $forum2); $this->assertEquals($disc23->id, $neighbours['prev']->id); $this->assertEquals($disc25->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc15, $forum1); $this->assertEquals($disc14->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); $neighbours = forum_get_discussion_neighbours($cm2, $disc25, $forum2); $this->assertEquals($disc24->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Admin user is only viewing group 1. $_POST['group'] = $group1->id; $this->assertEquals($group1->id, groups_get_activity_group($cm1, true)); $this->assertEquals($group1->id, groups_get_activity_group($cm2, true)); $neighbours = forum_get_discussion_neighbours($cm1, $disc11, $forum1); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc13->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc21, $forum2); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc23->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1); $this->assertEquals($disc11->id, $neighbours['prev']->id); $this->assertEquals($disc15->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2); $this->assertEquals($disc21->id, $neighbours['prev']->id); $this->assertEquals($disc25->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc15, $forum1); $this->assertEquals($disc13->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); $neighbours = forum_get_discussion_neighbours($cm2, $disc25, $forum2); $this->assertEquals($disc23->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Normal user viewing non-grouped posts (this is only possible in visible groups). $this->setUser($user1); $_POST['group'] = 0; $this->assertEquals(0, groups_get_activity_group($cm1, true)); // They can see anything in visible groups. $neighbours = forum_get_discussion_neighbours($cm1, $disc12, $forum1); $this->assertEquals($disc11->id, $neighbours['prev']->id); $this->assertEquals($disc13->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1); $this->assertEquals($disc12->id, $neighbours['prev']->id); $this->assertEquals($disc14->id, $neighbours['next']->id); // Normal user, orphan of groups, can only see non-grouped posts in separate groups. $this->setUser($user2); $_POST['group'] = 0; $this->assertEquals(0, groups_get_activity_group($cm2, true)); $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2); $this->assertEmpty($neighbours['prev']); $this->assertEmpty($neighbours['next']); $neighbours = forum_get_discussion_neighbours($cm2, $disc22, $forum2); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc23->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc24, $forum2); $this->assertEquals($disc23->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Switching to viewing group 1. $this->setUser($user1); $_POST['group'] = $group1->id; $this->assertEquals($group1->id, groups_get_activity_group($cm1, true)); $this->assertEquals($group1->id, groups_get_activity_group($cm2, true)); // They can see non-grouped or same group. $neighbours = forum_get_discussion_neighbours($cm1, $disc11, $forum1); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc13->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc21, $forum2); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc23->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1); $this->assertEquals($disc11->id, $neighbours['prev']->id); $this->assertEquals($disc15->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2); $this->assertEquals($disc21->id, $neighbours['prev']->id); $this->assertEquals($disc25->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc15, $forum1); $this->assertEquals($disc13->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); $neighbours = forum_get_discussion_neighbours($cm2, $disc25, $forum2); $this->assertEquals($disc23->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Querying the neighbours of a discussion passing the wrong CM. $this->expectException('coding_exception'); forum_get_discussion_neighbours($cm2, $disc11, $forum2); } /** * Test getting the neighbour threads of a blog-like forum with groups involved. */ public function test_forum_get_neighbours_with_groups_blog() { $this->resetAfterTest(); $timenow = time(); $timenext = $timenow; // Setup test data. $forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum'); $course = $this->getDataGenerator()->create_course(); $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->create_group_member(array('userid' => $user1->id, 'groupid' => $group1->id)); $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'type' => 'blog', 'groupmode' => VISIBLEGROUPS)); $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'type' => 'blog', 'groupmode' => SEPARATEGROUPS)); $cm1 = get_coursemodule_from_instance('forum', $forum1->id); $cm2 = get_coursemodule_from_instance('forum', $forum2->id); $context1 = \context_module::instance($cm1->id); $context2 = \context_module::instance($cm2->id); // Creating blog posts in both forums. $record = new \stdClass(); $record->course = $course->id; $record->userid = $user1->id; $record->forum = $forum1->id; $record->groupid = $group1->id; $record->timemodified = time(); $disc11 = $forumgen->create_discussion($record); $record->timenow = $timenext++; $record->forum = $forum2->id; $record->timemodified++; $disc21 = $forumgen->create_discussion($record); $record->timemodified++; $record->userid = $user2->id; $record->forum = $forum1->id; $record->groupid = $group2->id; $disc12 = $forumgen->create_discussion($record); $record->forum = $forum2->id; $disc22 = $forumgen->create_discussion($record); $record->timemodified++; $record->userid = $user1->id; $record->forum = $forum1->id; $record->groupid = null; $disc13 = $forumgen->create_discussion($record); $record->forum = $forum2->id; $disc23 = $forumgen->create_discussion($record); $record->timemodified++; $record->userid = $user2->id; $record->forum = $forum1->id; $record->groupid = $group2->id; $disc14 = $forumgen->create_discussion($record); $record->forum = $forum2->id; $disc24 = $forumgen->create_discussion($record); $record->timemodified++; $record->userid = $user1->id; $record->forum = $forum1->id; $record->groupid = $group1->id; $disc15 = $forumgen->create_discussion($record); $record->forum = $forum2->id; $disc25 = $forumgen->create_discussion($record); // Admin user can see all groups. $this->setAdminUser(); $neighbours = forum_get_discussion_neighbours($cm1, $disc11, $forum1); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc12->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc21, $forum2); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc22->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc12, $forum1); $this->assertEquals($disc11->id, $neighbours['prev']->id); $this->assertEquals($disc13->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc22, $forum2); $this->assertEquals($disc21->id, $neighbours['prev']->id); $this->assertEquals($disc23->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1); $this->assertEquals($disc12->id, $neighbours['prev']->id); $this->assertEquals($disc14->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2); $this->assertEquals($disc22->id, $neighbours['prev']->id); $this->assertEquals($disc24->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc14, $forum1); $this->assertEquals($disc13->id, $neighbours['prev']->id); $this->assertEquals($disc15->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc24, $forum2); $this->assertEquals($disc23->id, $neighbours['prev']->id); $this->assertEquals($disc25->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc15, $forum1); $this->assertEquals($disc14->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); $neighbours = forum_get_discussion_neighbours($cm2, $disc25, $forum2); $this->assertEquals($disc24->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Admin user is only viewing group 1. $_POST['group'] = $group1->id; $this->assertEquals($group1->id, groups_get_activity_group($cm1, true)); $this->assertEquals($group1->id, groups_get_activity_group($cm2, true)); $neighbours = forum_get_discussion_neighbours($cm1, $disc11, $forum1); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc13->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc21, $forum2); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc23->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1); $this->assertEquals($disc11->id, $neighbours['prev']->id); $this->assertEquals($disc15->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2); $this->assertEquals($disc21->id, $neighbours['prev']->id); $this->assertEquals($disc25->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc15, $forum1); $this->assertEquals($disc13->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); $neighbours = forum_get_discussion_neighbours($cm2, $disc25, $forum2); $this->assertEquals($disc23->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Normal user viewing non-grouped posts (this is only possible in visible groups). $this->setUser($user1); $_POST['group'] = 0; $this->assertEquals(0, groups_get_activity_group($cm1, true)); // They can see anything in visible groups. $neighbours = forum_get_discussion_neighbours($cm1, $disc12, $forum1); $this->assertEquals($disc11->id, $neighbours['prev']->id); $this->assertEquals($disc13->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1); $this->assertEquals($disc12->id, $neighbours['prev']->id); $this->assertEquals($disc14->id, $neighbours['next']->id); // Normal user, orphan of groups, can only see non-grouped posts in separate groups. $this->setUser($user2); $_POST['group'] = 0; $this->assertEquals(0, groups_get_activity_group($cm2, true)); $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2); $this->assertEmpty($neighbours['prev']); $this->assertEmpty($neighbours['next']); $neighbours = forum_get_discussion_neighbours($cm2, $disc22, $forum2); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc23->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc24, $forum2); $this->assertEquals($disc23->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Switching to viewing group 1. $this->setUser($user1); $_POST['group'] = $group1->id; $this->assertEquals($group1->id, groups_get_activity_group($cm1, true)); $this->assertEquals($group1->id, groups_get_activity_group($cm2, true)); // They can see non-grouped or same group. $neighbours = forum_get_discussion_neighbours($cm1, $disc11, $forum1); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc13->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc21, $forum2); $this->assertEmpty($neighbours['prev']); $this->assertEquals($disc23->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc13, $forum1); $this->assertEquals($disc11->id, $neighbours['prev']->id); $this->assertEquals($disc15->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm2, $disc23, $forum2); $this->assertEquals($disc21->id, $neighbours['prev']->id); $this->assertEquals($disc25->id, $neighbours['next']->id); $neighbours = forum_get_discussion_neighbours($cm1, $disc15, $forum1); $this->assertEquals($disc13->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); $neighbours = forum_get_discussion_neighbours($cm2, $disc25, $forum2); $this->assertEquals($disc23->id, $neighbours['prev']->id); $this->assertEmpty($neighbours['next']); // Querying the neighbours of a discussion passing the wrong CM. $this->expectException('coding_exception'); forum_get_discussion_neighbours($cm2, $disc11, $forum2); } public function test_count_discussion_replies_basic() { list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5); // Count the discussion replies in the forum. $result = forum_count_discussion_replies($forum->id); $this->assertCount(10, $result); } public function test_count_discussion_replies_limited() { list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5); // Adding limits shouldn't make a difference. $result = forum_count_discussion_replies($forum->id, "", 20); $this->assertCount(10, $result); } public function test_count_discussion_replies_paginated() { list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5); // Adding paging shouldn't make any difference. $result = forum_count_discussion_replies($forum->id, "", -1, 0, 100); $this->assertCount(10, $result); } public function test_count_discussion_replies_paginated_sorted() { list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5); // Specifying the forumsort should also give a good result. This follows a different path. $result = forum_count_discussion_replies($forum->id, "d.id asc", -1, 0, 100); $this->assertCount(10, $result); foreach ($result as $row) { // Grab the first discussionid. $discussionid = array_shift($discussionids); $this->assertEquals($discussionid, $row->discussion); } } public function test_count_discussion_replies_limited_sorted() { list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5); // Adding limits, and a forumsort shouldn't make a difference. $result = forum_count_discussion_replies($forum->id, "d.id asc", 20); $this->assertCount(10, $result); foreach ($result as $row) { // Grab the first discussionid. $discussionid = array_shift($discussionids); $this->assertEquals($discussionid, $row->discussion); } } public function test_count_discussion_replies_paginated_sorted_small() { list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5); // Grabbing a smaller subset and they should be ordered as expected. $result = forum_count_discussion_replies($forum->id, "d.id asc", -1, 0, 5); $this->assertCount(5, $result); foreach ($result as $row) { // Grab the first discussionid. $discussionid = array_shift($discussionids); $this->assertEquals($discussionid, $row->discussion); } } public function test_count_discussion_replies_paginated_sorted_small_reverse() { list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5); // Grabbing a smaller subset and they should be ordered as expected. $result = forum_count_discussion_replies($forum->id, "d.id desc", -1, 0, 5); $this->assertCount(5, $result); foreach ($result as $row) { // Grab the last discussionid. $discussionid = array_pop($discussionids); $this->assertEquals($discussionid, $row->discussion); } } public function test_count_discussion_replies_limited_sorted_small_reverse() { list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5); // Adding limits, and a forumsort shouldn't make a difference. $result = forum_count_discussion_replies($forum->id, "d.id desc", 5); $this->assertCount(5, $result); foreach ($result as $row) { // Grab the last discussionid. $discussionid = array_pop($discussionids); $this->assertEquals($discussionid, $row->discussion); } } /** * Test the reply count when used with private replies. */ public function test_forum_count_discussion_replies_private() { global $DB; $this->resetAfterTest(); $course = $this->getDataGenerator()->create_course(); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); $context = \context_module::instance($forum->cmid); $cm = get_coursemodule_from_instance('forum', $forum->id); $student = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($student->id, $course->id); $teacher = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($teacher->id, $course->id); $privilegeduser = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($privilegeduser->id, $course->id, 'editingteacher'); $otheruser = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($otheruser->id, $course->id); $generator = $this->getDataGenerator()->get_plugin_generator('mod_forum'); // Create a discussion with some replies. $record = new \stdClass(); $record->course = $forum->course; $record->forum = $forum->id; $record->userid = $student->id; $discussion = $generator->create_discussion($record); $replycount = 5; $replyto = $DB->get_record('forum_posts', array('discussion' => $discussion->id)); // Create a couple of standard replies. $post = new \stdClass(); $post->userid = $student->id; $post->discussion = $discussion->id; $post->parent = $replyto->id; for ($i = 0; $i < $replycount; $i++) { $post = $generator->create_post($post); } // Create a private reply post from the teacher back to the student. $reply = new \stdClass(); $reply->userid = $teacher->id; $reply->discussion = $discussion->id; $reply->parent = $replyto->id; $reply->privatereplyto = $replyto->userid; $generator->create_post($reply); // The user is the author of the private reply. $this->setUser($teacher->id); $counts = forum_count_discussion_replies($forum->id); $this->assertEquals($replycount + 1, $counts[$discussion->id]->replies); // The user is the intended recipient. $this->setUser($student->id); $counts = forum_count_discussion_replies($forum->id); $this->assertEquals($replycount + 1, $counts[$discussion->id]->replies); // The user is not the author or recipient, but does have the readprivatereplies capability. $this->setUser($privilegeduser->id); $counts = forum_count_discussion_replies($forum->id, "", -1, -1, 0, true); $this->assertEquals($replycount + 1, $counts[$discussion->id]->replies); // The user is not allowed to view this post. $this->setUser($otheruser->id); $counts = forum_count_discussion_replies($forum->id); $this->assertEquals($replycount, $counts[$discussion->id]->replies); } public function test_discussion_pinned_sort() { list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5); $cm = get_coursemodule_from_instance('forum', $forum->id); $discussions = forum_get_discussions($cm); // First discussion should be pinned. $first = reset($discussions); $this->assertEquals(1, $first->pinned, "First discussion should be pinned discussion"); } public function test_forum_view() { global $CFG; $CFG->enablecompletion = 1; $this->resetAfterTest(); // Setup test data. $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), array('completion' => 2, 'completionview' => 1)); $context = \context_module::instance($forum->cmid); $cm = get_coursemodule_from_instance('forum', $forum->id); // Trigger and capture the event. $sink = $this->redirectEvents(); $this->setAdminUser(); forum_view($forum, $course, $cm, $context); $events = $sink->get_events(); // 2 additional events thanks to completion. $this->assertCount(3, $events); $event = array_pop($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\mod_forum\event\course_module_viewed', $event); $this->assertEquals($context, $event->get_context()); $url = new \moodle_url('/mod/forum/view.php', array('f' => $forum->id)); $this->assertEquals($url, $event->get_url()); $this->assertEventContextNotUsed($event); $this->assertNotEmpty($event->get_name()); // Check completion status. $completion = new \completion_info($course); $completiondata = $completion->get_data($cm); $this->assertEquals(1, $completiondata->completionstate); } /** * Test forum_discussion_view. */ public function test_forum_discussion_view() { global $CFG, $USER; $this->resetAfterTest(); // Setup test data. $course = $this->getDataGenerator()->create_course(); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); $discussion = $this->create_single_discussion_with_replies($forum, $USER, 2); $context = \context_module::instance($forum->cmid); $cm = get_coursemodule_from_instance('forum', $forum->id); // Trigger and capture the event. $sink = $this->redirectEvents(); $this->setAdminUser(); forum_discussion_view($context, $forum, $discussion); $events = $sink->get_events(); $this->assertCount(1, $events); $event = array_pop($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\mod_forum\event\discussion_viewed', $event); $this->assertEquals($context, $event->get_context());< $expected = array($course->id, 'forum', 'view discussion', "discuss.php?d={$discussion->id}", < $discussion->id, $forum->cmid); < $this->assertEventLegacyLogData($expected, $event);$this->assertEventContextNotUsed($event); $this->assertNotEmpty($event->get_name()); } /** * Create a new course, forum, and user with a number of discussions and replies. * * @param int $discussioncount The number of discussions to create * @param int $replycount The number of replies to create in each discussion * @return array Containing the created forum object, and the ids of the created discussions. */ protected function create_multiple_discussions_with_replies($discussioncount, $replycount) { $this->resetAfterTest(); // Setup the content. $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $record = new \stdClass(); $record->course = $course->id; $forum = $this->getDataGenerator()->create_module('forum', $record); // Create 10 discussions with replies. $discussionids = array(); for ($i = 0; $i < $discussioncount; $i++) { // Pin 3rd discussion. if ($i == 3) { $discussion = $this->create_single_discussion_pinned_with_replies($forum, $user, $replycount); } else { $discussion = $this->create_single_discussion_with_replies($forum, $user, $replycount); } $discussionids[] = $discussion->id; } return array($forum, $discussionids); } /** * Create a discussion with a number of replies. * * @param object $forum The forum which has been created * @param object $user The user making the discussion and replies * @param int $replycount The number of replies * @return object $discussion */ protected function create_single_discussion_with_replies($forum, $user, $replycount) { global $DB; $generator = self::getDataGenerator()->get_plugin_generator('mod_forum'); $record = new \stdClass(); $record->course = $forum->course; $record->forum = $forum->id; $record->userid = $user->id; $discussion = $generator->create_discussion($record); // Retrieve the first post. $replyto = $DB->get_record('forum_posts', array('discussion' => $discussion->id)); // Create the replies. $post = new \stdClass(); $post->userid = $user->id; $post->discussion = $discussion->id; $post->parent = $replyto->id; for ($i = 0; $i < $replycount; $i++) { $generator->create_post($post); } return $discussion; } /** * Create a discussion with a number of replies. * * @param object $forum The forum which has been created * @param object $user The user making the discussion and replies * @param int $replycount The number of replies * @return object $discussion */ protected function create_single_discussion_pinned_with_replies($forum, $user, $replycount) { global $DB; $generator = self::getDataGenerator()->get_plugin_generator('mod_forum'); $record = new \stdClass(); $record->course = $forum->course; $record->forum = $forum->id; $record->userid = $user->id; $record->pinned = FORUM_DISCUSSION_PINNED; $discussion = $generator->create_discussion($record); // Retrieve the first post. $replyto = $DB->get_record('forum_posts', array('discussion' => $discussion->id)); // Create the replies. $post = new \stdClass(); $post->userid = $user->id; $post->discussion = $discussion->id; $post->parent = $replyto->id; for ($i = 0; $i < $replycount; $i++) { $generator->create_post($post); } return $discussion; } /** * Tests for mod_forum_rating_can_see_item_ratings(). * * @throws coding_exception * @throws rating_exception */ public function test_mod_forum_rating_can_see_item_ratings() { global $DB; $this->resetAfterTest(); // Setup test data. $course = new \stdClass(); $course->groupmode = SEPARATEGROUPS; $course->groupmodeforce = true; $course = $this->getDataGenerator()->create_course($course); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); $generator = self::getDataGenerator()->get_plugin_generator('mod_forum'); $cm = get_coursemodule_from_instance('forum', $forum->id); $context = \context_module::instance($cm->id); // Create users. $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $user3 = $this->getDataGenerator()->create_user(); $user4 = $this->getDataGenerator()->create_user(); // Groups and stuff. $role = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST); $this->getDataGenerator()->enrol_user($user1->id, $course->id, $role->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id, $role->id); $this->getDataGenerator()->enrol_user($user3->id, $course->id, $role->id); $this->getDataGenerator()->enrol_user($user4->id, $course->id, $role->id); $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); groups_add_member($group1, $user1); groups_add_member($group1, $user2); groups_add_member($group2, $user3); groups_add_member($group2, $user4); $record = new \stdClass(); $record->course = $forum->course; $record->forum = $forum->id; $record->userid = $user1->id; $record->groupid = $group1->id; $discussion = $generator->create_discussion($record); // Retrieve the first post. $post = $DB->get_record('forum_posts', array('discussion' => $discussion->id)); $ratingoptions = new \stdClass; $ratingoptions->context = $context; $ratingoptions->ratingarea = 'post'; $ratingoptions->component = 'mod_forum'; $ratingoptions->itemid = $post->id; $ratingoptions->scaleid = 2; $ratingoptions->userid = $user2->id; $rating = new \rating($ratingoptions); $rating->update_rating(2); // Now try to access it as various users. unassign_capability('moodle/site:accessallgroups', $role->id); $params = array('contextid' => 2, 'component' => 'mod_forum', 'ratingarea' => 'post', 'itemid' => $post->id, 'scaleid' => 2); $this->setUser($user1); $this->assertTrue(mod_forum_rating_can_see_item_ratings($params)); $this->setUser($user2); $this->assertTrue(mod_forum_rating_can_see_item_ratings($params)); $this->setUser($user3); $this->assertFalse(mod_forum_rating_can_see_item_ratings($params)); $this->setUser($user4); $this->assertFalse(mod_forum_rating_can_see_item_ratings($params)); // Now try with accessallgroups cap and make sure everything is visible. assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $role->id, $context->id); $this->setUser($user1); $this->assertTrue(mod_forum_rating_can_see_item_ratings($params)); $this->setUser($user2); $this->assertTrue(mod_forum_rating_can_see_item_ratings($params)); $this->setUser($user3); $this->assertTrue(mod_forum_rating_can_see_item_ratings($params)); $this->setUser($user4); $this->assertTrue(mod_forum_rating_can_see_item_ratings($params)); // Change group mode and verify visibility. $course->groupmode = VISIBLEGROUPS; $DB->update_record('course', $course); unassign_capability('moodle/site:accessallgroups', $role->id); $this->setUser($user1); $this->assertTrue(mod_forum_rating_can_see_item_ratings($params)); $this->setUser($user2); $this->assertTrue(mod_forum_rating_can_see_item_ratings($params)); $this->setUser($user3); $this->assertTrue(mod_forum_rating_can_see_item_ratings($params)); $this->setUser($user4); $this->assertTrue(mod_forum_rating_can_see_item_ratings($params)); } /** * Test forum_get_discussions */ public function test_forum_get_discussions_with_groups() { global $DB; $this->resetAfterTest(true); // Create course to add the module. $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0)); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); self::getDataGenerator()->enrol_user($user1->id, $course->id, $role->id); self::getDataGenerator()->enrol_user($user2->id, $course->id, $role->id); self::getDataGenerator()->enrol_user($user3->id, $course->id, $role->id); // Forum forcing separate gropus. $record = new \stdClass(); $record->course = $course->id; $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS)); $cm = get_coursemodule_from_instance('forum', $forum->id); // Create groups. $group1 = self::getDataGenerator()->create_group(array('courseid' => $course->id, 'name' => 'group1')); $group2 = self::getDataGenerator()->create_group(array('courseid' => $course->id, 'name' => 'group2')); $group3 = self::getDataGenerator()->create_group(array('courseid' => $course->id, 'name' => 'group3')); // Add the user1 to g1 and g2 groups. groups_add_member($group1->id, $user1->id); groups_add_member($group2->id, $user1->id); // Add the user 2 and 3 to only one group. groups_add_member($group1->id, $user2->id); groups_add_member($group3->id, $user3->id); // Add a few discussions. $record = array(); $record['course'] = $course->id; $record['forum'] = $forum->id; $record['userid'] = $user1->id; $record['groupid'] = $group1->id; $discussiong1u1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); $record['groupid'] = $group2->id; $discussiong2u1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); $record['userid'] = $user2->id; $record['groupid'] = $group1->id; $discussiong1u2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); $record['userid'] = $user3->id; $record['groupid'] = $group3->id; $discussiong3u3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); self::setUser($user1); // Test retrieve discussions not passing the groupid parameter. We will receive only first group discussions. $discussions = forum_get_discussions($cm); self::assertCount(2, $discussions); foreach ($discussions as $discussion) { self::assertEquals($group1->id, $discussion->groupid); } // Get all my discussions. $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, 0); self::assertCount(3, $discussions); // Get all my g1 discussions. $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, $group1->id); self::assertCount(2, $discussions); foreach ($discussions as $discussion) { self::assertEquals($group1->id, $discussion->groupid); } // Get all my g2 discussions. $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, $group2->id); self::assertCount(1, $discussions); $discussion = array_shift($discussions); self::assertEquals($group2->id, $discussion->groupid); self::assertEquals($user1->id, $discussion->userid); self::assertEquals($discussiong2u1->id, $discussion->discussion); // Get all my g3 discussions (I'm not enrolled in that group). $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, $group3->id); self::assertCount(0, $discussions); // This group does not exist. $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, $group3->id + 1000); self::assertCount(0, $discussions); self::setUser($user2); // Test retrieve discussions not passing the groupid parameter. We will receive only first group discussions. $discussions = forum_get_discussions($cm); self::assertCount(2, $discussions); foreach ($discussions as $discussion) { self::assertEquals($group1->id, $discussion->groupid); } // Get all my viewable discussions. $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, 0); self::assertCount(2, $discussions); foreach ($discussions as $discussion) { self::assertEquals($group1->id, $discussion->groupid); } // Get all my g2 discussions (I'm not enrolled in that group). $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, $group2->id); self::assertCount(0, $discussions); // Get all my g3 discussions (I'm not enrolled in that group). $discussions = forum_get_discussions($cm, '', true, -1, -1, false, -1, 0, $group3->id); self::assertCount(0, $discussions); } /** * Test forum_user_can_post_discussion */ public function test_forum_user_can_post_discussion() { global $DB; $this->resetAfterTest(true); // Create course to add the module. $course = self::getDataGenerator()->create_course(array('groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1)); $user = self::getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user->id, $course->id); // Forum forcing separate gropus. $record = new \stdClass(); $record->course = $course->id; $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS)); $cm = get_coursemodule_from_instance('forum', $forum->id); $context = \context_module::instance($cm->id); self::setUser($user); // The user is not enroled in any group, try to post in a forum with separate groups. $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context); $this->assertFalse($can); // Create a group. $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); // Try to post in a group the user is not enrolled. $can = forum_user_can_post_discussion($forum, $group->id, -1, $cm, $context); $this->assertFalse($can); // Add the user to a group. groups_add_member($group->id, $user->id); // Try to post in a group the user is not enrolled. $can = forum_user_can_post_discussion($forum, $group->id + 1, -1, $cm, $context); $this->assertFalse($can); // Now try to post in the user group. (null means it will guess the group). $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context); $this->assertTrue($can); $can = forum_user_can_post_discussion($forum, $group->id, -1, $cm, $context); $this->assertTrue($can); // Test all groups. $can = forum_user_can_post_discussion($forum, -1, -1, $cm, $context); $this->assertFalse($can); $this->setAdminUser(); $can = forum_user_can_post_discussion($forum, -1, -1, $cm, $context); $this->assertTrue($can); // Change forum type. $forum->type = 'news'; $DB->update_record('forum', $forum); // Admin can post news. $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context); $this->assertTrue($can); // Normal users don't. self::setUser($user); $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context); $this->assertFalse($can); // Change forum type. $forum->type = 'eachuser'; $DB->update_record('forum', $forum); // I didn't post yet, so I should be able to post. $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context); $this->assertTrue($can); // Post now. $record = new \stdClass(); $record->course = $course->id; $record->userid = $user->id; $record->forum = $forum->id; $record->groupid = $group->id; $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); // I already posted, I shouldn't be able to post. $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context); $this->assertFalse($can); // Last check with no groups, normal forum and course. $course->groupmode = NOGROUPS; $course->groupmodeforce = 0; $DB->update_record('course', $course); $forum->type = 'general'; $forum->groupmode = NOGROUPS; $DB->update_record('forum', $forum); $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context); $this->assertTrue($can); } /** * Test forum_user_can_post_discussion_after_cutoff */ public function test_forum_user_can_post_discussion_after_cutoff() { $this->resetAfterTest(true); // Create course to add the module. $course = self::getDataGenerator()->create_course(array('groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1)); $student = self::getDataGenerator()->create_user(); $teacher = self::getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($student->id, $course->id); $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher'); // Forum forcing separate gropus. $record = new \stdClass(); $record->course = $course->id; $record->cutoffdate = time() - 1; $forum = self::getDataGenerator()->create_module('forum', $record); $cm = get_coursemodule_from_instance('forum', $forum->id); $context = \context_module::instance($cm->id); self::setUser($student); // Students usually don't have the mod/forum:canoverridecutoff capability. $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context); $this->assertFalse($can); self::setUser($teacher); // Teachers usually have the mod/forum:canoverridecutoff capability. $can = forum_user_can_post_discussion($forum, null, -1, $cm, $context); $this->assertTrue($can); } /** * Test forum_user_has_posted_discussion with no groups. */ public function test_forum_user_has_posted_discussion_no_groups() { global $CFG; $this->resetAfterTest(true); $course = self::getDataGenerator()->create_course(); $author = self::getDataGenerator()->create_user(); $other = self::getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($author->id, $course->id); $forum = self::getDataGenerator()->create_module('forum', (object) ['course' => $course->id ]); self::setUser($author); // Neither user has posted. $this->assertFalse(forum_user_has_posted_discussion($forum->id, $author->id)); $this->assertFalse(forum_user_has_posted_discussion($forum->id, $other->id)); // Post in the forum. $record = new \stdClass(); $record->course = $course->id; $record->userid = $author->id; $record->forum = $forum->id; $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); // The author has now posted, but the other user has not. $this->assertTrue(forum_user_has_posted_discussion($forum->id, $author->id)); $this->assertFalse(forum_user_has_posted_discussion($forum->id, $other->id)); } /** * Test forum_user_has_posted_discussion with multiple forums */ public function test_forum_user_has_posted_discussion_multiple_forums() { global $CFG; $this->resetAfterTest(true); $course = self::getDataGenerator()->create_course(); $author = self::getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($author->id, $course->id); $forum1 = self::getDataGenerator()->create_module('forum', (object) ['course' => $course->id ]); $forum2 = self::getDataGenerator()->create_module('forum', (object) ['course' => $course->id ]); self::setUser($author); // No post in either forum. $this->assertFalse(forum_user_has_posted_discussion($forum1->id, $author->id)); $this->assertFalse(forum_user_has_posted_discussion($forum2->id, $author->id)); // Post in the forum. $record = new \stdClass(); $record->course = $course->id; $record->userid = $author->id; $record->forum = $forum1->id; $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); // The author has now posted in forum1, but not forum2. $this->assertTrue(forum_user_has_posted_discussion($forum1->id, $author->id)); $this->assertFalse(forum_user_has_posted_discussion($forum2->id, $author->id)); } /** * Test forum_user_has_posted_discussion with multiple groups. */ public function test_forum_user_has_posted_discussion_multiple_groups() { global $CFG; $this->resetAfterTest(true); $course = self::getDataGenerator()->create_course(); $author = self::getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($author->id, $course->id); $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); groups_add_member($group1->id, $author->id); groups_add_member($group2->id, $author->id); $forum = self::getDataGenerator()->create_module('forum', (object) ['course' => $course->id ], [ 'groupmode' => SEPARATEGROUPS, ]); self::setUser($author); // The user has not posted in either group. $this->assertFalse(forum_user_has_posted_discussion($forum->id, $author->id)); $this->assertFalse(forum_user_has_posted_discussion($forum->id, $author->id, $group1->id)); $this->assertFalse(forum_user_has_posted_discussion($forum->id, $author->id, $group2->id)); // Post in one group. $record = new \stdClass(); $record->course = $course->id; $record->userid = $author->id; $record->forum = $forum->id; $record->groupid = $group1->id; $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); // The author has now posted in one group, but the other user has not. $this->assertTrue(forum_user_has_posted_discussion($forum->id, $author->id)); $this->assertTrue(forum_user_has_posted_discussion($forum->id, $author->id, $group1->id)); $this->assertFalse(forum_user_has_posted_discussion($forum->id, $author->id, $group2->id)); // Post in the other group. $record = new \stdClass(); $record->course = $course->id; $record->userid = $author->id; $record->forum = $forum->id; $record->groupid = $group2->id; $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); // The author has now posted in one group, but the other user has not. $this->assertTrue(forum_user_has_posted_discussion($forum->id, $author->id)); $this->assertTrue(forum_user_has_posted_discussion($forum->id, $author->id, $group1->id)); $this->assertTrue(forum_user_has_posted_discussion($forum->id, $author->id, $group2->id)); } /** * Tests the mod_forum_myprofile_navigation() function. */ public function test_mod_forum_myprofile_navigation() { $this->resetAfterTest(true); // Set up the test. $tree = new \core_user\output\myprofile\tree(); $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $iscurrentuser = true; // Set as the current user. $this->setUser($user); // Check the node tree is correct. mod_forum_myprofile_navigation($tree, $user, $iscurrentuser, $course); $reflector = new \ReflectionObject($tree); $nodes = $reflector->getProperty('nodes'); $nodes->setAccessible(true); $this->assertArrayHasKey('forumposts', $nodes->getValue($tree)); $this->assertArrayHasKey('forumdiscussions', $nodes->getValue($tree)); } /** * Tests the mod_forum_myprofile_navigation() function as a guest. */ public function test_mod_forum_myprofile_navigation_as_guest() { global $USER; $this->resetAfterTest(true); // Set up the test. $tree = new \core_user\output\myprofile\tree(); $course = $this->getDataGenerator()->create_course(); $iscurrentuser = true; // Set user as guest. $this->setGuestUser(); // Check the node tree is correct. mod_forum_myprofile_navigation($tree, $USER, $iscurrentuser, $course); $reflector = new \ReflectionObject($tree); $nodes = $reflector->getProperty('nodes'); $nodes->setAccessible(true); $this->assertArrayNotHasKey('forumposts', $nodes->getValue($tree)); $this->assertArrayNotHasKey('forumdiscussions', $nodes->getValue($tree)); } /** * Tests the mod_forum_myprofile_navigation() function as a user viewing another user's profile. */ public function test_mod_forum_myprofile_navigation_different_user() { $this->resetAfterTest(true); // Set up the test. $tree = new \core_user\output\myprofile\tree(); $user = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $iscurrentuser = true; // Set to different user's profile. $this->setUser($user2); // Check the node tree is correct. mod_forum_myprofile_navigation($tree, $user, $iscurrentuser, $course); $reflector = new \ReflectionObject($tree); $nodes = $reflector->getProperty('nodes'); $nodes->setAccessible(true); $this->assertArrayHasKey('forumposts', $nodes->getValue($tree)); $this->assertArrayHasKey('forumdiscussions', $nodes->getValue($tree)); } /** * Test test_pinned_discussion_with_group. */ public function test_pinned_discussion_with_group() { global $SESSION; $this->resetAfterTest(); $course1 = $this->getDataGenerator()->create_course(); $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course1->id)); // Create an author user. $author = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($author->id, $course1->id); // Create two viewer users - one in a group, one not. $viewer1 = $this->getDataGenerator()->create_user((object) array('trackforums' => 1)); $this->getDataGenerator()->enrol_user($viewer1->id, $course1->id); $viewer2 = $this->getDataGenerator()->create_user((object) array('trackforums' => 1)); $this->getDataGenerator()->enrol_user($viewer2->id, $course1->id); $this->getDataGenerator()->create_group_member(array('userid' => $viewer2->id, 'groupid' => $group1->id)); $forum1 = $this->getDataGenerator()->create_module('forum', (object) array( 'course' => $course1->id, 'groupmode' => SEPARATEGROUPS, )); $coursemodule = get_coursemodule_from_instance('forum', $forum1->id); $alldiscussions = array(); $group1discussions = array(); // Create 4 discussions in all participants group and group1, where the first // discussion is pinned in each group. $allrecord = new \stdClass(); $allrecord->course = $course1->id; $allrecord->userid = $author->id; $allrecord->forum = $forum1->id; $allrecord->pinned = FORUM_DISCUSSION_PINNED; $group1record = new \stdClass(); $group1record->course = $course1->id; $group1record->userid = $author->id; $group1record->forum = $forum1->id; $group1record->groupid = $group1->id; $group1record->pinned = FORUM_DISCUSSION_PINNED; $alldiscussions[] = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($allrecord); $group1discussions[] = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($group1record); // Create unpinned discussions. $allrecord->pinned = FORUM_DISCUSSION_UNPINNED; $group1record->pinned = FORUM_DISCUSSION_UNPINNED; for ($i = 0; $i < 3; $i++) { $alldiscussions[] = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($allrecord); $group1discussions[] = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($group1record); } // As viewer1 (no group). This user shouldn't see any of group1's discussions // so their expected discussion order is (where rightmost is highest priority): // Ad1, ad2, ad3, ad0. $this->setUser($viewer1->id); // CHECK 1. // Take the neighbours of ad3, which should be prev: ad2 and next: ad0. $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[3], $forum1); // Ad2 check. $this->assertEquals($alldiscussions[2]->id, $neighbours['prev']->id); // Ad0 check. $this->assertEquals($alldiscussions[0]->id, $neighbours['next']->id); // CHECK 2. // Take the neighbours of ad0, which should be prev: ad3 and next: null. $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[0], $forum1); // Ad3 check. $this->assertEquals($alldiscussions[3]->id, $neighbours['prev']->id); // Null check. $this->assertEmpty($neighbours['next']); // CHECK 3. // Take the neighbours of ad1, which should be prev: null and next: ad2. $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[1], $forum1); // Null check. $this->assertEmpty($neighbours['prev']); // Ad2 check. $this->assertEquals($alldiscussions[2]->id, $neighbours['next']->id); // Temporary hack to workaround for MDL-52656. $SESSION->currentgroup = null; // As viewer2 (group1). This user should see all of group1's posts and the all participants group. // The expected discussion order is (rightmost is highest priority): // Ad1, gd1, ad2, gd2, ad3, gd3, ad0, gd0. $this->setUser($viewer2->id); // CHECK 1. // Take the neighbours of ad1, which should be prev: null and next: gd1. $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[1], $forum1); // Null check. $this->assertEmpty($neighbours['prev']); // Gd1 check. $this->assertEquals($group1discussions[1]->id, $neighbours['next']->id); // CHECK 2. // Take the neighbours of ad3, which should be prev: gd2 and next: gd3. $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[3], $forum1); // Gd2 check. $this->assertEquals($group1discussions[2]->id, $neighbours['prev']->id); // Gd3 check. $this->assertEquals($group1discussions[3]->id, $neighbours['next']->id); // CHECK 3. // Take the neighbours of gd3, which should be prev: ad3 and next: ad0. $neighbours = forum_get_discussion_neighbours($coursemodule, $group1discussions[3], $forum1); // Ad3 check. $this->assertEquals($alldiscussions[3]->id, $neighbours['prev']->id); // Ad0 check. $this->assertEquals($alldiscussions[0]->id, $neighbours['next']->id); // CHECK 4. // Take the neighbours of gd0, which should be prev: ad0 and next: null. $neighbours = forum_get_discussion_neighbours($coursemodule, $group1discussions[0], $forum1); // Ad0 check. $this->assertEquals($alldiscussions[0]->id, $neighbours['prev']->id); // Null check. $this->assertEmpty($neighbours['next']); } /** * Test test_pinned_with_timed_discussions. */ public function test_pinned_with_timed_discussions() { global $CFG; $CFG->forum_enabletimedposts = true; $this->resetAfterTest(); $course = $this->getDataGenerator()->create_course(); // Create an user. $user = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user->id, $course->id); // Create a forum. $record = new \stdClass(); $record->course = $course->id; $forum = $this->getDataGenerator()->create_module('forum', (object) array( 'course' => $course->id, 'groupmode' => SEPARATEGROUPS, )); $coursemodule = get_coursemodule_from_instance('forum', $forum->id); $now = time(); $discussions = array(); $discussiongenerator = $this->getDataGenerator()->get_plugin_generator('mod_forum'); $record = new \stdClass(); $record->course = $course->id; $record->userid = $user->id; $record->forum = $forum->id; $record->pinned = FORUM_DISCUSSION_PINNED; $record->timemodified = $now; $discussions[] = $discussiongenerator->create_discussion($record); $record->pinned = FORUM_DISCUSSION_UNPINNED; $record->timestart = $now + 10; $discussions[] = $discussiongenerator->create_discussion($record); $record->timestart = $now; $discussions[] = $discussiongenerator->create_discussion($record); // Expected order of discussions: // D2, d1, d0. $this->setUser($user->id); // CHECK 1. $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[2], $forum); // Null check. $this->assertEmpty($neighbours['prev']); // D1 check. $this->assertEquals($discussions[1]->id, $neighbours['next']->id); // CHECK 2. $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[1], $forum); // D2 check. $this->assertEquals($discussions[2]->id, $neighbours['prev']->id); // D0 check. $this->assertEquals($discussions[0]->id, $neighbours['next']->id); // CHECK 3. $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[0], $forum); // D2 check. $this->assertEquals($discussions[1]->id, $neighbours['prev']->id); // Null check. $this->assertEmpty($neighbours['next']); } /** * Test test_pinned_timed_discussions_with_timed_discussions. */ public function test_pinned_timed_discussions_with_timed_discussions() { global $CFG; $CFG->forum_enabletimedposts = true; $this->resetAfterTest(); $course = $this->getDataGenerator()->create_course(); // Create an user. $user = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user->id, $course->id); // Create a forum. $record = new \stdClass(); $record->course = $course->id; $forum = $this->getDataGenerator()->create_module('forum', (object) array( 'course' => $course->id, 'groupmode' => SEPARATEGROUPS, )); $coursemodule = get_coursemodule_from_instance('forum', $forum->id); $now = time(); $discussions = array(); $discussiongenerator = $this->getDataGenerator()->get_plugin_generator('mod_forum'); $record = new \stdClass(); $record->course = $course->id; $record->userid = $user->id; $record->forum = $forum->id; $record->pinned = FORUM_DISCUSSION_PINNED; $record->timemodified = $now; $record->timestart = $now + 10; $discussions[] = $discussiongenerator->create_discussion($record); $record->pinned = FORUM_DISCUSSION_UNPINNED; $discussions[] = $discussiongenerator->create_discussion($record); $record->timestart = $now; $discussions[] = $discussiongenerator->create_discussion($record); $record->pinned = FORUM_DISCUSSION_PINNED; $discussions[] = $discussiongenerator->create_discussion($record); // Expected order of discussions: // D2, d1, d3, d0. $this->setUser($user->id); // CHECK 1. $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[2], $forum); // Null check. $this->assertEmpty($neighbours['prev']); // D1 check. $this->assertEquals($discussions[1]->id, $neighbours['next']->id); // CHECK 2. $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[1], $forum); // D2 check. $this->assertEquals($discussions[2]->id, $neighbours['prev']->id); // D3 check. $this->assertEquals($discussions[3]->id, $neighbours['next']->id); // CHECK 3. $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[3], $forum); // D1 check. $this->assertEquals($discussions[1]->id, $neighbours['prev']->id); // D0 check. $this->assertEquals($discussions[0]->id, $neighbours['next']->id); // CHECK 4. $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[0], $forum); // D3 check. $this->assertEquals($discussions[3]->id, $neighbours['prev']->id); // Null check. $this->assertEmpty($neighbours['next']); } /** * Test for forum_is_author_hidden. */ public function test_forum_is_author_hidden() { // First post, different forum type. $post = (object) ['parent' => 0]; $forum = (object) ['type' => 'standard']; $this->assertFalse(forum_is_author_hidden($post, $forum)); // Child post, different forum type. $post->parent = 1; $this->assertFalse(forum_is_author_hidden($post, $forum)); // First post, single simple discussion forum type. $post->parent = 0; $forum->type = 'single'; $this->assertTrue(forum_is_author_hidden($post, $forum)); // Child post, single simple discussion forum type. $post->parent = 1; $this->assertFalse(forum_is_author_hidden($post, $forum)); // Incorrect parameters: $post. $this->expectException('coding_exception'); $this->expectExceptionMessage('$post->parent must be set.'); unset($post->parent); forum_is_author_hidden($post, $forum); // Incorrect parameters: $forum. $this->expectException('coding_exception'); $this->expectExceptionMessage('$forum->type must be set.'); unset($forum->type); forum_is_author_hidden($post, $forum); } /** * Test the forum_discussion_is_locked function. * * @dataProvider forum_discussion_is_locked_provider * @param \stdClass $forum * @param \stdClass $discussion * @param bool $expect */ public function test_forum_discussion_is_locked($forum, $discussion, $expect) { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $plugingenerator = $datagenerator->get_plugin_generator('mod_forum'); $course = $datagenerator->create_course(); $user = $datagenerator->create_user(); $forum = $datagenerator->create_module('forum', (object) array_merge([ 'course' => $course->id ], $forum)); $discussion = $plugingenerator->create_discussion((object) array_merge([ 'course' => $course->id, 'userid' => $user->id, 'forum' => $forum->id, ], $discussion)); $this->assertEquals($expect, forum_discussion_is_locked($forum, $discussion)); } /** * Dataprovider for forum_discussion_is_locked tests. * * @return array */ public function forum_discussion_is_locked_provider() { return [ 'Unlocked: lockdiscussionafter is false' => [ ['lockdiscussionafter' => false], [], false ], 'Unlocked: lockdiscussionafter is set; forum is of type single; post is recent' => [ ['lockdiscussionafter' => DAYSECS, 'type' => 'single'], ['timemodified' => time()], false ], 'Unlocked: lockdiscussionafter is set; forum is of type single; post is old' => [ ['lockdiscussionafter' => MINSECS, 'type' => 'single'], ['timemodified' => time() - DAYSECS], false ], 'Unlocked: lockdiscussionafter is set; forum is of type eachuser; post is recent' => [ ['lockdiscussionafter' => DAYSECS, 'type' => 'eachuser'], ['timemodified' => time()], false ], 'Locked: lockdiscussionafter is set; forum is of type eachuser; post is old' => [ ['lockdiscussionafter' => MINSECS, 'type' => 'eachuser'], ['timemodified' => time() - DAYSECS], true ], ]; } /** * Test the forum_is_cutoff_date_reached function. * * @dataProvider forum_is_cutoff_date_reached_provider * @param array $forum * @param bool $expect */ public function test_forum_is_cutoff_date_reached($forum, $expect) { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $course = $datagenerator->create_course(); $forum = $datagenerator->create_module('forum', (object) array_merge([ 'course' => $course->id ], $forum)); $this->assertEquals($expect, forum_is_cutoff_date_reached($forum)); } /** * Dataprovider for forum_is_cutoff_date_reached tests. * * @return array */ public function forum_is_cutoff_date_reached_provider() { $now = time(); return [ 'cutoffdate is unset' => [ [], false ], 'cutoffdate is 0' => [ ['cutoffdate' => 0], false ], 'cutoffdate is set and is in future' => [ ['cutoffdate' => $now + 86400], false ], 'cutoffdate is set and is in past' => [ ['cutoffdate' => $now - 86400], true ], ]; } /** * Test the forum_is_due_date_reached function. * * @dataProvider forum_is_due_date_reached_provider * @param \stdClass $forum * @param bool $expect */ public function test_forum_is_due_date_reached($forum, $expect) { $this->resetAfterTest(); $this->setAdminUser(); $datagenerator = $this->getDataGenerator(); $course = $datagenerator->create_course(); $forum = $datagenerator->create_module('forum', (object) array_merge([ 'course' => $course->id ], $forum)); $this->assertEquals($expect, forum_is_due_date_reached($forum)); } /** * Dataprovider for forum_is_due_date_reached tests. * * @return array */ public function forum_is_due_date_reached_provider() { $now = time(); return [ 'duedate is unset' => [ [], false ], 'duedate is 0' => [ ['duedate' => 0], false ], 'duedate is set and is in future' => [ ['duedate' => $now + 86400], false ], 'duedate is set and is in past' => [ ['duedate' => $now - 86400], true ], ]; } /** * Test that {@link forum_update_post()} keeps correct forum_discussions usermodified. */ public function test_forum_update_post_keeps_discussions_usermodified() { global $DB; $this->resetAfterTest(); // Let there be light. $teacher = self::getDataGenerator()->create_user(); $student = self::getDataGenerator()->create_user(); $course = self::getDataGenerator()->create_course(); $forum = self::getDataGenerator()->create_module('forum', (object)[ 'course' => $course->id, ]); $generator = self::getDataGenerator()->get_plugin_generator('mod_forum'); // Let the teacher start a discussion. $discussion = $generator->create_discussion((object)[ 'course' => $course->id, 'userid' => $teacher->id, 'forum' => $forum->id, ]); // On this freshly created discussion, the teacher is the author of the last post. $this->assertEquals($teacher->id, $DB->get_field('forum_discussions', 'usermodified', ['id' => $discussion->id])); // Fetch modified timestamp of the discussion. $discussionmodified = $DB->get_field('forum_discussions', 'timemodified', ['id' => $discussion->id]); $pasttime = $discussionmodified - 3600; // Adjust the discussion modified timestamp back an hour, so it's in the past. $adjustment = (object)[ 'id' => $discussion->id, 'timemodified' => $pasttime, ]; $DB->update_record('forum_discussions', $adjustment); // Let the student reply to the teacher's post. $reply = $generator->create_post((object)[ 'course' => $course->id, 'userid' => $student->id, 'forum' => $forum->id, 'discussion' => $discussion->id, 'parent' => $discussion->firstpost, ]); // The student should now be the last post's author. $this->assertEquals($student->id, $DB->get_field('forum_discussions', 'usermodified', ['id' => $discussion->id])); // Fetch modified timestamp of the discussion and student's post. $discussionmodified = $DB->get_field('forum_discussions', 'timemodified', ['id' => $discussion->id]); $postmodified = $DB->get_field('forum_posts', 'modified', ['id' => $reply->id]); // Discussion modified time should be updated to be equal to the newly created post's time. $this->assertEquals($discussionmodified, $postmodified); // Adjust the discussion and post timestamps, so they are in the past. $adjustment = (object)[ 'id' => $discussion->id, 'timemodified' => $pasttime, ]; $DB->update_record('forum_discussions', $adjustment); $adjustment = (object)[ 'id' => $reply->id, 'modified' => $pasttime, ]; $DB->update_record('forum_posts', $adjustment); // The discussion and student's post time should now be an hour in the past. $this->assertEquals($pasttime, $DB->get_field('forum_discussions', 'timemodified', ['id' => $discussion->id])); $this->assertEquals($pasttime, $DB->get_field('forum_posts', 'modified', ['id' => $reply->id])); // Let the teacher edit the student's reply. $this->setUser($teacher->id); $newpost = (object)[ 'id' => $reply->id, 'itemid' => 0, 'subject' => 'Amended subject', ]; forum_update_post($newpost, null); // The student should still be the last post's author. $this->assertEquals($student->id, $DB->get_field('forum_discussions', 'usermodified', ['id' => $discussion->id])); // The discussion modified time should not have changed. $this->assertEquals($pasttime, $DB->get_field('forum_discussions', 'timemodified', ['id' => $discussion->id])); // The post time should be updated. $this->assertGreaterThan($pasttime, $DB->get_field('forum_posts', 'modified', ['id' => $reply->id])); } public function test_forum_core_calendar_provide_event_action() { $this->resetAfterTest(); $this->setAdminUser(); // Create the activity. $course = $this->getDataGenerator()->create_course(); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'completionreplies' => 5, 'completiondiscussions' => 2)); // Create a calendar event. $event = $this->create_action_event($course->id, $forum->id, \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED); // Create an action factory. $factory = new \core_calendar\action_factory(); // Decorate action event. $actionevent = mod_forum_core_calendar_provide_event_action($event, $factory); // Confirm the event was decorated. $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); $this->assertEquals(get_string('view'), $actionevent->get_name()); $this->assertInstanceOf('moodle_url', $actionevent->get_url()); $this->assertEquals(7, $actionevent->get_item_count()); $this->assertTrue($actionevent->is_actionable()); } public function test_forum_core_calendar_provide_event_action_in_hidden_section() { global $CFG; $this->resetAfterTest(); $this->setAdminUser(); // Create a course. $course = $this->getDataGenerator()->create_course(); // Create a student. $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); // Create the activity. $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'completionreplies' => 5, 'completiondiscussions' => 2)); // Create a calendar event. $event = $this->create_action_event($course->id, $forum->id, \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED); // Set sections 0 as hidden. set_section_visible($course->id, 0, 0); // Now, log out. $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities. $this->setUser(); // Create an action factory. $factory = new \core_calendar\action_factory(); // Decorate action event for the student. $actionevent = mod_forum_core_calendar_provide_event_action($event, $factory, $student->id); // Confirm the event is not shown at all. $this->assertNull($actionevent); } public function test_forum_core_calendar_provide_event_action_for_user() { global $CFG; $this->resetAfterTest(); $this->setAdminUser(); // Create a course. $course = $this->getDataGenerator()->create_course(); // Create a student. $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); // Create the activity. $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id, 'completionreplies' => 5, 'completiondiscussions' => 2)); // Create a calendar event. $event = $this->create_action_event($course->id, $forum->id, \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED); // Now log out. $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities. $this->setUser(); // Create an action factory. $factory = new \core_calendar\action_factory(); // Decorate action event for the student. $actionevent = mod_forum_core_calendar_provide_event_action($event, $factory, $student->id); // Confirm the event was decorated. $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); $this->assertEquals(get_string('view'), $actionevent->get_name()); $this->assertInstanceOf('moodle_url', $actionevent->get_url()); $this->assertEquals(7, $actionevent->get_item_count()); $this->assertTrue($actionevent->is_actionable()); } public function test_forum_core_calendar_provide_event_action_as_non_user() { global $CFG; $this->resetAfterTest(); $this->setAdminUser(); // Create the activity. $course = $this->getDataGenerator()->create_course(); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); // Create a calendar event. $event = $this->create_action_event($course->id, $forum->id, \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED); // Log out the user and set force login to true. \core\session\manager::init_empty_session(); $CFG->forcelogin = true; // Create an action factory. $factory = new \core_calendar\action_factory(); // Decorate action event. $actionevent = mod_forum_core_calendar_provide_event_action($event, $factory); // Ensure result was null. $this->assertNull($actionevent); } public function test_forum_core_calendar_provide_event_action_already_completed() { global $CFG; $this->resetAfterTest(); $this->setAdminUser(); $CFG->enablecompletion = 1; // Create the activity. $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS)); // Get some additional data. $cm = get_coursemodule_from_instance('forum', $forum->id); // Create a calendar event. $event = $this->create_action_event($course->id, $forum->id, \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED); // Mark the activity as completed. $completion = new \completion_info($course); $completion->set_module_viewed($cm); // Create an action factory. $factory = new \core_calendar\action_factory(); // Decorate action event. $actionevent = mod_forum_core_calendar_provide_event_action($event, $factory); // Ensure result was null. $this->assertNull($actionevent); } public function test_forum_core_calendar_provide_event_action_already_completed_for_user() { global $CFG; $this->resetAfterTest(); $this->setAdminUser(); $CFG->enablecompletion = 1; // Create a course. $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); // Create a student. $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); // Create the activity. $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS)); // Get some additional data. $cm = get_coursemodule_from_instance('forum', $forum->id); // Create a calendar event. $event = $this->create_action_event($course->id, $forum->id, \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED); // Mark the activity as completed for the student. $completion = new \completion_info($course); $completion->set_module_viewed($cm, $student->id); // Create an action factory. $factory = new \core_calendar\action_factory(); // Decorate action event. $actionevent = mod_forum_core_calendar_provide_event_action($event, $factory, $student->id); // Ensure result was null. $this->assertNull($actionevent); } public function test_mod_forum_get_tagged_posts() { global $DB; $this->resetAfterTest(); $this->setAdminUser(); // Setup test data. $forumgenerator = $this->getDataGenerator()->get_plugin_generator('mod_forum'); $course3 = $this->getDataGenerator()->create_course(); $course2 = $this->getDataGenerator()->create_course(); $course1 = $this->getDataGenerator()->create_course(); $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id)); $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id)); $forum3 = $this->getDataGenerator()->create_module('forum', array('course' => $course3->id)); $post11 = $forumgenerator->create_content($forum1, array('tags' => array('Cats', 'Dogs'))); $post12 = $forumgenerator->create_content($forum1, array('tags' => array('Cats', 'mice'))); $post13 = $forumgenerator->create_content($forum1, array('tags' => array('Cats'))); $post14 = $forumgenerator->create_content($forum1); $post15 = $forumgenerator->create_content($forum1, array('tags' => array('Cats'))); $post16 = $forumgenerator->create_content($forum1, array('tags' => array('Cats'), 'hidden' => true)); $post21 = $forumgenerator->create_content($forum2, array('tags' => array('Cats'))); $post22 = $forumgenerator->create_content($forum2, array('tags' => array('Cats', 'Dogs'))); $post23 = $forumgenerator->create_content($forum2, array('tags' => array('mice', 'Cats'))); $post31 = $forumgenerator->create_content($forum3, array('tags' => array('mice', 'Cats'))); $tag = \core_tag_tag::get_by_name(0, 'Cats'); // Admin can see everything. $res = mod_forum_get_tagged_posts($tag, /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$post = */0); $this->assertMatchesRegularExpression('/'.$post11->subject.'</', $res->content); $this->assertMatchesRegularExpression('/'.$post12->subject.'</', $res->content); $this->assertMatchesRegularExpression('/'.$post13->subject.'</', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post14->subject.'</', $res->content); $this->assertMatchesRegularExpression('/'.$post15->subject.'</', $res->content); $this->assertMatchesRegularExpression('/'.$post16->subject.'</', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post21->subject.'</', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post22->subject.'</', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post23->subject.'</', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post31->subject.'</', $res->content); $this->assertEmpty($res->prevpageurl); $this->assertNotEmpty($res->nextpageurl); $res = mod_forum_get_tagged_posts($tag, /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$post = */1); $this->assertDoesNotMatchRegularExpression('/'.$post11->subject.'</', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post12->subject.'</', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post13->subject.'</', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post14->subject.'</', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post15->subject.'</', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post16->subject.'</', $res->content); $this->assertMatchesRegularExpression('/'.$post21->subject.'</', $res->content); $this->assertMatchesRegularExpression('/'.$post22->subject.'</', $res->content); $this->assertMatchesRegularExpression('/'.$post23->subject.'</', $res->content); $this->assertMatchesRegularExpression('/'.$post31->subject.'</', $res->content); $this->assertNotEmpty($res->prevpageurl); $this->assertEmpty($res->nextpageurl); // Create and enrol a user. $student = self::getDataGenerator()->create_user(); $studentrole = $DB->get_record('role', array('shortname' => 'student')); $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual'); $this->getDataGenerator()->enrol_user($student->id, $course2->id, $studentrole->id, 'manual'); $this->setUser($student); \core_tag_index_builder::reset_caches(); // User can not see posts in course 3 because he is not enrolled. $res = mod_forum_get_tagged_posts($tag, /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$post = */1); $this->assertMatchesRegularExpression('/'.$post22->subject.'/', $res->content); $this->assertMatchesRegularExpression('/'.$post23->subject.'/', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post31->subject.'/', $res->content); // User can search forum posts inside a course. $coursecontext = \context_course::instance($course1->id); $res = mod_forum_get_tagged_posts($tag, /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$coursecontext->id, /*$rec = */1, /*$post = */0); $this->assertMatchesRegularExpression('/'.$post11->subject.'/', $res->content); $this->assertMatchesRegularExpression('/'.$post12->subject.'/', $res->content); $this->assertMatchesRegularExpression('/'.$post13->subject.'/', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post14->subject.'/', $res->content); $this->assertMatchesRegularExpression('/'.$post15->subject.'/', $res->content); $this->assertMatchesRegularExpression('/'.$post16->subject.'/', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post21->subject.'/', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post22->subject.'/', $res->content); $this->assertDoesNotMatchRegularExpression('/'.$post23->subject.'/', $res->content); $this->assertEmpty($res->nextpageurl); } /** * Creates an action event. * * @param int $courseid The course id. * @param int $instanceid The instance id. * @param string $eventtype The event type. * @return bool|calendar_event */ private function create_action_event($courseid, $instanceid, $eventtype) { $event = new \stdClass(); $event->name = 'Calendar event'; $event->modulename = 'forum'; $event->courseid = $courseid; $event->instance = $instanceid; $event->type = CALENDAR_EVENT_TYPE_ACTION; $event->eventtype = $eventtype; $event->timestart = time(); return \calendar_event::create($event); } /** * Test the callback responsible for returning the completion rule descriptions. * This function should work given either an instance of the module (cm_info), such as when checking the active rules, * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type. */ public function test_mod_forum_completion_get_active_rule_descriptions() { $this->resetAfterTest(); $this->setAdminUser(); // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't. $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]); $forum1 = $this->getDataGenerator()->create_module('forum', [ 'course' => $course->id, 'completion' => 2, 'completiondiscussions' => 3, 'completionreplies' => 3, 'completionposts' => 3 ]); $forum2 = $this->getDataGenerator()->create_module('forum', [ 'course' => $course->id, 'completion' => 2, 'completiondiscussions' => 0, 'completionreplies' => 0, 'completionposts' => 0 ]); $cm1 = \cm_info::create(get_coursemodule_from_instance('forum', $forum1->id)); $cm2 = \cm_info::create(get_coursemodule_from_instance('forum', $forum2->id)); // Data for the stdClass input type. // This type of input would occur when checking the default completion rules for an activity type, where we don't have // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info. $moddefaults = new \stdClass(); $moddefaults->customdata = ['customcompletionrules' => [ 'completiondiscussions' => 3, 'completionreplies' => 3, 'completionposts' => 3 ]]; $moddefaults->completion = 2; $activeruledescriptions = [ get_string('completiondiscussionsdesc', 'forum', 3), get_string('completionrepliesdesc', 'forum', 3), get_string('completionpostsdesc', 'forum', 3) ]; $this->assertEquals(mod_forum_get_completion_active_rule_descriptions($cm1), $activeruledescriptions); $this->assertEquals(mod_forum_get_completion_active_rule_descriptions($cm2), []); $this->assertEquals(mod_forum_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions); $this->assertEquals(mod_forum_get_completion_active_rule_descriptions(new \stdClass()), []); } /** * Test the forum_post_is_visible_privately function used in private replies. */ public function test_forum_post_is_visible_privately() { $this->resetAfterTest(); $course = $this->getDataGenerator()->create_course(); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); $context = \context_module::instance($forum->cmid); $cm = get_coursemodule_from_instance('forum', $forum->id); $author = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($author->id, $course->id); $recipient = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($recipient->id, $course->id); $privilegeduser = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($privilegeduser->id, $course->id, 'editingteacher'); $otheruser = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($otheruser->id, $course->id); // Fake a post - this does not need to be persisted to the DB. $post = new \stdClass(); $post->userid = $author->id; $post->privatereplyto = $recipient->id; // The user is the author. $this->setUser($author->id); $this->assertTrue(forum_post_is_visible_privately($post, $cm)); // The user is the intended recipient. $this->setUser($recipient->id); $this->assertTrue(forum_post_is_visible_privately($post, $cm)); // The user is not the author or recipient, but does have the readprivatereplies capability. $this->setUser($privilegeduser->id); $this->assertTrue(forum_post_is_visible_privately($post, $cm)); // The user is not allowed to view this post. $this->setUser($otheruser->id); $this->assertFalse(forum_post_is_visible_privately($post, $cm)); } /** * An unkown event type should not have any limits */ public function test_mod_forum_core_calendar_get_valid_event_timestart_range_unknown_event() { global $CFG; require_once($CFG->dirroot . "/calendar/lib.php"); $this->resetAfterTest(true); $this->setAdminUser(); $generator = $this->getDataGenerator(); $course = $generator->create_course(); $duedate = time() + DAYSECS; $forum = new \stdClass(); $forum->duedate = $duedate; // Create a valid event. $event = new \calendar_event([ 'name' => 'Test event', 'description' => '', 'format' => 1, 'courseid' => $course->id, 'groupid' => 0, 'userid' => 2, 'modulename' => 'forum', 'instance' => 1, 'eventtype' => FORUM_EVENT_TYPE_DUE . "SOMETHING ELSE", 'timestart' => 1, 'timeduration' => 86400, 'visible' => 1 ]); list ($min, $max) = mod_forum_core_calendar_get_valid_event_timestart_range($event, $forum); $this->assertNull($min); $this->assertNull($max); } /** * Forums configured without a cutoff date should not have any limits applied. */ public function test_mod_forum_core_calendar_get_valid_event_timestart_range_due_no_limit() { global $CFG; require_once($CFG->dirroot . '/calendar/lib.php'); $this->resetAfterTest(true); $this->setAdminUser(); $generator = $this->getDataGenerator(); $course = $generator->create_course(); $duedate = time() + DAYSECS; $forum = new \stdClass(); $forum->duedate = $duedate; // Create a valid event. $event = new \calendar_event([ 'name' => 'Test event', 'description' => '', 'format' => 1, 'courseid' => $course->id, 'groupid' => 0, 'userid' => 2, 'modulename' => 'forum', 'instance' => 1, 'eventtype' => FORUM_EVENT_TYPE_DUE, 'timestart' => 1, 'timeduration' => 86400, 'visible' => 1 ]); list($min, $max) = mod_forum_core_calendar_get_valid_event_timestart_range($event, $forum); $this->assertNull($min); $this->assertNull($max); } /** * Forums should be top bound by the cutoff date. */ public function test_mod_forum_core_calendar_get_valid_event_timestart_range_due_with_limits() { global $CFG; require_once($CFG->dirroot . '/calendar/lib.php'); $this->resetAfterTest(true); $this->setAdminUser(); $generator = $this->getDataGenerator(); $course = $generator->create_course(); $duedate = time() + DAYSECS; $cutoffdate = $duedate + DAYSECS; $forum = new \stdClass(); $forum->duedate = $duedate; $forum->cutoffdate = $cutoffdate; // Create a valid event. $event = new \calendar_event([ 'name' => 'Test event', 'description' => '', 'format' => 1, 'courseid' => $course->id, 'groupid' => 0, 'userid' => 2, 'modulename' => 'forum', 'instance' => 1, 'eventtype' => FORUM_EVENT_TYPE_DUE, 'timestart' => 1, 'timeduration' => 86400, 'visible' => 1 ]); list($min, $max) = mod_forum_core_calendar_get_valid_event_timestart_range($event, $forum); $this->assertNull($min); $this->assertEquals($cutoffdate, $max[0]); $this->assertNotEmpty($max[1]); } /** * An unknown event type should not change the forum instance. */ public function test_mod_forum_core_calendar_event_timestart_updated_unknown_event() { global $CFG, $DB; require_once($CFG->dirroot . "/calendar/lib.php"); $this->resetAfterTest(true); $this->setAdminUser(); $generator = $this->getDataGenerator(); $course = $generator->create_course(); $forumgenerator = $generator->get_plugin_generator('mod_forum'); $duedate = time() + DAYSECS; $cutoffdate = $duedate + DAYSECS; $forum = $forumgenerator->create_instance(['course' => $course->id]); $forum->duedate = $duedate; $forum->cutoffdate = $cutoffdate; $DB->update_record('forum', $forum); // Create a valid event. $event = new \calendar_event([ 'name' => 'Test event', 'description' => '', 'format' => 1, 'courseid' => $course->id, 'groupid' => 0, 'userid' => 2, 'modulename' => 'forum', 'instance' => $forum->id, 'eventtype' => FORUM_EVENT_TYPE_DUE . "SOMETHING ELSE", 'timestart' => 1, 'timeduration' => 86400, 'visible' => 1 ]); mod_forum_core_calendar_event_timestart_updated($event, $forum); $forum = $DB->get_record('forum', ['id' => $forum->id]); $this->assertEquals($duedate, $forum->duedate); $this->assertEquals($cutoffdate, $forum->cutoffdate); } /** * Due date events should update the forum due date. */ public function test_mod_forum_core_calendar_event_timestart_updated_due_event() { global $CFG, $DB; require_once($CFG->dirroot . "/calendar/lib.php"); $this->resetAfterTest(true); $this->setAdminUser(); $generator = $this->getDataGenerator(); $course = $generator->create_course(); $forumgenerator = $generator->get_plugin_generator('mod_forum'); $duedate = time() + DAYSECS; $cutoffdate = $duedate + DAYSECS; $newduedate = $duedate + 1; $forum = $forumgenerator->create_instance(['course' => $course->id]); $forum->duedate = $duedate; $forum->cutoffdate = $cutoffdate; $DB->update_record('forum', $forum); // Create a valid event. $event = new \calendar_event([ 'name' => 'Test event', 'description' => '', 'format' => 1, 'courseid' => $course->id, 'groupid' => 0, 'userid' => 2, 'modulename' => 'forum', 'instance' => $forum->id, 'eventtype' => FORUM_EVENT_TYPE_DUE, 'timestart' => $newduedate, 'timeduration' => 86400, 'visible' => 1 ]); mod_forum_core_calendar_event_timestart_updated($event, $forum); $forum = $DB->get_record('forum', ['id' => $forum->id]); $this->assertEquals($newduedate, $forum->duedate); $this->assertEquals($cutoffdate, $forum->cutoffdate); } /** * Test forum_get_layout_modes function. */ public function test_forum_get_layout_modes() { $expectednormal = [ FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'), FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'), FORUM_MODE_THREADED => get_string('modethreaded', 'forum'), FORUM_MODE_NESTED => get_string('modenested', 'forum') ]; $expectedexperimental = [ FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'), FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'), FORUM_MODE_THREADED => get_string('modethreaded', 'forum'), FORUM_MODE_NESTED_V2 => get_string('modenestedv2', 'forum') ]; $this->assertEquals($expectednormal, forum_get_layout_modes()); $this->assertEquals($expectednormal, forum_get_layout_modes(false)); $this->assertEquals($expectedexperimental, forum_get_layout_modes(true)); } /** * Provides data for tests that cause forum_check_throttling to return early. * * @return array */ public function forum_check_throttling_early_returns_provider() { return [ 'Empty blockafter' => [(object)['id' => 1, 'course' => SITEID, 'blockafter' => 0]], 'Empty blockperiod' => [(object)['id' => 1, 'course' => SITEID, 'blockafter' => DAYSECS, 'blockperiod' => 0]], ]; } /** * Tests the early return scenarios of forum_check_throttling. * * @dataProvider forum_check_throttling_early_returns_provider * @covers ::forum_check_throttling * @param \stdClass $forum The forum data. */ public function test_forum_check_throttling_early_returns(\stdClass $forum) { $this->assertFalse(forum_check_throttling($forum)); } /** * Provides data for tests that cause forum_check_throttling to throw exceptions early. * * @return array */ public function forum_check_throttling_early_exceptions_provider() { return [ 'Non-object forum' => ['a'], 'Forum ID not set' => [(object)['id' => false]], 'Course ID not set' => [(object)['id' => 1]], ]; } /** * Tests the early exception scenarios of forum_check_throttling. * * @dataProvider forum_check_throttling_early_exceptions_provider * @covers ::forum_check_throttling * @param mixed $forum The forum data. */ public function test_forum_check_throttling_early_exceptions($forum) { $this->expectException(\coding_exception::class); $this->assertFalse(forum_check_throttling($forum)); } /** * Tests forum_check_throttling when a non-existent numeric ID is passed for its forum parameter. * * @covers ::forum_check_throttling */ public function test_forum_check_throttling_nonexistent_numeric_id() { $this->resetAfterTest(); $this->expectException(\moodle_exception::class); forum_check_throttling(1); } /** * Tests forum_check_throttling when a non-existent forum record is passed for its forum parameter. * * @covers ::forum_check_throttling */ public function test_forum_check_throttling_nonexistent_forum_cm() { $this->resetAfterTest(); $dummyforum = (object)[ 'id' => 1, 'course' => SITEID, 'blockafter' => 2, 'blockperiod' => DAYSECS, ]; $this->expectException(\moodle_exception::class); forum_check_throttling($dummyforum); } /** * Tests forum_check_throttling when a user with the 'mod/forum:postwithoutthrottling' capability. * * @covers ::forum_check_throttling */ public function test_forum_check_throttling_teacher() { $this->resetAfterTest(); $generator = $this->getDataGenerator(); $course = $generator->create_course(); $teacher = $generator->create_and_enrol($course, 'teacher'); /** @var mod_forum_generator $forumgenerator */ $forumgenerator = $generator->get_plugin_generator('mod_forum'); // Forum that limits students from creating more than two posts per day. $forum = $forumgenerator->create_instance( [ 'course' => $course->id, 'blockafter' => 2, 'blockperiod' => DAYSECS, ] ); $this->setUser($teacher); $discussionrecord = [ 'course' => $course->id, 'forum' => $forum->id, 'userid' => $teacher->id, ]; $discussion = $forumgenerator->create_discussion($discussionrecord); // Create a forum post as the teacher. $postrecord = [ 'userid' => $teacher->id, 'discussion' => $discussion->id, ]; $forumgenerator->create_post($postrecord); // Create another forum post. $forumgenerator->create_post($postrecord); $this->assertFalse(forum_check_throttling($forum)); } /** * Tests forum_check_throttling for students. * * @covers ::forum_check_throttling */ public function test_forum_check_throttling_student() { $this->resetAfterTest(); $generator = $this->getDataGenerator(); $course = $generator->create_course(); $student = $generator->create_and_enrol($course, 'student'); /** @var mod_forum_generator $forumgenerator */ $forumgenerator = $generator->get_plugin_generator('mod_forum'); // Forum that limits students from creating more than two posts per day. $forum = $forumgenerator->create_instance( [ 'course' => $course->id, 'blockafter' => 2, 'blockperiod' => DAYSECS, 'warnafter' => 1, ] ); $this->setUser($student); // Student hasn't posted yet so no warning will be shown. $throttling = forum_check_throttling($forum); $this->assertFalse($throttling); // Create a discussion. $discussionrecord = [ 'course' => $course->id, 'forum' => $forum->id, 'userid' => $student->id, ]; $discussion = $forumgenerator->create_discussion($discussionrecord); // A warning will be shown to the student, but they should still be able to post. $throttling = forum_check_throttling($forum); $this->assertIsObject($throttling); $this->assertTrue($throttling->canpost); // Create another forum post as the student. $postrecord = [ 'userid' => $student->id, 'discussion' => $discussion->id, ]; $forumgenerator->create_post($postrecord); // Student should now be unable to post after their second post. $throttling = forum_check_throttling($forum); $this->assertIsObject($throttling); $this->assertFalse($throttling->canpost); } }