Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
/message/ -> lib.php (source)

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 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   * Library functions for messaging
  19   *
  20   * @package   core_message
  21   * @copyright 2008 Luis Rodrigues
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  define('MESSAGE_SHORTLENGTH', 300);
  26  
  27  define('MESSAGE_HISTORY_ALL', 1);
  28  
  29  define('MESSAGE_SEARCH_MAX_RESULTS', 200);
  30  
  31  define('MESSAGE_TYPE_NOTIFICATION', 'notification');
  32  define('MESSAGE_TYPE_MESSAGE', 'message');
  33  
  34  /**
  35   * Define contants for messaging default settings population. For unambiguity of
  36   * plugin developer intentions we use 4-bit value (LSB numbering):
  37   * bit 0 - whether to send message (MESSAGE_DEFAULT_ENABLED)
  38   * bit 1 - Deprecated: whether to send message (MESSAGE_DEFAULT_LOGGEDOFF). Used to mean only when the user is logged off.
  39   * bit 2..3 - messaging permission (MESSAGE_DISALLOWED|MESSAGE_PERMITTED|MESSAGE_FORCED)
  40   *
  41   * MESSAGE_PERMITTED_MASK contains the mask we use to distinguish permission setting.
  42   */
  43  
  44   /**
  45    * @deprecated since Moodle 4.0. Use MESSAGE_DEFAULT_ENABLED instead.
  46    * @todo Remove on MDL-73284.
  47    */
  48  define('MESSAGE_DEFAULT_LOGGEDIN', 0x01); // 0001
  49  
  50   /**
  51    * @deprecated since Moodle 4.0 MDL-73284. Use MESSAGE_DEFAULT_ENABLED instead.
  52    * @todo Remove on MDL-73284.
  53    */
  54  define('MESSAGE_DEFAULT_LOGGEDOFF', 0x02); // 0010
  55  
  56  define('MESSAGE_DEFAULT_ENABLED', 0x01); // 0001.
  57  
  58  define('MESSAGE_DISALLOWED', 0x04); // 0100.
  59  define('MESSAGE_PERMITTED', 0x08); // 1000.
  60  define('MESSAGE_FORCED', 0x0c); // 1100.
  61  
  62  define('MESSAGE_PERMITTED_MASK', 0x0c); // 1100.
  63  
  64  /**
  65   * Set default value for default outputs permitted setting
  66   * @deprecated since Moodle 4.0 MDL-73284.
  67   * @todo Remove on MDL-73284.
  68   */
  69  define('MESSAGE_DEFAULT_PERMITTED', 'permitted');
  70  
  71  /**
  72   * Set default values for polling.
  73   */
  74  define('MESSAGE_DEFAULT_MIN_POLL_IN_SECONDS', 10);
  75  define('MESSAGE_DEFAULT_MAX_POLL_IN_SECONDS', 2 * MINSECS);
  76  define('MESSAGE_DEFAULT_TIMEOUT_POLL_IN_SECONDS', 5 * MINSECS);
  77  
  78  /**
  79   * To get only read, unread or both messages or notifications.
  80   */
  81  define('MESSAGE_GET_UNREAD', 0);
  82  define('MESSAGE_GET_READ', 1);
  83  define('MESSAGE_GET_READ_AND_UNREAD', 2);
  84  
  85  /**
  86   * Returns the count of unread messages for user. Either from a specific user or from all users.
  87   *
  88   * @deprecated since 3.10
  89   * TODO: MDL-69643
  90   * @param object $user1 the first user. Defaults to $USER
  91   * @param object $user2 the second user. If null this function will count all of user 1's unread messages.
  92   * @return int the count of $user1's unread messages
  93   */
  94  function message_count_unread_messages($user1=null, $user2=null) {
  95      global $USER, $DB;
  96  
  97      debugging('message_count_unread_messages is deprecated and no longer used',
  98          DEBUG_DEVELOPER);
  99  
 100      if (empty($user1)) {
 101          $user1 = $USER;
 102      }
 103  
 104      $sql = "SELECT COUNT(m.id)
 105                FROM {messages} m
 106          INNER JOIN {message_conversations} mc
 107                  ON mc.id = m.conversationid
 108          INNER JOIN {message_conversation_members} mcm
 109                  ON mcm.conversationid = mc.id
 110           LEFT JOIN {message_user_actions} mua
 111                  ON (mua.messageid = m.id AND mua.userid = ? AND (mua.action = ? OR mua.action = ?))
 112               WHERE mua.id is NULL
 113                 AND mcm.userid = ?";
 114      $params = [$user1->id, \core_message\api::MESSAGE_ACTION_DELETED, \core_message\api::MESSAGE_ACTION_READ, $user1->id];
 115  
 116      if (!empty($user2)) {
 117          $sql .= " AND m.useridfrom = ?";
 118          $params[] = $user2->id;
 119      } else {
 120          $sql .= " AND m.useridfrom <> ?";
 121          $params[] = $user1->id;
 122      }
 123  
 124      return $DB->count_records_sql($sql, $params);
 125  }
 126  
 127  /**
 128   * Try to guess how to convert the message to html.
 129   *
 130   * @access private
 131   *
 132   * @param stdClass $message
 133   * @param bool $forcetexttohtml
 134   * @return string html fragment
 135   */
 136  function message_format_message_text($message, $forcetexttohtml = false) {
 137      // Note: this is a very nasty hack that tries to work around the weird messaging rules and design.
 138  
 139      $options = new stdClass();
 140      $options->para = false;
 141      $options->blanktarget = true;
 142      $options->trusted = isset($message->fullmessagetrust) ? $message->fullmessagetrust : false;
 143  
 144      $format = $message->fullmessageformat;
 145  
 146      if (strval($message->smallmessage) !== '') {
 147          if (!empty($message->notification)) {
 148              if (strval($message->fullmessagehtml) !== '' or strval($message->fullmessage) !== '') {
 149                  $format = FORMAT_PLAIN;
 150              }
 151          }
 152          $messagetext = $message->smallmessage;
 153  
 154      } else if ($message->fullmessageformat == FORMAT_HTML) {
 155          if (strval($message->fullmessagehtml) !== '') {
 156              $messagetext = $message->fullmessagehtml;
 157          } else {
 158              $messagetext = $message->fullmessage;
 159              $format = FORMAT_MOODLE;
 160          }
 161  
 162      } else {
 163          if (strval($message->fullmessage) !== '') {
 164              $messagetext = $message->fullmessage;
 165          } else {
 166              $messagetext = $message->fullmessagehtml;
 167              $format = FORMAT_HTML;
 168          }
 169      }
 170  
 171      if ($forcetexttohtml) {
 172          // This is a crazy hack, why not set proper format when creating the notifications?
 173          if ($format === FORMAT_PLAIN) {
 174              $format = FORMAT_MOODLE;
 175          }
 176      }
 177      return format_text($messagetext, $format, $options);
 178  }
 179  
 180  /**
 181   * Search through course users.
 182   *
 183   * If $courseids contains the site course then this function searches
 184   * through all undeleted and confirmed users.
 185   *
 186   * @param int|array $courseids Course ID or array of course IDs.
 187   * @param string $searchtext the text to search for.
 188   * @param string $sort the column name to order by.
 189   * @param string|array $exceptions comma separated list or array of user IDs to exclude.
 190   * @return array An array of {@link $USER} records.
 191   */
 192  function message_search_users($courseids, $searchtext, $sort='', $exceptions='') {
 193      global $CFG, $USER, $DB;
 194  
 195      // Basic validation to ensure that the parameter $courseids is not an empty array or an empty value.
 196      if (!$courseids) {
 197          $courseids = array(SITEID);
 198      }
 199  
 200      // Allow an integer to be passed.
 201      if (!is_array($courseids)) {
 202          $courseids = array($courseids);
 203      }
 204  
 205      $fullname = $DB->sql_fullname();
 206      $userfieldsapi = \core_user\fields::for_userpic();
 207      $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
 208  
 209      if (!empty($sort)) {
 210          $order = ' ORDER BY '. $sort;
 211      } else {
 212          $order = '';
 213      }
 214  
 215      $params = array(
 216          'userid' => $USER->id,
 217          'userid2' => $USER->id,
 218          'query' => "%$searchtext%"
 219      );
 220  
 221      if (empty($exceptions)) {
 222          $exceptions = array();
 223      } else if (!empty($exceptions) && is_string($exceptions)) {
 224          $exceptions = explode(',', $exceptions);
 225      }
 226  
 227      // Ignore self and guest account.
 228      $exceptions[] = $USER->id;
 229      $exceptions[] = $CFG->siteguest;
 230  
 231      // Exclude exceptions from the search result.
 232      list($except, $params_except) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'param', false);
 233      $except = ' AND u.id ' . $except;
 234      $params = array_merge($params_except, $params);
 235  
 236      if (in_array(SITEID, $courseids)) {
 237          // Search on site level.
 238          return $DB->get_records_sql("SELECT $ufields, mc.id as contactlistid, mub.id as userblockedid
 239                                         FROM {user} u
 240                                         LEFT JOIN {message_contacts} mc
 241                                              ON mc.contactid = u.id AND mc.userid = :userid
 242                                         LEFT JOIN {message_users_blocked} mub
 243                                              ON mub.userid = :userid2 AND mub.blockeduserid = u.id
 244                                        WHERE u.deleted = '0' AND u.confirmed = '1'
 245                                              AND (".$DB->sql_like($fullname, ':query', false).")
 246                                              $except
 247                                       $order", $params);
 248      } else {
 249          // Search in courses.
 250  
 251          // Getting the context IDs or each course.
 252          $contextids = array();
 253          foreach ($courseids as $courseid) {
 254              $context = context_course::instance($courseid);
 255              $contextids = array_merge($contextids, $context->get_parent_context_ids(true));
 256          }
 257          list($contextwhere, $contextparams) = $DB->get_in_or_equal(array_unique($contextids), SQL_PARAMS_NAMED, 'context');
 258          $params = array_merge($params, $contextparams);
 259  
 260          // Everyone who has a role assignment in this course or higher.
 261          // TODO: add enabled enrolment join here (skodak)
 262          $users = $DB->get_records_sql("SELECT DISTINCT $ufields, mc.id as contactlistid, mub.id as userblockedid
 263                                           FROM {user} u
 264                                           JOIN {role_assignments} ra ON ra.userid = u.id
 265                                           LEFT JOIN {message_contacts} mc
 266                                                ON mc.contactid = u.id AND mc.userid = :userid
 267                                           LEFT JOIN {message_users_blocked} mub
 268                                                ON mub.userid = :userid2 AND mub.blockeduserid = u.id
 269                                          WHERE u.deleted = '0' AND u.confirmed = '1'
 270                                                AND (".$DB->sql_like($fullname, ':query', false).")
 271                                                AND ra.contextid $contextwhere
 272                                                $except
 273                                         $order", $params);
 274  
 275          return $users;
 276      }
 277  }
 278  
 279  /**
 280   * Format a message for display in the message history
 281   *
 282   * @param object $message the message object
 283   * @param string $format optional date format
 284   * @param string $keywords keywords to highlight
 285   * @param string $class CSS class to apply to the div around the message
 286   * @return string the formatted message
 287   */
 288  function message_format_message($message, $format='', $keywords='', $class='other') {
 289  
 290      static $dateformat;
 291  
 292      //if we haven't previously set the date format or they've supplied a new one
 293      if ( empty($dateformat) || (!empty($format) && $dateformat != $format) ) {
 294          if ($format) {
 295              $dateformat = $format;
 296          } else {
 297              $dateformat = get_string('strftimedatetimeshort');
 298          }
 299      }
 300      $time = userdate($message->timecreated, $dateformat);
 301  
 302      $messagetext = message_format_message_text($message, false);
 303  
 304      if ($keywords) {
 305          $messagetext = highlight($keywords, $messagetext);
 306      }
 307  
 308      $messagetext .= message_format_contexturl($message);
 309  
 310      $messagetext = clean_text($messagetext, FORMAT_HTML);
 311  
 312      return <<<TEMPLATE
 313  <div class='message $class'>
 314      <a name="m{$message->id}"></a>
 315      <span class="message-meta"><span class="time">$time</span></span>: <span class="text">$messagetext</span>
 316  </div>
 317  TEMPLATE;
 318  }
 319  
 320  /**
 321   * Format a the context url and context url name of a message for display
 322   *
 323   * @param object $message the message object
 324   * @return string the formatted string
 325   */
 326  function message_format_contexturl($message) {
 327      $s = null;
 328  
 329      if (!empty($message->contexturl)) {
 330          $displaytext = null;
 331          if (!empty($message->contexturlname)) {
 332              $displaytext= $message->contexturlname;
 333          } else {
 334              $displaytext= $message->contexturl;
 335          }
 336          $s .= html_writer::start_tag('div',array('class' => 'messagecontext'));
 337              $s .= get_string('view').': '.html_writer::tag('a', $displaytext, array('href' => $message->contexturl));
 338          $s .= html_writer::end_tag('div');
 339      }
 340  
 341      return $s;
 342  }
 343  
 344  /**
 345   * Send a message from one user to another. Will be delivered according to the message recipients messaging preferences
 346   *
 347   * @param object $userfrom the message sender
 348   * @param object $userto the message recipient
 349   * @param string $message the message
 350   * @param int $format message format such as FORMAT_PLAIN or FORMAT_HTML
 351   * @return int|false the ID of the new message or false
 352   */
 353  function message_post_message($userfrom, $userto, $message, $format) {
 354      global $PAGE;
 355  
 356      $eventdata = new \core\message\message();
 357      $eventdata->courseid         = 1;
 358      $eventdata->component        = 'moodle';
 359      $eventdata->name             = 'instantmessage';
 360      $eventdata->userfrom         = $userfrom;
 361      $eventdata->userto           = $userto;
 362  
 363      //using string manager directly so that strings in the message will be in the message recipients language rather than the senders
 364      $eventdata->subject          = get_string_manager()->get_string('unreadnewmessage', 'message', fullname($userfrom), $userto->lang);
 365  
 366      if ($format == FORMAT_HTML) {
 367          $eventdata->fullmessagehtml  = $message;
 368          //some message processors may revert to sending plain text even if html is supplied
 369          //so we keep both plain and html versions if we're intending to send html
 370          $eventdata->fullmessage = html_to_text($eventdata->fullmessagehtml);
 371      } else {
 372          $eventdata->fullmessage      = $message;
 373          $eventdata->fullmessagehtml  = '';
 374      }
 375  
 376      $eventdata->fullmessageformat = $format;
 377      $eventdata->smallmessage     = $message;//store the message unfiltered. Clean up on output.
 378      $eventdata->timecreated     = time();
 379      $eventdata->notification    = 0;
 380      // User image.
 381      $userpicture = new user_picture($userfrom);
 382      $userpicture->size = 1; // Use f1 size.
 383      $userpicture->includetoken = $userto->id; // Generate an out-of-session token for the user receiving the message.
 384      $eventdata->customdata = [
 385          'notificationiconurl' => $userpicture->get_url($PAGE)->out(false),
 386          'actionbuttons' => [
 387              'send' => get_string_manager()->get_string('send', 'message', null, $eventdata->userto->lang),
 388          ],
 389          'placeholders' => [
 390              'send' => get_string_manager()->get_string('writeamessage', 'message', null, $eventdata->userto->lang),
 391          ],
 392      ];
 393      return message_send($eventdata);
 394  }
 395  
 396  /**
 397   * Get all message processors, validate corresponding plugin existance and
 398   * system configuration
 399   *
 400   * @param bool $ready only return ready-to-use processors
 401   * @param bool $reset Reset list of message processors (used in unit tests)
 402   * @param bool $resetonly Just reset, then exit
 403   * @return mixed $processors array of objects containing information on message processors
 404   */
 405  function get_message_processors($ready = false, $reset = false, $resetonly = false) {
 406      global $DB, $CFG;
 407  
 408      static $processors;
 409      if ($reset) {
 410          $processors = array();
 411  
 412          if ($resetonly) {
 413              return $processors;
 414          }
 415      }
 416  
 417      if (empty($processors)) {
 418          // Get all processors, ensure the name column is the first so it will be the array key
 419          $processors = $DB->get_records('message_processors', null, 'name DESC', 'name, id, enabled');
 420          foreach ($processors as &$processor){
 421              $processor = \core_message\api::get_processed_processor_object($processor);
 422          }
 423      }
 424      if ($ready) {
 425          // Filter out enabled and system_configured processors
 426          $readyprocessors = $processors;
 427          foreach ($readyprocessors as $readyprocessor) {
 428              if (!($readyprocessor->enabled && $readyprocessor->configured)) {
 429                  unset($readyprocessors[$readyprocessor->name]);
 430              }
 431          }
 432          return $readyprocessors;
 433      }
 434  
 435      return $processors;
 436  }
 437  
 438  /**
 439   * Get all message providers, validate their plugin existance and
 440   * system configuration
 441   *
 442   * @return mixed $processors array of objects containing information on message processors
 443   */
 444  function get_message_providers() {
 445      global $CFG, $DB;
 446  
 447      $pluginman = core_plugin_manager::instance();
 448  
 449      $providers = $DB->get_records('message_providers', null, 'name');
 450  
 451      // Remove all the providers whose plugins are disabled or don't exist
 452      foreach ($providers as $providerid => $provider) {
 453          $plugin = $pluginman->get_plugin_info($provider->component);
 454          if ($plugin) {
 455              if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_MISSING) {
 456                  unset($providers[$providerid]);   // Plugins does not exist
 457                  continue;
 458              }
 459              if ($plugin->is_enabled() === false) {
 460                  unset($providers[$providerid]);   // Plugin disabled
 461                  continue;
 462              }
 463          }
 464      }
 465      return $providers;
 466  }
 467  
 468  /**
 469   * Get an instance of the message_output class for one of the output plugins.
 470   * @param string $type the message output type. E.g. 'email' or 'jabber'.
 471   * @return message_output message_output the requested class.
 472   */
 473  function get_message_processor($type) {
 474      global $CFG;
 475  
 476      // Note, we cannot use the get_message_processors function here, becaues this
 477      // code is called during install after installing each messaging plugin, and
 478      // get_message_processors caches the list of installed plugins.
 479  
 480      $processorfile = $CFG->dirroot . "/message/output/{$type}/message_output_{$type}.php";
 481      if (!is_readable($processorfile)) {
 482          throw new coding_exception('Unknown message processor type ' . $type);
 483      }
 484  
 485      include_once($processorfile);
 486  
 487      $processclass = 'message_output_' . $type;
 488      if (!class_exists($processclass)) {
 489          throw new coding_exception('Message processor ' . $type .
 490                  ' does not define the right class');
 491      }
 492  
 493      return new $processclass();
 494  }
 495  
 496  /**
 497   * Get messaging outputs default (site) preferences
 498   *
 499   * @return object $processors object containing information on message processors
 500   */
 501  function get_message_output_default_preferences() {
 502      return get_config('message');
 503  }
 504  
 505  /**
 506   * Translate message default settings from binary value to the array of string
 507   * representing the settings to be stored. Also validate the provided value and
 508   * use default if it is malformed.
 509   * @todo Remove usage of MESSAGE_DEFAULT_LOGGEDOFF on MDL-73284.
 510   *
 511   * @param  int    $plugindefault Default setting suggested by plugin
 512   * @param  string $processorname The name of processor
 513   * @return array  $settings array of strings in the order: $locked, $enabled.
 514   */
 515  function translate_message_default_setting($plugindefault, $processorname) {
 516  
 517      // Define the default setting.
 518      $processor = get_message_processor($processorname);
 519      $default = $processor->get_default_messaging_settings();
 520  
 521      // Validate the value. It should not exceed the maximum size
 522      if (!is_int($plugindefault) || ($plugindefault > 0x0f)) {
 523          debugging(get_string('errortranslatingdefault', 'message'));
 524          $plugindefault = $default;
 525      }
 526      // Use plugin default setting of 'permitted' is 0
 527      if (!($plugindefault & MESSAGE_PERMITTED_MASK)) {
 528          $plugindefault = $default;
 529      }
 530  
 531      $locked = false;
 532      $enabled = false;
 533  
 534      $permitted = $plugindefault & MESSAGE_PERMITTED_MASK;
 535      switch ($permitted) {
 536          case MESSAGE_FORCED:
 537              $locked = true;
 538              $enabled = true;
 539              break;
 540          case MESSAGE_DISALLOWED:
 541              $locked = true;
 542              $enabled = false;
 543              break;
 544          default:
 545              $locked = false;
 546              // It's equivalent to logged in.
 547              $enabled = $plugindefault & MESSAGE_DEFAULT_ENABLED == MESSAGE_DEFAULT_ENABLED;
 548  
 549              // MESSAGE_DEFAULT_LOGGEDOFF is deprecated but we're checking it just in case.
 550              $loggedoff = $plugindefault & MESSAGE_DEFAULT_LOGGEDOFF == MESSAGE_DEFAULT_LOGGEDOFF;
 551              $enabled = $enabled || $loggedoff;
 552              break;
 553      }
 554  
 555      return array($locked, $enabled);
 556  }
 557  
 558  /**
 559   * Return a list of page types
 560   *
 561   * @param string $pagetype current page type
 562   * @param context|null $parentcontext Block's parent context
 563   * @param context|null $currentcontext Current context of block
 564   * @return array
 565   */
 566  function message_page_type_list(string $pagetype, ?context $parentcontext, ?context $currentcontext): array {
 567      return [
 568          'message-*' => get_string('page-message-x', 'message'),
 569      ];
 570  }
 571  
 572  /**
 573   * Get messages sent or/and received by the specified users.
 574   * Please note that this function return deleted messages too. Besides, only individual conversation messages
 575   * are returned to maintain backwards compatibility.
 576   *
 577   * @param  int      $useridto       the user id who received the message
 578   * @param  int      $useridfrom     the user id who sent the message. -10 or -20 for no-reply or support user
 579   * @param  int      $notifications  1 for retrieving notifications, 0 for messages, -1 for both
 580   * @param  int      $read           Either MESSAGE_GET_READ, MESSAGE_GET_UNREAD or MESSAGE_GET_READ_AND_UNREAD.
 581   * @param  string   $sort           the column name to order by including optionally direction
 582   * @param  int      $limitfrom      limit from
 583   * @param  int      $limitnum       limit num
 584   * @return external_description
 585   * @since  2.8
 586   */
 587  function message_get_messages($useridto, $useridfrom = 0, $notifications = -1, $read = MESSAGE_GET_READ,
 588                                  $sort = 'mr.timecreated DESC', $limitfrom = 0, $limitnum = 0) {
 589      global $DB;
 590  
 591      if (is_bool($read)) {
 592          // Backwards compatibility, this parameter was a bool before 4.0.
 593          $read = (int) $read;
 594      }
 595  
 596      // If the 'useridto' value is empty then we are going to retrieve messages sent by the useridfrom to any user.
 597      $userfieldsapi = \core_user\fields::for_name();
 598      if (empty($useridto)) {
 599          $userfields = $userfieldsapi->get_sql('u', false, 'userto', '', false)->selects;
 600          $messageuseridtosql = 'u.id as useridto';
 601      } else {
 602          $userfields = $userfieldsapi->get_sql('u', false, 'userfrom', '', false)->selects;
 603          $messageuseridtosql = "$useridto as useridto";
 604      }
 605  
 606      // Create the SQL we will be using.
 607      $messagesql = "SELECT mr.*, $userfields, 0 as notification, '' as contexturl, '' as contexturlname,
 608                            mua.timecreated as timeusertodeleted, mua2.timecreated as timeread,
 609                            mua3.timecreated as timeuserfromdeleted, $messageuseridtosql
 610                       FROM {messages} mr
 611                 INNER JOIN {message_conversations} mc
 612                         ON mc.id = mr.conversationid
 613                 INNER JOIN {message_conversation_members} mcm
 614                         ON mcm.conversationid = mc.id ";
 615  
 616      $notificationsql = "SELECT mr.*, $userfields, 1 as notification
 617                            FROM {notifications} mr ";
 618  
 619      $messagejoinsql = "LEFT JOIN {message_user_actions} mua
 620                                ON (mua.messageid = mr.id AND mua.userid = mcm.userid AND mua.action = ?)
 621                         LEFT JOIN {message_user_actions} mua2
 622                                ON (mua2.messageid = mr.id AND mua2.userid = mcm.userid AND mua2.action = ?)
 623                         LEFT JOIN {message_user_actions} mua3
 624                                ON (mua3.messageid = mr.id AND mua3.userid = mr.useridfrom AND mua3.action = ?)";
 625      $messagejoinparams = [\core_message\api::MESSAGE_ACTION_DELETED, \core_message\api::MESSAGE_ACTION_READ,
 626          \core_message\api::MESSAGE_ACTION_DELETED];
 627      $notificationsparams = [];
 628  
 629      // If the 'useridto' value is empty then we are going to retrieve messages sent by the useridfrom to any user.
 630      if (empty($useridto)) {
 631          // Create the messaging query and params.
 632          $messagesql .= "INNER JOIN {user} u
 633                                  ON u.id = mcm.userid
 634                                  $messagejoinsql
 635                               WHERE mr.useridfrom = ?
 636                                 AND mr.useridfrom != mcm.userid
 637                                 AND u.deleted = 0
 638                                 AND mc.type = ? ";
 639          $messageparams = array_merge($messagejoinparams, [$useridfrom, \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL]);
 640  
 641          // Create the notifications query and params.
 642          $notificationsql .= "INNER JOIN {user} u
 643                                       ON u.id = mr.useridto
 644                                    WHERE mr.useridfrom = ?
 645                                      AND u.deleted = 0 ";
 646          $notificationsparams[] = $useridfrom;
 647      } else {
 648          // Create the messaging query and params.
 649          // Left join because useridfrom may be -10 or -20 (no-reply and support users).
 650          $messagesql .= "LEFT JOIN {user} u
 651                                 ON u.id = mr.useridfrom
 652                                 $messagejoinsql
 653                              WHERE mcm.userid = ?
 654                                AND mr.useridfrom != mcm.userid
 655                                AND u.deleted = 0
 656                                AND mc.type = ? ";
 657          $messageparams = array_merge($messagejoinparams, [$useridto, \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL]);
 658  
 659          // If we're dealing with messages only and both useridto and useridfrom are set,
 660          // try to get a conversation between the users. Break early if we can't find one.
 661          if (!empty($useridfrom) && $notifications == 0) {
 662              $messagesql .= " AND mr.useridfrom = ? ";
 663              $messageparams[] = $useridfrom;
 664  
 665              // There should be an individual conversation between the users. If not, we can return early.
 666              $conversationid = \core_message\api::get_conversation_between_users([$useridto, $useridfrom]);
 667              if (empty($conversationid)) {
 668                  return [];
 669              }
 670              $messagesql .= " AND mc.id = ? ";
 671              $messageparams[] = $conversationid;
 672          }
 673  
 674          // Create the notifications query and params.
 675          // Left join because useridfrom may be -10 or -20 (no-reply and support users).
 676          $notificationsql .= "LEFT JOIN {user} u
 677                                      ON (u.id = mr.useridfrom AND u.deleted = 0)
 678                                   WHERE mr.useridto = ? ";
 679          $notificationsparams[] = $useridto;
 680          if (!empty($useridfrom)) {
 681              $notificationsql .= " AND mr.useridfrom = ? ";
 682              $notificationsparams[] = $useridfrom;
 683          }
 684      }
 685      if ($read === MESSAGE_GET_READ) {
 686          $notificationsql .= "AND mr.timeread IS NOT NULL ";
 687      } else if ($read === MESSAGE_GET_UNREAD) {
 688          $notificationsql .= "AND mr.timeread IS NULL ";
 689      }
 690      $messagesql .= "ORDER BY $sort";
 691      $notificationsql .= "ORDER BY $sort";
 692  
 693      // Handle messages if needed.
 694      if ($notifications === -1 || $notifications === 0) {
 695          $messages = $DB->get_records_sql($messagesql, $messageparams, $limitfrom, $limitnum);
 696          if ($read !== MESSAGE_GET_READ_AND_UNREAD) {
 697              // Get rid of the messages that have either been read or not read depending on the value of $read.
 698              $messages = array_filter($messages, function ($message) use ($read) {
 699                  if ($read === MESSAGE_GET_READ) {
 700                      return !is_null($message->timeread);
 701                  }
 702  
 703                  return is_null($message->timeread);
 704              });
 705          }
 706      }
 707  
 708      // All.
 709      if ($notifications === -1) {
 710          return array_merge($messages, $DB->get_records_sql($notificationsql, $notificationsparams, $limitfrom, $limitnum));
 711      } else if ($notifications === 1) { // Just notifications.
 712          return $DB->get_records_sql($notificationsql, $notificationsparams, $limitfrom, $limitnum);
 713      }
 714  
 715      // Just messages.
 716      return $messages;
 717  }
 718  
 719  /**
 720   * Handles displaying processor settings in a fragment.
 721   *
 722   * @param array $args
 723   * @return bool|string
 724   * @throws moodle_exception
 725   */
 726  function message_output_fragment_processor_settings($args = []) {
 727      global $PAGE;
 728  
 729      if (!isset($args['type'])) {
 730          throw new moodle_exception('Must provide a processor type');
 731      }
 732  
 733      if (!isset($args['userid'])) {
 734          throw new moodle_exception('Must provide a userid');
 735      }
 736  
 737      $type = clean_param($args['type'], PARAM_SAFEDIR);
 738      $userid = clean_param($args['userid'], PARAM_INT);
 739  
 740      $user = core_user::get_user($userid, '*', MUST_EXIST);
 741      if (!core_message_can_edit_message_profile($user)) {
 742          throw new moodle_exception('Cannot edit message profile');
 743      }
 744  
 745      $processor = get_message_processor($type);
 746      $providers = message_get_providers_for_user($userid);
 747      $processorwrapper = new stdClass();
 748      $processorwrapper->object = $processor;
 749      $preferences = \core_message\api::get_all_message_preferences([$processorwrapper], $providers, $user);
 750  
 751      $processoroutput = new \core_message\output\preferences\processor($processor, $preferences, $user, $type);
 752      $renderer = $PAGE->get_renderer('core', 'message');
 753  
 754      return $renderer->render_from_template('core_message/preferences_processor', $processoroutput->export_for_template($renderer));
 755  }
 756  
 757  /**
 758   * Checks if current user is allowed to edit messaging preferences of another user
 759   *
 760   * @param stdClass $user user whose preferences we are updating
 761   * @return bool
 762   */
 763  function core_message_can_edit_message_profile($user) {
 764      global $USER;
 765      if ($user->id == $USER->id) {
 766          return has_capability('moodle/user:editownmessageprofile', context_system::instance());
 767      } else {
 768          $personalcontext = context_user::instance($user->id);
 769          if (!has_capability('moodle/user:editmessageprofile', $personalcontext)) {
 770              return false;
 771          }
 772          if (isguestuser($user)) {
 773              return false;
 774          }
 775          // No editing of admins by non-admins.
 776          if (is_siteadmin($user) and !is_siteadmin($USER)) {
 777              return false;
 778          }
 779          return true;
 780      }
 781  }
 782  
 783  /**
 784   * Implements callback user_preferences, lists preferences that users are allowed to update directly
 785   *
 786   * Used in {@see core_user::fill_preferences_cache()}, see also {@see useredit_update_user_preference()}
 787   *
 788   * @return array
 789   */
 790  function core_message_user_preferences() {
 791      $preferences = [];
 792      $preferences['message_blocknoncontacts'] = array(
 793          'type' => PARAM_INT,
 794          'null' => NULL_NOT_ALLOWED,
 795          'default' => 0,
 796          'choices' => array(
 797              \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS,
 798              \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER,
 799              \core_message\api::MESSAGE_PRIVACY_SITE
 800          ),
 801          'cleancallback' => function ($value) {
 802              global $CFG;
 803  
 804              // When site-wide messaging between users is disabled, MESSAGE_PRIVACY_SITE should be converted.
 805              if (empty($CFG->messagingallusers) && $value === \core_message\api::MESSAGE_PRIVACY_SITE) {
 806                  return \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER;
 807              }
 808              return $value;
 809          }
 810      );
 811      $preferences['message_entertosend'] = array(
 812          'type' => PARAM_BOOL,
 813          'null' => NULL_NOT_ALLOWED,
 814          'default' => false
 815      );
 816      $preferences['/^message_provider_([\w\d_]*)_enabled$/'] = array('isregex' => true, 'type' => PARAM_NOTAGS,
 817          'null' => NULL_NOT_ALLOWED, 'default' => 'none',
 818          'permissioncallback' => function ($user, $preferencename) {
 819              global $CFG;
 820              require_once($CFG->libdir.'/messagelib.php');
 821              if (core_message_can_edit_message_profile($user) &&
 822                      preg_match('/^message_provider_([\w\d_]*)_enabled$/', $preferencename, $matches)) {
 823                  $providers = message_get_providers_for_user($user->id);
 824                  foreach ($providers as $provider) {
 825                      if ($matches[1] === $provider->component . '_' . $provider->name) {
 826                         return true;
 827                      }
 828                  }
 829              }
 830              return false;
 831          },
 832          'cleancallback' => function ($value, $preferencename) {
 833              if ($value === 'none' || empty($value)) {
 834                  return 'none';
 835              }
 836              $parts = explode('/,/', $value);
 837              $processors = array_keys(get_message_processors());
 838              array_filter($parts, function($v) use ($processors) {return in_array($v, $processors);});
 839              return $parts ? join(',', $parts) : 'none';
 840          });
 841      return $preferences;
 842  }
 843  
 844  /**
 845   * Render the message drawer to be included in the top of the body of each page.
 846   *
 847   * @return string HTML
 848   */
 849  function core_message_standard_after_main_region_html() {
 850      return \core_message\helper::render_messaging_widget(true, null, null);
 851  }