MDL-65896 admin: add admin setting to show emoji picker
[moodle.git] / message / classes / helper.php
CommitLineData
879e2bef
MN
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/>.
16
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 */
24
25namespace core_message;
26
27defined('MOODLE_INTERNAL') || die();
28
8be24909
RW
29require_once($CFG->dirroot . '/message/lib.php');
30
879e2bef
MN
31/**
32 * Helper class for the message area.
33 *
34 * @copyright 2016 Mark Nelson <markn@moodle.com>
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 */
37class helper {
38
39 /**
40 * Helper function to retrieve the messages between two users
41 *
7ece4ca9
MN
42 * TODO: This function should be removed once the related web services go through final deprecation.
43 * The related web services are data_for_messagearea_messages AND data_for_messagearea_get_most_recent_message.
44 * Followup: MDL-63261
4461288d 45 *
879e2bef
MN
46 * @param int $userid the current user
47 * @param int $otheruserid the other user
dec0cd99 48 * @param int $timedeleted the time the message was deleted
879e2bef
MN
49 * @param int $limitfrom
50 * @param int $limitnum
51 * @param string $sort
ffd7798c
MN
52 * @param int $timefrom the time from the message being sent
53 * @param int $timeto the time up until the message being sent
879e2bef
MN
54 * @return array of messages
55 */
7b55aaa1 56 public static function get_messages($userid, $otheruserid, $timedeleted = 0, $limitfrom = 0, $limitnum = 0,
ffd7798c 57 $sort = 'timecreated ASC', $timefrom = 0, $timeto = 0) {
879e2bef
MN
58 global $DB;
59
e159b53b
MN
60 $hash = self::get_conversation_hash([$userid, $otheruserid]);
61
62 $sql = "SELECT m.id, m.useridfrom, m.subject, m.fullmessage, m.fullmessagehtml,
3a5afbf5 63 m.fullmessageformat, m.fullmessagetrust, m.smallmessage, m.timecreated,
64 mc.contextid, muaread.timecreated AS timeread
e159b53b
MN
65 FROM {message_conversations} mc
66 INNER JOIN {messages} m
67 ON m.conversationid = mc.id
68 LEFT JOIN {message_user_actions} muaread
69 ON (muaread.messageid = m.id
70 AND muaread.userid = :userid1
71 AND muaread.action = :readaction)";
72 $params = ['userid1' => $userid, 'readaction' => api::MESSAGE_ACTION_READ, 'convhash' => $hash];
ebc746dc 73
883ce421
MN
74 if (empty($timedeleted)) {
75 $sql .= " LEFT JOIN {message_user_actions} mua
e159b53b
MN
76 ON (mua.messageid = m.id
77 AND mua.userid = :userid2
78 AND mua.action = :deleteaction
79 AND mua.timecreated is NOT NULL)";
883ce421
MN
80 } else {
81 $sql .= " INNER JOIN {message_user_actions} mua
e159b53b
MN
82 ON (mua.messageid = m.id
83 AND mua.userid = :userid2
84 AND mua.action = :deleteaction
85 AND mua.timecreated = :timedeleted)";
86 $params['timedeleted'] = $timedeleted;
883ce421 87 }
fb1469d8 88
e159b53b
MN
89 $params['userid2'] = $userid;
90 $params['deleteaction'] = api::MESSAGE_ACTION_DELETED;
91
92 $sql .= " WHERE mc.convhash = :convhash";
fb1469d8 93
ffd7798c 94 if (!empty($timefrom)) {
e159b53b
MN
95 $sql .= " AND m.timecreated >= :timefrom";
96 $params['timefrom'] = $timefrom;
fb1469d8
RW
97 }
98
ffd7798c 99 if (!empty($timeto)) {
e159b53b
MN
100 $sql .= " AND m.timecreated <= :timeto";
101 $params['timeto'] = $timeto;
883ce421
MN
102 }
103
104 if (empty($timedeleted)) {
105 $sql .= " AND mua.id is NULL";
fb1469d8
RW
106 }
107
883ce421 108 $sql .= " ORDER BY m.$sort";
879e2bef 109
e159b53b
MN
110 $messages = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
111 foreach ($messages as &$message) {
112 $message->useridto = ($message->useridfrom == $userid) ? $otheruserid : $userid;
113 }
114
115 return $messages;
879e2bef
MN
116 }
117
fb04293b
SA
118 /**
119 * Helper function to retrieve conversation messages.
120 *
121 * @param int $userid The current user.
122 * @param int $convid The conversation identifier.
123 * @param int $timedeleted The time the message was deleted
124 * @param int $limitfrom Return a subset of records, starting at this point (optional).
125 * @param int $limitnum Return a subset comprising this many records in total (optional, required if $limitfrom is set).
126 * @param string $sort The column name to order by including optionally direction.
127 * @param int $timefrom The time from the message being sent.
128 * @param int $timeto The time up until the message being sent.
129 * @return array of messages
130 */
131 public static function get_conversation_messages(int $userid, int $convid, int $timedeleted = 0, int $limitfrom = 0,
132 int $limitnum = 0, string $sort = 'timecreated ASC', int $timefrom = 0,
133 int $timeto = 0) : array {
134 global $DB;
135
136 $sql = "SELECT m.id, m.useridfrom, m.subject, m.fullmessage, m.fullmessagehtml,
3a5afbf5 137 m.fullmessageformat, m.fullmessagetrust, m.smallmessage, m.timecreated,
138 mc.contextid, muaread.timecreated AS timeread
fb04293b
SA
139 FROM {message_conversations} mc
140 INNER JOIN {messages} m
141 ON m.conversationid = mc.id
142 LEFT JOIN {message_user_actions} muaread
143 ON (muaread.messageid = m.id
144 AND muaread.userid = :userid1
145 AND muaread.action = :readaction)";
146 $params = ['userid1' => $userid, 'readaction' => api::MESSAGE_ACTION_READ, 'convid' => $convid];
147
148 if (empty($timedeleted)) {
149 $sql .= " LEFT JOIN {message_user_actions} mua
150 ON (mua.messageid = m.id
151 AND mua.userid = :userid2
152 AND mua.action = :deleteaction
153 AND mua.timecreated is NOT NULL)";
154 } else {
155 $sql .= " INNER JOIN {message_user_actions} mua
156 ON (mua.messageid = m.id
157 AND mua.userid = :userid2
158 AND mua.action = :deleteaction
159 AND mua.timecreated = :timedeleted)";
160 $params['timedeleted'] = $timedeleted;
161 }
162
163 $params['userid2'] = $userid;
164 $params['deleteaction'] = api::MESSAGE_ACTION_DELETED;
165
166 $sql .= " WHERE mc.id = :convid";
167
168 if (!empty($timefrom)) {
169 $sql .= " AND m.timecreated >= :timefrom";
170 $params['timefrom'] = $timefrom;
171 }
172
173 if (!empty($timeto)) {
174 $sql .= " AND m.timecreated <= :timeto";
175 $params['timeto'] = $timeto;
176 }
177
178 if (empty($timedeleted)) {
179 $sql .= " AND mua.id is NULL";
180 }
181
182 $sql .= " ORDER BY m.$sort";
183
184 $messages = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
185
186 return $messages;
187 }
188
d89d0d65
SA
189 /**
190 * Helper function to return a conversation messages with the involved members (only the ones
191 * who have sent any of these messages).
192 *
193 * @param int $userid The current userid.
194 * @param int $convid The conversation id.
195 * @param array $messages The formated array messages.
196 * @return array A conversation array with the messages and the involved members.
197 */
198 public static function format_conversation_messages(int $userid, int $convid, array $messages) : array {
199 global $USER;
200
201 // Create the conversation array.
202 $conversation = array(
203 'id' => $convid,
204 );
205
206 // Store the messages.
207 $arrmessages = array();
208
d89d0d65
SA
209 foreach ($messages as $message) {
210 // Store the message information.
211 $msg = new \stdClass();
212 $msg->id = $message->id;
213 $msg->useridfrom = $message->useridfrom;
214 $msg->text = message_format_message_text($message);
215 $msg->timecreated = $message->timecreated;
216 $arrmessages[] = $msg;
217 }
218 // Add the messages to the conversation.
219 $conversation['messages'] = $arrmessages;
220
221 // Get the users who have sent any of the $messages.
222 $memberids = array_unique(array_map(function($message) {
223 return $message->useridfrom;
224 }, $messages));
32b4212e
RW
225
226 if (!empty($memberids)) {
227 // Get members information.
228 $conversation['members'] = self::get_member_info($userid, $memberids);
229 } else {
230 $conversation['members'] = array();
231 }
d89d0d65
SA
232
233 return $conversation;
234 }
235
879e2bef 236 /**
de55cb1b 237 * Helper function to return an array of messages.
879e2bef 238 *
7ece4ca9
MN
239 * TODO: This function should be removed once the related web services go through final deprecation.
240 * The related web services are data_for_messagearea_messages AND data_for_messagearea_get_most_recent_message.
241 * Followup: MDL-63261
4461288d 242 *
879e2bef
MN
243 * @param int $userid
244 * @param array $messages
de55cb1b 245 * @return array
879e2bef
MN
246 */
247 public static function create_messages($userid, $messages) {
248 // Store the messages.
249 $arrmessages = array();
250
8ec78c48
MN
251 // We always view messages from oldest to newest, ensure we have it in that order.
252 $lastmessage = end($messages);
253 $firstmessage = reset($messages);
254 if ($lastmessage->timecreated < $firstmessage->timecreated) {
255 $messages = array_reverse($messages);
256 }
257
879e2bef
MN
258 // Keeps track of the last day, month and year combo we were viewing.
259 $day = '';
260 $month = '';
261 $year = '';
262 foreach ($messages as $message) {
263 // Check if we are now viewing a different block period.
3090f52f 264 $displayblocktime = false;
879e2bef
MN
265 $date = usergetdate($message->timecreated);
266 if ($day != $date['mday'] || $month != $date['month'] || $year != $date['year']) {
267 $day = $date['mday'];
268 $month = $date['month'];
269 $year = $date['year'];
3090f52f 270 $displayblocktime = true;
879e2bef
MN
271 }
272 // Store the message to pass to the renderable.
273 $msg = new \stdClass();
3090f52f 274 $msg->id = $message->id;
879e2bef
MN
275 $msg->text = message_format_message_text($message);
276 $msg->currentuserid = $userid;
277 $msg->useridfrom = $message->useridfrom;
278 $msg->useridto = $message->useridto;
3090f52f 279 $msg->displayblocktime = $displayblocktime;
879e2bef 280 $msg->timecreated = $message->timecreated;
3090f52f 281 $msg->timeread = $message->timeread;
de55cb1b 282 $arrmessages[] = $msg;
879e2bef
MN
283 }
284
285 return $arrmessages;
286 }
287
288 /**
de55cb1b 289 * Helper function for creating a contact object.
879e2bef
MN
290 *
291 * @param \stdClass $contact
cd03b8d7 292 * @param string $prefix
de55cb1b 293 * @return \stdClass
879e2bef 294 */
cd03b8d7 295 public static function create_contact($contact, $prefix = '') {
bf58081d 296 global $PAGE;
879e2bef
MN
297
298 // Create the data we are going to pass to the renderable.
cd03b8d7 299 $userfields = \user_picture::unalias($contact, array('lastaccess'), $prefix . 'id', $prefix);
879e2bef
MN
300 $data = new \stdClass();
301 $data->userid = $userfields->id;
89a70ba1 302 $data->useridfrom = null;
879e2bef
MN
303 $data->fullname = fullname($userfields);
304 // Get the user picture data.
305 $userpicture = new \user_picture($userfields);
306 $userpicture->size = 1; // Size f1.
307 $data->profileimageurl = $userpicture->get_url($PAGE)->out(false);
308 $userpicture->size = 0; // Size f2.
309 $data->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
310 // Store the message if we have it.
5bf0ff27 311 $data->ismessaging = false;
cd03b8d7 312 $data->lastmessage = null;
0802c38a 313 $data->lastmessagedate = null;
cd03b8d7 314 $data->messageid = null;
879e2bef 315 if (isset($contact->smallmessage)) {
5bf0ff27 316 $data->ismessaging = true;
54d83992
RW
317 // Strip the HTML tags from the message for displaying in the contact area.
318 $data->lastmessage = clean_param($contact->smallmessage, PARAM_NOTAGS);
0802c38a 319 $data->lastmessagedate = $contact->timecreated;
89a70ba1 320 $data->useridfrom = $contact->useridfrom;
cd03b8d7
MN
321 if (isset($contact->messageid)) {
322 $data->messageid = $contact->messageid;
323 }
879e2bef 324 }
cb805753 325 $data->isonline = null;
4cdc9f73 326 $user = \core_user::get_user($data->userid);
327 if (self::show_online_status($user)) {
cb805753
MN
328 $data->isonline = self::is_online($userfields->lastaccess);
329 }
1f64514d
MN
330 $data->isblocked = isset($contact->blocked) ? (bool) $contact->blocked : false;
331 $data->isread = isset($contact->isread) ? (bool) $contact->isread : false;
c33b7d89 332 $data->unreadcount = isset($contact->unreadcount) ? $contact->unreadcount : null;
d2708759 333 $data->conversationid = $contact->conversationid ?? null;
879e2bef 334
de55cb1b 335 return $data;
879e2bef 336 }
bf58081d 337
cb805753
MN
338 /**
339 * Helper function for checking if we should show the user's online status.
340 *
341 * @param \stdClass $user
342 * @return boolean
343 */
344 public static function show_online_status($user) {
345 global $CFG;
346
347 require_once($CFG->dirroot . '/user/lib.php');
348
349 if ($lastaccess = user_get_user_details($user, null, array('lastaccess'))) {
350 if (isset($lastaccess['lastaccess'])) {
351 return true;
352 }
353 }
354
355 return false;
356 }
357
bf58081d
MN
358 /**
359 * Helper function for checking the time meets the 'online' condition.
360 *
361 * @param int $lastaccess
362 * @return boolean
363 */
364 public static function is_online($lastaccess) {
365 global $CFG;
366
367 // Variable to check if we consider this user online or not.
368 $timetoshowusers = 300; // Seconds default.
369 if (isset($CFG->block_online_users_timetosee)) {
370 $timetoshowusers = $CFG->block_online_users_timetosee * 60;
371 }
372 $time = time() - $timetoshowusers;
373
374 return $lastaccess >= $time;
375 }
79f6c36c
MN
376
377 /**
378 * Get providers preferences.
379 *
380 * @param array $providers
381 * @param int $userid
382 * @return \stdClass
383 */
384 public static function get_providers_preferences($providers, $userid) {
385 $preferences = new \stdClass();
386
387 // Get providers preferences.
388 foreach ($providers as $provider) {
389 foreach (array('loggedin', 'loggedoff') as $state) {
390 $linepref = get_user_preferences('message_provider_' . $provider->component . '_' . $provider->name
391 . '_' . $state, '', $userid);
392 if ($linepref == '') {
393 continue;
394 }
395 $lineprefarray = explode(',', $linepref);
396 $preferences->{$provider->component.'_'.$provider->name.'_'.$state} = array();
397 foreach ($lineprefarray as $pref) {
398 $preferences->{$provider->component.'_'.$provider->name.'_'.$state}[$pref] = 1;
399 }
400 }
401 }
402
403 return $preferences;
404 }
405
406 /**
407 * Requires the JS libraries for the toggle contact button.
408 *
409 * @return void
410 */
411 public static function togglecontact_requirejs() {
412 global $PAGE;
413
414 static $done = false;
415 if ($done) {
416 return;
417 }
418
419 $PAGE->requires->js_call_amd('core_message/toggle_contact_button', 'enhance', array('#toggle-contact-button'));
420 $done = true;
421 }
422
423 /**
424 * Returns the attributes to place on a contact button.
425 *
426 * @param object $user User object.
427 * @param bool $iscontact
428 * @return array
429 */
430 public static function togglecontact_link_params($user, $iscontact = false) {
431 $params = array(
432 'data-userid' => $user->id,
433 'data-is-contact' => $iscontact,
434 'id' => 'toggle-contact-button',
435 'role' => 'button',
436 'class' => 'ajax-contact-button',
437 );
438
439 return $params;
440 }
ffd7798c 441
6f9d34bd
MN
442 /**
443 * Requires the JS libraries for the message user button.
444 *
445 * @return void
446 */
447 public static function messageuser_requirejs() {
448 global $PAGE;
449
450 static $done = false;
451 if ($done) {
452 return;
453 }
454
455 $PAGE->requires->js_call_amd('core_message/message_user_button', 'send', array('#message-user-button'));
456 $done = true;
457 }
458
459 /**
460 * Returns the attributes to place on the message user button.
461 *
462 * @param int $useridto
463 * @return array
464 */
465 public static function messageuser_link_params(int $useridto) : array {
466 global $USER;
467
468 return [
469 'id' => 'message-user-button',
470 'role' => 'button',
471 'data-conversationid' => api::get_conversation_between_users([$USER->id, $useridto]),
472 'data-userid' => $useridto,
473 ];
474 }
475
b2cd17e6
MN
476 /**
477 * Returns the conversation hash between users for easy look-ups in the DB.
478 *
479 * @param array $userids
480 * @return string
481 */
482 public static function get_conversation_hash(array $userids) {
483 sort($userids);
484
485 return sha1(implode('-', $userids));
486 }
487
4699b8bc
AN
488 /**
489 * Returns the cache key for the time created value of the last message of this conversation.
490 *
491 * @param int $convid The conversation identifier.
492 * @return string The key.
493 */
494 public static function get_last_message_time_created_cache_key(int $convid) {
495 return $convid;
496 }
497
78348dfc
MN
498 /**
499 * Checks if legacy messages exist for a given user.
500 *
501 * @param int $userid
502 * @return bool
503 */
504 public static function legacy_messages_exist($userid) {
505 global $DB;
506
507 $sql = "SELECT id
508 FROM {message} m
509 WHERE useridfrom = ?
510 OR useridto = ?";
511 $messageexists = $DB->record_exists_sql($sql, [$userid, $userid]);
512
513 $sql = "SELECT id
514 FROM {message_read} m
515 WHERE useridfrom = ?
516 OR useridto = ?";
517 $messagereadexists = $DB->record_exists_sql($sql, [$userid, $userid]);
518
519 return $messageexists || $messagereadexists;
520 }
2a2b86f1
JD
521
522 /**
523 * Returns conversation member info for the supplied users, relative to the supplied referenceuserid.
524 *
525 * This is the basic structure used when returning members, and includes information about the relationship between each member
526 * and the referenceuser, such as a whether the referenceuser has marked the member as a contact, or has blocked them.
527 *
528 * @param int $referenceuserid the id of the user which check contact and blocked status.
529 * @param array $userids
054834b0 530 * @param bool $includecontactrequests Do we want to include contact requests with this data?
82e0973c
MN
531 * @param bool $includeprivacyinfo Do we want to include whether the user can message another, and if the user
532 * requires a contact.
2a2b86f1
JD
533 * @return array the array of objects containing member info, indexed by userid.
534 * @throws \coding_exception
535 * @throws \dml_exception
536 */
82e0973c
MN
537 public static function get_member_info(int $referenceuserid, array $userids, bool $includecontactrequests = false,
538 bool $includeprivacyinfo = false) : array {
2a2b86f1
JD
539 global $DB, $PAGE;
540
6981de10
MN
541 // Prevent exception being thrown when array is empty.
542 if (empty($userids)) {
543 return [];
544 }
545
2a2b86f1
JD
546 list($useridsql, $usersparams) = $DB->get_in_or_equal($userids);
547 $userfields = \user_picture::fields('u', array('lastaccess'));
eb5865da 548 $userssql = "SELECT $userfields, u.deleted, mc.id AS contactid, mub.id AS blockedid
2a2b86f1
JD
549 FROM {user} u
550 LEFT JOIN {message_contacts} mc
38004e77 551 ON ((mc.userid = ? AND mc.contactid = u.id) OR (mc.userid = u.id AND mc.contactid = ?))
2a2b86f1
JD
552 LEFT JOIN {message_users_blocked} mub
553 ON (mub.userid = ? AND mub.blockeduserid = u.id)
eb5865da 554 WHERE u.id $useridsql";
38004e77 555 $usersparams = array_merge([$referenceuserid, $referenceuserid, $referenceuserid], $usersparams);
2a2b86f1
JD
556 $otherusers = $DB->get_records_sql($userssql, $usersparams);
557
558 $members = [];
559 foreach ($otherusers as $member) {
560 // Set basic data.
561 $data = new \stdClass();
562 $data->id = $member->id;
563 $data->fullname = fullname($member);
564
9b39f282
MN
565 // Create the URL for their profile.
566 $profileurl = new \moodle_url('/user/profile.php', ['id' => $member->id]);
567 $data->profileurl = $profileurl->out(false);
568
2a2b86f1
JD
569 // Set the user picture data.
570 $userpicture = new \user_picture($member);
571 $userpicture->size = 1; // Size f1.
572 $data->profileimageurl = $userpicture->get_url($PAGE)->out(false);
573 $userpicture->size = 0; // Size f2.
574 $data->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
575
576 // Set online status indicators.
52beee65
MN
577 $data->isonline = false;
578 $data->showonlinestatus = false;
579 if (!$member->deleted) {
580 $data->isonline = self::show_online_status($member) ? self::is_online($member->lastaccess) : null;
581 $data->showonlinestatus = is_null($data->isonline) ? false : true;
582 }
2a2b86f1
JD
583
584 // Set contact and blocked status indicators.
585 $data->iscontact = ($member->contactid) ? true : false;
66fffdbc
MN
586
587 // We don't want that a user has been blocked if they can message the user anyways.
588 $canmessageifblocked = api::can_send_message($referenceuserid, $member->id, true);
589 $data->isblocked = ($member->blockedid && !$canmessageifblocked) ? true : false;
2a2b86f1 590
eb5865da
JD
591 $data->isdeleted = ($member->deleted) ? true : false;
592
82e0973c
MN
593 $data->requirescontact = null;
594 $data->canmessage = null;
90403c5d 595 $data->canmessageevenifblocked = null;
82e0973c
MN
596 if ($includeprivacyinfo) {
597 $privacysetting = api::get_user_privacy_messaging_preference($member->id);
598 $data->requirescontact = $privacysetting == api::MESSAGE_PRIVACY_ONLYCONTACTS;
599
66fffdbc
MN
600 // Here we check that if the sender wanted to block the recipient, the
601 // recipient would still be able to message them regardless.
602 $data->canmessageevenifblocked = !$data->isdeleted && $canmessageifblocked;
06d046c1 603 $data->canmessage = !$data->isdeleted && api::can_send_message($member->id, $referenceuserid);
82e0973c
MN
604 }
605
cef1d977
MN
606 // Populate the contact requests, even if we don't need them.
607 $data->contactrequests = [];
608
2a2b86f1
JD
609 $members[$data->id] = $data;
610 }
054834b0
MN
611
612 // Check if we want to include contact requests as well.
613 if (!empty($members) && $includecontactrequests) {
614 list($useridsql, $usersparams) = $DB->get_in_or_equal($userids);
615
5c675c50
MN
616 $wheresql = "(userid $useridsql AND requesteduserid = ?) OR (userid = ? AND requesteduserid $useridsql)";
617 $params = array_merge($usersparams, [$referenceuserid, $referenceuserid], $usersparams);
618 if ($contactrequests = $DB->get_records_select('message_contact_requests', $wheresql, $params,
619 'timecreated ASC, id ASC')) {
054834b0
MN
620 foreach ($contactrequests as $contactrequest) {
621 if (isset($members[$contactrequest->userid])) {
622 $members[$contactrequest->userid]->contactrequests[] = $contactrequest;
623 }
624 if (isset($members[$contactrequest->requesteduserid])) {
625 $members[$contactrequest->requesteduserid]->contactrequests[] = $contactrequest;
626 }
627 }
628 }
629 }
630
fc266450
MN
631 // Remove any userids not in $members. This can happen in the case of a user who has been deleted
632 // from the Moodle database table (which can happen in earlier versions of Moodle).
633 $userids = array_filter($userids, function($userid) use ($members) {
634 return isset($members[$userid]);
635 });
636
9e6734a7
JD
637 // Return member information in the same order as the userids originally provided.
638 $members = array_replace(array_flip($userids), $members);
639
2a2b86f1
JD
640 return $members;
641 }
eb35e0b1
JD
642
643 /**
644 * Backwards compatibility formatter, transforming the new output of get_conversations() into the old format.
645 *
7ece4ca9
MN
646 * TODO: This function should be removed once the related web services go through final deprecation.
647 * The related web services are data_for_messagearea_conversations.
648 * Followup: MDL-63261
649 *
eb35e0b1
JD
650 * @param array $conversations the array of conversations, which must come from get_conversations().
651 * @return array the array of conversations, formatted in the legacy style.
652 */
653 public static function get_conversations_legacy_formatter(array $conversations) : array {
654 // Transform new data format back into the old format, just for BC during the deprecation life cycle.
655 $tmp = [];
656 foreach ($conversations as $id => $conv) {
d7602911
JD
657 // Only individual conversations were supported in legacy messaging.
658 if ($conv->type != \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) {
659 continue;
660 }
eb35e0b1
JD
661 $data = new \stdClass();
662 // The logic for the 'other user' is as follows:
663 // If a conversation is of type 'individual', the other user is always the member who is not the current user.
664 // If the conversation is of type 'group', the other user is always the sender of the most recent message.
665 // The get_conversations method already follows this logic, so we just need the first member.
666 $otheruser = reset($conv->members);
667 $data->userid = $otheruser->id;
668 $data->useridfrom = $conv->messages[0]->useridfrom ?? null;
669 $data->fullname = $conv->members[$otheruser->id]->fullname;
670 $data->profileimageurl = $conv->members[$otheruser->id]->profileimageurl;
671 $data->profileimageurlsmall = $conv->members[$otheruser->id]->profileimageurlsmall;
672 $data->ismessaging = isset($conv->messages[0]->text) ? true : false;
74fc14e5 673 $data->lastmessage = $conv->messages[0]->text ? clean_param($conv->messages[0]->text, PARAM_NOTAGS) : null;
0802c38a 674 $data->lastmessagedate = $conv->messages[0]->timecreated ?? null;
eb35e0b1
JD
675 $data->messageid = $conv->messages[0]->id ?? null;
676 $data->isonline = $conv->members[$otheruser->id]->isonline ?? null;
677 $data->isblocked = $conv->members[$otheruser->id]->isblocked ?? null;
678 $data->isread = $conv->isread;
679 $data->unreadcount = $conv->unreadcount;
680 $tmp[$data->userid] = $data;
681 }
682 return $tmp;
683 }
fd998fc6
MN
684
685 /**
686 * Renders the messaging widget.
687 *
688 * @param bool $isdrawer Are we are rendering the drawer or is this on a full page?
689 * @param int|null $sendtouser The ID of the user we want to send a message to
690 * @param int|null $conversationid The ID of the conversation we want to load
6cb33ce7 691 * @param string|null $view The first view to load in the message widget
fd998fc6
MN
692 * @return string The HTML.
693 */
6cb33ce7
RW
694 public static function render_messaging_widget(
695 bool $isdrawer,
696 int $sendtouser = null,
697 int $conversationid = null,
698 string $view = null
699 ) {
fd998fc6
MN
700 global $USER, $CFG, $PAGE;
701
702 // Early bail out conditions.
703 if (empty($CFG->messaging) || !isloggedin() || isguestuser() || user_not_fully_set_up($USER) ||
704 get_user_preferences('auth_forcepasswordchange') ||
705 (!$USER->policyagreed && !is_siteadmin() &&
706 ($manager = new \core_privacy\local\sitepolicy\manager()) && $manager->is_defined())) {
707 return '';
708 }
709
710 $renderer = $PAGE->get_renderer('core');
711 $requestcount = \core_message\api::get_received_contact_requests_count($USER->id);
712 $contactscount = \core_message\api::count_contacts($USER->id);
713
714 $choices = [];
715 $choices[] = [
716 'value' => \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS,
717 'text' => get_string('contactableprivacy_onlycontacts', 'message')
718 ];
719 $choices[] = [
720 'value' => \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER,
721 'text' => get_string('contactableprivacy_coursemember', 'message')
722 ];
723 if (!empty($CFG->messagingallusers)) {
724 // Add the MESSAGE_PRIVACY_SITE option when site-wide messaging between users is enabled.
725 $choices[] = [
726 'value' => \core_message\api::MESSAGE_PRIVACY_SITE,
727 'text' => get_string('contactableprivacy_site', 'message')
728 ];
729 }
730
731 // Enter to send.
732 $entertosend = get_user_preferences('message_entertosend', $CFG->messagingdefaultpressenter, $USER);
733
6408ea75
MN
734 $notification = '';
735 if (!get_user_preferences('core_message_migrate_data', false)) {
736 $notification = get_string('messagingdatahasnotbeenmigrated', 'message');
737 }
738
fd998fc6
MN
739 if ($isdrawer) {
740 $template = 'core_message/message_drawer';
741 $messageurl = new \moodle_url('/message/index.php');
742 } else {
743 $template = 'core_message/message_index';
744 $messageurl = null;
745 }
746
747 $templatecontext = [
748 'contactrequestcount' => $requestcount,
749 'loggedinuser' => [
750 'id' => $USER->id,
751 'midnight' => usergetmidnight(time())
752 ],
8be24909
RW
753 // The starting timeout value for message polling.
754 'messagepollmin' => $CFG->messagingminpoll ?? MESSAGE_DEFAULT_MIN_POLL_IN_SECONDS,
755 // The maximum value that message polling timeout can reach.
756 'messagepollmax' => $CFG->messagingmaxpoll ?? MESSAGE_DEFAULT_MAX_POLL_IN_SECONDS,
757 // The timeout to reset back to after the max polling time has been reached.
758 'messagepollaftermax' => $CFG->messagingtimeoutpoll ?? MESSAGE_DEFAULT_TIMEOUT_POLL_IN_SECONDS,
fd998fc6
MN
759 'contacts' => [
760 'sectioncontacts' => [
761 'placeholders' => array_fill(0, $contactscount > 50 ? 50 : $contactscount, true)
762 ],
763 'sectionrequests' => [
764 'placeholders' => array_fill(0, $requestcount > 50 ? 50 : $requestcount, true)
765 ],
766 ],
767 'settings' => [
768 'privacy' => $choices,
769 'entertosend' => $entertosend
770 ],
771 'overview' => [
6408ea75
MN
772 'messageurl' => $messageurl,
773 'notification' => $notification
fd998fc6 774 ],
4060fcc6
RW
775 'isdrawer' => $isdrawer,
776 'showemojipicker' => !empty($CFG->allowemojipicker)
fd998fc6
MN
777 ];
778
6cb33ce7
RW
779 if ($sendtouser || $conversationid) {
780 $route = [
781 'path' => 'view-conversation',
782 'params' => $conversationid ? [$conversationid] : [null, 'create', $sendtouser]
783 ];
784 } else if ($view === 'contactrequests') {
785 $route = [
786 'path' => 'view-contacts',
787 'params' => ['requests']
788 ];
789 } else {
790 $route = null;
d5d4a5aa
MN
791 }
792
6cb33ce7 793 $templatecontext['route'] = json_encode($route);
fd998fc6
MN
794
795 return $renderer->render_from_template($template, $templatecontext);
796 }
3edac090
JD
797
798 /**
799 * Returns user details for a user, if they are visible to the current user in the message search.
800 *
801 * This method checks the visibility of a user specifically for the purpose of inclusion in the message search results.
802 * Visibility depends on the site-wide messaging setting 'messagingallusers':
803 * If enabled, visibility depends only on the core notion of visibility; a visible site or course profile.
804 * If disabled, visibility requires that the user be sharing a course with the searching user, and have a visible profile there.
805 * The current user is always returned.
806 *
807 * @param \stdClass $user
808 * @return array the array of userdetails, if visible, or an empty array otherwise.
809 */
810 public static function search_get_user_details(\stdClass $user) : array {
811 global $CFG, $USER;
812 require_once($CFG->dirroot . '/user/lib.php');
813
814 if ($CFG->messagingallusers || $user->id == $USER->id) {
815 return \user_get_user_details_courses($user) ?? []; // This checks visibility of site and course profiles.
816 } else {
817 // Messaging specific: user must share a course with the searching user AND have a visible profile there.
818 $sharedcourses = enrol_get_shared_courses($USER, $user);
819 foreach ($sharedcourses as $course) {
820 if (user_can_view_profile($user, $course)) {
821 $userdetails = user_get_user_details($user, $course);
822 if (!is_null($userdetails)) {
823 return $userdetails;
824 }
825 }
826 }
827 }
828 return [];
829 }
eda6bc19 830}