237cd7c36aa848f7a68fc1ecc82b4785d81f327b
[moodle.git] / message / classes / helper.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  * Contains helper class for the message area.
19  *
20  * @package    core_message
21  * @copyright  2016 Mark Nelson <markn@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace core_message;
27 defined('MOODLE_INTERNAL') || die();
29 /**
30  * Helper class for the message area.
31  *
32  * @copyright  2016 Mark Nelson <markn@moodle.com>
33  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34  */
35 class helper {
37     /**
38      * Helper function to retrieve the messages between two users
39      *
40      * @param int $userid the current user
41      * @param int $otheruserid the other user
42      * @param int $timedeleted the time the message was deleted
43      * @param int $limitfrom
44      * @param int $limitnum
45      * @param string $sort
46      * @param int $timefrom the time from the message being sent
47      * @param int $timeto the time up until the message being sent
48      * @return array of messages
49      */
50     public static function get_messages($userid, $otheruserid, $timedeleted = 0, $limitfrom = 0, $limitnum = 0,
51                                         $sort = 'timecreated ASC', $timefrom = 0, $timeto = 0) {
52         global $DB;
54         $hash = self::get_conversation_hash([$userid, $otheruserid]);
56         $sql = "SELECT m.id, m.useridfrom, m.subject, m.fullmessage, m.fullmessagehtml,
57                        m.fullmessageformat, m.smallmessage, m.timecreated, muaread.timecreated AS timeread
58                   FROM {message_conversations} mc
59             INNER JOIN {messages} m
60                     ON m.conversationid = mc.id
61              LEFT JOIN {message_user_actions} muaread
62                     ON (muaread.messageid = m.id
63                    AND muaread.userid = :userid1
64                    AND muaread.action = :readaction)";
65         $params = ['userid1' => $userid, 'readaction' => api::MESSAGE_ACTION_READ, 'convhash' => $hash];
67         if (empty($timedeleted)) {
68             $sql .= " LEFT JOIN {message_user_actions} mua
69                              ON (mua.messageid = m.id
70                             AND mua.userid = :userid2
71                             AND mua.action = :deleteaction
72                             AND mua.timecreated is NOT NULL)";
73         } else {
74             $sql .= " INNER JOIN {message_user_actions} mua
75                               ON (mua.messageid = m.id
76                              AND mua.userid = :userid2
77                              AND mua.action = :deleteaction
78                              AND mua.timecreated = :timedeleted)";
79             $params['timedeleted'] = $timedeleted;
80         }
82         $params['userid2'] = $userid;
83         $params['deleteaction'] = api::MESSAGE_ACTION_DELETED;
85         $sql .= " WHERE mc.convhash = :convhash";
87         if (!empty($timefrom)) {
88             $sql .= " AND m.timecreated >= :timefrom";
89             $params['timefrom'] = $timefrom;
90         }
92         if (!empty($timeto)) {
93             $sql .= " AND m.timecreated <= :timeto";
94             $params['timeto'] = $timeto;
95         }
97         if (empty($timedeleted)) {
98             $sql .= " AND mua.id is NULL";
99         }
101         $sql .= " ORDER BY m.$sort";
103         $messages = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
104         foreach ($messages as &$message) {
105             $message->useridto = ($message->useridfrom == $userid) ? $otheruserid : $userid;
106         }
108         return $messages;
109     }
111     /**
112      * Helper function to retrieve conversation messages.
113      *
114      * @param  int $userid The current user.
115      * @param  int $convid The conversation identifier.
116      * @param  int $timedeleted The time the message was deleted
117      * @param  int $limitfrom Return a subset of records, starting at this point (optional).
118      * @param  int $limitnum Return a subset comprising this many records in total (optional, required if $limitfrom is set).
119      * @param  string $sort The column name to order by including optionally direction.
120      * @param  int $timefrom The time from the message being sent.
121      * @param  int $timeto The time up until the message being sent.
122      * @return array of messages
123      */
124     public static function get_conversation_messages(int $userid, int $convid, int $timedeleted = 0, int $limitfrom = 0,
125                                                      int $limitnum = 0, string $sort = 'timecreated ASC', int $timefrom = 0,
126                                                      int $timeto = 0) : array {
127         global $DB;
129         $sql = "SELECT m.id, m.useridfrom, m.subject, m.fullmessage, m.fullmessagehtml,
130                        m.fullmessageformat, m.smallmessage, m.timecreated, muaread.timecreated AS timeread
131                   FROM {message_conversations} mc
132             INNER JOIN {messages} m
133                     ON m.conversationid = mc.id
134              LEFT JOIN {message_user_actions} muaread
135                     ON (muaread.messageid = m.id
136                    AND muaread.userid = :userid1
137                    AND muaread.action = :readaction)";
138         $params = ['userid1' => $userid, 'readaction' => api::MESSAGE_ACTION_READ, 'convid' => $convid];
140         if (empty($timedeleted)) {
141             $sql .= " LEFT JOIN {message_user_actions} mua
142                              ON (mua.messageid = m.id
143                             AND mua.userid = :userid2
144                             AND mua.action = :deleteaction
145                             AND mua.timecreated is NOT NULL)";
146         } else {
147             $sql .= " INNER JOIN {message_user_actions} mua
148                               ON (mua.messageid = m.id
149                              AND mua.userid = :userid2
150                              AND mua.action = :deleteaction
151                              AND mua.timecreated = :timedeleted)";
152             $params['timedeleted'] = $timedeleted;
153         }
155         $params['userid2'] = $userid;
156         $params['deleteaction'] = api::MESSAGE_ACTION_DELETED;
158         $sql .= " WHERE mc.id = :convid";
160         if (!empty($timefrom)) {
161             $sql .= " AND m.timecreated >= :timefrom";
162             $params['timefrom'] = $timefrom;
163         }
165         if (!empty($timeto)) {
166             $sql .= " AND m.timecreated <= :timeto";
167             $params['timeto'] = $timeto;
168         }
170         if (empty($timedeleted)) {
171             $sql .= " AND mua.id is NULL";
172         }
174         $sql .= " ORDER BY m.$sort";
176         $messages = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
178         return $messages;
179     }
181     /**
182      * Helper function to return a conversation messages with the involved members (only the ones
183      * who have sent any of these messages).
184      *
185      * @param int $userid The current userid.
186      * @param int $convid The conversation id.
187      * @param array $messages The formated array messages.
188      * @return array A conversation array with the messages and the involved members.
189      */
190     public static function format_conversation_messages(int $userid, int $convid, array $messages) : array {
191         global $USER;
193         // Create the conversation array.
194         $conversation = array(
195             'id' => $convid,
196         );
198         // Store the messages.
199         $arrmessages = array();
201         // We always view messages from oldest to newest, ensure we have it in that order.
202         $lastmessage = end($messages);
203         $firstmessage = reset($messages);
204         if ($lastmessage->timecreated < $firstmessage->timecreated) {
205             $messages = array_reverse($messages);
206         }
208         foreach ($messages as $message) {
209             // Store the message information.
210             $msg = new \stdClass();
211             $msg->id = $message->id;
212             $msg->useridfrom = $message->useridfrom;
213             $msg->text = message_format_message_text($message);
214             $msg->timecreated = $message->timecreated;
215             $arrmessages[] = $msg;
216         }
217         // Add the messages to the conversation.
218         $conversation['messages'] = $arrmessages;
220         // Get the users who have sent any of the $messages.
221         $memberids = array_unique(array_map(function($message) {
222             return $message->useridfrom;
223         }, $messages));
224         // Get members information.
225         $arrmembers = self::get_member_info($userid, $memberids);
226         // Add the members to the conversation.
227         $conversation['members'] = $arrmembers;
229         return $conversation;
230     }
232     /**
233      * Helper function to return an array of messages.
234      *
235      * @param int $userid
236      * @param array $messages
237      * @return array
238      */
239     public static function create_messages($userid, $messages) {
240         // Store the messages.
241         $arrmessages = array();
243         // We always view messages from oldest to newest, ensure we have it in that order.
244         $lastmessage = end($messages);
245         $firstmessage = reset($messages);
246         if ($lastmessage->timecreated < $firstmessage->timecreated) {
247             $messages = array_reverse($messages);
248         }
250         // Keeps track of the last day, month and year combo we were viewing.
251         $day = '';
252         $month = '';
253         $year = '';
254         foreach ($messages as $message) {
255             // Check if we are now viewing a different block period.
256             $displayblocktime = false;
257             $date = usergetdate($message->timecreated);
258             if ($day != $date['mday'] || $month != $date['month'] || $year != $date['year']) {
259                 $day = $date['mday'];
260                 $month = $date['month'];
261                 $year = $date['year'];
262                 $displayblocktime = true;
263             }
264             // Store the message to pass to the renderable.
265             $msg = new \stdClass();
266             $msg->id = $message->id;
267             $msg->text = message_format_message_text($message);
268             $msg->currentuserid = $userid;
269             $msg->useridfrom = $message->useridfrom;
270             $msg->useridto = $message->useridto;
271             $msg->displayblocktime = $displayblocktime;
272             $msg->timecreated = $message->timecreated;
273             $msg->timeread = $message->timeread;
274             $arrmessages[] = $msg;
275         }
277         return $arrmessages;
278     }
280     /**
281      * Helper function for creating a contact object.
282      *
283      * @param \stdClass $contact
284      * @param string $prefix
285      * @return \stdClass
286      */
287     public static function create_contact($contact, $prefix = '') {
288         global $PAGE;
290         // Create the data we are going to pass to the renderable.
291         $userfields = \user_picture::unalias($contact, array('lastaccess'), $prefix . 'id', $prefix);
292         $data = new \stdClass();
293         $data->userid = $userfields->id;
294         $data->useridfrom = null;
295         $data->fullname = fullname($userfields);
296         // Get the user picture data.
297         $userpicture = new \user_picture($userfields);
298         $userpicture->size = 1; // Size f1.
299         $data->profileimageurl = $userpicture->get_url($PAGE)->out(false);
300         $userpicture->size = 0; // Size f2.
301         $data->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
302         // Store the message if we have it.
303         $data->ismessaging = false;
304         $data->lastmessage = null;
305         $data->messageid = null;
306         if (isset($contact->smallmessage)) {
307             $data->ismessaging = true;
308             // Strip the HTML tags from the message for displaying in the contact area.
309             $data->lastmessage = clean_param($contact->smallmessage, PARAM_NOTAGS);
310             $data->useridfrom = $contact->useridfrom;
311             if (isset($contact->messageid)) {
312                 $data->messageid = $contact->messageid;
313             }
314         }
315         $data->isonline = null;
316         if (self::show_online_status($userfields)) {
317             $data->isonline = self::is_online($userfields->lastaccess);
318         }
319         $data->isblocked = isset($contact->blocked) ? (bool) $contact->blocked : false;
320         $data->isread = isset($contact->isread) ? (bool) $contact->isread : false;
321         $data->unreadcount = isset($contact->unreadcount) ? $contact->unreadcount : null;
323         return $data;
324     }
326     /**
327      * Helper function for checking if we should show the user's online status.
328      *
329      * @param \stdClass $user
330      * @return boolean
331      */
332     public static function show_online_status($user) {
333         global $CFG;
335         require_once($CFG->dirroot . '/user/lib.php');
337         if ($lastaccess = user_get_user_details($user, null, array('lastaccess'))) {
338             if (isset($lastaccess['lastaccess'])) {
339                 return true;
340             }
341         }
343         return false;
344     }
346     /**
347      * Helper function for checking the time meets the 'online' condition.
348      *
349      * @param int $lastaccess
350      * @return boolean
351      */
352     public static function is_online($lastaccess) {
353         global $CFG;
355         // Variable to check if we consider this user online or not.
356         $timetoshowusers = 300; // Seconds default.
357         if (isset($CFG->block_online_users_timetosee)) {
358             $timetoshowusers = $CFG->block_online_users_timetosee * 60;
359         }
360         $time = time() - $timetoshowusers;
362         return $lastaccess >= $time;
363     }
365     /**
366      * Get providers preferences.
367      *
368      * @param array $providers
369      * @param int $userid
370      * @return \stdClass
371      */
372     public static function get_providers_preferences($providers, $userid) {
373         $preferences = new \stdClass();
375         // Get providers preferences.
376         foreach ($providers as $provider) {
377             foreach (array('loggedin', 'loggedoff') as $state) {
378                 $linepref = get_user_preferences('message_provider_' . $provider->component . '_' . $provider->name
379                     . '_' . $state, '', $userid);
380                 if ($linepref == '') {
381                     continue;
382                 }
383                 $lineprefarray = explode(',', $linepref);
384                 $preferences->{$provider->component.'_'.$provider->name.'_'.$state} = array();
385                 foreach ($lineprefarray as $pref) {
386                     $preferences->{$provider->component.'_'.$provider->name.'_'.$state}[$pref] = 1;
387                 }
388             }
389         }
391         return $preferences;
392     }
394     /**
395      * Requires the JS libraries for the toggle contact button.
396      *
397      * @return void
398      */
399     public static function togglecontact_requirejs() {
400         global $PAGE;
402         static $done = false;
403         if ($done) {
404             return;
405         }
407         $PAGE->requires->js_call_amd('core_message/toggle_contact_button', 'enhance', array('#toggle-contact-button'));
408         $done = true;
409     }
411     /**
412      * Returns the attributes to place on a contact button.
413      *
414      * @param object $user User object.
415      * @param bool $iscontact
416      * @return array
417      */
418     public static function togglecontact_link_params($user, $iscontact = false) {
419         $params = array(
420             'data-userid' => $user->id,
421             'data-is-contact' => $iscontact,
422             'id' => 'toggle-contact-button',
423             'role' => 'button',
424             'class' => 'ajax-contact-button',
425         );
427         return $params;
428     }
430     /**
431      * Returns the conversation hash between users for easy look-ups in the DB.
432      *
433      * @param array $userids
434      * @return string
435      */
436     public static function get_conversation_hash(array $userids) {
437         sort($userids);
439         return sha1(implode('-', $userids));
440     }
442     /**
443      * Checks if legacy messages exist for a given user.
444      *
445      * @param int $userid
446      * @return bool
447      */
448     public static function legacy_messages_exist($userid) {
449         global $DB;
451         $sql = "SELECT id
452                   FROM {message} m
453                  WHERE useridfrom = ?
454                     OR useridto = ?";
455         $messageexists = $DB->record_exists_sql($sql, [$userid, $userid]);
457         $sql = "SELECT id
458                   FROM {message_read} m
459                  WHERE useridfrom = ?
460                     OR useridto = ?";
461         $messagereadexists = $DB->record_exists_sql($sql, [$userid, $userid]);
463         return $messageexists || $messagereadexists;
464     }
466     /**
467      * Returns conversation member info for the supplied users, relative to the supplied referenceuserid.
468      *
469      * This is the basic structure used when returning members, and includes information about the relationship between each member
470      * and the referenceuser, such as a whether the referenceuser has marked the member as a contact, or has blocked them.
471      *
472      * @param int $referenceuserid the id of the user which check contact and blocked status.
473      * @param array $userids
474      * @return array the array of objects containing member info, indexed by userid.
475      * @throws \coding_exception
476      * @throws \dml_exception
477      */
478     public static function get_member_info(int $referenceuserid, array $userids) : array {
479         global $DB, $PAGE;
481         list($useridsql, $usersparams) = $DB->get_in_or_equal($userids);
482         $userfields = \user_picture::fields('u', array('lastaccess'));
483         $userssql = "SELECT $userfields, mc.id AS contactid, mub.id AS blockedid
484                        FROM {user} u
485                   LEFT JOIN {message_contacts} mc
486                          ON (mc.userid = ? AND mc.contactid = u.id)
487                   LEFT JOIN {message_users_blocked} mub
488                          ON (mub.userid = ? AND mub.blockeduserid = u.id)
489                       WHERE u.id $useridsql
490                         AND u.deleted = 0";
491         $usersparams = array_merge([$referenceuserid, $referenceuserid], $usersparams);
492         $otherusers = $DB->get_records_sql($userssql, $usersparams);
494         $members = [];
495         foreach ($otherusers as $member) {
496             // Set basic data.
497             $data = new \stdClass();
498             $data->id = $member->id;
499             $data->fullname = fullname($member);
501             // Set the user picture data.
502             $userpicture = new \user_picture($member);
503             $userpicture->size = 1; // Size f1.
504             $data->profileimageurl = $userpicture->get_url($PAGE)->out(false);
505             $userpicture->size = 0; // Size f2.
506             $data->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
508             // Set online status indicators.
509             $data->isonline = self::show_online_status($member) ? self::is_online($member->lastaccess) : null;
510             $data->showonlinestatus = is_null($data->isonline) ? false : true;
512             // Set contact and blocked status indicators.
513             $data->iscontact = ($member->contactid) ? true : false;
514             $data->isblocked = ($member->blockedid) ? true : false;
516             $members[$data->id] = $data;
517         }
518         return $members;
519     }