mirror of
https://github.com/moodle/moodle.git
synced 2025-04-11 19:42:35 +02:00
MDL-46881 mod_forum: Move cron around
This commit is contained in:
parent
28fd710f84
commit
4459ad290d
@ -17,16 +17,68 @@
|
||||
/**
|
||||
* A scheduled task for forum cron.
|
||||
*
|
||||
* @todo MDL-44734 This job will be split up properly.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2014 Dan Poltawski <dan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace mod_forum\task;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
|
||||
/**
|
||||
* The main scheduled task for the forum.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class cron_task extends \core\task\scheduled_task {
|
||||
|
||||
// Use the logging trait to get some nice, juicy, logging.
|
||||
use \core\task\logging_trait;
|
||||
|
||||
/**
|
||||
* @var The list of courses which contain posts to be sent.
|
||||
*/
|
||||
protected $courses = [];
|
||||
|
||||
/**
|
||||
* @var The list of forums which contain posts to be sent.
|
||||
*/
|
||||
protected $forums = [];
|
||||
|
||||
/**
|
||||
* @var The list of discussions which contain posts to be sent.
|
||||
*/
|
||||
protected $discussions = [];
|
||||
|
||||
/**
|
||||
* @var The list of posts to be sent.
|
||||
*/
|
||||
protected $posts = [];
|
||||
|
||||
/**
|
||||
* @var The list of post authors.
|
||||
*/
|
||||
protected $users = [];
|
||||
|
||||
/**
|
||||
* @var The list of subscribed users.
|
||||
*/
|
||||
protected $subscribedusers = [];
|
||||
|
||||
/**
|
||||
* @var The list of digest users.
|
||||
*/
|
||||
protected $digestusers = [];
|
||||
|
||||
/**
|
||||
* @var The list of adhoc data for sending.
|
||||
*/
|
||||
protected $adhocdata = [];
|
||||
|
||||
/**
|
||||
* Get a descriptive name for this task (shown to admins).
|
||||
*
|
||||
@ -37,12 +89,512 @@ class cron_task extends \core\task\scheduled_task {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run forum cron.
|
||||
* Execute the scheduled task.
|
||||
*/
|
||||
public function execute() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
forum_cron();
|
||||
global $CFG, $DB;
|
||||
|
||||
$timenow = time();
|
||||
|
||||
// Delete any really old posts in the digest queue.
|
||||
$weekago = $timenow - (7 * 24 * 3600);
|
||||
$this->log_start("Removing old digest records from 7 days ago.");
|
||||
$DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
|
||||
$this->log_finish("Removed all old digest records.");
|
||||
|
||||
$endtime = $timenow - $CFG->maxeditingtime;
|
||||
$starttime = $endtime - (2 * DAYSECS);
|
||||
$this->log_start("Fetching unmailed posts.");
|
||||
if (!$posts = $this->get_unmailed_posts($starttime, $endtime, $timenow)) {
|
||||
$this->log_finish("No posts found.", 1);
|
||||
return false;
|
||||
}
|
||||
$this->log_finish("Done");
|
||||
|
||||
// Process post data and turn into adhoc tasks.
|
||||
$this->process_post_data($posts);
|
||||
|
||||
// Mark posts as read.
|
||||
list($in, $params) = $DB->get_in_or_equal(array_keys($posts));
|
||||
$DB->set_field_select('forum_posts', 'mailed', 1, "id {$in}", $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all posts and convert to appropriated hoc tasks.
|
||||
*
|
||||
* @param \stdClass[] $posts
|
||||
*/
|
||||
protected function process_post_data($posts) {
|
||||
$trace = $this->get_trace();
|
||||
|
||||
$discussionids = [];
|
||||
$forumids = [];
|
||||
$courseids = [];
|
||||
|
||||
$this->log_start("Processing post information");
|
||||
|
||||
$start = microtime(true);
|
||||
foreach ($posts as $id => $post) {
|
||||
$discussionids[$post->discussion] = true;
|
||||
$forumids[$post->forum] = true;
|
||||
$courseids[$post->course] = true;
|
||||
$this->add_data_for_post($post);
|
||||
$this->posts[$id] = $post;
|
||||
}
|
||||
$this->log_finish(sprintf("Processed %s posts", count($this->posts)));
|
||||
|
||||
if (empty($this->posts)) {
|
||||
$this->log("No posts found. Returning early.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Please note, this order is intentional.
|
||||
// The forum cache makes use of the course.
|
||||
$this->log_start("Filling caches");
|
||||
|
||||
$start = microtime(true);
|
||||
$this->log_start("Filling course cache", 1);
|
||||
$this->fill_course_cache(array_keys($courseids));
|
||||
$this->log_finish("Done", 1);
|
||||
|
||||
$this->log_start("Filling forum cache", 1);
|
||||
$this->fill_forum_cache(array_keys($forumids));
|
||||
$this->log_finish("Done", 1);
|
||||
|
||||
$this->log_start("Filling discussion cache", 1);
|
||||
$this->fill_discussion_cache(array_keys($discussionids));
|
||||
$this->log_finish("Done", 1);
|
||||
|
||||
$this->log_start("Filling user subscription cache", 1);
|
||||
$this->fill_user_subscription_cache();
|
||||
$this->log_finish("Done", 1);
|
||||
|
||||
$this->log_start("Filling digest cache", 1);
|
||||
$this->fill_digest_cache();
|
||||
$this->log_finish("Done", 1);
|
||||
|
||||
$this->log_start("Filling user posted cache for Q&A forums", 1);
|
||||
$this->fetch_user_has_posted();
|
||||
$this->log_finish("Done", 1);
|
||||
|
||||
$this->log_start("Queueing user tasks.");
|
||||
$this->queue_user_tasks();
|
||||
$this->log_finish("All tasks queued.", 1);
|
||||
|
||||
$this->log_finish("All caches filled");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the course cache.
|
||||
*
|
||||
* @param int[] $courseids
|
||||
*/
|
||||
protected function fill_course_cache($courseids) {
|
||||
global $DB;
|
||||
|
||||
list($in, $params) = $DB->get_in_or_equal($courseids);
|
||||
$this->courses = $DB->get_records_select('course', "id $in", $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the forum cache.
|
||||
*
|
||||
* @param int[] $forumids
|
||||
*/
|
||||
protected function fill_forum_cache($forumids) {
|
||||
global $DB;
|
||||
|
||||
$requiredfields = [
|
||||
'id',
|
||||
'course',
|
||||
'forcesubscribe',
|
||||
'type',
|
||||
];
|
||||
list($in, $params) = $DB->get_in_or_equal($forumids);
|
||||
$this->forums = $DB->get_records_select('forum', "id $in", $params, '', implode(', ', $requiredfields));
|
||||
foreach ($this->forums as $id => $forum) {
|
||||
\mod_forum\subscriptions::fill_subscription_cache($id);
|
||||
\mod_forum\subscriptions::fill_discussion_subscription_cache($id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the discussion cache.
|
||||
*
|
||||
* @param int[] $discussionids
|
||||
*/
|
||||
protected function fill_discussion_cache($discussionids) {
|
||||
global $DB;
|
||||
|
||||
if (empty($discussionids)) {
|
||||
$this->discussion = [];
|
||||
} else {
|
||||
|
||||
$requiredfields = [
|
||||
'id',
|
||||
'groupid',
|
||||
'firstpost',
|
||||
'timestart',
|
||||
'timeend',
|
||||
];
|
||||
|
||||
list($in, $params) = $DB->get_in_or_equal($discussionids);
|
||||
$this->discussions = $DB->get_records_select(
|
||||
'forum_discussions', "id $in", $params, '', implode(', ', $requiredfields));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the cache of user digest preferences.
|
||||
*/
|
||||
protected function fill_digest_cache() {
|
||||
global $DB;
|
||||
|
||||
if (empty($this->users)) {
|
||||
return;
|
||||
}
|
||||
// Get the list of forum subscriptions for per-user per-forum maildigest settings.
|
||||
list($in, $params) = $DB->get_in_or_equal(array_keys($this->users));
|
||||
$digestspreferences = $DB->get_recordset_select(
|
||||
'forum_digests', "userid $in", $params, '', 'id, userid, forum, maildigest');
|
||||
foreach ($digestspreferences as $digestpreference) {
|
||||
if (!isset($this->digestusers[$digestpreference->forum])) {
|
||||
$this->digestusers[$digestpreference->forum] = [];
|
||||
}
|
||||
$this->digestusers[$digestpreference->forum][$digestpreference->userid] = $digestpreference->maildigest;
|
||||
}
|
||||
$digestspreferences->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dsta for the current forum post to the structure of adhoc data.
|
||||
*
|
||||
* @param \stdClass $post
|
||||
*/
|
||||
protected function add_data_for_post($post) {
|
||||
if (!isset($this->adhocdata[$post->course])) {
|
||||
$this->adhocdata[$post->course] = [];
|
||||
}
|
||||
|
||||
if (!isset($this->adhocdata[$post->course][$post->forum])) {
|
||||
$this->adhocdata[$post->course][$post->forum] = [];
|
||||
}
|
||||
|
||||
if (!isset($this->adhocdata[$post->course][$post->forum][$post->discussion])) {
|
||||
$this->adhocdata[$post->course][$post->forum][$post->discussion] = [];
|
||||
}
|
||||
|
||||
$this->adhocdata[$post->course][$post->forum][$post->discussion][$post->id] = $post->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the cache of user subscriptions.
|
||||
*/
|
||||
protected function fill_user_subscription_cache() {
|
||||
foreach ($this->forums as $forum) {
|
||||
$cm = get_fast_modinfo($this->courses[$forum->course])->instances['forum'][$forum->id];
|
||||
$modcontext = \context_module::instance($cm->id);
|
||||
|
||||
$this->subscribedusers[$forum->id] = [];
|
||||
if ($users = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, $modcontext, 'u.id, u.maildigest', true)) {
|
||||
foreach ($users as $user) {
|
||||
// This user is subscribed to this forum.
|
||||
$this->subscribedusers[$forum->id][$user->id] = $user->id;
|
||||
if (!isset($this->users[$user->id])) {
|
||||
// Store minimal user info.
|
||||
$this->users[$user->id] = $user;
|
||||
}
|
||||
}
|
||||
// Release memory.
|
||||
unset($users);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the cache for Q&A forums.
|
||||
*/
|
||||
protected function fetch_user_has_posted() {
|
||||
global $DB;
|
||||
|
||||
$forums = array_filter($this->forums, function($forum) {
|
||||
return $forum->type === 'qanda';
|
||||
});
|
||||
|
||||
if (empty($forums)) {
|
||||
return;
|
||||
}
|
||||
|
||||
list($in, $params) = $DB->get_in_or_equal(array_keys($forums));
|
||||
|
||||
$sql = "SELECT d.forum, d.firstpost, p.userid
|
||||
FROM {forum} f
|
||||
INNER JOIN {forum_discussions} d ON d.forum = f.id
|
||||
LEFT JOIN {forum_posts} p ON p.discussion = d.id
|
||||
WHERE f.type = 'qanda'
|
||||
AND f.id {$in}
|
||||
GROUP BY p.userid, d.forum, d.firstpost";
|
||||
|
||||
$rs = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($rs as $row) {
|
||||
if (empty($this->qandametadata[$row->forum])) {
|
||||
$this->qandametadata[$row->forum] = (object) [
|
||||
'users' => [],
|
||||
'firstpost' => [$row->firstpost],
|
||||
];
|
||||
}
|
||||
$this->qandametadata[$row->forum]->users[$row->userid] = true;
|
||||
}
|
||||
$rs->close();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue the user tasks.
|
||||
*/
|
||||
protected function queue_user_tasks() {
|
||||
global $CFG, $DB;
|
||||
|
||||
$trace = $this->get_trace();
|
||||
|
||||
$timenow = time();
|
||||
$sitetimezone = \core_date::get_server_timezone();
|
||||
$counts = [
|
||||
'digests' => 0,
|
||||
'individuals' => 0,
|
||||
'users' => 0,
|
||||
'ignored' => 0,
|
||||
'messages' => 0,
|
||||
];
|
||||
$this->log("Processing " . count($this->users) . " users", 1);
|
||||
foreach ($this->users as $user) {
|
||||
$usercounts = [
|
||||
'digests' => 0,
|
||||
'messages' => 0,
|
||||
];
|
||||
|
||||
$send = false;
|
||||
// Setup this user so that the capabilities are cached, and environment matches receiving user.
|
||||
cron_setup_user($user);
|
||||
|
||||
list($individualpostdata, $digestpostdata) = $this->fetch_posts_for_user($user);
|
||||
|
||||
if (!empty($digestpostdata)) {
|
||||
// Insert all of the records for the digest.
|
||||
$DB->insert_records('forum_queue', $digestpostdata);
|
||||
$digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
|
||||
|
||||
$task = new \mod_forum\task\send_user_digests();
|
||||
$task->set_userid($user->id);
|
||||
$task->set_component('mod_forum');
|
||||
$task->set_next_run_time($digesttime);
|
||||
\core\task\manager::queue_adhoc_task($task, true);
|
||||
$usercounts['digests']++;
|
||||
$send = true;
|
||||
}
|
||||
|
||||
if (!empty($individualpostdata)) {
|
||||
$usercounts['messages'] += count($individualpostdata);
|
||||
|
||||
$task = new \mod_forum\task\send_user_notifications();
|
||||
$task->set_userid($user->id);
|
||||
$task->set_custom_data($individualpostdata);
|
||||
$task->set_component('mod_forum');
|
||||
\core\task\manager::queue_adhoc_task($task);
|
||||
$counts['individuals']++;
|
||||
$send = true;
|
||||
}
|
||||
|
||||
if ($send) {
|
||||
$counts['users']++;
|
||||
$counts['messages'] += $usercounts['messages'];
|
||||
$counts['digests'] += $usercounts['digests'];
|
||||
} else {
|
||||
$counts['ignored']++;
|
||||
}
|
||||
|
||||
$this->log(sprintf("Queued %d digests and %d messages for %s",
|
||||
$usercounts['digests'],
|
||||
$usercounts['messages'],
|
||||
$user->id
|
||||
), 2);
|
||||
}
|
||||
$this->log(
|
||||
sprintf(
|
||||
"Queued %d digests, and %d individual tasks for %d post mails. " .
|
||||
"Unique users: %d (%d ignored)",
|
||||
$counts['digests'],
|
||||
$counts['individuals'],
|
||||
$counts['messages'],
|
||||
$counts['users'],
|
||||
$counts['ignored']
|
||||
), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch posts for this user.
|
||||
*
|
||||
* @param \stdClass $user The user to fetch posts for.
|
||||
*/
|
||||
protected function fetch_posts_for_user($user) {
|
||||
// We maintain a mapping of user groups for each forum.
|
||||
$usergroups = [];
|
||||
$digeststructure = [];
|
||||
|
||||
$poststructure = $this->adhocdata;
|
||||
$poststosend = [];
|
||||
foreach ($poststructure as $courseid => $forumids) {
|
||||
$course = $this->courses[$courseid];
|
||||
foreach ($forumids as $forumid => $discussionids) {
|
||||
$forum = $this->forums[$forumid];
|
||||
$maildigest = forum_get_user_maildigest_bulk($this->digestusers, $user, $forumid);
|
||||
|
||||
if (!isset($this->subscribedusers[$forumid][$user->id])) {
|
||||
// This user has no subscription of any kind to this forum.
|
||||
// Do not send them any posts at all.
|
||||
unset($poststructure[$courseid][$forumid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$subscriptiontime = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $user->id);
|
||||
|
||||
$cm = get_fast_modinfo($course)->instances['forum'][$forumid];
|
||||
foreach ($discussionids as $discussionid => $postids) {
|
||||
$discussion = $this->discussions[$discussionid];
|
||||
if (!\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussionid, $cm)) {
|
||||
// The user does not subscribe to this forum as a whole, or to this specific discussion.
|
||||
unset($poststructure[$courseid][$forumid][$discussionid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {
|
||||
// This discussion has a groupmode set (SEPARATEGROUPS or VISIBLEGROUPS).
|
||||
// Check whether the user can view it based on their groups.
|
||||
if (!isset($usergroups[$forum->id])) {
|
||||
$usergroups[$forum->id] = groups_get_all_groups($courseid, $user->id, $cm->groupingid);
|
||||
}
|
||||
|
||||
if (!isset($usergroups[$forum->id][$discussion->groupid])) {
|
||||
// This user is not a member of this group, or the group no longer exists.
|
||||
|
||||
$modcontext = \context_module::instance($cm->id);
|
||||
if (!has_capability('moodle/site:accessallgroups', $modcontext, $user)) {
|
||||
// This user does not have the accessallgroups and is not a member of the group.
|
||||
// Do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS.
|
||||
unset($poststructure[$courseid][$forumid][$discussionid]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($postids as $postid) {
|
||||
$post = $this->posts[$postid];
|
||||
if ($subscriptiontime) {
|
||||
// Skip posts if the user subscribed to the discussion after it was created.
|
||||
$subscribedafter = isset($subscriptiontime[$post->discussion]);
|
||||
$subscribedafter = $subscribedafter && ($subscriptiontime[$post->discussion] > $post->created);
|
||||
if ($subscribedafter) {
|
||||
// The user subscribed to the discussion/forum after this post was created.
|
||||
unset($poststructure[$courseid][$forumid][$discussionid]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($forum->type === 'qanda' && $postid != $discussion->firstpost) {
|
||||
$hasqandapost = isset($this->qandametadata[$forumid]);
|
||||
$hasqandapost = $hasqandapost && isset($this->qandametadata[$forumid]->users[$user->id]);
|
||||
if (!$hasqandapost) {
|
||||
// The user has not posted to this qanda forum.
|
||||
unset($poststructure[$courseid][$forumid][$discussionid]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
|
||||
// The user is not allowed to see the post for some other reason.
|
||||
unset($poststructure[$courseid][$forumid][$discussionid][$postid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($maildigest > 0) {
|
||||
// This user wants the mails to be in digest form.
|
||||
$digeststructure[] = (object) [
|
||||
'userid' => $user->id,
|
||||
'discussionid' => $discussion->id,
|
||||
'postid' => $post->id,
|
||||
'timemodified' => $post->created,
|
||||
];
|
||||
unset($poststructure[$courseid][$forumid][$discussionid][$postid]);
|
||||
continue;
|
||||
} else {
|
||||
// Add this post to the list of postids to be sent.
|
||||
$poststosend[] = $postid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($poststructure[$courseid][$forumid])) {
|
||||
// This user is not subscribed to any discussions in this forum at all.
|
||||
unset($poststructure[$courseid][$forumid]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (empty($poststructure[$courseid])) {
|
||||
// This user is not subscribed to any forums in this course.
|
||||
unset($poststructure[$courseid]);
|
||||
}
|
||||
}
|
||||
|
||||
return [$poststosend, $digeststructure];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all new posts that have not been mailed yet
|
||||
*
|
||||
* @param int $starttime posts created after this time
|
||||
* @param int $endtime posts created before this
|
||||
* @param int $now used for timed discussions only
|
||||
* @return array
|
||||
*/
|
||||
protected function get_unmailed_posts($starttime, $endtime, $now = null) {
|
||||
global $CFG, $DB;
|
||||
|
||||
$params = array();
|
||||
$params['mailed'] = FORUM_MAILED_PENDING;
|
||||
$params['ptimestart'] = $starttime;
|
||||
$params['ptimeend'] = $endtime;
|
||||
$params['mailnow'] = 1;
|
||||
|
||||
if (!empty($CFG->forum_enabletimedposts)) {
|
||||
if (empty($now)) {
|
||||
$now = time();
|
||||
}
|
||||
$selectsql = "AND (p.created >= :ptimestart OR d.timestart >= :pptimestart)";
|
||||
$params['pptimestart'] = $starttime;
|
||||
$timedsql = "AND (d.timestart < :dtimestart AND (d.timeend = 0 OR d.timeend > :dtimeend))";
|
||||
$params['dtimestart'] = $now;
|
||||
$params['dtimeend'] = $now;
|
||||
} else {
|
||||
$timedsql = "";
|
||||
$selectsql = "AND p.created >= :ptimestart";
|
||||
}
|
||||
|
||||
return $DB->get_records_sql(
|
||||
"SELECT
|
||||
p.id,
|
||||
p.discussion,
|
||||
d.forum,
|
||||
d.course,
|
||||
p.created,
|
||||
p.parent,
|
||||
p.userid
|
||||
FROM {forum_posts} p
|
||||
JOIN {forum_discussions} d ON d.id = p.discussion
|
||||
WHERE p.mailed = :mailed
|
||||
$selectsql
|
||||
AND (p.created < :ptimeend OR p.mailnow = :mailnow)
|
||||
$timedsql
|
||||
ORDER BY p.modified ASC",
|
||||
$params);
|
||||
}
|
||||
}
|
||||
|
599
mod/forum/classes/task/send_user_digests.php
Normal file
599
mod/forum/classes/task/send_user_digests.php
Normal file
@ -0,0 +1,599 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This file defines an adhoc task to send notifications.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\task;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use html_writer;
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
|
||||
/**
|
||||
* Adhoc task to send moodle forum digests for the specified user.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class send_user_digests extends \core\task\adhoc_task {
|
||||
|
||||
// Use the logging trait to get some nice, juicy, logging.
|
||||
use \core\task\logging_trait;
|
||||
|
||||
/**
|
||||
* @var \stdClass A shortcut to $USER.
|
||||
*/
|
||||
protected $recipient;
|
||||
|
||||
/**
|
||||
* @var bool[] Whether the user can view fullnames for each forum.
|
||||
*/
|
||||
protected $viewfullnames = [];
|
||||
|
||||
/**
|
||||
* @var bool[] Whether the user can post in each forum.
|
||||
*/
|
||||
protected $canpostto = [];
|
||||
|
||||
/**
|
||||
* @var \stdClass[] Courses with posts them.
|
||||
*/
|
||||
protected $courses = [];
|
||||
|
||||
/**
|
||||
* @var \stdClass[] Forums with posts them.
|
||||
*/
|
||||
protected $forums = [];
|
||||
|
||||
/**
|
||||
* @var \stdClass[] Discussions with posts them.
|
||||
*/
|
||||
protected $discussions = [];
|
||||
|
||||
/**
|
||||
* @var \stdClass[] The posts to be sent.
|
||||
*/
|
||||
protected $posts = [];
|
||||
|
||||
/**
|
||||
* @var \stdClass[] The various authors.
|
||||
*/
|
||||
protected $users = [];
|
||||
|
||||
/**
|
||||
* @var \stdClass[] A list of any per-forum digest preference that this user holds.
|
||||
*/
|
||||
protected $forumdigesttypes = [];
|
||||
|
||||
/**
|
||||
* @var bool Whether the user has requested HTML or not.
|
||||
*/
|
||||
protected $allowhtml = true;
|
||||
|
||||
/**
|
||||
* @var string The subject of the message.
|
||||
*/
|
||||
protected $postsubject = '';
|
||||
|
||||
/**
|
||||
* @var string The plaintext content of the whole message.
|
||||
*/
|
||||
protected $notificationtext = '';
|
||||
|
||||
/**
|
||||
* @var string The HTML content of the whole message.
|
||||
*/
|
||||
protected $notificationhtml = '';
|
||||
|
||||
/**
|
||||
* @var string The plaintext content for the current discussion being processed.
|
||||
*/
|
||||
protected $discussiontext = '';
|
||||
|
||||
/**
|
||||
* @var string The HTML content for the current discussion being processed.
|
||||
*/
|
||||
protected $discussionhtml = '';
|
||||
|
||||
/**
|
||||
* @var int The number of messages sent in this digest.
|
||||
*/
|
||||
protected $sentcount = 0;
|
||||
|
||||
/**
|
||||
* @var \renderer[][] A cache of the different types of renderer, stored both by target (HTML, or Text), and type.
|
||||
*/
|
||||
protected $renderers = [
|
||||
'html' => [],
|
||||
'text' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var int[] A list of post IDs to be marked as read for this user.
|
||||
*/
|
||||
protected $markpostsasread = [];
|
||||
|
||||
/**
|
||||
* Send out messages.
|
||||
*/
|
||||
public function execute() {
|
||||
// Terminate if not able to fetch all digests in 5 minutes.
|
||||
\core_php_time_limit::raise(300);
|
||||
|
||||
$starttime = time();
|
||||
|
||||
$this->recipient = \core_user::get_user($this->get_userid());
|
||||
$this->log_start("Sending forum digests for {$this->recipient->username} ({$this->recipient->id})");
|
||||
|
||||
if (empty($this->recipient->mailformat) || $this->recipient->mailformat != 1) {
|
||||
// This user does not want to receive HTML.
|
||||
$this->allowhtml = false;
|
||||
}
|
||||
|
||||
// Fetch all of the data we need to mail these posts.
|
||||
$this->prepare_data($starttime);
|
||||
|
||||
if (empty($this->posts) || empty($this->discussions) || empty($this->forums)) {
|
||||
$this->log_finish("No messages found to send.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the message headers.
|
||||
$this->add_message_header();
|
||||
|
||||
foreach ($this->discussions as $discussion) {
|
||||
// Raise the time limit for each discussion.
|
||||
\core_php_time_limit::raise(120);
|
||||
|
||||
// Grab the data pertaining to this discussion.
|
||||
$forum = $this->forums[$discussion->forum];
|
||||
$course = $this->courses[$forum->course];
|
||||
$cm = get_fast_modinfo($course)->instances['forum'][$forum->id];
|
||||
$modcontext = \context_module::instance($cm->id);
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
|
||||
if (empty($this->posts[$discussion->id])) {
|
||||
// Somehow there are no posts.
|
||||
// This should not happen but better safe than sorry.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
|
||||
// The course is hidden and the user does not have access to it.
|
||||
// Permissions may have changed since it was queued.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!forum_user_can_see_discussion($forum, $discussion, $modcontext, $this->recipient)) {
|
||||
// User cannot see this discussion.
|
||||
// Permissions may have changed since it was queued.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\mod_forum\subscriptions::is_subscribed($this->recipient->id, $forum, $discussion->id, $cm)) {
|
||||
// The user does not subscribe to this forum as a whole, or to this specific discussion.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch additional values relating to this forum.
|
||||
if (!isset($this->canpostto[$discussion->id])) {
|
||||
$this->canpostto[$discussion->id] = forum_user_can_post(
|
||||
$forum, $discussion, $this->recipient, $cm, $course, $modcontext);
|
||||
}
|
||||
|
||||
if (!isset($this->viewfullnames[$forum->id])) {
|
||||
$this->viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $this->recipient->id);
|
||||
}
|
||||
|
||||
// Set the discussion storage values.
|
||||
$discussionpostcount = 0;
|
||||
$this->discussiontext = '';
|
||||
$this->discussionhtml = '';
|
||||
|
||||
// Add the header for this discussion.
|
||||
$this->add_discussion_header($discussion, $forum, $course);
|
||||
$this->log_start("Adding messages in discussion {$discussion->id} (forum {$forum->id})", 1);
|
||||
|
||||
// Add all posts in this forum.
|
||||
foreach ($this->posts[$discussion->id] as $post) {
|
||||
$author = $this->get_post_author($post->userid, $course, $forum, $cm, $modcontext);
|
||||
if (empty($author)) {
|
||||
// Unable to find the author. Skip to avoid errors.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!forum_user_can_see_post($forum, $discussion, $post, $this->recipient, $cm)) {
|
||||
// User cannot see this post.
|
||||
// Permissions may have changed since it was queued.
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->add_post_body($author, $post, $discussion, $forum, $cm, $course);
|
||||
$discussionpostcount++;
|
||||
}
|
||||
|
||||
// Add the forum footer.
|
||||
$this->add_discussion_footer($discussion, $forum, $course);
|
||||
|
||||
// Add the data for this discussion to the notification body.
|
||||
if ($discussionpostcount) {
|
||||
$this->sentcount += $discussionpostcount;
|
||||
$this->notificationtext .= $this->discussiontext;
|
||||
$this->notificationhtml .= $this->discussionhtml;
|
||||
$this->log_finish("Added {$discussionpostcount} messages to discussion {$discussion->id}", 1);
|
||||
} else {
|
||||
$this->log_finish("No messages found in discussion {$discussion->id} - skipped.", 1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->sentcount) {
|
||||
// This digest has at least one post and should therefore be sent.
|
||||
if ($this->send_mail()) {
|
||||
$this->log_finish("Digest sent with {$this->sentcount} messages.");
|
||||
if (get_user_preferences('forum_markasreadonnotification', 1, $this->recipient->id) == 1) {
|
||||
forum_tp_mark_posts_read($this->recipient, $this->markpostsasread);
|
||||
}
|
||||
} else {
|
||||
$this->log_finish("Issue sending digest. Skipping.");
|
||||
}
|
||||
} else {
|
||||
$this->log_finish("No messages found to send.");
|
||||
}
|
||||
|
||||
// We have finishied all digest emails, update $CFG->digestmailtimelast.
|
||||
set_config('digestmailtimelast', $starttime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for this run.
|
||||
*
|
||||
* Note: This will also remove posts from the queue.
|
||||
*
|
||||
* @param int $timenow
|
||||
*/
|
||||
protected function prepare_data(int $timenow) {
|
||||
global $DB;
|
||||
|
||||
$sql = "SELECT p.*, f.id AS forum, f.course
|
||||
FROM {forum_queue} q
|
||||
INNER JOIN {forum_posts} p ON p.id = q.postid
|
||||
INNER JOIN {forum_discussions} d ON d.id = p.discussion
|
||||
INNER JOIN {forum} f ON f.id = d.forum
|
||||
WHERE q.userid = :userid
|
||||
AND q.timemodified < :timemodified
|
||||
ORDER BY d.id, q.timemodified ASC";
|
||||
|
||||
$queueparams = [
|
||||
'userid' => $this->recipient->id,
|
||||
'timemodified' => $timenow,
|
||||
];
|
||||
|
||||
$posts = $DB->get_recordset_sql($sql, $queueparams);
|
||||
$discussionids = [];
|
||||
$forumids = [];
|
||||
$courseids = [];
|
||||
$userids = [];
|
||||
foreach ($posts as $post) {
|
||||
$discussionids[] = $post->discussion;
|
||||
$forumids[] = $post->forum;
|
||||
$courseids[] = $post->course;
|
||||
$userids[] = $post->userid;
|
||||
unset($post->forum);
|
||||
if (!isset($this->posts[$post->discussion])) {
|
||||
$this->posts[$post->discussion] = [];
|
||||
}
|
||||
$this->posts[$post->discussion][$post->id] = $post;
|
||||
}
|
||||
$posts->close();
|
||||
|
||||
list($in, $params) = $DB->get_in_or_equal($discussionids);
|
||||
$this->discussions = $DB->get_records_select('forum_discussions', "id {$in}", $params);
|
||||
|
||||
list($in, $params) = $DB->get_in_or_equal($forumids);
|
||||
$this->forums = $DB->get_records_select('forum', "id {$in}", $params);
|
||||
|
||||
list($in, $params) = $DB->get_in_or_equal($courseids);
|
||||
$this->courses = $DB->get_records_select('course', "id $in", $params);
|
||||
|
||||
list($in, $params) = $DB->get_in_or_equal($userids);
|
||||
$this->users = $DB->get_records_select('user', "id $in", $params);
|
||||
|
||||
$this->fill_digest_cache();
|
||||
|
||||
$DB->delete_records_select('forum_queue', "userid = :userid AND timemodified < :timemodified", $queueparams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the cron digest cache.
|
||||
*/
|
||||
protected function fill_digest_cache() {
|
||||
global $DB;
|
||||
|
||||
$this->forumdigesttypes = $DB->get_records_menu('forum_digests', [
|
||||
'userid' => $this->recipient->id,
|
||||
], '', 'forum, maildigest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and initialise the post author.
|
||||
*
|
||||
* @param int $userid The id of the user to fetch
|
||||
* @param \stdClass $course
|
||||
* @param \stdClass $forum
|
||||
* @param \stdClass $cm
|
||||
* @param \context $context
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected function get_post_author($userid, $course, $forum, $cm, $context) {
|
||||
if (!isset($this->users[$userid])) {
|
||||
// This user no longer exists.
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = $this->users[$userid];
|
||||
|
||||
if (!isset($user->groups)) {
|
||||
// Initialise the groups list.
|
||||
$user->groups = [];
|
||||
}
|
||||
|
||||
if (!isset($user->groups[$forum->id])) {
|
||||
$user->groups[$forum->id] = groups_get_all_groups($course->id, $user->id, $cm->groupingid);
|
||||
}
|
||||
|
||||
// Clone the user object to prevent leaks between messages.
|
||||
return (object) (array) $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the header to this message.
|
||||
*/
|
||||
protected function add_message_header() {
|
||||
$site = get_site();
|
||||
|
||||
// Set the subject of the message.
|
||||
$this->postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
|
||||
|
||||
// And the content of the header in body.
|
||||
$headerdata = (object) [
|
||||
'sitename' => format_string($site->fullname, true),
|
||||
'userprefs' => (new \moodle_url('/user/forum.php', [
|
||||
'id' => $this->recipient->id,
|
||||
'course' => $site->id,
|
||||
]))->out(false),
|
||||
];
|
||||
|
||||
$this->notificationtext .= get_string('digestmailheader', 'forum', $headerdata) . "\n";
|
||||
|
||||
if ($this->allowhtml) {
|
||||
$headerdata->userprefs = html_writer::link($headerdata->userprefs, get_string('digestmailprefs', 'forum'), [
|
||||
'target' => '_blank',
|
||||
]);
|
||||
|
||||
$this->notificationhtml .= html_writer::tag('p', get_string('digestmailheader', 'forum', $headerdata));
|
||||
$this->notificationhtml .= html_writer::empty_tag('br');
|
||||
$this->notificationhtml .= html_writer::empty_tag('hr', [
|
||||
'size' => 1,
|
||||
'noshade' => 'noshade',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the header for this discussion.
|
||||
*
|
||||
* @param \stdClass $discussion The discussion to add the footer for
|
||||
* @param \stdClass $forum The forum that the discussion belongs to
|
||||
* @param \stdClass $course The course that the forum belongs to
|
||||
*/
|
||||
protected function add_discussion_header($discussion, $forum, $course) {
|
||||
global $CFG;
|
||||
|
||||
$shortname = format_string($course->shortname, true, [
|
||||
'context' => \context_course::instance($course->id),
|
||||
]);
|
||||
|
||||
$strforums = get_string('forums', 'forum');
|
||||
|
||||
$this->discussiontext .= "\n=====================================================================\n\n";
|
||||
$this->discussiontext .= "$shortname -> $strforums -> " . format_string($forum->name, true);
|
||||
if ($discussion->name != $forum->name) {
|
||||
$this->discussiontext .= " -> " . format_string($discussion->name, true);
|
||||
}
|
||||
$this->discussiontext .= "\n";
|
||||
$this->discussiontext .= new \moodle_url('/mod/forum/discuss.php', [
|
||||
'd' => $discussion->id,
|
||||
]);
|
||||
$this->discussiontext .= "\n";
|
||||
|
||||
if ($this->allowhtml) {
|
||||
$this->discussionhtml .= "<p><font face=\"sans-serif\">".
|
||||
"<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
|
||||
"<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
|
||||
"<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">" .
|
||||
format_string($forum->name, true)."</a>";
|
||||
if ($discussion->name == $forum->name) {
|
||||
$this->discussionhtml .= "</font></p>";
|
||||
} else {
|
||||
$this->discussionhtml .=
|
||||
" -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">" .
|
||||
format_string($discussion->name, true)."</a></font></p>";
|
||||
}
|
||||
$this->discussionhtml .= '<p>';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the body of this post.
|
||||
*
|
||||
* @param \stdClass $author The author of the post
|
||||
* @param \stdClass $post The post being sent
|
||||
* @param \stdClass $discussion The discussion that the post is in
|
||||
* @param \stdClass $forum The forum that the discussion belongs to
|
||||
* @param \cminfo $cm The cminfo object for the forum
|
||||
* @param \stdClass $course The course that the forum belongs to
|
||||
*/
|
||||
protected function add_post_body($author, $post, $discussion, $forum, $cm, $course) {
|
||||
global $CFG;
|
||||
|
||||
$canreply = $this->canpostto[$discussion->id];
|
||||
|
||||
$data = new \mod_forum\output\forum_post_email(
|
||||
$course,
|
||||
$cm,
|
||||
$forum,
|
||||
$discussion,
|
||||
$post,
|
||||
$author,
|
||||
$this->recipient,
|
||||
$canreply
|
||||
);
|
||||
|
||||
// Override the viewfullnames value.
|
||||
$data->viewfullnames = $this->viewfullnames[$forum->id];
|
||||
|
||||
// Determine the type of digest being sent.
|
||||
$maildigest = $this->get_maildigest($forum->id);
|
||||
|
||||
$textrenderer = $this->get_renderer($maildigest);
|
||||
$this->discussiontext .= $textrenderer->render($data);
|
||||
$this->discussiontext .= "\n";
|
||||
if ($this->allowhtml) {
|
||||
$htmlrenderer = $this->get_renderer($maildigest, true);
|
||||
$this->discussionhtml .= $htmlrenderer->render($data);
|
||||
$this->log("Adding post {$post->id} in format {$maildigest} with HTML", 2);
|
||||
} else {
|
||||
$this->log("Adding post {$post->id} in format {$maildigest} without HTML", 2);
|
||||
}
|
||||
|
||||
if ($maildigest == 1 && $CFG->forum_usermarksread) {
|
||||
// Create an array of postid's for this user to mark as read.
|
||||
$this->markpostsasread[] = $post->id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the footer for this discussion.
|
||||
*
|
||||
* @param \stdClass $discussion The discussion to add the footer for
|
||||
*/
|
||||
protected function add_discussion_footer($discussion) {
|
||||
global $CFG;
|
||||
|
||||
if ($this->allowhtml) {
|
||||
$footerlinks = [];
|
||||
|
||||
$forum = $this->forums[$discussion->forum];
|
||||
if (\mod_forum\subscriptions::is_forcesubscribed($forum)) {
|
||||
// This forum is force subscribed. The user cannot unsubscribe.
|
||||
$footerlinks[] = get_string("everyoneissubscribed", "forum");
|
||||
} else {
|
||||
$footerlinks[] = "<a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">" .
|
||||
get_string("unsubscribe", "forum") . "</a>";
|
||||
}
|
||||
$footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" .
|
||||
get_string("digestmailpost", "forum") . '</a>';
|
||||
|
||||
$this->discussionhtml .= "\n<div class='mdl-right'><font size=\"1\">" .
|
||||
implode(' ', $footerlinks) . '</font></div>';
|
||||
$this->discussionhtml .= '<hr size="1" noshade="noshade" /></p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum digest type for the specified forum, failing back to
|
||||
* the default setting for the current user if not specified.
|
||||
*
|
||||
* @param int $forumid
|
||||
* @return int
|
||||
*/
|
||||
protected function get_maildigest($forumid) {
|
||||
$maildigest = -1;
|
||||
|
||||
if (isset($this->forumdigesttypes[$forumid])) {
|
||||
$maildigest = $this->forumdigesttypes[$forumid];
|
||||
}
|
||||
|
||||
if ($maildigest === -1 && !empty($this->recipient->maildigest)) {
|
||||
$maildigest = $this->recipient->maildigest;
|
||||
}
|
||||
|
||||
if ($maildigest === -1) {
|
||||
// There is no maildigest type right now.
|
||||
$maildigest = 1;
|
||||
}
|
||||
|
||||
return $maildigest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the composed message to the user.
|
||||
*/
|
||||
protected function send_mail() {
|
||||
// Headers to help prevent auto-responders.
|
||||
$userfrom = \core_user::get_noreply_user();
|
||||
$userfrom->customheaders = array(
|
||||
"Precedence: Bulk",
|
||||
'X-Auto-Response-Suppress: All',
|
||||
'Auto-Submitted: auto-generated',
|
||||
);
|
||||
|
||||
$eventdata = new \core\message\message();
|
||||
$eventdata->courseid = SITEID;
|
||||
$eventdata->component = 'mod_forum';
|
||||
$eventdata->name = 'digests';
|
||||
$eventdata->userfrom = $userfrom;
|
||||
$eventdata->userto = $this->recipient;
|
||||
$eventdata->subject = $this->postsubject;
|
||||
$eventdata->fullmessage = $this->notificationtext;
|
||||
$eventdata->fullmessageformat = FORMAT_PLAIN;
|
||||
$eventdata->fullmessagehtml = $this->notificationhtml;
|
||||
$eventdata->notification = 1;
|
||||
$eventdata->smallmessage = get_string('smallmessagedigest', 'forum', $this->sentcount);
|
||||
|
||||
return message_send($eventdata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fetch the required renderer, instantiating as required.
|
||||
*
|
||||
* @param int $maildigest The type of mail digest being sent
|
||||
* @param bool $html Whether to fetch the HTML renderer
|
||||
* @return \core_renderer
|
||||
*/
|
||||
protected function get_renderer($maildigest, $html = false) {
|
||||
global $PAGE;
|
||||
|
||||
$type = $maildigest == 2 ? 'emaildigestbasic' : 'emaildigestfull';
|
||||
$target = $html ? 'htmlemail' : 'textemail';
|
||||
|
||||
if (!isset($this->renderers[$target][$type])) {
|
||||
$this->renderers[$target][$type] = $PAGE->get_renderer('mod_forum', $type, $target);
|
||||
}
|
||||
|
||||
return $this->renderers[$target][$type];
|
||||
}
|
||||
}
|
534
mod/forum/classes/task/send_user_notifications.php
Normal file
534
mod/forum/classes/task/send_user_notifications.php
Normal file
@ -0,0 +1,534 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This file defines an adhoc task to send notifications.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\task;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Adhoc task to send user forum notifications.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class send_user_notifications extends \core\task\adhoc_task {
|
||||
|
||||
// Use the logging trait to get some nice, juicy, logging.
|
||||
use \core\task\logging_trait;
|
||||
|
||||
/**
|
||||
* @var \stdClass A shortcut to $USER.
|
||||
*/
|
||||
protected $recipient;
|
||||
|
||||
/**
|
||||
* @var \stdClass[] List of courses the messages are in, indexed by courseid.
|
||||
*/
|
||||
protected $courses = [];
|
||||
|
||||
/**
|
||||
* @var \stdClass[] List of forums the messages are in, indexed by courseid.
|
||||
*/
|
||||
protected $forums = [];
|
||||
|
||||
/**
|
||||
* @var int[] List of IDs for forums in each course.
|
||||
*/
|
||||
protected $courseforums = [];
|
||||
|
||||
/**
|
||||
* @var \stdClass[] List of discussions the messages are in, indexed by forumid.
|
||||
*/
|
||||
protected $discussions = [];
|
||||
|
||||
/**
|
||||
* @var \stdClass[] List of IDs for discussions in each forum.
|
||||
*/
|
||||
protected $forumdiscussions = [];
|
||||
|
||||
/**
|
||||
* @var \stdClass[] List of posts the messages are in, indexed by discussionid.
|
||||
*/
|
||||
protected $posts = [];
|
||||
|
||||
/**
|
||||
* @var bool[] Whether the user can view fullnames for each forum.
|
||||
*/
|
||||
protected $viewfullnames = [];
|
||||
|
||||
/**
|
||||
* @var bool[] Whether the user can post in each discussion.
|
||||
*/
|
||||
protected $canpostto = [];
|
||||
|
||||
/**
|
||||
* @var \renderer[] The renderers.
|
||||
*/
|
||||
protected $renderers = [];
|
||||
|
||||
/**
|
||||
* @var \core\message\inbound\address_manager The inbound message address manager.
|
||||
*/
|
||||
protected $inboundmanager;
|
||||
|
||||
/**
|
||||
* Send out messages.
|
||||
*/
|
||||
public function execute() {
|
||||
global $CFG;
|
||||
|
||||
$this->recipient = \core_user::get_user($this->get_userid());
|
||||
|
||||
// Create the generic messageinboundgenerator.
|
||||
$this->inboundmanager = new \core\message\inbound\address_manager();
|
||||
$this->inboundmanager->set_handler('\mod_forum\message\inbound\reply_handler');
|
||||
|
||||
$data = $this->get_custom_data();
|
||||
|
||||
$this->prepare_data((array) $data);
|
||||
|
||||
$markposts = [];
|
||||
$errorcount = 0;
|
||||
$sentcount = 0;
|
||||
$this->log_start("Sending messages to {$this->recipient->username} ({$this->recipient->id})");
|
||||
foreach ($this->courses as $course) {
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
|
||||
// The course is hidden and the user does not have access to it.
|
||||
// Permissions may have changed since it was queued.
|
||||
continue;
|
||||
}
|
||||
foreach ($this->courseforums[$course->id] as $forumid) {
|
||||
$forum = $this->forums[$forumid];
|
||||
|
||||
$cm = get_fast_modinfo($course)->instances['forum'][$forumid];
|
||||
$modcontext = \context_module::instance($cm->id);
|
||||
|
||||
foreach (array_values($this->forumdiscussions[$forumid]) as $discussionid) {
|
||||
$discussion = $this->discussions[$discussionid];
|
||||
|
||||
if (!forum_user_can_see_discussion($forum, $discussion, $modcontext, $this->recipient)) {
|
||||
// User cannot see this discussion.
|
||||
// Permissions may have changed since it was queued.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\mod_forum\subscriptions::is_subscribed($this->recipient->id, $forum, $discussionid, $cm)) {
|
||||
// The user does not subscribe to this forum as a whole, or to this specific discussion.
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->posts[$discussionid] as $post) {
|
||||
if (!forum_user_can_see_post($forum, $discussion, $post, $this->recipient, $cm)) {
|
||||
// User cannot see this post.
|
||||
// Permissions may have changed since it was queued.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->send_post($course, $forum, $discussion, $post, $cm, $modcontext)) {
|
||||
$this->log("Post {$post->id} sent", 1);
|
||||
// Mark post as read if forum_usermarksread is set off.
|
||||
if (!$CFG->forum_usermarksread) {
|
||||
$markposts[$post->id] = true;
|
||||
}
|
||||
$sentcount++;
|
||||
} else {
|
||||
$this->log("Failed to send post {$post->id}", 1);
|
||||
$errorcount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->log_finish("Sent {$sentcount} messages with {$errorcount} failures");
|
||||
if (!empty($markposts)) {
|
||||
if (get_user_preferences('forum_markasreadonnotification', 1, $this->recipient->id) == 1) {
|
||||
$this->log_start("Marking posts as read");
|
||||
$count = count($markposts);
|
||||
forum_tp_mark_posts_read($this->recipient, $markposts);
|
||||
$this->log_finish("Marked {$count} posts as read");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare all data for this run.
|
||||
*
|
||||
* Take all post ids, and fetch the relevant authors, discussions, forums, and courses for them.
|
||||
*
|
||||
* @param int[] $postids The list of post IDs
|
||||
*/
|
||||
protected function prepare_data(array $postids) {
|
||||
global $DB;
|
||||
|
||||
if (empty($postids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
list($in, $params) = $DB->get_in_or_equal(array_values($postids));
|
||||
$sql = "SELECT p.*, f.id AS forum, f.course
|
||||
FROM {forum_posts} p
|
||||
INNER JOIN {forum_discussions} d ON d.id = p.discussion
|
||||
INNER JOIN {forum} f ON f.id = d.forum
|
||||
WHERE p.id {$in}";
|
||||
|
||||
$posts = $DB->get_recordset_sql($sql, $params);
|
||||
$discussionids = [];
|
||||
$forumids = [];
|
||||
$courseids = [];
|
||||
$userids = [];
|
||||
foreach ($posts as $post) {
|
||||
$discussionids[] = $post->discussion;
|
||||
$forumids[] = $post->forum;
|
||||
$courseids[] = $post->course;
|
||||
$userids[] = $post->userid;
|
||||
unset($post->forum);
|
||||
if (!isset($this->posts[$post->discussion])) {
|
||||
$this->posts[$post->discussion] = [];
|
||||
}
|
||||
$this->posts[$post->discussion][$post->id] = $post;
|
||||
}
|
||||
$posts->close();
|
||||
|
||||
// Fetch all discussions.
|
||||
list($in, $params) = $DB->get_in_or_equal(array_values($discussionids));
|
||||
$this->discussions = $DB->get_records_select('forum_discussions', "id {$in}", $params);
|
||||
foreach ($this->discussions as $discussion) {
|
||||
if (empty($this->forumdiscussions[$discussion->forum])) {
|
||||
$this->forumdiscussions[$discussion->forum] = [];
|
||||
}
|
||||
$this->forumdiscussions[$discussion->forum][] = $discussion->id;
|
||||
}
|
||||
|
||||
// Fetch all forums.
|
||||
list($in, $params) = $DB->get_in_or_equal(array_values($forumids));
|
||||
$this->forums = $DB->get_records_select('forum', "id {$in}", $params);
|
||||
foreach ($this->forums as $forum) {
|
||||
if (empty($this->courseforums[$forum->course])) {
|
||||
$this->courseforums[$forum->course] = [];
|
||||
}
|
||||
$this->courseforums[$forum->course][] = $forum->id;
|
||||
}
|
||||
|
||||
// Fetch all courses.
|
||||
list($in, $params) = $DB->get_in_or_equal(array_values($courseids));
|
||||
$this->courses = $DB->get_records_select('course', "id $in", $params);
|
||||
|
||||
// Fetch all authors.
|
||||
list($in, $params) = $DB->get_in_or_equal(array_values($userids));
|
||||
$users = $DB->get_recordset_select('user', "id $in", $params);
|
||||
foreach ($users as $user) {
|
||||
$this->minimise_user_record($user);
|
||||
$this->users[$user->id] = $user;
|
||||
}
|
||||
$users->close();
|
||||
|
||||
// Fill subscription caches for each forum.
|
||||
// These are per-user.
|
||||
foreach (array_values($forumids) as $id) {
|
||||
\mod_forum\subscriptions::fill_subscription_cache($id);
|
||||
\mod_forum\subscriptions::fill_discussion_subscription_cache($id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the specified post for the current user.
|
||||
*
|
||||
* @param \stdClass $course
|
||||
* @param \stdClass $forum
|
||||
* @param \stdClass $discussion
|
||||
* @param \stdClass $post
|
||||
* @param \stdClass $cm
|
||||
* @param \context $context
|
||||
*/
|
||||
protected function send_post($course, $forum, $discussion, $post, $cm, $context) {
|
||||
global $CFG;
|
||||
|
||||
$author = $this->get_post_author($post->userid, $course, $forum, $cm, $context);
|
||||
if (empty($author)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepare to actually send the post now, and build up the content.
|
||||
$cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
|
||||
|
||||
$shortname = format_string($course->shortname, true, [
|
||||
'context' => \context_course::instance($course->id),
|
||||
]);
|
||||
|
||||
// Generate a reply-to address from using the Inbound Message handler.
|
||||
$replyaddress = $this->get_reply_address($course, $forum, $discussion, $post, $cm, $context);
|
||||
|
||||
$data = new \mod_forum\output\forum_post_email(
|
||||
$course,
|
||||
$cm,
|
||||
$forum,
|
||||
$discussion,
|
||||
$post,
|
||||
$author,
|
||||
$this->recipient,
|
||||
$this->can_post($course, $forum, $discussion, $post, $cm, $context)
|
||||
);
|
||||
$data->viewfullnames = $this->can_view_fullnames($course, $forum, $discussion, $post, $cm, $context);
|
||||
|
||||
// Not all of these variables are used in the default string but are made available to support custom subjects.
|
||||
$site = get_site();
|
||||
$a = (object) [
|
||||
'subject' => $data->get_subject(),
|
||||
'forumname' => $cleanforumname,
|
||||
'sitefullname' => format_string($site->fullname),
|
||||
'siteshortname' => format_string($site->shortname),
|
||||
'courseidnumber' => $data->get_courseidnumber(),
|
||||
'coursefullname' => $data->get_coursefullname(),
|
||||
'courseshortname' => $data->get_coursename(),
|
||||
];
|
||||
$postsubject = html_to_text(get_string('postmailsubject', 'forum', $a), 0);
|
||||
|
||||
// Message headers are stored against the message author.
|
||||
$author->customheaders = $this->get_message_headers($course, $forum, $discussion, $post, $a, $data);
|
||||
|
||||
$eventdata = new \core\message\message();
|
||||
$eventdata->courseid = $course->id;
|
||||
$eventdata->component = 'mod_forum';
|
||||
$eventdata->name = 'posts';
|
||||
$eventdata->userfrom = $author;
|
||||
$eventdata->userto = $this->recipient;
|
||||
$eventdata->subject = $postsubject;
|
||||
$eventdata->fullmessage = $this->get_renderer()->render($data);
|
||||
$eventdata->fullmessageformat = FORMAT_PLAIN;
|
||||
$eventdata->fullmessagehtml = $this->get_renderer(true)->render($data);
|
||||
$eventdata->notification = 1;
|
||||
$eventdata->replyto = $replyaddress;
|
||||
if (!empty($replyaddress)) {
|
||||
// Add extra text to email messages if they can reply back.
|
||||
$eventdata->set_additional_content('email', [
|
||||
'fullmessage' => [
|
||||
'footer' => "\n\n" . get_string('replytopostbyemail', 'mod_forum'),
|
||||
],
|
||||
'fullmessagehtml' => [
|
||||
'footer' => \html_writer::tag('p', get_string('replytopostbyemail', 'mod_forum')),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
$eventdata->smallmessage = get_string('smallmessage', 'forum', (object) [
|
||||
'user' => fullname($author),
|
||||
'forumname' => "$shortname: " . format_string($forum->name, true) . ": " . $discussion->name,
|
||||
'message' => $post->message,
|
||||
]);
|
||||
|
||||
$contexturl = new \moodle_url('/mod/forum/discuss.php', ['d' => $discussion->id], "p{$post->id}");
|
||||
$eventdata->contexturl = $contexturl->out();
|
||||
$eventdata->contexturlname = $discussion->name;
|
||||
|
||||
return message_send($eventdata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and initialise the post author.
|
||||
*
|
||||
* @param int $userid The id of the user to fetch
|
||||
* @param \stdClass $course
|
||||
* @param \stdClass $forum
|
||||
* @param \stdClass $cm
|
||||
* @param \context $context
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected function get_post_author($userid, $course, $forum, $cm, $context) {
|
||||
if (!isset($this->users[$userid])) {
|
||||
// This user no longer exists.
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = $this->users[$userid];
|
||||
|
||||
if (!isset($user->groups)) {
|
||||
// Initialise the groups list.
|
||||
$user->groups = [];
|
||||
}
|
||||
|
||||
if (!isset($user->groups[$forum->id])) {
|
||||
$user->groups[$forum->id] = groups_get_all_groups($course->id, $user->id, $cm->groupingid);
|
||||
}
|
||||
|
||||
// Clone the user object to prevent leaks between messages.
|
||||
return (object) (array) $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fetch the required renderer, instantiating as required.
|
||||
*
|
||||
* @param bool $html Whether to fetch the HTML renderer
|
||||
* @return \core_renderer
|
||||
*/
|
||||
protected function get_renderer($html = false) {
|
||||
global $PAGE;
|
||||
|
||||
$target = $html ? 'htmlemail' : 'textemail';
|
||||
|
||||
if (!isset($this->renderers[$target])) {
|
||||
$this->renderers[$target] = $PAGE->get_renderer('mod_forum', 'email', $target);
|
||||
}
|
||||
|
||||
return $this->renderers[$target];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of message headers.
|
||||
*
|
||||
* @param \stdClass $course
|
||||
* @param \stdClass $forum
|
||||
* @param \stdClass $discussion
|
||||
* @param \stdClass $post
|
||||
* @param \stdClass $a The list of strings for this post
|
||||
* @param \core\message\message $message The message to be sent
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected function get_message_headers($course, $forum, $discussion, $post, $a, $message) {
|
||||
$cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
|
||||
$viewurl = new \moodle_url('/mod/forum/view.php', ['f' => $forum->id]);
|
||||
|
||||
$headers = [
|
||||
// Headers to make emails easier to track.
|
||||
'List-Id: "' . $cleanforumname . '" ' . generate_email_messageid('moodleforum' . $forum->id),
|
||||
'List-Help: ' . $viewurl->out(),
|
||||
'Message-ID: ' . forum_get_email_message_id($post->id, $this->recipient->id),
|
||||
'X-Course-Id: ' . $course->id,
|
||||
'X-Course-Name: '. format_string($course->fullname, true),
|
||||
|
||||
// Headers to help prevent auto-responders.
|
||||
'Precedence: Bulk',
|
||||
'X-Auto-Response-Suppress: All',
|
||||
'Auto-Submitted: auto-generated',
|
||||
'List-Unsubscribe: <' . $message->get_unsubscribediscussionlink() . '>',
|
||||
];
|
||||
|
||||
$rootid = forum_get_email_message_id($discussion->firstpost, $this->recipient->id);
|
||||
|
||||
if ($post->parent) {
|
||||
// This post is a reply, so add reply header (RFC 2822).
|
||||
$parentid = forum_get_email_message_id($post->parent, $this->recipient->id);
|
||||
$headers[] = "In-Reply-To: $parentid";
|
||||
|
||||
// If the post is deeply nested we also reference the parent message id and
|
||||
// the root message id (if different) to aid threading when parts of the email
|
||||
// conversation have been deleted (RFC1036).
|
||||
if ($post->parent != $discussion->firstpost) {
|
||||
$headers[] = "References: $rootid $parentid";
|
||||
} else {
|
||||
$headers[] = "References: $parentid";
|
||||
}
|
||||
}
|
||||
|
||||
// MS Outlook / Office uses poorly documented and non standard headers, including
|
||||
// Thread-Topic which overrides the Subject and shouldn't contain Re: or Fwd: etc.
|
||||
$aclone = (object) (array) $a;
|
||||
$aclone->subject = $discussion->name;
|
||||
$threadtopic = html_to_text(get_string('postmailsubject', 'forum', $aclone), 0);
|
||||
$headers[] = "Thread-Topic: $threadtopic";
|
||||
$headers[] = "Thread-Index: " . substr($rootid, 1, 28);
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a no-reply address for this user to reply to the current post.
|
||||
*
|
||||
* @param \stdClass $course
|
||||
* @param \stdClass $forum
|
||||
* @param \stdClass $discussion
|
||||
* @param \stdClass $post
|
||||
* @param \stdClass $cm
|
||||
* @param \context $context
|
||||
* @return string
|
||||
*/
|
||||
protected function get_reply_address($course, $forum, $discussion, $post, $cm, $context) {
|
||||
if ($this->can_post($course, $forum, $discussion, $post, $cm, $context)) {
|
||||
// Generate a reply-to address from using the Inbound Message handler.
|
||||
$this->inboundmanager->set_data($post->id);
|
||||
return $this->inboundmanager->generate($this->recipient->id);
|
||||
}
|
||||
|
||||
// TODO Check if we can return a string.
|
||||
// This will be controlled by the event.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the user can post.
|
||||
*
|
||||
* @param \stdClass $course
|
||||
* @param \stdClass $forum
|
||||
* @param \stdClass $discussion
|
||||
* @param \stdClass $post
|
||||
* @param \stdClass $cm
|
||||
* @param \context $context
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_post($course, $forum, $discussion, $post, $cm, $context) {
|
||||
if (!isset($this->canpostto[$discussion->id])) {
|
||||
$this->canpostto[$discussion->id] = forum_user_can_post($forum, $discussion, $this->recipient, $cm, $course, $context);
|
||||
}
|
||||
return $this->canpostto[$discussion->id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the user can view full names of other users.
|
||||
*
|
||||
* @param \stdClass $course
|
||||
* @param \stdClass $forum
|
||||
* @param \stdClass $discussion
|
||||
* @param \stdClass $post
|
||||
* @param \stdClass $cm
|
||||
* @param \context $context
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_view_fullnames($course, $forum, $discussion, $post, $cm, $context) {
|
||||
if (!isset($this->viewfullnames[$forum->id])) {
|
||||
$this->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $context, $this->recipient->id);
|
||||
}
|
||||
|
||||
return $this->viewfullnames[$forum->id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes properties from user record that are not necessary for sending post notifications.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
*/
|
||||
protected function minimise_user_record(\stdClass $user) {
|
||||
// We store large amount of users in one huge array, make sure we do not store info there we do not actually
|
||||
// need in mail generation code or messaging.
|
||||
unset($user->institution);
|
||||
unset($user->department);
|
||||
unset($user->address);
|
||||
unset($user->city);
|
||||
unset($user->url);
|
||||
unset($user->currentlogin);
|
||||
unset($user->description);
|
||||
unset($user->descriptionformat);
|
||||
}
|
||||
}
|
@ -372,3 +372,41 @@ function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfro
|
||||
|
||||
return $renderer->render($renderable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes properties from user record that are not necessary for sending post notifications.
|
||||
*
|
||||
* @param stdClass $user
|
||||
* @return void, $user parameter is modified
|
||||
* @deprecated since Moodle 3.7
|
||||
*/
|
||||
function forum_cron_minimise_user_record(stdClass $user) {
|
||||
debugging("forum_cron_minimise_user_record() has been deprecated and has not been replaced.",
|
||||
DEBUG_DEVELOPER);
|
||||
|
||||
// We store large amount of users in one huge array,
|
||||
// make sure we do not store info there we do not actually need
|
||||
// in mail generation code or messaging.
|
||||
|
||||
unset($user->institution);
|
||||
unset($user->department);
|
||||
unset($user->address);
|
||||
unset($user->city);
|
||||
unset($user->url);
|
||||
unset($user->currentlogin);
|
||||
unset($user->description);
|
||||
unset($user->descriptionformat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to be run periodically according to the scheduled task.
|
||||
*
|
||||
* Finds all posts that have yet to be mailed out, and mails them out to all subscribers as well as other maintance
|
||||
* tasks.
|
||||
*
|
||||
* @deprecated since Moodle 3.7
|
||||
*/
|
||||
function forum_cron() {
|
||||
debugging("forum_cron() has been deprecated and replaced with new tasks. Please uses these instead.",
|
||||
DEBUG_DEVELOPER);
|
||||
}
|
||||
|
@ -423,829 +423,6 @@ function forum_get_email_message_id($postid, $usertoid) {
|
||||
return generate_email_messageid(hash('sha256', $postid . 'to' . $usertoid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes properties from user record that are not necessary
|
||||
* for sending post notifications.
|
||||
* @param stdClass $user
|
||||
* @return void, $user parameter is modified
|
||||
*/
|
||||
function forum_cron_minimise_user_record(stdClass $user) {
|
||||
|
||||
// We store large amount of users in one huge array,
|
||||
// make sure we do not store info there we do not actually need
|
||||
// in mail generation code or messaging.
|
||||
|
||||
unset($user->institution);
|
||||
unset($user->department);
|
||||
unset($user->address);
|
||||
unset($user->city);
|
||||
unset($user->url);
|
||||
unset($user->currentlogin);
|
||||
unset($user->description);
|
||||
unset($user->descriptionformat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to be run periodically according to the scheduled task.
|
||||
*
|
||||
* Finds all posts that have yet to be mailed out, and mails them
|
||||
* out to all subscribers as well as other maintance tasks.
|
||||
*
|
||||
* NOTE: Since 2.7.2 this function is run by scheduled task rather
|
||||
* than standard cron.
|
||||
*
|
||||
* @todo MDL-44734 The function will be split up into seperate tasks.
|
||||
*/
|
||||
function forum_cron() {
|
||||
global $CFG, $USER, $DB, $PAGE;
|
||||
|
||||
$site = get_site();
|
||||
|
||||
// The main renderers.
|
||||
$htmlout = $PAGE->get_renderer('mod_forum', 'email', 'htmlemail');
|
||||
$textout = $PAGE->get_renderer('mod_forum', 'email', 'textemail');
|
||||
$htmldigestfullout = $PAGE->get_renderer('mod_forum', 'emaildigestfull', 'htmlemail');
|
||||
$textdigestfullout = $PAGE->get_renderer('mod_forum', 'emaildigestfull', 'textemail');
|
||||
$htmldigestbasicout = $PAGE->get_renderer('mod_forum', 'emaildigestbasic', 'htmlemail');
|
||||
$textdigestbasicout = $PAGE->get_renderer('mod_forum', 'emaildigestbasic', 'textemail');
|
||||
|
||||
// All users that are subscribed to any post that needs sending,
|
||||
// please increase $CFG->extramemorylimit on large sites that
|
||||
// send notifications to a large number of users.
|
||||
$users = array();
|
||||
$userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
|
||||
|
||||
// Status arrays.
|
||||
$mailcount = array();
|
||||
$errorcount = array();
|
||||
|
||||
// caches
|
||||
$discussions = array();
|
||||
$forums = array();
|
||||
$courses = array();
|
||||
$coursemodules = array();
|
||||
$subscribedusers = array();
|
||||
$messageinboundhandlers = array();
|
||||
|
||||
// Posts older than 2 days will not be mailed. This is to avoid the problem where
|
||||
// cron has not been running for a long time, and then suddenly people are flooded
|
||||
// with mail from the past few weeks or months
|
||||
$timenow = time();
|
||||
$endtime = $timenow - $CFG->maxeditingtime;
|
||||
$starttime = $endtime - 48 * 3600; // Two days earlier
|
||||
|
||||
// Get the list of forum subscriptions for per-user per-forum maildigest settings.
|
||||
$digestsset = $DB->get_recordset('forum_digests', null, '', 'id, userid, forum, maildigest');
|
||||
$digests = array();
|
||||
foreach ($digestsset as $thisrow) {
|
||||
if (!isset($digests[$thisrow->forum])) {
|
||||
$digests[$thisrow->forum] = array();
|
||||
}
|
||||
$digests[$thisrow->forum][$thisrow->userid] = $thisrow->maildigest;
|
||||
}
|
||||
$digestsset->close();
|
||||
|
||||
// Create the generic messageinboundgenerator.
|
||||
$messageinboundgenerator = new \core\message\inbound\address_manager();
|
||||
$messageinboundgenerator->set_handler('\mod_forum\message\inbound\reply_handler');
|
||||
|
||||
if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
|
||||
// Mark them all now as being mailed. It's unlikely but possible there
|
||||
// might be an error later so that a post is NOT actually mailed out,
|
||||
// but since mail isn't crucial, we can accept this risk. Doing it now
|
||||
// prevents the risk of duplicated mails, which is a worse problem.
|
||||
|
||||
if (!forum_mark_old_posts_as_mailed($endtime)) {
|
||||
mtrace('Errors occurred while trying to mark some posts as being mailed.');
|
||||
return false; // Don't continue trying to mail them, in case we are in a cron loop
|
||||
}
|
||||
|
||||
// checking post validity, and adding users to loop through later
|
||||
foreach ($posts as $pid => $post) {
|
||||
|
||||
$discussionid = $post->discussion;
|
||||
if (!isset($discussions[$discussionid])) {
|
||||
if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
|
||||
$discussions[$discussionid] = $discussion;
|
||||
\mod_forum\subscriptions::fill_subscription_cache($discussion->forum);
|
||||
\mod_forum\subscriptions::fill_discussion_subscription_cache($discussion->forum);
|
||||
|
||||
} else {
|
||||
mtrace('Could not find discussion ' . $discussionid);
|
||||
unset($posts[$pid]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$forumid = $discussions[$discussionid]->forum;
|
||||
if (!isset($forums[$forumid])) {
|
||||
if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
|
||||
$forums[$forumid] = $forum;
|
||||
} else {
|
||||
mtrace('Could not find forum '.$forumid);
|
||||
unset($posts[$pid]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$courseid = $forums[$forumid]->course;
|
||||
if (!isset($courses[$courseid])) {
|
||||
if ($course = $DB->get_record('course', array('id' => $courseid))) {
|
||||
$courses[$courseid] = $course;
|
||||
} else {
|
||||
mtrace('Could not find course '.$courseid);
|
||||
unset($posts[$pid]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!isset($coursemodules[$forumid])) {
|
||||
if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
|
||||
$coursemodules[$forumid] = $cm;
|
||||
} else {
|
||||
mtrace('Could not find course module for forum '.$forumid);
|
||||
unset($posts[$pid]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$modcontext = context_module::instance($coursemodules[$forumid]->id);
|
||||
|
||||
// Save the Inbound Message datakey here to reduce DB queries later.
|
||||
$messageinboundgenerator->set_data($pid);
|
||||
$messageinboundhandlers[$pid] = $messageinboundgenerator->fetch_data_key();
|
||||
|
||||
// Caching subscribed users of each forum.
|
||||
if (!isset($subscribedusers[$forumid])) {
|
||||
if ($subusers = \mod_forum\subscriptions::fetch_subscribed_users($forums[$forumid], 0, $modcontext, 'u.*', true)) {
|
||||
|
||||
foreach ($subusers as $postuser) {
|
||||
// this user is subscribed to this forum
|
||||
$subscribedusers[$forumid][$postuser->id] = $postuser->id;
|
||||
$userscount++;
|
||||
if ($userscount > FORUM_CRON_USER_CACHE) {
|
||||
// Store minimal user info.
|
||||
$minuser = new stdClass();
|
||||
$minuser->id = $postuser->id;
|
||||
$users[$postuser->id] = $minuser;
|
||||
} else {
|
||||
// Cache full user record.
|
||||
forum_cron_minimise_user_record($postuser);
|
||||
$users[$postuser->id] = $postuser;
|
||||
}
|
||||
}
|
||||
// Release memory.
|
||||
unset($subusers);
|
||||
unset($postuser);
|
||||
}
|
||||
}
|
||||
$mailcount[$pid] = 0;
|
||||
$errorcount[$pid] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($users && $posts) {
|
||||
|
||||
foreach ($users as $userto) {
|
||||
// Terminate if processing of any account takes longer than 2 minutes.
|
||||
core_php_time_limit::raise(120);
|
||||
|
||||
mtrace('Processing user ' . $userto->id);
|
||||
|
||||
// Init user caches - we keep the cache for one cycle only, otherwise it could consume too much memory.
|
||||
if (isset($userto->username)) {
|
||||
$userto = clone($userto);
|
||||
} else {
|
||||
$userto = $DB->get_record('user', array('id' => $userto->id));
|
||||
forum_cron_minimise_user_record($userto);
|
||||
}
|
||||
$userto->viewfullnames = array();
|
||||
$userto->canpost = array();
|
||||
$userto->markposts = array();
|
||||
|
||||
// Setup this user so that the capabilities are cached, and environment matches receiving user.
|
||||
cron_setup_user($userto);
|
||||
|
||||
// Reset the caches.
|
||||
foreach ($coursemodules as $forumid => $unused) {
|
||||
$coursemodules[$forumid]->cache = new stdClass();
|
||||
$coursemodules[$forumid]->cache->caps = array();
|
||||
unset($coursemodules[$forumid]->uservisible);
|
||||
}
|
||||
|
||||
foreach ($posts as $pid => $post) {
|
||||
$discussion = $discussions[$post->discussion];
|
||||
$forum = $forums[$discussion->forum];
|
||||
$course = $courses[$forum->course];
|
||||
$cm =& $coursemodules[$forum->id];
|
||||
|
||||
// Do some checks to see if we can bail out now.
|
||||
|
||||
// Only active enrolled users are in the list of subscribers.
|
||||
// This does not necessarily mean that the user is subscribed to the forum or to the discussion though.
|
||||
if (!isset($subscribedusers[$forum->id][$userto->id])) {
|
||||
// The user does not subscribe to this forum.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\mod_forum\subscriptions::is_subscribed($userto->id, $forum, $post->discussion, $coursemodules[$forum->id])) {
|
||||
// The user does not subscribe to this forum, or to this specific discussion.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($subscriptiontime = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $userto->id)) {
|
||||
// Skip posts if the user subscribed to the discussion after it was created.
|
||||
if (isset($subscriptiontime[$post->discussion]) && ($subscriptiontime[$post->discussion] > $post->created)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext, $userto->id)) {
|
||||
// The course is hidden and the user does not have access to it.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't send email if the forum is Q&A and the user has not posted.
|
||||
// Initial topics are still mailed.
|
||||
if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
|
||||
mtrace('Did not email ' . $userto->id.' because user has not posted in discussion');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get info about the sending user.
|
||||
if (array_key_exists($post->userid, $users)) {
|
||||
// We might know the user already.
|
||||
$userfrom = $users[$post->userid];
|
||||
if (!isset($userfrom->idnumber)) {
|
||||
// Minimalised user info, fetch full record.
|
||||
$userfrom = $DB->get_record('user', array('id' => $userfrom->id));
|
||||
forum_cron_minimise_user_record($userfrom);
|
||||
}
|
||||
|
||||
} else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
|
||||
forum_cron_minimise_user_record($userfrom);
|
||||
// Fetch only once if possible, we can add it to user list, it will be skipped anyway.
|
||||
if ($userscount <= FORUM_CRON_USER_CACHE) {
|
||||
$userscount++;
|
||||
$users[$userfrom->id] = $userfrom;
|
||||
}
|
||||
} else {
|
||||
mtrace('Could not find user ' . $post->userid . ', author of post ' . $post->id . '. Unable to send message.');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Note: If we want to check that userto and userfrom are not the same person this is probably the spot to do it.
|
||||
|
||||
// Setup global $COURSE properly - needed for roles and languages.
|
||||
cron_setup_user($userto, $course);
|
||||
|
||||
// Fill caches.
|
||||
if (!isset($userto->viewfullnames[$forum->id])) {
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
$userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
|
||||
}
|
||||
if (!isset($userto->canpost[$discussion->id])) {
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
$userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
|
||||
}
|
||||
if (!isset($userfrom->groups[$forum->id])) {
|
||||
if (!isset($userfrom->groups)) {
|
||||
$userfrom->groups = array();
|
||||
if (isset($users[$userfrom->id])) {
|
||||
$users[$userfrom->id]->groups = array();
|
||||
}
|
||||
}
|
||||
$userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
|
||||
if (isset($users[$userfrom->id])) {
|
||||
$users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure groups allow this user to see this email.
|
||||
if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {
|
||||
// Groups are being used.
|
||||
if (!groups_group_exists($discussion->groupid)) {
|
||||
// Can't find group - be safe and don't this message.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
|
||||
// Do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we're allowed to see the post.
|
||||
if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
|
||||
mtrace('User ' . $userto->id .' can not see ' . $post->id . '. Not sending message.');
|
||||
continue;
|
||||
}
|
||||
|
||||
// OK so we need to send the email.
|
||||
|
||||
// Does the user want this post in a digest? If so postpone it for now.
|
||||
$maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
|
||||
|
||||
if ($maildigest > 0) {
|
||||
// This user wants the mails to be in digest form.
|
||||
$queue = new stdClass();
|
||||
$queue->userid = $userto->id;
|
||||
$queue->discussionid = $discussion->id;
|
||||
$queue->postid = $post->id;
|
||||
$queue->timemodified = $post->created;
|
||||
$DB->insert_record('forum_queue', $queue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prepare to actually send the post now, and build up the content.
|
||||
|
||||
$cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
|
||||
|
||||
$userfrom->customheaders = array (
|
||||
// Headers to make emails easier to track.
|
||||
'List-Id: "' . $cleanforumname . '" ' . generate_email_messageid('moodleforum' . $forum->id),
|
||||
'List-Help: ' . $CFG->wwwroot . '/mod/forum/view.php?f=' . $forum->id,
|
||||
'Message-ID: ' . forum_get_email_message_id($post->id, $userto->id),
|
||||
'X-Course-Id: ' . $course->id,
|
||||
'X-Course-Name: ' . format_string($course->fullname, true),
|
||||
|
||||
// Headers to help prevent auto-responders.
|
||||
'Precedence: Bulk',
|
||||
'X-Auto-Response-Suppress: All',
|
||||
'Auto-Submitted: auto-generated',
|
||||
);
|
||||
|
||||
$shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
|
||||
|
||||
// Generate a reply-to address from using the Inbound Message handler.
|
||||
$replyaddress = null;
|
||||
if ($userto->canpost[$discussion->id] && array_key_exists($post->id, $messageinboundhandlers)) {
|
||||
$messageinboundgenerator->set_data($post->id, $messageinboundhandlers[$post->id]);
|
||||
$replyaddress = $messageinboundgenerator->generate($userto->id);
|
||||
}
|
||||
|
||||
if (!isset($userto->canpost[$discussion->id])) {
|
||||
$canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
|
||||
} else {
|
||||
$canreply = $userto->canpost[$discussion->id];
|
||||
}
|
||||
|
||||
$data = new \mod_forum\output\forum_post_email(
|
||||
$course,
|
||||
$cm,
|
||||
$forum,
|
||||
$discussion,
|
||||
$post,
|
||||
$userfrom,
|
||||
$userto,
|
||||
$canreply
|
||||
);
|
||||
|
||||
$userfrom->customheaders[] = sprintf('List-Unsubscribe: <%s>',
|
||||
$data->get_unsubscribediscussionlink());
|
||||
|
||||
if (!isset($userto->viewfullnames[$forum->id])) {
|
||||
$data->viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
|
||||
} else {
|
||||
$data->viewfullnames = $userto->viewfullnames[$forum->id];
|
||||
}
|
||||
|
||||
// Not all of these variables are used in the default language
|
||||
// string but are made available to support custom subjects.
|
||||
$a = new stdClass();
|
||||
$a->subject = $data->get_subject();
|
||||
$a->forumname = $cleanforumname;
|
||||
$a->sitefullname = format_string($site->fullname);
|
||||
$a->siteshortname = format_string($site->shortname);
|
||||
$a->courseidnumber = $data->get_courseidnumber();
|
||||
$a->coursefullname = $data->get_coursefullname();
|
||||
$a->courseshortname = $data->get_coursename();
|
||||
$postsubject = html_to_text(get_string('postmailsubject', 'forum', $a), 0);
|
||||
|
||||
$rootid = forum_get_email_message_id($discussion->firstpost, $userto->id);
|
||||
|
||||
if ($post->parent) {
|
||||
// This post is a reply, so add reply header (RFC 2822).
|
||||
$parentid = forum_get_email_message_id($post->parent, $userto->id);
|
||||
$userfrom->customheaders[] = "In-Reply-To: $parentid";
|
||||
|
||||
// If the post is deeply nested we also reference the parent message id and
|
||||
// the root message id (if different) to aid threading when parts of the email
|
||||
// conversation have been deleted (RFC1036).
|
||||
if ($post->parent != $discussion->firstpost) {
|
||||
$userfrom->customheaders[] = "References: $rootid $parentid";
|
||||
} else {
|
||||
$userfrom->customheaders[] = "References: $parentid";
|
||||
}
|
||||
}
|
||||
|
||||
// MS Outlook / Office uses poorly documented and non standard headers, including
|
||||
// Thread-Topic which overrides the Subject and shouldn't contain Re: or Fwd: etc.
|
||||
$a->subject = $discussion->name;
|
||||
$threadtopic = html_to_text(get_string('postmailsubject', 'forum', $a), 0);
|
||||
$userfrom->customheaders[] = "Thread-Topic: $threadtopic";
|
||||
$userfrom->customheaders[] = "Thread-Index: " . substr($rootid, 1, 28);
|
||||
|
||||
// Send the post now!
|
||||
mtrace('Sending ', '');
|
||||
|
||||
$eventdata = new \core\message\message();
|
||||
$eventdata->courseid = $course->id;
|
||||
$eventdata->component = 'mod_forum';
|
||||
$eventdata->name = 'posts';
|
||||
$eventdata->userfrom = $userfrom;
|
||||
$eventdata->userto = $userto;
|
||||
$eventdata->subject = $postsubject;
|
||||
$eventdata->fullmessage = $textout->render($data);
|
||||
$eventdata->fullmessageformat = FORMAT_PLAIN;
|
||||
$eventdata->fullmessagehtml = $htmlout->render($data);
|
||||
$eventdata->notification = 1;
|
||||
$eventdata->replyto = $replyaddress;
|
||||
if (!empty($replyaddress)) {
|
||||
// Add extra text to email messages if they can reply back.
|
||||
$textfooter = "\n\n" . get_string('replytopostbyemail', 'mod_forum');
|
||||
$htmlfooter = html_writer::tag('p', get_string('replytopostbyemail', 'mod_forum'));
|
||||
$additionalcontent = array('fullmessage' => array('footer' => $textfooter),
|
||||
'fullmessagehtml' => array('footer' => $htmlfooter));
|
||||
$eventdata->set_additional_content('email', $additionalcontent);
|
||||
}
|
||||
|
||||
$smallmessagestrings = new stdClass();
|
||||
$smallmessagestrings->user = fullname($userfrom);
|
||||
$smallmessagestrings->forumname = "$shortname: " . format_string($forum->name, true) . ": " . $discussion->name;
|
||||
$smallmessagestrings->message = $post->message;
|
||||
|
||||
// Make sure strings are in message recipients language.
|
||||
$eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
|
||||
|
||||
$contexturl = new moodle_url('/mod/forum/discuss.php', array('d' => $discussion->id), 'p' . $post->id);
|
||||
$eventdata->contexturl = $contexturl->out();
|
||||
$eventdata->contexturlname = $discussion->name;
|
||||
|
||||
$mailresult = message_send($eventdata);
|
||||
if (!$mailresult) {
|
||||
mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
|
||||
" ($userto->email) .. not trying again.");
|
||||
$errorcount[$post->id]++;
|
||||
} else {
|
||||
$mailcount[$post->id]++;
|
||||
|
||||
// Mark post as read if forum_usermarksread is set off.
|
||||
if (!$CFG->forum_usermarksread) {
|
||||
$userto->markposts[$post->id] = $post->id;
|
||||
}
|
||||
}
|
||||
|
||||
mtrace('post ' . $post->id . ': ' . $post->subject);
|
||||
}
|
||||
|
||||
// Mark processed posts as read.
|
||||
if (get_user_preferences('forum_markasreadonnotification', 1, $userto->id) == 1) {
|
||||
forum_tp_mark_posts_read($userto, $userto->markposts);
|
||||
}
|
||||
|
||||
unset($userto);
|
||||
}
|
||||
}
|
||||
|
||||
if ($posts) {
|
||||
foreach ($posts as $post) {
|
||||
mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
|
||||
if ($errorcount[$post->id]) {
|
||||
$DB->set_field('forum_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// release some memory
|
||||
unset($subscribedusers);
|
||||
unset($mailcount);
|
||||
unset($errorcount);
|
||||
|
||||
cron_setup_user();
|
||||
|
||||
$sitetimezone = core_date::get_server_timezone();
|
||||
|
||||
// Now see if there are any digest mails waiting to be sent, and if we should send them
|
||||
|
||||
mtrace('Starting digest processing...');
|
||||
|
||||
core_php_time_limit::raise(300); // terminate if not able to fetch all digests in 5 minutes
|
||||
|
||||
if (!isset($CFG->digestmailtimelast)) { // To catch the first time
|
||||
set_config('digestmailtimelast', 0);
|
||||
}
|
||||
|
||||
$timenow = time();
|
||||
$digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
|
||||
|
||||
// Delete any really old ones (normally there shouldn't be any)
|
||||
$weekago = $timenow - (7 * 24 * 3600);
|
||||
$DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
|
||||
mtrace ('Cleaned old digest records');
|
||||
|
||||
if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
|
||||
|
||||
mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
|
||||
|
||||
$digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
|
||||
|
||||
if ($digestposts_rs->valid()) {
|
||||
|
||||
// We have work to do
|
||||
$usermailcount = 0;
|
||||
|
||||
//caches - reuse the those filled before too
|
||||
$discussionposts = array();
|
||||
$userdiscussions = array();
|
||||
|
||||
foreach ($digestposts_rs as $digestpost) {
|
||||
if (!isset($posts[$digestpost->postid])) {
|
||||
if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
|
||||
$posts[$digestpost->postid] = $post;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$discussionid = $digestpost->discussionid;
|
||||
if (!isset($discussions[$discussionid])) {
|
||||
if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
|
||||
$discussions[$discussionid] = $discussion;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$forumid = $discussions[$discussionid]->forum;
|
||||
if (!isset($forums[$forumid])) {
|
||||
if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
|
||||
$forums[$forumid] = $forum;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$courseid = $forums[$forumid]->course;
|
||||
if (!isset($courses[$courseid])) {
|
||||
if ($course = $DB->get_record('course', array('id' => $courseid))) {
|
||||
$courses[$courseid] = $course;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($coursemodules[$forumid])) {
|
||||
if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
|
||||
$coursemodules[$forumid] = $cm;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
|
||||
$discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
|
||||
}
|
||||
$digestposts_rs->close(); /// Finished iteration, let's close the resultset
|
||||
|
||||
// Data collected, start sending out emails to each user
|
||||
foreach ($userdiscussions as $userid => $thesediscussions) {
|
||||
|
||||
core_php_time_limit::raise(120); // terminate if processing of any account takes longer than 2 minutes
|
||||
|
||||
cron_setup_user();
|
||||
|
||||
mtrace(get_string('processingdigest', 'forum', $userid), '... ');
|
||||
|
||||
// First of all delete all the queue entries for this user
|
||||
$DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
|
||||
|
||||
// Init user caches - we keep the cache for one cycle only,
|
||||
// otherwise it would unnecessarily consume memory.
|
||||
if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
|
||||
$userto = clone($users[$userid]);
|
||||
} else {
|
||||
$userto = $DB->get_record('user', array('id' => $userid));
|
||||
forum_cron_minimise_user_record($userto);
|
||||
}
|
||||
$userto->viewfullnames = array();
|
||||
$userto->canpost = array();
|
||||
$userto->markposts = array();
|
||||
|
||||
// Override the language and timezone of the "current" user, so that
|
||||
// mail is customised for the receiver.
|
||||
cron_setup_user($userto);
|
||||
|
||||
$postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
|
||||
|
||||
$headerdata = new stdClass();
|
||||
$headerdata->sitename = format_string($site->fullname, true);
|
||||
$headerdata->userprefs = $CFG->wwwroot.'/user/forum.php?id='.$userid.'&course='.$site->id;
|
||||
|
||||
$posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
|
||||
$headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
|
||||
|
||||
$posthtml = '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p>'
|
||||
. '<br /><hr size="1" noshade="noshade" />';
|
||||
|
||||
foreach ($thesediscussions as $discussionid) {
|
||||
|
||||
core_php_time_limit::raise(120); // to be reset for each post
|
||||
|
||||
$discussion = $discussions[$discussionid];
|
||||
$forum = $forums[$discussion->forum];
|
||||
$course = $courses[$forum->course];
|
||||
$cm = $coursemodules[$forum->id];
|
||||
|
||||
//override language
|
||||
cron_setup_user($userto, $course);
|
||||
|
||||
// Fill caches
|
||||
if (!isset($userto->viewfullnames[$forum->id])) {
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
$userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
|
||||
}
|
||||
if (!isset($userto->canpost[$discussion->id])) {
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
$userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
|
||||
}
|
||||
|
||||
$strforums = get_string('forums', 'forum');
|
||||
$canunsubscribe = ! \mod_forum\subscriptions::is_forcesubscribed($forum);
|
||||
$canreply = $userto->canpost[$discussion->id];
|
||||
$shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
|
||||
|
||||
$posttext .= "\n \n";
|
||||
$posttext .= '=====================================================================';
|
||||
$posttext .= "\n \n";
|
||||
$posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
|
||||
if ($discussion->name != $forum->name) {
|
||||
$posttext .= " -> ".format_string($discussion->name,true);
|
||||
}
|
||||
$posttext .= "\n";
|
||||
$posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
|
||||
$posttext .= "\n";
|
||||
|
||||
$posthtml .= "<p><font face=\"sans-serif\">".
|
||||
"<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
|
||||
"<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
|
||||
"<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
|
||||
if ($discussion->name == $forum->name) {
|
||||
$posthtml .= "</font></p>";
|
||||
} else {
|
||||
$posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
|
||||
}
|
||||
$posthtml .= '<p>';
|
||||
|
||||
$postsarray = $discussionposts[$discussionid];
|
||||
sort($postsarray);
|
||||
$sentcount = 0;
|
||||
|
||||
foreach ($postsarray as $postid) {
|
||||
$post = $posts[$postid];
|
||||
|
||||
if (array_key_exists($post->userid, $users)) { // we might know him/her already
|
||||
$userfrom = $users[$post->userid];
|
||||
if (!isset($userfrom->idnumber)) {
|
||||
$userfrom = $DB->get_record('user', array('id' => $userfrom->id));
|
||||
forum_cron_minimise_user_record($userfrom);
|
||||
}
|
||||
|
||||
} else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
|
||||
forum_cron_minimise_user_record($userfrom);
|
||||
if ($userscount <= FORUM_CRON_USER_CACHE) {
|
||||
$userscount++;
|
||||
$users[$userfrom->id] = $userfrom;
|
||||
}
|
||||
|
||||
} else {
|
||||
mtrace('Could not find user '.$post->userid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($userfrom->groups[$forum->id])) {
|
||||
if (!isset($userfrom->groups)) {
|
||||
$userfrom->groups = array();
|
||||
if (isset($users[$userfrom->id])) {
|
||||
$users[$userfrom->id]->groups = array();
|
||||
}
|
||||
}
|
||||
$userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
|
||||
if (isset($users[$userfrom->id])) {
|
||||
$users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
|
||||
}
|
||||
}
|
||||
|
||||
// Headers to help prevent auto-responders.
|
||||
$userfrom->customheaders = array(
|
||||
"Precedence: Bulk",
|
||||
'X-Auto-Response-Suppress: All',
|
||||
'Auto-Submitted: auto-generated',
|
||||
);
|
||||
|
||||
$maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
|
||||
if (!isset($userto->canpost[$discussion->id])) {
|
||||
$canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
|
||||
} else {
|
||||
$canreply = $userto->canpost[$discussion->id];
|
||||
}
|
||||
|
||||
$data = new \mod_forum\output\forum_post_email(
|
||||
$course,
|
||||
$cm,
|
||||
$forum,
|
||||
$discussion,
|
||||
$post,
|
||||
$userfrom,
|
||||
$userto,
|
||||
$canreply
|
||||
);
|
||||
|
||||
if (!isset($userto->viewfullnames[$forum->id])) {
|
||||
$data->viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
|
||||
} else {
|
||||
$data->viewfullnames = $userto->viewfullnames[$forum->id];
|
||||
}
|
||||
|
||||
if ($maildigest == 2) {
|
||||
// Subjects and link only.
|
||||
$posttext .= $textdigestbasicout->render($data);
|
||||
$posthtml .= $htmldigestbasicout->render($data);
|
||||
} else {
|
||||
// The full treatment.
|
||||
$posttext .= $textdigestfullout->render($data);
|
||||
$posthtml .= $htmldigestfullout->render($data);
|
||||
|
||||
// Create an array of postid's for this user to mark as read.
|
||||
if (!$CFG->forum_usermarksread) {
|
||||
$userto->markposts[$post->id] = $post->id;
|
||||
}
|
||||
}
|
||||
$sentcount++;
|
||||
}
|
||||
$footerlinks = array();
|
||||
if ($canunsubscribe) {
|
||||
$footerlinks[] = "<a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">" . get_string("unsubscribe", "forum") . "</a>";
|
||||
} else {
|
||||
$footerlinks[] = get_string("everyoneissubscribed", "forum");
|
||||
}
|
||||
$footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string("digestmailpost", "forum") . '</a>';
|
||||
$posthtml .= "\n<div class='mdl-right'><font size=\"1\">" . implode(' ', $footerlinks) . '</font></div>';
|
||||
$posthtml .= '<hr size="1" noshade="noshade" /></p>';
|
||||
}
|
||||
|
||||
if (empty($userto->mailformat) || $userto->mailformat != 1) {
|
||||
// This user DOESN'T want to receive HTML
|
||||
$posthtml = '';
|
||||
}
|
||||
|
||||
$eventdata = new \core\message\message();
|
||||
$eventdata->courseid = SITEID;
|
||||
$eventdata->component = 'mod_forum';
|
||||
$eventdata->name = 'digests';
|
||||
$eventdata->userfrom = core_user::get_noreply_user();
|
||||
$eventdata->userto = $userto;
|
||||
$eventdata->subject = $postsubject;
|
||||
$eventdata->fullmessage = $posttext;
|
||||
$eventdata->fullmessageformat = FORMAT_PLAIN;
|
||||
$eventdata->fullmessagehtml = $posthtml;
|
||||
$eventdata->notification = 1;
|
||||
$eventdata->smallmessage = get_string('smallmessagedigest', 'forum', $sentcount);
|
||||
$mailresult = message_send($eventdata);
|
||||
|
||||
if (!$mailresult) {
|
||||
mtrace("ERROR: mod/forum/cron.php: Could not send out digest mail to user $userto->id ".
|
||||
"($userto->email)... not trying again.");
|
||||
} else {
|
||||
mtrace("success.");
|
||||
$usermailcount++;
|
||||
|
||||
// Mark post as read if forum_usermarksread is set off
|
||||
if (get_user_preferences('forum_markasreadonnotification', 1, $userto->id) == 1) {
|
||||
forum_tp_mark_posts_read($userto, $userto->markposts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// We have finishied all digest emails, update $CFG->digestmailtimelast
|
||||
set_config('digestmailtimelast', $timenow);
|
||||
}
|
||||
|
||||
cron_setup_user();
|
||||
|
||||
if (!empty($usermailcount)) {
|
||||
mtrace(get_string('digestsentusers', 'forum', $usermailcount));
|
||||
}
|
||||
|
||||
if (!empty($CFG->forum_lastreadclean)) {
|
||||
$timenow = time();
|
||||
if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
|
||||
set_config('forum_lastreadclean', $timenow);
|
||||
mtrace('Removing old forum read tracking info...');
|
||||
forum_tp_clean_read_records();
|
||||
}
|
||||
} else {
|
||||
set_config('forum_lastreadclean', time());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param object $course
|
||||
|
@ -44,7 +44,8 @@
|
||||
} {{/ str }}
|
||||
---------------------------------------------------------------------
|
||||
{{{ message }}}
|
||||
|
||||
{{# attachments }}
|
||||
{{{ attachments }}}
|
||||
{{/ attachments }}
|
||||
---------------------------------------------------------------------
|
||||
{{# str }} digestmailpostlink, forum, {{{ forumindexlink }}} {{/ str }}
|
||||
|
129
mod/forum/tests/cron_trait.php
Normal file
129
mod/forum/tests/cron_trait.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* The forum module cron trait.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
trait mod_forum_tests_cron_trait {
|
||||
/**
|
||||
* Run the main cron task to queue all tasks, and ensure that posts
|
||||
* were sent to the correct users.
|
||||
*
|
||||
* @param \stdClass[] $expectations The list of users, along with their expected count of messages and digests.
|
||||
*/
|
||||
protected function queue_tasks_and_assert($expectations = []) {
|
||||
global $DB;
|
||||
|
||||
// Note, we cannot use expectOutputRegex because it only allows for a single RegExp.
|
||||
ob_start();
|
||||
cron_setup_user();
|
||||
$cron = new \mod_forum\task\cron_task();
|
||||
$cron->execute();
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$uniqueusers = 0;
|
||||
foreach ($expectations as $expect) {
|
||||
$expect->digests = isset($expect->digests) ? $expect->digests : 0;
|
||||
$expect->messages = isset($expect->messages) ? $expect->messages : 0;
|
||||
$expect->mentioned = isset($expect->mentioned) ? $expect->mentioned : false;
|
||||
if ($expect->digests || $expect->messages) {
|
||||
$expect->mentioned = true;
|
||||
}
|
||||
if (!$expect->mentioned) {
|
||||
$this->assertNotRegExp("/Queued 0 for {$expect->userid}/", $output);
|
||||
} else {
|
||||
$uniqueusers++;
|
||||
$this->assertRegExp(
|
||||
"/Queued {$expect->digests} digests and {$expect->messages} messages for {$expect->userid}/",
|
||||
$output
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($expectations)) {
|
||||
$this->assertRegExp("/No posts found./", $output);
|
||||
} else {
|
||||
$this->assertRegExp("/Unique users: {$uniqueusers}/", $output);
|
||||
}
|
||||
|
||||
// Update the forum queue for digests.
|
||||
$DB->execute("UPDATE {forum_queue} SET timemodified = timemodified - 1");
|
||||
}
|
||||
|
||||
/**
|
||||
* Run any send_user_notifications tasks for the specified user, and
|
||||
* ensure that the posts specified were sent.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
* @param \stdClass[] $posts
|
||||
* @param bool $ignoreemptyposts
|
||||
*/
|
||||
protected function send_notifications_and_assert($user, $posts = [], $ignoreemptyposts = false) {
|
||||
ob_start();
|
||||
$this->runAdhocTasks(\mod_forum\task\send_user_notifications::class, $user->id);
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
if (empty($posts) && !$ignoreemptyposts) {
|
||||
$this->assertEquals('', $output);
|
||||
} else {
|
||||
$this->assertRegExp("/Sending messages to {$user->username}/", $output);
|
||||
foreach ($posts as $post) {
|
||||
$this->assertRegExp("/Post {$post->id} sent/", $output);
|
||||
}
|
||||
$count = count($posts);
|
||||
$this->assertRegExp("/Sent {$count} messages with 0 failures/", $output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run any send_user_digests tasks for the specified user, and
|
||||
* ensure that the posts specified were sent.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
* @param \stdClass[] $fullposts
|
||||
* @param \stdClass[] $shortposts
|
||||
*/
|
||||
protected function send_digests_and_assert($user, $fullposts = [], $shortposts = []) {
|
||||
ob_start();
|
||||
$this->runAdhocTasks(\mod_forum\task\send_user_digests::class, $user->id);
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
if (empty($shortposts) && empty($fullposts)) {
|
||||
$this->assertEquals('', $output);
|
||||
$this->assertRegExp("/Digest sent with 0 messages./", $output);
|
||||
} else {
|
||||
$this->assertRegExp("/Sending forum digests for {$user->username}/", $output);
|
||||
foreach ($fullposts as $post) {
|
||||
$this->assertRegExp("/Adding post {$post->id} in format 1/", $output);
|
||||
}
|
||||
foreach ($shortposts as $post) {
|
||||
$this->assertRegExp("/Adding post {$post->id} in format 2/", $output);
|
||||
}
|
||||
$count = count($fullposts) + count($shortposts);
|
||||
$this->assertRegExp("/Digest sent with {$count} messages./", $output);
|
||||
}
|
||||
}
|
||||
}
|
159
mod/forum/tests/generator_trait.php
Normal file
159
mod/forum/tests/generator_trait.php
Normal file
@ -0,0 +1,159 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* The forum module trait with additional generator helpers.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
trait mod_forum_tests_generator_trait {
|
||||
|
||||
/**
|
||||
* Helper to create the required number of users in the specified course.
|
||||
* Users are enrolled as students by default.
|
||||
*
|
||||
* @param stdClass $course The course object
|
||||
* @param integer $count The number of users to create
|
||||
* @param string $role The role to assign users as
|
||||
* @return array The users created
|
||||
*/
|
||||
protected function helper_create_users($course, $count, $role = null) {
|
||||
$users = array();
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, $role);
|
||||
$users[] = $user;
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new discussion and post within the specified forum, as the
|
||||
* specified author.
|
||||
*
|
||||
* @param stdClass $forum The forum to post in
|
||||
* @param stdClass $author The author to post as
|
||||
* @param array $fields any other fields in discussion (name, message, messageformat, ...)
|
||||
* @return array An array containing the discussion object, and the post object
|
||||
*/
|
||||
protected function helper_post_to_forum($forum, $author, $fields = array()) {
|
||||
global $DB;
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
// Create a discussion in the forum, and then add a post to that discussion.
|
||||
$record = (object)$fields;
|
||||
$record->course = $forum->course;
|
||||
$record->userid = $author->id;
|
||||
$record->forum = $forum->id;
|
||||
$discussion = $generator->create_discussion($record);
|
||||
|
||||
// Retrieve the post which was created by create_discussion.
|
||||
$post = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
|
||||
|
||||
return array($discussion, $post);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the post time for the specified post by $factor.
|
||||
*
|
||||
* @param stdClass $post The post to update
|
||||
* @param int $factor The amount to update by
|
||||
*/
|
||||
protected function helper_update_post_time($post, $factor) {
|
||||
global $DB;
|
||||
|
||||
// Update the post to have a created in the past.
|
||||
$DB->set_field('forum_posts', 'created', $post->created + $factor, array('id' => $post->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the subscription time for the specified user/discussion by $factor.
|
||||
*
|
||||
* @param stdClass $user The user to update
|
||||
* @param stdClass $discussion The discussion to update for this user
|
||||
* @param int $factor The amount to update by
|
||||
*/
|
||||
protected function helper_update_subscription_time($user, $discussion, $factor) {
|
||||
global $DB;
|
||||
|
||||
$sub = $DB->get_record('forum_discussion_subs', array('userid' => $user->id, 'discussion' => $discussion->id));
|
||||
|
||||
// Update the subscription to have a preference in the past.
|
||||
$DB->set_field('forum_discussion_subs', 'preference', $sub->preference + $factor, array('id' => $sub->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new post within an existing discussion, as the specified author.
|
||||
*
|
||||
* @param stdClass $forum The forum to post in
|
||||
* @param stdClass $discussion The discussion to post in
|
||||
* @param stdClass $author The author to post as
|
||||
* @return stdClass The forum post
|
||||
*/
|
||||
protected function helper_post_to_discussion($forum, $discussion, $author) {
|
||||
global $DB;
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
// Add a post to the discussion.
|
||||
$record = new stdClass();
|
||||
$record->course = $forum->course;
|
||||
$strre = get_string('re', 'forum');
|
||||
$record->subject = $strre . ' ' . $discussion->subject;
|
||||
$record->userid = $author->id;
|
||||
$record->forum = $forum->id;
|
||||
$record->discussion = $discussion->id;
|
||||
$record->mailnow = 1;
|
||||
|
||||
$post = $generator->create_post($record);
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new post within an existing discussion, as the specified author.
|
||||
*
|
||||
* @param stdClass $parent The post being replied to
|
||||
* @param stdClass $author The author to post as
|
||||
* @return stdClass The forum post
|
||||
*/
|
||||
protected function helper_reply_to_post($parent, $author) {
|
||||
global $DB;
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
// Add a post to the discussion.
|
||||
$strre = get_string('re', 'forum');
|
||||
$record = (object) [
|
||||
'discussion' => $parent->discussion,
|
||||
'parent' => $parent->id,
|
||||
'userid' => $author->id,
|
||||
'mailnow' => 1,
|
||||
'subject' => $strre . ' ' . $parent->subject,
|
||||
];
|
||||
|
||||
$post = $generator->create_post($record);
|
||||
|
||||
return $post;
|
||||
}
|
||||
}
|
246
mod/forum/tests/mail_group_test.php
Normal file
246
mod/forum/tests/mail_group_test.php
Normal file
@ -0,0 +1,246 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* The forum module mail generation tests for groups.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2013 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
require_once(__DIR__ . '/cron_trait.php');
|
||||
require_once(__DIR__ . '/generator_trait.php');
|
||||
|
||||
/**
|
||||
* The forum module mail generation tests for groups.
|
||||
*
|
||||
* @copyright 2013 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class mod_forum_mail_group_testcase extends advanced_testcase {
|
||||
// Make use of the cron tester trait.
|
||||
use mod_forum_tests_cron_trait;
|
||||
|
||||
// Make use of the test generator trait.
|
||||
use mod_forum_tests_generator_trait;
|
||||
|
||||
/**
|
||||
* @var \phpunit_message_sink
|
||||
*/
|
||||
protected $messagesink;
|
||||
|
||||
/**
|
||||
* @var \phpunit_mailer_sink
|
||||
*/
|
||||
protected $mailsink;
|
||||
|
||||
public function setUp() {
|
||||
global $CFG;
|
||||
|
||||
// We must clear the subscription caches. This has to be done both before each test, and after in case of other
|
||||
// tests using these functions.
|
||||
\mod_forum\subscriptions::reset_forum_cache();
|
||||
\mod_forum\subscriptions::reset_discussion_cache();
|
||||
|
||||
// Messaging is not compatible with transactions...
|
||||
$this->preventResetByRollback();
|
||||
|
||||
// Catch all messages.
|
||||
$this->messagesink = $this->redirectMessages();
|
||||
$this->mailsink = $this->redirectEmails();
|
||||
|
||||
// Forcibly reduce the maxeditingtime to a second in the past to
|
||||
// ensure that messages are sent out.
|
||||
$CFG->maxeditingtime = -1;
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
// 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();
|
||||
|
||||
$this->messagesink->clear();
|
||||
$this->messagesink->close();
|
||||
unset($this->messagesink);
|
||||
|
||||
$this->mailsink->clear();
|
||||
$this->mailsink->close();
|
||||
unset($this->mailsink);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that posts written in a forum marked for separate groups includes notifications for the members of that
|
||||
* group, and any user with accessallgroups.
|
||||
*/
|
||||
public function test_separate_group() {
|
||||
global $CFG, $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Create a course, with a forum.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
'forcesubscribe' => FORUM_INITIALSUBSCRIBE,
|
||||
'groupmode' => SEPARATEGROUPS,
|
||||
]);
|
||||
|
||||
// Create three students:
|
||||
// - author, enrolled in group A; and
|
||||
// - recipient, enrolled in group B; and
|
||||
// - other, enrolled in the course, but no groups.
|
||||
list($author, $recipient, $otheruser) = $this->helper_create_users($course, 3);
|
||||
|
||||
// Create one teacher, not in any group and no accessallgroups capability.
|
||||
list($teacher) = $this->helper_create_users($course, 1, 'teacher');
|
||||
|
||||
// Create one editing teacher, not in any group but with accessallgroups capability.
|
||||
list($editingteacher) = $this->helper_create_users($course, 1, 'editingteacher');
|
||||
|
||||
$groupa = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
|
||||
$groupb = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
|
||||
$this->getDataGenerator()->create_group_member([
|
||||
'groupid' => $groupa->id,
|
||||
'userid' => $author->id,
|
||||
]);
|
||||
$this->getDataGenerator()->create_group_member([
|
||||
'groupid' => $groupb->id,
|
||||
'userid' => $recipient->id,
|
||||
]);
|
||||
|
||||
// Post a discussion to the forum.
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum, $author, [
|
||||
'groupid' => $groupa->id,
|
||||
]);
|
||||
|
||||
// Only the author should receive.
|
||||
$expect = [
|
||||
'author' => (object) [
|
||||
'userid' => $author->id,
|
||||
'messages' => 1,
|
||||
],
|
||||
'recipient' => (object) [
|
||||
'userid' => $recipient->id,
|
||||
'messages' => 0,
|
||||
],
|
||||
'otheruser' => (object) [
|
||||
'userid' => $otheruser->id,
|
||||
'messages' => 0,
|
||||
],
|
||||
'teacher' => (object) [
|
||||
'userid' => $teacher->id,
|
||||
'messages' => 0,
|
||||
],
|
||||
'editingteacher' => (object) [
|
||||
'userid' => $editingteacher->id,
|
||||
'messages' => 1,
|
||||
],
|
||||
];
|
||||
$this->queue_tasks_and_assert($expect);
|
||||
|
||||
// No notifications should be queued.
|
||||
$this->send_notifications_and_assert($author, [$post]);
|
||||
$this->send_notifications_and_assert($recipient, []);
|
||||
$this->send_notifications_and_assert($otheruser, []);
|
||||
$this->send_notifications_and_assert($teacher, []);
|
||||
$this->send_notifications_and_assert($editingteacher, [$post]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that posts written in a forum marked for visible groups includes notifications for the members of that
|
||||
* group, and any user with accessallgroups.
|
||||
*/
|
||||
public function test_visible_group() {
|
||||
global $CFG, $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Create a course, with a forum.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
'forcesubscribe' => FORUM_INITIALSUBSCRIBE,
|
||||
'groupmode' => VISIBLEGROUPS,
|
||||
]);
|
||||
|
||||
// Create three students:
|
||||
// - author, enrolled in group A; and
|
||||
// - recipient, enrolled in group B; and
|
||||
// - other, enrolled in the course, but no groups.
|
||||
list($author, $recipient, $otheruser) = $this->helper_create_users($course, 3);
|
||||
|
||||
// Create one teacher, not in any group and no accessallgroups capability.
|
||||
list($teacher) = $this->helper_create_users($course, 1, 'teacher');
|
||||
|
||||
// Create one editing teacher, not in any group but with accessallgroups capability.
|
||||
list($editingteacher) = $this->helper_create_users($course, 1, 'editingteacher');
|
||||
|
||||
$groupa = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
|
||||
$groupb = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
|
||||
$this->getDataGenerator()->create_group_member([
|
||||
'groupid' => $groupa->id,
|
||||
'userid' => $author->id,
|
||||
]);
|
||||
$this->getDataGenerator()->create_group_member([
|
||||
'groupid' => $groupb->id,
|
||||
'userid' => $recipient->id,
|
||||
]);
|
||||
|
||||
// Post a discussion to the forum.
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum, $author, [
|
||||
'groupid' => $groupa->id,
|
||||
]);
|
||||
|
||||
// Only the author should receive.
|
||||
$expect = [
|
||||
'author' => (object) [
|
||||
'userid' => $author->id,
|
||||
'messages' => 1,
|
||||
],
|
||||
'recipient' => (object) [
|
||||
'userid' => $recipient->id,
|
||||
'messages' => 0,
|
||||
],
|
||||
'otheruser' => (object) [
|
||||
'userid' => $otheruser->id,
|
||||
'messages' => 0,
|
||||
],
|
||||
'teacher' => (object) [
|
||||
'userid' => $teacher->id,
|
||||
'messages' => 0,
|
||||
],
|
||||
'editingteacher' => (object) [
|
||||
'userid' => $editingteacher->id,
|
||||
'messages' => 1,
|
||||
],
|
||||
];
|
||||
$this->queue_tasks_and_assert($expect);
|
||||
|
||||
// No notifications should be queued.
|
||||
$this->send_notifications_and_assert($author, [$post]);
|
||||
$this->send_notifications_and_assert($recipient, []);
|
||||
$this->send_notifications_and_assert($otheruser, []);
|
||||
$this->send_notifications_and_assert($teacher, []);
|
||||
$this->send_notifications_and_assert($editingteacher, [$post]);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -26,17 +26,16 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once(__DIR__ . '/cron_trait.php');
|
||||
require_once(__DIR__ . '/generator_trait.php');
|
||||
|
||||
class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Keep track of the message and mail sinks that we set up for each
|
||||
* test.
|
||||
*
|
||||
* @var stdClass $helper
|
||||
*/
|
||||
protected $helper;
|
||||
// Make use of the cron tester trait.
|
||||
use mod_forum_tests_cron_trait;
|
||||
|
||||
// Make use of the test generator trait.
|
||||
use mod_forum_tests_generator_trait;
|
||||
|
||||
/**
|
||||
* Set up message and mail sinks, and set up other requirements for the
|
||||
@ -45,20 +44,18 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
public function setUp() {
|
||||
global $CFG;
|
||||
|
||||
$this->helper = new stdClass();
|
||||
|
||||
// Messaging is not compatible with transactions...
|
||||
$this->preventResetByRollback();
|
||||
|
||||
// Catch all messages
|
||||
$this->helper->messagesink = $this->redirectMessages();
|
||||
$this->helper->mailsink = $this->redirectEmails();
|
||||
$this->messagesink = $this->redirectMessages();
|
||||
$this->mailsink = $this->redirectEmails();
|
||||
|
||||
// Confirm that we have an empty message sink so far.
|
||||
$messages = $this->helper->messagesink->get_messages();
|
||||
$messages = $this->messagesink->get_messages();
|
||||
$this->assertEquals(0, count($messages));
|
||||
|
||||
$messages = $this->helper->mailsink->get_messages();
|
||||
$messages = $this->mailsink->get_messages();
|
||||
$this->assertEquals(0, count($messages));
|
||||
|
||||
// Tell Moodle that we've not sent any digest messages out recently.
|
||||
@ -82,11 +79,11 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
* Clear the message sinks set up in this test.
|
||||
*/
|
||||
public function tearDown() {
|
||||
$this->helper->messagesink->clear();
|
||||
$this->helper->messagesink->close();
|
||||
$this->messagesink->clear();
|
||||
$this->messagesink->close();
|
||||
|
||||
$this->helper->mailsink->clear();
|
||||
$this->helper->mailsink->close();
|
||||
$this->mailsink->clear();
|
||||
$this->mailsink->close();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,52 +128,6 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to falsify all forum post records for a digest run.
|
||||
*/
|
||||
protected function helper_force_digest_mail_times() {
|
||||
global $CFG, $DB;
|
||||
// Fake all of the post editing times because digests aren't sent until
|
||||
// the start of an hour where the modification time on the message is before
|
||||
// the start of that hour
|
||||
$sitetimezone = core_date::get_server_timezone();
|
||||
$digesttime = usergetmidnight(time(), $sitetimezone) + ($CFG->digestmailtime * 3600) - (60 * 60);
|
||||
$DB->set_field('forum_posts', 'modified', $digesttime, array('mailed' => 0));
|
||||
$DB->set_field('forum_posts', 'created', $digesttime, array('mailed' => 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the forum cron, and check that the specified post was sent the
|
||||
* specified number of times.
|
||||
*
|
||||
* @param integer $expected The number of times that the post should have been sent
|
||||
* @param integer $individualcount The number of individual messages sent
|
||||
* @param integer $digestcount The number of digest messages sent
|
||||
*/
|
||||
protected function helper_run_cron_check_count($expected, $individualcount, $digestcount) {
|
||||
if ($expected === 0) {
|
||||
$this->expectOutputRegex('/(Email digests successfully sent to .* users.){0}/');
|
||||
} else {
|
||||
$this->expectOutputRegex("/Email digests successfully sent to {$expected} users/");
|
||||
}
|
||||
forum_cron();
|
||||
|
||||
// Now check the results in the message sink.
|
||||
$messages = $this->helper->messagesink->get_messages();
|
||||
|
||||
$counts = (object) array('digest' => 0, 'individual' => 0);
|
||||
foreach ($messages as $message) {
|
||||
if (strpos($message->subject, 'forum digest') !== false) {
|
||||
$counts->digest++;
|
||||
} else {
|
||||
$counts->individual++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertEquals($digestcount, $counts->digest);
|
||||
$this->assertEquals($individualcount, $counts->individual);
|
||||
}
|
||||
|
||||
public function test_set_maildigest() {
|
||||
global $DB;
|
||||
|
||||
@ -302,10 +253,9 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$this->helper_force_digest_mail_times();
|
||||
|
||||
// Initially the forum cron should generate no messages as we've made no posts.
|
||||
$this->helper_run_cron_check_count(0, 0, 0);
|
||||
$expect = [];
|
||||
$this->queue_tasks_and_assert($expect);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -324,27 +274,19 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
$forum1 = $userhelper->forums->forum1;
|
||||
$forum2 = $userhelper->forums->forum2;
|
||||
|
||||
// Add some discussions to the forums.
|
||||
$record = new stdClass();
|
||||
$record->course = $course1->id;
|
||||
$record->userid = $user->id;
|
||||
$record->mailnow = 1;
|
||||
|
||||
// Add 5 discussions to forum 1.
|
||||
$record->forum = $forum1->id;
|
||||
$posts = [];
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum1, $user, ['mailnow' => 1]);
|
||||
$posts[] = $post;
|
||||
}
|
||||
|
||||
// Add 5 discussions to forum 2.
|
||||
$record->forum = $forum2->id;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum2, $user, ['mailnow' => 1]);
|
||||
$posts[] = $post;
|
||||
}
|
||||
|
||||
// Ensure that the creation times mean that the messages will be sent.
|
||||
$this->helper_force_digest_mail_times();
|
||||
|
||||
// Set the tested user's default maildigest setting.
|
||||
$DB->set_field('user', 'maildigest', 0, array('id' => $user->id));
|
||||
|
||||
@ -355,7 +297,16 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
forum_set_user_maildigest($forum2, -1, $user);
|
||||
|
||||
// No digests mails should be sent, but 10 forum mails will be sent.
|
||||
$this->helper_run_cron_check_count(0, 10, 0);
|
||||
$expect = [
|
||||
(object) [
|
||||
'userid' => $user->id,
|
||||
'messages' => 10,
|
||||
'digests' => 0,
|
||||
],
|
||||
];
|
||||
$this->queue_tasks_and_assert($expect);
|
||||
|
||||
$this->send_notifications_and_assert($user, $posts);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -373,28 +324,20 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
$course1 = $userhelper->courses->course1;
|
||||
$forum1 = $userhelper->forums->forum1;
|
||||
$forum2 = $userhelper->forums->forum2;
|
||||
|
||||
// Add a discussion to the forums.
|
||||
$record = new stdClass();
|
||||
$record->course = $course1->id;
|
||||
$record->userid = $user->id;
|
||||
$record->mailnow = 1;
|
||||
$posts = [];
|
||||
|
||||
// Add 5 discussions to forum 1.
|
||||
$record->forum = $forum1->id;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum1, $user, ['mailnow' => 1]);
|
||||
$posts[] = $post;
|
||||
}
|
||||
|
||||
// Add 5 discussions to forum 2.
|
||||
$record->forum = $forum2->id;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum2, $user, ['mailnow' => 1]);
|
||||
$posts[] = $post;
|
||||
}
|
||||
|
||||
// Ensure that the creation times mean that the messages will be sent.
|
||||
$this->helper_force_digest_mail_times();
|
||||
|
||||
// Set the tested user's default maildigest setting.
|
||||
$DB->set_field('user', 'maildigest', 1, array('id' => $user->id));
|
||||
|
||||
@ -404,8 +347,17 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
// Set the maildigest preference for forum2 to default.
|
||||
forum_set_user_maildigest($forum2, -1, $user);
|
||||
|
||||
// One digest mail should be sent, with no notifications, and one e-mail.
|
||||
$this->helper_run_cron_check_count(1, 0, 1);
|
||||
// No digests mails should be sent, but 10 forum mails will be sent.
|
||||
$expect = [
|
||||
(object) [
|
||||
'userid' => $user->id,
|
||||
'messages' => 0,
|
||||
'digests' => 1,
|
||||
],
|
||||
];
|
||||
$this->queue_tasks_and_assert($expect);
|
||||
|
||||
$this->send_digests_and_assert($user, $posts);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -424,28 +376,21 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
$course1 = $userhelper->courses->course1;
|
||||
$forum1 = $userhelper->forums->forum1;
|
||||
$forum2 = $userhelper->forums->forum2;
|
||||
|
||||
// Add a discussion to the forums.
|
||||
$record = new stdClass();
|
||||
$record->course = $course1->id;
|
||||
$record->userid = $user->id;
|
||||
$record->mailnow = 1;
|
||||
$posts = [];
|
||||
$digests = [];
|
||||
|
||||
// Add 5 discussions to forum 1.
|
||||
$record->forum = $forum1->id;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum1, $user, ['mailnow' => 1]);
|
||||
$digests[] = $post;
|
||||
}
|
||||
|
||||
// Add 5 discussions to forum 2.
|
||||
$record->forum = $forum2->id;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum2, $user, ['mailnow' => 1]);
|
||||
$posts[] = $post;
|
||||
}
|
||||
|
||||
// Ensure that the creation times mean that the messages will be sent.
|
||||
$this->helper_force_digest_mail_times();
|
||||
|
||||
// Set the tested user's default maildigest setting.
|
||||
$DB->set_field('user', 'maildigest', 0, array('id' => $user->id));
|
||||
|
||||
@ -456,7 +401,17 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
forum_set_user_maildigest($forum2, -1, $user);
|
||||
|
||||
// One digest e-mail should be sent, and five individual notifications.
|
||||
$this->helper_run_cron_check_count(1, 5, 1);
|
||||
$expect = [
|
||||
(object) [
|
||||
'userid' => $user->id,
|
||||
'messages' => 5,
|
||||
'digests' => 1,
|
||||
],
|
||||
];
|
||||
$this->queue_tasks_and_assert($expect);
|
||||
|
||||
$this->send_notifications_and_assert($user, $posts);
|
||||
$this->send_digests_and_assert($user, $digests);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -475,28 +430,21 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
$course1 = $userhelper->courses->course1;
|
||||
$forum1 = $userhelper->forums->forum1;
|
||||
$forum2 = $userhelper->forums->forum2;
|
||||
|
||||
// Add a discussion to the forums.
|
||||
$record = new stdClass();
|
||||
$record->course = $course1->id;
|
||||
$record->userid = $user->id;
|
||||
$record->mailnow = 1;
|
||||
$posts = [];
|
||||
$digests = [];
|
||||
|
||||
// Add 5 discussions to forum 1.
|
||||
$record->forum = $forum1->id;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum1, $user, ['mailnow' => 1]);
|
||||
$digests[] = $post;
|
||||
}
|
||||
|
||||
// Add 5 discussions to forum 2.
|
||||
$record->forum = $forum2->id;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum2, $user, ['mailnow' => 1]);
|
||||
$posts[] = $post;
|
||||
}
|
||||
|
||||
// Ensure that the creation times mean that the messages will be sent.
|
||||
$this->helper_force_digest_mail_times();
|
||||
|
||||
// Set the tested user's default maildigest setting.
|
||||
$DB->set_field('user', 'maildigest', 1, array('id' => $user->id));
|
||||
|
||||
@ -507,7 +455,17 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
forum_set_user_maildigest($forum2, 0, $user);
|
||||
|
||||
// One digest e-mail should be sent, and five individual notifications.
|
||||
$this->helper_run_cron_check_count(1, 5, 1);
|
||||
$expect = [
|
||||
(object) [
|
||||
'userid' => $user->id,
|
||||
'messages' => 5,
|
||||
'digests' => 1,
|
||||
],
|
||||
];
|
||||
$this->queue_tasks_and_assert($expect);
|
||||
|
||||
$this->send_notifications_and_assert($user, $posts);
|
||||
$this->send_digests_and_assert($user, $digests);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -525,28 +483,21 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
$course1 = $userhelper->courses->course1;
|
||||
$forum1 = $userhelper->forums->forum1;
|
||||
$forum2 = $userhelper->forums->forum2;
|
||||
|
||||
// Add a discussion to the forums.
|
||||
$record = new stdClass();
|
||||
$record->course = $course1->id;
|
||||
$record->userid = $user->id;
|
||||
$record->mailnow = 1;
|
||||
$fulldigests = [];
|
||||
$shortdigests = [];
|
||||
|
||||
// Add 5 discussions to forum 1.
|
||||
$record->forum = $forum1->id;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum1, $user, ['mailnow' => 1]);
|
||||
$fulldigests[] = $post;
|
||||
}
|
||||
|
||||
// Add 5 discussions to forum 2.
|
||||
$record->forum = $forum2->id;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum2, $user, ['mailnow' => 1]);
|
||||
$shortdigests[] = $post;
|
||||
}
|
||||
|
||||
// Ensure that the creation times mean that the messages will be sent.
|
||||
$this->helper_force_digest_mail_times();
|
||||
|
||||
// Set the tested user's default maildigest setting.
|
||||
$DB->set_field('user', 'maildigest', 0, array('id' => $user->id));
|
||||
|
||||
@ -557,7 +508,14 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
|
||||
forum_set_user_maildigest($forum2, 2, $user);
|
||||
|
||||
// One digest e-mail should be sent, and no individual notifications.
|
||||
$this->helper_run_cron_check_count(1, 0, 1);
|
||||
}
|
||||
$expect = [
|
||||
(object) [
|
||||
'userid' => $user->id,
|
||||
'digests' => 1,
|
||||
],
|
||||
];
|
||||
$this->queue_tasks_and_assert($expect);
|
||||
|
||||
$this->send_digests_and_assert($user, $fulldigests, $shortdigests);
|
||||
}
|
||||
}
|
||||
|
153
mod/forum/tests/qanda_test.php
Normal file
153
mod/forum/tests/qanda_test.php
Normal file
@ -0,0 +1,153 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* The forum module mail generation tests for groups.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2013 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
require_once(__DIR__ . '/cron_trait.php');
|
||||
require_once(__DIR__ . '/generator_trait.php');
|
||||
|
||||
/**
|
||||
* The forum module mail generation tests for groups.
|
||||
*
|
||||
* @copyright 2013 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class mod_forum_qanda_testcase extends advanced_testcase {
|
||||
// Make use of the cron tester trait.
|
||||
use mod_forum_tests_cron_trait;
|
||||
|
||||
// Make use of the test generator trait.
|
||||
use mod_forum_tests_generator_trait;
|
||||
|
||||
/**
|
||||
* @var \phpunit_message_sink
|
||||
*/
|
||||
protected $messagesink;
|
||||
|
||||
/**
|
||||
* @var \phpunit_mailer_sink
|
||||
*/
|
||||
protected $mailsink;
|
||||
|
||||
public function setUp() {
|
||||
global $CFG;
|
||||
|
||||
// We must clear the subscription caches. This has to be done both before each test, and after in case of other
|
||||
// tests using these functions.
|
||||
\mod_forum\subscriptions::reset_forum_cache();
|
||||
\mod_forum\subscriptions::reset_discussion_cache();
|
||||
|
||||
// Messaging is not compatible with transactions...
|
||||
$this->preventResetByRollback();
|
||||
|
||||
// Catch all messages.
|
||||
$this->messagesink = $this->redirectMessages();
|
||||
$this->mailsink = $this->redirectEmails();
|
||||
|
||||
// Forcibly reduce the maxeditingtime to a second in the past to
|
||||
// ensure that messages are sent out.
|
||||
$CFG->maxeditingtime = -1;
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
// 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();
|
||||
|
||||
$this->messagesink->clear();
|
||||
$this->messagesink->close();
|
||||
unset($this->messagesink);
|
||||
|
||||
$this->mailsink->clear();
|
||||
$this->mailsink->close();
|
||||
unset($this->mailsink);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a user who has not posted in a q&a forum does not receive
|
||||
* notificatinos.
|
||||
*/
|
||||
public function test_user_has_not_posted() {
|
||||
global $CFG, $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Create a course, with a forum.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
'forcesubscribe' => FORUM_INITIALSUBSCRIBE,
|
||||
'groupmode' => SEPARATEGROUPS,
|
||||
'type' => 'qanda',
|
||||
]);
|
||||
|
||||
// Create three students:
|
||||
// - author, enrolled in group A; and
|
||||
// - recipient, enrolled in group B; and
|
||||
// - other, enrolled in the course, but no groups.
|
||||
list($author, $recipient, $otheruser) = $this->helper_create_users($course, 3);
|
||||
|
||||
// Create one editing teacher, not in any group but with accessallgroups capability.
|
||||
list($editingteacher) = $this->helper_create_users($course, 1, 'editingteacher');
|
||||
|
||||
// Post a discussion to the forum.
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum, $editingteacher);
|
||||
$reply = $this->helper_reply_to_post($post, $author);
|
||||
$otherreply = $this->helper_reply_to_post($post, $recipient);
|
||||
$DB->execute("UPDATE {forum_posts} SET modified = modified - 1");
|
||||
$DB->execute("UPDATE {forum_posts} SET created = created - 1");
|
||||
$DB->execute("UPDATE {forum_discussions} SET timemodified = timemodified - 1");
|
||||
|
||||
// Only the author, recipient, and teachers should receive.
|
||||
$expect = [
|
||||
'author' => (object) [
|
||||
'userid' => $author->id,
|
||||
'messages' => 3,
|
||||
],
|
||||
'recipient' => (object) [
|
||||
'userid' => $recipient->id,
|
||||
'messages' => 3,
|
||||
],
|
||||
'otheruser' => (object) [
|
||||
'userid' => $otheruser->id,
|
||||
'messages' => 1,
|
||||
],
|
||||
'editingteacher' => (object) [
|
||||
'userid' => $editingteacher->id,
|
||||
'messages' => 3,
|
||||
],
|
||||
];
|
||||
$this->queue_tasks_and_assert($expect);
|
||||
$posts = [$post, $reply, $otherreply];
|
||||
|
||||
// No notifications should be queued.
|
||||
$this->send_notifications_and_assert($author, $posts);
|
||||
$this->send_notifications_and_assert($recipient, $posts);
|
||||
$this->send_notifications_and_assert($otheruser, [$post]);
|
||||
$this->send_notifications_and_assert($editingteacher, $posts);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user