Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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

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

   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   * Functions for interacting with the message system
  19   *
  20   * @package   core_message
  21   * @copyright 2008 Luis Rodrigues and Martin Dougiamas
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  require_once (__DIR__ . '/../message/lib.php');
  28  
  29  /**
  30   * Called when a message provider wants to send a message.
  31   * This functions checks the message recipient's message processor configuration then
  32   * sends the message to the configured processors
  33   *
  34   * Required parameters of the $eventdata object:
  35   *  component string component name. must exist in message_providers
  36   *  name string message type name. must exist in message_providers
  37   *  userfrom object|int the user sending the message
  38   *  userto object|int the message recipient
  39   *  subject string the message subject
  40   *  fullmessage string the full message in a given format
  41   *  fullmessageformat int the format if the full message (FORMAT_MOODLE, FORMAT_HTML, ..)
  42   *  fullmessagehtml string the full version (the message processor will choose with one to use)
  43   *  smallmessage string the small version of the message
  44   *
  45   * Optional parameters of the $eventdata object:
  46   *  notification bool should the message be considered as a notification rather than a personal message
  47   *  contexturl string if this is a notification then you can specify a url to view the event. For example the forum post the user is being notified of.
  48   *  contexturlname string the display text for contexturl
  49   *
  50   * Note: processor failure will not reported as false return value in all scenarios,
  51   *       for example when it is called while a database transaction is open,
  52   *       earlier versions did not do it consistently either.
  53   *
  54   * @category message
  55   * @param \core\message\message $eventdata information about the message (component, userfrom, userto, ...)
  56   * @return mixed the integer ID of the new message or false if there was a problem (with submitted data or sending the message to the message processor)
  57   */
  58  function message_send(\core\message\message $eventdata) {
  59      global $CFG, $DB, $SITE;
  60  
  61      //new message ID to return
  62      $messageid = false;
  63  
  64      // Fetch default (site) preferences
  65      $defaultpreferences = get_message_output_default_preferences();
  66      $preferencebase = $eventdata->component.'_'.$eventdata->name;
  67  
  68      // If the message provider is disabled via preferences, then don't send the message.
  69      if (!empty($defaultpreferences->{$preferencebase.'_disable'})) {
  70          return $messageid;
  71      }
  72  
  73      // By default a message is a notification. Only personal/private messages aren't notifications.
  74      if (!isset($eventdata->notification)) {
  75          $eventdata->notification = 1;
  76      }
  77  
  78      if (!is_object($eventdata->userfrom)) {
  79          $eventdata->userfrom = core_user::get_user($eventdata->userfrom);
  80      }
  81      if (!$eventdata->userfrom) {
  82          debugging('Attempt to send msg from unknown user', DEBUG_NORMAL);
  83          return false;
  84      }
  85  
  86      // Legacy messages (FROM a single user TO a single user) must be converted into conversation messages.
  87      // Then, these will be passed through the conversation messages code below.
  88      if (!$eventdata->notification && !$eventdata->convid) {
  89          // If messaging is disabled at the site level, then the 'instantmessage' provider is always disabled.
  90          // Given this is the only 'message' type message provider, we can exit now if this is the case.
  91          // Don't waste processing time trying to work out the other conversation member, if it's an individual
  92          // conversation, just throw a generic debugging notice and return.
  93          if (empty($CFG->messaging) || $eventdata->component !== 'moodle' || $eventdata->name !== 'instantmessage') {
  94              debugging('Attempt to send msg from a provider '.$eventdata->component.'/'.$eventdata->name.
  95                  ' that is inactive or not allowed for the user id='.$eventdata->userto->id, DEBUG_NORMAL);
  96              return false;
  97          }
  98  
  99          if (!is_object($eventdata->userto)) {
 100              $eventdata->userto = core_user::get_user($eventdata->userto);
 101          }
 102          if (!$eventdata->userto) {
 103              debugging('Attempt to send msg to unknown user', DEBUG_NORMAL);
 104              return false;
 105          }
 106  
 107          // Verify all necessary data fields are present.
 108          if (!isset($eventdata->userto->auth) or !isset($eventdata->userto->suspended)
 109              or !isset($eventdata->userto->deleted) or !isset($eventdata->userto->emailstop)) {
 110  
 111              debugging('Necessary properties missing in userto object, fetching full record', DEBUG_DEVELOPER);
 112              $eventdata->userto = core_user::get_user($eventdata->userto->id);
 113          }
 114  
 115          $usertoisrealuser = (core_user::is_real_user($eventdata->userto->id) != false);
 116          // If recipient is internal user (noreply user), and emailstop is set then don't send any msg.
 117          if (!$usertoisrealuser && !empty($eventdata->userto->emailstop)) {
 118              debugging('Attempt to send msg to internal (noreply) user', DEBUG_NORMAL);
 119              return false;
 120          }
 121  
 122          if ($eventdata->userfrom->id == $eventdata->userto->id) {
 123              // It's a self conversation.
 124              $conversation = \core_message\api::get_self_conversation($eventdata->userfrom->id);
 125              if (empty($conversation)) {
 126                  $conversation = \core_message\api::create_conversation(
 127                      \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
 128                      [$eventdata->userfrom->id]
 129                  );
 130              }
 131          } else {
 132              if (!$conversationid = \core_message\api::get_conversation_between_users([$eventdata->userfrom->id,
 133                                                                                        $eventdata->userto->id])) {
 134                  // It's a private conversation between users.
 135                  $conversation = \core_message\api::create_conversation(
 136                      \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
 137                      [
 138                          $eventdata->userfrom->id,
 139                          $eventdata->userto->id
 140                      ]
 141                  );
 142              }
 143          }
 144          // We either have found a conversation, or created one.
 145          $conversationid = !empty($conversationid) ? $conversationid : $conversation->id;
 146          $eventdata->convid = $conversationid;
 147      }
 148  
 149      // This is a message directed to a conversation, not a specific user as was the way in legacy messaging.
 150      // The above code has adapted the legacy messages into conversation messages.
 151      // We must call send_message_to_conversation(), which handles per-member processor iteration and triggers
 152      // a per-conversation event.
 153      // All eventdata for messages should now have a convid, as we fixed this above.
 154      if (!$eventdata->notification) {
 155  
 156          // Only one message will be saved to the DB.
 157          $conversationid = $eventdata->convid;
 158          $table = 'messages';
 159          $tabledata = new stdClass();
 160          $tabledata->courseid = $eventdata->courseid;
 161          $tabledata->useridfrom = $eventdata->userfrom->id;
 162          $tabledata->conversationid = $conversationid;
 163          $tabledata->subject = $eventdata->subject;
 164          $tabledata->fullmessage = $eventdata->fullmessage;
 165          $tabledata->fullmessageformat = $eventdata->fullmessageformat;
 166          $tabledata->fullmessagehtml = $eventdata->fullmessagehtml;
 167          $tabledata->smallmessage = $eventdata->smallmessage;
 168          $tabledata->timecreated = time();
 169          $tabledata->customdata = $eventdata->customdata;
 170  
 171          // The Trusted Content system.
 172          // Texts created or uploaded by such users will be marked as trusted and will not be cleaned before display.
 173          if (trusttext_active()) {
 174              // Individual conversations are always in system context.
 175              $messagecontext = \context_system::instance();
 176              // We need to know the type of conversation and the contextid if it is a group conversation.
 177              if ($conv = $DB->get_record('message_conversations', ['id' => $conversationid], 'id, type, contextid')) {
 178                  if ($conv->type == \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP && $conv->contextid) {
 179                      $messagecontext = \context::instance_by_id($conv->contextid);
 180                  }
 181              }
 182              $tabledata->fullmessagetrust = trusttext_trusted($messagecontext);
 183          } else {
 184              $tabledata->fullmessagetrust = false;
 185          }
 186  
 187          if ($messageid = message_handle_phpunit_redirection($eventdata, $table, $tabledata)) {
 188              return $messageid;
 189          }
 190  
 191          // Cache messages.
 192          if (!empty($eventdata->convid)) {
 193              // Cache the timecreated value of the last message in this conversation.
 194              $cache = cache::make('core', 'message_time_last_message_between_users');
 195              $key = \core_message\helper::get_last_message_time_created_cache_key($eventdata->convid);
 196              $cache->set($key, $tabledata->timecreated);
 197          }
 198  
 199          // Store unread message just in case we get a fatal error any time later.
 200          $tabledata->id = $DB->insert_record($table, $tabledata);
 201          $eventdata->savedmessageid = $tabledata->id;
 202  
 203          return \core\message\manager::send_message_to_conversation($eventdata, $tabledata);
 204      }
 205  
 206      // Else the message is a notification.
 207      if (!is_object($eventdata->userto)) {
 208          $eventdata->userto = core_user::get_user($eventdata->userto);
 209      }
 210      if (!$eventdata->userto) {
 211          debugging('Attempt to send msg to unknown user', DEBUG_NORMAL);
 212          return false;
 213      }
 214  
 215      // If the provider's component is disabled or the user can't receive messages from it, don't send the message.
 216      $isproviderallowed = false;
 217      foreach (message_get_providers_for_user($eventdata->userto->id) as $provider) {
 218          if ($provider->component === $eventdata->component && $provider->name === $eventdata->name) {
 219              $isproviderallowed = true;
 220              break;
 221          }
 222      }
 223      if (!$isproviderallowed) {
 224          debugging('Attempt to send msg from a provider '.$eventdata->component.'/'.$eventdata->name.
 225              ' that is inactive or not allowed for the user id='.$eventdata->userto->id, DEBUG_NORMAL);
 226          return false;
 227      }
 228  
 229      // Verify all necessary data fields are present.
 230      if (!isset($eventdata->userto->auth) or !isset($eventdata->userto->suspended)
 231              or !isset($eventdata->userto->deleted) or !isset($eventdata->userto->emailstop)) {
 232  
 233          debugging('Necessary properties missing in userto object, fetching full record', DEBUG_DEVELOPER);
 234          $eventdata->userto = core_user::get_user($eventdata->userto->id);
 235      }
 236  
 237      $usertoisrealuser = (core_user::is_real_user($eventdata->userto->id) != false);
 238      // If recipient is internal user (noreply user), and emailstop is set then don't send any msg.
 239      if (!$usertoisrealuser && !empty($eventdata->userto->emailstop)) {
 240          debugging('Attempt to send msg to internal (noreply) user', DEBUG_NORMAL);
 241          return false;
 242      }
 243  
 244      // Check if we are creating a notification or message.
 245      $table = 'notifications';
 246  
 247      $tabledata = new stdClass();
 248      $tabledata->useridfrom = $eventdata->userfrom->id;
 249      $tabledata->useridto = $eventdata->userto->id;
 250      $tabledata->subject = $eventdata->subject;
 251      $tabledata->fullmessage = $eventdata->fullmessage;
 252      $tabledata->fullmessageformat = $eventdata->fullmessageformat;
 253      $tabledata->fullmessagehtml = $eventdata->fullmessagehtml;
 254      $tabledata->smallmessage = $eventdata->smallmessage;
 255      $tabledata->eventtype = $eventdata->name;
 256      $tabledata->component = $eventdata->component;
 257      $tabledata->timecreated = time();
 258      $tabledata->customdata = $eventdata->customdata;
 259      if (!empty($eventdata->contexturl)) {
 260          $tabledata->contexturl = (string)$eventdata->contexturl;
 261      } else {
 262          $tabledata->contexturl = null;
 263      }
 264  
 265      if (!empty($eventdata->contexturlname)) {
 266          $tabledata->contexturlname = (string)$eventdata->contexturlname;
 267      } else {
 268          $tabledata->contexturlname = null;
 269      }
 270  
 271      if ($messageid = message_handle_phpunit_redirection($eventdata, $table, $tabledata)) {
 272          return $messageid;
 273      }
 274  
 275      // Fetch enabled processors.
 276      $processors = get_message_processors(true);
 277  
 278      // Preset variables
 279      $processorlist = array();
 280      // Fill in the array of processors to be used based on default and user preferences
 281      foreach ($processors as $processor) {
 282          // Skip adding processors for internal user, if processor doesn't support sending message to internal user.
 283          if (!$usertoisrealuser && !$processor->object->can_send_to_any_users()) {
 284              continue;
 285          }
 286  
 287          // First find out permissions
 288          $defaultlockedpreference = $processor->name . '_provider_' . $preferencebase . '_locked';
 289          $locked = false;
 290          if (isset($defaultpreferences->{$defaultlockedpreference})) {
 291              $locked = $defaultpreferences->{$defaultlockedpreference};
 292          } else {
 293              // MDL-25114 They supplied an $eventdata->component $eventdata->name combination which doesn't
 294              // exist in the message_provider table (thus there is no default settings for them).
 295              $preferrormsg = "Could not load preference $defaultlockedpreference. Make sure the component and name you supplied
 296                      to message_send() are valid.";
 297              throw new coding_exception($preferrormsg);
 298          }
 299  
 300          $preferencename = 'message_provider_'.$preferencebase.'_enabled';
 301          $forced = false;
 302          if ($locked && isset($defaultpreferences->{$preferencename})) {
 303              $userpreference = $defaultpreferences->{$preferencename};
 304              $forced = in_array($processor->name, explode(',', $userpreference));
 305          }
 306  
 307          // Find out if user has configured this output
 308          // Some processors cannot function without settings from the user
 309          $userisconfigured = $processor->object->is_user_configured($eventdata->userto);
 310  
 311          // DEBUG: notify if we are forcing unconfigured output
 312          if ($forced && !$userisconfigured) {
 313              debugging('Attempt to force message delivery to user who has "'.$processor->name.'" output unconfigured', DEBUG_NORMAL);
 314          }
 315  
 316          // Populate the list of processors we will be using
 317          if ($forced && $userisconfigured) {
 318              // An admin is forcing users to use this message processor. Use this processor unconditionally.
 319              $processorlist[] = $processor->name;
 320          } else if (!$forced && !$locked && $userisconfigured && !$eventdata->userto->emailstop) {
 321              // User has not disabled notifications
 322              // See if user set any notification preferences, otherwise use site default ones
 323              if ($userpreference = get_user_preferences($preferencename, null, $eventdata->userto)) {
 324                  if (in_array($processor->name, explode(',', $userpreference))) {
 325                      $processorlist[] = $processor->name;
 326                  }
 327              } else if (isset($defaultpreferences->{$preferencename})) {
 328                  if (in_array($processor->name, explode(',', $defaultpreferences->{$preferencename}))) {
 329                      $processorlist[] = $processor->name;
 330                  }
 331              }
 332          }
 333      }
 334  
 335      // Store unread message just in case we get a fatal error any time later.
 336      $tabledata->id = $DB->insert_record($table, $tabledata);
 337      $eventdata->savedmessageid = $tabledata->id;
 338  
 339      // Let the manager do the sending or buffering when db transaction in progress.
 340      try {
 341          return \core\message\manager::send_message($eventdata, $tabledata, $processorlist);
 342      } catch (\moodle_exception $exception) {
 343          return false;
 344      }
 345  }
 346  
 347  /**
 348   * Helper method containing the PHPUnit specific code, used to redirect and capture messages/notifications.
 349   *
 350   * @param \core\message\message $eventdata the message object
 351   * @param string $table the table to store the tabledata in, either messages or notifications.
 352   * @param stdClass $tabledata the data to be stored when creating the message/notification.
 353   * @return int the id of the stored message.
 354   */
 355  function message_handle_phpunit_redirection(\core\message\message $eventdata, string $table, \stdClass $tabledata) {
 356      global $DB;
 357      if (PHPUNIT_TEST and class_exists('phpunit_util')) {
 358          // Add some more tests to make sure the normal code can actually work.
 359          $componentdir = core_component::get_component_directory($eventdata->component);
 360          if (!$componentdir or !is_dir($componentdir)) {
 361              throw new coding_exception('Invalid component specified in message-send(): '.$eventdata->component);
 362          }
 363          if (!file_exists("$componentdir/db/messages.php")) {
 364              throw new coding_exception("$eventdata->component does not contain db/messages.php necessary for message_send()");
 365          }
 366          $messageproviders = null;
 367          include("$componentdir/db/messages.php");
 368          if (!isset($messageproviders[$eventdata->name])) {
 369              throw new coding_exception("Missing messaging defaults for event '$eventdata->name' in '$eventdata->component' " .
 370                  "messages.php file");
 371          }
 372          unset($componentdir);
 373          unset($messageproviders);
 374          // Now ask phpunit if it wants to catch this message.
 375          if (phpunit_util::is_redirecting_messages()) {
 376              $messageid = $DB->insert_record($table, $tabledata);
 377              $message = $DB->get_record($table, array('id' => $messageid));
 378  
 379              if ($eventdata->notification) {
 380                  // Add the useridto attribute for BC.
 381                  $message->useridto = $eventdata->userto->id;
 382  
 383                  // Mark the notification as read.
 384                  \core_message\api::mark_notification_as_read($message);
 385              } else {
 386                  // Add the useridto attribute for BC.
 387                  if (isset($eventdata->userto)) {
 388                      $message->useridto = $eventdata->userto->id;
 389                  }
 390                  // Mark the message as read for each of the other users.
 391                  $sql = "SELECT u.*
 392                    FROM {message_conversation_members} mcm
 393                    JOIN {user} u
 394                      ON (mcm.conversationid = :convid AND u.id = mcm.userid AND u.id != :userid)";
 395                  $otherusers = $DB->get_records_sql($sql, ['convid' => $eventdata->convid, 'userid' => $eventdata->userfrom->id]);
 396                  foreach ($otherusers as $othermember) {
 397                      \core_message\api::mark_message_as_read($othermember->id, $message);
 398                  }
 399              }
 400  
 401              // Unit tests need this detail.
 402              $message->notification = $eventdata->notification;
 403              phpunit_util::message_sent($message);
 404              return $messageid;
 405          }
 406      }
 407  }
 408  
 409  /**
 410   * Updates the message_providers table with the current set of message providers
 411   *
 412   * @param string $component For example 'moodle', 'mod_forum' or 'block_activity_results'
 413   * @return boolean True on success
 414   */
 415  function message_update_providers($component='moodle') {
 416      global $DB;
 417  
 418      // load message providers from files
 419      $fileproviders = message_get_providers_from_file($component);
 420  
 421      // load message providers from the database
 422      $dbproviders = message_get_providers_from_db($component);
 423  
 424      foreach ($fileproviders as $messagename => $fileprovider) {
 425  
 426          if (!empty($dbproviders[$messagename])) {   // Already exists in the database
 427              // check if capability has changed
 428              if ($dbproviders[$messagename]->capability == $fileprovider['capability']) {  // Same, so ignore
 429                  // exact same message provider already present in db, ignore this entry
 430                  unset($dbproviders[$messagename]);
 431                  continue;
 432  
 433              } else {                                // Update existing one
 434                  $provider = new stdClass();
 435                  $provider->id         = $dbproviders[$messagename]->id;
 436                  $provider->capability = $fileprovider['capability'];
 437                  $DB->update_record('message_providers', $provider);
 438                  unset($dbproviders[$messagename]);
 439                  continue;
 440              }
 441  
 442          } else {             // New message provider, add it
 443  
 444              $provider = new stdClass();
 445              $provider->name       = $messagename;
 446              $provider->component  = $component;
 447              $provider->capability = $fileprovider['capability'];
 448  
 449              $transaction = $DB->start_delegated_transaction();
 450              $DB->insert_record('message_providers', $provider);
 451              message_set_default_message_preference($component, $messagename, $fileprovider);
 452              $transaction->allow_commit();
 453          }
 454      }
 455  
 456      foreach ($dbproviders as $dbprovider) {  // Delete old ones
 457          $DB->delete_records('message_providers', array('id' => $dbprovider->id));
 458          $DB->delete_records_select('config_plugins', "plugin = 'message' AND ".$DB->sql_like('name', '?', false), array("%_provider_{$component}_{$dbprovider->name}_%"));
 459          $DB->delete_records_select('user_preferences', $DB->sql_like('name', '?', false), array("message_provider_{$component}_{$dbprovider->name}_%"));
 460          cache_helper::invalidate_by_definition('core', 'config', array(), 'message');
 461      }
 462  
 463      return true;
 464  }
 465  
 466  /**
 467   * This function populates default message preferences for all existing providers
 468   * when the new message processor is added.
 469   *
 470   * @param string $processorname The name of message processor plugin (e.g. 'email', 'jabber')
 471   * @throws invalid_parameter_exception if $processorname does not exist in the database
 472   */
 473  function message_update_processors($processorname) {
 474      global $DB;
 475  
 476      // validate if our processor exists
 477      $processor = $DB->get_records('message_processors', array('name' => $processorname));
 478      if (empty($processor)) {
 479          throw new invalid_parameter_exception();
 480      }
 481  
 482      $providers = $DB->get_records_sql('SELECT DISTINCT component FROM {message_providers}');
 483  
 484      $transaction = $DB->start_delegated_transaction();
 485      foreach ($providers as $provider) {
 486          // load message providers from files
 487          $fileproviders = message_get_providers_from_file($provider->component);
 488          foreach ($fileproviders as $messagename => $fileprovider) {
 489              message_set_default_message_preference($provider->component, $messagename, $fileprovider, $processorname);
 490          }
 491      }
 492      $transaction->allow_commit();
 493  }
 494  
 495  /**
 496   * Setting default messaging preferences for particular message provider
 497   *
 498   * @param  string $component   The name of component (e.g. moodle, mod_forum, etc.)
 499   * @param  string $messagename The name of message provider
 500   * @param  array  $fileprovider The value of $messagename key in the array defined in plugin messages.php
 501   * @param  string $processorname The optional name of message processor
 502   */
 503  function message_set_default_message_preference($component, $messagename, $fileprovider, $processorname='') {
 504      global $DB;
 505  
 506      // Fetch message processors
 507      $condition = null;
 508      // If we need to process a particular processor, set the select condition
 509      if (!empty($processorname)) {
 510         $condition = array('name' => $processorname);
 511      }
 512      $processors = $DB->get_records('message_processors', $condition);
 513  
 514      // load default messaging preferences
 515      $defaultpreferences = get_message_output_default_preferences();
 516  
 517      // Setting default preference
 518      $componentproviderbase = $component.'_'.$messagename;
 519      $enabledpref = [];
 520      // Set 'locked' preference first for each messaging processor.
 521      foreach ($processors as $processor) {
 522          $preferencename = $processor->name.'_provider_'.$componentproviderbase.'_locked';
 523          // If we do not have this setting yet, set it.
 524          if (!isset($defaultpreferences->{$preferencename})) {
 525              // Determine plugin default settings.
 526              $plugindefault = 0;
 527              if (isset($fileprovider['defaults'][$processor->name])) {
 528                  $plugindefault = $fileprovider['defaults'][$processor->name];
 529              }
 530              // Get string values of the settings.
 531              list($locked, $enabled) = translate_message_default_setting($plugindefault, $processor->name);
 532              // Store default preferences for current processor.
 533              set_config($preferencename, $locked, 'message');
 534              // Save enabled settings.
 535              if ($enabled) {
 536                  $enabledpref[] = $processor->name;
 537              }
 538          }
 539      }
 540      // Now set enabled preferences.
 541      if (!empty($enabledpref)) {
 542          $preferencename = 'message_provider_'.$componentproviderbase.'_enabled';
 543          if (isset($defaultpreferences->{$preferencename})) {
 544              // We have the default preferences for this message provider, which
 545              // likely means that we have been adding a new processor. Add defaults
 546              // to exisitng preferences.
 547              $enabledpref = array_merge($enabledpref, explode(',', $defaultpreferences->{$preferencename}));
 548          }
 549          set_config($preferencename, join(',', $enabledpref), 'message');
 550      }
 551  }
 552  
 553  /**
 554   * Returns the active providers for the user specified, based on capability
 555   *
 556   * @param int $userid id of user
 557   * @return array An array of message providers
 558   */
 559  function message_get_providers_for_user($userid) {
 560      global $DB, $CFG;
 561  
 562      $providers = get_message_providers();
 563  
 564      // Ensure user is not allowed to configure instantmessage if it is globally disabled.
 565      if (!$CFG->messaging) {
 566          foreach ($providers as $providerid => $provider) {
 567              if ($provider->name == 'instantmessage') {
 568                  unset($providers[$providerid]);
 569                  break;
 570              }
 571          }
 572      }
 573  
 574      // If the component is an enrolment plugin, check it is enabled
 575      foreach ($providers as $providerid => $provider) {
 576          list($type, $name) = core_component::normalize_component($provider->component);
 577          if ($type == 'enrol' && !enrol_is_enabled($name)) {
 578              unset($providers[$providerid]);
 579          }
 580      }
 581  
 582      // Now we need to check capabilities. We need to eliminate the providers
 583      // where the user does not have the corresponding capability anywhere.
 584      // Here we deal with the common simple case of the user having the
 585      // capability in the system context. That handles $CFG->defaultuserroleid.
 586      // For the remaining providers/capabilities, we need to do a more complex
 587      // query involving all overrides everywhere.
 588      $unsureproviders = array();
 589      $unsurecapabilities = array();
 590      $systemcontext = context_system::instance();
 591      foreach ($providers as $providerid => $provider) {
 592          if (empty($provider->capability) || has_capability($provider->capability, $systemcontext, $userid)) {
 593              // The provider is relevant to this user.
 594              continue;
 595          }
 596  
 597          $unsureproviders[$providerid] = $provider;
 598          $unsurecapabilities[$provider->capability] = 1;
 599          unset($providers[$providerid]);
 600      }
 601  
 602      if (empty($unsureproviders)) {
 603          // More complex checks are not required.
 604          return $providers;
 605      }
 606  
 607      // Now check the unsure capabilities.
 608      list($capcondition, $params) = $DB->get_in_or_equal(
 609              array_keys($unsurecapabilities), SQL_PARAMS_NAMED);
 610      $params['userid'] = $userid;
 611  
 612      $sql = "SELECT DISTINCT rc.capability, 1
 613  
 614                FROM {role_assignments} ra
 615                JOIN {context} actx ON actx.id = ra.contextid
 616                JOIN {role_capabilities} rc ON rc.roleid = ra.roleid
 617                JOIN {context} cctx ON cctx.id = rc.contextid
 618  
 619               WHERE ra.userid = :userid
 620                 AND rc.capability $capcondition
 621                 AND rc.permission > 0
 622                 AND (".$DB->sql_concat('actx.path', "'/'")." LIKE ".$DB->sql_concat('cctx.path', "'/%'").
 623                 " OR ".$DB->sql_concat('cctx.path', "'/'")." LIKE ".$DB->sql_concat('actx.path', "'/%'").")";
 624  
 625      if (!empty($CFG->defaultfrontpageroleid)) {
 626          $frontpagecontext = context_course::instance(SITEID);
 627  
 628          list($capcondition2, $params2) = $DB->get_in_or_equal(
 629                  array_keys($unsurecapabilities), SQL_PARAMS_NAMED);
 630          $params = array_merge($params, $params2);
 631          $params['frontpageroleid'] = $CFG->defaultfrontpageroleid;
 632          $params['frontpagepathpattern'] = $frontpagecontext->path . '/';
 633  
 634          $sql .= "
 635               UNION
 636  
 637              SELECT DISTINCT rc.capability, 1
 638  
 639                FROM {role_capabilities} rc
 640                JOIN {context} cctx ON cctx.id = rc.contextid
 641  
 642               WHERE rc.roleid = :frontpageroleid
 643                 AND rc.capability $capcondition2
 644                 AND rc.permission > 0
 645                 AND ".$DB->sql_concat('cctx.path', "'/'")." LIKE :frontpagepathpattern";
 646      }
 647  
 648      $relevantcapabilities = $DB->get_records_sql_menu($sql, $params);
 649  
 650      // Add back any providers based on the detailed capability check.
 651      foreach ($unsureproviders as $providerid => $provider) {
 652          if (array_key_exists($provider->capability, $relevantcapabilities)) {
 653              $providers[$providerid] = $provider;
 654          }
 655      }
 656  
 657      return $providers;
 658  }
 659  
 660  /**
 661   * Gets the message providers that are in the database for this component.
 662   *
 663   * This is an internal function used within messagelib.php
 664   *
 665   * @see message_update_providers()
 666   * @param string $component A moodle component like 'moodle', 'mod_forum', 'block_activity_results'
 667   * @return array An array of message providers
 668   */
 669  function message_get_providers_from_db($component) {
 670      global $DB;
 671  
 672      return $DB->get_records('message_providers', array('component'=>$component), '', 'name, id, component, capability');  // Name is unique per component
 673  }
 674  
 675  /**
 676   * Loads the messages definitions for a component from file
 677   *
 678   * If no messages are defined for the component, return an empty array.
 679   * This is an internal function used within messagelib.php
 680   *
 681   * @see message_update_providers()
 682   * @see message_update_processors()
 683   * @param string $component A moodle component like 'moodle', 'mod_forum', 'block_activity_results'
 684   * @return array An array of message providers or empty array if not exists
 685   */
 686  function message_get_providers_from_file($component) {
 687      $defpath = core_component::get_component_directory($component).'/db/messages.php';
 688  
 689      $messageproviders = array();
 690  
 691      if (file_exists($defpath)) {
 692          require($defpath);
 693      }
 694  
 695      foreach ($messageproviders as $name => $messageprovider) {   // Fix up missing values if required
 696          if (empty($messageprovider['capability'])) {
 697              $messageproviders[$name]['capability'] = NULL;
 698          }
 699          if (empty($messageprovider['defaults'])) {
 700              $messageproviders[$name]['defaults'] = array();
 701          }
 702      }
 703  
 704      return $messageproviders;
 705  }
 706  
 707  /**
 708   * Remove all message providers for particular component and corresponding settings
 709   *
 710   * @param string $component A moodle component like 'moodle', 'mod_forum', 'block_activity_results'
 711   * @return void
 712   */
 713  function message_provider_uninstall($component) {
 714      global $DB;
 715  
 716      $transaction = $DB->start_delegated_transaction();
 717      $DB->delete_records('message_providers', array('component' => $component));
 718      $DB->delete_records_select('config_plugins', "plugin = 'message' AND ".$DB->sql_like('name', '?', false), array("%_provider_{$component}_%"));
 719      $DB->delete_records_select('user_preferences', $DB->sql_like('name', '?', false), array("message_provider_{$component}_%"));
 720      $transaction->allow_commit();
 721      // Purge all messaging settings from the caches. They are stored by plugin so we have to clear all message settings.
 722      cache_helper::invalidate_by_definition('core', 'config', array(), 'message');
 723  }
 724  
 725  /**
 726   * Uninstall a message processor
 727   *
 728   * @param string $name A message processor name like 'email', 'jabber'
 729   */
 730  function message_processor_uninstall($name) {
 731      global $DB;
 732  
 733      $transaction = $DB->start_delegated_transaction();
 734      $DB->delete_records('message_processors', array('name' => $name));
 735      $DB->delete_records_select('config_plugins', "plugin = ?", array("message_{$name}"));
 736      // Delete permission preferences only, we do not care about enabled defaults,
 737      // they will be removed on the next attempt to update the preferences.
 738      $DB->delete_records_select('config_plugins', "plugin = 'message' AND ".$DB->sql_like('name', '?', false), array("{$name}_provider_%"));
 739      $transaction->allow_commit();
 740      // Purge all messaging settings from the caches. They are stored by plugin so we have to clear all message settings.
 741      cache_helper::invalidate_by_definition('core', 'config', array(), array('message', "message_{$name}"));
 742  }