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.
/lib/ -> messagelib.php (source)

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