MDL-63466 core_message: Added format_conversation_messages helper
[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 return a conversation messages with the involved members (only the ones
113      * who have sent any of these messages).
114      *
115      * @param int $userid The current userid.
116      * @param int $convid The conversation id.
117      * @param array $messages The formated array messages.
118      * @return array A conversation array with the messages and the involved members.
119      */
120     public static function format_conversation_messages(int $userid, int $convid, array $messages) : array {
121         global $USER;
123         // Create the conversation array.
124         $conversation = array(
125             'id' => $convid,
126         );
128         // Store the messages.
129         $arrmessages = array();
131         // We always view messages from oldest to newest, ensure we have it in that order.
132         $lastmessage = end($messages);
133         $firstmessage = reset($messages);
134         if ($lastmessage->timecreated < $firstmessage->timecreated) {
135             $messages = array_reverse($messages);
136         }
138         foreach ($messages as $message) {
139             // Store the message information.
140             $msg = new \stdClass();
141             $msg->id = $message->id;
142             $msg->useridfrom = $message->useridfrom;
143             $msg->text = message_format_message_text($message);
144             $msg->timecreated = $message->timecreated;
145             $arrmessages[] = $msg;
146         }
147         // Add the messages to the conversation.
148         $conversation['messages'] = $arrmessages;
150         // Get the users who have sent any of the $messages.
151         $memberids = array_unique(array_map(function($message) {
152             return $message->useridfrom;
153         }, $messages));
154         // Get members information.
155         $arrmembers = self::get_member_info($userid, $memberids);
156         // Add the members to the conversation.
157         $conversation['members'] = $arrmembers;
159         return $conversation;
160     }
162     /**
163      * Helper function to return an array of messages.
164      *
165      * @param int $userid
166      * @param array $messages
167      * @return array
168      */
169     public static function create_messages($userid, $messages) {
170         // Store the messages.
171         $arrmessages = array();
173         // We always view messages from oldest to newest, ensure we have it in that order.
174         $lastmessage = end($messages);
175         $firstmessage = reset($messages);
176         if ($lastmessage->timecreated < $firstmessage->timecreated) {
177             $messages = array_reverse($messages);
178         }
180         // Keeps track of the last day, month and year combo we were viewing.
181         $day = '';
182         $month = '';
183         $year = '';
184         foreach ($messages as $message) {
185             // Check if we are now viewing a different block period.
186             $displayblocktime = false;
187             $date = usergetdate($message->timecreated);
188             if ($day != $date['mday'] || $month != $date['month'] || $year != $date['year']) {
189                 $day = $date['mday'];
190                 $month = $date['month'];
191                 $year = $date['year'];
192                 $displayblocktime = true;
193             }
194             // Store the message to pass to the renderable.
195             $msg = new \stdClass();
196             $msg->id = $message->id;
197             $msg->text = message_format_message_text($message);
198             $msg->currentuserid = $userid;
199             $msg->useridfrom = $message->useridfrom;
200             $msg->useridto = $message->useridto;
201             $msg->displayblocktime = $displayblocktime;
202             $msg->timecreated = $message->timecreated;
203             $msg->timeread = $message->timeread;
204             $arrmessages[] = $msg;
205         }
207         return $arrmessages;
208     }
210     /**
211      * Helper function for creating a contact object.
212      *
213      * @param \stdClass $contact
214      * @param string $prefix
215      * @return \stdClass
216      */
217     public static function create_contact($contact, $prefix = '') {
218         global $PAGE;
220         // Create the data we are going to pass to the renderable.
221         $userfields = \user_picture::unalias($contact, array('lastaccess'), $prefix . 'id', $prefix);
222         $data = new \stdClass();
223         $data->userid = $userfields->id;
224         $data->useridfrom = null;
225         $data->fullname = fullname($userfields);
226         // Get the user picture data.
227         $userpicture = new \user_picture($userfields);
228         $userpicture->size = 1; // Size f1.
229         $data->profileimageurl = $userpicture->get_url($PAGE)->out(false);
230         $userpicture->size = 0; // Size f2.
231         $data->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
232         // Store the message if we have it.
233         $data->ismessaging = false;
234         $data->lastmessage = null;
235         $data->messageid = null;
236         if (isset($contact->smallmessage)) {
237             $data->ismessaging = true;
238             // Strip the HTML tags from the message for displaying in the contact area.
239             $data->lastmessage = clean_param($contact->smallmessage, PARAM_NOTAGS);
240             $data->useridfrom = $contact->useridfrom;
241             if (isset($contact->messageid)) {
242                 $data->messageid = $contact->messageid;
243             }
244         }
245         $data->isonline = null;
246         if (self::show_online_status($userfields)) {
247             $data->isonline = self::is_online($userfields->lastaccess);
248         }
249         $data->isblocked = isset($contact->blocked) ? (bool) $contact->blocked : false;
250         $data->isread = isset($contact->isread) ? (bool) $contact->isread : false;
251         $data->unreadcount = isset($contact->unreadcount) ? $contact->unreadcount : null;
253         return $data;
254     }
256     /**
257      * Helper function for checking if we should show the user's online status.
258      *
259      * @param \stdClass $user
260      * @return boolean
261      */
262     public static function show_online_status($user) {
263         global $CFG;
265         require_once($CFG->dirroot . '/user/lib.php');
267         if ($lastaccess = user_get_user_details($user, null, array('lastaccess'))) {
268             if (isset($lastaccess['lastaccess'])) {
269                 return true;
270             }
271         }
273         return false;
274     }
276     /**
277      * Helper function for checking the time meets the 'online' condition.
278      *
279      * @param int $lastaccess
280      * @return boolean
281      */
282     public static function is_online($lastaccess) {
283         global $CFG;
285         // Variable to check if we consider this user online or not.
286         $timetoshowusers = 300; // Seconds default.
287         if (isset($CFG->block_online_users_timetosee)) {
288             $timetoshowusers = $CFG->block_online_users_timetosee * 60;
289         }
290         $time = time() - $timetoshowusers;
292         return $lastaccess >= $time;
293     }
295     /**
296      * Get providers preferences.
297      *
298      * @param array $providers
299      * @param int $userid
300      * @return \stdClass
301      */
302     public static function get_providers_preferences($providers, $userid) {
303         $preferences = new \stdClass();
305         // Get providers preferences.
306         foreach ($providers as $provider) {
307             foreach (array('loggedin', 'loggedoff') as $state) {
308                 $linepref = get_user_preferences('message_provider_' . $provider->component . '_' . $provider->name
309                     . '_' . $state, '', $userid);
310                 if ($linepref == '') {
311                     continue;
312                 }
313                 $lineprefarray = explode(',', $linepref);
314                 $preferences->{$provider->component.'_'.$provider->name.'_'.$state} = array();
315                 foreach ($lineprefarray as $pref) {
316                     $preferences->{$provider->component.'_'.$provider->name.'_'.$state}[$pref] = 1;
317                 }
318             }
319         }
321         return $preferences;
322     }
324     /**
325      * Requires the JS libraries for the toggle contact button.
326      *
327      * @return void
328      */
329     public static function togglecontact_requirejs() {
330         global $PAGE;
332         static $done = false;
333         if ($done) {
334             return;
335         }
337         $PAGE->requires->js_call_amd('core_message/toggle_contact_button', 'enhance', array('#toggle-contact-button'));
338         $done = true;
339     }
341     /**
342      * Returns the attributes to place on a contact button.
343      *
344      * @param object $user User object.
345      * @param bool $iscontact
346      * @return array
347      */
348     public static function togglecontact_link_params($user, $iscontact = false) {
349         $params = array(
350             'data-userid' => $user->id,
351             'data-is-contact' => $iscontact,
352             'id' => 'toggle-contact-button',
353             'role' => 'button',
354             'class' => 'ajax-contact-button',
355         );
357         return $params;
358     }
360     /**
361      * Returns the conversation hash between users for easy look-ups in the DB.
362      *
363      * @param array $userids
364      * @return string
365      */
366     public static function get_conversation_hash(array $userids) {
367         sort($userids);
369         return sha1(implode('-', $userids));
370     }
372     /**
373      * Returns the cache key for the time created value of the last message between two users.
374      *
375      * @param int $userid
376      * @param int $user2id
377      * @return string
378      */
379     public static function get_last_message_time_created_cache_key($userid, $user2id) {
380         $ids = [$userid, $user2id];
381         sort($ids);
382         return implode('_', $ids);
383     }
385     /**
386      * Checks if legacy messages exist for a given user.
387      *
388      * @param int $userid
389      * @return bool
390      */
391     public static function legacy_messages_exist($userid) {
392         global $DB;
394         $sql = "SELECT id
395                   FROM {message} m
396                  WHERE useridfrom = ?
397                     OR useridto = ?";
398         $messageexists = $DB->record_exists_sql($sql, [$userid, $userid]);
400         $sql = "SELECT id
401                   FROM {message_read} m
402                  WHERE useridfrom = ?
403                     OR useridto = ?";
404         $messagereadexists = $DB->record_exists_sql($sql, [$userid, $userid]);
406         return $messageexists || $messagereadexists;
407     }
409     /**
410      * Returns conversation member info for the supplied users, relative to the supplied referenceuserid.
411      *
412      * This is the basic structure used when returning members, and includes information about the relationship between each member
413      * and the referenceuser, such as a whether the referenceuser has marked the member as a contact, or has blocked them.
414      *
415      * @param int $referenceuserid the id of the user which check contact and blocked status.
416      * @param array $userids
417      * @return array the array of objects containing member info, indexed by userid.
418      * @throws \coding_exception
419      * @throws \dml_exception
420      */
421     public static function get_member_info(int $referenceuserid, array $userids) : array {
422         global $DB, $PAGE;
424         list($useridsql, $usersparams) = $DB->get_in_or_equal($userids);
425         $userfields = \user_picture::fields('u', array('lastaccess'));
426         $userssql = "SELECT $userfields, mc.id AS contactid, mub.id AS blockedid
427                        FROM {user} u
428                   LEFT JOIN {message_contacts} mc
429                          ON (mc.userid = ? AND mc.contactid = u.id)
430                   LEFT JOIN {message_users_blocked} mub
431                          ON (mub.userid = ? AND mub.blockeduserid = u.id)
432                       WHERE u.id $useridsql
433                         AND u.deleted = 0";
434         $usersparams = array_merge([$referenceuserid, $referenceuserid], $usersparams);
435         $otherusers = $DB->get_records_sql($userssql, $usersparams);
437         $members = [];
438         foreach ($otherusers as $member) {
439             // Set basic data.
440             $data = new \stdClass();
441             $data->id = $member->id;
442             $data->fullname = fullname($member);
444             // Set the user picture data.
445             $userpicture = new \user_picture($member);
446             $userpicture->size = 1; // Size f1.
447             $data->profileimageurl = $userpicture->get_url($PAGE)->out(false);
448             $userpicture->size = 0; // Size f2.
449             $data->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
451             // Set online status indicators.
452             $data->isonline = self::show_online_status($member) ? self::is_online($member->lastaccess) : null;
453             $data->showonlinestatus = is_null($data->isonline) ? false : true;
455             // Set contact and blocked status indicators.
456             $data->iscontact = ($member->contactid) ? true : false;
457             $data->isblocked = ($member->blockedid) ? true : false;
459             $members[$data->id] = $data;
460         }
461         return $members;
462     }