e8ce1ef452d5c23042410ea09e5f74103e15dfd7
[moodle.git] / lib / messagelib.php
1 <?php
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/>.
18 /**
19  * messagelib.php - Contains generic messaging functions for the message system
20  *
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
25  */
27 defined('MOODLE_INTERNAL') || die();
29 require_once(dirname(dirname(__FILE__)) . '/message/lib.php');
31 /**
32  * Called when a message provider wants to send a message.
33  * This functions checks the user's processor configuration to send the given type of message,
34  * then tries to send it.
35  *
36  * Required parameter $eventdata structure:
37  *  component string component name. must exist in message_providers
38  *  name string message type name. must exist in message_providers
39  *  userfrom object|int the user sending the message
40  *  userto object|int the message recipient
41  *  subject string the message subject
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
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
48  *
49  * @param object $eventdata information about the message (component, userfrom, userto, ...)
50  * @return int|false the ID of the new message or false if there was a problem with a processor
51  */
52 function message_send($eventdata) {
53     global $CFG, $DB;
55     //new message ID to return
56     $messageid = false;
58     //TODO: we need to solve problems with database transactions here somehow, for now we just prevent transactions - sorry
59     $DB->transactions_forbidden();
61     if (is_int($eventdata->userto)) {
62         $eventdata->userto = $DB->get_record('user', array('id' => $eventdata->userto));
63     }
64     if (is_int($eventdata->userfrom)) {
65         $eventdata->userfrom = $DB->get_record('user', array('id' => $eventdata->userfrom));
66     }
68     //after how long inactive should the user be considered logged off?
69     if (isset($CFG->block_online_users_timetosee)) {
70         $timetoshowusers = $CFG->block_online_users_timetosee * 60;
71     } else {
72         $timetoshowusers = 300;//5 minutes
73     }
75     // Work out if the user is logged in or not
76     if (!empty($eventdata->userto->lastaccess) && (time()-$timetoshowusers) < $eventdata->userto->lastaccess) {
77         $userstate = 'loggedin';
78     } else {
79         $userstate = 'loggedoff';
80     }
82     // Create the message object
83     $savemessage = new stdClass();
84     $savemessage->useridfrom        = $eventdata->userfrom->id;
85     $savemessage->useridto          = $eventdata->userto->id;
86     $savemessage->subject           = $eventdata->subject;
87     $savemessage->fullmessage       = $eventdata->fullmessage;
88     $savemessage->fullmessageformat = $eventdata->fullmessageformat;
89     $savemessage->fullmessagehtml   = $eventdata->fullmessagehtml;
90     $savemessage->smallmessage      = $eventdata->smallmessage;
92     if (!empty($eventdata->notification)) {
93         $savemessage->notification = $eventdata->notification;
94     } else {
95         $savemessage->notification = 0;
96     }
98     if (!empty($eventdata->contexturl)) {
99         $savemessage->contexturl = $eventdata->contexturl;
100     } else {
101         $savemessage->contexturl = null;
102     }
104     if (!empty($eventdata->contexturlname)) {
105         $savemessage->contexturlname = $eventdata->contexturlname;
106     } else {
107         $savemessage->contexturlname = null;
108     }
110     $savemessage->timecreated = time();
112     // Fetch enabled processors
113     $processors = get_message_processors(true);
114     // Fetch default (site) preferences
115     $defaultpreferences = get_message_output_default_preferences();
117     // Preset variables
118     $processorlist = array();
119     $preferencebase = $eventdata->component.'_'.$eventdata->name;
120     // Fill in the array of processors to be used based on default and user preferences
121     foreach ($processors as $processor) {
122         // First find out permissions
123         $defaultpreference = $processor->name.'_provider_'.$preferencebase.'_permitted';
124         if (array_key_exists($defaultpreference, $defaultpreferences)) {
125             $permitted = $defaultpreferences->{$defaultpreference};
126         } else {
127             //MDL-25114 They supplied an $eventdata->component $eventdata->name combination which doesn't
128             //exist in the message_provider table (thus there is no default settings for them)
129             $preferrormsg = get_string('couldnotfindpreference', 'message', $preferencename);
130             throw new coding_exception($preferrormsg,'blah');
131         }
133         // Find out if user has configured this output
134         $is_user_configured = $processor->object->is_user_configured($eventdata->userto);
136         // DEBUG: noify if we are forcing unconfigured output
137         if ($permitted == 'forced' && !$is_user_configured) {
138             debugging('Attempt to force message delivery to user who has "'.$processor->name.'" output unconfigured', DEBUG_NORMAL);
139         }
141         // Populate the list of processors we will be using
142         if ($permitted == 'forced' && $is_user_configured) {
143             // We force messages for this processor, so use this processor unconditionally if user has configured it
144             $processorlist[] = $processor->name;
145         } else if ($permitted == 'permitted' && $is_user_configured) {
146             // User settings are permitted, see if user set any, othervice use site default ones
147             $userpreferencename = 'message_provider_'.$preferencebase.'_'.$userstate;
148             if ($userpreference = get_user_preferences($userpreferencename, null, $eventdata->userto->id)) {
149                 if (in_array($processor->name, explode(',', $userpreference))) {
150                     $processorlist[] = $processor->name;
151                 }
152             } else if (array_key_exists($userpreferencename, $defaultpreferences)) {
153                 if (in_array($processor->name, explode(',', $defaultpreferences->{$userpreferencename}))) {
154                     $processorlist[] = $processor->name;
155                 }
156             }
157         }
158     }
160     if (empty($processorlist) && $savemessage->notification) {
161         //if they have deselected all processors and its a notification mark it read. The user doesnt want to be bothered
162         $savemessage->timeread = time();
163         $messageid = $DB->insert_record('message_read', $savemessage);
164     } else {                        // Process the message
165         // Store unread message just in case we can not send it
166         $messageid = $savemessage->id = $DB->insert_record('message', $savemessage);
167         $eventdata->savedmessageid = $savemessage->id;
169         // Try to deliver the message to each processor
170         if (!empty($processorlist)) {
171             foreach ($processorlist as $procname) {
172                 if (!$processors[$procname]->object->send_message($eventdata)) {
173                     debugging('Error calling message processor '.$procname);
174                     $messageid = false;
175                 }
176             }
178             //if messaging is disabled and they previously had forum notifications handled by the popup processor
179             //or any processor that puts a row in message_working then the notification will remain forever
180             //unread. To prevent this mark the message read if messaging is disabled
181             if (empty($CFG->messaging)) {
182                 require_once($CFG->dirroot.'/message/lib.php');
183                 $messageid = message_mark_message_read($savemessage, time());
184             } else if ( $DB->count_records('message_working', array('unreadmessageid' => $savemessage->id)) == 0){
185                 //if there is no more processors that want to process this we can move message to message_read
186                 require_once($CFG->dirroot.'/message/lib.php');
187                 $messageid = message_mark_message_read($savemessage, time(), true);
188             }
189         }
190     }
192     return $messageid;
196 /**
197  * This code updates the message_providers table with the current set of providers
198  * @param $component - examples: 'moodle', 'mod_forum', 'block_quiz_results'
199  * @return boolean
200  */
201 function message_update_providers($component='moodle') {
202     global $DB;
204     // load message providers from files
205     $fileproviders = message_get_providers_from_file($component);
207     // load message providers from the database
208     $dbproviders = message_get_providers_from_db($component);
210     foreach ($fileproviders as $messagename => $fileprovider) {
212         if (!empty($dbproviders[$messagename])) {   // Already exists in the database
214             if ($dbproviders[$messagename]->capability == $fileprovider['capability']) {  // Same, so ignore
215                 // exact same message provider already present in db, ignore this entry
216                 unset($dbproviders[$messagename]);
217                 continue;
219             } else {                                // Update existing one
220                 $provider = new stdClass();
221                 $provider->id         = $dbproviders[$messagename]->id;
222                 $provider->capability = $fileprovider['capability'];
223                 $DB->update_record('message_providers', $provider);
224                 unset($dbproviders[$messagename]);
225                 continue;
226             }
228         } else {             // New message provider, add it
230             $provider = new stdClass();
231             $provider->name       = $messagename;
232             $provider->component  = $component;
233             $provider->capability = $fileprovider['capability'];
235             $transaction = $DB->start_delegated_transaction();
236             $DB->insert_record('message_providers', $provider);
237             message_set_default_message_preference($component, $messagename, $fileprovider);
238             $transaction->allow_commit();
239         }
240     }
242     foreach ($dbproviders as $dbprovider) {  // Delete old ones
243         $DB->delete_records('message_providers', array('id' => $dbprovider->id));
244     }
246     return true;
249 /**
250  * Setting default messaging preference for particular message provider
251  * @param  string $component   The name of component (e.g. moodle, mod_forum, etc.)
252  * @param  string $messagename The name of message provider
253  * @param  array  $fileprovider The value of $messagename key in the array defined in plugin messages.php
254  * @return bool
255  */
256 function message_set_default_message_preference($component, $messagename, $fileprovider) {
257     global $DB;
259     // Fetch message processors
260     $processors = get_message_processors();
262     // load default messaging preferences
263     $defaultpreferences = get_message_output_default_preferences();
265     // Setting site default preferences
266     $componentproviderbase = $component.'_'.$messagename;
267     $loggedinpref = array();
268     $loggedoffpref = array();
269     foreach ($processors as $processor) {
270         $preferencename = $processor->name.'_provider_'.$componentproviderbase.'_permitted';
271         if (!array_key_exists($preferencename, $defaultpreferences)) {
272             // determine plugin default settings
273             $plugindefault = 0;
274             if (isset($fileprovider['defaults'][$processor->name])) {
275                 $plugindefault = $fileprovider['defaults'][$processor->name];
276             }
277             // get string values of the settings
278             list($permitted, $loggedin, $loggedoff) = translate_message_default_setting($plugindefault, $processor->name);
279             // store default preferences for current processor
280             set_config($preferencename, $permitted, 'message');
281             // save loggedin/loggedoff settings
282             if ($loggedin) {
283                 $loggedinpref[] = $processor->name;
284             }
285             if ($loggedoff) {
286                 $loggedoffpref[] = $processor->name;
287             }
288         }
289     }
290     // store loggedin/loggedoff preferences
291     if (!empty($loggedinpref)) {
292         $preferencename = 'message_provider_'.$componentproviderbase.'_loggedin';
293         set_config($preferencename, join(',', $loggedinpref), 'message');
294     }
295     if (!empty($loggedoffpref)) {
296         $preferencename = 'message_provider_'.$componentproviderbase.'_loggedoff';
297         set_config($preferencename, join(',', $loggedoffpref), 'message');
298     }
301 /**
302  * Returns the active providers for the current user, based on capability
303  * @return array of message providers
304  */
305 function message_get_my_providers() {
306     global $DB;
308     $systemcontext = get_context_instance(CONTEXT_SYSTEM);
310     $providers = $DB->get_records('message_providers', null, 'name');
312     // Remove all the providers we aren't allowed to see now
313     foreach ($providers as $providerid => $provider) {
314         if (!empty($provider->capability)) {
315             if (!has_capability($provider->capability, $systemcontext)) {
316                 unset($providers[$providerid]);   // Not allowed to see this
317             }
318         }
319     }
321     return $providers;
324 /**
325  * Gets the message providers that are in the database for this component.
326  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
327  * @return array of message providers
328  *
329  * INTERNAL - to be used from messagelib only
330  */
331 function message_get_providers_from_db($component) {
332     global $DB;
334     return $DB->get_records('message_providers', array('component'=>$component), '', 'name, id, component, capability');  // Name is unique per component
337 /**
338  * Loads the messages definitions for the component (from file). If no
339  * messages are defined for the component, we simply return an empty array.
340  * @param $component - examples: 'moodle', 'mod_forum', 'block_quiz_results'
341  * @return array of message providerss or empty array if not exists
342  *
343  * INTERNAL - to be used from messagelib only
344  */
345 function message_get_providers_from_file($component) {
346     $defpath = get_component_directory($component).'/db/messages.php';
348     $messageproviders = array();
350     if (file_exists($defpath)) {
351         require($defpath);
352     }
354     foreach ($messageproviders as $name => $messageprovider) {   // Fix up missing values if required
355         if (empty($messageprovider['capability'])) {
356             $messageproviders[$name]['capability'] = NULL;
357         }
358         if (empty($messageprovider['defaults'])) {
359             $messageproviders[$name]['defaults'] = array();
360         }
361     }
363     return $messageproviders;
366 /**
367  * Remove all message providers
368  * @param $component - examples: 'moodle', 'mod_forum', 'block_quiz_results'
369  */
370 function message_uninstall($component) {
371     global $DB;
373     $transaction = $DB->start_delegated_transaction();
374     $DB->delete_records('message_providers', array('component' => $component));
375     $DB->delete_records_select('config_plugins', "plugin = 'message' AND ".$DB->sql_like('name', '?', false), array("%_provider_{$component}_%"));
376     $DB->delete_records_select('user_preferences', $DB->sql_like('name', '?', false), array("message_provider_{$component}_%"));
377     $transaction->allow_commit();
379     return true;