MDL-31355 mod_forum: Prevent users from posting after cut-off date

This commit is contained in:
Shamim Rezaie 2018-06-14 14:13:23 +10:00
parent 99bcb31895
commit cbf63d8efc
12 changed files with 271 additions and 5 deletions

View File

@ -69,7 +69,9 @@ class forum {
'completionreplies' => $forum->get_completion_replies(),
'completionposts' => $forum->get_completion_posts(),
'displaywordcount' => $forum->should_display_word_count(),
'lockdiscussionafter' => $forum->get_lock_discussions_after()
'lockdiscussionafter' => $forum->get_lock_discussions_after(),
'duedate' => $forum->get_due_date(),
'cutoffdate' => $forum->get_cutoff_date()
];
}, $forums);
}

View File

@ -99,6 +99,10 @@ class forum {
private $displaywordcounts;
/** @var bool $lockdiscussionafter Timestamp after which discussions should be locked */
private $lockdiscussionafter;
/** @var int $duedate Timestamp that represents the due date for forum posts */
private $duedate;
/** @var int $cutoffdate Timestamp after which forum posts will no longer be accepted */
private $cutoffdate;
/**
* Constructor
@ -132,6 +136,8 @@ class forum {
* @param int $completionposts Completion posts
* @param bool $displaywordcount Should display word counts in posts
* @param int $lockdiscussionafter Timestamp after which discussions should be locked
* @param int $duedate Timestamp that represents the due date for forum posts
* @param int $cutoffdate Timestamp after which forum posts will no longer be accepted
*/
public function __construct(
context $context,
@ -162,7 +168,9 @@ class forum {
int $completionreplies,
int $completionposts,
bool $displaywordcount,
int $lockdiscussionafter
int $lockdiscussionafter,
int $duedate,
int $cutoffdate
) {
$this->context = $context;
$this->coursemodule = $coursemodule;
@ -193,6 +201,8 @@ class forum {
$this->completionposts = $completionposts;
$this->displaywordcount = $displaywordcount;
$this->lockdiscussionafter = $lockdiscussionafter;
$this->duedate = $duedate;
$this->cutoffdate = $cutoffdate;
}
/**
@ -546,4 +556,66 @@ class forum {
return (($discussion->get_time_modified() + $this->get_lock_discussions_after()) < time());
}
/**
* Get the cutoff date.
*
* @return int
*/
public function get_cutoff_date() : int {
return $this->cutoffdate;
}
/**
* Does the forum have a cutoff date?
*
* @return bool
*/
public function has_cutoff_date() : bool {
return !empty($this->get_cutoff_date());
}
/**
* Is the cutoff date for the forum reached?
*
* @return bool
*/
public function is_cutoff_date_reached() : bool {
if ($this->has_cutoff_date() && ($this->get_cutoff_date() < time())) {
return true;
}
return false;
}
/**
* Get the due date.
*
* @return int
*/
public function get_due_date() : int {
return $this->duedate;
}
/**
* Does the forum have a due date?
*
* @return bool
*/
public function has_due_date() : bool {
return !empty($this->get_due_date());
}
/**
* Is the due date for the forum reached?
*
* @return bool
*/
public function is_due_date_reached() : bool {
if ($this->has_due_date() && ($this->get_due_date() < time())) {
return true;
}
return false;
}
}

View File

@ -100,7 +100,9 @@ class entity {
$record->completionreplies,
$record->completionposts,
$record->displaywordcount,
$record->lockdiscussionafter
$record->lockdiscussionafter,
$record->duedate,
$record->cutoffdate
);
}

View File

@ -110,6 +110,12 @@ class capability {
return false;
}
if ($this->forum->is_cutoff_date_reached()) {
if (!has_capability('mod/forum:canoverridecutoff', $this->get_context())) {
return false;
}
}
switch ($this->forum->get_type()) {
case 'news':
$capability = 'mod/forum:addnews';

View File

@ -377,6 +377,18 @@ class discussion {
$forum = $this->forum;
$renderer = $this->renderer;
if ($forum->is_cutoff_date_reached()) {
$notifications[] = (new notification(
get_string('cutoffdatereached', 'forum'),
notification::NOTIFY_INFO
))->set_show_closebutton();
} else if ($forum->has_due_date()) {
$notifications[] = (new notification(
get_string('thisforumhasduedate', 'forum', userdate($forum->get_due_date())),
notification::NOTIFY_INFO
))->set_show_closebutton();
}
if ($forum->is_discussion_locked($discussion)) {
$notifications[] = (new notification(
get_string('discussionlocked', 'forum'),

View File

@ -327,6 +327,18 @@ class discussion_list {
$renderer = $this->renderer;
$capabilitymanager = $this->capabilitymanager;
if ($forum->is_cutoff_date_reached()) {
$notifications[] = (new notification(
get_string('cutoffdatereached', 'forum'),
notification::NOTIFY_INFO
))->set_show_closebutton();
} else if ($forum->has_due_date()) {
$notifications[] = (new notification(
get_string('thisforumhasduedate', 'forum', userdate($forum->get_due_date())),
notification::NOTIFY_INFO
))->set_show_closebutton();
}
if ($forum->has_blocking_enabled()) {
$notifications[] = (new notification(
get_string('thisforumisthrottled', 'forum', [

View File

@ -395,5 +395,15 @@ $capabilities = array(
'manager' => CAP_ALLOW
)
),
'mod/forum:canoverridecutoff' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'mod/forum:canoverridediscussionlock'
),
);

View File

@ -126,6 +126,7 @@ $string['couldnotupdate'] = 'Could not update your post due to an unknown error'
$string['crontask'] = 'Forum mailings and maintenance jobs';
$string['cutoffdate'] = 'Cut-off date';
$string['cutoffdate_help'] = 'If set, the forum will not accept posts after this date.';
$string['cutoffdatereached'] = 'The cut-off date for posting to this forum is reached so you can no longer post to it.';
$string['cutoffdatevalidation'] = 'The cut-off date cannot be earlier than the due date.';
$string['delete'] = 'Delete';
$string['deleteddiscussion'] = 'The discussion topic has been deleted';
@ -235,6 +236,7 @@ $string['forum:addinstance'] = 'Add a new forum';
$string['forum:addnews'] = 'Add announcements';
$string['forum:addquestion'] = 'Add question';
$string['forum:allowforcesubscribe'] = 'Allow force subscribe';
$string['forum:canoverridecutoff'] = 'Post to forums after their cut-off date';
$string['forum:canoverridediscussionlock'] = 'Reply to locked discussions';
$string['forumauthorhidden'] = 'Author (hidden)';
$string['forumblockingalmosttoomanyposts'] = 'You are approaching the posting threshold. You have posted {$a->numposts} times in the last {$a->blockperiod} and the limit is {$a->blockafter} posts.';
@ -587,6 +589,7 @@ $string['subscriptions'] = 'Subscriptions';
$string['tagarea_forum_posts'] = 'Forum posts';
$string['tagsdeleted'] = 'Forum tags have been deleted';
$string['thisforumisthrottled'] = 'This forum has a limit to the number of forum postings you can make in a given time period - this is currently set at {$a->blockafter} posting(s) in {$a->blockperiod}';
$string['thisforumhasduedate'] = 'The due date for posting to this forum is {$a}.';
$string['timedhidden'] = 'Timed status: Hidden from students';
$string['timedposts'] = 'Timed posts';
$string['timedvisible'] = 'Timed status: Visible to all users';

View File

@ -3703,6 +3703,12 @@ function forum_user_can_post_discussion($forum, $currentgroup=null, $unused=-1,
$context = context_module::instance($cm->id);
}
if (forum_is_cutoff_date_reached($forum)) {
if (!has_capability('mod/forum:canoverridecutoff', $context)) {
return false;
}
}
if ($currentgroup === null) {
$currentgroup = groups_get_activity_group($cm);
}
@ -3796,6 +3802,12 @@ function forum_user_can_post($forum, $discussion, $user=NULL, $cm=NULL, $course=
$context = context_module::instance($cm->id);
}
if (forum_is_cutoff_date_reached($forum)) {
if (!has_capability('mod/forum:canoverridecutoff', $context)) {
return false;
}
}
// Check whether the discussion is locked.
if (forum_discussion_is_locked($forum, $discussion)) {
if (!has_capability('mod/forum:canoverridediscussionlock', $context)) {
@ -6305,6 +6317,26 @@ function mod_forum_inplace_editable($itemtype, $itemid, $newvalue) {
}
}
/**
* Determine whether the specified forum's cutoff date is reached.
*
* @param stdClass $forum The forum
* @return bool
*/
function forum_is_cutoff_date_reached($forum) {
$entityfactory = \mod_forum\local\container::get_entity_factory();
$coursemoduleinfo = get_fast_modinfo($forum->course);
$cminfo = $coursemoduleinfo->instances['forum'][$forum->id];
$forumentity = $entityfactory->get_forum_from_stdclass(
$forum,
context_module::instance($cminfo->id),
$cminfo->get_course_module_record(),
$cminfo->get_course()
);
return $forumentity->is_cutoff_date_reached();
}
/**
* Determine whether the specified discussion is time-locked.
*

View File

@ -92,6 +92,8 @@ class mod_forum_entities_forum_testcase extends advanced_testcase {
$completionposts = 0;
$displaywordcount = false;
$lockdiscussionafter = 0;
$duedate = 0;
$cutoffdate = 0;
$forum = new forum_entity(
$context,
@ -122,7 +124,9 @@ class mod_forum_entities_forum_testcase extends advanced_testcase {
$completionreplies,
$completionposts,
$displaywordcount,
$lockdiscussionafter
$lockdiscussionafter,
$duedate,
$cutoffdate
);
$this->assertEquals($context, $forum->get_context());
@ -160,5 +164,9 @@ class mod_forum_entities_forum_testcase extends advanced_testcase {
$this->assertEquals($lockdiscussionafter, $forum->get_lock_discussions_after());
$this->assertEquals(false, $forum->has_lock_discussions_after());
$this->assertEquals(false, $forum->is_discussion_locked($discussion));
$this->assertEquals(false, $forum->has_due_date());
$this->assertEquals(false, $forum->is_due_date_reached());
$this->assertEquals(false, $forum->has_cutoff_date());
$this->assertEquals(false, $forum->is_cutoff_date_reached());
}
}

View File

@ -1458,7 +1458,34 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
$this->assertTrue($result['status']);
$this->assertTrue($result['canpindiscussions']);
$this->assertTrue($result['cancreateattachment']);
}
/*
* A basic test to make sure users cannot post to forum after the cutoff date.
*/
public function test_can_add_discussion_after_cutoff() {
$this->resetAfterTest(true);
// Create courses to add the modules.
$course = self::getDataGenerator()->create_course();
$user = self::getDataGenerator()->create_user();
// Create a forum with cutoff date set to a past date.
$forum = self::getDataGenerator()->create_module('forum', ['course' => $course->id, 'cutoffdate' => time() - 1]);
// User with no mod/forum:canoverridecutoff capability.
self::setUser($user);
$this->getDataGenerator()->enrol_user($user->id, $course->id);
$result = mod_forum_external::can_add_discussion($forum->id);
$result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
$this->assertFalse($result['status']);
self::setAdminUser();
$result = mod_forum_external::can_add_discussion($forum->id);
$result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
$this->assertTrue($result['status']);
}
/**

View File

@ -2200,7 +2200,7 @@ class mod_forum_lib_testcase extends advanced_testcase {
* Test forum_user_can_post_discussion
*/
public function test_forum_user_can_post_discussion() {
global $CFG, $DB;
global $DB;
$this->resetAfterTest(true);
@ -2297,6 +2297,40 @@ class mod_forum_lib_testcase extends advanced_testcase {
$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.
*/
@ -3138,6 +3172,52 @@ class mod_forum_lib_testcase extends advanced_testcase {
];
}
/**
* 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 that {@link forum_update_post()} keeps correct forum_discussions usermodified.
*/