Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
/message/ -> lib.php (source)

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

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