Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
/message/ -> lib.php (source)

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

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