576f58a8d812563aa8d7d95f2a0de647b0528b26
[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             $DB->insert_record('message_providers', $provider);
236         }
237     }
239     foreach ($dbproviders as $dbprovider) {  // Delete old ones
240         $DB->delete_records('message_providers', array('id' => $dbprovider->id));
241     }
243     return true;
246 /**
247  * Returns the active providers for the current user, based on capability
248  * @return array of message providers
249  */
250 function message_get_my_providers() {
251     global $DB;
253     $systemcontext = get_context_instance(CONTEXT_SYSTEM);
255     $providers = $DB->get_records('message_providers', null, 'name');
257     // Remove all the providers we aren't allowed to see now
258     foreach ($providers as $providerid => $provider) {
259         if (!empty($provider->capability)) {
260             if (!has_capability($provider->capability, $systemcontext)) {
261                 unset($providers[$providerid]);   // Not allowed to see this
262             }
263         }
264     }
266     return $providers;
269 /**
270  * Gets the message providers that are in the database for this component.
271  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
272  * @return array of message providers
273  *
274  * INTERNAL - to be used from messagelib only
275  */
276 function message_get_providers_from_db($component) {
277     global $DB;
279     return $DB->get_records('message_providers', array('component'=>$component), '', 'name, id, component, capability');  // Name is unique per component
282 /**
283  * Loads the messages definitions for the component (from file). If no
284  * messages are defined for the component, we simply return an empty array.
285  * @param $component - examples: 'moodle', 'mod_forum', 'block_quiz_results'
286  * @return array of message providerss or empty array if not exists
287  *
288  * INTERNAL - to be used from messagelib only
289  */
290 function message_get_providers_from_file($component) {
291     $defpath = get_component_directory($component).'/db/messages.php';
293     $messageproviders = array();
295     if (file_exists($defpath)) {
296         require($defpath);
297     }
299     foreach ($messageproviders as $name => $messageprovider) {   // Fix up missing values if required
300         if (empty($messageprovider['capability'])) {
301             $messageproviders[$name]['capability'] = NULL;
302         }
303     }
305     return $messageproviders;
308 /**
309  * Remove all message providers
310  * @param $component - examples: 'moodle', 'mod_forum', 'block_quiz_results'
311  */
312 function message_uninstall($component) {
313     global $DB;
314     return $DB->delete_records('message_providers', array('component' => $component));
317 /**
318  * Set default message preferences.
319  * @param $user - User to set message preferences
320  */
321 function message_set_default_message_preferences($user) {
322     global $DB;
324     //check for the pre 2.0 disable email setting
325     $useemail = empty($user->emailstop);
327     //look for the pre-2.0 preference if it exists
328     $oldpreference = get_user_preferences('message_showmessagewindow', -1, $user->id);
329     //if they elected to see popups or the preference didnt exist
330     $usepopups = (intval($oldpreference)==1 || intval($oldpreference)==-1);
332     $defaultonlineprocessor = 'none';
333     $defaultofflineprocessor = 'none';
334     
335     if ($useemail) {
336         $defaultonlineprocessor = 'email';
337         $defaultofflineprocessor = 'email';
338     } else if ($usepopups) {
339         $defaultonlineprocessor = 'popup';
340         $defaultofflineprocessor = 'popup';
341     }
343     $offlineprocessortouse = $onlineprocessortouse = null;
345     $providers = $DB->get_records('message_providers');
346     $preferences = array();
348     foreach ($providers as $providerid => $provider) {
350         //force some specific defaults for IMs
351         if ($provider->name=='instantmessage' && $usepopups && $useemail) {
352             $onlineprocessortouse = 'popup';
353             $offlineprocessortouse = 'email,popup';
354         } else {
355             $onlineprocessortouse = $defaultonlineprocessor;
356             $offlineprocessortouse = $defaultofflineprocessor;
357         }
358         
359         $preferences['message_provider_'.$provider->component.'_'.$provider->name.'_loggedin'] = $onlineprocessortouse;
360         $preferences['message_provider_'.$provider->component.'_'.$provider->name.'_loggedoff'] = $offlineprocessortouse;
361     }
362     return set_user_preferences($preferences, $user->id);