Merge branch 'MDL-67610-311-2' of git://github.com/junpataleta/moodle into MOODLE_311_STABLE

This commit is contained in:
Eloy Lafuente (stronk7) 2021-09-07 19:25:26 +02:00
commit 7c9fe59856
2 changed files with 450 additions and 31 deletions

View file

@ -4507,14 +4507,28 @@ function forum_tp_is_post_old($post, $time=null) {
function forum_tp_get_course_unread_posts($userid, $courseid) {
global $CFG, $DB;
$now = floor(time() / 60) * 60; // DB cache friendliness.
$cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 60 * 60);
$params = array($userid, $userid, $courseid, $cutoffdate, $userid);
$modinfo = get_fast_modinfo($courseid);
$forumcms = $modinfo->get_instances_of('forum');
if (empty($forumcms)) {
// Return early if the course doesn't have any forum. Will save us a DB query.
return [];
}
$now = floor(time() / MINSECS) * MINSECS; // DB cache friendliness.
$cutoffdate = $now - ($CFG->forum_oldpostdays * DAYSECS);
$params = [
'privatereplyto' => $userid,
'modified' => $cutoffdate,
'readuserid' => $userid,
'trackprefsuser' => $userid,
'courseid' => $courseid,
'trackforumuser' => $userid,
];
if (!empty($CFG->forum_enabletimedposts)) {
$timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
$params[] = $now;
$params[] = $now;
$timedsql = "AND d.timestart < :timestart AND (d.timeend = 0 OR d.timeend > :timeend)";
$params['timestart'] = $now;
$params['timeend'] = $now;
} else {
$timedsql = "";
}
@ -4522,31 +4536,65 @@ function forum_tp_get_course_unread_posts($userid, $courseid) {
if ($CFG->forum_allowforcedreadtracking) {
$trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_FORCED."
OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL
AND (SELECT trackforums FROM {user} WHERE id = ?) = 1))";
AND (SELECT trackforums FROM {user} WHERE id = :trackforumuser) = 1))";
} else {
$trackingsql = "AND ((f.trackingtype = ".FORUM_TRACKING_OPTIONAL." OR f.trackingtype = ".FORUM_TRACKING_FORCED.")
AND tf.id IS NULL
AND (SELECT trackforums FROM {user} WHERE id = ?) = 1)";
AND (SELECT trackforums FROM {user} WHERE id = :trackforumuser) = 1)";
}
$sql = "SELECT f.id, COUNT(p.id) AS unread
FROM {forum_posts} p
$sql = "SELECT f.id, COUNT(p.id) AS unread,
COUNT(p.privatereply) as privatereplies,
COUNT(p.privatereplytouser) as privaterepliestouser
FROM (
SELECT
id,
discussion,
CASE WHEN privatereplyto <> 0 THEN 1 END privatereply,
CASE WHEN privatereplyto = :privatereplyto THEN 1 END privatereplytouser
FROM {forum_posts}
WHERE modified >= :modified
) p
JOIN {forum_discussions} d ON d.id = p.discussion
JOIN {forum} f ON f.id = d.forum
JOIN {course} c ON c.id = f.course
LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
WHERE f.course = ?
AND p.modified >= ? AND r.id is NULL
LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = :readuserid)
LEFT JOIN {forum_track_prefs} tf ON (tf.userid = :trackprefsuser AND tf.forumid = f.id)
WHERE f.course = :courseid
AND r.id is NULL
$trackingsql
$timedsql
GROUP BY f.id";
if ($return = $DB->get_records_sql($sql, $params)) {
return $return;
$results = [];
if ($records = $DB->get_records_sql($sql, $params)) {
// Loop through each forum instance to check for capability and count the number of unread posts.
foreach ($forumcms as $cm) {
// Check that the forum instance exists in the query results.
if (!isset($records[$cm->instance])) {
continue;
}
$record = $records[$cm->instance];
$unread = $record->unread;
// Check if the user has the capability to read private replies for this forum instance.
$forumcontext = context_module::instance($cm->id);
if (!has_capability('mod/forum:readprivatereplies', $forumcontext, $userid)) {
// The real unread count would be the total of unread count minus the number of unread private replies plus
// the total unread private replies to the user.
$unread = $record->unread - $record->privatereplies + $record->privaterepliestouser;
}
// Build and add the object to the array of results to be returned.
$results[$record->id] = (object)[
'id' => $record->id,
'unread' => $unread,
];
}
}
return array();
return $results;
}
/**
@ -4591,7 +4639,8 @@ function forum_tp_count_forum_unread_posts($cm, $course, $resetreadcache = false
return $readcache[$course->id][$forumid];
}
if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
$forumcontext = context_module::instance($cm->id);
if (has_any_capability(['moodle/site:accessallgroups', 'mod/forum:readprivatereplies'], $forumcontext)) {
return $readcache[$course->id][$forumid];
}
@ -4604,30 +4653,36 @@ function forum_tp_count_forum_unread_posts($cm, $course, $resetreadcache = false
// add all groups posts
$mygroups[-1] = -1;
list ($groups_sql, $groups_params) = $DB->get_in_or_equal($mygroups);
list ($groupssql, $groupsparams) = $DB->get_in_or_equal($mygroups, SQL_PARAMS_NAMED);
$now = floor(time() / 60) * 60; // DB Cache friendliness.
$cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
$params = array($USER->id, $forumid, $cutoffdate);
$now = floor(time() / MINSECS) * MINSECS; // DB Cache friendliness.
$cutoffdate = $now - ($CFG->forum_oldpostdays * DAYSECS);
$params = [
'readuser' => $USER->id,
'forum' => $forumid,
'cutoffdate' => $cutoffdate,
'privatereplyto' => $USER->id,
];
if (!empty($CFG->forum_enabletimedposts)) {
$timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
$params[] = $now;
$params[] = $now;
$timedsql = "AND d.timestart < :timestart AND (d.timeend = 0 OR d.timeend > :timeend)";
$params['timestart'] = $now;
$params['timeend'] = $now;
} else {
$timedsql = "";
}
$params = array_merge($params, $groups_params);
$params = array_merge($params, $groupsparams);
$sql = "SELECT COUNT(p.id)
FROM {forum_posts} p
JOIN {forum_discussions} d ON p.discussion = d.id
LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
WHERE d.forum = ?
AND p.modified >= ? AND r.id is NULL
JOIN {forum_discussions} d ON p.discussion = d.id
LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = :readuser)
WHERE d.forum = :forum
AND p.modified >= :cutoffdate AND r.id is NULL
$timedsql
AND d.groupid $groups_sql";
AND d.groupid $groupssql
AND (p.privatereplyto = 0 OR p.privatereplyto = :privatereplyto)";
return $DB->get_field_sql($sql, $params);
}

View file

@ -509,6 +509,370 @@ class mod_forum_lib_testcase extends advanced_testcase {
$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.
*/