weekly release 2.3dev (blame stronk7 for 0202 mistake)
[moodle.git] / lib / messagelib.php
CommitLineData
ce02a9bf 1<?php
3b120e46 2
ce02a9bf 3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
3b120e46 17
18/**
120b3758 19 * messagelib.php - Contains generic messaging functions for the message system
3b120e46 20 *
78bfb562
PS
21 * @package core
22 * @subpackage message
23 * @copyright Luis Rodrigues and Martin Dougiamas
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3b120e46 25 */
26
78bfb562
PS
27defined('MOODLE_INTERNAL') || die();
28
2e075f41
RK
29require_once(dirname(dirname(__FILE__)) . '/message/lib.php');
30
3b120e46 31/**
7c7d3afa 32 * Called when a message provider wants to send a message.
3b120e46 33 * This functions checks the user's processor configuration to send the given type of message,
34 * then tries to send it.
7c7d3afa
PS
35 *
36 * Required parameter $eventdata structure:
305adb10
AD
37 * component string component name. must exist in message_providers
38 * name string message type name. must exist in message_providers
76a8aef9
RK
39 * userfrom object|int the user sending the message
40 * userto object|int the message recipient
fe983847 41 * subject string the message subject
7c7d3afa
PS
42 * fullmessage - the full message in a given format
43 * fullmessageformat - the format if the full message (FORMAT_MOODLE, FORMAT_HTML, ..)
44 * fullmessagehtml - the full version (the message processor will choose with one to use)
45 * smallmessage - the small version of the message
14a0e7dd
AD
46 * contexturl - 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.
47 * contexturlname - the display text for contexturl
7c7d3afa 48 *
305adb10 49 * @param object $eventdata information about the message (component, userfrom, userto, ...)
3a00a167 50 * @return int|false the ID of the new message or false if there was a problem with a processor
3b120e46 51 */
7c7d3afa 52function message_send($eventdata) {
3b120e46 53 global $CFG, $DB;
54
3a00a167 55 //new message ID to return
56 $messageid = false;
57
e484041b
PS
58 //TODO: we need to solve problems with database transactions here somehow, for now we just prevent transactions - sorry
59 $DB->transactions_forbidden();
7c7d3afa 60
fe983847 61 if (is_int($eventdata->userto)) {
2af2210c 62 $eventdata->userto = $DB->get_record('user', array('id' => $eventdata->userto));
fe983847
AD
63 }
64 if (is_int($eventdata->userfrom)) {
2af2210c 65 $eventdata->userfrom = $DB->get_record('user', array('id' => $eventdata->userfrom));
fe983847 66 }
ca8fe0b0
PS
67 if (!isset($eventdata->userto->auth) or !isset($eventdata->userto->suspended) or !isset($eventdata->userto->deleted)) {
68 $eventdata->userto = $DB->get_record('user', array('id' => $eventdata->userto->id));
69 }
fe983847 70
1560760f 71 //after how long inactive should the user be considered logged off?
3b120e46 72 if (isset($CFG->block_online_users_timetosee)) {
73 $timetoshowusers = $CFG->block_online_users_timetosee * 60;
74 } else {
1560760f 75 $timetoshowusers = 300;//5 minutes
3b120e46 76 }
77
1560760f 78 // Work out if the user is logged in or not
bc68fc9a 79 if (!empty($eventdata->userto->lastaccess) && (time()-$timetoshowusers) < $eventdata->userto->lastaccess) {
3b120e46 80 $userstate = 'loggedin';
1560760f
AD
81 } else {
82 $userstate = 'loggedoff';
3b120e46 83 }
117bd748 84
1560760f 85 // Create the message object
365a5941 86 $savemessage = new stdClass();
3b120e46 87 $savemessage->useridfrom = $eventdata->userfrom->id;
88 $savemessage->useridto = $eventdata->userto->id;
89 $savemessage->subject = $eventdata->subject;
90 $savemessage->fullmessage = $eventdata->fullmessage;
91 $savemessage->fullmessageformat = $eventdata->fullmessageformat;
92 $savemessage->fullmessagehtml = $eventdata->fullmessagehtml;
93 $savemessage->smallmessage = $eventdata->smallmessage;
ff5b4081 94
a813a748
AD
95 if (!empty($eventdata->notification)) {
96 $savemessage->notification = $eventdata->notification;
ff5b4081 97 } else {
a813a748 98 $savemessage->notification = 0;
ff5b4081
AD
99 }
100
14a0e7dd
AD
101 if (!empty($eventdata->contexturl)) {
102 $savemessage->contexturl = $eventdata->contexturl;
103 } else {
104 $savemessage->contexturl = null;
105 }
106
107 if (!empty($eventdata->contexturlname)) {
108 $savemessage->contexturlname = $eventdata->contexturlname;
109 } else {
110 $savemessage->contexturlname = null;
111 }
112
ff5b4081 113 $savemessage->timecreated = time();
3b120e46 114
2e075f41
RK
115 // Fetch enabled processors
116 $processors = get_message_processors(true);
117 // Fetch default (site) preferences
118 $defaultpreferences = get_message_output_default_preferences();
119
120 // Preset variables
121 $processorlist = array();
122 $preferencebase = $eventdata->component.'_'.$eventdata->name;
123 // Fill in the array of processors to be used based on default and user preferences
124 foreach ($processors as $processor) {
125 // First find out permissions
126 $defaultpreference = $processor->name.'_provider_'.$preferencebase.'_permitted';
e8fc7940 127 if (isset($defaultpreferences->{$defaultpreference})) {
2e075f41
RK
128 $permitted = $defaultpreferences->{$defaultpreference};
129 } else {
a6de5ed5 130 //MDL-25114 They supplied an $eventdata->component $eventdata->name combination which doesn't
2e075f41 131 //exist in the message_provider table (thus there is no default settings for them)
50ed196e 132 $preferrormsg = get_string('couldnotfindpreference', 'message', $preferencename); //TODO: undefined $preferencename
a6de5ed5 133 throw new coding_exception($preferrormsg,'blah');
60dd7688 134 }
2e075f41
RK
135
136 // Find out if user has configured this output
bb3546f3 137 // Some processors cannot function without settings from the user
b46ca3d7 138 $userisconfigured = $processor->object->is_user_configured($eventdata->userto);
2e075f41 139
be34802d 140 // DEBUG: notify if we are forcing unconfigured output
b46ca3d7 141 if ($permitted == 'forced' && !$userisconfigured) {
2e075f41
RK
142 debugging('Attempt to force message delivery to user who has "'.$processor->name.'" output unconfigured', DEBUG_NORMAL);
143 }
144
d8aa5ec7
AD
145 // Warn developers that necessary data is missing regardless of how the processors are configured
146 if (!isset($eventdata->userto->emailstop)) {
147 debugging('userto->emailstop is not set. Retrieving it from the user table');
148 $eventdata->userto->emailstop = $DB->get_field('user', 'emailstop', array('id'=>$eventdata->userto->id));
149 }
150
2e075f41 151 // Populate the list of processors we will be using
b46ca3d7 152 if ($permitted == 'forced' && $userisconfigured) {
d8aa5ec7 153 // An admin is forcing users to use this message processor. Use this processor unconditionally.
2e075f41 154 $processorlist[] = $processor->name;
bb3546f3 155 } else if ($permitted == 'permitted' && $userisconfigured && !$eventdata->userto->emailstop) {
d8aa5ec7
AD
156 // User has not disabled notifications
157 // See if user set any notification preferences, otherwise use site default ones
2e075f41
RK
158 $userpreferencename = 'message_provider_'.$preferencebase.'_'.$userstate;
159 if ($userpreference = get_user_preferences($userpreferencename, null, $eventdata->userto->id)) {
160 if (in_array($processor->name, explode(',', $userpreference))) {
161 $processorlist[] = $processor->name;
162 }
e8fc7940 163 } else if (isset($defaultpreferences->{$userpreferencename})) {
2e075f41
RK
164 if (in_array($processor->name, explode(',', $defaultpreferences->{$userpreferencename}))) {
165 $processorlist[] = $processor->name;
166 }
167 }
168 }
2044a2b2 169 }
3b120e46 170
2e075f41 171 if (empty($processorlist) && $savemessage->notification) {
a813a748 172 //if they have deselected all processors and its a notification mark it read. The user doesnt want to be bothered
6c4e8db0 173 $savemessage->timeread = time();
3a00a167 174 $messageid = $DB->insert_record('message_read', $savemessage);
3b120e46 175 } else { // Process the message
4d8b36aa 176 // Store unread message just in case we can not send it
3a00a167 177 $messageid = $savemessage->id = $DB->insert_record('message', $savemessage);
fe983847 178 $eventdata->savedmessageid = $savemessage->id;
117bd748 179
4d8b36aa 180 // Try to deliver the message to each processor
2e075f41 181 if (!empty($processorlist)) {
a813a748 182 foreach ($processorlist as $procname) {
2e075f41
RK
183 if (!$processors[$procname]->object->send_message($eventdata)) {
184 debugging('Error calling message processor '.$procname);
3a00a167 185 $messageid = false;
3b120e46 186 }
3b120e46 187 }
2e075f41 188
388025cb
AD
189 //if messaging is disabled and they previously had forum notifications handled by the popup processor
190 //or any processor that puts a row in message_working then the notification will remain forever
191 //unread. To prevent this mark the message read if messaging is disabled
192 if (empty($CFG->messaging)) {
193 require_once($CFG->dirroot.'/message/lib.php');
3a00a167 194 $messageid = message_mark_message_read($savemessage, time());
388025cb
AD
195 } else if ( $DB->count_records('message_working', array('unreadmessageid' => $savemessage->id)) == 0){
196 //if there is no more processors that want to process this we can move message to message_read
a813a748 197 require_once($CFG->dirroot.'/message/lib.php');
3a00a167 198 $messageid = message_mark_message_read($savemessage, time(), true);
a813a748 199 }
4d8b36aa 200 }
3b120e46 201 }
202
3a00a167 203 return $messageid;
3b120e46 204}
205
120b3758 206
207/**
208 * This code updates the message_providers table with the current set of providers
ebe0c008 209 *
17da2e6f 210 * @param $component - examples: 'moodle', 'mod_forum', 'block_quiz_results'
31afb0a4 211 * @return boolean
120b3758 212 */
213function message_update_providers($component='moodle') {
214 global $DB;
215
216 // load message providers from files
217 $fileproviders = message_get_providers_from_file($component);
218
219 // load message providers from the database
220 $dbproviders = message_get_providers_from_db($component);
221
222 foreach ($fileproviders as $messagename => $fileprovider) {
223
224 if (!empty($dbproviders[$messagename])) { // Already exists in the database
298925d4 225 // check if capability has changed
120b3758 226 if ($dbproviders[$messagename]->capability == $fileprovider['capability']) { // Same, so ignore
227 // exact same message provider already present in db, ignore this entry
228 unset($dbproviders[$messagename]);
229 continue;
230
231 } else { // Update existing one
365a5941 232 $provider = new stdClass();
7c7d3afa
PS
233 $provider->id = $dbproviders[$messagename]->id;
234 $provider->capability = $fileprovider['capability'];
120b3758 235 $DB->update_record('message_providers', $provider);
236 unset($dbproviders[$messagename]);
237 continue;
238 }
239
240 } else { // New message provider, add it
241
365a5941 242 $provider = new stdClass();
120b3758 243 $provider->name = $messagename;
244 $provider->component = $component;
245 $provider->capability = $fileprovider['capability'];
246
7a04c476 247 $transaction = $DB->start_delegated_transaction();
120b3758 248 $DB->insert_record('message_providers', $provider);
7a04c476
RK
249 message_set_default_message_preference($component, $messagename, $fileprovider);
250 $transaction->allow_commit();
120b3758 251 }
252 }
253
254 foreach ($dbproviders as $dbprovider) { // Delete old ones
255 $DB->delete_records('message_providers', array('id' => $dbprovider->id));
298925d4
RK
256 $DB->delete_records_select('config_plugins', "plugin = 'message' AND ".$DB->sql_like('name', '?', false), array("%_provider_{$component}_{$dbprovider->name}_%"));
257 $DB->delete_records_select('user_preferences', $DB->sql_like('name', '?', false), array("message_provider_{$component}_{$dbprovider->name}_%"));
120b3758 258 }
31afb0a4
RK
259
260 return true;
120b3758 261}
262
afed8d0f
RK
263/**
264 * This function populates default message preferences for all existing providers
265 * when the new message processor is added.
266 *
267 * @param string $processorname The name of message processor plugin (e.g. 'email', 'jabber')
268 * @return void
269 * @throws invalid_parameter_exception if $processorname does not exist
270 */
271function message_update_processors($processorname) {
272 global $DB;
273
274 // validate if our processor exists
275 $processor = $DB->get_records('message_processors', array('name' => $processorname));
276 if (empty($processor)) {
277 throw new invalid_parameter_exception();
278 }
279
280 $providers = $DB->get_records_sql('SELECT DISTINCT component FROM {message_providers}');
281
282 $transaction = $DB->start_delegated_transaction();
283 foreach ($providers as $provider) {
284 // load message providers from files
285 $fileproviders = message_get_providers_from_file($provider->component);
286 foreach ($fileproviders as $messagename => $fileprovider) {
287 message_set_default_message_preference($provider->component, $messagename, $fileprovider, $processorname);
288 }
289 }
290 $transaction->allow_commit();
291}
292
7a04c476
RK
293/**
294 * Setting default messaging preference for particular message provider
ebe0c008 295 *
7a04c476
RK
296 * @param string $component The name of component (e.g. moodle, mod_forum, etc.)
297 * @param string $messagename The name of message provider
298 * @param array $fileprovider The value of $messagename key in the array defined in plugin messages.php
afed8d0f 299 * @param string $processorname The optinal name of message processor
ebe0c008 300 * @return void
7a04c476 301 */
afed8d0f 302function message_set_default_message_preference($component, $messagename, $fileprovider, $processorname='') {
7a04c476
RK
303 global $DB;
304
305 // Fetch message processors
afed8d0f
RK
306 $condition = null;
307 // If we need to process a particular processor, set the select condition
308 if (!empty($processorname)) {
309 $condition = array('name' => $processorname);
310 }
311 $processors = $DB->get_records('message_processors', $condition);
7a04c476
RK
312
313 // load default messaging preferences
314 $defaultpreferences = get_message_output_default_preferences();
315
ebe0c008 316 // Setting default preference
7a04c476
RK
317 $componentproviderbase = $component.'_'.$messagename;
318 $loggedinpref = array();
319 $loggedoffpref = array();
ebe0c008 320 // set 'permitted' preference first for each messaging processor
7a04c476
RK
321 foreach ($processors as $processor) {
322 $preferencename = $processor->name.'_provider_'.$componentproviderbase.'_permitted';
ebe0c008 323 // if we do not have this setting yet, set it
e8fc7940 324 if (!isset($defaultpreferences->{$preferencename})) {
7a04c476
RK
325 // determine plugin default settings
326 $plugindefault = 0;
327 if (isset($fileprovider['defaults'][$processor->name])) {
328 $plugindefault = $fileprovider['defaults'][$processor->name];
329 }
330 // get string values of the settings
331 list($permitted, $loggedin, $loggedoff) = translate_message_default_setting($plugindefault, $processor->name);
332 // store default preferences for current processor
333 set_config($preferencename, $permitted, 'message');
334 // save loggedin/loggedoff settings
335 if ($loggedin) {
336 $loggedinpref[] = $processor->name;
337 }
338 if ($loggedoff) {
339 $loggedoffpref[] = $processor->name;
340 }
341 }
342 }
ebe0c008 343 // now set loggedin/loggedoff preferences
7a04c476
RK
344 if (!empty($loggedinpref)) {
345 $preferencename = 'message_provider_'.$componentproviderbase.'_loggedin';
afed8d0f
RK
346 if (isset($defaultpreferences->{$preferencename})) {
347 // We have the default preferences for this message provider, which
348 // likely means that we have been adding a new processor. Add defaults
349 // to exisitng preferences.
350 $loggedinpref = array_merge($loggedinpref, explode(',', $defaultpreferences->{$preferencename}));
351 }
7a04c476
RK
352 set_config($preferencename, join(',', $loggedinpref), 'message');
353 }
354 if (!empty($loggedoffpref)) {
355 $preferencename = 'message_provider_'.$componentproviderbase.'_loggedoff';
afed8d0f
RK
356 if (isset($defaultpreferences->{$preferencename})) {
357 // We have the default preferences for this message provider, which
358 // likely means that we have been adding a new processor. Add defaults
359 // to exisitng preferences.
360 $loggedoffpref = array_merge($loggedoffpref, explode(',', $defaultpreferences->{$preferencename}));
361 }
7a04c476
RK
362 set_config($preferencename, join(',', $loggedoffpref), 'message');
363 }
364}
365
120b3758 366/**
367 * Returns the active providers for the current user, based on capability
269c173e 368 *
0723dcab 369 * This function has been deprecated please use {@see message_get_providers_for_user()} instead.
ebe0c008 370 *
0723dcab
SH
371 * @deprecated since 2.1
372 * @todo Remove in 2.2
120b3758 373 * @return array of message providers
374 */
375function message_get_my_providers() {
0723dcab
SH
376 global $USER;
377 return message_get_providers_for_user($USER->id);
378}
379
120b3758 380/**
a8134ff6 381 * Returns the active providers for the user specified, based on capability
ebe0c008 382 *
a8134ff6 383 * @param int $userid id of user
120b3758 384 * @return array of message providers
385 */
a8134ff6 386function message_get_providers_for_user($userid) {
9f05a1a6 387 global $DB, $CFG;
120b3758 388
389 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
390
a813a748 391 $providers = $DB->get_records('message_providers', null, 'name');
120b3758 392
393 // Remove all the providers we aren't allowed to see now
394 foreach ($providers as $providerid => $provider) {
395 if (!empty($provider->capability)) {
a8134ff6 396 if (!has_capability($provider->capability, $systemcontext, $userid)) {
120b3758 397 unset($providers[$providerid]); // Not allowed to see this
398 }
399 }
9f05a1a6
RK
400 // Ensure user is not allowed to configure instantmessage if it is globally disabled.
401 if (!$CFG->messaging && $provider->name == 'instantmessage') {
402 unset($providers[$providerid]);
403 }
120b3758 404 }
405
406 return $providers;
407}
408
409/**
410 * Gets the message providers that are in the database for this component.
ebe0c008 411 *
120b3758 412 * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
413 * @return array of message providers
414 *
415 * INTERNAL - to be used from messagelib only
416 */
417function message_get_providers_from_db($component) {
418 global $DB;
419
7c7d3afa 420 return $DB->get_records('message_providers', array('component'=>$component), '', 'name, id, component, capability'); // Name is unique per component
120b3758 421}
422
423/**
424 * Loads the messages definitions for the component (from file). If no
425 * messages are defined for the component, we simply return an empty array.
ebe0c008 426 *
17da2e6f 427 * @param $component - examples: 'moodle', 'mod_forum', 'block_quiz_results'
120b3758 428 * @return array of message providerss or empty array if not exists
429 *
430 * INTERNAL - to be used from messagelib only
431 */
432function message_get_providers_from_file($component) {
17da2e6f 433 $defpath = get_component_directory($component).'/db/messages.php';
120b3758 434
435 $messageproviders = array();
436
437 if (file_exists($defpath)) {
438 require($defpath);
439 }
440
441 foreach ($messageproviders as $name => $messageprovider) { // Fix up missing values if required
442 if (empty($messageprovider['capability'])) {
443 $messageproviders[$name]['capability'] = NULL;
444 }
7a04c476
RK
445 if (empty($messageprovider['defaults'])) {
446 $messageproviders[$name]['defaults'] = array();
447 }
120b3758 448 }
449
450 return $messageproviders;
451}
452
453/**
8e265315 454 * Remove all message providers for particular plugin and corresponding settings
ebe0c008 455 *
8e265315 456 * @param string $component - examples: 'moodle', 'mod_forum', 'block_quiz_results'
ebe0c008 457 * @return void
120b3758 458 */
8e265315 459function message_provider_uninstall($component) {
03dd0575 460 global $DB;
a813a748 461
7a04c476
RK
462 $transaction = $DB->start_delegated_transaction();
463 $DB->delete_records('message_providers', array('component' => $component));
464 $DB->delete_records_select('config_plugins', "plugin = 'message' AND ".$DB->sql_like('name', '?', false), array("%_provider_{$component}_%"));
465 $DB->delete_records_select('user_preferences', $DB->sql_like('name', '?', false), array("message_provider_{$component}_%"));
466 $transaction->allow_commit();
2044a2b2 467}
8e265315
RK
468
469/**
470 * Remove message processor
471 *
472 * @param string $name - examples: 'email', 'jabber'
473 * @return void
474 */
475function message_processor_uninstall($name) {
476 global $DB;
477
478 $transaction = $DB->start_delegated_transaction();
479 $DB->delete_records('message_processors', array('name' => $name));
480 // delete permission preferences only, we do not care about loggedin/loggedoff
481 // defaults, they will be removed on the next attempt to update the preferences
482 $DB->delete_records_select('config_plugins', "plugin = 'message' AND ".$DB->sql_like('name', '?', false), array("{$name}_provider_%"));
483 $transaction->allow_commit();
484}