protected function get_safety_net($time)
 {
     // The digest safety net is 24 hours earlier because digest posts may
     // be delayed by 24 hours.
     return parent::get_safety_net($time) - 24 * 3600;
 }
 public static function email_normal()
 {
     global $USER, $CFG, $PERF;
     $exceptioncount = 0;
     // Obtain information about all mails that are due for sending
     mtrace('Email processing:');
     $before = microtime(true);
     if (!empty($PERF->dbqueries)) {
         $beforequeries = $PERF->dbqueries;
     }
     mtrace('Initial query: ', '');
     $list = new mod_forumng_mail_list(true);
     mtrace(round(microtime(true) - $before, 1) . 's');
     // Cumulative time spent actually sending emails
     $mailtime = 0;
     $totalemailcount = 0;
     // Forum loop
     while ($list->next_forum($forum, $cm, $context, $course)) {
         self::debug("DEBUG: Forum " . $forum->get_name() . " on course {$course->shortname} " . "(cmid {$cm->id} contextid {$context->id})");
         // We had problems with cron running out of memory when it sends
         // a lot of emails. On the basis that the PHP garbage collector
         // might have 'issues', perhaps it may help to call it manually.
         gc_collect_cycles();
         // Set up course details
         // Note: This code is a bit sketchy; borrowed from cron_setup_user
         $PAGE = new moodle_page();
         $PAGE->set_course($course);
         // Count posts and emails just for logging
         $postcount = 0;
         $emailcount = 0;
         // Get subscribers to forum
         try {
             $subscribers = $forum->get_subscribers();
             self::debug("DEBUG: Subscribers before filter " . count($subscribers), '');
             self::email_filter_subscribers($course, $cm, $forum, $subscribers, false);
             self::debug(", after " . count($subscribers));
             if (count($subscribers) == 0) {
                 continue;
             }
         } catch (coding_exception $e) {
             // If an error occurs while getting subscribers, continue
             // to next forum
             mtrace(' Exception while getting subscribers for forum ' . $forum->get_id());
             mtrace($e->__toString());
             continue;
         }
         while ($list->next_discussion($discussion)) {
             self::debug("DEBUG: Discussion " . $discussion->get_subject() . ' (' . $discussion->get_id() . ')');
             // Evaluate list of users based on this discussion (which holds
             // group info). Organise list by language, timezone and email
             // type.
             $langusers = array();
             foreach ($subscribers as $subscriber) {
                 // Conditions for each subscriber to get this discussion
                 if (self::subscriber_receives_discussion($forum, $discussion, $subscriber)) {
                     $oldlang = $USER->lang;
                     $USER->lang = $subscriber->lang;
                     $lang = current_language();
                     $USER->lang = $oldlang;
                     $langusers[$lang][$subscriber->timezone][$subscriber->emailtype][$subscriber->id] = $subscriber;
                 }
             }
             if (self::debug()) {
                 $debugcount = 0;
                 foreach ($langusers as $lang => $tzusers) {
                     foreach ($tzusers as $timezone => $typeusers) {
                         foreach ($typeusers as $emailtype => $users) {
                             mtrace("DEBUG: Subscribers for lang [{$lang}] " . "tz [{$timezone}] type [{$emailtype}]: " . count($users));
                             $debugcount += count($users);
                         }
                     }
                 }
                 mtrace("DEBUG: Total discussion subscribers: {$debugcount}");
             }
             while ($list->next_post($post, $inreplyto)) {
                 if (self::debug()) {
                     mtrace("DEBUG: Post " . $post->get_id(), '');
                     $debugcount = $emailcount;
                 }
                 try {
                     $from = $post->get_user();
                     // These loops are intended so that we generate identical
                     // emails once only, and can then send them in batches
                     foreach ($langusers as $lang => $tzusers) {
                         foreach ($tzusers as $timezone => $typeusers) {
                             foreach ($typeusers as $emailtype => $users) {
                                 // We get both plaintext and html versions.
                                 // The html version will be blank if set to
                                 // plain text mode.
                                 $post->build_email($inreplyto, $subject, $plaintext, $html, $emailtype & 1, $emailtype & 2, $emailtype & 4, $lang, $timezone);
                                 if ($post->get_asmoderator() == mod_forumng::ASMODERATOR_ANON) {
                                     $from->maildisplay = false;
                                     $from->firstname = get_string('moderator', 'forumng');
                                     $from->lastname = '';
                                 }
                                 $beforemail = microtime(true);
                                 if ($CFG->forumng_usebcc) {
                                     // Use BCC to send all emails at once
                                     $emailcount += self::email_send_bcc($users, $from, $subject, $html, $plaintext, "post " . $post->get_id(), $emailtype & 1, $emailtype & 4);
                                 } else {
                                     // Loop through subscribers, sending mail to
                                     // each one
                                     foreach ($users as $mailto) {
                                         self::email_send($mailto, $from, $subject, $plaintext, $html);
                                         $emailcount++;
                                     }
                                 }
                                 $mailtime += microtime(true) - $beforemail;
                             }
                         }
                     }
                     // Reset exception count; while some posts are
                     // successful, we'll keep trying to send them out
                     $exceptioncount = 0;
                 } catch (exception $e) {
                     mtrace(' Exception while sending post ' . $post->get_id());
                     mtrace($e->__toString());
                     $exceptioncount++;
                     if ($exceptioncount > 100) {
                         throw new moodle_exception('error_system', 'forumng', '', 'Too many post exceptions in a row, aborting');
                     }
                 }
                 $postcount++;
                 if (self::debug()) {
                     mtrace(", sent " . ($emailcount - $debugcount) . " emails");
                 }
             }
         }
         // Trace and log information
         $counts = "{$postcount} posts ({$emailcount} emails) to " . count($subscribers) . " subscribers";
         mtrace("Forum " . $forum->get_name() . ": sent {$counts}");
         $params = array('other' => array('type' => 'sub', 'count' => $counts), 'context' => $forum->get_context());
         $event = \mod_forumng\event\mail_sent::create($params);
         $event->trigger();
         $totalemailcount += $emailcount;
     }
     $queryinfo = '';
     if (!empty($PERF->dbqueries)) {
         $queryinfo = ', ' . ($PERF->dbqueries - $beforequeries) . ' queries';
     }
     $totalpostcount = $list->get_post_count_so_far();
     $totaltime = microtime(true) - $before;
     mtrace("Email processing ({$totalpostcount} new posts, {$totalemailcount} new emails) " . "complete, total: " . round($totaltime, 1) . 's (mail sending ' . round($mailtime, 1) . 's = ' . round(100.0 * $mailtime / $totaltime, 1) . '%)' . $queryinfo);
 }