Пример #1
0
 if (!$cmto->uservisible) {
     print_error('cannotmovenotvisible', 'twf', $return);
 }
 $destinationctx = context_module::instance($cmto->id);
 require_capability('mod/twf:startdiscussion', $destinationctx);
 if (!twf_move_attachments($discussion, $twf->id, $twfto->id)) {
     echo $OUTPUT->notification("Errors occurred while moving attachment directories - check your file permissions");
 }
 // For each subscribed user in this twf and discussion, copy over per-discussion subscriptions if required.
 $discussiongroup = $discussion->groupid == -1 ? 0 : $discussion->groupid;
 $potentialsubscribers = \mod_twf\subscriptions::fetch_subscribed_users($twf, $discussiongroup, $modcontext, 'u.id', true);
 // Pre-seed the subscribed_discussion caches.
 // Firstly for the twf being moved to.
 \mod_twf\subscriptions::fill_subscription_cache($twfto->id);
 // And also for the discussion being moved.
 \mod_twf\subscriptions::fill_subscription_cache($twf->id);
 $subscriptionchanges = array();
 $subscriptiontime = time();
 foreach ($potentialsubscribers as $subuser) {
     $userid = $subuser->id;
     $targetsubscription = \mod_twf\subscriptions::is_subscribed($userid, $twfto, null, $cmto);
     $discussionsubscribed = \mod_twf\subscriptions::is_subscribed($userid, $twf, $discussion->id);
     $twfsubscribed = \mod_twf\subscriptions::is_subscribed($userid, $twf);
     if ($twfsubscribed && !$discussionsubscribed && $targetsubscription) {
         // The user has opted out of this discussion and the move would cause them to receive notifications again.
         // Ensure they are unsubscribed from the discussion still.
         $subscriptionchanges[$userid] = \mod_twf\subscriptions::FORUM_DISCUSSION_UNSUBSCRIBED;
     } else {
         if (!$twfsubscribed && $discussionsubscribed && !$targetsubscription) {
             // The user has opted into this discussion and would otherwise not receive the subscription after the move.
             // Ensure they are subscribed to the discussion still.
Пример #2
0
 /**
  * Test that the subscription cache can be pre-filled.
  */
 public function test_subscription_cache_prefill()
 {
     global $DB;
     $this->resetAfterTest(true);
     // Create a course, with a twf.
     $course = $this->getDataGenerator()->create_course();
     $options = array('course' => $course->id, 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
     $twf = $this->getDataGenerator()->create_module('twf', $options);
     // Create some users.
     $users = $this->helper_create_users($course, 20);
     // Reset the subscription cache.
     \mod_twf\subscriptions::reset_twf_cache();
     // Filling the subscription cache should only use a single query.
     $startcount = $DB->perf_get_reads();
     $this->assertNull(\mod_twf\subscriptions::fill_subscription_cache($twf->id));
     $postfillcount = $DB->perf_get_reads();
     $this->assertEquals(1, $postfillcount - $startcount);
     // Now fetch some subscriptions from that twf - these should use
     // the cache and not perform additional queries.
     foreach ($users as $user) {
         $this->assertTrue(\mod_twf\subscriptions::fetch_subscription_cache($twf->id, $user->id));
     }
     $finalcount = $DB->perf_get_reads();
     $this->assertEquals(0, $finalcount - $postfillcount);
 }
Пример #3
0
/**
 * Function to be run periodically according to the scheduled task.
 *
 * Finds all posts that have yet to be mailed out, and mails them
 * out to all subscribers as well as other maintance tasks.
 *
 * NOTE: Since 2.7.2 this function is run by scheduled task rather
 * than standard cron.
 *
 * @todo MDL-44734 The function will be split up into seperate tasks.
 */
function twf_cron()
{
    global $CFG, $USER, $DB;
    $site = get_site();
    // All users that are subscribed to any post that needs sending,
    // please increase $CFG->extramemorylimit on large sites that
    // send notifications to a large number of users.
    $users = array();
    $userscount = 0;
    // Cached user counter - count($users) in PHP is horribly slow!!!
    // Status arrays.
    $mailcount = array();
    $errorcount = array();
    // caches
    $discussions = array();
    $twfs = array();
    $courses = array();
    $coursemodules = array();
    $subscribedusers = array();
    $messageinboundhandlers = array();
    // Posts older than 2 days will not be mailed.  This is to avoid the problem where
    // cron has not been running for a long time, and then suddenly people are flooded
    // with mail from the past few weeks or months
    $timenow = time();
    $endtime = $timenow - $CFG->maxeditingtime;
    $starttime = $endtime - 48 * 3600;
    // Two days earlier
    // Get the list of twf subscriptions for per-user per-twf maildigest settings.
    $digestsset = $DB->get_recordset('twf_digests', null, '', 'id, userid, twf, maildigest');
    $digests = array();
    foreach ($digestsset as $thisrow) {
        if (!isset($digests[$thisrow->twf])) {
            $digests[$thisrow->twf] = array();
        }
        $digests[$thisrow->twf][$thisrow->userid] = $thisrow->maildigest;
    }
    $digestsset->close();
    // Create the generic messageinboundgenerator.
    $messageinboundgenerator = new \core\message\inbound\address_manager();
    $messageinboundgenerator->set_handler('\\mod_twf\\message\\inbound\\reply_handler');
    if ($posts = twf_get_unmailed_posts($starttime, $endtime, $timenow)) {
        // Mark them all now as being mailed.  It's unlikely but possible there
        // might be an error later so that a post is NOT actually mailed out,
        // but since mail isn't crucial, we can accept this risk.  Doing it now
        // prevents the risk of duplicated mails, which is a worse problem.
        if (!twf_mark_old_posts_as_mailed($endtime)) {
            mtrace('Errors occurred while trying to mark some posts as being mailed.');
            return false;
            // Don't continue trying to mail them, in case we are in a cron loop
        }
        // checking post validity, and adding users to loop through later
        foreach ($posts as $pid => $post) {
            $discussionid = $post->discussion;
            if (!isset($discussions[$discussionid])) {
                if ($discussion = $DB->get_record('twf_discussions', array('id' => $post->discussion))) {
                    $discussions[$discussionid] = $discussion;
                    \mod_twf\subscriptions::fill_subscription_cache($discussion->twf);
                    \mod_twf\subscriptions::fill_discussion_subscription_cache($discussion->twf);
                } else {
                    mtrace('Could not find discussion ' . $discussionid);
                    unset($posts[$pid]);
                    continue;
                }
            }
            $twfid = $discussions[$discussionid]->twf;
            if (!isset($twfs[$twfid])) {
                if ($twf = $DB->get_record('twf', array('id' => $twfid))) {
                    $twfs[$twfid] = $twf;
                } else {
                    mtrace('Could not find twf ' . $twfid);
                    unset($posts[$pid]);
                    continue;
                }
            }
            $courseid = $twfs[$twfid]->course;
            if (!isset($courses[$courseid])) {
                if ($course = $DB->get_record('course', array('id' => $courseid))) {
                    $courses[$courseid] = $course;
                } else {
                    mtrace('Could not find course ' . $courseid);
                    unset($posts[$pid]);
                    continue;
                }
            }
            if (!isset($coursemodules[$twfid])) {
                if ($cm = get_coursemodule_from_instance('twf', $twfid, $courseid)) {
                    $coursemodules[$twfid] = $cm;
                } else {
                    mtrace('Could not find course module for twf ' . $twfid);
                    unset($posts[$pid]);
                    continue;
                }
            }
            // Save the Inbound Message datakey here to reduce DB queries later.
            $messageinboundgenerator->set_data($pid);
            $messageinboundhandlers[$pid] = $messageinboundgenerator->fetch_data_key();
            // Caching subscribed users of each twf.
            if (!isset($subscribedusers[$twfid])) {
                $modcontext = context_module::instance($coursemodules[$twfid]->id);
                if ($subusers = \mod_twf\subscriptions::fetch_subscribed_users($twfs[$twfid], 0, $modcontext, 'u.*', true)) {
                    foreach ($subusers as $postuser) {
                        // this user is subscribed to this twf
                        $subscribedusers[$twfid][$postuser->id] = $postuser->id;
                        $userscount++;
                        if ($userscount > FORUM_CRON_USER_CACHE) {
                            // Store minimal user info.
                            $minuser = new stdClass();
                            $minuser->id = $postuser->id;
                            $users[$postuser->id] = $minuser;
                        } else {
                            // Cache full user record.
                            twf_cron_minimise_user_record($postuser);
                            $users[$postuser->id] = $postuser;
                        }
                    }
                    // Release memory.
                    unset($subusers);
                    unset($postuser);
                }
            }
            $mailcount[$pid] = 0;
            $errorcount[$pid] = 0;
        }
    }
    if ($users && $posts) {
        $urlinfo = parse_url($CFG->wwwroot);
        $hostname = $urlinfo['host'];
        foreach ($users as $userto) {
            // Terminate if processing of any account takes longer than 2 minutes.
            core_php_time_limit::raise(120);
            mtrace('Processing user ' . $userto->id);
            // Init user caches - we keep the cache for one cycle only, otherwise it could consume too much memory.
            if (isset($userto->username)) {
                $userto = clone $userto;
            } else {
                $userto = $DB->get_record('user', array('id' => $userto->id));
                twf_cron_minimise_user_record($userto);
            }
            $userto->viewfullnames = array();
            $userto->canpost = array();
            $userto->markposts = array();
            // Setup this user so that the capabilities are cached, and environment matches receiving user.
            cron_setup_user($userto);
            // Reset the caches.
            foreach ($coursemodules as $twfid => $unused) {
                $coursemodules[$twfid]->cache = new stdClass();
                $coursemodules[$twfid]->cache->caps = array();
                unset($coursemodules[$twfid]->uservisible);
            }
            foreach ($posts as $pid => $post) {
                $discussion = $discussions[$post->discussion];
                $twf = $twfs[$discussion->twf];
                $course = $courses[$twf->course];
                $cm =& $coursemodules[$twf->id];
                // Do some checks to see if we can bail out now.
                // Only active enrolled users are in the list of subscribers.
                // This does not necessarily mean that the user is subscribed to the twf or to the discussion though.
                if (!isset($subscribedusers[$twf->id][$userto->id])) {
                    // The user does not subscribe to this twf.
                    continue;
                }
                if (!\mod_twf\subscriptions::is_subscribed($userto->id, $twf, $post->discussion, $coursemodules[$twf->id])) {
                    // The user does not subscribe to this twf, or to this specific discussion.
                    continue;
                }
                if ($subscriptiontime = \mod_twf\subscriptions::fetch_discussion_subscription($twf->id, $userto->id)) {
                    // Skip posts if the user subscribed to the discussion after it was created.
                    if (isset($subscriptiontime[$post->discussion]) && $subscriptiontime[$post->discussion] > $post->created) {
                        continue;
                    }
                }
                // Don't send email if the twf is Q&A and the user has not posted.
                // Initial topics are still mailed.
                if ($twf->type == 'qanda' && !twf_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
                    mtrace('Did not email ' . $userto->id . ' because user has not posted in discussion');
                    continue;
                }
                // Get info about the sending user.
                if (array_key_exists($post->userid, $users)) {
                    // We might know the user already.
                    $userfrom = $users[$post->userid];
                    if (!isset($userfrom->idnumber)) {
                        // Minimalised user info, fetch full record.
                        $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
                        twf_cron_minimise_user_record($userfrom);
                    }
                } else {
                    if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
                        twf_cron_minimise_user_record($userfrom);
                        // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
                        if ($userscount <= FORUM_CRON_USER_CACHE) {
                            $userscount++;
                            $users[$userfrom->id] = $userfrom;
                        }
                    } else {
                        mtrace('Could not find user ' . $post->userid . ', author of post ' . $post->id . '. Unable to send message.');
                        continue;
                    }
                }
                // Note: If we want to check that userto and userfrom are not the same person this is probably the spot to do it.
                // Setup global $COURSE properly - needed for roles and languages.
                cron_setup_user($userto, $course);
                // Fill caches.
                if (!isset($userto->viewfullnames[$twf->id])) {
                    $modcontext = context_module::instance($cm->id);
                    $userto->viewfullnames[$twf->id] = has_capability('moodle/site:viewfullnames', $modcontext);
                }
                if (!isset($userto->canpost[$discussion->id])) {
                    $modcontext = context_module::instance($cm->id);
                    $userto->canpost[$discussion->id] = twf_user_can_post($twf, $discussion, $userto, $cm, $course, $modcontext);
                }
                if (!isset($userfrom->groups[$twf->id])) {
                    if (!isset($userfrom->groups)) {
                        $userfrom->groups = array();
                        if (isset($users[$userfrom->id])) {
                            $users[$userfrom->id]->groups = array();
                        }
                    }
                    $userfrom->groups[$twf->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
                    if (isset($users[$userfrom->id])) {
                        $users[$userfrom->id]->groups[$twf->id] = $userfrom->groups[$twf->id];
                    }
                }
                // Make sure groups allow this user to see this email.
                if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {
                    // Groups are being used.
                    if (!groups_group_exists($discussion->groupid)) {
                        // Can't find group - be safe and don't this message.
                        continue;
                    }
                    if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
                        // Do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS.
                        continue;
                    }
                }
                // Make sure we're allowed to see the post.
                if (!twf_user_can_see_post($twf, $discussion, $post, null, $cm)) {
                    mtrace('User ' . $userto->id . ' can not see ' . $post->id . '. Not sending message.');
                    continue;
                }
                // OK so we need to send the email.
                // Does the user want this post in a digest?  If so postpone it for now.
                $maildigest = twf_get_user_maildigest_bulk($digests, $userto, $twf->id);
                if ($maildigest > 0) {
                    // This user wants the mails to be in digest form.
                    $queue = new stdClass();
                    $queue->userid = $userto->id;
                    $queue->discussionid = $discussion->id;
                    $queue->postid = $post->id;
                    $queue->timemodified = $post->created;
                    $DB->insert_record('twf_queue', $queue);
                    continue;
                }
                // Prepare to actually send the post now, and build up the content.
                $cleantwfname = str_replace('"', "'", strip_tags(format_string($twf->name)));
                $userfrom->customheaders = array('List-Id: "' . $cleantwfname . '" <moodletwf' . $twf->id . '@' . $hostname . '>', 'List-Help: ' . $CFG->wwwroot . '/mod/twf/view.php?f=' . $twf->id, 'Message-ID: ' . twf_get_email_message_id($post->id, $userto->id, $hostname), 'X-Course-Id: ' . $course->id, 'X-Course-Name: ' . format_string($course->fullname, true), 'Precedence: Bulk', 'X-Auto-Response-Suppress: All', 'Auto-Submitted: auto-generated');
                if ($post->parent) {
                    // This post is a reply, so add headers for threading (see MDL-22551).
                    $userfrom->customheaders[] = 'In-Reply-To: ' . twf_get_email_message_id($post->parent, $userto->id, $hostname);
                    $userfrom->customheaders[] = 'References: ' . twf_get_email_message_id($post->parent, $userto->id, $hostname);
                }
                $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
                // Generate a reply-to address from using the Inbound Message handler.
                $replyaddress = null;
                if ($userto->canpost[$discussion->id] && array_key_exists($post->id, $messageinboundhandlers)) {
                    $messageinboundgenerator->set_data($post->id, $messageinboundhandlers[$post->id]);
                    $replyaddress = $messageinboundgenerator->generate($userto->id);
                }
                $a = new stdClass();
                $a->courseshortname = $shortname;
                $a->twfname = $cleantwfname;
                $a->subject = format_string($post->subject, true);
                $postsubject = html_to_text(get_string('postmailsubject', 'twf', $a), 0);
                $posttext = twf_make_mail_text($course, $cm, $twf, $discussion, $post, $userfrom, $userto, false, $replyaddress);
                $posthtml = twf_make_mail_html($course, $cm, $twf, $discussion, $post, $userfrom, $userto, $replyaddress);
                // Send the post now!
                mtrace('Sending ', '');
                $eventdata = new \core\message\message();
                $eventdata->component = 'mod_twf';
                $eventdata->name = 'posts';
                $eventdata->userfrom = $userfrom;
                $eventdata->userto = $userto;
                $eventdata->subject = $postsubject;
                $eventdata->fullmessage = $posttext;
                $eventdata->fullmessageformat = FORMAT_PLAIN;
                $eventdata->fullmessagehtml = $posthtml;
                $eventdata->notification = 1;
                $eventdata->replyto = $replyaddress;
                if (!empty($replyaddress)) {
                    // Add extra text to email messages if they can reply back.
                    $textfooter = "\n\n" . get_string('replytopostbyemail', 'mod_twf');
                    $htmlfooter = html_writer::tag('p', get_string('replytopostbyemail', 'mod_twf'));
                    $additionalcontent = array('fullmessage' => array('footer' => $textfooter), 'fullmessagehtml' => array('footer' => $htmlfooter));
                    $eventdata->set_additional_content('email', $additionalcontent);
                }
                // If twf_replytouser is not set then send mail using the noreplyaddress.
                if (empty($CFG->twf_replytouser)) {
                    $eventdata->userfrom = core_user::get_noreply_user();
                }
                $smallmessagestrings = new stdClass();
                $smallmessagestrings->user = fullname($userfrom);
                $smallmessagestrings->twfname = "{$shortname}: " . format_string($twf->name, true) . ": " . $discussion->name;
                $smallmessagestrings->message = $post->message;
                // Make sure strings are in message recipients language.
                $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'twf', $smallmessagestrings, $userto->lang);
                $contexturl = new moodle_url('/mod/twf/discuss.php', array('d' => $discussion->id), 'p' . $post->id);
                $eventdata->contexturl = $contexturl->out();
                $eventdata->contexturlname = $discussion->name;
                $mailresult = message_send($eventdata);
                if (!$mailresult) {
                    mtrace("Error: mod/twf/lib.php twf_cron(): Could not send out mail for id {$post->id} to user {$userto->id}" . " ({$userto->email}) .. not trying again.");
                    $errorcount[$post->id]++;
                } else {
                    $mailcount[$post->id]++;
                    // Mark post as read if twf_usermarksread is set off.
                    if (!$CFG->twf_usermarksread) {
                        $userto->markposts[$post->id] = $post->id;
                    }
                }
                mtrace('post ' . $post->id . ': ' . $post->subject);
            }
            // Mark processed posts as read.
            twf_tp_mark_posts_read($userto, $userto->markposts);
            unset($userto);
        }
    }
    if ($posts) {
        foreach ($posts as $post) {
            mtrace($mailcount[$post->id] . " users were sent post {$post->id}, '{$post->subject}'");
            if ($errorcount[$post->id]) {
                $DB->set_field('twf_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id));
            }
        }
    }
    // release some memory
    unset($subscribedusers);
    unset($mailcount);
    unset($errorcount);
    cron_setup_user();
    $sitetimezone = core_date::get_server_timezone();
    // Now see if there are any digest mails waiting to be sent, and if we should send them
    mtrace('Starting digest processing...');
    core_php_time_limit::raise(300);
    // terminate if not able to fetch all digests in 5 minutes
    if (!isset($CFG->digestmailtimelast)) {
        // To catch the first time
        set_config('digestmailtimelast', 0);
    }
    $timenow = time();
    $digesttime = usergetmidnight($timenow, $sitetimezone) + $CFG->digestmailtime * 3600;
    // Delete any really old ones (normally there shouldn't be any)
    $weekago = $timenow - 7 * 24 * 3600;
    $DB->delete_records_select('twf_queue', "timemodified < ?", array($weekago));
    mtrace('Cleaned old digest records');
    if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
        mtrace('Sending twf digests: ' . userdate($timenow, '', $sitetimezone));
        $digestposts_rs = $DB->get_recordset_select('twf_queue', "timemodified < ?", array($digesttime));
        if ($digestposts_rs->valid()) {
            // We have work to do
            $usermailcount = 0;
            //caches - reuse the those filled before too
            $discussionposts = array();
            $userdiscussions = array();
            foreach ($digestposts_rs as $digestpost) {
                if (!isset($posts[$digestpost->postid])) {
                    if ($post = $DB->get_record('twf_posts', array('id' => $digestpost->postid))) {
                        $posts[$digestpost->postid] = $post;
                    } else {
                        continue;
                    }
                }
                $discussionid = $digestpost->discussionid;
                if (!isset($discussions[$discussionid])) {
                    if ($discussion = $DB->get_record('twf_discussions', array('id' => $discussionid))) {
                        $discussions[$discussionid] = $discussion;
                    } else {
                        continue;
                    }
                }
                $twfid = $discussions[$discussionid]->twf;
                if (!isset($twfs[$twfid])) {
                    if ($twf = $DB->get_record('twf', array('id' => $twfid))) {
                        $twfs[$twfid] = $twf;
                    } else {
                        continue;
                    }
                }
                $courseid = $twfs[$twfid]->course;
                if (!isset($courses[$courseid])) {
                    if ($course = $DB->get_record('course', array('id' => $courseid))) {
                        $courses[$courseid] = $course;
                    } else {
                        continue;
                    }
                }
                if (!isset($coursemodules[$twfid])) {
                    if ($cm = get_coursemodule_from_instance('twf', $twfid, $courseid)) {
                        $coursemodules[$twfid] = $cm;
                    } else {
                        continue;
                    }
                }
                $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
                $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
            }
            $digestposts_rs->close();
            /// Finished iteration, let's close the resultset
            // Data collected, start sending out emails to each user
            foreach ($userdiscussions as $userid => $thesediscussions) {
                core_php_time_limit::raise(120);
                // terminate if processing of any account takes longer than 2 minutes
                cron_setup_user();
                mtrace(get_string('processingdigest', 'twf', $userid), '... ');
                // First of all delete all the queue entries for this user
                $DB->delete_records_select('twf_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
                // Init user caches - we keep the cache for one cycle only,
                // otherwise it would unnecessarily consume memory.
                if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
                    $userto = clone $users[$userid];
                } else {
                    $userto = $DB->get_record('user', array('id' => $userid));
                    twf_cron_minimise_user_record($userto);
                }
                $userto->viewfullnames = array();
                $userto->canpost = array();
                $userto->markposts = array();
                // Override the language and timezone of the "current" user, so that
                // mail is customised for the receiver.
                cron_setup_user($userto);
                $postsubject = get_string('digestmailsubject', 'twf', format_string($site->shortname, true));
                $headerdata = new stdClass();
                $headerdata->sitename = format_string($site->fullname, true);
                $headerdata->userprefs = $CFG->wwwroot . '/user/edit.php?id=' . $userid . '&amp;course=' . $site->id;
                $posttext = get_string('digestmailheader', 'twf', $headerdata) . "\n\n";
                $headerdata->userprefs = '<a target="_blank" href="' . $headerdata->userprefs . '">' . get_string('digestmailprefs', 'twf') . '</a>';
                $posthtml = "<head>";
                /*                foreach ($CFG->stylesheets as $stylesheet) {
                                    //TODO: MDL-21120
                                    $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
                                }*/
                $posthtml .= "</head>\n<body id=\"email\">\n";
                $posthtml .= '<p>' . get_string('digestmailheader', 'twf', $headerdata) . '</p><br /><hr size="1" noshade="noshade" />';
                foreach ($thesediscussions as $discussionid) {
                    core_php_time_limit::raise(120);
                    // to be reset for each post
                    $discussion = $discussions[$discussionid];
                    $twf = $twfs[$discussion->twf];
                    $course = $courses[$twf->course];
                    $cm = $coursemodules[$twf->id];
                    //override language
                    cron_setup_user($userto, $course);
                    // Fill caches
                    if (!isset($userto->viewfullnames[$twf->id])) {
                        $modcontext = context_module::instance($cm->id);
                        $userto->viewfullnames[$twf->id] = has_capability('moodle/site:viewfullnames', $modcontext);
                    }
                    if (!isset($userto->canpost[$discussion->id])) {
                        $modcontext = context_module::instance($cm->id);
                        $userto->canpost[$discussion->id] = twf_user_can_post($twf, $discussion, $userto, $cm, $course, $modcontext);
                    }
                    $strtwfs = get_string('twfs', 'twf');
                    $canunsubscribe = !\mod_twf\subscriptions::is_forcesubscribed($twf);
                    $canreply = $userto->canpost[$discussion->id];
                    $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
                    $posttext .= "\n \n";
                    $posttext .= '=====================================================================';
                    $posttext .= "\n \n";
                    $posttext .= "{$shortname} -> {$strtwfs} -> " . format_string($twf->name, true);
                    if ($discussion->name != $twf->name) {
                        $posttext .= " -> " . format_string($discussion->name, true);
                    }
                    $posttext .= "\n";
                    $posttext .= $CFG->wwwroot . '/mod/twf/discuss.php?d=' . $discussion->id;
                    $posttext .= "\n";
                    $posthtml .= "<p><font face=\"sans-serif\">" . "<a target=\"_blank\" href=\"{$CFG->wwwroot}/course/view.php?id={$course->id}\">{$shortname}</a> -> " . "<a target=\"_blank\" href=\"{$CFG->wwwroot}/mod/twf/index.php?id={$course->id}\">{$strtwfs}</a> -> " . "<a target=\"_blank\" href=\"{$CFG->wwwroot}/mod/twf/view.php?f={$twf->id}\">" . format_string($twf->name, true) . "</a>";
                    if ($discussion->name == $twf->name) {
                        $posthtml .= "</font></p>";
                    } else {
                        $posthtml .= " -> <a target=\"_blank\" href=\"{$CFG->wwwroot}/mod/twf/discuss.php?d={$discussion->id}\">" . format_string($discussion->name, true) . "</a></font></p>";
                    }
                    $posthtml .= '<p>';
                    $postsarray = $discussionposts[$discussionid];
                    sort($postsarray);
                    foreach ($postsarray as $postid) {
                        $post = $posts[$postid];
                        if (array_key_exists($post->userid, $users)) {
                            // we might know him/her already
                            $userfrom = $users[$post->userid];
                            if (!isset($userfrom->idnumber)) {
                                $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
                                twf_cron_minimise_user_record($userfrom);
                            }
                        } else {
                            if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
                                twf_cron_minimise_user_record($userfrom);
                                if ($userscount <= FORUM_CRON_USER_CACHE) {
                                    $userscount++;
                                    $users[$userfrom->id] = $userfrom;
                                }
                            } else {
                                mtrace('Could not find user ' . $post->userid);
                                continue;
                            }
                        }
                        if (!isset($userfrom->groups[$twf->id])) {
                            if (!isset($userfrom->groups)) {
                                $userfrom->groups = array();
                                if (isset($users[$userfrom->id])) {
                                    $users[$userfrom->id]->groups = array();
                                }
                            }
                            $userfrom->groups[$twf->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
                            if (isset($users[$userfrom->id])) {
                                $users[$userfrom->id]->groups[$twf->id] = $userfrom->groups[$twf->id];
                            }
                        }
                        // Headers to help prevent auto-responders.
                        $userfrom->customheaders = array("Precedence: Bulk", 'X-Auto-Response-Suppress: All', 'Auto-Submitted: auto-generated');
                        $maildigest = twf_get_user_maildigest_bulk($digests, $userto, $twf->id);
                        if ($maildigest == 2) {
                            // Subjects and link only
                            $posttext .= "\n";
                            $posttext .= $CFG->wwwroot . '/mod/twf/discuss.php?d=' . $discussion->id;
                            $by = new stdClass();
                            $by->name = fullname($userfrom);
                            $by->date = userdate($post->modified);
                            $posttext .= "\n" . format_string($post->subject, true) . ' ' . get_string("bynameondate", "twf", $by);
                            $posttext .= "\n---------------------------------------------------------------------";
                            $by->name = "<a target=\"_blank\" href=\"{$CFG->wwwroot}/user/view.php?id={$userfrom->id}&amp;course={$course->id}\">{$by->name}</a>";
                            $posthtml .= '<div><a target="_blank" href="' . $CFG->wwwroot . '/mod/twf/discuss.php?d=' . $discussion->id . '#p' . $post->id . '">' . format_string($post->subject, true) . '</a> ' . get_string("bynameondate", "twf", $by) . '</div>';
                        } else {
                            // The full treatment
                            $posttext .= twf_make_mail_text($course, $cm, $twf, $discussion, $post, $userfrom, $userto, true);
                            $posthtml .= twf_make_mail_post($course, $cm, $twf, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
                            // Create an array of postid's for this user to mark as read.
                            if (!$CFG->twf_usermarksread) {
                                $userto->markposts[$post->id] = $post->id;
                            }
                        }
                    }
                    $footerlinks = array();
                    if ($canunsubscribe) {
                        $footerlinks[] = "<a href=\"{$CFG->wwwroot}/mod/twf/subscribe.php?id={$twf->id}\">" . get_string("unsubscribe", "twf") . "</a>";
                    } else {
                        $footerlinks[] = get_string("everyoneissubscribed", "twf");
                    }
                    $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/twf/index.php?id={$twf->course}'>" . get_string("digestmailpost", "twf") . '</a>';
                    $posthtml .= "\n<div class='mdl-right'><font size=\"1\">" . implode('&nbsp;', $footerlinks) . '</font></div>';
                    $posthtml .= '<hr size="1" noshade="noshade" /></p>';
                }
                $posthtml .= '</body>';
                if (empty($userto->mailformat) || $userto->mailformat != 1) {
                    // This user DOESN'T want to receive HTML
                    $posthtml = '';
                }
                $attachment = $attachname = '';
                // Directly email twf digests rather than sending them via messaging, use the
                // site shortname as 'from name', the noreply address will be used by email_to_user.
                $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
                if (!$mailresult) {
                    mtrace("ERROR: mod/twf/cron.php: Could not send out digest mail to user {$userto->id} " . "({$userto->email})... not trying again.");
                } else {
                    mtrace("success.");
                    $usermailcount++;
                    // Mark post as read if twf_usermarksread is set off
                    twf_tp_mark_posts_read($userto, $userto->markposts);
                }
            }
        }
        /// We have finishied all digest emails, update $CFG->digestmailtimelast
        set_config('digestmailtimelast', $timenow);
    }
    cron_setup_user();
    if (!empty($usermailcount)) {
        mtrace(get_string('digestsentusers', 'twf', $usermailcount));
    }
    if (!empty($CFG->twf_lastreadclean)) {
        $timenow = time();
        if ($CFG->twf_lastreadclean + 24 * 3600 < $timenow) {
            set_config('twf_lastreadclean', $timenow);
            mtrace('Removing old twf read tracking info...');
            twf_tp_clean_read_records();
        }
    } else {
        set_config('twf_lastreadclean', time());
    }
    return true;
}