weekly release 3.10.1+
[moodle.git] / message / lib.php
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/>.
17 /**
18  * Library functions for messaging
19  *
20  * @package   core_message
21  * @copyright 2008 Luis Rodrigues
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 define('MESSAGE_SHORTLENGTH', 300);
27 define('MESSAGE_HISTORY_ALL', 1);
29 define('MESSAGE_SEARCH_MAX_RESULTS', 200);
31 define('MESSAGE_TYPE_NOTIFICATION', 'notification');
32 define('MESSAGE_TYPE_MESSAGE', 'message');
34 /**
35  * Define contants for messaging default settings population. For unambiguity of
36  * plugin developer intentions we use 4-bit value (LSB numbering):
37  * bit 0 - whether to send message when user is loggedin (MESSAGE_DEFAULT_LOGGEDIN)
38  * bit 1 - whether to send message when user is loggedoff (MESSAGE_DEFAULT_LOGGEDOFF)
39  * bit 2..3 - messaging permission (MESSAGE_DISALLOWED|MESSAGE_PERMITTED|MESSAGE_FORCED)
40  *
41  * MESSAGE_PERMITTED_MASK contains the mask we use to distinguish permission setting
42  */
44 define('MESSAGE_DEFAULT_LOGGEDIN', 0x01); // 0001
45 define('MESSAGE_DEFAULT_LOGGEDOFF', 0x02); // 0010
47 define('MESSAGE_DISALLOWED', 0x04); // 0100
48 define('MESSAGE_PERMITTED', 0x08); // 1000
49 define('MESSAGE_FORCED', 0x0c); // 1100
51 define('MESSAGE_PERMITTED_MASK', 0x0c); // 1100
53 /**
54  * Set default value for default outputs permitted setting
55  */
56 define('MESSAGE_DEFAULT_PERMITTED', 'permitted');
58 /**
59  * Set default values for polling.
60  */
61 define('MESSAGE_DEFAULT_MIN_POLL_IN_SECONDS', 10);
62 define('MESSAGE_DEFAULT_MAX_POLL_IN_SECONDS', 2 * MINSECS);
63 define('MESSAGE_DEFAULT_TIMEOUT_POLL_IN_SECONDS', 5 * MINSECS);
65 /**
66  * Returns the count of unread messages for user. Either from a specific user or from all users.
67  *
68  * @deprecated since 3.10
69  * TODO: MDL-69643
70  * @param object $user1 the first user. Defaults to $USER
71  * @param object $user2 the second user. If null this function will count all of user 1's unread messages.
72  * @return int the count of $user1's unread messages
73  */
74 function message_count_unread_messages($user1=null, $user2=null) {
75     global $USER, $DB;
77     debugging('message_count_unread_messages is deprecated and no longer used',
78         DEBUG_DEVELOPER);
80     if (empty($user1)) {
81         $user1 = $USER;
82     }
84     $sql = "SELECT COUNT(m.id)
85               FROM {messages} m
86         INNER JOIN {message_conversations} mc
87                 ON mc.id = m.conversationid
88         INNER JOIN {message_conversation_members} mcm
89                 ON mcm.conversationid = mc.id
90          LEFT JOIN {message_user_actions} mua
91                 ON (mua.messageid = m.id AND mua.userid = ? AND (mua.action = ? OR mua.action = ?))
92              WHERE mua.id is NULL
93                AND mcm.userid = ?";
94     $params = [$user1->id, \core_message\api::MESSAGE_ACTION_DELETED, \core_message\api::MESSAGE_ACTION_READ, $user1->id];
96     if (!empty($user2)) {
97         $sql .= " AND m.useridfrom = ?";
98         $params[] = $user2->id;
99     } else {
100         $sql .= " AND m.useridfrom <> ?";
101         $params[] = $user1->id;
102     }
104     return $DB->count_records_sql($sql, $params);
107 /**
108  * Try to guess how to convert the message to html.
109  *
110  * @access private
111  *
112  * @param stdClass $message
113  * @param bool $forcetexttohtml
114  * @return string html fragment
115  */
116 function message_format_message_text($message, $forcetexttohtml = false) {
117     // Note: this is a very nasty hack that tries to work around the weird messaging rules and design.
119     $options = new stdClass();
120     $options->para = false;
121     $options->blanktarget = true;
122     $options->trusted = isset($message->fullmessagetrust) ? $message->fullmessagetrust : false;
124     $format = $message->fullmessageformat;
126     if (strval($message->smallmessage) !== '') {
127         if (!empty($message->notification)) {
128             if (strval($message->fullmessagehtml) !== '' or strval($message->fullmessage) !== '') {
129                 $format = FORMAT_PLAIN;
130             }
131         }
132         $messagetext = $message->smallmessage;
134     } else if ($message->fullmessageformat == FORMAT_HTML) {
135         if (strval($message->fullmessagehtml) !== '') {
136             $messagetext = $message->fullmessagehtml;
137         } else {
138             $messagetext = $message->fullmessage;
139             $format = FORMAT_MOODLE;
140         }
142     } else {
143         if (strval($message->fullmessage) !== '') {
144             $messagetext = $message->fullmessage;
145         } else {
146             $messagetext = $message->fullmessagehtml;
147             $format = FORMAT_HTML;
148         }
149     }
151     if ($forcetexttohtml) {
152         // This is a crazy hack, why not set proper format when creating the notifications?
153         if ($format === FORMAT_PLAIN) {
154             $format = FORMAT_MOODLE;
155         }
156     }
157     return format_text($messagetext, $format, $options);
160 /**
161  * Search through course users.
162  *
163  * If $courseids contains the site course then this function searches
164  * through all undeleted and confirmed users.
165  *
166  * @param int|array $courseids Course ID or array of course IDs.
167  * @param string $searchtext the text to search for.
168  * @param string $sort the column name to order by.
169  * @param string|array $exceptions comma separated list or array of user IDs to exclude.
170  * @return array An array of {@link $USER} records.
171  */
172 function message_search_users($courseids, $searchtext, $sort='', $exceptions='') {
173     global $CFG, $USER, $DB;
175     // Basic validation to ensure that the parameter $courseids is not an empty array or an empty value.
176     if (!$courseids) {
177         $courseids = array(SITEID);
178     }
180     // Allow an integer to be passed.
181     if (!is_array($courseids)) {
182         $courseids = array($courseids);
183     }
185     $fullname = $DB->sql_fullname();
186     $ufields = user_picture::fields('u');
188     if (!empty($sort)) {
189         $order = ' ORDER BY '. $sort;
190     } else {
191         $order = '';
192     }
194     $params = array(
195         'userid' => $USER->id,
196         'userid2' => $USER->id,
197         'query' => "%$searchtext%"
198     );
200     if (empty($exceptions)) {
201         $exceptions = array();
202     } else if (!empty($exceptions) && is_string($exceptions)) {
203         $exceptions = explode(',', $exceptions);
204     }
206     // Ignore self and guest account.
207     $exceptions[] = $USER->id;
208     $exceptions[] = $CFG->siteguest;
210     // Exclude exceptions from the search result.
211     list($except, $params_except) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'param', false);
212     $except = ' AND u.id ' . $except;
213     $params = array_merge($params_except, $params);
215     if (in_array(SITEID, $courseids)) {
216         // Search on site level.
217         return $DB->get_records_sql("SELECT $ufields, mc.id as contactlistid, mub.id as userblockedid
218                                        FROM {user} u
219                                        LEFT JOIN {message_contacts} mc
220                                             ON mc.contactid = u.id AND mc.userid = :userid
221                                        LEFT JOIN {message_users_blocked} mub
222                                             ON mub.userid = :userid2 AND mub.blockeduserid = u.id
223                                       WHERE u.deleted = '0' AND u.confirmed = '1'
224                                             AND (".$DB->sql_like($fullname, ':query', false).")
225                                             $except
226                                      $order", $params);
227     } else {
228         // Search in courses.
230         // Getting the context IDs or each course.
231         $contextids = array();
232         foreach ($courseids as $courseid) {
233             $context = context_course::instance($courseid);
234             $contextids = array_merge($contextids, $context->get_parent_context_ids(true));
235         }
236         list($contextwhere, $contextparams) = $DB->get_in_or_equal(array_unique($contextids), SQL_PARAMS_NAMED, 'context');
237         $params = array_merge($params, $contextparams);
239         // Everyone who has a role assignment in this course or higher.
240         // TODO: add enabled enrolment join here (skodak)
241         $users = $DB->get_records_sql("SELECT DISTINCT $ufields, mc.id as contactlistid, mub.id as userblockedid
242                                          FROM {user} u
243                                          JOIN {role_assignments} ra ON ra.userid = u.id
244                                          LEFT JOIN {message_contacts} mc
245                                               ON mc.contactid = u.id AND mc.userid = :userid
246                                          LEFT JOIN {message_users_blocked} mub
247                                               ON mub.userid = :userid2 AND mub.blockeduserid = u.id
248                                         WHERE u.deleted = '0' AND u.confirmed = '1'
249                                               AND (".$DB->sql_like($fullname, ':query', false).")
250                                               AND ra.contextid $contextwhere
251                                               $except
252                                        $order", $params);
254         return $users;
255     }
258 /**
259  * Format a message for display in the message history
260  *
261  * @param object $message the message object
262  * @param string $format optional date format
263  * @param string $keywords keywords to highlight
264  * @param string $class CSS class to apply to the div around the message
265  * @return string the formatted message
266  */
267 function message_format_message($message, $format='', $keywords='', $class='other') {
269     static $dateformat;
271     //if we haven't previously set the date format or they've supplied a new one
272     if ( empty($dateformat) || (!empty($format) && $dateformat != $format) ) {
273         if ($format) {
274             $dateformat = $format;
275         } else {
276             $dateformat = get_string('strftimedatetimeshort');
277         }
278     }
279     $time = userdate($message->timecreated, $dateformat);
281     $messagetext = message_format_message_text($message, false);
283     if ($keywords) {
284         $messagetext = highlight($keywords, $messagetext);
285     }
287     $messagetext .= message_format_contexturl($message);
289     $messagetext = clean_text($messagetext, FORMAT_HTML);
291     return <<<TEMPLATE
292 <div class='message $class'>
293     <a name="m{$message->id}"></a>
294     <span class="message-meta"><span class="time">$time</span></span>: <span class="text">$messagetext</span>
295 </div>
296 TEMPLATE;
299 /**
300  * Format a the context url and context url name of a message for display
301  *
302  * @param object $message the message object
303  * @return string the formatted string
304  */
305 function message_format_contexturl($message) {
306     $s = null;
308     if (!empty($message->contexturl)) {
309         $displaytext = null;
310         if (!empty($message->contexturlname)) {
311             $displaytext= $message->contexturlname;
312         } else {
313             $displaytext= $message->contexturl;
314         }
315         $s .= html_writer::start_tag('div',array('class' => 'messagecontext'));
316             $s .= get_string('view').': '.html_writer::tag('a', $displaytext, array('href' => $message->contexturl));
317         $s .= html_writer::end_tag('div');
318     }
320     return $s;
323 /**
324  * Send a message from one user to another. Will be delivered according to the message recipients messaging preferences
325  *
326  * @param object $userfrom the message sender
327  * @param object $userto the message recipient
328  * @param string $message the message
329  * @param int $format message format such as FORMAT_PLAIN or FORMAT_HTML
330  * @return int|false the ID of the new message or false
331  */
332 function message_post_message($userfrom, $userto, $message, $format) {
333     global $PAGE;
335     $eventdata = new \core\message\message();
336     $eventdata->courseid         = 1;
337     $eventdata->component        = 'moodle';
338     $eventdata->name             = 'instantmessage';
339     $eventdata->userfrom         = $userfrom;
340     $eventdata->userto           = $userto;
342     //using string manager directly so that strings in the message will be in the message recipients language rather than the senders
343     $eventdata->subject          = get_string_manager()->get_string('unreadnewmessage', 'message', fullname($userfrom), $userto->lang);
345     if ($format == FORMAT_HTML) {
346         $eventdata->fullmessagehtml  = $message;
347         //some message processors may revert to sending plain text even if html is supplied
348         //so we keep both plain and html versions if we're intending to send html
349         $eventdata->fullmessage = html_to_text($eventdata->fullmessagehtml);
350     } else {
351         $eventdata->fullmessage      = $message;
352         $eventdata->fullmessagehtml  = '';
353     }
355     $eventdata->fullmessageformat = $format;
356     $eventdata->smallmessage     = $message;//store the message unfiltered. Clean up on output.
357     $eventdata->timecreated     = time();
358     $eventdata->notification    = 0;
359     // User image.
360     $userpicture = new user_picture($userfrom);
361     $userpicture->size = 1; // Use f1 size.
362     $userpicture->includetoken = $userto->id; // Generate an out-of-session token for the user receiving the message.
363     $eventdata->customdata = [
364         'notificationiconurl' => $userpicture->get_url($PAGE)->out(false),
365         'actionbuttons' => [
366             'send' => get_string_manager()->get_string('send', 'message', null, $eventdata->userto->lang),
367         ],
368         'placeholders' => [
369             'send' => get_string_manager()->get_string('writeamessage', 'message', null, $eventdata->userto->lang),
370         ],
371     ];
372     return message_send($eventdata);
375 /**
376  * Get all message processors, validate corresponding plugin existance and
377  * system configuration
378  *
379  * @param bool $ready only return ready-to-use processors
380  * @param bool $reset Reset list of message processors (used in unit tests)
381  * @param bool $resetonly Just reset, then exit
382  * @return mixed $processors array of objects containing information on message processors
383  */
384 function get_message_processors($ready = false, $reset = false, $resetonly = false) {
385     global $DB, $CFG;
387     static $processors;
388     if ($reset) {
389         $processors = array();
391         if ($resetonly) {
392             return $processors;
393         }
394     }
396     if (empty($processors)) {
397         // Get all processors, ensure the name column is the first so it will be the array key
398         $processors = $DB->get_records('message_processors', null, 'name DESC', 'name, id, enabled');
399         foreach ($processors as &$processor){
400             $processor = \core_message\api::get_processed_processor_object($processor);
401         }
402     }
403     if ($ready) {
404         // Filter out enabled and system_configured processors
405         $readyprocessors = $processors;
406         foreach ($readyprocessors as $readyprocessor) {
407             if (!($readyprocessor->enabled && $readyprocessor->configured)) {
408                 unset($readyprocessors[$readyprocessor->name]);
409             }
410         }
411         return $readyprocessors;
412     }
414     return $processors;
417 /**
418  * Get all message providers, validate their plugin existance and
419  * system configuration
420  *
421  * @return mixed $processors array of objects containing information on message processors
422  */
423 function get_message_providers() {
424     global $CFG, $DB;
426     $pluginman = core_plugin_manager::instance();
428     $providers = $DB->get_records('message_providers', null, 'name');
430     // Remove all the providers whose plugins are disabled or don't exist
431     foreach ($providers as $providerid => $provider) {
432         $plugin = $pluginman->get_plugin_info($provider->component);
433         if ($plugin) {
434             if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_MISSING) {
435                 unset($providers[$providerid]);   // Plugins does not exist
436                 continue;
437             }
438             if ($plugin->is_enabled() === false) {
439                 unset($providers[$providerid]);   // Plugin disabled
440                 continue;
441             }
442         }
443     }
444     return $providers;
447 /**
448  * Get an instance of the message_output class for one of the output plugins.
449  * @param string $type the message output type. E.g. 'email' or 'jabber'.
450  * @return message_output message_output the requested class.
451  */
452 function get_message_processor($type) {
453     global $CFG;
455     // Note, we cannot use the get_message_processors function here, becaues this
456     // code is called during install after installing each messaging plugin, and
457     // get_message_processors caches the list of installed plugins.
459     $processorfile = $CFG->dirroot . "/message/output/{$type}/message_output_{$type}.php";
460     if (!is_readable($processorfile)) {
461         throw new coding_exception('Unknown message processor type ' . $type);
462     }
464     include_once($processorfile);
466     $processclass = 'message_output_' . $type;
467     if (!class_exists($processclass)) {
468         throw new coding_exception('Message processor ' . $type .
469                 ' does not define the right class');
470     }
472     return new $processclass();
475 /**
476  * Get messaging outputs default (site) preferences
477  *
478  * @return object $processors object containing information on message processors
479  */
480 function get_message_output_default_preferences() {
481     return get_config('message');
484 /**
485  * Translate message default settings from binary value to the array of string
486  * representing the settings to be stored. Also validate the provided value and
487  * use default if it is malformed.
488  *
489  * @param  int    $plugindefault Default setting suggested by plugin
490  * @param  string $processorname The name of processor
491  * @return array  $settings array of strings in the order: $permitted, $loggedin, $loggedoff.
492  */
493 function translate_message_default_setting($plugindefault, $processorname) {
494     // Preset translation arrays
495     $permittedvalues = array(
496         MESSAGE_DISALLOWED => 'disallowed',
497         MESSAGE_PERMITTED  => 'permitted',
498         MESSAGE_FORCED     => 'forced',
499     );
501     $loggedinstatusvalues = array(
502         0x00 => null, // use null if loggedin/loggedoff is not defined
503         MESSAGE_DEFAULT_LOGGEDIN  => 'loggedin',
504         MESSAGE_DEFAULT_LOGGEDOFF => 'loggedoff',
505     );
507     // define the default setting
508     $processor = get_message_processor($processorname);
509     $default = $processor->get_default_messaging_settings();
511     // Validate the value. It should not exceed the maximum size
512     if (!is_int($plugindefault) || ($plugindefault > 0x0f)) {
513         debugging(get_string('errortranslatingdefault', 'message'));
514         $plugindefault = $default;
515     }
516     // Use plugin default setting of 'permitted' is 0
517     if (!($plugindefault & MESSAGE_PERMITTED_MASK)) {
518         $plugindefault = $default;
519     }
521     $permitted = $permittedvalues[$plugindefault & MESSAGE_PERMITTED_MASK];
522     $loggedin = $loggedoff = null;
524     if (($plugindefault & MESSAGE_PERMITTED_MASK) == MESSAGE_PERMITTED) {
525         $loggedin = $loggedinstatusvalues[$plugindefault & MESSAGE_DEFAULT_LOGGEDIN];
526         $loggedoff = $loggedinstatusvalues[$plugindefault & MESSAGE_DEFAULT_LOGGEDOFF];
527     }
529     return array($permitted, $loggedin, $loggedoff);
532 /**
533  * Get messages sent or/and received by the specified users.
534  * Please note that this function return deleted messages too. Besides, only individual conversation messages
535  * are returned to maintain backwards compatibility.
536  *
537  * @param  int      $useridto       the user id who received the message
538  * @param  int      $useridfrom     the user id who sent the message. -10 or -20 for no-reply or support user
539  * @param  int      $notifications  1 for retrieving notifications, 0 for messages, -1 for both
540  * @param  bool     $read           true for retrieving read messages, false for unread
541  * @param  string   $sort           the column name to order by including optionally direction
542  * @param  int      $limitfrom      limit from
543  * @param  int      $limitnum       limit num
544  * @return external_description
545  * @since  2.8
546  */
547 function message_get_messages($useridto, $useridfrom = 0, $notifications = -1, $read = true,
548                                 $sort = 'mr.timecreated DESC', $limitfrom = 0, $limitnum = 0) {
549     global $DB;
551     // If the 'useridto' value is empty then we are going to retrieve messages sent by the useridfrom to any user.
552     if (empty($useridto)) {
553         $userfields = get_all_user_name_fields(true, 'u', '', 'userto');
554         $messageuseridtosql = 'u.id as useridto';
555     } else {
556         $userfields = get_all_user_name_fields(true, 'u', '', 'userfrom');
557         $messageuseridtosql = "$useridto as useridto";
558     }
560     // Create the SQL we will be using.
561     $messagesql = "SELECT mr.*, $userfields, 0 as notification, '' as contexturl, '' as contexturlname,
562                           mua.timecreated as timeusertodeleted, mua2.timecreated as timeread,
563                           mua3.timecreated as timeuserfromdeleted, $messageuseridtosql
564                      FROM {messages} mr
565                INNER JOIN {message_conversations} mc
566                        ON mc.id = mr.conversationid
567                INNER JOIN {message_conversation_members} mcm
568                        ON mcm.conversationid = mc.id ";
570     $notificationsql = "SELECT mr.*, $userfields, 1 as notification
571                           FROM {notifications} mr ";
573     $messagejoinsql = "LEFT JOIN {message_user_actions} mua
574                               ON (mua.messageid = mr.id AND mua.userid = mcm.userid AND mua.action = ?)
575                        LEFT JOIN {message_user_actions} mua2
576                               ON (mua2.messageid = mr.id AND mua2.userid = mcm.userid AND mua2.action = ?)
577                        LEFT JOIN {message_user_actions} mua3
578                               ON (mua3.messageid = mr.id AND mua3.userid = mr.useridfrom AND mua3.action = ?)";
579     $messagejoinparams = [\core_message\api::MESSAGE_ACTION_DELETED, \core_message\api::MESSAGE_ACTION_READ,
580         \core_message\api::MESSAGE_ACTION_DELETED];
581     $notificationsparams = [];
583     // If the 'useridto' value is empty then we are going to retrieve messages sent by the useridfrom to any user.
584     if (empty($useridto)) {
585         // Create the messaging query and params.
586         $messagesql .= "INNER JOIN {user} u
587                                 ON u.id = mcm.userid
588                                 $messagejoinsql
589                              WHERE mr.useridfrom = ?
590                                AND mr.useridfrom != mcm.userid
591                                AND u.deleted = 0
592                                AND mc.type = ? ";
593         $messageparams = array_merge($messagejoinparams, [$useridfrom, \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL]);
595         // Create the notifications query and params.
596         $notificationsql .= "INNER JOIN {user} u
597                                      ON u.id = mr.useridto
598                                   WHERE mr.useridfrom = ?
599                                     AND u.deleted = 0 ";
600         $notificationsparams[] = $useridfrom;
601     } else {
602         // Create the messaging query and params.
603         // Left join because useridfrom may be -10 or -20 (no-reply and support users).
604         $messagesql .= "LEFT JOIN {user} u
605                                ON u.id = mr.useridfrom
606                                $messagejoinsql
607                             WHERE mcm.userid = ?
608                               AND mr.useridfrom != mcm.userid
609                               AND u.deleted = 0
610                               AND mc.type = ? ";
611         $messageparams = array_merge($messagejoinparams, [$useridto, \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL]);
613         // If we're dealing with messages only and both useridto and useridfrom are set,
614         // try to get a conversation between the users. Break early if we can't find one.
615         if (!empty($useridfrom) && $notifications == 0) {
616             $messagesql .= " AND mr.useridfrom = ? ";
617             $messageparams[] = $useridfrom;
619             // There should be an individual conversation between the users. If not, we can return early.
620             $conversationid = \core_message\api::get_conversation_between_users([$useridto, $useridfrom]);
621             if (empty($conversationid)) {
622                 return [];
623             }
624             $messagesql .= " AND mc.id = ? ";
625             $messageparams[] = $conversationid;
626         }
628         // Create the notifications query and params.
629         // Left join because useridfrom may be -10 or -20 (no-reply and support users).
630         $notificationsql .= "LEFT JOIN {user} u
631                                     ON (u.id = mr.useridfrom AND u.deleted = 0)
632                                  WHERE mr.useridto = ? ";
633         $notificationsparams[] = $useridto;
634         if (!empty($useridfrom)) {
635             $notificationsql .= " AND mr.useridfrom = ? ";
636             $notificationsparams[] = $useridfrom;
637         }
638     }
639     if ($read) {
640         $notificationsql .= "AND mr.timeread IS NOT NULL ";
641     } else {
642         $notificationsql .= "AND mr.timeread IS NULL ";
643     }
644     $messagesql .= "ORDER BY $sort";
645     $notificationsql .= "ORDER BY $sort";
647     // Handle messages if needed.
648     if ($notifications === -1 || $notifications === 0) {
649         $messages = $DB->get_records_sql($messagesql, $messageparams, $limitfrom, $limitnum);
650         // Get rid of the messages that have either been read or not read depending on the value of $read.
651         $messages = array_filter($messages, function ($message) use ($read) {
652             if ($read) {
653                 return !is_null($message->timeread);
654             }
656             return is_null($message->timeread);
657         });
658     }
660     // All.
661     if ($notifications === -1) {
662         return array_merge($messages, $DB->get_records_sql($notificationsql, $notificationsparams, $limitfrom, $limitnum));
663     } else if ($notifications === 1) { // Just notifications.
664         return $DB->get_records_sql($notificationsql, $notificationsparams, $limitfrom, $limitnum);
665     }
667     // Just messages.
668     return $messages;
671 /**
672  * Handles displaying processor settings in a fragment.
673  *
674  * @param array $args
675  * @return bool|string
676  * @throws moodle_exception
677  */
678 function message_output_fragment_processor_settings($args = []) {
679     global $PAGE;
681     if (!isset($args['type'])) {
682         throw new moodle_exception('Must provide a processor type');
683     }
685     if (!isset($args['userid'])) {
686         throw new moodle_exception('Must provide a userid');
687     }
689     $type = $args['type'];
690     $userid = $args['userid'];
692     $user = core_user::get_user($userid, '*', MUST_EXIST);
693     $processor = get_message_processor($type);
694     $providers = message_get_providers_for_user($userid);
695     $processorwrapper = new stdClass();
696     $processorwrapper->object = $processor;
697     $preferences = \core_message\api::get_all_message_preferences([$processorwrapper], $providers, $user);
699     $processoroutput = new \core_message\output\preferences\processor($processor, $preferences, $user, $type);
700     $renderer = $PAGE->get_renderer('core', 'message');
702     return $renderer->render_from_template('core_message/preferences_processor', $processoroutput->export_for_template($renderer));
705 /**
706  * Checks if current user is allowed to edit messaging preferences of another user
707  *
708  * @param stdClass $user user whose preferences we are updating
709  * @return bool
710  */
711 function core_message_can_edit_message_profile($user) {
712     global $USER;
713     if ($user->id == $USER->id) {
714         return has_capability('moodle/user:editownmessageprofile', context_system::instance());
715     } else {
716         $personalcontext = context_user::instance($user->id);
717         if (!has_capability('moodle/user:editmessageprofile', $personalcontext)) {
718             return false;
719         }
720         if (isguestuser($user)) {
721             return false;
722         }
723         // No editing of admins by non-admins.
724         if (is_siteadmin($user) and !is_siteadmin($USER)) {
725             return false;
726         }
727         return true;
728     }
731 /**
732  * Implements callback user_preferences, lists preferences that users are allowed to update directly
733  *
734  * Used in {@see core_user::fill_preferences_cache()}, see also {@see useredit_update_user_preference()}
735  *
736  * @return array
737  */
738 function core_message_user_preferences() {
739     $preferences = [];
740     $preferences['message_blocknoncontacts'] = array(
741         'type' => PARAM_INT,
742         'null' => NULL_NOT_ALLOWED,
743         'default' => 0,
744         'choices' => array(
745             \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS,
746             \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER,
747             \core_message\api::MESSAGE_PRIVACY_SITE
748         ),
749         'cleancallback' => function ($value) {
750             global $CFG;
752             // When site-wide messaging between users is disabled, MESSAGE_PRIVACY_SITE should be converted.
753             if (empty($CFG->messagingallusers) && $value === \core_message\api::MESSAGE_PRIVACY_SITE) {
754                 return \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER;
755             }
756             return $value;
757         }
758     );
759     $preferences['message_entertosend'] = array(
760         'type' => PARAM_BOOL,
761         'null' => NULL_NOT_ALLOWED,
762         'default' => false
763     );
764     $preferences['/^message_provider_([\w\d_]*)_logged(in|off)$/'] = array('isregex' => true, 'type' => PARAM_NOTAGS,
765         'null' => NULL_NOT_ALLOWED, 'default' => 'none',
766         'permissioncallback' => function ($user, $preferencename) {
767             global $CFG;
768             require_once($CFG->libdir.'/messagelib.php');
769             if (core_message_can_edit_message_profile($user) &&
770                     preg_match('/^message_provider_([\w\d_]*)_logged(in|off)$/', $preferencename, $matches)) {
771                 $providers = message_get_providers_for_user($user->id);
772                 foreach ($providers as $provider) {
773                     if ($matches[1] === $provider->component . '_' . $provider->name) {
774                        return true;
775                     }
776                 }
777             }
778             return false;
779         },
780         'cleancallback' => function ($value, $preferencename) {
781             if ($value === 'none' || empty($value)) {
782                 return 'none';
783             }
784             $parts = explode('/,/', $value);
785             $processors = array_keys(get_message_processors());
786             array_filter($parts, function($v) use ($processors) {return in_array($v, $processors);});
787             return $parts ? join(',', $parts) : 'none';
788         });
789     return $preferences;
792 /**
793  * Render the message drawer to be included in the top of the body of each page.
794  *
795  * @return string HTML
796  */
797 function core_message_standard_after_main_region_html() {
798     return \core_message\helper::render_messaging_widget(true, null, null);