diff --git a/classes/manager/mail_manager.php b/classes/manager/mail_manager.php
index 1c80659e88..4fe9c43294 100644
--- a/classes/manager/mail_manager.php
+++ b/classes/manager/mail_manager.php
@@ -27,10 +27,17 @@
use context_course;
use context_module;
use core\context\course;
+use core\cron;
+use core\message\message;
use core_php_time_limit;
+use core_user;
+use dml_exception;
use mod_moodleoverflow\anonymous;
use mod_moodleoverflow\output\moodleoverflow_email;
use mod_moodleoverflow\subscriptions;
+use moodle_exception;
+use moodle_url;
+use renderer_base;
use stdClass;
/**
@@ -60,134 +67,345 @@ class mail_manager {
const MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS = 3;
/**
- * Sends mail notifications about new posts.
+ * This functions executes the task of sending a notification mail to users that are subscribed to a moodleoverflow.
+ * This function does the following:
+ * - Retrieve all posts that are unmailed and need to be send.
+ *
*
* @return bool
+ * @throws moodle_exception
+ * @throws dml_exception
*/
public static function moodleoverflow_send_mails(): bool {
- global $CFG, $DB, $PAGE;
-
- // Get the course object of the top level site.
- $site = get_site();
+ global $DB, $PAGE;
// Get the main renderers.
$htmlout = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'htmlemail');
$textout = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'textemail');
- // Initiate the arrays that are saving the users that are subscribed to posts that needs sending.
- $users = [];
+ // Posts older than x days will not be mailed.
+ // This will avoid problems with the cron not being run for a long time.
+ $timenow = time();
+ $endtime = $timenow - get_config('moodleoverflow', 'maxeditingtime');
+ $starttime = $endtime - (get_config('moodleoverflow', 'maxmailingtime') * 60 * 60);
- // Status arrays.
- $mailcount = [];
- $errorcount = [];
+ // Retrieve posts that need to be send to users.
+ if (!$records = self::moodleoverflow_get_unmailed_posts($starttime, $endtime)) {
+ return true;
+ }
- // Cache arrays.
- $discussions = [];
- $moodleoverflows = [];
+ // Mark those posts as mailed.
+ if (!self::moodleoverflow_mark_old_posts_as_mailed($endtime)) {
+ return false;
+ }
+
+ // Start processing the records.
+ // Build cache arrays for most important objects. All caches are structured with id => object.
+ $posts = [];
+ $authors = [];
+ $recipients = [];
$courses = [];
+ $moodleoverflows = [];
+ $discussions = [];
$coursemodules = [];
- // Posts older than x days will not be mailed. This will avoid problems with the cron not ran for a long time.
- $timenow = time();
- $endtime = $timenow - get_config('moodleoverflow', 'maxeditingtime');
- $starttime = $endtime - (get_config('moodleoverflow', 'maxmailingtime') * 60 * 60);
+ // Loop through each records.
+ foreach ($records as $record) {
+ // Terminate if the process takes more time then two minutes.
+
+ // Fill the caches with objects if needed.
+ // Add additional information that were not retrievable from the database to the objects if needed.
+ self::moodleoverflow_update_mail_caches($record, $coursemodules, $courses, $moodleoverflows,
+ $discussions, $posts, $authors, $recipients);
- // Retrieve all unmailed posts.
- $posts = self::moodleoverflow_get_unmailed_posts($starttime, $endtime);
- if ($posts) {
- // Mark those posts as mailed.
- if (!self::moodleoverflow_mark_old_posts_as_mailed($endtime)) {
- mtrace('Errors occurred while trying to mark some posts as being mailed.');
- return false;
+ // Filter records that are not getting mailed.
+ // Check if the user can see the post.
+ if (!moodleoverflow_user_can_see_post($moodleoverflows[$record->moodleoverflowid], $discussions[$record->discussionid],
+ $posts[$record->postid], $coursemodules[$record->cmid])) {
+ continue;
}
- // Loop through all posts to be mailed.
- foreach ($posts as $postid => $post) {
- self::check_post($post, $mailcount, $users, $discussions, $errorcount, $posts, $postid,
- $moodleoverflows, $courses, $coursemodules);
+
+ // Check if the user subscribed to the post wants a summary instead of a notification mail.
+ if ($record->usertomaildigest != 0) {
+ // Process the record for the mail digest.
+ self::moodleoverflow_process_maildigest_record($record);
+ continue;
}
- }
- // Send mails to the users with information about the posts.
- if ($users && $posts) {
- // Send one mail to every user.
- foreach ($users as $userto) {
- // Terminate if the process takes more time then two minutes.
- core_php_time_limit::raise(120);
-
- // Tracing information.
- mtrace('Processing user ' . $userto->id);
- // Initiate the user caches to save memory.
- $userto = clone($userto);
- $userto->ciewfullnames = [];
- $userto->canpost = [];
- $userto->markposts = [];
-
- // Cache the capabilities of the user.
- $CFG->branch >= 402 ? \core\cron::setup_user($userto) : cron_setup_user($userto);
-
- // Reset the caches.
- foreach ($coursemodules as $moodleoverflowid) {
- $coursemodules[$moodleoverflowid]->cache = new stdClass();
- $coursemodules[$moodleoverflowid]->cache->caps = [];
- unset($coursemodules[$moodleoverflowid]->uservisible);
- }
+ // Determine if the author should be anonymous.
+ $authoranonymous = match ((int)$record->moodleoverflowanonymous) {
+ anonymous::NOT_ANONYMOUS => false,
+ anonymous::EVERYTHING_ANONYMOUS => true,
+ anonymous::QUESTION_ANONYMOUS => ($record->discussionuserid == $record->authorid)
+ };
+
+ // Set the userfrom variable, that is anonymous or the post author.
+ $authoranonymous ? $userfrom = core_user::get_noreply_user() : $userfrom = clone($authors[$record->authorid]);
+ $userfrom->anonymous = $authoranonymous;
+
+ // Cache the recipients capabilities to view full names for the moodleoverflow instance.
+ if (!isset($recipients[$record->usertoid]->viewfullnames[$record->moodleoverflowid])) {
+ // Find the context module.
+ $modulecontext = context_module::instance($record->cmid);
+
+ // Check the users capabilities.
+ $recipients[$record->usertoid]->viewfullnames[$record->moodleoverflowid] =
+ has_capability('moodle/site:viewfullnames', $modulecontext, $record->usertoid);
+ }
- // Loop through all posts of this users.
- foreach ($posts as $post) {
- self::send_post($userto, $post, $coursemodules, $errorcount,
- $discussions, $moodleoverflows, $courses, $mailcount, $users, $site, $textout, $htmlout);
- }
+ // Cache the recipients capability to post in the discussion.
+ if (!isset($recipients[$record->usertoid]->canpost[$record->discussionid])) {
+ // Find the context module.
+ $modulecontext = context_module::instance($record->cmid);
- // Release the memory.
- unset($userto);
+ // Check the users capabilities.
+ $canreply = moodleoverflow_user_can_post($modulecontext, $posts[$record->postid], $record->usertoid);
+ $recipients[$record->usertoid]->canpost[$record->discussionid] = $canreply;
}
- }
- // Check for all posts whether errors occurred.
- foreach ($posts as $post) {
- // Tracing information.
- mtrace($mailcount[$post->id] . " users were sent post $post->id");
+ // Preparation complete. Ready to send message.
+
+ // Build the mail object.
+ $email = new moodleoverflow_email(
+ $courses[$record->courseid],
+ $coursemodules[$record->cmid],
+ $moodleoverflows[$record->moodleoverflowid],
+ $discussions[$record->discussionid],
+ $posts[$record->postid],
+ $userfrom,
+ $recipients[$record->usertoid],
+ $recipients[$record->usertoid]->canpost[$record->discussionid]
+ );
+
+ // LEARNWEB-TODO: check if this is needed.
+ $email->viewfullnames = $recipients[$record->usertoid]->viewfullnames[$record->moodleoverflowid];
+
+ // The email object is build. Now build all data that is needed for the event that really send the mail.
+
+ // Build post subject.
+ $subject = html_to_text(get_string('postmailsubject', 'moodleoverflow',
+ ['subject' => $email->get_subject(), 'courseshortname' => $email->get_coursename()]), 0);
+
+ // Finally: send the notification mail.
+ $userto = $recipients[$record->usertoid];
+ $htmlmessage = $htmlout->render($email);
+ $textmessage = $textout->render($email);
- // Mark the posts with errors in the database.
- if ($errorcount[$post->id]) {
- $DB->set_field('moodleoverflow_posts', 'mailed', self::MOODLEOVERFLOW_MAILED_ERROR, ['id' => $post->id]);
+ $mailsent = email_to_user($userto, core_user::get_noreply_user(), $subject, $textmessage, $htmlmessage);
+
+ // Check if an error occurred and mark the post as mailed_error.
+ if (!$mailsent) {
+ // A mail does not get resend if it was not sent successfully.
+ $DB->set_field('moodleoverflow_posts', 'mailed', MOODLEOVERFLOW_MAILED_ERROR, ['id' => $record->postid]);
}
}
- // The task was completed.
+ // The task is completed.
return true;
}
+
/**
- * Returns a list of all posts that have not been mailed yet.
+ * Return a list of records that will be mailed. One record has all the information that is needed. This includes:
+ * - The post, discussion, moodleoverflow data of a post that is unmailed
+ * - The data of the post author
+ * - The data of the user, that is subscribed to the moodleoverflow discussion, that has the unmailed post
+ *
+ * The same post and user can be found redundantly, because one posts is mailed to many user and one user gets notified about
+ * many posts. Because all data is in one table, every record represents one mail.
*
* @param int $starttime posts created after this time
- * @param int $endtime posts created before this time
+ * @param int $endtime posts created before this time
*
* @return array
+ * @throws dml_exception
*/
- public static function moodleoverflow_get_unmailed_posts($starttime, $endtime) {
+ public static function moodleoverflow_get_unmailed_posts($starttime, $endtime): array {
global $DB;
- // Set params for the sql query.
- $params = [];
- $params['ptimestart'] = $starttime;
- $params['ptimeend'] = $endtime;
+ // Define fields that will be retrieved from the database.
+ $postfields = "p.id AS postid, p.message AS postmessage, p.messageformat as postmessageformat, p.modified as postmodified,
+ p.parent AS postparent, p.userid AS postuserid, p.reviewed AS postreviewed";
+ $discussionfields = "d.id AS discussionid, d.name AS discussionname, d.userid AS discussionuserid,
+ d.firstpost AS discussionfirstpost";
+ $moodleoverflowfields = "mo.id AS moodleoverflowid, mo.name AS moodleoverflowname, mo.anonymous AS moodleoverflowanonymous,
+ mo.forcesubscribe AS moodleoverflowforcesubscribe";
+ $coursefields = "c.id AS courseid, c.idnumber AS courseidnumber, c.fullname AS coursefullname,
+ c.shortname AS courseshortname";
+ $cmfields = "cm.id AS cmid, cm.groupingid AS cmgroupingid";
+ $authorfields = "author.id AS authorid, author.firstname AS authorfirstname, author.lastname AS authorlastname,
+ author.firstnamephonetic AS authorfirstnamephonetic, author.lastnamephonetic AS authorlastnamephonetic,
+ author.middlename AS authormiddlename, author.alternatename AS authoralternatename,
+ author.picture AS authorpicture, author.imagealt AS authorimagealt, author.email AS authoremail";
+ $usertofields = "userto.id AS usertoid, userto.maildigest AS usertomaildigest, userto.mailformat AS usertomailformat,
+ userto.maildisplay AS usertomaildisplay, userto.description AS usertodescription,
+ userto.password AS usertopassword, userto.lang AS usertolang, userto.auth AS usertoauth,
+ userto.suspended AS usertosuspended, userto.deleted AS usertodeleted, userto.emailstop AS usertoemailstop,
+ userto.email AS usertoemail, userto.username AS usertousername, userto.firstname AS usertofirstname,
+ userto.lastname AS usertolastname, userto.firstnamephonetic AS usertofirstnamephonetic,
+ userto.lastnamephonetic AS usertolastnamephonetic, userto.middlename AS usertomiddlename,
+ userto.alternatename AS usertoalternatename";
+
+ $fields = "(ROW_NUMBER() OVER (ORDER BY p.modified)) AS row_num, " . $postfields . ", " . $discussionfields . ", "
+ . $moodleoverflowfields . ", " . $coursefields . ", " . $cmfields . ", " . $authorfields . ", " . $usertofields;
- $pendingmail = self::MOODLEOVERFLOW_MAILED_PENDING;
- $reviewsent = self::MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS;
+ // Set params for the sql query.
+ $timenow = round(time(), -2);
+ $params = [
+ 'unsubscribed' => subscriptions::MOODLEOVERFLOW_DISCUSSION_UNSUBSCRIBED,
+ 'forcesubscribe' => MOODLEOVERFLOW_FORCESUBSCRIBE,
+ 'active' => ENROL_USER_ACTIVE,
+ 'enabled' => ENROL_INSTANCE_ENABLED,
+ 'now1' => $timenow,
+ 'now2' => $timenow,
+ 'pendingmail' => self::MOODLEOVERFLOW_MAILED_PENDING,
+ 'reviewsent' => self::MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS,
+ 'ptimestart' => $starttime,
+ 'ptimeend' => $endtime,
+ ];
// Retrieve the records.
- $sql = "SELECT p.*, d.course, d.moodleoverflow
- FROM {moodleoverflow_posts} p
- JOIN {moodleoverflow_discussions} d ON d.id = p.discussion
- WHERE p.mailed IN ($pendingmail, $reviewsent) AND p.reviewed = 1
- AND COALESCE(p.timereviewed, p.created) >= :ptimestart AND p.created < :ptimeend
- ORDER BY p.modified ASC";
+ // Documentation can be found on: https://github.com/learnweb/moodle-mod_moodleoverflow/wiki/Documentation-for-Developers.
+ $sql = "SELECT $fields
+ FROM {moodleoverflow_posts} p
+ JOIN {moodleoverflow_discussions} d ON d.id = p.discussion
+ JOIN {moodleoverflow} mo ON mo.id = d.moodleoverflow
+ JOIN {course} c ON c.id = mo.course
+ JOIN (
+ SELECT cm.id, cm.groupingid, cm.instance
+ FROM {course_modules} cm
+ JOIN {modules} md ON md.id = cm.module
+ WHERE md.name = 'moodleoverflow'
+ ) cm ON cm.instance = mo.id
+ JOIN {user} author ON author.id = p.userid
+ JOIN (
+ SELECT *
+ FROM (
+ SELECT userid, moodleoverflow, -1 as discussion
+ FROM {moodleoverflow_subscriptions} s
+ UNION
+ SELECT userid, moodleoverflow, discussion
+ FROM {moodleoverflow_discuss_subs} ds
+ WHERE ds.preference <> :unsubscribed
+ UNION
+ SELECT userid, moodleoverflow, discussion
+ FROM (
+ SELECT u.id AS userid, m.id AS moodleoverflow, -1 AS discussion
+ FROM {user} u
+ JOIN {user_enrolments} ue ON ue.userid = u.id
+ JOIN {enrol} e ON e.id = ue.enrolid
+ JOIN {course} c ON c.id = e.courseid
+ JOIN {moodleoverflow} m ON m.course = c.id
+ WHERE m.forcesubscribe = :forcesubscribe
+ AND ue.status = :active
+ AND e.status = :enabled
+ AND ue.timestart < :now1
+ AND (ue.timeend = 0 OR ue.timeend > :now2)
+ ) as forcedsubs
+ ) as subscriptions
+ LEFT JOIN {user} u ON u.id = subscriptions.userid
+ ORDER BY u.email ASC
+ ) userto ON ((d.moodleoverflow = userto.moodleoverflow) AND
+ ((p.discussion = userto.discussion) OR
+ (userto.discussion = -1))
+ )
+ WHERE p.mailed IN (:pendingmail, :reviewsent) AND p.reviewed = 1
+ AND COALESCE(p.timereviewed, p.created) >= :ptimestart AND p.created < :ptimeend
+ AND author.id <> userto.id";
return $DB->get_records_sql($sql, $params);
}
+ /**
+ * Fills and updates cache arrays with data from a record object.
+ * This function checks if specific data (course modules, courses, moodleoverflows, discussions, posts, authors, recipients)
+ * is already cached. If not, it creates an object with the relevant data from the provided record and stores it in the cache.
+ *
+ * @param object $record The record containing data to be cached.
+ * @param array $coursemodules Cache for course module data, indexed by course module ID.
+ * @param array $courses Cache for course data, indexed by course ID.
+ * @param array $moodleoverflows Cache for moodleoverflow data, indexed by moodleoverflow ID.
+ * @param array $discussions Cache for discussion data, indexed by discussion ID.
+ * @param array $posts Cache for post data, indexed by post ID.
+ * @param array $authors Cache for author data, indexed by author ID.
+ * @param array $recipients Cache for recipient data, indexed by recipient ID.
+ *
+ * @return void
+ */
+ public static function moodleoverflow_update_mail_caches(object $record, array &$coursemodules, array &$courses,
+ array &$moodleoverflows, array &$discussions, array &$posts,
+ array &$authors, array &$recipients ): void {
+ // Define cache types and their corresponding record properties.
+ $cachetypes = [
+ 'coursemodules' => ['id' => 'cmid', 'groupingid' => 'cmgroupingid'],
+ 'courses' => ['id' => 'courseid', 'idnumber' => 'courseidnumber', 'fullname' => 'coursefullname',
+ 'shortname' => 'courseshortname'],
+ 'moodleoverflows' => ['id' => 'moodleoverflowid', 'name' => 'moodleoverflowname',
+ 'anonymous' => 'moodleoverflowanonymous', 'forcesubscribe' => 'moodleoverflowforcesubscribe'],
+ 'discussions' => ['id' => 'discussionid', 'name' => 'discussionname', 'userid' => 'discussionuserid',
+ 'firstpost' => 'discussionfirstpost'],
+ 'posts' => ['id' => 'postid', 'message' => 'postmessage', 'messageformat' => 'postmessageformat',
+ 'modified' => 'postmodified', 'parent' => 'postparent', 'userid' => 'postuserid',
+ 'reviewed' => 'postreviewed'],
+ 'authors' => ['id' => 'authorid', 'firstname' => 'authorfirstname', 'lastname' => 'authorlastname',
+ 'firstnamephonetic' => 'authorfirstnamephonetic', 'lastnamephonetic' => 'authorlastnamephonetic',
+ 'middlename' => 'authormiddlename', 'alternatename' => 'authoralternatename',
+ 'picture' => 'authorpicture', 'imagealt' => 'authorimagealt', 'email' => 'authoremail'],
+ 'recipients' => ['id' => 'usertoid', 'maildigest' => 'usertomaildigest', 'mailformat' => 'usertomailformat',
+ 'maildisplay' => 'usertomaildisplay', 'description' => 'usertodescription',
+ 'password' => 'usertopassword', 'lang' => 'usertolang', 'auth' => 'usertoauth',
+ 'suspended' => 'usertosuspended', 'deleted' => 'usertodeleted',
+ 'emailstop' => 'usertoemailstop', 'email' => 'usertoemail', 'username' => 'usertousername',
+ 'firstname' => 'usertofirstname', 'lastname' => 'usertolastname',
+ 'firstnamephonetic' => 'usertofirstnamephonetic', 'lastnamephonetic' => 'usertolastnamephonetic',
+ 'middlename' => 'usertomiddlename', 'alternatename' => 'usertoalternatename'],
+ ];
+
+ // Iterate over cache types and update caches if not already set.
+ foreach ($cachetypes as $cachename => $properties) {
+ $cachekey = $record->{$properties['id']};
+ if (!isset(${$cachename}[$cachekey])) {
+ $obj = new stdClass();
+ foreach ($properties as $propname => $recordkey) {
+ $obj->$propname = $record->$recordkey;
+ }
+ // Only for recipients, add empty arrays for viewfullnames and canpost.
+ if ($cachename === 'recipients') {
+ $obj->viewfullnames = [];
+ $obj->canpost = [];
+ }
+ ${$cachename}[$cachekey] = $obj;
+ }
+ }
+ }
+
+ /**
+ * Function that processes a record from self::moodleoverflow_get_unmailed_posts() if the user that gets the mail wants a
+ * resume instead of a mail for every post.
+ *
+ * @param object $data a single record object from self::moodleoverflow_get_unmailed_posts()
+ * @return void
+ */
+ public static function moodleoverflow_process_maildigest_record(object $data): void {
+ global $DB;
+ // LEARNWEB-TODO: Rename database table attribute names. Rethink the table structure. What should the mail have?
+ // If the record exists, update it. If not, insert a new record.
+ if ($dbrecord = $DB->get_record('moodleoverflow_mail_info', ['userid' => $data->usertoid, 'courseid' => $data->courseid,
+ 'forumid' => $data->moodleoverflowid, 'forumdiscussionid' => $data->discussionid, ], 'numberofposts, id')) {
+ $dbrecord->numberofposts++;
+ $DB->update_record('moodleoverflow_mail_info', $dbrecord);
+ } else {
+ $record = (object) [
+ 'userid' => $data->usertoid,
+ 'courseid' => $data->courseid,
+ 'forumid' => $data->moodleoverflowid,
+ 'forumdiscussionid' => $data->discussionid,
+ 'numberofposts' => 1,
+ ];
+ $DB->insert_record('moodleoverflow_mail_info', $record);
+ }
+ }
+
/**
* Marks posts before a certain time as being mailed already.
*
@@ -211,391 +429,10 @@ public static function moodleoverflow_mark_old_posts_as_mailed($endtime) {
// Define the sql query.
$sql = "UPDATE {moodleoverflow_posts}
- SET mailed = :mailedsuccess
- WHERE (created < :endtime) AND mailed IN (:mailedpending, :mailedreviewsent) AND reviewed = 1";
+ SET mailed = :mailedsuccess
+ WHERE (created < :endtime) AND mailed IN (:mailedpending, :mailedreviewsent) AND reviewed = 1";
return $DB->execute($sql, $params);
}
- /**
- * Removes unnecessary information from the user records for the mail generation.
- *
- * @param stdClass $user
- */
- public static function moodleoverflow_minimise_user_record(stdClass $user) {
- // Remove all information for the mail generation that are not needed.
- unset($user->institution);
- unset($user->department);
- unset($user->address);
- unset($user->city);
- unset($user->url);
- unset($user->currentlogin);
- unset($user->description);
- unset($user->descriptionformat);
- }
-
- /**
- * Check for a single post if the mail should be send. This includes:
- * 1) Does a) the moodleoverflow
- * b) moodleoverflow discussion
- * c) course module
- * still exists?
- * 2) Is the user subscriped?
- * @param stdClass $post
- * @param array $mailcount
- * @param array $users
- * @param array $discussions
- * @param array $errorcount
- * @param array $posts
- * @param int $postid
- * @param array $moodleoverflows
- * @param array $courses
- * @param array $coursemodules
- * @return void
- * @throws \coding_exception
- * @throws \dml_exception
- */
- private static function check_post($post, array &$mailcount, array &$users, array &$discussions, array &$errorcount,
- array &$posts, int $postid, array &$moodleoverflows, array &$courses,
- array &$coursemodules) {
- // Check the cache if the discussion exists.
- $discussionid = $post->discussion;
- if (!self::cache_record('moodleoverflow_discussions', $discussionid, $discussions,
- 'Could not find discussion ', $posts, $postid, true)) {
- return;
- }
-
- // Retrieve the connected moodleoverflow instance from the database.
- $moodleoverflowid = $discussions[$discussionid]->moodleoverflow;
- if (!self::cache_record('moodleoverflow', $moodleoverflowid, $moodleoverflows,
- 'Could not find moodleoverflow ', $posts, $postid, false)) {
- return;
- }
-
- // Retrieve the connected courses from the database.
- $courseid = $moodleoverflows[$moodleoverflowid]->course;
- if (!self::cache_record('course', $courseid, $courses,
- 'Could not find course ', $posts, $postid, false)) {
- return;
- }
-
- // Retrieve the connected course modules from the database.
- if (!isset($coursemodules[$moodleoverflowid])) {
- // Retrieve the coursemodule and update the cache.
- if ($cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflowid, $courseid)) {
- $coursemodules[$moodleoverflowid] = $cm;
- } else {
- mtrace('Could not find course module for moodleoverflow ' . $moodleoverflowid);
- unset($posts[$postid]);
- return;
- }
- }
-
- // Cache subscribed users of each moodleoverflow.
- if (!isset($subscribedusers[$moodleoverflowid])) {
- // Retrieve the context module.
- $modulecontext = context_module::instance($coursemodules[$moodleoverflowid]->id);
-
- // Retrieve all subscribed users.
- $mid = $moodleoverflows[$moodleoverflowid];
- if ($subusers = subscriptions::get_subscribed_users($mid, $modulecontext, 'u.*', true)) {
- // Loop through all subscribed users.
- foreach ($subusers as $postuser) {
- // Save the user into the cache.
- $subscribedusers[$moodleoverflowid][$postuser->id] = $postuser->id;
- self::moodleoverflow_minimise_user_record($postuser);
- self::moodleoverflow_minimise_user_record($postuser);
- $users[$postuser->id] = $postuser;
- }
-
- // Release the memory.
- unset($subusers);
- unset($postuser);
- }
- }
-
- // Initiate the count of the mails send and errors.
- $mailcount[$postid] = 0;
- $errorcount[$postid] = 0;
- }
-
- /**
- * Helper function for check_post(). Caches the a record exists in the database and caches the record if needed.
- * @param string $table
- * @param int $id
- * @param array $cache
- * @param string $errormessage
- * @param array $posts
- * @param int $postid
- * @param bool $fillsubscache If the subscription cache is being filled (only when checking discussion cache)
- * @return bool
- * @throws \dml_exception
- */
- private static function cache_record($table, $id, &$cache, $errormessage, &$posts, $postid, $fillsubscache) {
- global $DB;
- // Check if cache if an record exists already in the cache.
- if (!isset($cache[$id])) {
- // If there is a record in the database, update the cache. Else ignore the post.
- if ($record = $DB->get_record($table, ['id' => $id])) {
- $cache[$id] = $record;
- if ($fillsubscache) {
- subscriptions::fill_subscription_cache($record->moodleoverflow);
- subscriptions::fill_discussion_subscription_cache($record->moodleoverflow);
- }
- } else {
- mtrace($errormessage . $id);
- unset($posts[$postid]);
- return false;
- }
- }
- return true;
- }
-
-
- /**
- * Send the Mail with information of the post depending on theinformation available.
- * E.g. anonymous post do not include names, users who want resumes do not get single mails.
- * @param stdClass $userto
- * @param stdClass $post
- * @param array $coursemodules
- * @param array $errorcount
- * @param array $discussions
- * @param array $moodleoverflows
- * @param array $courses
- * @param array $mailcount
- * @param array $users
- * @param stdClass $site
- * @param stdClass $textout
- * @param stdClass $htmlout
- * @return void
- * @throws \coding_exception
- * @throws \dml_exception
- * @throws \moodle_exception
- */
- private static function send_post($userto, $post, array &$coursemodules, array &$errorcount,
- array &$discussions, array &$moodleoverflows, array &$courses, array &$mailcount,
- array &$users, $site, $textout, $htmlout) {
- global $DB, $CFG;
-
- // Initiate variables for the post.
- $discussion = $discussions[$post->discussion];
- $moodleoverflow = $moodleoverflows[$discussion->moodleoverflow];
- $course = $courses[$moodleoverflow->course];
- $cm =& $coursemodules[$moodleoverflow->id];
- $modulecontext = context_module::instance($cm->id);
-
- // Check if user wants a resume.
- // in this case: make a new dataset in "moodleoverflow_mail_info" to save the posts data.
- // Dataset from moodleoverflow_mail_info will be send later in a mail.
- $usermailsetting = $userto->maildigest;
- if ($usermailsetting != 0) {
- $dataobject = new stdClass();
- $dataobject->userid = $userto->id;
- $dataobject->courseid = $course->id;
- $dataobject->forumid = $moodleoverflow->id;
- $dataobject->forumdiscussionid = $discussion->id;
- $record = $DB->get_record('moodleoverflow_mail_info',
- ['userid' => $dataobject->userid,
- 'courseid' => $dataobject->courseid,
- 'forumid' => $dataobject->forumid,
- 'forumdiscussionid' => $dataobject->forumdiscussionid, ],
- 'numberofposts, id');
- if (is_object($record)) {
- $dataset = $record;
- $dataobject->numberofposts = $dataset->numberofposts + 1;
- $dataobject->id = $dataset->id;
- $DB->update_record('moodleoverflow_mail_info', $dataobject);
- } else {
- $dataobject->numberofposts = 1;
- $DB->insert_record('moodleoverflow_mail_info', $dataobject);
- }
- return;
- }
-
- // Check whether the user is subscribed.
- if (!isset($subscribedusers[$moodleoverflow->id][$userto->id])) {
- return;
- }
-
- // Check whether the user is subscribed to the discussion.
- $uid = $userto->id;
- if (!subscriptions::is_subscribed($uid, $moodleoverflow, $modulecontext, $post->discussion)) {
- return;
- }
-
- // Check whether the user unsubscribed to the discussion after it was created.
- $subnow = subscriptions::fetch_discussion_subscription($moodleoverflow->id, $userto->id);
- if ($subnow && isset($subnow[$post->discussion]) && ($subnow[$post->discussion] > $post->created)) {
- return;
- }
-
- if (anonymous::is_post_anonymous($discussion, $moodleoverflow, $post->userid)) {
- $userfrom = \core_user::get_noreply_user();
- } else {
- // Check whether the sending user is cached already.
- if (array_key_exists($post->userid, $users)) {
- $userfrom = $users[$post->userid];
- } else {
- // We dont know the the user yet.
-
- // Retrieve the user from the database.
- $userfrom = $DB->get_record('user', ['id' => $post->userid]);
- if ($userfrom) {
- self::moodleoverflow_minimise_user_record($userfrom);
- } else {
- $uid = $post->userid;
- $pid = $post->id;
- mtrace('Could not find user ' . $uid . ', author of post ' . $pid . '. Unable to send message.');
- return;
- }
- }
- }
-
- // Setup roles and languages.
- $CFG->branch >= 402 ? \core\cron::setup_user($userto, $course) : cron_setup_user($userto, $course);
-
- // Cache the users capability to view full names.
- if (!isset($userto->viewfullnames[$moodleoverflow->id])) {
-
- // Find the context module.
- $modulecontext = context_module::instance($cm->id);
-
- // Check the users capabilities.
- $userto->viewfullnames[$moodleoverflow->id] = has_capability('moodle/site:viewfullnames', $modulecontext);
- }
-
- // Cache the users capability to post in the discussion.
- if (!isset($userto->canpost[$discussion->id])) {
-
- // Find the context module.
- $modulecontext = context_module::instance($cm->id);
-
- // Check the users capabilities.
- $canpost = moodleoverflow_user_can_post($modulecontext, $post, $userto->id);
- $userto->canpost[$discussion->id] = $canpost;
- }
-
- // Make sure the current user is allowed to see the post.
- if (!moodleoverflow_user_can_see_post($moodleoverflow, $discussion, $post, $cm)) {
- mtrace('User ' . $userto->id . ' can not see ' . $post->id . '. Not sending message.');
- return;
- }
-
- // Sent the email.
-
- // Preapare to actually send the post now. Build up the content.
- $cleanname = str_replace('"', "'", strip_tags(format_string($moodleoverflow->name)));
- $shortname = format_string($course->shortname, true, ['context' => context_course::instance($course->id)]);
-
- // Define a header to make mails easier to track.
- $emailmessageid = generate_email_messageid('moodlemoodleoverflow' . $moodleoverflow->id);
- $userfrom->customheaders = [
- 'List-Id: "' . $cleanname . '" ' . $emailmessageid,
- 'List-Help: ' . $CFG->wwwroot . '/mod/moodleoverflow/view.php?m=' . $moodleoverflow->id,
- 'Message-ID: ' . generate_email_messageid(hash('sha256', $post->id . 'to' . $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',
- ];
-
- // Cache the users capabilities.
- if (!isset($userto->canpost[$discussion->id])) {
- $canreply = moodleoverflow_user_can_post($modulecontext, $post, $userto->id);
- } else {
- $canreply = $userto->canpost[$discussion->id];
- }
-
- // Format the data.
- $data = new moodleoverflow_email($course, $cm, $moodleoverflow, $discussion, $post, $userfrom, $userto, $canreply);
-
- // Retrieve the unsubscribe-link.
- $userfrom->customheaders[] = sprintf('List-Unsubscribe: <%s>', $data->get_unsubscribediscussionlink());
-
- // Check the capabilities to view full names.
- if (!isset($userto->viewfullnames[$moodleoverflow->id])) {
- $data->viewfullnames = has_capability('moodle/site:viewfullnames', $modulecontext, $userto->id);
- } else {
- $data->viewfullnames = $userto->viewfullnames[$moodleoverflow->id];
- }
-
- // Retrieve needed variables for the mail.
- $var = new stdClass();
- $var->subject = $data->get_subject();
- $var->moodleoverflowname = $cleanname;
- $var->sitefullname = format_string($site->fullname);
- $var->siteshortname = format_string($site->shortname);
- $var->courseidnumber = $data->get_courseidnumber();
- $var->coursefullname = $data->get_coursefullname();
- $var->courseshortname = $data->get_coursename();
- $postsubject = html_to_text(get_string('postmailsubject', 'moodleoverflow', $var), 0);
- $rootid = generate_email_messageid(hash('sha256', $discussion->firstpost . 'to' . $userto->id));
-
- // Check whether the post is a reply.
- if ($post->parent) {
- // Add a reply header.
- $parentid = generate_email_messageid(hash('sha256', $post->parent . 'to' . $userto->id));
- $userfrom->customheaders[] = "In-Reply-To: $parentid";
-
- // Comments need a reference to the starting post as well.
- if ($post->parent != $discussion->firstpost) {
- $userfrom->customheaders[] = "References: $rootid $parentid";
- } else {
- $userfrom->customheaders[] = "References: $parentid";
- }
- }
-
- // Send the post now.
- mtrace('Sending ', '');
-
- // Create the message event.
- $eventdata = new \core\message\message();
- $eventdata->courseid = $course->id;
- $eventdata->component = 'mod_moodleoverflow';
- $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;
-
- // Initiate another message array.
- $small = new stdClass();
- $small->user = fullname($userfrom);
- $formatedstring = format_string($moodleoverflow->name, true);
- $small->moodleoverflowname = "$shortname: " . $formatedstring . ": " . $discussion->name;
- $small->message = $post->message;
-
- // Make sure the language is correct.
- $usertol = $userto->lang;
- $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'moodleoverflow', $small, $usertol);
-
- // Generate the url to view the post.
- $url = '/mod/moodleoverflow/discussion.php';
- $params = ['d' => $discussion->id];
- $contexturl = new moodle_url($url, $params, 'p' . $post->id);
- $eventdata->contexturl = $contexturl->out();
- $eventdata->contexturlname = $discussion->name;
-
- // Actually send the message.
- $mailsent = message_send($eventdata);
-
- // Check whether the sending failed.
- if (!$mailsent) {
- mtrace('Error: mod/moodleoverflow/classes/task/send_mail.php execute(): ' .
- "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]++;
- }
-
- // Tracing message.
- mtrace('post ' . $post->id . ': ' . $discussion->name);
- }
-
}
diff --git a/classes/output/moodleoverflow_email.php b/classes/output/moodleoverflow_email.php
index 27865bb49c..f5c57f7d54 100644
--- a/classes/output/moodleoverflow_email.php
+++ b/classes/output/moodleoverflow_email.php
@@ -25,6 +25,7 @@
namespace mod_moodleoverflow\output;
use mod_moodleoverflow\anonymous;
+use mod_moodleoverflow\subscriptions;
/**
* Moodleoverflow email renderable for use in e-mail.
@@ -251,8 +252,7 @@ public function __set($name, $value) {
public function get_unsubscribediscussionlink() {
// Check whether the moodleoverflow is subscribable.
- $subscribable = \mod_moodleoverflow\subscriptions::is_subscribable($this->moodleoverflow,
- \context_module::instance($this->cm->id));
+ $subscribable = subscriptions::is_subscribable($this->moodleoverflow, \context_module::instance($this->cm->id));
if (!$subscribable) {
return null;
}
@@ -432,7 +432,7 @@ public function get_replylink() {
* @return string
*/
public function get_unsubscribemoodleoverflowlink() {
- if (!\mod_moodleoverflow\subscriptions::is_subscribable($this->moodleoverflow,
+ if (!subscriptions::is_subscribable($this->moodleoverflow,
\context_module::instance($this->cm->id))) {
return null;
}
diff --git a/classes/subscriptions.php b/classes/subscriptions.php
index 14fe64ceb7..e1c0075a22 100644
--- a/classes/subscriptions.php
+++ b/classes/subscriptions.php
@@ -536,11 +536,8 @@ public static function get_subscribed_users($moodleoverflow, $context, $fields =
// Default fields if none are submitted.
if (empty($fields)) {
- if ($CFG->branch >= 311) {
- $allnames = \core_user\fields::for_name()->get_sql('u', false, '', '', false)->selects;
- } else {
- $allnames = get_all_user_name_fields(true, 'u');
- }
+ $allnames = \core_user\fields::for_name()->get_sql('u', false, '', '', false)->selects;
+
$fields = "u.id, u.username, $allnames, u.maildisplay, u.mailformat, u.maildigest,
u.imagealt, u.email, u.emailstop, u.city, u.country, u.lastaccess, u.lastlogin,
u.picture, u.timezone, u.theme, u.lang, u.trackforums, u.mnethostid";
diff --git a/classes/task/send_daily_mail.php b/classes/task/send_daily_mails.php
similarity index 95%
rename from classes/task/send_daily_mail.php
rename to classes/task/send_daily_mails.php
index a4494bea74..807103c140 100644
--- a/classes/task/send_daily_mail.php
+++ b/classes/task/send_daily_mails.php
@@ -26,7 +26,7 @@
/**
* This task sends a daily mail of unread posts
*/
-class send_daily_mail extends \core\task\scheduled_task {
+class send_daily_mails extends \core\task\scheduled_task {
/**
* Return the task's name as shown in admin screens.
@@ -34,7 +34,7 @@ class send_daily_mail extends \core\task\scheduled_task {
* @return string
*/
public function get_name() {
- return get_string('tasksenddailymail', 'mod_moodleoverflow');
+ return get_string('tasksenddailymails', 'mod_moodleoverflow');
}
/**
@@ -88,7 +88,7 @@ public function execute() {
$message = implode('
', $mail);
$userto = $DB->get_record('user', ['id' => $user->userid]);
$from = \core_user::get_noreply_user();
- $subject = get_string('tasksenddailymail', 'mod_moodleoverflow');
+ $subject = get_string('tasksenddailymails', 'mod_moodleoverflow');
email_to_user($userto, $from, $subject, $message);
$DB->delete_records('moodleoverflow_mail_info', ['userid' => $user->userid]);
}
diff --git a/classes/task/send_mails.php b/classes/task/send_mails.php
index d39286e0b6..8fd15f98b7 100644
--- a/classes/task/send_mails.php
+++ b/classes/task/send_mails.php
@@ -18,151 +18,49 @@
* A scheduled task for moodleoverflow cron.
*
* @package mod_moodleoverflow
- * @copyright 2017 Kennet Winter
+ * @copyright 2025 Tamaro Walter
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_moodleoverflow\task;
-use core\session\exception;
-use mod_moodleoverflow\anonymous;
-use mod_moodleoverflow\output\moodleoverflow_email;
-
-defined('MOODLE_INTERNAL') || die();
-require_once(__DIR__ . '/../../locallib.php');
+use coding_exception;
+use core\notification;
+use Exception;
+use lang_string;
+use mod_moodleoverflow\manager\mail_manager;
/**
- * Class for sending mails to users who have subscribed a moodleoverflow.
+ * Class for sending mails to users that need to review a moodleoverflow post.
*
* @package mod_moodleoverflow
- * @copyright 2017 Kennet Winter
+ * @copyright 2025 Tamaro Walter
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class send_mails extends \core\task\scheduled_task {
/**
- * Get a descriptive name for this task (shown to admins).
+ * Get a descriptive name for this task (shwon to admins).
*
- * @return string
+ * @return lang_string|string
+ * @throws coding_exception
*/
- public function get_name() {
+ public function get_name(): lang_string|string {
return get_string('tasksendmails', 'mod_moodleoverflow');
}
/**
* Runs moodleoverflow cron.
+ *
+ * @return bool
*/
- public function execute() {
-
- // Send mail notifications.
- moodleoverflow_send_mails();
-
- $this->send_review_notifications();
-
- // The cron is finished.
- return true;
-
- }
-
- /**
- * Sends initial notifications for needed reviews to all users with review capability.
- */
- public function send_review_notifications() {
- global $DB, $OUTPUT, $PAGE, $CFG;
-
- $rendererhtml = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'htmlemail');
- $renderertext = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'textemail');
-
- $postinfos = $DB->get_records_sql(
- 'SELECT p.*, d.course as cid, d.moodleoverflow as mid, d.id as did FROM {moodleoverflow_posts} p ' .
- 'JOIN {moodleoverflow_discussions} d ON p.discussion = d.id ' .
- "WHERE p.mailed = :mailpending AND p.reviewed = 0 AND p.created < :timecutoff " .
- "ORDER BY d.course, d.moodleoverflow, d.id",
- [
- 'mailpending' => MOODLEOVERFLOW_MAILED_PENDING,
- 'timecutoff' => time() - get_config('moodleoverflow', 'reviewpossibleaftertime'),
- ]
- );
-
- if (empty($postinfos)) {
- mtrace('No review notifications to send.');
- return;
- }
-
- $course = null;
- $moodleoverflow = null;
- $usersto = null;
- $cm = null;
- $discussion = null;
- $success = [];
-
- foreach ($postinfos as $postinfo) {
- if ($course == null || $course->id != $postinfo->cid) {
- $course = get_course($postinfo->cid);
- }
-
- if ($moodleoverflow == null || $moodleoverflow->id != $postinfo->mid) {
- $cm = get_coursemodule_from_instance('moodleoverflow', $postinfo->mid, 0, false, MUST_EXIST);
- $modulecontext = \context_module::instance($cm->id);
- $userswithcapability = get_users_by_capability($modulecontext, 'mod/moodleoverflow:reviewpost');
- $coursecontext = \context_course::instance($course->id);
- $usersenrolled = get_enrolled_users($coursecontext);
- $usersto = [];
- foreach ($userswithcapability as $user) {
- if (in_array($user, $usersenrolled)) {
- array_push($usersto, $user);
- }
- }
-
- $moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $postinfo->mid], '*', MUST_EXIST);
- }
-
- if ($discussion == null || $discussion->id != $postinfo->did) {
- $discussion = $DB->get_record('moodleoverflow_discussions', ['id' => $postinfo->did], '*', MUST_EXIST);
- }
-
- $post = $postinfo;
- $userfrom = \core_user::get_user($postinfo->userid, '*', MUST_EXIST);
- $userfrom->anonymous = anonymous::is_post_anonymous($discussion, $moodleoverflow, $postinfo->userid);
-
- foreach ($usersto as $userto) {
- try {
- // Check for moodle version. Version 401 supported until 8 December 2025.
- if ($CFG->branch >= 402) {
- \core\cron::setup_user($userto, $course);
- } else {
- cron_setup_user($userto, $course);
- }
-
- $maildata = new moodleoverflow_email($course, $cm, $moodleoverflow, $discussion,
- $post, $userfrom, $userto, false);
-
- $textcontext = $maildata->export_for_template($renderertext, true);
- $htmlcontext = $maildata->export_for_template($rendererhtml, false);
-
- email_to_user(
- $userto,
- \core_user::get_noreply_user(),
- get_string('email_review_needed_subject', 'moodleoverflow', $textcontext),
- $OUTPUT->render_from_template('mod_moodleoverflow/email_review_needed_text', $textcontext),
- $OUTPUT->render_from_template('mod_moodleoverflow/email_review_needed_html', $htmlcontext)
- );
- } catch (exception $e) {
- mtrace("Error sending review notification for post $post->id to user $userto->id!");
- }
- }
- $success[] = $post->id;
- }
-
- if (!empty($success)) {
- list($insql, $inparams) = $DB->get_in_or_equal($success);
- $DB->set_field_select(
- 'moodleoverflow_posts', 'mailed', MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS,
- 'id ' . $insql, $inparams
- );
- mtrace('Sent review notifications for ' . count($success) . ' posts successfully!');
+ public function execute(): bool {
+ try {
+ mail_manager::moodleoverflow_send_mails();
+ } catch (Exception $e) {
+ notification::error(get_string('error_sending_mails', 'mod_moodleoverflow', $e->getMessage()));
+ return false;
}
+ return true;
}
-
}
-
diff --git a/classes/task/send_review_mails.php b/classes/task/send_review_mails.php
new file mode 100644
index 0000000000..2466c119d9
--- /dev/null
+++ b/classes/task/send_review_mails.php
@@ -0,0 +1,166 @@
+.
+
+/**
+ * A scheduled task for moodleoverflow cron.
+ *
+ * @package mod_moodleoverflow
+ * @copyright 2017 Kennet Winter
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_moodleoverflow\task;
+
+use core\session\exception;
+use mod_moodleoverflow\anonymous;
+use mod_moodleoverflow\manager\mail_manager;
+use mod_moodleoverflow\output\moodleoverflow_email;
+
+defined('MOODLE_INTERNAL') || die();
+require_once(__DIR__ . '/../../locallib.php');
+
+/**
+ * Class for sending mails to users that need to review a moodleoverflow post.
+ *
+ * @package mod_moodleoverflow
+ * @copyright 2017 Kennet Winter
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class send_review_mails extends \core\task\scheduled_task {
+
+ /**
+ * Get a descriptive name for this task (shown to admins).
+ *
+ * @return string
+ */
+ public function get_name() {
+ return get_string('tasksendreviewmails', 'mod_moodleoverflow');
+ }
+
+ /**
+ * Runs moodleoverflow cron.
+ */
+ public function execute() {
+ // Send review mails.
+ $this->send_review_notifications();
+
+ // The cron is finished.
+ return true;
+
+ }
+
+ /**
+ * Sends initial notifications for needed reviews to all users with review capability.
+ */
+ public function send_review_notifications() {
+ global $DB, $OUTPUT, $PAGE, $CFG;
+
+ $rendererhtml = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'htmlemail');
+ $renderertext = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'textemail');
+
+ $postinfos = $DB->get_records_sql(
+ 'SELECT p.*, d.course as cid, d.moodleoverflow as mid, d.id as did FROM {moodleoverflow_posts} p ' .
+ 'JOIN {moodleoverflow_discussions} d ON p.discussion = d.id ' .
+ "WHERE p.mailed = :mailpending AND p.reviewed = 0 AND p.created < :timecutoff " .
+ "ORDER BY d.course, d.moodleoverflow, d.id",
+ [
+ 'mailpending' => MOODLEOVERFLOW_MAILED_PENDING,
+ 'timecutoff' => time() - get_config('moodleoverflow', 'reviewpossibleaftertime'),
+ ]
+ );
+
+ if (empty($postinfos)) {
+ mtrace('No review notifications to send.');
+ return;
+ }
+
+ $course = null;
+ $moodleoverflow = null;
+ $usersto = null;
+ $cm = null;
+ $discussion = null;
+ $success = [];
+
+ foreach ($postinfos as $postinfo) {
+ if ($course == null || $course->id != $postinfo->cid) {
+ $course = get_course($postinfo->cid);
+ }
+
+ if ($moodleoverflow == null || $moodleoverflow->id != $postinfo->mid) {
+ $cm = get_coursemodule_from_instance('moodleoverflow', $postinfo->mid, 0, false, MUST_EXIST);
+ $modulecontext = \context_module::instance($cm->id);
+ $userswithcapability = get_users_by_capability($modulecontext, 'mod/moodleoverflow:reviewpost');
+ $coursecontext = \context_course::instance($course->id);
+ $usersenrolled = get_enrolled_users($coursecontext);
+ $usersto = [];
+ foreach ($userswithcapability as $user) {
+ if (in_array($user, $usersenrolled)) {
+ array_push($usersto, $user);
+ }
+ }
+
+ $moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $postinfo->mid], '*', MUST_EXIST);
+ }
+
+ if ($discussion == null || $discussion->id != $postinfo->did) {
+ $discussion = $DB->get_record('moodleoverflow_discussions', ['id' => $postinfo->did], '*', MUST_EXIST);
+ }
+
+ $post = $postinfo;
+ $userfrom = \core_user::get_user($postinfo->userid, '*', MUST_EXIST);
+ $userfrom->anonymous = anonymous::is_post_anonymous($discussion, $moodleoverflow, $postinfo->userid);
+
+ foreach ($usersto as $userto) {
+ try {
+ // Check for moodle version. Version 401 supported until 8 December 2025.
+ if ($CFG->branch >= 402) {
+ \core\cron::setup_user($userto, $course);
+ } else {
+ cron_setup_user($userto, $course);
+ }
+
+ $maildata = new moodleoverflow_email($course, $cm, $moodleoverflow, $discussion,
+ $post, $userfrom, $userto, false);
+
+ $textcontext = $maildata->export_for_template($renderertext, true);
+ $htmlcontext = $maildata->export_for_template($rendererhtml, false);
+
+ email_to_user(
+ $userto,
+ \core_user::get_noreply_user(),
+ get_string('email_review_needed_subject', 'moodleoverflow', $textcontext),
+ $OUTPUT->render_from_template('mod_moodleoverflow/email_review_needed_text', $textcontext),
+ $OUTPUT->render_from_template('mod_moodleoverflow/email_review_needed_html', $htmlcontext)
+ );
+ } catch (exception $e) {
+ mtrace("Error sending review notification for post $post->id to user $userto->id!");
+ }
+ }
+ $success[] = $post->id;
+ }
+
+ if (!empty($success)) {
+ list($insql, $inparams) = $DB->get_in_or_equal($success);
+ $DB->set_field_select(
+ 'moodleoverflow_posts', 'mailed', MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS,
+ 'id ' . $insql, $inparams
+ );
+ mtrace('Sent review notifications for ' . count($success) . ' posts successfully!');
+ }
+ }
+
+}
+
diff --git a/db/tasks.php b/db/tasks.php
index 0a688b84f5..750dd1bc41 100644
--- a/db/tasks.php
+++ b/db/tasks.php
@@ -26,9 +26,9 @@
$tasks = [
- // Deliver mail notification about new posts.
+ // Deliver mail notification about posts that need to be reviewed.
[
- 'classname' => 'mod_moodleoverflow\task\send_mails',
+ 'classname' => 'mod_moodleoverflow\task\send_review_mails',
'blocking' => 0,
'minute' => '*',
'hour' => '*',
@@ -48,9 +48,9 @@
'dayofweek' => '*',
],
- // Clean old read records.
+ // Send daily digest mail of unread posts.
[
- 'classname' => 'mod_moodleoverflow\task\send_daily_mail',
+ 'classname' => 'mod_moodleoverflow\task\send_daily_mails',
'blocking' => 0,
'minute' => '0',
'hour' => '17',
@@ -58,4 +58,15 @@
'month' => '*',
'dayofweek' => '*',
],
+
+ // Task to send mail notification of new posts in subscribed discussions.
+ [
+ 'classname' => 'mod_moodleoverflow\task\send_mails',
+ 'blocking' => 0,
+ 'minute' => '*',
+ 'hour' => '*',
+ 'day' => '*',
+ 'month' => '*',
+ 'dayofweek' => '*',
+ ],
];
diff --git a/lang/en/moodleoverflow.php b/lang/en/moodleoverflow.php
index 169b594b81..f0b433d1f3 100644
--- a/lang/en/moodleoverflow.php
+++ b/lang/en/moodleoverflow.php
@@ -384,8 +384,9 @@
$string['switchtoauto'] = 'If you switch to the auto subscription, all enrolled users will be subscribed to this forum!';
$string['switchtooptional'] = 'If you switch to the optional subscription, all currently subscribed users will be unsubscribed from this forum!';
$string['taskcleanreadrecords'] = 'Moodleoverflow maintenance job to clean old read records';
-$string['tasksenddailymail'] = 'Moodleoverflow job to send a daily mail of unread post';
-$string['tasksendmails'] = 'Moodleoverflow maintenance job to send mails';
+$string['tasksenddailymails'] = 'Moodleoverflow job to send a daily summary mail of unread posts';
+$string['tasksendmails'] = 'Moodleoverflow maintenance job to send notification mails of unread posts';
+$string['tasksendreviewmails'] = 'Moodleoverflow job to send a needed review notification mail';
$string['taskupdategrades'] = 'Moodleoverflow maintenance job to update grades';
$string['teacherrating'] = 'Solution';
$string['there_are_no_posts_needing_review'] = 'There are no more posts in this forum that need to be reviewed.';
diff --git a/lib.php b/lib.php
index d8c512e6be..ac849241aa 100644
--- a/lib.php
+++ b/lib.php
@@ -31,6 +31,7 @@
// LEARNWEB-TODO: Adapt functions to the new way of working with posts and discussions (Replace the post/discussion functions).
use core\context\course;
+use mod_moodleoverflow\anonymous;
defined('MOODLE_INTERNAL') || die();
require_once(dirname(__FILE__) . '/locallib.php');
@@ -586,530 +587,6 @@ function moodleoverflow_get_context($moodleoverflowid, $context = null) {
return $context;
}
-/**
- * Sends mail notifications about new posts.
- *
- * @return bool
- */
-function moodleoverflow_send_mails() {
- global $DB, $CFG, $PAGE;
-
- // Get the course object of the top level site.
- $site = get_site();
-
- // Get the main renderers.
- $htmlout = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'htmlemail');
- $textout = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'textemail');
-
- // Initiate the arrays that are saving the users that are subscribed to posts that needs sending.
- $users = [];
- $userscount = 0; // Count($users) is slow. This avoids using this.
-
- // Status arrays.
- $mailcount = [];
- $errorcount = [];
-
- // Cache arrays.
- $discussions = [];
- $moodleoverflows = [];
- $courses = [];
- $coursemodules = [];
- $subscribedusers = [];
-
- // Posts older than x days will not be mailed.
- // This will avoid problems with the cron not beeing ran for a long time.
- $timenow = time();
- $endtime = $timenow - get_config('moodleoverflow', 'maxeditingtime');
- $starttime = $endtime - (get_config('moodleoverflow', 'maxmailingtime') * 60 * 60);
-
- // Retrieve all unmailed posts.
- $posts = moodleoverflow_get_unmailed_posts($starttime, $endtime);
- if ($posts) {
-
- // Mark those posts as mailed.
- if (!moodleoverflow_mark_old_posts_as_mailed($endtime)) {
- mtrace('Errors occurred while trying to mark some posts as being mailed.');
-
- return false;
- }
-
- // Loop through all posts to be mailed.
- foreach ($posts as $postid => $post) {
-
- // Check the cache if the discussion exists.
- $discussionid = $post->discussion;
- if (!isset($discussions[$discussionid])) {
-
- // Retrieve the discussion from the database.
- $discussion = $DB->get_record('moodleoverflow_discussions', ['id' => $post->discussion]);
-
- // If there is a record, update the cache. Else ignore the post.
- if ($discussion) {
- $discussions[$discussionid] = $discussion;
- \mod_moodleoverflow\subscriptions::fill_subscription_cache($discussion->moodleoverflow);
- \mod_moodleoverflow\subscriptions::fill_discussion_subscription_cache($discussion->moodleoverflow);
- } else {
- mtrace('Could not find discussion ' . $discussionid);
- unset($posts[$postid]);
- continue;
- }
- }
-
- // Retrieve the connected moodleoverflow instance from the database.
- $moodleoverflowid = $discussions[$discussionid]->moodleoverflow;
- if (!isset($moodleoverflows[$moodleoverflowid])) {
-
- // Retrieve the record from the database and update the cache.
- $moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $moodleoverflowid]);
- if ($moodleoverflow) {
- $moodleoverflows[$moodleoverflowid] = $moodleoverflow;
- } else {
- mtrace('Could not find moodleoverflow ' . $moodleoverflowid);
- unset($posts[$postid]);
- continue;
- }
- }
-
- // Retrieve the connected courses from the database.
- $courseid = $moodleoverflows[$moodleoverflowid]->course;
- if (!isset($courses[$courseid])) {
-
- // Retrieve the record from the database and update the cache.
- $course = $DB->get_record('course', ['id' => $courseid]);
- if ($course) {
- $courses[$courseid] = $course;
- } else {
- mtrace('Could not find course ' . $courseid);
- unset($posts[$postid]);
- continue;
- }
- }
-
- // Retrieve the connected course modules from the database.
- if (!isset($coursemodules[$moodleoverflowid])) {
-
- // Retrieve the coursemodule and update the cache.
- $cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflowid, $courseid);
- if ($cm) {
- $coursemodules[$moodleoverflowid] = $cm;
- } else {
- mtrace('Could not find course module for moodleoverflow ' . $moodleoverflowid);
- unset($posts[$postid]);
- continue;
- }
- }
-
- // Cache subscribed users of each moodleoverflow.
- if (!isset($subscribedusers[$moodleoverflowid])) {
-
- // Retrieve the context module.
- $modulecontext = context_module::instance($coursemodules[$moodleoverflowid]->id);
-
- // Retrieve all subscribed users.
- $mid = $moodleoverflows[$moodleoverflowid];
- $subusers = \mod_moodleoverflow\subscriptions::get_subscribed_users($mid, $modulecontext, 'u.*', true);
- if ($subusers) {
-
- // Loop through all subscribed users.
- foreach ($subusers as $postuser) {
-
- // Save the user into the cache.
- $subscribedusers[$moodleoverflowid][$postuser->id] = $postuser->id;
- $userscount++;
- moodleoverflow_minimise_user_record($postuser);
- $users[$postuser->id] = $postuser;
- }
-
- // Release the memory.
- unset($subusers);
- unset($postuser);
- }
- }
-
- // Initiate the count of the mails send and errors.
- $mailcount[$postid] = 0;
- $errorcount[$postid] = 0;
- }
- }
-
- // Send mails to the users with information about the posts.
- if ($users && $posts) {
- // Send one mail to every user.
- foreach ($users as $userto) {
- // Terminate if the process takes more time then two minutes.
- core_php_time_limit::raise(120);
-
- // Tracing information.
- mtrace('Processing user ' . $userto->id);
- // Initiate the user caches to save memory.
- $userto = clone($userto);
- $userto->ciewfullnames = [];
- $userto->canpost = [];
- $userto->markposts = [];
-
- // Cache the capabilities of the user.
- // Check for moodle version. Version 401 supported until 8 December 2025.
- if ($CFG->branch >= 402) {
- \core\cron::setup_user($userto);
- } else {
- cron_setup_user($userto);
- }
-
- // Reset the caches.
- foreach ($coursemodules as $moodleoverflowid => $unused) {
- $coursemodules[$moodleoverflowid]->cache = new stdClass();
- $coursemodules[$moodleoverflowid]->cache->caps = [];
- unset($coursemodules[$moodleoverflowid]->uservisible);
- }
-
- // Loop through all posts of this users.
- foreach ($posts as $postid => $post) {
-
- // Initiate variables for the post.
- $discussion = $discussions[$post->discussion];
- $moodleoverflow = $moodleoverflows[$discussion->moodleoverflow];
- $course = $courses[$moodleoverflow->course];
- $cm =& $coursemodules[$moodleoverflow->id];
-
- // Check if user wants a resume.
- // in this case: make a new dataset in "moodleoverflow_mail_info" to save the posts data.
- // Dataset from moodleoverflow_mail_info will be send later in a mail.
- $usermailsetting = $userto->maildigest;
- if ($usermailsetting != 0) {
- $dataobject = new stdClass();
- $dataobject->userid = $userto->id;
- $dataobject->courseid = $course->id;
- $dataobject->forumid = $moodleoverflow->id;
- $dataobject->forumdiscussionid = $discussion->id;
- $record = $DB->get_record('moodleoverflow_mail_info',
- ['userid' => $dataobject->userid,
- 'courseid' => $dataobject->courseid,
- 'forumid' => $dataobject->forumid,
- 'forumdiscussionid' => $dataobject->forumdiscussionid, ],
- 'numberofposts, id');
- if (is_object($record)) {
- $dataset = $record;
- $dataobject->numberofposts = $dataset->numberofposts + 1;
- $dataobject->id = $dataset->id;
- $DB->update_record('moodleoverflow_mail_info', $dataobject);
- } else {
- $dataobject->numberofposts = 1;
- $DB->insert_record('moodleoverflow_mail_info', $dataobject);
- }
- continue;
- }
-
- // Check whether the user is subscribed.
- if (!isset($subscribedusers[$moodleoverflow->id][$userto->id])) {
- continue;
- }
-
- // Check whether the user is subscribed to the discussion.
- $iscm = $coursemodules[$moodleoverflow->id];
- $uid = $userto->id;
- $did = $post->discussion;
- $issubscribed = \mod_moodleoverflow\subscriptions::is_subscribed($uid, $moodleoverflow, $modulecontext, $did);
- if (!$issubscribed) {
- continue;
- }
-
- // Check whether the user unsubscribed to the discussion after it was created.
- $subnow = \mod_moodleoverflow\subscriptions::fetch_discussion_subscription($moodleoverflow->id, $userto->id);
- if ($subnow && isset($subnow[$post->discussion]) && ($subnow[$post->discussion] > $post->created)) {
- continue;
- }
-
- if (\mod_moodleoverflow\anonymous::is_post_anonymous($discussion, $moodleoverflow, $post->userid)) {
- $userfrom = \core_user::get_noreply_user();
- $userfrom->anonymous = true;
- } else {
- // Check whether the sending user is cached already.
- if (array_key_exists($post->userid, $users)) {
- $userfrom = $users[$post->userid];
- $userfrom->anonymous = false;
- } else {
- // We dont know the the user yet.
-
- // Retrieve the user from the database.
- $userfrom = $DB->get_record('user', ['id' => $post->userid]);
- if ($userfrom) {
- moodleoverflow_minimise_user_record($userfrom);
- $userfrom->anonymous = false;
- } else {
- $uid = $post->userid;
- $pid = $post->id;
- mtrace('Could not find user ' . $uid . ', author of post ' . $pid . '. Unable to send message.');
- continue;
- }
- }
- }
-
- // Setup roles and languages.
- // Check for moodle version. Version 401 supported until 8 December 2025.
- if ($CFG->branch >= 402) {
- \core\cron::setup_user($userto, $course);
- } else {
- cron_setup_user($userto, $course);
- }
-
- // Cache the users capability to view full names.
- if (!isset($userto->viewfullnames[$moodleoverflow->id])) {
-
- // Find the context module.
- $modulecontext = context_module::instance($cm->id);
-
- // Check the users capabilities.
- $userto->viewfullnames[$moodleoverflow->id] = has_capability('moodle/site:viewfullnames', $modulecontext);
- }
-
- // Cache the users capability to post in the discussion.
- if (!isset($userto->canpost[$discussion->id])) {
-
- // Find the context module.
- $modulecontext = context_module::instance($cm->id);
-
- // Check the users capabilities.
- $canpost = moodleoverflow_user_can_post($modulecontext, $post, $userto->id);
- $userto->canpost[$discussion->id] = $canpost;
- }
-
- // Make sure the current user is allowed to see the post.
- if (!moodleoverflow_user_can_see_post($moodleoverflow, $discussion, $post, $cm)) {
- mtrace('User ' . $userto->id . ' can not see ' . $post->id . '. Not sending message.');
- continue;
- }
-
- // Sent the email.
-
- // Preapare to actually send the post now. Build up the content.
- $cleanname = str_replace('"', "'", strip_tags(format_string($moodleoverflow->name)));
- $coursecontext = context_course::instance($course->id);
- $shortname = format_string($course->shortname, true, ['context' => $coursecontext]);
-
- // Define a header to make mails easier to track.
- $emailmessageid = generate_email_messageid('moodlemoodleoverflow' . $moodleoverflow->id);
- $userfrom->customheaders = [
- 'List-Id: "' . $cleanname . '" ' . $emailmessageid,
- 'List-Help: ' . $CFG->wwwroot . '/mod/moodleoverflow/view.php?m=' . $moodleoverflow->id,
- 'Message-ID: ' . generate_email_messageid(hash('sha256', $post->id . 'to' . $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',
- ];
-
- // Cache the users capabilities.
- if (!isset($userto->canpost[$discussion->id])) {
- $canreply = moodleoverflow_user_can_post($modulecontext, $post, $userto->id);
- } else {
- $canreply = $userto->canpost[$discussion->id];
- }
-
- // Format the data.
- $data = new \mod_moodleoverflow\output\moodleoverflow_email(
- $course,
- $cm,
- $moodleoverflow,
- $discussion,
- $post,
- $userfrom,
- $userto,
- $canreply
- );
-
- // Retrieve the unsubscribe-link.
- $userfrom->customheaders[] = sprintf('List-Unsubscribe: <%s>', $data->get_unsubscribediscussionlink());
-
- // Check the capabilities to view full names.
- if (!isset($userto->viewfullnames[$moodleoverflow->id])) {
- $data->viewfullnames = has_capability('moodle/site:viewfullnames', $modulecontext, $userto->id);
- } else {
- $data->viewfullnames = $userto->viewfullnames[$moodleoverflow->id];
- }
-
- // Retrieve needed variables for the mail.
- $var = new \stdClass();
- $var->subject = $data->get_subject();
- $var->moodleoverflowname = $cleanname;
- $var->sitefullname = format_string($site->fullname);
- $var->siteshortname = format_string($site->shortname);
- $var->courseidnumber = $data->get_courseidnumber();
- $var->coursefullname = $data->get_coursefullname();
- $var->courseshortname = $data->get_coursename();
- $postsubject = html_to_text(get_string('postmailsubject', 'moodleoverflow', $var), 0);
- $rootid = generate_email_messageid(hash('sha256', $discussion->firstpost . 'to' . $userto->id));
-
- // Check whether the post is a reply.
- if ($post->parent) {
-
- // Add a reply header.
- $parentid = generate_email_messageid(hash('sha256', $post->parent . 'to' . $userto->id));
- $userfrom->customheaders[] = "In-Reply-To: $parentid";
-
- // Comments need a reference to the starting post as well.
- if ($post->parent != $discussion->firstpost) {
- $userfrom->customheaders[] = "References: $rootid $parentid";
- } else {
- $userfrom->customheaders[] = "References: $parentid";
- }
- }
-
- // Send the post now.
- mtrace('Sending ', '');
-
- // Create the message event.
- $eventdata = new \core\message\message();
- $eventdata->courseid = $course->id;
- $eventdata->component = 'mod_moodleoverflow';
- $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;
-
- // Initiate another message array.
- $small = new \stdClass();
- $small->user = fullname($userfrom);
- $formatedstring = format_string($moodleoverflow->name, true);
- $small->moodleoverflowname = "$shortname: " . $formatedstring . ": " . $discussion->name;
- $small->message = $post->message;
-
- // Make sure the language is correct.
- $usertol = $userto->lang;
- $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'moodleoverflow', $small, $usertol);
-
- // Generate the url to view the post.
- $url = '/mod/moodleoverflow/discussion.php';
- $params = ['d' => $discussion->id];
- $contexturl = new moodle_url($url, $params, 'p' . $post->id);
- $eventdata->contexturl = $contexturl->out();
- $eventdata->contexturlname = $discussion->name;
-
- // Actually send the message.
- $mailsent = message_send($eventdata);
-
- // Check whether the sending failed.
- if (!$mailsent) {
- mtrace('Error: mod/moodleoverflow/classes/task/send_mail.php execute(): ' .
- "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]++;
- }
-
- // Tracing message.
- mtrace('post ' . $post->id . ': ' . $discussion->name);
- }
-
- // Release the memory.
- unset($userto);
- }
- }
-
- // Check for all posts whether errors occurred.
- if ($posts) {
-
- // Loop through all posts.
- foreach ($posts as $post) {
-
- // Tracing information.
- mtrace($mailcount[$post->id] . " users were sent post $post->id, '$discussion->name'");
-
- // Mark the posts with errors in the database.
- if ($errorcount[$post->id]) {
- $DB->set_field('moodleoverflow_posts', 'mailed', MOODLEOVERFLOW_MAILED_ERROR, ['id' => $post->id]);
- }
- }
- }
-
- // The task was completed.
- return true;
-}
-
-/**
- * Returns a list of all posts that have not been mailed yet.
- *
- * @param int $starttime posts created after this time
- * @param int $endtime posts created before this time
- *
- * @return array
- */
-function moodleoverflow_get_unmailed_posts($starttime, $endtime) {
- global $DB;
-
- // Set params for the sql query.
- $params = [];
- $params['ptimestart'] = $starttime;
- $params['ptimeend'] = $endtime;
-
- $pendingmail = MOODLEOVERFLOW_MAILED_PENDING;
- $reviewsent = MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS;
-
- // Retrieve the records.
- $sql = "SELECT p.*, d.course, d.moodleoverflow
- FROM {moodleoverflow_posts} p
- JOIN {moodleoverflow_discussions} d ON d.id = p.discussion
- WHERE p.mailed IN ($pendingmail, $reviewsent) AND p.reviewed = 1
- AND COALESCE(p.timereviewed, p.created) >= :ptimestart AND p.created < :ptimeend
- ORDER BY p.modified ASC";
-
- return $DB->get_records_sql($sql, $params);
-}
-
-/**
- * Marks posts before a certain time as being mailed already.
- *
- * @param int $endtime
- *
- * @return bool
- */
-function moodleoverflow_mark_old_posts_as_mailed($endtime) {
- global $DB;
-
- // Get the current timestamp.
- $now = time();
-
- // Define variables for the sql query.
- $params = [];
- $params['mailedsuccess'] = MOODLEOVERFLOW_MAILED_SUCCESS;
- $params['mailedreviewsent'] = MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS;
- $params['now'] = $now;
- $params['endtime'] = $endtime;
- $params['mailedpending'] = MOODLEOVERFLOW_MAILED_PENDING;
-
- // Define the sql query.
- $sql = "UPDATE {moodleoverflow_posts}
- SET mailed = :mailedsuccess
- WHERE (created < :endtime) AND mailed IN (:mailedpending, :mailedreviewsent) AND reviewed = 1";
-
- return $DB->execute($sql, $params);
-
-}
-
-/**
- * Removes unnecessary information from the user records for the mail generation.
- *
- * @param stdClass $user
- */
-function moodleoverflow_minimise_user_record(stdClass &$user) {
-
- // Remove all information for the mail generation that are not needed.
- unset($user->institution);
- unset($user->department);
- unset($user->address);
- unset($user->city);
- unset($user->url);
- unset($user->currentlogin);
- unset($user->description);
- unset($user->descriptionformat);
-}
-
/**
* Adds information about unread messages, that is only required for the course view page (and
* similar), to the course-module object.
diff --git a/locallib.php b/locallib.php
index 44b3a381d5..79fde129e7 100644
--- a/locallib.php
+++ b/locallib.php
@@ -901,7 +901,7 @@ function moodleoverflow_go_back_to($default) {
/**
* Checks whether the user can reply to posts in a discussion.
*
- * @param object $modulecontext
+ * @param context $modulecontext
* @param object $posttoreplyto
* @param bool $considerreviewstatus
* @param int $userid
@@ -1751,7 +1751,7 @@ function moodleoverflow_add_new_post($post) {
// Set to not reviewed, if posts should be reviewed, and user is not a reviewer themselves.
if (review::get_review_level($moodleoverflow) == review::EVERYTHING &&
- !has_capability('mod/moodleoverflow:reviewpost', context_module::instance($cm->id))) {
+ !has_capability('mod/moodleoverflow:reviewpost', $context)) {
$post->reviewed = 0;
} else {
$post->reviewed = 1;
diff --git a/settings.php b/settings.php
index bb2e60dd27..73941449f6 100644
--- a/settings.php
+++ b/settings.php
@@ -137,7 +137,7 @@
$setting->set_updatedcallback('moodleoverflow_update_all_grades');
}
- // Allow teachers to see cummulative userstats.
+ // Allow teachers to see cumulative userstats.
$settings->add(new admin_setting_configcheckbox('moodleoverflow/showuserstats',
get_string('showuserstats', 'moodleoverflow'), get_string('configshowuserstats', 'moodleoverflow'), 0));
diff --git a/tests/dailymail_test.php b/tests/dailymail_test.php
index d0a32a3ee2..9ac3c1e39d 100644
--- a/tests/dailymail_test.php
+++ b/tests/dailymail_test.php
@@ -24,7 +24,8 @@
namespace mod_moodleoverflow;
use mod_moodleoverflow\task\send_mails;
-use mod_moodleoverflow\task\send_daily_mail;
+use mod_moodleoverflow\task\send_daily_mails;
+use stdClass;
defined('MOODLE_INTERNAL') || die();
@@ -38,30 +39,19 @@
* @package mod_moodleoverflow
* @copyright 2023 Tamaro Walter
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @covers \mod_moodleoverflow\task\send_daily_mail::execute
+ * @covers \mod_moodleoverflow\task\send_daily_mails::execute
*/
final class dailymail_test extends \advanced_testcase {
- /** @var \stdClass collection of messages */
- private $sink;
- /** @var \stdClass test course */
- private $course;
-
- /** @var \stdClass test user*/
- private $user;
-
- /** @var \stdClass moodleoverflow instance */
- private $moodleoverflow;
-
- /** @var \stdClass coursemodule instance */
- private $coursemodule;
-
- /** @var \stdClass discussion instance */
- private $discussion;
-
- /** @var \component_generator_base moodleoverflow generator */
- private $generator;
+ /** @var stdClass test environment
+ * This Class contains the test environment:
+ * - the message sink to check if mails were sent.
+ * - a course, moodleoverflow, coursemodule (cm) and discussion instance.
+ * - a teacher and a student user.
+ * - the moodleoverflow generator.
+ */
+ private $env;
/**
* Test setUp.
@@ -69,17 +59,18 @@ final class dailymail_test extends \advanced_testcase {
public function setUp(): void {
parent::setUp();
$this->resetAfterTest();
- set_config('maxeditingtime', -10, 'moodleoverflow');
+ $this->env = new stdClass();
+ set_config('maxeditingtime', -10, 'moodleoverflow');
unset_config('noemailever');
- $this->sink = $this->redirectEmails();
+ $this->env->sink = $this->redirectEmails();
$this->preventResetByRollback();
- $this->redirectMessages();
+
// Create a new course with a moodleoverflow forum.
- $this->course = $this->getDataGenerator()->create_course();
- $location = ['course' => $this->course->id, 'forcesubscribe' => MOODLEOVERFLOW_FORCESUBSCRIBE];
- $this->moodleoverflow = $this->getDataGenerator()->create_module('moodleoverflow', $location);
- $this->coursemodule = get_coursemodule_from_instance('moodleoverflow', $this->moodleoverflow->id);
+ $this->env->course = $this->getDataGenerator()->create_course();
+ $location = ['course' => $this->env->course->id, 'forcesubscribe' => MOODLEOVERFLOW_FORCESUBSCRIBE];
+ $this->env->moodleoverflow = $this->getDataGenerator()->create_module('moodleoverflow', $location);
+ $this->env->coursemodule = get_coursemodule_from_instance('moodleoverflow', $this->env->moodleoverflow->id);
}
/**
@@ -87,8 +78,8 @@ public function setUp(): void {
*/
public function tearDown(): void {
// Clear all caches.
- \mod_moodleoverflow\subscriptions::reset_moodleoverflow_cache();
- \mod_moodleoverflow\subscriptions::reset_discussion_cache();
+ subscriptions::reset_moodleoverflow_cache();
+ subscriptions::reset_discussion_cache();
parent::tearDown();
}
@@ -98,83 +89,75 @@ public function tearDown(): void {
* Function that creates a new user, which adds a new discussion an post to the moodleoverflow.
* @param int $maildigest The maildigest setting: 0 = off , 1 = on
*/
- public function helper_create_user_and_discussion($maildigest) {
+ public function helper_test_set_up($maildigest) {
+ $this->env->generator = $this->getDataGenerator();
// Create a user enrolled in the course as student.
- $this->user = $this->getDataGenerator()->create_user(['firstname' => 'Tamaro', 'email' => 'tamaromail@example.com',
+ $this->env->teacher = $this->env->generator->create_user(['firstname' => 'Tamaro', 'email' => 'tamaromail@example.com',
'maildigest' => $maildigest, ]);
- $this->getDataGenerator()->enrol_user($this->user->id, $this->course->id, 'student');
+ $this->env->student = $this->env->generator->create_user(['firstname' => 'Student1', 'email' => 'student1mail@example.com',
+ 'maildigest' => $maildigest, ]);
+ $this->env->generator->enrol_user($this->env->teacher->id, $this->env->course->id, 'teacher');
+ $this->env->generator->enrol_user($this->env->student->id, $this->env->course->id, 'teacher');
// Create a new discussion and post within the moodleoverflow.
- $this->generator = $this->getDataGenerator()->get_plugin_generator('mod_moodleoverflow');
- $this->discussion = $this->generator->post_to_forum($this->moodleoverflow, $this->user);
+ $this->env->plugingenerator = $this->env->generator->get_plugin_generator('mod_moodleoverflow');
+ $this->env->discussions = $this->env->plugingenerator->post_to_forum($this->env->moodleoverflow, $this->env->student);
}
/**
* Run the send daily mail task.
* @return false|string
*/
- private function helper_run_send_daily_mail() {
- $mailtask = new send_daily_mail();
+ private function helper_run_send_daily_mails() {
+ $dailymailtask = new send_daily_mails();
+ $notificationmailtask = new send_mails();
+ $notificationmailtask->execute();
ob_start();
- $mailtask->execute();
+ $dailymailtask->execute();
$output = ob_get_contents();
ob_end_clean();
return $output;
}
- /**
- * Run the send mails task.
- * @return false|string
- */
- private function helper_run_send_mails() {
- $mailtask = new send_mails();
- ob_start();
- $mailtask->execute();
- $output = ob_get_contents();
- ob_end_clean();
- return $output;
- }
-
-
-
// Begin of test functions.
/**
- * Test if the task send_daily_mail sends a mail to the user.
+ * Test if the task send_daily_mails sends a mail to the user.
*/
public function test_mail_delivery(): void {
// Create user with maildigest = on.
- $this->helper_create_user_and_discussion('1');
+ $this->helper_test_set_up('1');
// Send a mail and test if the mail was sent.
- $this->helper_run_send_mails();
- $this->helper_run_send_daily_mail();
- $messages = $this->sink->count();
+ $this->helper_run_send_daily_mails();
+ $messages = $this->env->sink->count();
$this->assertEquals(1, $messages);
}
/**
- * Test if the task send_daily_mail does not sends email from posts that are not in the course of the user.
+ * Test if the task send_daily_mails does not sends email from posts that are not in the course of the user.
*/
public function test_delivery_not_enrolled(): void {
// Create user with maildigest = on.
- $this->helper_create_user_and_discussion('1');
+ $this->helper_test_set_up('1');
// Create another user, course and a moodleoverflow post.
- $course = $this->getDataGenerator()->create_course();
+ $course = $this->env->generator->create_course();
$location = ['course' => $course->id, 'forcesubscribe' => MOODLEOVERFLOW_FORCESUBSCRIBE];
- $moodleoverflow = $this->getDataGenerator()->create_module('moodleoverflow', $location);
- $student = $this->getDataGenerator()->create_user(['firstname' => 'Ethan', 'email' => 'ethanmail@example.com',
+ $moodleoverflow = $this->env->generator->create_module('moodleoverflow', $location);
+ $student = $this->env->generator->create_user(['firstname' => 'Student2', 'email' => 'student2@example.com',
'maildigest' => '1', ]);
- $this->getDataGenerator()->enrol_user($student->id, $course->id, 'teacher');
- $discussion = $this->generator->post_to_forum($moodleoverflow, $student);
+ $teacher = $this->env->generator->create_user(['firstname' => 'Teacher2', 'email' => 'teacher2@example.com',
+ 'maildigest' => '1', ]);
+ $this->env->generator->enrol_user($student->id, $course->id, 'student');
+ $this->env->generator->enrol_user($teacher->id, $course->id, 'teacher');
+ $discussion = $this->env->plugingenerator->post_to_forum($moodleoverflow, $student);
// Send the mails.
- $this->helper_run_send_mails();
- $this->helper_run_send_daily_mail();
- $messages = $this->sink->count();
- $content = $this->sink->get_messages();
+ $this->helper_run_send_daily_mails();
+ $messages = $this->env->sink->count();
+ $content = $this->env->sink->get_messages();
// There should be 2 mails.
$this->assertEquals(2, $messages);
@@ -184,17 +167,17 @@ public function test_delivery_not_enrolled(): void {
$secondmail = $content[1];
// Depending on the order of the mails, check the recipient and the discussion that is addressed.
if ($firstmail->to == "tamaromail@example.com") {
- $this->assertStringContainsString($this->discussion[0]->name, $firstmail->body);
+ $this->assertStringContainsString($this->env->discussions[0]->name, $firstmail->body);
$this->assertStringNotContainsString($discussion[0]->name, $firstmail->body);
- $this->assertEquals('ethanmail@example.com', $secondmail->to);
+ $this->assertEquals('teacher2@example.com', $secondmail->to);
$this->assertStringContainsString($discussion[0]->name, $secondmail->body);
- $this->assertStringNotContainsString($this->discussion[0]->name, $secondmail->body);
+ $this->assertStringNotContainsString($this->env->discussions[0]->name, $secondmail->body);
} else {
- $this->assertEquals('ethanmail@example.com', $firstmail->to);
+ $this->assertEquals('teacher2@example.com', $firstmail->to);
$this->assertStringContainsString($discussion[0]->name, $firstmail->body);
- $this->assertStringNotContainsString($this->discussion[0]->name, $firstmail->body);
+ $this->assertStringNotContainsString($this->env->discussions[0]->name, $firstmail->body);
$this->assertEquals('tamaromail@example.com', $secondmail->to);
- $this->assertStringContainsString($this->discussion[0]->name, $secondmail->body);
+ $this->assertStringContainsString($this->env->discussions[0]->name, $secondmail->body);
$this->assertStringNotContainsString($discussion[0]->name, $secondmail->body);
}
}
@@ -206,22 +189,21 @@ public function test_delivery_not_enrolled(): void {
public function test_content_of_mail_delivery(): void {
// Create user with maildigest = on.
- $this->helper_create_user_and_discussion('1');
+ $this->helper_test_set_up('1');
// Send the mails and count the messages.
- $this->helper_run_send_mails();
- $this->helper_run_send_daily_mail();
- $content = $this->sink->get_messages();
+ $this->helper_run_send_daily_mails();
+ $content = $this->env->sink->get_messages();
$message = $content[0]->body;
$message = str_replace(["\n\r", "\n", "\r"], '', $message);
- $messagecount = $this->sink->count();
+ $messagecount = $this->env->sink->count();
// Build the text that the mail should have.
// Text structure at get_string('digestunreadpost', moodleoverflow).
- $linktocourse = 'course->id;
- $linktoforum = 'coursemodule->id;
+ $linktocourse = 'env->course->id;
+ $linktoforum = 'env->coursemodule->id;
$linktodiscussion = 'discussion[0]->id;
+ . $this->env->discussions[0]->id;
$this->assertStringContainsString($linktocourse, $message);
$this->assertStringContainsString($linktoforum, $message);
@@ -235,14 +217,14 @@ public function test_content_of_mail_delivery(): void {
*/
public function test_mail_not_send(): void {
// Creat user with daily_mail = off.
- $this->helper_create_user_and_discussion('0');
+ $this->helper_test_set_up('0');
// Now send the mails and test if no mail was sent.
- $this->helper_run_send_mails();
- $this->helper_run_send_daily_mail();
- $messages = $this->sink->count();
+ $this->helper_run_send_daily_mails();
+ $messages = $this->env->sink->get_messages()[0];
- $this->assertEquals(0, $messages);
+ // The teacher now gets a notification mail. The subject of the mail is now different.
+ $this->assertNotEquals($messages->subject, get_string('tasksenddailymails', 'mod_moodleoverflow'));
}
/**
@@ -251,14 +233,13 @@ public function test_mail_not_send(): void {
public function test_records_removed(): void {
global $DB;
// Create user with maildigest = on.
- $this->helper_create_user_and_discussion('1');
+ $this->helper_test_set_up('1');
// Now send the mails.
- $this->helper_run_send_mails();
- $this->helper_run_send_daily_mail();
+ $this->helper_run_send_daily_mails();
// Now check the database if the records of the users are deleted.
- $records = $DB->get_records('moodleoverflow_mail_info', ['userid' => $this->user->id]);
+ $records = $DB->get_records('moodleoverflow_mail_info', ['userid' => $this->env->teacher->id]);
$this->assertEmpty($records);
}
}
diff --git a/tests/generator/lib.php b/tests/generator/lib.php
index c27be6dad5..c8d2873704 100644
--- a/tests/generator/lib.php
+++ b/tests/generator/lib.php
@@ -91,15 +91,6 @@ public function create_instance($record = null, ?array $options = null) {
return parent::create_instance($record, (array) $options);
}
- /**
- * Creates a moodleoverflow discussion.
- *
- * @param null $record
- *
- * @return bool|int
- * @throws coding_exception
- */
-
/**
* Creates a moodleoverflow discussion.
*
diff --git a/tests/notification_mail_test.php b/tests/notification_mail_test.php
new file mode 100644
index 0000000000..c5cb4b0fd2
--- /dev/null
+++ b/tests/notification_mail_test.php
@@ -0,0 +1,147 @@
+.
+
+namespace mod_moodleoverflow;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/completionlib.php');
+
+use mod_moodleoverflow\manager\mail_manager;
+use mod_moodleoverflow\subscriptions;
+use mod_moodleoverflow\task\send_mails;
+use PHPUnit\Exception;
+use PHPUnit\Framework\Attributes\CoversClass;
+use stdClass;
+
+/**
+ * Unit tests for the mod_moodleoverflow plugin.
+ *
+ * @package mod_moodleoverflow
+ * @copyright 2025 Tamaro Walter
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * PHPUnit tests for testing the process of sending notification of new posts via email.
+ *
+ * @package mod_moodleoverflow
+ * @copyright 2025 Tamaro Walter
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ *
+ * @covers \mod_moodleoverflow\manager\mail_manager
+ */
+#[CoversClass(mail_manager::class)]
+final class notification_mail_test extends \advanced_testcase {
+
+ // Attributes.
+
+ /** @var object The data that will be used for testing.
+ * This Class contains the test data:
+ * - one course.
+ * - a moodleoverflow activity
+ * - a teacher.
+ * - two students.
+ * - message and mail sinks to check if mails were sent.
+ */
+ private $testdata;
+
+ // Construct functions.
+ public function setUp(): void {
+ parent::setUp();
+ $this->testdata = new stdClass();
+ $this->resetAfterTest();
+ $this->helper_course_set_up();
+ }
+
+ public function tearDown(): void {
+ $this->testdata = null;
+ parent::tearDown();
+ }
+
+ // Tests.
+
+ /**
+ * Test if order of the mails is correct.
+ *
+ * @return void
+ * @covers \mod_moodleoverflow\task\send_mails
+ */
+ public function test_sortorder(): void {
+ global $DB;
+ $result = $this->helper_run_task();
+ $this->assertTrue(true);
+ /* LEARNWEB-TODO: Add tests. A simple test coverage of the notification mails are in review_test.php for now.
+ They need to be removed from there and added here (+extending test cases). */
+ }
+
+ // Helper functions.
+
+ /**
+ * Helper function that creates:
+ * - two courses.
+ * - an assignment in each course.
+ * - an activity completion in the first course.
+ * - a teacher that is enrolled in both courses.
+ * - a student in each course.
+ */
+ private function helper_course_set_up(): void {
+ global $DB;
+ $datagenerator = $this->getDataGenerator();
+ $plugingenerator = $datagenerator->get_plugin_generator('mod_moodleoverflow');
+ $this->testdata->mailsink = $this->redirectEmails();
+ $this->testdata->messagesink = $this->redirectMessages();
+ // Create a new course.
+ $this->testdata->course = $datagenerator->create_course();
+
+ // Create a teacher and a student and enroll them in the course.
+ $this->testdata->teacher = $datagenerator->create_user(['firstname' => 'Tamaro', 'email' => 'tamaromail@example.com',
+ 'maildigest' => 0, ]);
+ $this->testdata->student1 = $datagenerator->create_user(['firstname' => 'Student1', 'email' => 'student1mail@example.com',
+ 'maildigest' => 0, ]);
+ $this->testdata->student2 = $datagenerator->create_user(['firstname' => 'Student1', 'email' => 'student1mail@example.com',
+ 'maildigest' => 0, ]);
+
+ $datagenerator->enrol_user($this->testdata->teacher->id, $this->testdata->course->id, 'teacher');
+ $datagenerator->enrol_user($this->testdata->student1->id, $this->testdata->course->id, 'student');
+ $datagenerator->enrol_user($this->testdata->student2->id, $this->testdata->course->id, 'student');
+
+ // Change configs so that mails will be sent immediately.
+ set_config('reviewpossibleaftertime', -10, 'moodleoverflow');
+ set_config('maxeditingtime', -10, 'moodleoverflow');
+ unset_config('noemailever');
+
+ // Create a moodleoverflow with a discussion from the teacher.
+ $options = ['course' => $this->testdata->course->id, 'forcesubscribe' => MOODLEOVERFLOW_FORCESUBSCRIBE];
+ $this->testdata->moodleoverflow = $datagenerator->create_module('moodleoverflow', $options);
+ $this->testdata->coursemodule = get_coursemodule_from_instance('moodleoverflow', $this->testdata->moodleoverflow->id);
+ $this->testdata->discussion = $plugingenerator->post_to_forum($this->testdata->moodleoverflow, $this->testdata->teacher);
+ }
+
+ /**
+ * Runs the task to send notification mails.
+ * @return false|string
+ */
+ private function helper_run_task() {
+ $mailtask = new send_mails();
+ ob_start();
+ $mailtask->execute();
+ $this->testdata->output = ob_get_contents();
+ ob_end_clean();
+ return false;
+ }
+}
diff --git a/tests/review_test.php b/tests/review_test.php
index 4ef36a3f70..dce3b3f9ef 100644
--- a/tests/review_test.php
+++ b/tests/review_test.php
@@ -24,7 +24,9 @@
namespace mod_moodleoverflow;
+use mod_moodleoverflow\manager\mail_manager;
use mod_moodleoverflow\task\send_mails;
+use mod_moodleoverflow\task\send_review_mails;
defined('MOODLE_INTERNAL') || die();
@@ -62,10 +64,7 @@ final class review_test extends \advanced_testcase {
* @var \phpunit_message_sink
*/
private $mailsink;
- /**
- * @var \phpunit_message_sink
- */
- private $messagesink;
+
/**
* set Up testing data.
@@ -87,8 +86,6 @@ protected function setUp(): void {
unset_config('noemailever');
$this->mailsink = $this->redirectEmails();
-
- $this->messagesink = $this->redirectMessages();
}
/**
@@ -99,10 +96,6 @@ protected function tearDown(): void {
$this->mailsink->clear();
$this->mailsink->close();
unset($this->mailsink);
-
- $this->messagesink->clear();
- $this->messagesink->close();
- unset($this->messagesink);
parent::tearDown();
}
@@ -121,45 +114,46 @@ public function test_forum_review_everything(): void {
$posts = $this->create_post($options);
$this->check_mail_records($posts['teacherpost'], $posts['studentpost'], 1, 0, MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS);
- $this->assertEquals(1, $this->mailsink->count()); // Teacher has to approve student message.
- $this->assertEquals(2, $this->messagesink->count()); // Student and teacher get notification for student message.
+ // There should be one review mail for the teacher.
+ // And 1 notification mail for the student (teacher does not get a notification mail for his own post).
+ $this->assertEquals(2, $this->mailsink->count()); // Teacher has to approve student message.
$this->mailsink->clear();
- $this->messagesink->clear();
$this->assertNull(\mod_moodleoverflow_external::review_approve_post($posts['studentpost']->id));
- $this->run_send_mails();
- $this->run_send_mails(); // Execute twice to ensure no duplicate mails.
+ $this->run_send_notification_mails();
+ $this->run_send_review_mails();
$post = $DB->get_record('moodleoverflow_posts', ['id' => $posts['studentpost']->id]);
$this->assert_matches_properties(['mailed' => MOODLEOVERFLOW_MAILED_SUCCESS, 'reviewed' => 1], $post);
$this->assertNotNull($post->timereviewed ?? null);
- $this->assertEquals(0, $this->mailsink->count());
- $this->assertEquals(2, $this->messagesink->count());
-
- $this->messagesink->clear();
+ // There should be one notification mail for the approved post.
+ $this->assertEquals(1, $this->mailsink->count());
+ $this->mailsink->clear();
+ $this->setUser($this->student);
$studentanswer1 = $this->generator->reply_to_post($posts['teacherpost'], $this->student, false);
$studentanswer2 = $this->generator->reply_to_post($posts['teacherpost'], $this->student, false);
+ $this->setAdminUser();
- $this->run_send_mails();
- $this->run_send_mails(); // Execute twice to ensure no duplicate mails.
+ $this->run_send_notification_mails();
+ $this->run_send_review_mails();
+ // There should be two review mails for the teacher.
$this->assertEquals(2, $this->mailsink->count());
- $this->assertEquals(0, $this->messagesink->count());
$this->mailsink->clear();
$this->assertNotNull(\mod_moodleoverflow_external::review_approve_post($studentanswer1->id));
$this->assertNull(\mod_moodleoverflow_external::review_reject_post($studentanswer2->id, 'This post was not good!'));
- $this->run_send_mails();
- $this->run_send_mails(); // Execute twice to ensure no duplicate mails.
+ $this->run_send_notification_mails();
+ $this->run_send_review_mails();
- $this->assertEquals(1, $this->mailsink->count());
- $this->assertEquals(2, $this->messagesink->count());
+ // One review mail for the teacher and one notification mail for the student.
+ $this->assertEquals(2, $this->mailsink->count());
$rejectionmessage = $this->mailsink->get_messages()[0];
@@ -185,33 +179,27 @@ public function test_forum_review_only_questions(): void {
$posts = $this->create_post($options);
$this->check_mail_records($posts['teacherpost'], $posts['studentpost'], 1, 0, MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS);
- $this->assertEquals(1, $this->mailsink->count()); // Teacher has to approve student message.
- $this->assertEquals(2, $this->messagesink->count()); // Student and teacher get notification for student message.
+ // There should be one review needed mail for the teacher and one notification mail for the student.
+ $this->assertEquals(2, $this->mailsink->count());
$this->mailsink->clear();
- $this->messagesink->clear();
$this->assertNull(\mod_moodleoverflow_external::review_approve_post($posts['studentpost']->id));
- $this->run_send_mails();
- $this->run_send_mails(); // Execute twice to ensure no duplicate mails.
+ $this->run_send_notification_mails();
+ $this->run_send_review_mails();
$post = $DB->get_record('moodleoverflow_posts', ['id' => $posts['studentpost']->id]);
$this->assert_matches_properties(['mailed' => MOODLEOVERFLOW_MAILED_SUCCESS, 'reviewed' => 1], $post);
$this->assertNotNull($post->timereviewed ?? null);
- $this->assertEquals(0, $this->mailsink->count());
- $this->assertEquals(2, $this->messagesink->count());
-
- $this->messagesink->clear();
+ // There should be one notification mail for the student for the approved post.
+ $this->assertEquals(1, $this->mailsink->count());
$studentanswer1 = $this->generator->reply_to_post($posts['teacherpost'], $this->student, false);
$studentanswer2 = $this->generator->reply_to_post($posts['teacherpost'], $this->student, false);
$this->check_mail_records($studentanswer1, $studentanswer2, 1, 1, MOODLEOVERFLOW_MAILED_SUCCESS);
-
- $this->assertEquals(0, $this->mailsink->count());
- $this->assertEquals(4, $this->messagesink->count());
}
/**
@@ -226,26 +214,37 @@ public function test_forum_review_disallowed(): void {
$posts = $this->create_post($options);
$this->check_mail_records($posts['teacherpost'], $posts['studentpost'], 1, 1, MOODLEOVERFLOW_MAILED_SUCCESS);
- $this->assertEquals(0, $this->mailsink->count()); // Teacher has to approve student message.
- $this->assertEquals(4, $this->messagesink->count()); // Student and teacher get notification for student message.
+ // There should be 2 notifications mails (one for each post).
+ $this->assertEquals(2, $this->mailsink->count()); // Teacher has to approve student message.
$this->mailsink->clear();
- $this->messagesink->clear();
+ $this->setUser($this->student);
$studentanswer1 = $this->generator->reply_to_post($posts['teacherpost'], $this->student, false);
$studentanswer2 = $this->generator->reply_to_post($posts['teacherpost'], $this->student, false);
+ $this->setAdminUser();
$this->check_mail_records($studentanswer1, $studentanswer2, 1, 1, MOODLEOVERFLOW_MAILED_SUCCESS);
+ }
- $this->assertEquals(0, $this->mailsink->count());
- $this->assertEquals(4, $this->messagesink->count());
+ /**
+ * Run the send mails task.
+ * @return false|string
+ */
+ private function run_send_review_mails() {
+ $mailtask = new send_review_mails();
+ ob_start();
+ $mailtask->execute();
+ $output = ob_get_contents();
+ ob_end_clean();
+ return $output;
}
/**
* Run the send mails task.
* @return false|string
*/
- private function run_send_mails() {
+ private function run_send_notification_mails() {
$mailtask = new send_mails();
ob_start();
$mailtask->execute();
@@ -307,8 +306,8 @@ private function check_mail_records($teacherpost, $studentpost, $review1, $revie
'reviewed' => $review2, 'timereviewed' => null, ],
$DB->get_record('moodleoverflow_posts', ['id' => $studentpost->id]));
- $this->run_send_mails();
- $this->run_send_mails(); // Execute twice to ensure no duplicate mails.
+ $this->run_send_notification_mails();
+ $this->run_send_review_mails();
$this->assert_matches_properties(['mailed' => MOODLEOVERFLOW_MAILED_SUCCESS,
'reviewed' => $review1, 'timereviewed' => null, ],
diff --git a/version.php b/version.php
index 6f099b71c6..15e7ad3556 100644
--- a/version.php
+++ b/version.php
@@ -26,7 +26,7 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2025070700;
+$plugin->version = 2025072500;
$plugin->requires = 2022112819; // Require Moodle 4.1.
$plugin->supported = [401, 500];
$plugin->component = 'mod_moodleoverflow';