Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
/message/ -> lib.php (source)

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