Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 310 and 400] [Versions 39 and 400] [Versions 400 and 402] [Versions 400 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Contains the class responsible for sending emails as a digest.
  19   *
  20   * @package    message_email
  21   * @copyright  2019 Mark Nelson <markn@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  namespace message_email\task;
  25  
  26  use core\task\scheduled_task;
  27  use moodle_recordset;
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  /**
  32   * Class responsible for sending emails as a digest.
  33   *
  34   * @package    message_email
  35   * @copyright  2019 Mark Nelson <markn@moodle.com>
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class send_email_task extends scheduled_task {
  39  
  40      /**
  41       * @var int $maxid This is the maximum id of the message in 'message_email_messages'.
  42       *                 We use this so we know what records to process, as more records may be added
  43       *                 while this task runs.
  44       */
  45      private $maxid;
  46  
  47      /**
  48       * Get a descriptive name for this task (shown to admins).
  49       *
  50       * @return string
  51       */
  52      public function get_name() {
  53          return get_string('tasksendemail', 'message_email');
  54      }
  55  
  56      /**
  57       * Send out emails.
  58       */
  59      public function execute() {
  60          global $DB, $PAGE, $SITE;
  61  
  62          // Get the maximum id we are going to use.
  63          // We use this as records may be added to the table while this task runs.
  64          $this->maxid = $DB->get_field_sql("SELECT MAX(id) FROM {message_email_messages}");
  65  
  66          // We are going to send these emails from 'noreplyaddress'.
  67          $noreplyuser = \core_user::get_noreply_user();
  68  
  69          // The renderers used for sending emails.
  70          $htmlrenderer = $PAGE->get_renderer('message_email', 'email', 'htmlemail');
  71          $textrenderer = $PAGE->get_renderer('message_email', 'email', 'textemail');
  72  
  73          // Keep track of which emails failed to send.
  74          $users = $this->get_unique_users();
  75          foreach ($users as $user) {
  76              cron_setup_user($user);
  77  
  78              $hascontent = false;
  79              $renderable = new \message_email\output\email_digest($user);
  80              $conversations = $this->get_conversations_for_user($user->id);
  81              foreach ($conversations as $conversation) {
  82                  $renderable->add_conversation($conversation);
  83                  $messages = $this->get_users_messages_for_conversation($conversation->id, $user->id);
  84                  if ($messages->valid()) {
  85                      $hascontent = true;
  86                      foreach ($messages as $message) {
  87                          $renderable->add_message($message);
  88                      }
  89                  }
  90                  $messages->close();
  91              }
  92              $conversations->close();
  93              if ($hascontent) {
  94                  $subject = get_string('messagedigestemailsubject', 'message_email', format_string($SITE->fullname));
  95                  $message = $textrenderer->render($renderable);
  96                  $messagehtml = $htmlrenderer->render($renderable);
  97                  if (email_to_user($user, $noreplyuser, $subject, $message, $messagehtml)) {
  98                      $DB->delete_records_select('message_email_messages', 'useridto = ? AND id <= ?', [$user->id, $this->maxid]);
  99                  }
 100              }
 101          }
 102          cron_setup_user();
 103          $users->close();
 104      }
 105  
 106      /**
 107       * Returns an array of users in the given conversation.
 108       *
 109       * @return moodle_recordset A moodle_recordset instance.
 110       */
 111      private function get_unique_users() : moodle_recordset {
 112          global $DB;
 113  
 114          $subsql = 'SELECT DISTINCT(useridto) as id
 115                       FROM {message_email_messages}
 116                      WHERE id <= ?';
 117  
 118          $sql = "SELECT *
 119                    FROM {user} u
 120                   WHERE id IN ($subsql)";
 121  
 122          return $DB->get_recordset_sql($sql, [$this->maxid]);
 123      }
 124  
 125      /**
 126       * Returns an array of unique conversations that require processing.
 127       *
 128       * @param int $userid The ID of the user we are sending a digest to.
 129       * @return moodle_recordset A moodle_recordset instance.
 130       */
 131      private function get_conversations_for_user(int $userid) : moodle_recordset {
 132          global $DB;
 133  
 134          // We shouldn't be joining directly on the group table as group
 135          // conversations may (in the future) be something created that
 136          // isn't related to an actual group in a course. However, for
 137          // now this will have to do before 3.7 code freeze.
 138          // See related MDL-63814.
 139          $sql = "SELECT DISTINCT mc.id, mc.name, c.id as courseid, c.fullname as coursename, g.id as groupid,
 140                                  g.picture
 141                    FROM {message_conversations} mc
 142                    JOIN {groups} g
 143                      ON mc.itemid = g.id
 144                    JOIN {course} c
 145                      ON g.courseid = c.id
 146                    JOIN {message_email_messages} mem
 147                      ON mem.conversationid = mc.id
 148                   WHERE mem.useridto = ?
 149                     AND mem.id <= ?";
 150  
 151          return $DB->get_recordset_sql($sql, [$userid, $this->maxid]);
 152      }
 153  
 154      /**
 155       * Returns the messages to send to a user for a given conversation
 156       *
 157       * @param int $conversationid
 158       * @param int $userid
 159       * @return moodle_recordset A moodle_recordset instance.
 160       */
 161      protected function get_users_messages_for_conversation(int $conversationid, int $userid) : moodle_recordset {
 162          global $DB;
 163  
 164          $userfieldsapi = \core_user\fields::for_userpic();
 165          $usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
 166          $sql = "SELECT $usernamefields, m.*
 167                    FROM {messages} m
 168                    JOIN {user} u
 169                      ON u.id = m.useridfrom
 170                    JOIN {message_email_messages} mem
 171                      ON mem.messageid = m.id
 172                   WHERE mem.useridto = ?
 173                     AND mem.conversationid = ?
 174                     AND mem.id <= ?";
 175  
 176          return $DB->get_recordset_sql($sql, [$userid, $conversationid, $this->maxid]);
 177      }
 178  }