40fda1106e686d8cc5771f521fbf6afdd04b66e7
[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 require_once($CFG->libdir.'/eventslib.php');
27 define ('MESSAGE_SHORTLENGTH', 300);
29 define ('MESSAGE_DISCUSSION_WIDTH',600);
30 define ('MESSAGE_DISCUSSION_HEIGHT',500);
32 define ('MESSAGE_SHORTVIEW_LIMIT', 8);//the maximum number of messages to show on the short message history
34 define('MESSAGE_HISTORY_SHORT',0);
35 define('MESSAGE_HISTORY_ALL',1);
37 define('MESSAGE_VIEW_UNREAD_MESSAGES','unread');
38 define('MESSAGE_VIEW_RECENT_CONVERSATIONS','recentconversations');
39 define('MESSAGE_VIEW_RECENT_NOTIFICATIONS','recentnotifications');
40 define('MESSAGE_VIEW_CONTACTS','contacts');
41 define('MESSAGE_VIEW_BLOCKED','blockedusers');
42 define('MESSAGE_VIEW_COURSE','course_');
43 define('MESSAGE_VIEW_SEARCH','search');
45 define('MESSAGE_SEARCH_MAX_RESULTS', 200);
47 define('MESSAGE_CONTACTS_PER_PAGE',10);
48 define('MESSAGE_MAX_COURSE_NAME_LENGTH', 30);
50 /**
51  * Define contants for messaging default settings population. For unambiguity of
52  * plugin developer intentions we use 4-bit value (LSB numbering):
53  * bit 0 - whether to send message when user is loggedin (MESSAGE_DEFAULT_LOGGEDIN)
54  * bit 1 - whether to send message when user is loggedoff (MESSAGE_DEFAULT_LOGGEDOFF)
55  * bit 2..3 - messaging permission (MESSAGE_DISALLOWED|MESSAGE_PERMITTED|MESSAGE_FORCED)
56  *
57  * MESSAGE_PERMITTED_MASK contains the mask we use to distinguish permission setting
58  */
60 define('MESSAGE_DEFAULT_LOGGEDIN', 0x01); // 0001
61 define('MESSAGE_DEFAULT_LOGGEDOFF', 0x02); // 0010
63 define('MESSAGE_DISALLOWED', 0x04); // 0100
64 define('MESSAGE_PERMITTED', 0x08); // 1000
65 define('MESSAGE_FORCED', 0x0c); // 1100
67 define('MESSAGE_PERMITTED_MASK', 0x0c); // 1100
69 /**
70  * Set default value for default outputs permitted setting
71  */
72 define('MESSAGE_DEFAULT_PERMITTED', 'permitted');
74 /**
75  * Retrieve users blocked by $user1
76  *
77  * @param object $user1 the user whose messages are being viewed
78  * @param object $user2 the user $user1 is talking to. If they are being blocked
79  *                      they will have a variable called 'isblocked' added to their user object
80  * @return array the users blocked by $user1
81  */
82 function message_get_blocked_users($user1=null, $user2=null) {
83     global $DB, $USER;
85     if (empty($user1)) {
86         $user1 = $USER;
87     }
89     if (!empty($user2)) {
90         $user2->isblocked = false;
91     }
93     $blockedusers = array();
95     $userfields = user_picture::fields('u', array('lastaccess'));
96     $blockeduserssql = "SELECT $userfields, COUNT(m.id) AS messagecount
97                           FROM {message_contacts} mc
98                           JOIN {user} u ON u.id = mc.contactid
99                           LEFT OUTER JOIN {message} m ON m.useridfrom = mc.contactid AND m.useridto = :user1id1
100                          WHERE u.deleted = 0 AND mc.userid = :user1id2 AND mc.blocked = 1
101                       GROUP BY $userfields
102                       ORDER BY u.firstname ASC";
103     $rs =  $DB->get_recordset_sql($blockeduserssql, array('user1id1' => $user1->id, 'user1id2' => $user1->id));
105     foreach($rs as $rd) {
106         $blockedusers[] = $rd;
108         if (!empty($user2) && $user2->id == $rd->id) {
109             $user2->isblocked = true;
110         }
111     }
112     $rs->close();
114     return $blockedusers;
117 /**
118  * Retrieve $user1's contacts (online, offline and strangers)
119  *
120  * @param object $user1 the user whose messages are being viewed
121  * @param object $user2 the user $user1 is talking to. If they are a contact
122  *                      they will have a variable called 'iscontact' added to their user object
123  * @return array containing 3 arrays. array($onlinecontacts, $offlinecontacts, $strangers)
124  */
125 function message_get_contacts($user1=null, $user2=null) {
126     global $DB, $CFG, $USER;
128     if (empty($user1)) {
129         $user1 = $USER;
130     }
132     if (!empty($user2)) {
133         $user2->iscontact = false;
134     }
136     $timetoshowusers = 300; //Seconds default
137     if (isset($CFG->block_online_users_timetosee)) {
138         $timetoshowusers = $CFG->block_online_users_timetosee * 60;
139     }
141     // time which a user is counting as being active since
142     $timefrom = time()-$timetoshowusers;
144     // people in our contactlist who are online
145     $onlinecontacts  = array();
146     // people in our contactlist who are offline
147     $offlinecontacts = array();
148     // people who are not in our contactlist but have sent us a message
149     $strangers       = array();
151     $userfields = user_picture::fields('u', array('lastaccess'));
153     // get all in our contactlist who are not blocked in our contact list
154     // and count messages we have waiting from each of them
155     $contactsql = "SELECT $userfields, COUNT(m.id) AS messagecount
156                      FROM {message_contacts} mc
157                      JOIN {user} u ON u.id = mc.contactid
158                      LEFT OUTER JOIN {message} m ON m.useridfrom = mc.contactid AND m.useridto = ?
159                     WHERE u.deleted = 0 AND mc.userid = ? AND mc.blocked = 0
160                  GROUP BY $userfields
161                  ORDER BY u.firstname ASC";
163     $rs = $DB->get_recordset_sql($contactsql, array($user1->id, $user1->id));
164     foreach ($rs as $rd) {
165         if ($rd->lastaccess >= $timefrom) {
166             // they have been active recently, so are counted online
167             $onlinecontacts[] = $rd;
169         } else {
170             $offlinecontacts[] = $rd;
171         }
173         if (!empty($user2) && $user2->id == $rd->id) {
174             $user2->iscontact = true;
175         }
176     }
177     $rs->close();
179     // get messages from anyone who isn't in our contact list and count the number
180     // of messages we have from each of them
181     $strangersql = "SELECT $userfields, count(m.id) as messagecount
182                       FROM {message} m
183                       JOIN {user} u  ON u.id = m.useridfrom
184                       LEFT OUTER JOIN {message_contacts} mc ON mc.contactid = m.useridfrom AND mc.userid = m.useridto
185                      WHERE u.deleted = 0 AND mc.id IS NULL AND m.useridto = ?
186                   GROUP BY $userfields
187                   ORDER BY u.firstname ASC";
189     $rs = $DB->get_recordset_sql($strangersql, array($USER->id));
190     // Add user id as array index, so supportuser and noreply user don't get duplicated (if they are real users).
191     foreach ($rs as $rd) {
192         $strangers[$rd->id] = $rd;
193     }
194     $rs->close();
196     // Add noreply user and support user to the list, if they don't exist.
197     $supportuser = core_user::get_support_user();
198     if (!isset($strangers[$supportuser->id])) {
199         $supportuser->messagecount = message_count_unread_messages($USER, $supportuser);
200         if ($supportuser->messagecount > 0) {
201             $strangers[$supportuser->id] = $supportuser;
202         }
203     }
205     $noreplyuser = core_user::get_noreply_user();
206     if (!isset($strangers[$noreplyuser->id])) {
207         $noreplyuser->messagecount = message_count_unread_messages($USER, $noreplyuser);
208         if ($noreplyuser->messagecount > 0) {
209             $strangers[$noreplyuser->id] = $noreplyuser;
210         }
211     }
212     return array($onlinecontacts, $offlinecontacts, $strangers);
215 /**
216  * Load the course contexts for all of the users courses
217  *
218  * @param array $courses array of course objects. The courses the user is enrolled in.
219  * @return array of course contexts
220  */
221 function message_get_course_contexts($courses) {
222     $coursecontexts = array();
224     foreach($courses as $course) {
225         $coursecontexts[$course->id] = context_course::instance($course->id);
226     }
228     return $coursecontexts;
231 /**
232  * strip off action parameters like 'removecontact'
233  *
234  * @param moodle_url/string $moodleurl a URL. Typically the current page URL.
235  * @return string the URL minus parameters that perform actions (like adding/removing/blocking a contact).
236  */
237 function message_remove_url_params($moodleurl) {
238     $newurl = new moodle_url($moodleurl);
239     $newurl->remove_params('addcontact','removecontact','blockcontact','unblockcontact');
240     return $newurl->out();
243 /**
244  * Count the number of messages with a field having a specified value.
245  * if $field is empty then return count of the whole array
246  * if $field is non-existent then return 0
247  *
248  * @param array $messagearray array of message objects
249  * @param string $field the field to inspect on the message objects
250  * @param string $value the value to test the field against
251  */
252 function message_count_messages($messagearray, $field='', $value='') {
253     if (!is_array($messagearray)) return 0;
254     if ($field == '' or empty($messagearray)) return count($messagearray);
256     $count = 0;
257     foreach ($messagearray as $message) {
258         $count += ($message->$field == $value) ? 1 : 0;
259     }
260     return $count;
263 /**
264  * Returns the count of unread messages for user. Either from a specific user or from all users.
265  *
266  * @param object $user1 the first user. Defaults to $USER
267  * @param object $user2 the second user. If null this function will count all of user 1's unread messages.
268  * @return int the count of $user1's unread messages
269  */
270 function message_count_unread_messages($user1=null, $user2=null) {
271     global $USER, $DB;
273     if (empty($user1)) {
274         $user1 = $USER;
275     }
277     if (!empty($user2)) {
278         return $DB->count_records_select('message', "useridto = ? AND useridfrom = ?",
279             array($user1->id, $user2->id), "COUNT('id')");
280     } else {
281         return $DB->count_records_select('message', "useridto = ?",
282             array($user1->id), "COUNT('id')");
283     }
286 /**
287  * Count the number of users blocked by $user1
288  *
289  * @param object $user1 user object
290  * @return int the number of blocked users
291  */
292 function message_count_blocked_users($user1=null) {
293     global $USER, $DB;
295     if (empty($user1)) {
296         $user1 = $USER;
297     }
299     $sql = "SELECT count(mc.id)
300             FROM {message_contacts} mc
301             WHERE mc.userid = :userid AND mc.blocked = 1";
302     $params = array('userid' => $user1->id);
304     return $DB->count_records_sql($sql, $params);
307 /**
308  * Get the users recent conversations meaning all the people they've recently
309  * sent or received a message from plus the most recent message sent to or received from each other user
310  *
311  * @param object|int $user the current user
312  * @param int $limitfrom can be used for paging
313  * @param int $limitto can be used for paging
314  * @return array
315  */
316 function message_get_recent_conversations($user, $limitfrom=0, $limitto=100) {
317     global $DB;
319     if (is_numeric($user)) {
320         $userid = $user;
321         $user = new stdClass();
322         $user->id = $userid;
323     }
325     $userfields = user_picture::fields('otheruser', array('lastaccess'));
327     // This query retrieves the most recent message received from or sent to
328     // seach other user.
329     //
330     // If two messages have the same timecreated, we take the one with the
331     // larger id.
332     //
333     // There is a separate query for read and unread messages as they are stored
334     // in different tables. They were originally retrieved in one query but it
335     // was so large that it was difficult to be confident in its correctness.
336     $uniquefield = $DB->sql_concat('message.useridfrom', "'-'", 'message.useridto');
337     $sql = "SELECT $uniquefield, $userfields,
338                    message.id as mid, message.notification, message.smallmessage, message.fullmessage,
339                    message.fullmessagehtml, message.fullmessageformat, message.timecreated,
340                    contact.id as contactlistid, contact.blocked
341               FROM {message_read} message
342               JOIN (
343                         SELECT MAX(id) AS messageid,
344                                matchedmessage.useridto,
345                                matchedmessage.useridfrom
346                          FROM {message_read} matchedmessage
347                    INNER JOIN (
348                                SELECT MAX(recentmessages.timecreated) timecreated,
349                                       recentmessages.useridfrom,
350                                       recentmessages.useridto
351                                  FROM {message_read} recentmessages
352                                 WHERE (
353                                       (recentmessages.useridfrom = :userid1 AND recentmessages.timeuserfromdeleted = 0) OR
354                                       (recentmessages.useridto = :userid2   AND recentmessages.timeusertodeleted = 0)
355                                       )
356                              GROUP BY recentmessages.useridfrom, recentmessages.useridto
357                               ) recent ON matchedmessage.useridto     = recent.useridto
358                            AND matchedmessage.useridfrom   = recent.useridfrom
359                            AND matchedmessage.timecreated  = recent.timecreated
360                            WHERE (
361                                  (matchedmessage.useridfrom = :userid6 AND matchedmessage.timeuserfromdeleted = 0) OR
362                                  (matchedmessage.useridto = :userid7   AND matchedmessage.timeusertodeleted = 0)
363                                  )
364                       GROUP BY matchedmessage.useridto, matchedmessage.useridfrom
365                    ) messagesubset ON messagesubset.messageid = message.id
366               JOIN {user} otheruser ON (message.useridfrom = :userid4 AND message.useridto = otheruser.id)
367                 OR (message.useridto   = :userid5 AND message.useridfrom   = otheruser.id)
368          LEFT JOIN {message_contacts} contact ON contact.userid  = :userid3 AND contact.contactid = otheruser.id
369              WHERE otheruser.deleted = 0 AND message.notification = 0
370           ORDER BY message.timecreated DESC";
371     $params = array(
372             'userid1' => $user->id,
373             'userid2' => $user->id,
374             'userid3' => $user->id,
375             'userid4' => $user->id,
376             'userid5' => $user->id,
377             'userid6' => $user->id,
378             'userid7' => $user->id
379         );
380     $read = $DB->get_records_sql($sql, $params, $limitfrom, $limitto);
382     // We want to get the messages that have not been read. These are stored in the 'message' table. It is the
383     // exact same query as the one above, except for the table we are querying. So, simply replace references to
384     // the 'message_read' table with the 'message' table.
385     $sql = str_replace('{message_read}', '{message}', $sql);
386     $unread = $DB->get_records_sql($sql, $params, $limitfrom, $limitto);
388     // Union the 2 result sets together looking for the message with the most
389     // recent timecreated for each other user.
390     // $conversation->id (the array key) is the other user's ID.
391     $conversations = array();
392     $conversation_arrays = array($unread, $read);
393     foreach ($conversation_arrays as $conversation_array) {
394         foreach ($conversation_array as $conversation) {
395             if (!isset($conversations[$conversation->id])) {
396                 $conversations[$conversation->id] = $conversation;
397             } else {
398                 $current = $conversations[$conversation->id];
399                 if ($current->timecreated < $conversation->timecreated) {
400                     $conversations[$conversation->id] = $conversation;
401                 } else if ($current->timecreated == $conversation->timecreated) {
402                     if ($current->mid < $conversation->mid) {
403                         $conversations[$conversation->id] = $conversation;
404                     }
405                 }
406             }
407         }
408     }
410     // Sort the conversations by $conversation->timecreated, newest to oldest
411     // There may be multiple conversations with the same timecreated
412     // The conversations array contains both read and unread messages (different tables) so sorting by ID won't work
413     $result = core_collator::asort_objects_by_property($conversations, 'timecreated', core_collator::SORT_NUMERIC);
414     $conversations = array_reverse($conversations);
416     return $conversations;
419 /**
420  * Get the users recent event notifications
421  *
422  * @param object $user the current user
423  * @param int $limitfrom can be used for paging
424  * @param int $limitto can be used for paging
425  * @return array
426  */
427 function message_get_recent_notifications($user, $limitfrom=0, $limitto=100) {
428     global $DB;
430     $userfields = user_picture::fields('u', array('lastaccess'));
431     $sql = "SELECT mr.id AS message_read_id, $userfields, mr.notification, mr.smallmessage, mr.fullmessage, mr.fullmessagehtml, mr.fullmessageformat, mr.timecreated as timecreated, mr.contexturl, mr.contexturlname
432               FROM {message_read} mr
433                    JOIN {user} u ON u.id=mr.useridfrom
434              WHERE mr.useridto = :userid1 AND u.deleted = '0' AND mr.notification = :notification
435              ORDER BY mr.timecreated DESC";
436     $params = array('userid1' => $user->id, 'notification' => 1);
438     $notifications =  $DB->get_records_sql($sql, $params, $limitfrom, $limitto);
439     return $notifications;
442 /**
443  * Try to guess how to convert the message to html.
444  *
445  * @access private
446  *
447  * @param stdClass $message
448  * @param bool $forcetexttohtml
449  * @return string html fragment
450  */
451 function message_format_message_text($message, $forcetexttohtml = false) {
452     // Note: this is a very nasty hack that tries to work around the weird messaging rules and design.
454     $options = new stdClass();
455     $options->para = false;
456     $options->blanktarget = true;
458     $format = $message->fullmessageformat;
460     if (strval($message->smallmessage) !== '') {
461         if ($message->notification == 1) {
462             if (strval($message->fullmessagehtml) !== '' or strval($message->fullmessage) !== '') {
463                 $format = FORMAT_PLAIN;
464             }
465         }
466         $messagetext = $message->smallmessage;
468     } else if ($message->fullmessageformat == FORMAT_HTML) {
469         if (strval($message->fullmessagehtml) !== '') {
470             $messagetext = $message->fullmessagehtml;
471         } else {
472             $messagetext = $message->fullmessage;
473             $format = FORMAT_MOODLE;
474         }
476     } else {
477         if (strval($message->fullmessage) !== '') {
478             $messagetext = $message->fullmessage;
479         } else {
480             $messagetext = $message->fullmessagehtml;
481             $format = FORMAT_HTML;
482         }
483     }
485     if ($forcetexttohtml) {
486         // This is a crazy hack, why not set proper format when creating the notifications?
487         if ($format === FORMAT_PLAIN) {
488             $format = FORMAT_MOODLE;
489         }
490     }
491     return format_text($messagetext, $format, $options);
494 /**
495  * Add the selected user as a contact for the current user
496  *
497  * @param int $contactid the ID of the user to add as a contact
498  * @param int $blocked 1 if you wish to block the contact
499  * @param int $userid the user ID of the user we want to add the contact for, defaults to current user if not specified.
500  * @return bool/int false if the $contactid isnt a valid user id. True if no changes made.
501  *                  Otherwise returns the result of update_record() or insert_record()
502  */
503 function message_add_contact($contactid, $blocked = 0, $userid = 0) {
504     global $USER, $DB;
506     if (!$DB->record_exists('user', array('id' => $contactid))) { // invalid userid
507         return false;
508     }
510     if (empty($userid)) {
511         $userid = $USER->id;
512     }
514     // Check if a record already exists as we may be changing blocking status.
515     if (($contact = $DB->get_record('message_contacts', array('userid' => $userid, 'contactid' => $contactid))) !== false) {
516         // Check if blocking status has been changed.
517         if ($contact->blocked != $blocked) {
518             $contact->blocked = $blocked;
519             $DB->update_record('message_contacts', $contact);
521             if ($blocked == 1) {
522                 // Trigger event for blocking a contact.
523                 $event = \core\event\message_contact_blocked::create(array(
524                     'objectid' => $contact->id,
525                     'userid' => $contact->userid,
526                     'relateduserid' => $contact->contactid,
527                     'context'  => context_user::instance($contact->userid)
528                 ));
529                 $event->add_record_snapshot('message_contacts', $contact);
530                 $event->trigger();
531             } else {
532                 // Trigger event for unblocking a contact.
533                 $event = \core\event\message_contact_unblocked::create(array(
534                     'objectid' => $contact->id,
535                     'userid' => $contact->userid,
536                     'relateduserid' => $contact->contactid,
537                     'context'  => context_user::instance($contact->userid)
538                 ));
539                 $event->add_record_snapshot('message_contacts', $contact);
540                 $event->trigger();
541             }
543             return true;
544         } else {
545             // No change to blocking status.
546             return true;
547         }
549     } else {
550         // New contact record.
551         $contact = new stdClass();
552         $contact->userid = $userid;
553         $contact->contactid = $contactid;
554         $contact->blocked = $blocked;
555         $contact->id = $DB->insert_record('message_contacts', $contact);
557         $eventparams = array(
558             'objectid' => $contact->id,
559             'userid' => $contact->userid,
560             'relateduserid' => $contact->contactid,
561             'context'  => context_user::instance($contact->userid)
562         );
564         if ($blocked) {
565             $event = \core\event\message_contact_blocked::create($eventparams);
566         } else {
567             $event = \core\event\message_contact_added::create($eventparams);
568         }
569         // Trigger event.
570         $event->trigger();
572         return true;
573     }
576 /**
577  * remove a contact
578  *
579  * @param int $contactid the user ID of the contact to remove
580  * @param int $userid the user ID of the user we want to remove the contacts for, defaults to current user if not specified.
581  * @return bool returns the result of delete_records()
582  */
583 function message_remove_contact($contactid, $userid = 0) {
584     global $USER, $DB;
586     if (empty($userid)) {
587         $userid = $USER->id;
588     }
590     if ($contact = $DB->get_record('message_contacts', array('userid' => $userid, 'contactid' => $contactid))) {
591         $DB->delete_records('message_contacts', array('id' => $contact->id));
593         // Trigger event for removing a contact.
594         $event = \core\event\message_contact_removed::create(array(
595             'objectid' => $contact->id,
596             'userid' => $contact->userid,
597             'relateduserid' => $contact->contactid,
598             'context'  => context_user::instance($contact->userid)
599         ));
600         $event->add_record_snapshot('message_contacts', $contact);
601         $event->trigger();
603         return true;
604     }
606     return false;
609 /**
610  * Unblock a contact. Note that this reverts the previously blocked user back to a non-contact.
611  *
612  * @param int $contactid the user ID of the contact to unblock
613  * @param int $userid the user ID of the user we want to unblock the contact for, defaults to current user
614  *  if not specified.
615  * @return bool returns the result of delete_records()
616  */
617 function message_unblock_contact($contactid, $userid = 0) {
618     return message_add_contact($contactid, 0, $userid);
621 /**
622  * Block a user.
623  *
624  * @param int $contactid the user ID of the user to block
625  * @param int $userid the user ID of the user we want to unblock the contact for, defaults to current user
626  *  if not specified.
627  * @return bool
628  */
629 function message_block_contact($contactid, $userid = 0) {
630     return message_add_contact($contactid, 1, $userid);
633 /**
634  * Checks if a user can delete a message.
635  *
636  * @param stdClass $message the message to delete
637  * @param string $userid the user id of who we want to delete the message for (this may be done by the admin
638  *  but will still seem as if it was by the user)
639  * @return bool Returns true if a user can delete the message, false otherwise.
640  */
641 function message_can_delete_message($message, $userid) {
642     global $USER;
644     if ($message->useridfrom == $userid) {
645         $userdeleting = 'useridfrom';
646     } else if ($message->useridto == $userid) {
647         $userdeleting = 'useridto';
648     } else {
649         return false;
650     }
652     $systemcontext = context_system::instance();
654     // Let's check if the user is allowed to delete this message.
655     if (has_capability('moodle/site:deleteanymessage', $systemcontext) ||
656         ((has_capability('moodle/site:deleteownmessage', $systemcontext) &&
657             $USER->id == $message->$userdeleting))) {
658         return true;
659     }
661     return false;
664 /**
665  * Deletes a message.
666  *
667  * This function does not verify any permissions.
668  *
669  * @param stdClass $message the message to delete
670  * @param string $userid the user id of who we want to delete the message for (this may be done by the admin
671  *  but will still seem as if it was by the user)
672  * @return bool
673  */
674 function message_delete_message($message, $userid) {
675     global $DB;
677     // The column we want to alter.
678     if ($message->useridfrom == $userid) {
679         $coltimedeleted = 'timeuserfromdeleted';
680     } else if ($message->useridto == $userid) {
681         $coltimedeleted = 'timeusertodeleted';
682     } else {
683         return false;
684     }
686     // Don't update it if it's already been deleted.
687     if ($message->$coltimedeleted > 0) {
688         return false;
689     }
691     // Get the table we want to update.
692     if (isset($message->timeread)) {
693         $messagetable = 'message_read';
694     } else {
695         $messagetable = 'message';
696     }
698     // Mark the message as deleted.
699     $updatemessage = new stdClass();
700     $updatemessage->id = $message->id;
701     $updatemessage->$coltimedeleted = time();
702     $success = $DB->update_record($messagetable, $updatemessage);
704     if ($success) {
705         // Trigger event for deleting a message.
706         \core\event\message_deleted::create_from_ids($message->useridfrom, $message->useridto,
707             $userid, $messagetable, $message->id)->trigger();
708     }
710     return $success;
713 /**
714  * Load a user's contact record
715  *
716  * @param int $contactid the user ID of the user whose contact record you want
717  * @return array message contacts
718  */
719 function message_get_contact($contactid) {
720     global $USER, $DB;
721     return $DB->get_record('message_contacts', array('userid' => $USER->id, 'contactid' => $contactid));
724 /**
725  * Print a message contact link
726  *
727  * @param int $userid the ID of the user to apply to action to
728  * @param string $linktype can be add, remove, block or unblock
729  * @param bool $return if true return the link as a string. If false echo the link.
730  * @param string $script the URL to send the user to when the link is clicked. If null, the current page.
731  * @param bool $text include text next to the icons?
732  * @param bool $icon include a graphical icon?
733  * @return string  if $return is true otherwise bool
734  */
735 function message_contact_link($userid, $linktype='add', $return=false, $script=null, $text=false, $icon=true) {
736     global $OUTPUT, $PAGE;
738     //hold onto the strings as we're probably creating a bunch of links
739     static $str;
741     if (empty($script)) {
742         //strip off previous action params like 'removecontact'
743         $script = message_remove_url_params($PAGE->url);
744     }
746     if (empty($str->blockcontact)) {
747        $str = new stdClass();
748        $str->blockcontact   =  get_string('blockcontact', 'message');
749        $str->unblockcontact =  get_string('unblockcontact', 'message');
750        $str->removecontact  =  get_string('removecontact', 'message');
751        $str->addcontact     =  get_string('addcontact', 'message');
752     }
754     $command = $linktype.'contact';
755     $string  = $str->{$command};
757     $safealttext = s($string);
759     $safestring = '';
760     if (!empty($text)) {
761         $safestring = $safealttext;
762     }
764     $img = '';
765     if ($icon) {
766         $iconpath = null;
767         switch ($linktype) {
768             case 'block':
769                 $iconpath = 't/block';
770                 break;
771             case 'unblock':
772                 $iconpath = 't/unblock';
773                 break;
774             case 'remove':
775                 $iconpath = 't/removecontact';
776                 break;
777             case 'add':
778             default:
779                 $iconpath = 't/addcontact';
780         }
782         $img = '<img src="'.$OUTPUT->pix_url($iconpath).'" class="iconsmall" alt="'.$safealttext.'" />';
783     }
785     $output = '<span class="'.$linktype.'contact">'.
786               '<a href="'.$script.'&amp;'.$command.'='.$userid.
787               '&amp;sesskey='.sesskey().'" title="'.$safealttext.'">'.
788               $img.
789               $safestring.'</a></span>';
791     if ($return) {
792         return $output;
793     } else {
794         echo $output;
795         return true;
796     }
799 /**
800  * echo or return a link to take the user to the full message history between themselves and another user
801  *
802  * @param int $userid1 the ID of the user displayed on the left (usually the current user)
803  * @param int $userid2 the ID of the other user
804  * @param bool $return true to return the link as a string. False to echo the link.
805  * @param string $keywords any keywords to highlight in the message history
806  * @param string $position anchor name to jump to within the message history
807  * @param string $linktext optionally specify the link text
808  * @return string|bool. Returns a string if $return is true. Otherwise returns a boolean.
809  */
810 function message_history_link($userid1, $userid2, $return=false, $keywords='', $position='', $linktext='') {
811     global $OUTPUT, $PAGE;
812     static $strmessagehistory;
814     if (empty($strmessagehistory)) {
815         $strmessagehistory = get_string('messagehistory', 'message');
816     }
818     if ($position) {
819         $position = "#$position";
820     }
821     if ($keywords) {
822         $keywords = "&search=".urlencode($keywords);
823     }
825     if ($linktext == 'icon') {  // Icon only
826         $fulllink = '<img src="'.$OUTPUT->pix_url('t/messages') . '" class="iconsmall" alt="'.$strmessagehistory.'" />';
827     } else if ($linktext == 'both') {  // Icon and standard name
828         $fulllink = '<img src="'.$OUTPUT->pix_url('t/messages') . '" class="iconsmall" alt="" />';
829         $fulllink .= '&nbsp;'.$strmessagehistory;
830     } else if ($linktext) {    // Custom name
831         $fulllink = $linktext;
832     } else {                   // Standard name only
833         $fulllink = $strmessagehistory;
834     }
836     $popupoptions = array(
837             'height' => 500,
838             'width' => 500,
839             'menubar' => false,
840             'location' => false,
841             'status' => true,
842             'scrollbars' => true,
843             'resizable' => true);
845     $link = new moodle_url('/message/index.php?history='.MESSAGE_HISTORY_ALL."&user1=$userid1&user2=$userid2$keywords$position");
846     if ($PAGE->url && $PAGE->url->get_param('viewing')) {
847         $link->param('viewing', $PAGE->url->get_param('viewing'));
848     }
849     $action = null;
850     $str = $OUTPUT->action_link($link, $fulllink, $action, array('title' => $strmessagehistory));
852     $str = '<span class="history">'.$str.'</span>';
854     if ($return) {
855         return $str;
856     } else {
857         echo $str;
858         return true;
859     }
863 /**
864  * Search through course users.
865  *
866  * If $courseids contains the site course then this function searches
867  * through all undeleted and confirmed users.
868  *
869  * @param int|array $courseids Course ID or array of course IDs.
870  * @param string $searchtext the text to search for.
871  * @param string $sort the column name to order by.
872  * @param string|array $exceptions comma separated list or array of user IDs to exclude.
873  * @return array An array of {@link $USER} records.
874  */
875 function message_search_users($courseids, $searchtext, $sort='', $exceptions='') {
876     global $CFG, $USER, $DB;
878     // Basic validation to ensure that the parameter $courseids is not an empty array or an empty value.
879     if (!$courseids) {
880         $courseids = array(SITEID);
881     }
883     // Allow an integer to be passed.
884     if (!is_array($courseids)) {
885         $courseids = array($courseids);
886     }
888     $fullname = $DB->sql_fullname();
889     $ufields = user_picture::fields('u');
891     if (!empty($sort)) {
892         $order = ' ORDER BY '. $sort;
893     } else {
894         $order = '';
895     }
897     $params = array(
898         'userid' => $USER->id,
899         'query' => "%$searchtext%"
900     );
902     if (empty($exceptions)) {
903         $exceptions = array();
904     } else if (!empty($exceptions) && is_string($exceptions)) {
905         $exceptions = explode(',', $exceptions);
906     }
908     // Ignore self and guest account.
909     $exceptions[] = $USER->id;
910     $exceptions[] = $CFG->siteguest;
912     // Exclude exceptions from the search result.
913     list($except, $params_except) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'param', false);
914     $except = ' AND u.id ' . $except;
915     $params = array_merge($params_except, $params);
917     if (in_array(SITEID, $courseids)) {
918         // Search on site level.
919         return $DB->get_records_sql("SELECT $ufields, mc.id as contactlistid, mc.blocked
920                                        FROM {user} u
921                                        LEFT JOIN {message_contacts} mc
922                                             ON mc.contactid = u.id AND mc.userid = :userid
923                                       WHERE u.deleted = '0' AND u.confirmed = '1'
924                                             AND (".$DB->sql_like($fullname, ':query', false).")
925                                             $except
926                                      $order", $params);
927     } else {
928         // Search in courses.
930         // Getting the context IDs or each course.
931         $contextids = array();
932         foreach ($courseids as $courseid) {
933             $context = context_course::instance($courseid);
934             $contextids = array_merge($contextids, $context->get_parent_context_ids(true));
935         }
936         list($contextwhere, $contextparams) = $DB->get_in_or_equal(array_unique($contextids), SQL_PARAMS_NAMED, 'context');
937         $params = array_merge($params, $contextparams);
939         // Everyone who has a role assignment in this course or higher.
940         // TODO: add enabled enrolment join here (skodak)
941         $users = $DB->get_records_sql("SELECT DISTINCT $ufields, mc.id as contactlistid, mc.blocked
942                                          FROM {user} u
943                                          JOIN {role_assignments} ra ON ra.userid = u.id
944                                          LEFT JOIN {message_contacts} mc
945                                               ON mc.contactid = u.id AND mc.userid = :userid
946                                         WHERE u.deleted = '0' AND u.confirmed = '1'
947                                               AND (".$DB->sql_like($fullname, ':query', false).")
948                                               AND ra.contextid $contextwhere
949                                               $except
950                                        $order", $params);
952         return $users;
953     }
956 /**
957  * Search a user's messages
958  *
959  * Returns a list of posts found using an array of search terms
960  * eg   word  +word -word
961  *
962  * @param array $searchterms an array of search terms (strings)
963  * @param bool $fromme include messages from the user?
964  * @param bool $tome include messages to the user?
965  * @param mixed $courseid SITEID for admins searching all messages. Other behaviour not yet implemented
966  * @param int $userid the user ID of the current user
967  * @return mixed An array of messages or false if no matching messages were found
968  */
969 function message_search($searchterms, $fromme=true, $tome=true, $courseid='none', $userid=0) {
970     global $CFG, $USER, $DB;
972     // If user is searching all messages check they are allowed to before doing anything else.
973     if ($courseid == SITEID && !has_capability('moodle/site:readallmessages', context_system::instance())) {
974         print_error('accessdenied','admin');
975     }
977     // If no userid sent then assume current user.
978     if ($userid == 0) $userid = $USER->id;
980     // Some differences in SQL syntax.
981     if ($DB->sql_regex_supported()) {
982         $REGEXP    = $DB->sql_regex(true);
983         $NOTREGEXP = $DB->sql_regex(false);
984     }
986     $searchcond = array();
987     $params = array();
988     $i = 0;
990     // Preprocess search terms to check whether we have at least 1 eligible search term.
991     // If we do we can drop words around it like 'a'.
992     $dropshortwords = false;
993     foreach ($searchterms as $searchterm) {
994         if (strlen($searchterm) >= 2) {
995             $dropshortwords = true;
996         }
997     }
999     foreach ($searchterms as $searchterm) {
1000         $i++;
1002         $NOT = false; // Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle.
1004         if ($dropshortwords && strlen($searchterm) < 2) {
1005             continue;
1006         }
1007         // Under Oracle and MSSQL, trim the + and - operators and perform simpler LIKE search.
1008         if (!$DB->sql_regex_supported()) {
1009             if (substr($searchterm, 0, 1) == '-') {
1010                 $NOT = true;
1011             }
1012             $searchterm = trim($searchterm, '+-');
1013         }
1015         if (substr($searchterm,0,1) == "+") {
1016             $searchterm = substr($searchterm,1);
1017             $searchterm = preg_quote($searchterm, '|');
1018             $searchcond[] = "m.fullmessage $REGEXP :ss$i";
1019             $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)";
1021         } else if (substr($searchterm,0,1) == "-") {
1022             $searchterm = substr($searchterm,1);
1023             $searchterm = preg_quote($searchterm, '|');
1024             $searchcond[] = "m.fullmessage $NOTREGEXP :ss$i";
1025             $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)";
1027         } else {
1028             $searchcond[] = $DB->sql_like("m.fullmessage", ":ss$i", false, true, $NOT);
1029             $params['ss'.$i] = "%$searchterm%";
1030         }
1031     }
1033     if (empty($searchcond)) {
1034         $searchcond = " ".$DB->sql_like('m.fullmessage', ':ss1', false);
1035         $params['ss1'] = "%";
1036     } else {
1037         $searchcond = implode(" AND ", $searchcond);
1038     }
1040     // There are several possibilities
1041     // 1. courseid = SITEID : The admin is searching messages by all users
1042     // 2. courseid = ??     : A teacher is searching messages by users in
1043     //                        one of their courses - currently disabled
1044     // 3. courseid = none   : User is searching their own messages;
1045     //    a.  Messages from user
1046     //    b.  Messages to user
1047     //    c.  Messages to and from user
1049     if ($fromme && $tome) {
1050         $searchcond .= " AND ((useridto = :useridto AND timeusertodeleted = 0) OR
1051             (useridfrom = :useridfrom AND timeuserfromdeleted = 0))";
1052         $params['useridto'] = $userid;
1053         $params['useridfrom'] = $userid;
1054     } else if ($fromme) {
1055         $searchcond .= " AND (useridfrom = :useridfrom AND timeuserfromdeleted = 0)";
1056         $params['useridfrom'] = $userid;
1057     } else if ($tome) {
1058         $searchcond .= " AND (useridto = :useridto AND timeusertodeleted = 0)";
1059         $params['useridto'] = $userid;
1060     }
1061     if ($courseid == SITEID) { // Admin is searching all messages.
1062         $m_read   = $DB->get_records_sql("SELECT m.id, m.useridto, m.useridfrom, m.smallmessage, m.fullmessage, m.timecreated
1063                                             FROM {message_read} m
1064                                            WHERE $searchcond", $params, 0, MESSAGE_SEARCH_MAX_RESULTS);
1065         $m_unread = $DB->get_records_sql("SELECT m.id, m.useridto, m.useridfrom, m.smallmessage, m.fullmessage, m.timecreated
1066                                             FROM {message} m
1067                                            WHERE $searchcond", $params, 0, MESSAGE_SEARCH_MAX_RESULTS);
1069     } else if ($courseid !== 'none') {
1070         // This has not been implemented due to security concerns.
1071         $m_read   = array();
1072         $m_unread = array();
1074     } else {
1076         if ($fromme and $tome) {
1077             $searchcond .= " AND (m.useridfrom=:userid1 OR m.useridto=:userid2)";
1078             $params['userid1'] = $userid;
1079             $params['userid2'] = $userid;
1081         } else if ($fromme) {
1082             $searchcond .= " AND m.useridfrom=:userid";
1083             $params['userid'] = $userid;
1085         } else if ($tome) {
1086             $searchcond .= " AND m.useridto=:userid";
1087             $params['userid'] = $userid;
1088         }
1090         $m_read   = $DB->get_records_sql("SELECT m.id, m.useridto, m.useridfrom, m.smallmessage, m.fullmessage, m.timecreated
1091                                             FROM {message_read} m
1092                                            WHERE $searchcond", $params, 0, MESSAGE_SEARCH_MAX_RESULTS);
1093         $m_unread = $DB->get_records_sql("SELECT m.id, m.useridto, m.useridfrom, m.smallmessage, m.fullmessage, m.timecreated
1094                                             FROM {message} m
1095                                            WHERE $searchcond", $params, 0, MESSAGE_SEARCH_MAX_RESULTS);
1097     }
1099     /// The keys may be duplicated in $m_read and $m_unread so we can't
1100     /// do a simple concatenation
1101     $messages = array();
1102     foreach ($m_read as $m) {
1103         $messages[] = $m;
1104     }
1105     foreach ($m_unread as $m) {
1106         $messages[] = $m;
1107     }
1109     return (empty($messages)) ? false : $messages;
1112 /**
1113  * Given a message object that we already know has a long message
1114  * this function truncates the message nicely to the first
1115  * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
1116  *
1117  * @param string $message the message
1118  * @param int $minlength the minimum length to trim the message to
1119  * @return string the shortened message
1120  */
1121 function message_shorten_message($message, $minlength = 0) {
1122     $i = 0;
1123     $tag = false;
1124     $length = strlen($message);
1125     $count = 0;
1126     $stopzone = false;
1127     $truncate = 0;
1128     if ($minlength == 0) $minlength = MESSAGE_SHORTLENGTH;
1131     for ($i=0; $i<$length; $i++) {
1132         $char = $message[$i];
1134         switch ($char) {
1135             case "<":
1136                 $tag = true;
1137                 break;
1138             case ">":
1139                 $tag = false;
1140                 break;
1141             default:
1142                 if (!$tag) {
1143                     if ($stopzone) {
1144                         if ($char == '.' or $char == ' ') {
1145                             $truncate = $i+1;
1146                             break 2;
1147                         }
1148                     }
1149                     $count++;
1150                 }
1151                 break;
1152         }
1153         if (!$stopzone) {
1154             if ($count > $minlength) {
1155                 $stopzone = true;
1156             }
1157         }
1158     }
1160     if (!$truncate) {
1161         $truncate = $i;
1162     }
1164     return substr($message, 0, $truncate);
1168 /**
1169  * Given a string and an array of keywords, this function looks
1170  * for the first keyword in the string, and then chops out a
1171  * small section from the text that shows that word in context.
1172  *
1173  * @param string $message the text to search
1174  * @param array $keywords array of keywords to find
1175  */
1176 function message_get_fragment($message, $keywords) {
1178     $fullsize = 160;
1179     $halfsize = (int)($fullsize/2);
1181     $message = strip_tags($message);
1183     foreach ($keywords as $keyword) {  // Just get the first one
1184         if ($keyword !== '') {
1185             break;
1186         }
1187     }
1188     if (empty($keyword)) {   // None found, so just return start of message
1189         return message_shorten_message($message, 30);
1190     }
1192     $leadin = $leadout = '';
1194 /// Find the start of the fragment
1195     $start = 0;
1196     $length = strlen($message);
1198     $pos = strpos($message, $keyword);
1199     if ($pos > $halfsize) {
1200         $start = $pos - $halfsize;
1201         $leadin = '...';
1202     }
1203 /// Find the end of the fragment
1204     $end = $start + $fullsize;
1205     if ($end > $length) {
1206         $end = $length;
1207     } else {
1208         $leadout = '...';
1209     }
1211 /// Pull out the fragment and format it
1213     $fragment = substr($message, $start, $end - $start);
1214     $fragment = $leadin.highlight(implode(' ',$keywords), $fragment).$leadout;
1215     return $fragment;
1218 /**
1219  * Retrieve the messages between two users
1220  *
1221  * @param object $user1 the current user
1222  * @param object $user2 the other user
1223  * @param int $limitnum the maximum number of messages to retrieve
1224  * @param bool $viewingnewmessages are we currently viewing new messages?
1225  */
1226 function message_get_history($user1, $user2, $limitnum=0, $viewingnewmessages=false) {
1227     global $DB, $CFG;
1229     $messages = array();
1231     //we want messages sorted oldest to newest but if getting a subset of messages we need to sort
1232     //desc to get the last $limitnum messages then flip the order in php
1233     $sort = 'asc';
1234     if ($limitnum>0) {
1235         $sort = 'desc';
1236     }
1238     $notificationswhere = null;
1239     //we have just moved new messages to read. If theyre here to see new messages dont hide notifications
1240     if (!$viewingnewmessages && $CFG->messaginghidereadnotifications) {
1241         $notificationswhere = 'AND notification=0';
1242     }
1244     //prevent notifications of your own actions appearing in your own message history
1245     $ownnotificationwhere = ' AND NOT (useridfrom=? AND notification=1)';
1247     $sql = "((useridto = ? AND useridfrom = ? AND timeusertodeleted = 0) OR
1248         (useridto = ? AND useridfrom = ? AND timeuserfromdeleted = 0))";
1249     if ($messages_read = $DB->get_records_select('message_read', $sql . $notificationswhere . $ownnotificationwhere,
1250                                                     array($user1->id, $user2->id, $user2->id, $user1->id, $user1->id),
1251                                                     "timecreated $sort", '*', 0, $limitnum)) {
1252         foreach ($messages_read as $message) {
1253             $messages[] = $message;
1254         }
1255     }
1256     if ($messages_new = $DB->get_records_select('message', $sql . $ownnotificationwhere,
1257                                                     array($user1->id, $user2->id, $user2->id, $user1->id, $user1->id),
1258                                                     "timecreated $sort", '*', 0, $limitnum)) {
1259         foreach ($messages_new as $message) {
1260             $messages[] = $message;
1261         }
1262     }
1264     $result = core_collator::asort_objects_by_property($messages, 'timecreated', core_collator::SORT_NUMERIC);
1266     //if we only want the last $limitnum messages
1267     $messagecount = count($messages);
1268     if ($limitnum > 0 && $messagecount > $limitnum) {
1269         $messages = array_slice($messages, $messagecount - $limitnum, $limitnum, true);
1270     }
1272     return $messages;
1275 /**
1276  * Format a message for display in the message history
1277  *
1278  * @param object $message the message object
1279  * @param string $format optional date format
1280  * @param string $keywords keywords to highlight
1281  * @param string $class CSS class to apply to the div around the message
1282  * @return string the formatted message
1283  */
1284 function message_format_message($message, $format='', $keywords='', $class='other') {
1286     static $dateformat;
1288     //if we haven't previously set the date format or they've supplied a new one
1289     if ( empty($dateformat) || (!empty($format) && $dateformat != $format) ) {
1290         if ($format) {
1291             $dateformat = $format;
1292         } else {
1293             $dateformat = get_string('strftimedatetimeshort');
1294         }
1295     }
1296     $time = userdate($message->timecreated, $dateformat);
1298     $messagetext = message_format_message_text($message, false);
1300     if ($keywords) {
1301         $messagetext = highlight($keywords, $messagetext);
1302     }
1304     $messagetext .= message_format_contexturl($message);
1306     $messagetext = clean_text($messagetext, FORMAT_HTML);
1308     return <<<TEMPLATE
1309 <div class='message $class'>
1310     <a name="m{$message->id}"></a>
1311     <span class="message-meta"><span class="time">$time</span></span>: <span class="text">$messagetext</span>
1312 </div>
1313 TEMPLATE;
1316 /**
1317  * Format a the context url and context url name of a message for display
1318  *
1319  * @param object $message the message object
1320  * @return string the formatted string
1321  */
1322 function message_format_contexturl($message) {
1323     $s = null;
1325     if (!empty($message->contexturl)) {
1326         $displaytext = null;
1327         if (!empty($message->contexturlname)) {
1328             $displaytext= $message->contexturlname;
1329         } else {
1330             $displaytext= $message->contexturl;
1331         }
1332         $s .= html_writer::start_tag('div',array('class' => 'messagecontext'));
1333             $s .= get_string('view').': '.html_writer::tag('a', $displaytext, array('href' => $message->contexturl));
1334         $s .= html_writer::end_tag('div');
1335     }
1337     return $s;
1340 /**
1341  * Send a message from one user to another. Will be delivered according to the message recipients messaging preferences
1342  *
1343  * @param object $userfrom the message sender
1344  * @param object $userto the message recipient
1345  * @param string $message the message
1346  * @param int $format message format such as FORMAT_PLAIN or FORMAT_HTML
1347  * @return int|false the ID of the new message or false
1348  */
1349 function message_post_message($userfrom, $userto, $message, $format) {
1350     global $SITE, $CFG, $USER;
1352     $eventdata = new stdClass();
1353     $eventdata->component        = 'moodle';
1354     $eventdata->name             = 'instantmessage';
1355     $eventdata->userfrom         = $userfrom;
1356     $eventdata->userto           = $userto;
1358     //using string manager directly so that strings in the message will be in the message recipients language rather than the senders
1359     $eventdata->subject          = get_string_manager()->get_string('unreadnewmessage', 'message', fullname($userfrom), $userto->lang);
1361     if ($format == FORMAT_HTML) {
1362         $eventdata->fullmessagehtml  = $message;
1363         //some message processors may revert to sending plain text even if html is supplied
1364         //so we keep both plain and html versions if we're intending to send html
1365         $eventdata->fullmessage = html_to_text($eventdata->fullmessagehtml);
1366     } else {
1367         $eventdata->fullmessage      = $message;
1368         $eventdata->fullmessagehtml  = '';
1369     }
1371     $eventdata->fullmessageformat = $format;
1372     $eventdata->smallmessage     = $message;//store the message unfiltered. Clean up on output.
1374     $s = new stdClass();
1375     $s->sitename = format_string($SITE->shortname, true, array('context' => context_course::instance(SITEID)));
1376     $s->url = $CFG->wwwroot.'/message/index.php?user='.$userto->id.'&id='.$userfrom->id;
1378     $emailtagline = get_string_manager()->get_string('emailtagline', 'message', $s, $userto->lang);
1379     if (!empty($eventdata->fullmessage)) {
1380         $eventdata->fullmessage .= "\n\n---------------------------------------------------------------------\n".$emailtagline;
1381     }
1382     if (!empty($eventdata->fullmessagehtml)) {
1383         $eventdata->fullmessagehtml .= "<br /><br />---------------------------------------------------------------------<br />".$emailtagline;
1384     }
1386     $eventdata->timecreated     = time();
1387     $eventdata->notification    = 0;
1388     return message_send($eventdata);
1391 /**
1392  * Constructs the add/remove contact link to display next to other users
1393  *
1394  * @param bool $incontactlist is the user a contact
1395  * @param bool $isblocked is the user blocked
1396  * @param stdClass $contact contact object
1397  * @param string $script the URL to send the user to when the link is clicked. If null, the current page.
1398  * @param bool $text include text next to the icons?
1399  * @param bool $icon include a graphical icon?
1400  * @return string
1401  */
1402 function message_get_contact_add_remove_link($incontactlist, $isblocked, $contact, $script=null, $text=false, $icon=true) {
1403     $strcontact = '';
1405     if($incontactlist){
1406         $strcontact = message_contact_link($contact->id, 'remove', true, $script, $text, $icon);
1407     } else if ($isblocked) {
1408         $strcontact = message_contact_link($contact->id, 'add', true, $script, $text, $icon);
1409     } else{
1410         $strcontact = message_contact_link($contact->id, 'add', true, $script, $text, $icon);
1411     }
1413     return $strcontact;
1416 /**
1417  * Constructs the block contact link to display next to other users
1418  *
1419  * @param bool $incontactlist is the user a contact?
1420  * @param bool $isblocked is the user blocked?
1421  * @param stdClass $contact contact object
1422  * @param string $script the URL to send the user to when the link is clicked. If null, the current page.
1423  * @param bool $text include text next to the icons?
1424  * @param bool $icon include a graphical icon?
1425  * @return string
1426  */
1427 function message_get_contact_block_link($incontactlist, $isblocked, $contact, $script=null, $text=false, $icon=true) {
1428     $strblock   = '';
1430     //commented out to allow the user to block a contact without having to remove them first
1431     /*if ($incontactlist) {
1432         //$strblock = '';
1433     } else*/
1434     if ($isblocked) {
1435         $strblock   = message_contact_link($contact->id, 'unblock', true, $script, $text, $icon);
1436     } else{
1437         $strblock   = message_contact_link($contact->id, 'block', true, $script, $text, $icon);
1438     }
1440     return $strblock;
1443 /**
1444  * Moves messages from a particular user from the message table (unread messages) to message_read
1445  * This is typically only used when a user is deleted
1446  *
1447  * @param object $userid User id
1448  * @return boolean success
1449  */
1450 function message_move_userfrom_unread2read($userid) {
1451     global $DB;
1453     // move all unread messages from message table to message_read
1454     if ($messages = $DB->get_records_select('message', 'useridfrom = ?', array($userid), 'timecreated')) {
1455         foreach ($messages as $message) {
1456             message_mark_message_read($message, 0); //set timeread to 0 as the message was never read
1457         }
1458     }
1459     return true;
1462 /**
1463  * marks ALL messages being sent from $fromuserid to $touserid as read
1464  *
1465  * @param int $touserid the id of the message recipient
1466  * @param int $fromuserid the id of the message sender
1467  * @return void
1468  */
1469 function message_mark_messages_read($touserid, $fromuserid) {
1470     global $DB;
1472     $sql = 'SELECT m.* FROM {message} m WHERE m.useridto=:useridto AND m.useridfrom=:useridfrom';
1473     $messages = $DB->get_recordset_sql($sql, array('useridto' => $touserid,'useridfrom' => $fromuserid));
1475     foreach ($messages as $message) {
1476         message_mark_message_read($message, time());
1477     }
1479     $messages->close();
1482 /**
1483  * Mark a single message as read
1484  *
1485  * @param stdClass $message An object with an object property ie $message->id which is an id in the message table
1486  * @param int $timeread the timestamp for when the message should be marked read. Usually time().
1487  * @param bool $messageworkingempty Is the message_working table already confirmed empty for this message?
1488  * @return int the ID of the message in the message_read table
1489  */
1490 function message_mark_message_read($message, $timeread, $messageworkingempty=false) {
1491     global $DB;
1493     $message->timeread = $timeread;
1495     $messageid = $message->id;
1496     unset($message->id);//unset because it will get a new id on insert into message_read
1498     //If any processors have pending actions abort them
1499     if (!$messageworkingempty) {
1500         $DB->delete_records('message_working', array('unreadmessageid' => $messageid));
1501     }
1502     $messagereadid = $DB->insert_record('message_read', $message);
1504     $DB->delete_records('message', array('id' => $messageid));
1506     // Get the context for the user who received the message.
1507     $context = context_user::instance($message->useridto, IGNORE_MISSING);
1508     // If the user no longer exists the context value will be false, in this case use the system context.
1509     if ($context === false) {
1510         $context = context_system::instance();
1511     }
1513     // Trigger event for reading a message.
1514     $event = \core\event\message_viewed::create(array(
1515         'objectid' => $messagereadid,
1516         'userid' => $message->useridto, // Using the user who read the message as they are the ones performing the action.
1517         'context' => $context,
1518         'relateduserid' => $message->useridfrom,
1519         'other' => array(
1520             'messageid' => $messageid
1521         )
1522     ));
1523     $event->trigger();
1525     return $messagereadid;
1528 /**
1529  * Get all message processors, validate corresponding plugin existance and
1530  * system configuration
1531  *
1532  * @param bool $ready only return ready-to-use processors
1533  * @param bool $reset Reset list of message processors (used in unit tests)
1534  * @param bool $resetonly Just reset, then exit
1535  * @return mixed $processors array of objects containing information on message processors
1536  */
1537 function get_message_processors($ready = false, $reset = false, $resetonly = false) {
1538     global $DB, $CFG;
1540     static $processors;
1541     if ($reset) {
1542         $processors = array();
1544         if ($resetonly) {
1545             return $processors;
1546         }
1547     }
1549     if (empty($processors)) {
1550         // Get all processors, ensure the name column is the first so it will be the array key
1551         $processors = $DB->get_records('message_processors', null, 'name DESC', 'name, id, enabled');
1552         foreach ($processors as &$processor){
1553             $processorfile = $CFG->dirroot. '/message/output/'.$processor->name.'/message_output_'.$processor->name.'.php';
1554             if (is_readable($processorfile)) {
1555                 include_once($processorfile);
1556                 $processclass = 'message_output_' . $processor->name;
1557                 if (class_exists($processclass)) {
1558                     $pclass = new $processclass();
1559                     $processor->object = $pclass;
1560                     $processor->configured = 0;
1561                     if ($pclass->is_system_configured()) {
1562                         $processor->configured = 1;
1563                     }
1564                     $processor->hassettings = 0;
1565                     if (is_readable($CFG->dirroot.'/message/output/'.$processor->name.'/settings.php')) {
1566                         $processor->hassettings = 1;
1567                     }
1568                     $processor->available = 1;
1569                 } else {
1570                     print_error('errorcallingprocessor', 'message');
1571                 }
1572             } else {
1573                 $processor->available = 0;
1574             }
1575         }
1576     }
1577     if ($ready) {
1578         // Filter out enabled and system_configured processors
1579         $readyprocessors = $processors;
1580         foreach ($readyprocessors as $readyprocessor) {
1581             if (!($readyprocessor->enabled && $readyprocessor->configured)) {
1582                 unset($readyprocessors[$readyprocessor->name]);
1583             }
1584         }
1585         return $readyprocessors;
1586     }
1588     return $processors;
1591 /**
1592  * Get all message providers, validate their plugin existance and
1593  * system configuration
1594  *
1595  * @return mixed $processors array of objects containing information on message processors
1596  */
1597 function get_message_providers() {
1598     global $CFG, $DB;
1600     $pluginman = core_plugin_manager::instance();
1602     $providers = $DB->get_records('message_providers', null, 'name');
1604     // Remove all the providers whose plugins are disabled or don't exist
1605     foreach ($providers as $providerid => $provider) {
1606         $plugin = $pluginman->get_plugin_info($provider->component);
1607         if ($plugin) {
1608             if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_MISSING) {
1609                 unset($providers[$providerid]);   // Plugins does not exist
1610                 continue;
1611             }
1612             if ($plugin->is_enabled() === false) {
1613                 unset($providers[$providerid]);   // Plugin disabled
1614                 continue;
1615             }
1616         }
1617     }
1618     return $providers;
1621 /**
1622  * Get an instance of the message_output class for one of the output plugins.
1623  * @param string $type the message output type. E.g. 'email' or 'jabber'.
1624  * @return message_output message_output the requested class.
1625  */
1626 function get_message_processor($type) {
1627     global $CFG;
1629     // Note, we cannot use the get_message_processors function here, becaues this
1630     // code is called during install after installing each messaging plugin, and
1631     // get_message_processors caches the list of installed plugins.
1633     $processorfile = $CFG->dirroot . "/message/output/{$type}/message_output_{$type}.php";
1634     if (!is_readable($processorfile)) {
1635         throw new coding_exception('Unknown message processor type ' . $type);
1636     }
1638     include_once($processorfile);
1640     $processclass = 'message_output_' . $type;
1641     if (!class_exists($processclass)) {
1642         throw new coding_exception('Message processor ' . $type .
1643                 ' does not define the right class');
1644     }
1646     return new $processclass();
1649 /**
1650  * Get messaging outputs default (site) preferences
1651  *
1652  * @return object $processors object containing information on message processors
1653  */
1654 function get_message_output_default_preferences() {
1655     return get_config('message');
1658 /**
1659  * Translate message default settings from binary value to the array of string
1660  * representing the settings to be stored. Also validate the provided value and
1661  * use default if it is malformed.
1662  *
1663  * @param  int    $plugindefault Default setting suggested by plugin
1664  * @param  string $processorname The name of processor
1665  * @return array  $settings array of strings in the order: $permitted, $loggedin, $loggedoff.
1666  */
1667 function translate_message_default_setting($plugindefault, $processorname) {
1668     // Preset translation arrays
1669     $permittedvalues = array(
1670         0x04 => 'disallowed',
1671         0x08 => 'permitted',
1672         0x0c => 'forced',
1673     );
1675     $loggedinstatusvalues = array(
1676         0x00 => null, // use null if loggedin/loggedoff is not defined
1677         0x01 => 'loggedin',
1678         0x02 => 'loggedoff',
1679     );
1681     // define the default setting
1682     $processor = get_message_processor($processorname);
1683     $default = $processor->get_default_messaging_settings();
1685     // Validate the value. It should not exceed the maximum size
1686     if (!is_int($plugindefault) || ($plugindefault > 0x0f)) {
1687         debugging(get_string('errortranslatingdefault', 'message'));
1688         $plugindefault = $default;
1689     }
1690     // Use plugin default setting of 'permitted' is 0
1691     if (!($plugindefault & MESSAGE_PERMITTED_MASK)) {
1692         $plugindefault = $default;
1693     }
1695     $permitted = $permittedvalues[$plugindefault & MESSAGE_PERMITTED_MASK];
1696     $loggedin = $loggedoff = null;
1698     if (($plugindefault & MESSAGE_PERMITTED_MASK) == MESSAGE_PERMITTED) {
1699         $loggedin = $loggedinstatusvalues[$plugindefault & MESSAGE_DEFAULT_LOGGEDIN];
1700         $loggedoff = $loggedinstatusvalues[$plugindefault & MESSAGE_DEFAULT_LOGGEDOFF];
1701     }
1703     return array($permitted, $loggedin, $loggedoff);
1706 /**
1707  * Return a list of page types
1708  * @param string $pagetype current page type
1709  * @param stdClass $parentcontext Block's parent context
1710  * @param stdClass $currentcontext Current context of block
1711  */
1712 function message_page_type_list($pagetype, $parentcontext, $currentcontext) {
1713     return array('messages-*'=>get_string('page-message-x', 'message'));
1716 /**
1717  * Get messages sent or/and received by the specified users.
1718  * Please note that this function return deleted messages too.
1719  *
1720  * @param  int      $useridto       the user id who received the message
1721  * @param  int      $useridfrom     the user id who sent the message. -10 or -20 for no-reply or support user
1722  * @param  int      $notifications  1 for retrieving notifications, 0 for messages, -1 for both
1723  * @param  bool     $read           true for retrieving read messages, false for unread
1724  * @param  string   $sort           the column name to order by including optionally direction
1725  * @param  int      $limitfrom      limit from
1726  * @param  int      $limitnum       limit num
1727  * @return external_description
1728  * @since  2.8
1729  */
1730 function message_get_messages($useridto, $useridfrom = 0, $notifications = -1, $read = true,
1731                                 $sort = 'mr.timecreated DESC', $limitfrom = 0, $limitnum = 0) {
1732     global $DB;
1734     $messagetable = $read ? '{message_read}' : '{message}';
1735     $params = array('deleted' => 0);
1737     // Empty useridto means that we are going to retrieve messages send by the useridfrom to any user.
1738     if (empty($useridto)) {
1739         $userfields = get_all_user_name_fields(true, 'u', '', 'userto');
1740         $joinsql = "JOIN {user} u ON u.id = mr.useridto";
1741         $usersql = "mr.useridfrom = :useridfrom AND u.deleted = :deleted";
1742         $params['useridfrom'] = $useridfrom;
1743     } else {
1744         $userfields = get_all_user_name_fields(true, 'u', '', 'userfrom');
1745         // Left join because useridfrom may be -10 or -20 (no-reply and support users).
1746         $joinsql = "LEFT JOIN {user} u ON u.id = mr.useridfrom";
1747         $usersql = "mr.useridto = :useridto AND (u.deleted IS NULL OR u.deleted = :deleted)";
1748         $params['useridto'] = $useridto;
1749         if (!empty($useridfrom)) {
1750             $usersql .= " AND mr.useridfrom = :useridfrom";
1751             $params['useridfrom'] = $useridfrom;
1752         }
1753     }
1755     // Now, if retrieve notifications, conversations or both.
1756     $typesql = "";
1757     if ($notifications !== -1) {
1758         $typesql = "AND mr.notification = :notification";
1759         $params['notification'] = ($notifications) ? 1 : 0;
1760     }
1762     $sql = "SELECT mr.*, $userfields
1763               FROM $messagetable mr
1764                    $joinsql
1765              WHERE $usersql
1766                    $typesql
1767              ORDER BY $sort";
1769     $messages = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
1770     return $messages;
1773 /**
1774  * Requires the JS libraries to send a message using a dialog.
1775  *
1776  * @return void
1777  */
1778 function message_messenger_requirejs() {
1779     global $PAGE;
1781     static $done = false;
1782     if ($done) {
1783         return;
1784     }
1786     $PAGE->requires->yui_module(
1787         array('moodle-core_message-messenger'),
1788         'Y.M.core_message.messenger.init',
1789         array(array())
1790     );
1791     $PAGE->requires->strings_for_js(array(
1792         'errorwhilesendingmessage',
1793         'messagesent',
1794         'messagetosend',
1795         'sendingmessage',
1796         'sendmessage',
1797         'viewconversation',
1798     ), 'core_message');
1799     $PAGE->requires->strings_for_js(array(
1800         'userisblockingyou',
1801         'userisblockingyounoncontact'
1802     ), 'message');
1803     $PAGE->requires->string_for_js('error', 'core');
1805     $done = true;
1808 /**
1809  * Requires the JS libraries for the toggle contact button.
1810  *
1811  * @return void
1812  */
1813 function message_togglecontact_requirejs() {
1814     global $PAGE;
1816     static $done = false;
1817     if ($done) {
1818         return;
1819     }
1821     $PAGE->requires->js_call_amd('message/toggle_contact_button', 'enhance', array('#toggle-contact-button'));
1822     $done = true;
1825 /**
1826  * Returns the attributes to place on a link to open the 'Send message' dialog.
1827  *
1828  * @param object $user User object.
1829  * @return void
1830  */
1831 function message_messenger_sendmessage_link_params($user) {
1832     $params = array(
1833         'data-trigger' => 'core_message-messenger::sendmessage',
1834         'data-fullname' => fullname($user),
1835         'data-userid' => $user->id,
1836         'role' => 'button'
1837     );
1839     if (message_is_user_non_contact_blocked($user)) {
1840         $params['data-blocked-string'] = 'userisblockingyounoncontact';
1841     } else if (message_is_user_blocked($user)) {
1842         $params['data-blocked-string'] = 'userisblockingyou';
1843     }
1845     return $params;
1848 /**
1849  * Returns the attributes to place on a contact button.
1850  *
1851  * @param object $user User object.
1852  * @param bool $iscontact
1853  * @return void
1854  */
1855 function message_togglecontact_link_params($user, $iscontact = false) {
1856     $params = array(
1857         'data-userid' => $user->id,
1858         'data-is-contact' => $iscontact,
1859         'id' => 'toggle-contact-button',
1860         'role' => 'button',
1861         'class' => 'ajax-contact-button',
1862     );
1864     return $params;
1867 /**
1868  * Determines if a user is permitted to send another user a private message.
1869  * If no sender is provided then it defaults to the logged in user.
1870  *
1871  * @param object $recipient User object.
1872  * @param object $sender User object.
1873  * @return bool true if user is permitted, false otherwise.
1874  */
1875 function message_can_post_message($recipient, $sender = null) {
1876     global $USER, $DB;
1878     if (is_null($sender)) {
1879         // The message is from the logged in user, unless otherwise specified.
1880         $sender = $USER;
1881     }
1883     if (!has_capability('moodle/site:sendmessage', context_system::instance(), $sender)) {
1884         return false;
1885     }
1887     // The recipient blocks messages from non-contacts and the
1888     // sender isn't a contact.
1889     if (message_is_user_non_contact_blocked($recipient, $sender)) {
1890         return false;
1891     }
1893     // The recipient has specifically blocked this sender.
1894     if (message_is_user_blocked($recipient, $sender)) {
1895         return false;
1896     }
1898     return true;
1901 /**
1902  * Checks if the recipient is allowing messages from users that aren't a
1903  * contact. If not then it checks to make sure the sender is in the
1904  * recipient's contacts.
1905  *
1906  * @param object $recipient User object.
1907  * @param object $sender User object.
1908  * @return bool true if $sender is blocked, false otherwise.
1909  */
1910 function message_is_user_non_contact_blocked($recipient, $sender = null) {
1911     global $USER, $DB;
1913     if (is_null($sender)) {
1914         // The message is from the logged in user, unless otherwise specified.
1915         $sender = $USER;
1916     }
1918     $blockednoncontacts = get_user_preferences('message_blocknoncontacts', '', $recipient->id);
1919     if (!empty($blockednoncontacts)) {
1920         // Confirm the sender is a contact of the recipient.
1921         $exists = $DB->record_exists('message_contacts', array('userid' => $recipient->id, 'contactid' => $sender->id));
1922         if ($exists) {
1923             // All good, the recipient is a contact of the sender.
1924             return false;
1925         } else {
1926             // Oh no, the recipient is not a contact. Looks like we can't send the message.
1927             return true;
1928         }
1929     }
1931     return false;
1934 /**
1935  * Checks if the recipient has specifically blocked the sending user.
1936  *
1937  * Note: This function will always return false if the sender has the
1938  * readallmessages capability at the system context level.
1939  *
1940  * @param object $recipient User object.
1941  * @param object $sender User object.
1942  * @return bool true if $sender is blocked, false otherwise.
1943  */
1944 function message_is_user_blocked($recipient, $sender = null) {
1945     global $USER, $DB;
1947     if (is_null($sender)) {
1948         // The message is from the logged in user, unless otherwise specified.
1949         $sender = $USER;
1950     }
1952     $systemcontext = context_system::instance();
1953     if (has_capability('moodle/site:readallmessages', $systemcontext, $sender)) {
1954         return false;
1955     }
1957     if ($contact = $DB->get_record('message_contacts', array('userid' => $recipient->id, 'contactid' => $sender->id))) {
1958         if ($contact->blocked) {
1959             return true;
1960         }
1961     }
1963     return false;