See Release Notes
Long Term Support Release
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body