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 forum_mail_list(true);
     mtrace(round(microtime(true) - $before, 1) . 's');
     // Cumulative time spent actually sending emails
     $mailtime = 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})");
         // Set up course details
         course_setup($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 (forum_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);
                                 $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 forum_exception('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}");
         add_to_log($forum->get_course_id(), 'forumng', 'mail ok', 'view.php?' . $forum->get_link_params(forum::PARAM_PLAIN), $counts);
     }
     $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) complete, total: " . round($totaltime, 1) . 's (mail sending ' . round($mailtime, 1) . 's = ' . round(100.0 * $mailtime / $totaltime, 1) . '%)' . $queryinfo);
 }
 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;
 }