message MDL-24771 another big commit of messaging fixes
[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 /**
30  * Called when a message provider wants to send a message.
31  * This functions checks the user's processor configuration to send the given type of message,
32  * then tries to send it.
33  *
34  * Required parameter $eventdata structure:
35  *  modulename     -
36  *  userfrom
37  *  userto
38  *  subject
39  *  fullmessage - the full message in a given format
40  *  fullmessageformat  - the format if the full message (FORMAT_MOODLE, FORMAT_HTML, ..)
41  *  fullmessagehtml  - the full version (the message processor will choose with one to use)
42  *  smallmessage - the small version of the message
43  *  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.
44  *  contexturlname - the display text for contexturl
45  *
46  * @param object $eventdata information about the message (modulename, userfrom, userto, ...)
47  * @return boolean success
48  */
49 function message_send($eventdata) {
50     global $CFG, $DB;
52     //TODO: this function is very slow and inefficient, it would be a major bottleneck in cron processing, this has to be improved in 2.0
53     //      probably we could add two parameters with user messaging preferences and we could somehow preload/cache them in cron
55     //TODO: we need to solve problems with database transactions here somehow, for now we just prevent transactions - sorry
56     $DB->transactions_forbidden();
58     //after how long inactive should the user be considered logged off?
59     if (isset($CFG->block_online_users_timetosee)) {
60         $timetoshowusers = $CFG->block_online_users_timetosee * 60;
61     } else {
62         $timetoshowusers = 300;//5 minutes
63     }
65     // Work out if the user is logged in or not
66     if ((time() - $timetoshowusers) < $eventdata->userto->lastaccess) {
67         $userstate = 'loggedin';
68     } else {
69         $userstate = 'loggedoff';
70     }
72     // Create the message object
73     $savemessage = new stdClass();
74     $savemessage->useridfrom        = $eventdata->userfrom->id;
75     $savemessage->useridto          = $eventdata->userto->id;
76     $savemessage->subject           = $eventdata->subject;
77     $savemessage->fullmessage       = $eventdata->fullmessage;
78     $savemessage->fullmessageformat = $eventdata->fullmessageformat;
79     $savemessage->fullmessagehtml   = $eventdata->fullmessagehtml;
80     $savemessage->smallmessage      = $eventdata->smallmessage;
82     if (!empty($eventdata->notification)) {
83         $savemessage->notification = $eventdata->notification;
84     } else {
85         $savemessage->notification = 0;
86     }
88     if (!empty($eventdata->contexturl)) {
89         $savemessage->contexturl = $eventdata->contexturl;
90     } else {
91         $savemessage->contexturl = null;
92     }
94     if (!empty($eventdata->contexturlname)) {
95         $savemessage->contexturlname = $eventdata->contexturlname;
96     } else {
97         $savemessage->contexturlname = null;
98     }
100     $savemessage->timecreated = time();
102     // Find out what processors are defined currently
103     // When a user doesn't have settings none gets return, if he doesn't want contact "" gets returned
104     $preferencename = 'message_provider_'.$eventdata->component.'_'.$eventdata->name.'_'.$userstate;
106     $processor = get_user_preferences($preferencename, null, $eventdata->userto->id);
107     if ($processor == NULL) { //this user never had a preference, save default
108         if (!message_set_default_message_preferences($eventdata->userto)) {
109             print_error('cannotsavemessageprefs', 'message');
110         }
111         $processor = get_user_preferences($preferencename, NULL, $eventdata->userto->id);
112     }
114     if ($processor=='none' && $savemessage->notification) {
115         //if they have deselected all processors and its a notification mark it read. The user doesnt want to be bothered
116         $savemessage->timeread = $timeread;
117         $DB->insert_record('message_read', $savemessage);
118     } else {                        // Process the message
119         // Store unread message just in case we can not send it
120         $savemessage->id = $DB->insert_record('message', $savemessage);
122         // Try to deliver the message to each processor
123         if ($processor!='none') {
124             $processorlist = explode(',', $processor);
125             foreach ($processorlist as $procname) {
126                 $processorfile = $CFG->dirroot. '/message/output/'.$procname.'/message_output_'.$procname.'.php';
128                 if (is_readable($processorfile)) {
129                     include_once($processorfile);  // defines $module with version etc
130                     $processclass = 'message_output_' . $procname;
132                     if (class_exists($processclass)) {
133                         $pclass = new $processclass();
135                         if (!$pclass->send_message($savemessage)) {
136                             debugging('Error calling message processor '.$procname);
137                             return false;
138                         }
139                     }
140                 } else {
141                     debugging('Error calling message processor '.$procname);
142                     return false;
143                 }
144             }
146             //if there is no more processors that want to process this we can move message to message_read
147             if ( $DB->count_records('message_working', array('unreadmessageid' => $savemessage->id)) == 0){
148                 require_once($CFG->dirroot.'/message/lib.php');
149                 message_mark_message_read($savemessage, time(), true);
150             }
151         }
152     }
154     return true;
158 /**
159  * This code updates the message_providers table with the current set of providers
160  * @param $component - examples: 'moodle', 'mod_forum', 'block_quiz_results'
161  * @return boolean
162  */
163 function message_update_providers($component='moodle') {
164     global $DB;
166     // load message providers from files
167     $fileproviders = message_get_providers_from_file($component);
169     // load message providers from the database
170     $dbproviders = message_get_providers_from_db($component);
172     foreach ($fileproviders as $messagename => $fileprovider) {
174         if (!empty($dbproviders[$messagename])) {   // Already exists in the database
176             if ($dbproviders[$messagename]->capability == $fileprovider['capability']) {  // Same, so ignore
177                 // exact same message provider already present in db, ignore this entry
178                 unset($dbproviders[$messagename]);
179                 continue;
181             } else {                                // Update existing one
182                 $provider = new stdClass();
183                 $provider->id         = $dbproviders[$messagename]->id;
184                 $provider->capability = $fileprovider['capability'];
185                 $DB->update_record('message_providers', $provider);
186                 unset($dbproviders[$messagename]);
187                 continue;
188             }
190         } else {             // New message provider, add it
192             $provider = new stdClass();
193             $provider->name       = $messagename;
194             $provider->component  = $component;
195             $provider->capability = $fileprovider['capability'];
197             $DB->insert_record('message_providers', $provider);
198         }
199     }
201     foreach ($dbproviders as $dbprovider) {  // Delete old ones
202         $DB->delete_records('message_providers', array('id' => $dbprovider->id));
203     }
205     return true;
208 /**
209  * Returns the active providers for the current user, based on capability
210  * @return array of message providers
211  */
212 function message_get_my_providers() {
213     global $DB;
215     $systemcontext = get_context_instance(CONTEXT_SYSTEM);
217     $providers = $DB->get_records('message_providers', null, 'name');
219     // Remove all the providers we aren't allowed to see now
220     foreach ($providers as $providerid => $provider) {
221         if (!empty($provider->capability)) {
222             if (!has_capability($provider->capability, $systemcontext)) {
223                 unset($providers[$providerid]);   // Not allowed to see this
224             }
225         }
226     }
228     return $providers;
231 /**
232  * Gets the message providers that are in the database for this component.
233  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
234  * @return array of message providers
235  *
236  * INTERNAL - to be used from messagelib only
237  */
238 function message_get_providers_from_db($component) {
239     global $DB;
241     return $DB->get_records('message_providers', array('component'=>$component), '', 'name, id, component, capability');  // Name is unique per component
244 /**
245  * Loads the messages definitions for the component (from file). If no
246  * messages are defined for the component, we simply return an empty array.
247  * @param $component - examples: 'moodle', 'mod_forum', 'block_quiz_results'
248  * @return array of message providerss or empty array if not exists
249  *
250  * INTERNAL - to be used from messagelib only
251  */
252 function message_get_providers_from_file($component) {
253     $defpath = get_component_directory($component).'/db/messages.php';
255     $messageproviders = array();
257     if (file_exists($defpath)) {
258         require($defpath);
259     }
261     foreach ($messageproviders as $name => $messageprovider) {   // Fix up missing values if required
262         if (empty($messageprovider['capability'])) {
263             $messageproviders[$name]['capability'] = NULL;
264         }
265     }
267     return $messageproviders;
270 /**
271  * Remove all message providers
272  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
273  */
274 function message_uninstall($component) {
275     global $DB;
276     return $DB->delete_records('message_providers', array('component' => $component));
279 /**
280  * Set default message preferences.
281  * @param $user - User to set message preferences
282  */
283 function message_set_default_message_preferences($user) {
284     global $DB;
286     $onlineprocessor = 'email';
287     //look for the pre-2.0 preference if it exists
288     $oldpreference = get_user_preferences('message_showmessagewindow', 0, $user->id);
289     if (!empty($oldpreference)) {
290         $onlineprocessor = 'popup';
291     }
293     $providers = $DB->get_records('message_providers');
294     $preferences = array();
295     foreach ($providers as $providerid => $provider) {
296         $preferences['message_provider_'.$provider->component.'_'.$provider->name.'_loggedin'] = $onlineprocessor;
297         $preferences['message_provider_'.$provider->component.'_'.$provider->name.'_loggedoff'] = 'email';
298     }
299     return set_user_preferences($preferences, $user->id);