Commit | Line | Data |
---|---|---|
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 class used to return information to display 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 | ||
25 | namespace core_message; | |
26 | ||
b8ff2c44 JD |
27 | use core_favourites\local\entity\favourite; |
28 | ||
879e2bef MN |
29 | defined('MOODLE_INTERNAL') || die(); |
30 | ||
7b55aaa1 MN |
31 | require_once($CFG->dirroot . '/lib/messagelib.php'); |
32 | ||
879e2bef MN |
33 | /** |
34 | * Class used to return information to display for the message area. | |
35 | * | |
36 | * @copyright 2016 Mark Nelson <markn@moodle.com> | |
37 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
38 | */ | |
39 | class api { | |
40 | ||
883ce421 MN |
41 | /** |
42 | * The action for reading a message. | |
43 | */ | |
44 | const MESSAGE_ACTION_READ = 1; | |
45 | ||
46 | /** | |
47 | * The action for deleting a message. | |
48 | */ | |
49 | const MESSAGE_ACTION_DELETED = 2; | |
50 | ||
f7dfa9ba SA |
51 | /** |
52 | * The privacy setting for being messaged by anyone within courses user is member of. | |
53 | */ | |
54 | const MESSAGE_PRIVACY_COURSEMEMBER = 0; | |
55 | ||
56 | /** | |
57 | * The privacy setting for being messaged only by contacts. | |
58 | */ | |
59 | const MESSAGE_PRIVACY_ONLYCONTACTS = 1; | |
60 | ||
61 | /** | |
62 | * The privacy setting for being messaged by anyone on the site. | |
63 | */ | |
64 | const MESSAGE_PRIVACY_SITE = 2; | |
65 | ||
f2ac0a3e MN |
66 | /** |
67 | * An individual conversation. | |
68 | */ | |
69 | const MESSAGE_CONVERSATION_TYPE_INDIVIDUAL = 1; | |
70 | ||
71 | /** | |
72 | * A group conversation. | |
73 | */ | |
74 | const MESSAGE_CONVERSATION_TYPE_GROUP = 2; | |
75 | ||
5571af16 MN |
76 | /** |
77 | * The state for an enabled conversation area. | |
78 | */ | |
76540bec | 79 | const MESSAGE_CONVERSATION_ENABLED = 1; |
5571af16 MN |
80 | |
81 | /** | |
82 | * The state for a disabled conversation area. | |
83 | */ | |
76540bec | 84 | const MESSAGE_CONVERSATION_DISABLED = 0; |
5571af16 | 85 | |
cd03b8d7 MN |
86 | /** |
87 | * Handles searching for messages in the message area. | |
88 | * | |
89 | * @param int $userid The user id doing the searching | |
90 | * @param string $search The string the user is searching | |
91 | * @param int $limitfrom | |
92 | * @param int $limitnum | |
de55cb1b | 93 | * @return array |
cd03b8d7 MN |
94 | */ |
95 | public static function search_messages($userid, $search, $limitfrom = 0, $limitnum = 0) { | |
96 | global $DB; | |
97 | ||
98 | // Get the user fields we want. | |
99 | $ufields = \user_picture::fields('u', array('lastaccess'), 'userfrom_id', 'userfrom_'); | |
100 | $ufields2 = \user_picture::fields('u2', array('lastaccess'), 'userto_id', 'userto_'); | |
101 | ||
883ce421 | 102 | $sql = "SELECT m.id, m.useridfrom, mcm.userid as useridto, m.subject, m.fullmessage, m.fullmessagehtml, m.fullmessageformat, |
d2708759 JD |
103 | m.smallmessage, m.conversationid, m.timecreated, 0 as isread, $ufields, mub.id as userfrom_blocked, |
104 | $ufields2, mub2.id as userto_blocked | |
883ce421 MN |
105 | FROM {messages} m |
106 | INNER JOIN {user} u | |
107 | ON u.id = m.useridfrom | |
108 | INNER JOIN {message_conversations} mc | |
109 | ON mc.id = m.conversationid | |
110 | INNER JOIN {message_conversation_members} mcm | |
111 | ON mcm.conversationid = m.conversationid | |
112 | INNER JOIN {user} u2 | |
113 | ON u2.id = mcm.userid | |
f219eac7 MN |
114 | LEFT JOIN {message_users_blocked} mub |
115 | ON (mub.blockeduserid = u.id AND mub.userid = ?) | |
116 | LEFT JOIN {message_users_blocked} mub2 | |
117 | ON (mub2.blockeduserid = u2.id AND mub2.userid = ?) | |
883ce421 MN |
118 | LEFT JOIN {message_user_actions} mua |
119 | ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?) | |
120 | WHERE (m.useridfrom = ? OR mcm.userid = ?) | |
121 | AND m.useridfrom != mcm.userid | |
cd03b8d7 MN |
122 | AND u.deleted = 0 |
123 | AND u2.deleted = 0 | |
883ce421 | 124 | AND mua.id is NULL |
cd03b8d7 MN |
125 | AND " . $DB->sql_like('smallmessage', '?', false) . " |
126 | ORDER BY timecreated DESC"; | |
883ce421 MN |
127 | |
128 | $params = array($userid, $userid, $userid, self::MESSAGE_ACTION_DELETED, $userid, $userid, '%' . $search . '%'); | |
cd03b8d7 MN |
129 | |
130 | // Convert the messages into searchable contacts with their last message being the message that was searched. | |
de55cb1b | 131 | $conversations = array(); |
cd03b8d7 MN |
132 | if ($messages = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum)) { |
133 | foreach ($messages as $message) { | |
134 | $prefix = 'userfrom_'; | |
135 | if ($userid == $message->useridfrom) { | |
136 | $prefix = 'userto_'; | |
137 | // If it from the user, then mark it as read, even if it wasn't by the receiver. | |
138 | $message->isread = true; | |
139 | } | |
dd0c1403 | 140 | $blockedcol = $prefix . 'blocked'; |
f219eac7 | 141 | $message->blocked = $message->$blockedcol ? 1 : 0; |
dd0c1403 | 142 | |
cd03b8d7 | 143 | $message->messageid = $message->id; |
de55cb1b | 144 | $conversations[] = helper::create_contact($message, $prefix); |
cd03b8d7 MN |
145 | } |
146 | } | |
147 | ||
de55cb1b | 148 | return $conversations; |
cd03b8d7 MN |
149 | } |
150 | ||
151 | /** | |
48e8bdba | 152 | * Handles searching for user in a particular course in the message area. |
cd03b8d7 | 153 | * |
4461288d | 154 | * TODO: This function should be removed once the new group messaging UI is in place and the old messaging UI is removed. |
548cac7d AA |
155 | * For now we are not removing/deprecating this function for backwards compatibility with messaging UI. |
156 | * But we are deprecating data_for_messagearea_search_users_in_course external function. | |
157 | * Followup: MDL-63915 | |
158 | * | |
cd03b8d7 MN |
159 | * @param int $userid The user id doing the searching |
160 | * @param int $courseid The id of the course we are searching in | |
161 | * @param string $search The string the user is searching | |
162 | * @param int $limitfrom | |
163 | * @param int $limitnum | |
de55cb1b | 164 | * @return array |
cd03b8d7 | 165 | */ |
48e8bdba | 166 | public static function search_users_in_course($userid, $courseid, $search, $limitfrom = 0, $limitnum = 0) { |
cd03b8d7 MN |
167 | global $DB; |
168 | ||
169 | // Get all the users in the course. | |
170 | list($esql, $params) = get_enrolled_sql(\context_course::instance($courseid), '', 0, true); | |
f219eac7 | 171 | $sql = "SELECT u.*, mub.id as isblocked |
cd03b8d7 | 172 | FROM {user} u |
dd0c1403 MN |
173 | JOIN ($esql) je |
174 | ON je.id = u.id | |
f219eac7 MN |
175 | LEFT JOIN {message_users_blocked} mub |
176 | ON (mub.blockeduserid = u.id AND mub.userid = :userid) | |
cd03b8d7 MN |
177 | WHERE u.deleted = 0"; |
178 | // Add more conditions. | |
179 | $fullname = $DB->sql_fullname(); | |
dd0c1403 | 180 | $sql .= " AND u.id != :userid2 |
cd03b8d7 MN |
181 | AND " . $DB->sql_like($fullname, ':search', false) . " |
182 | ORDER BY " . $DB->sql_fullname(); | |
dd0c1403 | 183 | $params = array_merge(array('userid' => $userid, 'userid2' => $userid, 'search' => '%' . $search . '%'), $params); |
cd03b8d7 | 184 | |
cd03b8d7 MN |
185 | // Convert all the user records into contacts. |
186 | $contacts = array(); | |
187 | if ($users = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum)) { | |
188 | foreach ($users as $user) { | |
f219eac7 | 189 | $user->blocked = $user->isblocked ? 1 : 0; |
de55cb1b | 190 | $contacts[] = helper::create_contact($user); |
cd03b8d7 MN |
191 | } |
192 | } | |
193 | ||
de55cb1b | 194 | return $contacts; |
cd03b8d7 MN |
195 | } |
196 | ||
197 | /** | |
48e8bdba | 198 | * Handles searching for user in the message area. |
cd03b8d7 | 199 | * |
4461288d | 200 | * TODO: This function should be removed once the new group messaging UI is in place and the old messaging UI is removed. |
548cac7d AA |
201 | * For now we are not removing/deprecating this function for backwards compatibility with messaging UI. |
202 | * But we are deprecating data_for_messagearea_search_users external function. | |
203 | * Followup: MDL-63915 | |
204 | * | |
cd03b8d7 MN |
205 | * @param int $userid The user id doing the searching |
206 | * @param string $search The string the user is searching | |
207 | * @param int $limitnum | |
de55cb1b | 208 | * @return array |
cd03b8d7 | 209 | */ |
48e8bdba | 210 | public static function search_users($userid, $search, $limitnum = 0) { |
cd03b8d7 MN |
211 | global $CFG, $DB; |
212 | ||
cd03b8d7 MN |
213 | // Used to search for contacts. |
214 | $fullname = $DB->sql_fullname(); | |
215 | $ufields = \user_picture::fields('u', array('lastaccess')); | |
216 | ||
217 | // Users not to include. | |
218 | $excludeusers = array($userid, $CFG->siteguest); | |
219 | list($exclude, $excludeparams) = $DB->get_in_or_equal($excludeusers, SQL_PARAMS_NAMED, 'param', false); | |
220 | ||
221 | // Ok, let's search for contacts first. | |
222 | $contacts = array(); | |
f219eac7 | 223 | $sql = "SELECT $ufields, mub.id as isuserblocked |
9acb8b35 JD |
224 | FROM {user} u |
225 | JOIN {message_contacts} mc | |
226 | ON u.id = mc.contactid | |
227 | LEFT JOIN {message_users_blocked} mub | |
228 | ON (mub.userid = :userid2 AND mub.blockeduserid = u.id) | |
229 | WHERE mc.userid = :userid | |
230 | AND u.deleted = 0 | |
231 | AND u.confirmed = 1 | |
232 | AND " . $DB->sql_like($fullname, ':search', false) . " | |
233 | AND u.id $exclude | |
234 | ORDER BY " . $DB->sql_fullname(); | |
235 | if ($users = $DB->get_records_sql($sql, array('userid' => $userid, 'userid2' => $userid, | |
236 | 'search' => '%' . $search . '%') + $excludeparams, 0, $limitnum)) { | |
cd03b8d7 | 237 | foreach ($users as $user) { |
f219eac7 | 238 | $user->blocked = $user->isuserblocked ? 1 : 0; |
de55cb1b | 239 | $contacts[] = helper::create_contact($user); |
cd03b8d7 MN |
240 | } |
241 | } | |
242 | ||
243 | // Now, let's get the courses. | |
f3249577 AG |
244 | // Make sure to limit searches to enrolled courses. |
245 | $enrolledcourses = enrol_get_my_courses(array('id', 'cacherev')); | |
cd03b8d7 | 246 | $courses = array(); |
93b47710 MN |
247 | // Really we want the user to be able to view the participants if they have the capability |
248 | // 'moodle/course:viewparticipants' or 'moodle/course:enrolreview', but since the search_courses function | |
249 | // only takes required parameters we can't. However, the chance of a user having 'moodle/course:enrolreview' but | |
250 | // *not* 'moodle/course:viewparticipants' are pretty much zero, so it is not worth addressing. | |
442f12f8 | 251 | if ($arrcourses = \core_course_category::search_courses(array('search' => $search), array('limit' => $limitnum), |
f3249577 | 252 | array('moodle/course:viewparticipants'))) { |
cd03b8d7 | 253 | foreach ($arrcourses as $course) { |
f3249577 AG |
254 | if (isset($enrolledcourses[$course->id])) { |
255 | $data = new \stdClass(); | |
256 | $data->id = $course->id; | |
257 | $data->shortname = $course->shortname; | |
258 | $data->fullname = $course->fullname; | |
259 | $courses[] = $data; | |
260 | } | |
cd03b8d7 MN |
261 | } |
262 | } | |
263 | ||
9acb8b35 JD |
264 | // Let's get those non-contacts. Toast them gears boi. |
265 | // Note - you can only block contacts, so these users will not be blocked, so no need to get that | |
266 | // extra detail from the database. | |
cd03b8d7 | 267 | $noncontacts = array(); |
9acb8b35 JD |
268 | $sql = "SELECT $ufields |
269 | FROM {user} u | |
270 | WHERE u.deleted = 0 | |
271 | AND u.confirmed = 1 | |
272 | AND " . $DB->sql_like($fullname, ':search', false) . " | |
273 | AND u.id $exclude | |
274 | AND u.id NOT IN (SELECT contactid | |
275 | FROM {message_contacts} | |
276 | WHERE userid = :userid) | |
277 | ORDER BY " . $DB->sql_fullname(); | |
278 | if ($users = $DB->get_records_sql($sql, array('userid' => $userid, 'search' => '%' . $search . '%') + $excludeparams, | |
279 | 0, $limitnum)) { | |
548cac7d AA |
280 | foreach ($users as $user) { |
281 | $noncontacts[] = helper::create_contact($user); | |
282 | } | |
283 | } | |
284 | ||
285 | return array($contacts, $courses, $noncontacts); | |
286 | } | |
287 | ||
288 | /** | |
289 | * Handles searching for user. | |
290 | * | |
291 | * @param int $userid The user id doing the searching | |
292 | * @param string $search The string the user is searching | |
293 | * @param int $limitfrom | |
294 | * @param int $limitnum | |
295 | * @return array | |
296 | */ | |
41485be2 | 297 | public static function message_search_users(int $userid, string $search, int $limitfrom = 0, int $limitnum = 20) : array { |
548cac7d AA |
298 | global $CFG, $DB; |
299 | ||
41485be2 JD |
300 | // Check if messaging is enabled. |
301 | if (empty($CFG->messaging)) { | |
302 | throw new \moodle_exception('disabled', 'message'); | |
303 | } | |
304 | ||
548cac7d AA |
305 | // Used to search for contacts. |
306 | $fullname = $DB->sql_fullname(); | |
307 | ||
308 | // Users not to include. | |
309 | $excludeusers = array($userid, $CFG->siteguest); | |
310 | list($exclude, $excludeparams) = $DB->get_in_or_equal($excludeusers, SQL_PARAMS_NAMED, 'param', false); | |
311 | ||
312 | $params = array('search' => '%' . $search . '%', 'userid1' => $userid, 'userid2' => $userid); | |
313 | ||
314 | // Ok, let's search for contacts first. | |
315 | $sql = "SELECT u.id | |
cd03b8d7 | 316 | FROM {user} u |
548cac7d AA |
317 | JOIN {message_contacts} mc |
318 | ON (u.id = mc.contactid AND mc.userid = :userid1) OR (u.id = mc.userid AND mc.contactid = :userid2) | |
cd03b8d7 MN |
319 | WHERE u.deleted = 0 |
320 | AND u.confirmed = 1 | |
321 | AND " . $DB->sql_like($fullname, ':search', false) . " | |
322 | AND u.id $exclude | |
cd03b8d7 | 323 | ORDER BY " . $DB->sql_fullname(); |
548cac7d AA |
324 | $foundusers = $DB->get_records_sql_menu($sql, $params + $excludeparams, $limitfrom, $limitnum); |
325 | ||
41485be2 | 326 | $orderedcontacts = array(); |
548cac7d AA |
327 | if (!empty($foundusers)) { |
328 | $contacts = helper::get_member_info($userid, array_keys($foundusers)); | |
329 | // The get_member_info returns an associative array, so is not ordered in the same way. | |
330 | // We need to reorder it again based on query's result. | |
331 | foreach ($foundusers as $key => $value) { | |
332 | $contact = $contacts[$key]; | |
333 | $contact->conversations = self::get_conversations_between_users($userid, $key, 0, 1000); | |
41485be2 | 334 | $orderedcontacts[] = $contact; |
cd03b8d7 MN |
335 | } |
336 | } | |
337 | ||
548cac7d | 338 | // Let's get those non-contacts. |
41485be2 | 339 | // If site wide messaging is enabled, we just fetch any matched users which are non-contacts. |
548cac7d | 340 | if ($CFG->messagingallusers) { |
548cac7d | 341 | $sql = "SELECT u.id |
41485be2 JD |
342 | FROM {user} u |
343 | WHERE u.deleted = 0 | |
344 | AND u.confirmed = 1 | |
345 | AND " . $DB->sql_like($fullname, ':search', false) . " | |
346 | AND u.id $exclude | |
347 | AND NOT EXISTS (SELECT mc.id | |
348 | FROM {message_contacts} mc | |
349 | WHERE (mc.userid = u.id AND mc.contactid = :userid1) | |
350 | OR (mc.userid = :userid2 AND mc.contactid = u.id)) | |
351 | ORDER BY " . $DB->sql_fullname(); | |
352 | ||
353 | $foundusers = $DB->get_records_sql($sql, $params + $excludeparams, $limitfrom, $limitnum); | |
548cac7d | 354 | } else { |
41485be2 JD |
355 | require_once($CFG->dirroot . '/user/lib.php'); |
356 | // If site-wide messaging is disabled, then we should only be able to search for users who we are allowed to see. | |
357 | // Because we can't achieve all the required visibility checks in SQL, we'll iterate through the non-contact records | |
358 | // and stop once we have enough matching the 'visible' criteria. | |
359 | // TODO: MDL-63983 - Improve the performance of non-contact searches when site-wide messaging is disabled (default). | |
360 | ||
361 | // Use a local generator to achieve this iteration. | |
362 | $getnoncontactusers = function ($limitfrom = 0, $limitnum = 0) use($fullname, $exclude, $params, $excludeparams) { | |
363 | global $DB; | |
364 | $sql = "SELECT u.* | |
548cac7d | 365 | FROM {user} u |
548cac7d AA |
366 | WHERE u.deleted = 0 |
367 | AND u.confirmed = 1 | |
368 | AND " . $DB->sql_like($fullname, ':search', false) . " | |
369 | AND u.id $exclude | |
370 | AND NOT EXISTS (SELECT mc.id | |
371 | FROM {message_contacts} mc | |
372 | WHERE (mc.userid = u.id AND mc.contactid = :userid1) | |
373 | OR (mc.userid = :userid2 AND mc.contactid = u.id)) | |
374 | ORDER BY " . $DB->sql_fullname(); | |
41485be2 JD |
375 | while ($records = $DB->get_records_sql($sql, $params + $excludeparams, $limitfrom, $limitnum)) { |
376 | yield $records; | |
377 | $limitfrom += $limitnum; | |
378 | } | |
379 | }; | |
380 | ||
381 | // Fetch in batches of $limitnum * 2 to improve the chances of matching a user without going back to the DB. | |
382 | // The generator cannot function without a sensible limiter, so set one if this is not set. | |
383 | $batchlimit = ($limitnum == 0) ? 20 : $limitnum; | |
384 | ||
385 | // We need to make the offset param work with the generator. | |
386 | // Basically, if we want to get say 10 records starting at the 40th record, we need to see 50 records and return only | |
387 | // those after the 40th record. We can never pass the method's offset param to the generator as we need to manage the | |
388 | // position within those valid records ourselves. | |
389 | // See MDL-63983 dealing with performance improvements to this area of code. | |
390 | $noofvalidseenrecords = 0; | |
391 | $returnedusers = []; | |
392 | foreach ($getnoncontactusers(0, $batchlimit) as $users) { | |
393 | foreach ($users as $id => $user) { | |
394 | $userdetails = \user_get_user_details_courses($user); | |
395 | ||
396 | // Return the user only if the searched field is returned. | |
397 | // Otherwise it means that the $USER was not allowed to search the returned user. | |
398 | if (!empty($userdetails) and !empty($userdetails['fullname'])) { | |
399 | // We know we've matched, but only save the record if it's within the offset area we need. | |
400 | if ($limitfrom == 0) { | |
401 | // No offset specified, so just save. | |
402 | $returnedusers[$id] = $user; | |
403 | } else { | |
404 | // There is an offset in play. | |
405 | // If we've passed enough records already (> offset value), then we can save this one. | |
406 | if ($noofvalidseenrecords >= $limitfrom) { | |
407 | $returnedusers[$id] = $user; | |
408 | } | |
409 | } | |
410 | if (count($returnedusers) == $limitnum) { | |
411 | break 2; | |
412 | } | |
413 | $noofvalidseenrecords++; | |
414 | } | |
415 | } | |
416 | } | |
417 | $foundusers = $returnedusers; | |
548cac7d | 418 | } |
548cac7d | 419 | |
41485be2 | 420 | $orderednoncontacts = array(); |
548cac7d AA |
421 | if (!empty($foundusers)) { |
422 | $noncontacts = helper::get_member_info($userid, array_keys($foundusers)); | |
423 | // The get_member_info returns an associative array, so is not ordered in the same way. | |
424 | // We need to reorder it again based on query's result. | |
425 | foreach ($foundusers as $key => $value) { | |
426 | $contact = $noncontacts[$key]; | |
427 | $contact->conversations = self::get_conversations_between_users($userid, $key, 0, 1000); | |
41485be2 | 428 | $orderednoncontacts[] = $contact; |
548cac7d AA |
429 | } |
430 | } | |
431 | ||
41485be2 | 432 | return array($orderedcontacts, $orderednoncontacts); |
cd03b8d7 MN |
433 | } |
434 | ||
eb5865da | 435 | /** |
003cdcce | 436 | * Gets extra fields, like image url and subname for any conversations linked to components. |
eb5865da JD |
437 | * |
438 | * The subname is like a subtitle for the conversation, to compliment it's name. | |
003cdcce | 439 | * The imageurl is the location of the image for the conversation, as might be seen on a listing of conversations for a user. |
eb5865da JD |
440 | * |
441 | * @param array $conversations a list of conversations records. | |
442 | * @return array the array of subnames, index by conversation id. | |
003cdcce JD |
443 | * @throws \coding_exception |
444 | * @throws \dml_exception | |
eb5865da | 445 | */ |
003cdcce | 446 | protected static function get_linked_conversation_extra_fields(array $conversations) : array { |
eb5865da JD |
447 | global $DB; |
448 | ||
449 | $linkedconversations = []; | |
450 | foreach ($conversations as $conversation) { | |
451 | if (!is_null($conversation->component) && !is_null($conversation->itemtype)) { | |
452 | $linkedconversations[$conversation->component][$conversation->itemtype][$conversation->id] | |
453 | = $conversation->itemid; | |
454 | } | |
455 | } | |
456 | if (empty($linkedconversations)) { | |
457 | return []; | |
458 | } | |
459 | ||
460 | // TODO: MDL-63814: Working out the subname for linked conversations should be done in a generic way. | |
461 | // Get the itemid, but only for course group linked conversation for now. | |
003cdcce | 462 | $extrafields = []; |
eb5865da JD |
463 | if (!empty($linkeditems = $linkedconversations['core_group']['groups'])) { // Format: [conversationid => itemid]. |
464 | // Get the name of the course to which the group belongs. | |
465 | list ($groupidsql, $groupidparams) = $DB->get_in_or_equal(array_values($linkeditems), SQL_PARAMS_NAMED, 'groupid'); | |
003cdcce | 466 | $sql = "SELECT g.*, c.shortname as courseshortname |
eb5865da JD |
467 | FROM {groups} g |
468 | JOIN {course} c | |
469 | ON g.courseid = c.id | |
470 | WHERE g.id $groupidsql"; | |
471 | $courseinfo = $DB->get_records_sql($sql, $groupidparams); | |
472 | foreach ($linkeditems as $convid => $groupid) { | |
473 | if (array_key_exists($groupid, $courseinfo)) { | |
003cdcce JD |
474 | $group = $courseinfo[$groupid]; |
475 | // Subname. | |
476 | $extrafields[$convid]['subname'] = format_string($courseinfo[$groupid]->courseshortname); | |
477 | ||
478 | // Imageurl. | |
9cef5491 JD |
479 | $extrafields[$convid]['imageurl'] = ''; |
480 | if ($url = get_group_picture_url($group, $group->courseid, true)) { | |
481 | $extrafields[$convid]['imageurl'] = $url->out(false); | |
482 | } | |
eb5865da JD |
483 | } |
484 | } | |
485 | } | |
003cdcce | 486 | return $extrafields; |
eb5865da JD |
487 | } |
488 | ||
489 | ||
879e2bef MN |
490 | /** |
491 | * Returns the contacts and their conversation to display in the contacts area. | |
492 | * | |
349f4261 RW |
493 | * ** WARNING ** |
494 | * It is HIGHLY recommended to use a sensible limit when calling this function. Trying | |
495 | * to retrieve too much information in a single call will cause performance problems. | |
496 | * ** WARNING ** | |
497 | * | |
498 | * This function has specifically been altered to break each of the data sets it | |
499 | * requires into separate database calls. This is to avoid the performance problems | |
500 | * observed when attempting to join large data sets (e.g. the message tables and | |
501 | * the user table). | |
502 | * | |
503 | * While it is possible to gather the data in a single query, and it may even be | |
504 | * more efficient with a correctly tuned database, we have opted to trade off some of | |
505 | * the benefits of a single query in order to ensure this function will work on | |
506 | * most databases with default tunings and with large data sets. | |
507 | * | |
879e2bef | 508 | * @param int $userid The user id |
879e2bef MN |
509 | * @param int $limitfrom |
510 | * @param int $limitnum | |
eb5865da JD |
511 | * @param int $type the type of the conversation, if you wish to filter to a certain type (see api constants). |
512 | * @param bool $favourites whether to include NO favourites (false) or ONLY favourites (true), or null to ignore this setting. | |
513 | * @return array the array of conversations | |
514 | * @throws \moodle_exception | |
879e2bef | 515 | */ |
b8ff2c44 | 516 | public static function get_conversations($userid, $limitfrom = 0, $limitnum = 20, int $type = null, |
eb5865da | 517 | bool $favourites = null) { |
349f4261 RW |
518 | global $DB; |
519 | ||
eb5865da JD |
520 | if (!is_null($type) && !in_array($type, [self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, |
521 | self::MESSAGE_CONVERSATION_TYPE_GROUP])) { | |
522 | throw new \moodle_exception("Invalid value ($type) for type param, please see api constants."); | |
523 | } | |
524 | ||
525 | // We need to know which conversations are favourites, so we can either: | |
526 | // 1) Include the 'isfavourite' attribute on conversations (when $favourite = null and we're including all conversations) | |
527 | // 2) Restrict the results to ONLY those conversations which are favourites (when $favourite = true) | |
528 | // 3) Restrict the results to ONLY those conversations which are NOT favourites (when $favourite = false). | |
529 | $service = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($userid)); | |
530 | $favouriteconversations = $service->find_favourites_by_type('core_message', 'message_conversations'); | |
531 | $favouriteconversationids = array_column($favouriteconversations, 'itemid'); | |
532 | if ($favourites && empty($favouriteconversationids)) { | |
533 | return []; // If we are aiming to return ONLY favourites, and we have none, there's nothing more to do. | |
534 | } | |
535 | ||
536 | // CONVERSATIONS AND MOST RECENT MESSAGE. | |
537 | // Include those conversations with messages first (ordered by most recent message, desc), then add any conversations which | |
538 | // don't have messages, such as newly created group conversations. | |
539 | // Because we're sorting by message 'timecreated', those conversations without messages could be at either the start or the | |
540 | // end of the results (behaviour for sorting of nulls differs between DB vendors), so we use the case to presort these. | |
541 | ||
542 | // If we need to return ONLY favourites, or NO favourites, generate the SQL snippet. | |
b8ff2c44 JD |
543 | $favouritesql = ""; |
544 | $favouriteparams = []; | |
e926b4d0 AN |
545 | if (null !== $favourites && !empty($favouriteconversationids)) { |
546 | list ($insql, $favouriteparams) = | |
547 | $DB->get_in_or_equal($favouriteconversationids, SQL_PARAMS_NAMED, 'favouriteids', $favourites); | |
548 | $favouritesql = " AND mc.id {$insql} "; | |
b8ff2c44 JD |
549 | } |
550 | ||
eb5865da JD |
551 | // If we need to restrict type, generate the SQL snippet. |
552 | $typesql = !is_null($type) ? " AND mc.type = :convtype " : ""; | |
553 | ||
554 | $sql = "SELECT m.id as messageid, mc.id as id, mc.name as conversationname, mc.type as conversationtype, m.useridfrom, | |
74fc14e5 JD |
555 | m.smallmessage, m.fullmessage, m.fullmessageformat, m.fullmessagehtml, m.timecreated, mc.component, |
556 | mc.itemtype, mc.itemid | |
eb5865da JD |
557 | FROM {message_conversations} mc |
558 | INNER JOIN {message_conversation_members} mcm | |
559 | ON (mcm.conversationid = mc.id AND mcm.userid = :userid3) | |
560 | LEFT JOIN ( | |
561 | SELECT m.conversationid, MAX(m.id) AS messageid | |
883ce421 MN |
562 | FROM {messages} m |
563 | INNER JOIN ( | |
564 | SELECT m.conversationid, MAX(m.timecreated) as maxtime | |
565 | FROM {messages} m | |
566 | INNER JOIN {message_conversation_members} mcm | |
567 | ON mcm.conversationid = m.conversationid | |
568 | LEFT JOIN {message_user_actions} mua | |
569 | ON (mua.messageid = m.id AND mua.userid = :userid AND mua.action = :action) | |
570 | WHERE mua.id is NULL | |
571 | AND mcm.userid = :userid2 | |
572 | GROUP BY m.conversationid | |
573 | ) maxmessage | |
574 | ON maxmessage.maxtime = m.timecreated AND maxmessage.conversationid = m.conversationid | |
575 | GROUP BY m.conversationid | |
576 | ) lastmessage | |
eb5865da JD |
577 | ON lastmessage.conversationid = mc.id |
578 | LEFT JOIN {messages} m | |
579 | ON m.id = lastmessage.messageid | |
18550c0a JD |
580 | WHERE mc.id IS NOT NULL |
581 | AND mc.enabled = 1 $typesql $favouritesql | |
eb5865da | 582 | ORDER BY (CASE WHEN m.timecreated IS NULL THEN 0 ELSE 1 END) DESC, m.timecreated DESC, id DESC"; |
b8ff2c44 JD |
583 | |
584 | $params = array_merge($favouriteparams, ['userid' => $userid, 'action' => self::MESSAGE_ACTION_DELETED, | |
eb5865da JD |
585 | 'userid2' => $userid, 'userid3' => $userid, 'convtype' => $type]); |
586 | $conversationset = $DB->get_recordset_sql($sql, $params, $limitfrom, $limitnum); | |
587 | ||
588 | $conversations = []; | |
eb5865da | 589 | $members = []; |
cef1d977 MN |
590 | $individualmembers = []; |
591 | $groupmembers = []; | |
eb5865da | 592 | foreach ($conversationset as $conversation) { |
cef1d977 | 593 | $conversations[$conversation->id] = $conversation; |
eb5865da | 594 | $members[$conversation->id] = []; |
349f4261 | 595 | } |
eb5865da | 596 | $conversationset->close(); |
349f4261 | 597 | |
eb5865da JD |
598 | // If there are no conversations found, then return early. |
599 | if (empty($conversations)) { | |
883ce421 MN |
600 | return []; |
601 | } | |
602 | ||
003cdcce JD |
603 | // COMPONENT-LINKED CONVERSATION FIELDS. |
604 | // Conversations linked to components may have extra information, such as: | |
605 | // - subname: Essentially a subtitle for the conversation. So you'd have "name: subname". | |
606 | // - imageurl: A URL to the image for the linked conversation. | |
eb5865da | 607 | // For now, this is ONLY course groups. |
003cdcce | 608 | $convextrafields = self::get_linked_conversation_extra_fields($conversations); |
eb5865da JD |
609 | |
610 | // MEMBERS. | |
611 | // Ideally, we want to get 1 member for each conversation, but this depends on the type and whether there is a recent | |
612 | // message or not. | |
613 | // | |
614 | // For 'individual' type conversations between 2 users, regardless of who sent the last message, | |
615 | // we want the details of the other member in the conversation (i.e. not the current user). | |
616 | // | |
617 | // For 'group' type conversations, we want the details of the member who sent the last message, if there is one. | |
618 | // This can be the current user or another group member, but for groups without messages, this will be empty. | |
619 | // | |
620 | // This also means that if type filtering is specified and only group conversations are returned, we don't need this extra | |
621 | // query to get the 'other' user as we already have that information. | |
622 | ||
623 | // Work out which members we have already, and which ones we might need to fetch. | |
624 | // If all the last messages were from another user, then we don't need to fetch anything further. | |
625 | foreach ($conversations as $conversation) { | |
626 | if ($conversation->conversationtype == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) { | |
627 | if (!is_null($conversation->useridfrom) && $conversation->useridfrom != $userid) { | |
628 | $members[$conversation->id][$conversation->useridfrom] = $conversation->useridfrom; | |
cef1d977 | 629 | $individualmembers[$conversation->useridfrom] = $conversation->useridfrom; |
eb5865da JD |
630 | } else { |
631 | $individualconversations[] = $conversation->id; | |
632 | } | |
633 | } else if ($conversation->conversationtype == self::MESSAGE_CONVERSATION_TYPE_GROUP) { | |
634 | // If we have a recent message, the sender is our member. | |
635 | if (!is_null($conversation->useridfrom)) { | |
636 | $members[$conversation->id][$conversation->useridfrom] = $conversation->useridfrom; | |
cef1d977 | 637 | $groupmembers[$conversation->useridfrom] = $conversation->useridfrom; |
eb5865da JD |
638 | } |
639 | } | |
640 | } | |
641 | // If we need to fetch any member information for any of the individual conversations. | |
642 | // This is the case if any of the individual conversations have a recent message sent by the current user. | |
643 | if (!empty($individualconversations)) { | |
644 | list ($icidinsql, $icidinparams) = $DB->get_in_or_equal($individualconversations, SQL_PARAMS_NAMED, 'convid'); | |
645 | $indmembersql = "SELECT mcm.id, mcm.conversationid, mcm.userid | |
646 | FROM {message_conversation_members} mcm | |
647 | WHERE mcm.conversationid $icidinsql | |
648 | AND mcm.userid != :userid | |
649 | ORDER BY mcm.id"; | |
650 | $indmemberparams = array_merge($icidinparams, ['userid' => $userid]); | |
651 | $conversationmembers = $DB->get_records_sql($indmembersql, $indmemberparams); | |
652 | ||
653 | foreach ($conversationmembers as $mid => $member) { | |
654 | $members[$member->conversationid][$member->userid] = $member->userid; | |
cef1d977 | 655 | $individualmembers[$member->userid] = $member->userid; |
eb5865da JD |
656 | } |
657 | } | |
eb5865da JD |
658 | |
659 | // We could fail early here if we're sure that: | |
660 | // a) we have no otherusers for all the conversations (users may have been deleted) | |
661 | // b) we're sure that all conversations are individual (1:1). | |
662 | ||
663 | // We need to pull out the list of users info corresponding to the memberids in the conversations.This | |
349f4261 RW |
664 | // needs to be done in a separate query to avoid doing a join on the messages tables and the user |
665 | // tables because on large sites these tables are massive which results in extremely slow | |
666 | // performance (typically due to join buffer exhaustion). | |
cef1d977 MN |
667 | if (!empty($individualmembers) || !empty($groupmembers)) { |
668 | // Now, we want to remove any duplicates from the group members array. For individual members we will | |
669 | // be doing a more extensive call as we want their contact requests as well as privacy information, | |
670 | // which is not necessary for group conversations. | |
671 | $diffgroupmembers = array_diff($groupmembers, $individualmembers); | |
672 | ||
673 | $individualmemberinfo = helper::get_member_info($userid, $individualmembers, true, true); | |
674 | $groupmemberinfo = helper::get_member_info($userid, $diffgroupmembers); | |
675 | ||
676 | // Don't use array_merge, as we lose array keys. | |
677 | $memberinfo = $individualmemberinfo + $groupmemberinfo; | |
eb5865da JD |
678 | |
679 | // Update the members array with the member information. | |
680 | $deletedmembers = []; | |
681 | foreach ($members as $convid => $memberarr) { | |
682 | foreach ($memberarr as $key => $memberid) { | |
683 | if (array_key_exists($memberid, $memberinfo)) { | |
684 | // If the user is deleted, remember that. | |
685 | if ($memberinfo[$memberid]->isdeleted) { | |
686 | $deletedmembers[$convid][] = $memberid; | |
687 | } | |
cef1d977 MN |
688 | |
689 | $members[$convid][$key] = clone $memberinfo[$memberid]; | |
690 | ||
691 | if ($conversations[$convid]->conversationtype == self::MESSAGE_CONVERSATION_TYPE_GROUP) { | |
692 | // Remove data we don't need for group. | |
693 | $members[$convid][$key]->requirescontact = null; | |
694 | $members[$convid][$key]->canmessage = null; | |
695 | $members[$convid][$key]->contactrequests = []; | |
696 | } | |
eb5865da JD |
697 | } |
698 | } | |
699 | } | |
883ce421 MN |
700 | } |
701 | ||
eb5865da JD |
702 | // MEMBER COUNT. |
703 | $cids = array_column($conversations, 'id'); | |
704 | list ($cidinsql, $cidinparams) = $DB->get_in_or_equal($cids, SQL_PARAMS_NAMED, 'convid'); | |
705 | $membercountsql = "SELECT conversationid, count(id) AS membercount | |
706 | FROM {message_conversation_members} mcm | |
707 | WHERE mcm.conversationid $cidinsql | |
708 | GROUP BY mcm.conversationid"; | |
709 | $membercounts = $DB->get_records_sql($membercountsql, $cidinparams); | |
710 | ||
711 | // UNREAD MESSAGE COUNT. | |
712 | // Finally, let's get the unread messages count for this user so that we can add it | |
883ce421 | 713 | // to the conversation. Remember we need to ignore the messages the user sent. |
eb5865da | 714 | $unreadcountssql = 'SELECT m.conversationid, count(m.id) as unreadcount |
883ce421 MN |
715 | FROM {messages} m |
716 | INNER JOIN {message_conversations} mc | |
717 | ON mc.id = m.conversationid | |
718 | INNER JOIN {message_conversation_members} mcm | |
719 | ON m.conversationid = mcm.conversationid | |
720 | LEFT JOIN {message_user_actions} mua | |
721 | ON (mua.messageid = m.id AND mua.userid = ? AND | |
722 | (mua.action = ? OR mua.action = ?)) | |
723 | WHERE mcm.userid = ? | |
724 | AND m.useridfrom != ? | |
725 | AND mua.id is NULL | |
eb5865da | 726 | GROUP BY m.conversationid'; |
883ce421 MN |
727 | $unreadcounts = $DB->get_records_sql($unreadcountssql, [$userid, self::MESSAGE_ACTION_READ, self::MESSAGE_ACTION_DELETED, |
728 | $userid, $userid]); | |
729 | ||
eb5865da JD |
730 | // Now, create the final return structure. |
731 | $arrconversations = []; | |
732 | foreach ($conversations as $conversation) { | |
aa748351 JD |
733 | // Do not include any individual conversation which: |
734 | // a) Contains a deleted member or | |
735 | // b) Does not contain a recent message for the user (this happens if the user has deleted all messages). | |
736 | // Group conversations with deleted users or no messages are always returned. | |
737 | if ($conversation->conversationtype == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL | |
738 | && (isset($deletedmembers[$conversation->id]) || empty($conversation->messageid))) { | |
883ce421 MN |
739 | continue; |
740 | } | |
741 | ||
eb5865da JD |
742 | $conv = new \stdClass(); |
743 | $conv->id = $conversation->id; | |
744 | $conv->name = $conversation->conversationname; | |
003cdcce JD |
745 | $conv->subname = $convextrafields[$conv->id]['subname'] ?? null; |
746 | $conv->imageurl = $convextrafields[$conv->id]['imageurl'] ?? null; | |
eb5865da JD |
747 | $conv->type = $conversation->conversationtype; |
748 | $conv->membercount = $membercounts[$conv->id]->membercount; | |
749 | $conv->isfavourite = in_array($conv->id, $favouriteconversationids); | |
750 | $conv->isread = isset($unreadcounts[$conv->id]) ? false : true; | |
751 | $conv->unreadcount = isset($unreadcounts[$conv->id]) ? $unreadcounts[$conv->id]->unreadcount : null; | |
752 | $conv->members = $members[$conv->id]; | |
753 | ||
754 | // Add the most recent message information. | |
755 | $conv->messages = []; | |
756 | if ($conversation->smallmessage) { | |
757 | $msg = new \stdClass(); | |
758 | $msg->id = $conversation->messageid; | |
74fc14e5 | 759 | $msg->text = message_format_message_text($conversation); |
eb5865da JD |
760 | $msg->useridfrom = $conversation->useridfrom; |
761 | $msg->timecreated = $conversation->timecreated; | |
762 | $conv->messages[] = $msg; | |
349f4261 RW |
763 | } |
764 | ||
eb5865da | 765 | $arrconversations[] = $conv; |
879e2bef | 766 | } |
de55cb1b | 767 | return $arrconversations; |
879e2bef MN |
768 | } |
769 | ||
548cac7d AA |
770 | /** |
771 | * Returns all conversations between two users | |
772 | * | |
773 | * @param int $userid1 One of the user's id | |
774 | * @param int $userid2 The other user's id | |
775 | * @param int $limitfrom | |
776 | * @param int $limitnum | |
777 | * @return array | |
778 | * @throws \dml_exception | |
779 | */ | |
780 | public static function get_conversations_between_users(int $userid1, int $userid2, | |
781 | int $limitfrom = 0, int $limitnum = 20) : array { | |
782 | ||
783 | global $DB; | |
784 | ||
785 | if ($userid1 == $userid2) { | |
786 | return array(); | |
787 | } | |
788 | ||
789 | // Get all conversation where both user1 and user2 are members. | |
790 | // TODO: Add subname value. Waiting for definite table structure. | |
791 | $sql = "SELECT mc.id, mc.type, mc.name, mc.timecreated | |
792 | FROM {message_conversations} mc | |
793 | INNER JOIN {message_conversation_members} mcm1 | |
794 | ON mc.id = mcm1.conversationid | |
795 | INNER JOIN {message_conversation_members} mcm2 | |
796 | ON mc.id = mcm2.conversationid | |
797 | WHERE mcm1.userid = :userid1 | |
798 | AND mcm2.userid = :userid2 | |
799 | AND mc.enabled != 0 | |
800 | ORDER BY mc.timecreated DESC"; | |
801 | ||
802 | return $DB->get_records_sql($sql, array('userid1' => $userid1, 'userid2' => $userid2), $limitfrom, $limitnum); | |
803 | } | |
804 | ||
4e313026 RW |
805 | /** |
806 | * Return a conversation. | |
807 | * | |
808 | * @param int $userid The user id to get the conversation for | |
809 | * @param int $conversationid The id of the conversation to fetch | |
810 | * @param bool $includecontactrequests Should contact requests be included between members | |
811 | * @param bool $includeprivacyinfo Should privacy info be included between members | |
812 | * @param int $memberlimit Limit number of members to load | |
813 | * @param int $memberoffset Offset members by this amount | |
814 | * @param int $messagelimit Limit number of messages to load | |
815 | * @param int $messageoffset Offset the messages | |
816 | * @param bool $newestmessagesfirst Order messages by newest first | |
817 | * @return \stdClass | |
818 | */ | |
819 | public static function get_conversation( | |
820 | int $userid, | |
821 | int $conversationid, | |
822 | bool $includecontactrequests = false, | |
823 | bool $includeprivacyinfo = false, | |
824 | int $memberlimit = 0, | |
825 | int $memberoffset = 0, | |
826 | int $messagelimit = 0, | |
827 | int $messageoffset = 0, | |
828 | bool $newestmessagesfirst = true | |
829 | ) { | |
830 | global $USER, $DB; | |
831 | ||
832 | $systemcontext = \context_system::instance(); | |
833 | $canreadallmessages = has_capability('moodle/site:readallmessages', $systemcontext); | |
834 | if (($USER->id != $userid) && !$canreadallmessages) { | |
835 | throw new \moodle_exception('You do not have permission to perform this action.'); | |
836 | } | |
837 | ||
838 | $conversation = $DB->get_record('message_conversations', ['id' => $conversationid]); | |
839 | if (!$conversation) { | |
840 | return null; | |
841 | } | |
842 | ||
843 | $isconversationmember = $DB->record_exists( | |
844 | 'message_conversation_members', | |
845 | [ | |
846 | 'conversationid' => $conversationid, | |
847 | 'userid' => $userid | |
848 | ] | |
849 | ); | |
850 | ||
851 | if (!$isconversationmember && !$canreadallmessages) { | |
852 | throw new \moodle_exception('You do not have permission to view this conversation.'); | |
853 | } | |
854 | ||
855 | $members = self::get_conversation_members( | |
856 | $userid, | |
857 | $conversationid, | |
858 | $includecontactrequests, | |
663ccd58 | 859 | $includeprivacyinfo, |
4e313026 RW |
860 | $memberoffset, |
861 | $memberlimit | |
862 | ); | |
863 | // Strip out the requesting user to match what get_conversations does. | |
864 | $members = array_filter($members, function($member) use ($userid) { | |
865 | return $member->id != $userid; | |
866 | }); | |
867 | ||
868 | $messages = self::get_conversation_messages( | |
869 | $userid, | |
870 | $conversationid, | |
871 | $messageoffset, | |
872 | $messagelimit, | |
873 | $newestmessagesfirst ? 'timecreated DESC' : 'timecreated ASC' | |
874 | ); | |
875 | ||
876 | $service = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($userid)); | |
877 | $isfavourite = $service->favourite_exists('core_message', 'message_conversations', $conversationid, $systemcontext); | |
878 | ||
879 | $convextrafields = self::get_linked_conversation_extra_fields([$conversation]); | |
880 | $subname = isset($convextrafields[$conversationid]) ? $convextrafields[$conversationid]['subname'] : null; | |
881 | $imageurl = isset($convextrafields[$conversationid]) ? $convextrafields[$conversationid]['imageurl'] : null; | |
882 | ||
883 | $unreadcountssql = 'SELECT count(m.id) | |
884 | FROM {messages} m | |
885 | INNER JOIN {message_conversations} mc | |
886 | ON mc.id = m.conversationid | |
887 | LEFT JOIN {message_user_actions} mua | |
888 | ON (mua.messageid = m.id AND mua.userid = ? AND | |
889 | (mua.action = ? OR mua.action = ?)) | |
890 | WHERE m.conversationid = ? | |
891 | AND m.useridfrom != ? | |
892 | AND mua.id is NULL'; | |
893 | $unreadcount = $DB->count_records_sql( | |
894 | $unreadcountssql, | |
895 | [ | |
896 | $userid, | |
897 | self::MESSAGE_ACTION_READ, | |
898 | self::MESSAGE_ACTION_DELETED, | |
899 | $conversationid, | |
900 | $userid | |
901 | ] | |
902 | ); | |
903 | ||
904 | $membercount = $DB->count_records('message_conversation_members', ['conversationid' => $conversationid]); | |
905 | ||
906 | return (object) [ | |
907 | 'id' => $conversation->id, | |
908 | 'name' => $conversation->name, | |
909 | 'subname' => $subname, | |
910 | 'imageurl' => $imageurl, | |
911 | 'type' => $conversation->type, | |
912 | 'membercount' => $membercount, | |
913 | 'isfavourite' => $isfavourite, | |
914 | 'isread' => empty($unreadcount), | |
915 | 'unreadcount' => $unreadcount, | |
916 | 'members' => $members, | |
917 | 'messages' => $messages['messages'] | |
918 | ]; | |
919 | } | |
920 | ||
b8ff2c44 JD |
921 | /** |
922 | * Mark a conversation as a favourite for the given user. | |
923 | * | |
924 | * @param int $conversationid the id of the conversation to mark as a favourite. | |
925 | * @param int $userid the id of the user to whom the favourite belongs. | |
926 | * @return favourite the favourite object. | |
927 | * @throws \moodle_exception if the user or conversation don't exist. | |
928 | */ | |
929 | public static function set_favourite_conversation(int $conversationid, int $userid) : favourite { | |
6b036d04 SA |
930 | global $DB; |
931 | ||
b8ff2c44 JD |
932 | if (!self::is_user_in_conversation($userid, $conversationid)) { |
933 | throw new \moodle_exception("Conversation doesn't exist or user is not a member"); | |
934 | } | |
6b036d04 SA |
935 | // Get the context for this conversation. |
936 | $conversation = $DB->get_record('message_conversations', ['id' => $conversationid]); | |
937 | $userctx = \context_user::instance($userid); | |
938 | if (empty($conversation->contextid)) { | |
939 | // When the conversation hasn't any contextid value defined, the favourite will be added to the user context. | |
940 | $conversationctx = $userctx; | |
9e189a91 | 941 | } else { |
6b036d04 SA |
942 | // If the contextid is defined, the favourite will be added there. |
943 | $conversationctx = \context::instance_by_id($conversation->contextid); | |
9e189a91 | 944 | } |
6b036d04 SA |
945 | |
946 | $ufservice = \core_favourites\service_factory::get_service_for_user_context($userctx); | |
2f7076eb AN |
947 | |
948 | if ($favourite = $ufservice->get_favourite('core_message', 'message_conversations', $conversationid, $conversationctx)) { | |
949 | return $favourite; | |
950 | } else { | |
951 | return $ufservice->create_favourite('core_message', 'message_conversations', $conversationid, $conversationctx); | |
952 | } | |
b8ff2c44 JD |
953 | } |
954 | ||
955 | /** | |
956 | * Unset a conversation as a favourite for the given user. | |
957 | * | |
958 | * @param int $conversationid the id of the conversation to unset as a favourite. | |
959 | * @param int $userid the id to whom the favourite belongs. | |
960 | * @throws \moodle_exception if the favourite does not exist for the user. | |
961 | */ | |
962 | public static function unset_favourite_conversation(int $conversationid, int $userid) { | |
6b036d04 SA |
963 | global $DB; |
964 | ||
965 | // Get the context for this conversation. | |
43f68992 | 966 | $conversation = $DB->get_record('message_conversations', ['id' => $conversationid]); |
6b036d04 SA |
967 | $userctx = \context_user::instance($userid); |
968 | if (empty($conversation->contextid)) { | |
969 | // When the conversation hasn't any contextid value defined, the favourite will be added to the user context. | |
970 | $conversationctx = $userctx; | |
971 | } else { | |
972 | // If the contextid is defined, the favourite will be added there. | |
973 | $conversationctx = \context::instance_by_id($conversation->contextid); | |
974 | } | |
975 | ||
976 | $ufservice = \core_favourites\service_factory::get_service_for_user_context($userctx); | |
977 | $ufservice->delete_favourite('core_message', 'message_conversations', $conversationid, $conversationctx); | |
b8ff2c44 JD |
978 | } |
979 | ||
879e2bef MN |
980 | /** |
981 | * Returns the contacts to display in the contacts area. | |
982 | * | |
4461288d MN |
983 | * TODO: This function should be removed once the new group messaging UI is in place and the old messaging UI is removed. |
984 | * For now we are not removing/deprecating this function for backwards compatibility with messaging UI. | |
985 | * Followup: MDL-63915 | |
986 | * | |
879e2bef MN |
987 | * @param int $userid The user id |
988 | * @param int $limitfrom | |
989 | * @param int $limitnum | |
de55cb1b | 990 | * @return array |
879e2bef MN |
991 | */ |
992 | public static function get_contacts($userid, $limitfrom = 0, $limitnum = 0) { | |
993 | global $DB; | |
994 | ||
f219eac7 MN |
995 | $contactids = []; |
996 | $sql = "SELECT mc.* | |
879e2bef | 997 | FROM {message_contacts} mc |
f219eac7 MN |
998 | WHERE mc.userid = ? OR mc.contactid = ? |
999 | ORDER BY timecreated DESC"; | |
1000 | if ($contacts = $DB->get_records_sql($sql, [$userid, $userid], $limitfrom, $limitnum)) { | |
879e2bef | 1001 | foreach ($contacts as $contact) { |
f219eac7 MN |
1002 | if ($userid == $contact->userid) { |
1003 | $contactids[] = $contact->contactid; | |
1004 | } else { | |
1005 | $contactids[] = $contact->userid; | |
1006 | } | |
1007 | } | |
1008 | } | |
1009 | ||
1010 | if (!empty($contactids)) { | |
1011 | list($insql, $inparams) = $DB->get_in_or_equal($contactids); | |
1012 | ||
1013 | $sql = "SELECT u.*, mub.id as isblocked | |
1014 | FROM {user} u | |
1015 | LEFT JOIN {message_users_blocked} mub | |
1016 | ON u.id = mub.blockeduserid | |
1017 | WHERE u.id $insql"; | |
1018 | if ($contacts = $DB->get_records_sql($sql, $inparams)) { | |
1019 | $arrcontacts = []; | |
1020 | foreach ($contacts as $contact) { | |
1021 | $contact->blocked = $contact->isblocked ? 1 : 0; | |
1022 | $arrcontacts[] = helper::create_contact($contact); | |
1023 | } | |
1024 | ||
1025 | return $arrcontacts; | |
879e2bef MN |
1026 | } |
1027 | } | |
1028 | ||
f219eac7 | 1029 | return []; |
879e2bef MN |
1030 | } |
1031 | ||
715c45aa MN |
1032 | /** |
1033 | * Get the contacts for a given user. | |
1034 | * | |
1035 | * @param int $userid | |
1036 | * @param int $limitfrom | |
1037 | * @param int $limitnum | |
1038 | * @return array An array of contacts | |
1039 | */ | |
1040 | public static function get_user_contacts(int $userid, int $limitfrom = 0, int $limitnum = 0) { | |
1041 | global $DB; | |
1042 | ||
1043 | $sql = "SELECT * | |
1044 | FROM {message_contacts} mc | |
1045 | WHERE mc.userid = ? OR mc.contactid = ? | |
1046 | ORDER BY timecreated DESC, id ASC"; | |
1047 | if ($contacts = $DB->get_records_sql($sql, [$userid, $userid], $limitfrom, $limitnum)) { | |
1048 | $userids = []; | |
1049 | foreach ($contacts as $contact) { | |
1050 | if ($contact->userid == $userid) { | |
1051 | $userids[] = $contact->contactid; | |
1052 | } else { | |
1053 | $userids[] = $contact->userid; | |
1054 | } | |
1055 | } | |
1056 | return helper::get_member_info($userid, $userids); | |
1057 | } | |
1058 | ||
1059 | return []; | |
1060 | } | |
1061 | ||
ecb4755c RW |
1062 | /** |
1063 | * Returns the contacts count. | |
1064 | * | |
1065 | * @param int $userid The user id | |
1066 | * @return array | |
1067 | */ | |
1068 | public static function count_contacts(int $userid) : int { | |
1069 | global $DB; | |
1070 | ||
1071 | $sql = "SELECT COUNT(id) | |
1072 | FROM {message_contacts} | |
1073 | WHERE userid = ? OR contactid = ?"; | |
1074 | return $DB->count_records_sql($sql, [$userid, $userid]); | |
1075 | } | |
1076 | ||
883ce421 MN |
1077 | /** |
1078 | * Returns the an array of the users the given user is in a conversation | |
1079 | * with who are a contact and the number of unread messages. | |
1080 | * | |
1081 | * @param int $userid The user id | |
1082 | * @param int $limitfrom | |
1083 | * @param int $limitnum | |
1084 | * @return array | |
1085 | */ | |
1086 | public static function get_contacts_with_unread_message_count($userid, $limitfrom = 0, $limitnum = 0) { | |
1087 | global $DB; | |
1088 | ||
1089 | $userfields = \user_picture::fields('u', array('lastaccess')); | |
1090 | $unreadcountssql = "SELECT $userfields, count(m.id) as messagecount | |
1091 | FROM {message_contacts} mc | |
1092 | INNER JOIN {user} u | |
f219eac7 | 1093 | ON (u.id = mc.contactid OR u.id = mc.userid) |
883ce421 | 1094 | LEFT JOIN {messages} m |
f219eac7 | 1095 | ON ((m.useridfrom = mc.contactid OR m.useridfrom = mc.userid) AND m.useridfrom != ?) |
883ce421 MN |
1096 | LEFT JOIN {message_conversation_members} mcm |
1097 | ON mcm.conversationid = m.conversationid AND mcm.userid = ? AND mcm.userid != m.useridfrom | |
1098 | LEFT JOIN {message_user_actions} mua | |
1099 | ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?) | |
f219eac7 MN |
1100 | LEFT JOIN {message_users_blocked} mub |
1101 | ON (mub.userid = ? AND mub.blockeduserid = u.id) | |
883ce421 | 1102 | WHERE mua.id is NULL |
f219eac7 MN |
1103 | AND mub.id is NULL |
1104 | AND (mc.userid = ? OR mc.contactid = ?) | |
1105 | AND u.id != ? | |
883ce421 MN |
1106 | AND u.deleted = 0 |
1107 | GROUP BY $userfields"; | |
1108 | ||
f219eac7 MN |
1109 | return $DB->get_records_sql($unreadcountssql, [$userid, $userid, $userid, self::MESSAGE_ACTION_READ, |
1110 | $userid, $userid, $userid, $userid], $limitfrom, $limitnum); | |
883ce421 MN |
1111 | } |
1112 | ||
1113 | /** | |
1114 | * Returns the an array of the users the given user is in a conversation | |
1115 | * with who are not a contact and the number of unread messages. | |
1116 | * | |
1117 | * @param int $userid The user id | |
1118 | * @param int $limitfrom | |
1119 | * @param int $limitnum | |
1120 | * @return array | |
1121 | */ | |
1122 | public static function get_non_contacts_with_unread_message_count($userid, $limitfrom = 0, $limitnum = 0) { | |
1123 | global $DB; | |
1124 | ||
1125 | $userfields = \user_picture::fields('u', array('lastaccess')); | |
1126 | $unreadcountssql = "SELECT $userfields, count(m.id) as messagecount | |
1127 | FROM {user} u | |
1128 | INNER JOIN {messages} m | |
1129 | ON m.useridfrom = u.id | |
1130 | INNER JOIN {message_conversation_members} mcm | |
1131 | ON mcm.conversationid = m.conversationid | |
1132 | LEFT JOIN {message_user_actions} mua | |
1133 | ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?) | |
1134 | LEFT JOIN {message_contacts} mc | |
1135 | ON (mc.userid = ? AND mc.contactid = u.id) | |
f219eac7 MN |
1136 | LEFT JOIN {message_users_blocked} mub |
1137 | ON (mub.userid = ? AND mub.blockeduserid = u.id) | |
883ce421 MN |
1138 | WHERE mcm.userid = ? |
1139 | AND mcm.userid != m.useridfrom | |
1140 | AND mua.id is NULL | |
f219eac7 | 1141 | AND mub.id is NULL |
883ce421 MN |
1142 | AND mc.id is NULL |
1143 | AND u.deleted = 0 | |
1144 | GROUP BY $userfields"; | |
1145 | ||
f219eac7 | 1146 | return $DB->get_records_sql($unreadcountssql, [$userid, self::MESSAGE_ACTION_READ, $userid, $userid, $userid], |
883ce421 MN |
1147 | $limitfrom, $limitnum); |
1148 | } | |
1149 | ||
879e2bef MN |
1150 | /** |
1151 | * Returns the messages to display in the message area. | |
1152 | * | |
4461288d MN |
1153 | * TODO: This function should be removed once the new group messaging UI is in place and the old messaging UI is removed. |
1154 | * For now we are not removing/deprecating this function for backwards compatibility with messaging UI. | |
1155 | * Followup: MDL-63915 | |
1156 | * | |
879e2bef MN |
1157 | * @param int $userid the current user |
1158 | * @param int $otheruserid the other user | |
1159 | * @param int $limitfrom | |
1160 | * @param int $limitnum | |
8ec78c48 | 1161 | * @param string $sort |
ffd7798c MN |
1162 | * @param int $timefrom the time from the message being sent |
1163 | * @param int $timeto the time up until the message being sent | |
de55cb1b | 1164 | * @return array |
879e2bef | 1165 | */ |
fb1469d8 | 1166 | public static function get_messages($userid, $otheruserid, $limitfrom = 0, $limitnum = 0, |
d1e8e69d | 1167 | $sort = 'timecreated ASC', $timefrom = 0, $timeto = 0) { |
fb1469d8 | 1168 | |
ffd7798c | 1169 | if (!empty($timefrom)) { |
fb04293b SA |
1170 | // Get the conversation between userid and otheruserid. |
1171 | $userids = [$userid, $otheruserid]; | |
1172 | if (!$conversationid = self::get_conversation_between_users($userids)) { | |
1173 | // This method was always used for individual conversations. | |
1174 | $conversation = self::create_conversation(self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $userids); | |
1175 | $conversationid = $conversation->id; | |
1176 | } | |
1177 | ||
fb1469d8 | 1178 | // Check the cache to see if we even need to do a DB query. |
4699b8bc AN |
1179 | $cache = \cache::make('core', 'message_time_last_message_between_users'); |
1180 | $key = helper::get_last_message_time_created_cache_key($conversationid); | |
1181 | $lastcreated = $cache->get($key); | |
fb1469d8 RW |
1182 | |
1183 | // The last known message time is earlier than the one being requested so we can | |
1184 | // just return an empty result set rather than having to query the DB. | |
ffd7798c | 1185 | if ($lastcreated && $lastcreated < $timefrom) { |
fb1469d8 RW |
1186 | return []; |
1187 | } | |
1188 | } | |
1189 | ||
879e2bef | 1190 | $arrmessages = array(); |
fb1469d8 | 1191 | if ($messages = helper::get_messages($userid, $otheruserid, 0, $limitfrom, $limitnum, |
ffd7798c | 1192 | $sort, $timefrom, $timeto)) { |
de55cb1b | 1193 | $arrmessages = helper::create_messages($userid, $messages); |
879e2bef MN |
1194 | } |
1195 | ||
de55cb1b | 1196 | return $arrmessages; |
879e2bef | 1197 | } |
c060cd49 | 1198 | |
fb04293b SA |
1199 | /** |
1200 | * Returns the messages for the defined conversation. | |
1201 | * | |
1202 | * @param int $userid The current user. | |
1203 | * @param int $convid The conversation where the messages belong. Could be an object or just the id. | |
1204 | * @param int $limitfrom Return a subset of records, starting at this point (optional). | |
1205 | * @param int $limitnum Return a subset comprising this many records in total (optional, required if $limitfrom is set). | |
1206 | * @param string $sort The column name to order by including optionally direction. | |
1207 | * @param int $timefrom The time from the message being sent. | |
1208 | * @param int $timeto The time up until the message being sent. | |
1209 | * @return array of messages | |
1210 | */ | |
1211 | public static function get_conversation_messages(int $userid, int $convid, int $limitfrom = 0, int $limitnum = 0, | |
1212 | string $sort = 'timecreated ASC', int $timefrom = 0, int $timeto = 0) : array { | |
1213 | ||
1214 | if (!empty($timefrom)) { | |
1215 | // Check the cache to see if we even need to do a DB query. | |
74138e92 | 1216 | $cache = \cache::make('core', 'message_time_last_message_between_users'); |
4699b8bc AN |
1217 | $key = helper::get_last_message_time_created_cache_key($convid); |
1218 | $lastcreated = $cache->get($key); | |
fb04293b SA |
1219 | |
1220 | // The last known message time is earlier than the one being requested so we can | |
1221 | // just return an empty result set rather than having to query the DB. | |
1222 | if ($lastcreated && $lastcreated < $timefrom) { | |
1223 | return []; | |
1224 | } | |
1225 | } | |
1226 | ||
32b4212e RW |
1227 | $messages = helper::get_conversation_messages($userid, $convid, 0, $limitfrom, $limitnum, $sort, $timefrom, $timeto); |
1228 | return helper::format_conversation_messages($userid, $convid, $messages); | |
fb04293b SA |
1229 | } |
1230 | ||
c060cd49 MN |
1231 | /** |
1232 | * Returns the most recent message between two users. | |
1233 | * | |
4461288d MN |
1234 | * TODO: This function should be removed once the new group messaging UI is in place and the old messaging UI is removed. |
1235 | * For now we are not removing/deprecating this function for backwards compatibility with messaging UI. | |
1236 | * Followup: MDL-63915 | |
1237 | * | |
c060cd49 MN |
1238 | * @param int $userid the current user |
1239 | * @param int $otheruserid the other user | |
de55cb1b | 1240 | * @return \stdClass|null |
c060cd49 MN |
1241 | */ |
1242 | public static function get_most_recent_message($userid, $otheruserid) { | |
1243 | // We want two messages here so we get an accurate 'blocktime' value. | |
de55cb1b | 1244 | if ($messages = helper::get_messages($userid, $otheruserid, 0, 0, 2, 'timecreated DESC')) { |
c060cd49 MN |
1245 | // Swap the order so we now have them in historical order. |
1246 | $messages = array_reverse($messages); | |
de55cb1b | 1247 | $arrmessages = helper::create_messages($userid, $messages); |
c060cd49 MN |
1248 | return array_pop($arrmessages); |
1249 | } | |
1250 | ||
1251 | return null; | |
1252 | } | |
c6e97f54 | 1253 | |
fb04293b SA |
1254 | /** |
1255 | * Returns the most recent message in a conversation. | |
1256 | * | |
1257 | * @param int $convid The conversation identifier. | |
1258 | * @param int $currentuserid The current user identifier. | |
1259 | * @return \stdClass|null The most recent message. | |
1260 | */ | |
1261 | public static function get_most_recent_conversation_message(int $convid, int $currentuserid = 0) { | |
1262 | global $USER; | |
1263 | ||
1264 | if (empty($currentuserid)) { | |
1265 | $currentuserid = $USER->id; | |
1266 | } | |
1267 | ||
1268 | if ($messages = helper::get_conversation_messages($currentuserid, $convid, 0, 0, 1, 'timecreated DESC')) { | |
1269 | $convmessages = helper::format_conversation_messages($currentuserid, $convid, $messages); | |
1270 | return array_pop($convmessages['messages']); | |
1271 | } | |
1272 | ||
1273 | return null; | |
1274 | } | |
1275 | ||
c6e97f54 MN |
1276 | /** |
1277 | * Returns the profile information for a contact for a user. | |
1278 | * | |
4461288d MN |
1279 | * TODO: This function should be removed once the new group messaging UI is in place and the old messaging UI is removed. |
1280 | * For now we are not removing/deprecating this function for backwards compatibility with messaging UI. | |
1281 | * Followup: MDL-63915 | |
1282 | * | |
c6e97f54 MN |
1283 | * @param int $userid The user id |
1284 | * @param int $otheruserid The id of the user whose profile we want to view. | |
de55cb1b | 1285 | * @return \stdClass |
c6e97f54 MN |
1286 | */ |
1287 | public static function get_profile($userid, $otheruserid) { | |
f219eac7 | 1288 | global $CFG, $PAGE; |
c6e97f54 MN |
1289 | |
1290 | require_once($CFG->dirroot . '/user/lib.php'); | |
1291 | ||
f4c39cb9 MN |
1292 | $user = \core_user::get_user($otheruserid, '*', MUST_EXIST); |
1293 | ||
1294 | // Create the data we are going to pass to the renderable. | |
1295 | $data = new \stdClass(); | |
1296 | $data->userid = $otheruserid; | |
1297 | $data->fullname = fullname($user); | |
1298 | $data->city = ''; | |
1299 | $data->country = ''; | |
1300 | $data->email = ''; | |
cb805753 | 1301 | $data->isonline = null; |
f4c39cb9 MN |
1302 | // Get the user picture data - messaging has always shown these to the user. |
1303 | $userpicture = new \user_picture($user); | |
1304 | $userpicture->size = 1; // Size f1. | |
1305 | $data->profileimageurl = $userpicture->get_url($PAGE)->out(false); | |
1306 | $userpicture->size = 0; // Size f2. | |
1307 | $data->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false); | |
1308 | ||
1309 | $userfields = user_get_user_details($user, null, array('city', 'country', 'email', 'lastaccess')); | |
1310 | if ($userfields) { | |
1311 | if (isset($userfields['city'])) { | |
1312 | $data->city = $userfields['city']; | |
1313 | } | |
1314 | if (isset($userfields['country'])) { | |
1315 | $data->country = $userfields['country']; | |
bf58081d | 1316 | } |
f4c39cb9 MN |
1317 | if (isset($userfields['email'])) { |
1318 | $data->email = $userfields['email']; | |
c6e97f54 | 1319 | } |
f4c39cb9 MN |
1320 | if (isset($userfields['lastaccess'])) { |
1321 | $data->isonline = helper::is_online($userfields['lastaccess']); | |
1322 | } | |
1323 | } | |
c6e97f54 | 1324 | |
f219eac7 MN |
1325 | $data->isblocked = self::is_blocked($userid, $otheruserid); |
1326 | $data->iscontact = self::is_contact($userid, $otheruserid); | |
f4c39cb9 MN |
1327 | |
1328 | return $data; | |
c6e97f54 | 1329 | } |
dec0cd99 MN |
1330 | |
1331 | /** | |
1332 | * Checks if a user can delete messages they have either received or sent. | |
1333 | * | |
1334 | * @param int $userid The user id of who we want to delete the messages for (this may be done by the admin | |
1335 | * but will still seem as if it was by the user) | |
15663b0b | 1336 | * @param int $conversationid The id of the conversation |
1f64514d | 1337 | * @return bool Returns true if a user can delete the conversation, false otherwise. |
dec0cd99 | 1338 | */ |
15663b0b | 1339 | public static function can_delete_conversation(int $userid, int $conversationid = null) : bool { |
dec0cd99 MN |
1340 | global $USER; |
1341 | ||
15663b0b MN |
1342 | if (is_null($conversationid)) { |
1343 | debugging('\core_message\api::can_delete_conversation() now expects a \'conversationid\' to be passed.', | |
1344 | DEBUG_DEVELOPER); | |
1345 | return false; | |
1346 | } | |
1347 | ||
dec0cd99 MN |
1348 | $systemcontext = \context_system::instance(); |
1349 | ||
15663b0b MN |
1350 | if (has_capability('moodle/site:deleteanymessage', $systemcontext)) { |
1351 | return true; | |
1352 | } | |
1353 | ||
1354 | if (!self::is_user_in_conversation($userid, $conversationid)) { | |
1355 | return false; | |
1356 | } | |
1357 | ||
1358 | if (has_capability('moodle/site:deleteownmessage', $systemcontext) && | |
1359 | $USER->id == $userid) { | |
dec0cd99 MN |
1360 | return true; |
1361 | } | |
1362 | ||
1363 | return false; | |
1364 | } | |
1365 | ||
1366 | /** | |
1367 | * Deletes a conversation. | |
1368 | * | |
1369 | * This function does not verify any permissions. | |
1370 | * | |
263ad984 | 1371 | * @deprecated since 3.6 |
dec0cd99 MN |
1372 | * @param int $userid The user id of who we want to delete the messages for (this may be done by the admin |
1373 | * but will still seem as if it was by the user) | |
1374 | * @param int $otheruserid The id of the other user in the conversation | |
1375 | * @return bool | |
1376 | */ | |
1377 | public static function delete_conversation($userid, $otheruserid) { | |
263ad984 MN |
1378 | debugging('\core_message\api::delete_conversation() is deprecated, please use ' . |
1379 | '\core_message\api::delete_conversation_by_id() instead.', DEBUG_DEVELOPER); | |
dec0cd99 | 1380 | |
b2cd17e6 | 1381 | $conversationid = self::get_conversation_between_users([$userid, $otheruserid]); |
dec0cd99 | 1382 | |
883ce421 MN |
1383 | // If there is no conversation, there is nothing to do. |
1384 | if (!$conversationid) { | |
1385 | return true; | |
1386 | } | |
1387 | ||
263ad984 MN |
1388 | self::delete_conversation_by_id($userid, $conversationid); |
1389 | ||
1390 | return true; | |
1391 | } | |
1392 | ||
1393 | /** | |
1394 | * Deletes a conversation for a specified user. | |
1395 | * | |
1396 | * This function does not verify any permissions. | |
1397 | * | |
1398 | * @param int $userid The user id of who we want to delete the messages for (this may be done by the admin | |
1399 | * but will still seem as if it was by the user) | |
1400 | * @param int $conversationid The id of the other user in the conversation | |
1401 | */ | |
1402 | public static function delete_conversation_by_id(int $userid, int $conversationid) { | |
1403 | global $DB, $USER; | |
1404 | ||
883ce421 MN |
1405 | // Get all messages belonging to this conversation that have not already been deleted by this user. |
1406 | $sql = "SELECT m.* | |
1407 | FROM {messages} m | |
1408 | INNER JOIN {message_conversations} mc | |
1409 | ON m.conversationid = mc.id | |
1410 | LEFT JOIN {message_user_actions} mua | |
1411 | ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?) | |
1412 | WHERE mua.id is NULL | |
1413 | AND mc.id = ? | |
1414 | ORDER BY m.timecreated ASC"; | |
1415 | $messages = $DB->get_records_sql($sql, [$userid, self::MESSAGE_ACTION_DELETED, $conversationid]); | |
1416 | ||
1417 | // Ok, mark these as deleted. | |
1418 | foreach ($messages as $message) { | |
1419 | $mua = new \stdClass(); | |
1420 | $mua->userid = $userid; | |
1421 | $mua->messageid = $message->id; | |
1422 | $mua->action = self::MESSAGE_ACTION_DELETED; | |
1423 | $mua->timecreated = time(); | |
1424 | $mua->id = $DB->insert_record('message_user_actions', $mua); | |
1425 | ||
14de10c4 MN |
1426 | \core\event\message_deleted::create_from_ids($userid, $USER->id, |
1427 | $message->id, $mua->id)->trigger(); | |
dec0cd99 | 1428 | } |
dec0cd99 | 1429 | } |
79f6c36c MN |
1430 | |
1431 | /** | |
1432 | * Returns the count of unread conversations (collection of messages from a single user) for | |
1433 | * the given user. | |
1434 | * | |
1435 | * @param \stdClass $user the user who's conversations should be counted | |
1436 | * @return int the count of the user's unread conversations | |
1437 | */ | |
1438 | public static function count_unread_conversations($user = null) { | |
1439 | global $USER, $DB; | |
1440 | ||
1441 | if (empty($user)) { | |
1442 | $user = $USER; | |
1443 | } | |
1444 | ||
883ce421 MN |
1445 | $sql = "SELECT COUNT(DISTINCT(m.conversationid)) |
1446 | FROM {messages} m | |
1447 | INNER JOIN {message_conversations} mc | |
1448 | ON m.conversationid = mc.id | |
1449 | INNER JOIN {message_conversation_members} mcm | |
1450 | ON mc.id = mcm.conversationid | |
1451 | LEFT JOIN {message_user_actions} mua | |
1452 | ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?) | |
1453 | WHERE mcm.userid = ? | |
1454 | AND mcm.userid != m.useridfrom | |
1455 | AND mua.id is NULL"; | |
1456 | ||
1457 | return $DB->count_records_sql($sql, [$user->id, self::MESSAGE_ACTION_READ, $user->id]); | |
79f6c36c MN |
1458 | } |
1459 | ||
abf7a261 MN |
1460 | /** |
1461 | * Checks if a user can mark all messages as read. | |
1462 | * | |
1463 | * @param int $userid The user id of who we want to mark the messages for | |
1464 | * @param int $conversationid The id of the conversation | |
1465 | * @return bool true if user is permitted, false otherwise | |
1466 | * @since 3.6 | |
1467 | */ | |
1468 | public static function can_mark_all_messages_as_read(int $userid, int $conversationid) : bool { | |
1469 | global $USER; | |
1470 | ||
1471 | $systemcontext = \context_system::instance(); | |
1472 | ||
1473 | if (has_capability('moodle/site:readallmessages', $systemcontext)) { | |
1474 | return true; | |
1475 | } | |
1476 | ||
1477 | if (!self::is_user_in_conversation($userid, $conversationid)) { | |
1478 | return false; | |
1479 | } | |
1480 | ||
1481 | if ($USER->id == $userid) { | |
1482 | return true; | |
1483 | } | |
1484 | ||
1485 | return false; | |
1486 | } | |
1487 | ||
6399c7ef RW |
1488 | /** |
1489 | * Returns the count of conversations (collection of messages from a single user) for | |
1490 | * the given user. | |
1491 | * | |
1492 | * @param \stdClass $user The user who's conversations should be counted | |
1493 | * @param int $type The conversation type | |
1494 | * @param bool $excludefavourites Exclude favourite conversations | |
1495 | * @return int the count of the user's unread conversations | |
1496 | */ | |
1497 | public static function count_conversations($user, int $type = null, bool $excludefavourites = false) { | |
1498 | global $DB; | |
1499 | ||
1500 | $params = []; | |
1501 | $favouritessql = ''; | |
1502 | ||
1503 | if ($excludefavourites) { | |
1504 | $favouritessql = "AND m.conversationid NOT IN ( | |
1505 | SELECT itemid | |
1506 | FROM {favourite} | |
1507 | WHERE component = 'core_message' | |
1508 | AND itemtype = 'message_conversations' | |
1509 | AND userid = ? | |
1510 | )"; | |
1511 | $params[] = $user->id; | |
1512 | } | |
1513 | ||
1514 | switch($type) { | |
1515 | case null: | |
1516 | $params = array_merge([$user->id, self::MESSAGE_ACTION_DELETED, $user->id], $params); | |
1517 | $sql = "SELECT COUNT(DISTINCT(m.conversationid)) | |
1518 | FROM {messages} m | |
1519 | LEFT JOIN {message_conversations} c | |
1520 | ON m.conversationid = c.id | |
1521 | LEFT JOIN {message_user_actions} ma | |
1522 | ON ma.messageid = m.id | |
1523 | LEFT JOIN {message_conversation_members} mcm | |
1524 | ON m.conversationid = mcm.conversationid | |
1525 | WHERE mcm.userid = ? | |
1526 | AND ( | |
1527 | c.type != " . self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL . " | |
1528 | OR | |
1529 | ( | |
1530 | (ma.action IS NULL OR ma.action != ? OR ma.userid != ?) | |
1531 | AND c.type = " . self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL . " | |
1532 | ) | |
1533 | ) | |
1534 | ${favouritessql}"; | |
1535 | break; | |
1536 | case self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL: | |
1537 | $params = array_merge([self::MESSAGE_ACTION_DELETED, $user->id, $user->id], $params); | |
1538 | $sql = "SELECT COUNT(DISTINCT(m.conversationid)) | |
1539 | FROM {messages} m | |
1540 | LEFT JOIN {message_conversations} c | |
1541 | ON m.conversationid = c.id | |
1542 | LEFT JOIN {message_user_actions} ma | |
1543 | ON ma.messageid = m.id | |
1544 | LEFT JOIN {message_conversation_members} mcm | |
1545 | ON m.conversationid = mcm.conversationid | |
1546 | WHERE (ma.action IS NULL OR ma.action != ? OR ma.userid != ?) | |
1547 | AND mcm.userid = ? | |
1548 | AND c.type = " . self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL . " | |
1549 | ${favouritessql}"; | |
1550 | break; | |
1551 | default: | |
1552 | $params = array_merge([$user->id, $type], $params); | |
1553 | $sql = "SELECT COUNT(m.conversationid) | |
1554 | FROM {message_conversation_members} m | |
1555 | LEFT JOIN {message_conversations} c | |
1556 | ON m.conversationid = c.id | |
1557 | WHERE m.userid = ? | |
1558 | AND c.type = ? | |
1559 | ${favouritessql}"; | |
1560 | ||
1561 | } | |
1562 | ||
1563 | return $DB->count_records_sql($sql, $params); | |
1564 | } | |
1565 | ||
79f6c36c | 1566 | /** |
74ad60bf | 1567 | * Marks all messages being sent to a user in a particular conversation. |
79f6c36c | 1568 | * |
74ad60bf | 1569 | * If $conversationdid is null then it marks all messages as read sent to $userid. |
79f6c36c | 1570 | * |
74ad60bf MN |
1571 | * @param int $userid |
1572 | * @param int|null $conversationid The conversation the messages belong to mark as read, if null mark all | |
79f6c36c | 1573 | */ |
74ad60bf | 1574 | public static function mark_all_messages_as_read($userid, $conversationid = null) { |
79f6c36c MN |
1575 | global $DB; |
1576 | ||
883ce421 MN |
1577 | $messagesql = "SELECT m.* |
1578 | FROM {messages} m | |
1579 | INNER JOIN {message_conversations} mc | |
1580 | ON mc.id = m.conversationid | |
1581 | INNER JOIN {message_conversation_members} mcm | |
1582 | ON mcm.conversationid = mc.id | |
5aac33c7 MN |
1583 | LEFT JOIN {message_user_actions} mua |
1584 | ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?) | |
1585 | WHERE mua.id is NULL | |
1586 | AND mcm.userid = ? | |
74ad60bf | 1587 | AND m.useridfrom != ?"; |
5aac33c7 MN |
1588 | $messageparams = []; |
1589 | $messageparams[] = $userid; | |
1590 | $messageparams[] = self::MESSAGE_ACTION_READ; | |
74ad60bf MN |
1591 | $messageparams[] = $userid; |
1592 | $messageparams[] = $userid; | |
1593 | if (!is_null($conversationid)) { | |
1594 | $messagesql .= " AND mc.id = ?"; | |
1595 | $messageparams[] = $conversationid; | |
79f6c36c MN |
1596 | } |
1597 | ||
74ad60bf MN |
1598 | $messages = $DB->get_recordset_sql($messagesql, $messageparams); |
1599 | foreach ($messages as $message) { | |
1600 | self::mark_message_as_read($userid, $message); | |
1601 | } | |
1602 | $messages->close(); | |
1603 | } | |
1604 | ||
1605 | /** | |
1606 | * Marks all notifications being sent from one user to another user as read. | |
1607 | * | |
1608 | * If the from user is null then it marks all notifications as read sent to the to user. | |
1609 | * | |
1610 | * @param int $touserid the id of the message recipient | |
1611 | * @param int|null $fromuserid the id of the message sender, null if all messages | |
1612 | * @return void | |
1613 | */ | |
1614 | public static function mark_all_notifications_as_read($touserid, $fromuserid = null) { | |
1615 | global $DB; | |
1616 | ||
883ce421 MN |
1617 | $notificationsql = "SELECT n.* |
1618 | FROM {notifications} n | |
74ad60bf MN |
1619 | WHERE useridto = ? |
1620 | AND timeread is NULL"; | |
883ce421 | 1621 | $notificationsparams = [$touserid]; |
79f6c36c | 1622 | if (!empty($fromuserid)) { |
883ce421 MN |
1623 | $notificationsql .= " AND useridfrom = ?"; |
1624 | $notificationsparams[] = $fromuserid; | |
79f6c36c MN |
1625 | } |
1626 | ||
74ad60bf MN |
1627 | $notifications = $DB->get_recordset_sql($notificationsql, $notificationsparams); |
1628 | foreach ($notifications as $notification) { | |
1629 | self::mark_notification_as_read($notification); | |
79f6c36c | 1630 | } |
74ad60bf MN |
1631 | $notifications->close(); |
1632 | } | |
79f6c36c | 1633 | |
74ad60bf MN |
1634 | /** |
1635 | * Marks ALL messages being sent from $fromuserid to $touserid as read. | |
1636 | * | |
1637 | * Can be filtered by type. | |
1638 | * | |
1639 | * @deprecated since 3.5 | |
1640 | * @param int $touserid the id of the message recipient | |
1641 | * @param int $fromuserid the id of the message sender | |
1642 | * @param string $type filter the messages by type, either MESSAGE_TYPE_NOTIFICATION, MESSAGE_TYPE_MESSAGE or '' for all. | |
1643 | * @return void | |
1644 | */ | |
1645 | public static function mark_all_read_for_user($touserid, $fromuserid = 0, $type = '') { | |
1646 | debugging('\core_message\api::mark_all_read_for_user is deprecated. Please either use ' . | |
1647 | '\core_message\api::mark_all_notifications_read_for_user or \core_message\api::mark_all_messages_read_for_user', | |
1648 | DEBUG_DEVELOPER); | |
1649 | ||
1650 | $type = strtolower($type); | |
1651 | ||
1652 | $conversationid = null; | |
1653 | $ignoremessages = false; | |
1654 | if (!empty($fromuserid)) { | |
99248f61 | 1655 | $conversationid = self::get_conversation_between_users([$touserid, $fromuserid]); |
74ad60bf MN |
1656 | if (!$conversationid) { // If there is no conversation between the users then there are no messages to mark. |
1657 | $ignoremessages = true; | |
1658 | } | |
79f6c36c MN |
1659 | } |
1660 | ||
74ad60bf MN |
1661 | if (!empty($type)) { |
1662 | if ($type == MESSAGE_TYPE_NOTIFICATION) { | |
99248f61 | 1663 | self::mark_all_notifications_as_read($touserid, $fromuserid); |
74ad60bf MN |
1664 | } else if ($type == MESSAGE_TYPE_MESSAGE) { |
1665 | if (!$ignoremessages) { | |
99248f61 | 1666 | self::mark_all_messages_as_read($touserid, $conversationid); |
74ad60bf MN |
1667 | } |
1668 | } | |
1669 | } else { // We want both. | |
99248f61 | 1670 | self::mark_all_notifications_as_read($touserid, $fromuserid); |
74ad60bf | 1671 | if (!$ignoremessages) { |
99248f61 | 1672 | self::mark_all_messages_as_read($touserid, $conversationid); |
74ad60bf | 1673 | } |
883ce421 | 1674 | } |
79f6c36c MN |
1675 | } |
1676 | ||
79f6c36c MN |
1677 | /** |
1678 | * Returns message preferences. | |
1679 | * | |
1680 | * @param array $processors | |
1681 | * @param array $providers | |
1682 | * @param \stdClass $user | |
1683 | * @return \stdClass | |
1684 | * @since 3.2 | |
1685 | */ | |
1686 | public static function get_all_message_preferences($processors, $providers, $user) { | |
1687 | $preferences = helper::get_providers_preferences($providers, $user->id); | |
1688 | $preferences->userdefaultemail = $user->email; // May be displayed by the email processor. | |
1689 | ||
1690 | // For every processors put its options on the form (need to get function from processor's lib.php). | |
1691 | foreach ($processors as $processor) { | |
1692 | $processor->object->load_data($preferences, $user->id); | |
1693 | } | |
1694 | ||
1695 | // Load general messaging preferences. | |
f7dfa9ba | 1696 | $preferences->blocknoncontacts = self::get_user_privacy_messaging_preference($user->id); |
79f6c36c MN |
1697 | $preferences->mailformat = $user->mailformat; |
1698 | $preferences->mailcharset = get_user_preferences('mailcharset', '', $user->id); | |
1699 | ||
1700 | return $preferences; | |
1701 | } | |
5b0769db MN |
1702 | |
1703 | /** | |
1704 | * Count the number of users blocked by a user. | |
1705 | * | |
1706 | * @param \stdClass $user The user object | |
1707 | * @return int the number of blocked users | |
1708 | */ | |
1709 | public static function count_blocked_users($user = null) { | |
1710 | global $USER, $DB; | |
1711 | ||
1712 | if (empty($user)) { | |
1713 | $user = $USER; | |
1714 | } | |
1715 | ||
f219eac7 MN |
1716 | $sql = "SELECT count(mub.id) |
1717 | FROM {message_users_blocked} mub | |
1718 | WHERE mub.userid = :userid"; | |
5b0769db MN |
1719 | return $DB->count_records_sql($sql, array('userid' => $user->id)); |
1720 | } | |
1721 | ||
1722 | /** | |
1723 | * Determines if a user is permitted to send another user a private message. | |
1724 | * If no sender is provided then it defaults to the logged in user. | |
1725 | * | |
1726 | * @param \stdClass $recipient The user object. | |
1727 | * @param \stdClass|null $sender The user object. | |
1728 | * @return bool true if user is permitted, false otherwise. | |
1729 | */ | |
1730 | public static function can_post_message($recipient, $sender = null) { | |
1731 | global $USER; | |
1732 | ||
1733 | if (is_null($sender)) { | |
1734 | // The message is from the logged in user, unless otherwise specified. | |
1735 | $sender = $USER; | |
1736 | } | |
1737 | ||
7983fb83 SA |
1738 | $systemcontext = \context_system::instance(); |
1739 | if (!has_capability('moodle/site:sendmessage', $systemcontext, $sender)) { | |
5b0769db MN |
1740 | return false; |
1741 | } | |
1742 | ||
7983fb83 | 1743 | if (has_capability('moodle/site:readallmessages', $systemcontext, $sender->id)) { |
c886e2c9 MN |
1744 | return true; |
1745 | } | |
1746 | ||
7983fb83 | 1747 | // Check if the recipient can be messaged by the sender. |
9f82758c | 1748 | return (self::can_contact_user($recipient->id, $sender->id)); |
5b0769db MN |
1749 | } |
1750 | ||
8e3bf150 JD |
1751 | /** |
1752 | * Determines if a user is permitted to send a message to a given conversation. | |
1753 | * If no sender is provided then it defaults to the logged in user. | |
1754 | * | |
1755 | * @param int $userid the id of the user on which the checks will be applied. | |
1756 | * @param int $conversationid the id of the conversation we wish to check. | |
1757 | * @return bool true if the user can send a message to the conversation, false otherwise. | |
1758 | * @throws \moodle_exception | |
1759 | */ | |
1760 | public static function can_send_message_to_conversation(int $userid, int $conversationid) : bool { | |
1761 | global $DB; | |
1762 | ||
1763 | $systemcontext = \context_system::instance(); | |
1764 | if (!has_capability('moodle/site:sendmessage', $systemcontext, $userid)) { | |
1765 | return false; | |
1766 | } | |
1767 | ||
1768 | if (!self::is_user_in_conversation($userid, $conversationid)) { | |
1769 | return false; | |
1770 | } | |
1771 | ||
1772 | // User can post messages and is in the conversation, but we need to check the conversation type to | |
1773 | // know whether or not to check the user privacy settings via can_contact_user(). | |
1774 | $conversation = $DB->get_record('message_conversations', ['id' => $conversationid], '*', MUST_EXIST); | |
1775 | if ($conversation->type == self::MESSAGE_CONVERSATION_TYPE_GROUP) { | |
1776 | return true; | |
1777 | } else if ($conversation->type == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) { | |
1778 | // Get the other user in the conversation. | |
1779 | $members = self::get_conversation_members($userid, $conversationid); | |
1780 | $otheruser = array_filter($members, function($member) use($userid) { | |
1781 | return $member->id != $userid; | |
1782 | }); | |
1783 | $otheruser = reset($otheruser); | |
1784 | ||
9f82758c | 1785 | return self::can_contact_user($otheruser->id, $userid); |
8e3bf150 JD |
1786 | } else { |
1787 | throw new \moodle_exception("Invalid conversation type '$conversation->type'."); | |
1788 | } | |
1789 | } | |
1790 | ||
52f9cf20 JD |
1791 | /** |
1792 | * Send a message from a user to a conversation. | |
1793 | * | |
1794 | * This method will create the basic eventdata and delegate to message creation to message_send. | |
1795 | * The message_send() method is responsible for event data that is specific to each recipient. | |
1796 | * | |
1797 | * @param int $userid the sender id. | |
1798 | * @param int $conversationid the conversation id. | |
1799 | * @param string $message the message to send. | |
1800 | * @param int $format the format of the message to send. | |
1801 | * @return \stdClass the message created. | |
1802 | * @throws \coding_exception | |
1803 | * @throws \moodle_exception if the user is not permitted to send a message to the conversation. | |
1804 | */ | |
1805 | public static function send_message_to_conversation(int $userid, int $conversationid, string $message, | |
1806 | int $format) : \stdClass { | |
1807 | global $DB; | |
1808 | ||
1809 | if (!self::can_send_message_to_conversation($userid, $conversationid)) { | |
1810 | throw new \moodle_exception("User $userid cannot send a message to conversation $conversationid"); | |
1811 | } | |
1812 | ||
1813 | $eventdata = new \core\message\message(); | |
1814 | $eventdata->courseid = 1; | |
1815 | $eventdata->component = 'moodle'; | |
1816 | $eventdata->name = 'instantmessage'; | |
1817 | $eventdata->userfrom = $userid; | |
1818 | $eventdata->convid = $conversationid; | |
1819 | ||
1820 | if ($format == FORMAT_HTML) { | |
1821 | $eventdata->fullmessagehtml = $message; | |
1822 | // Some message processors may revert to sending plain text even if html is supplied, | |
1823 | // so we keep both plain and html versions if we're intending to send html. | |
1824 | $eventdata->fullmessage = html_to_text($eventdata->fullmessagehtml); | |
1825 | } else { | |
1826 | $eventdata->fullmessage = $message; | |
1827 | $eventdata->fullmessagehtml = ''; | |
1828 | } | |
1829 | ||
1830 | $eventdata->fullmessageformat = $format; | |
1831 | $eventdata->smallmessage = $message; // Store the message unfiltered. Clean up on output. | |
1832 | ||
1833 | $eventdata->timecreated = time(); | |
1834 | $eventdata->notification = 0; | |
1835 | $messageid = message_send($eventdata); | |
1836 | ||
1837 | $messagerecord = $DB->get_record('messages', ['id' => $messageid], 'id, useridfrom, fullmessage, timecreated'); | |
1838 | $message = (object) [ | |
1839 | 'id' => $messagerecord->id, | |
1840 | 'useridfrom' => $messagerecord->useridfrom, | |
1841 | 'text' => $messagerecord->fullmessage, | |
1842 | 'timecreated' => $messagerecord->timecreated | |
1843 | ]; | |
1844 | return $message; | |
5b0769db MN |
1845 | } |
1846 | ||
f7dfa9ba SA |
1847 | /** |
1848 | * Get the messaging preference for a user. | |
1849 | * If the user has not any messaging privacy preference: | |
1850 | * - When $CFG->messagingallusers = false the default user preference is MESSAGE_PRIVACY_COURSEMEMBER. | |
1851 | * - When $CFG->messagingallusers = true the default user preference is MESSAGE_PRIVACY_SITE. | |
1852 | * | |
1853 | * @param int $userid The user identifier. | |
1854 | * @return int The default messaging preference. | |
1855 | */ | |
1856 | public static function get_user_privacy_messaging_preference(int $userid) : int { | |
d3d95d5d | 1857 | global $CFG, $USER; |
f7dfa9ba SA |
1858 | |
1859 | // When $CFG->messagingallusers is enabled, default value for the messaging preference will be "Anyone on the site"; | |
1860 | // otherwise, the default value will be "My contacts and anyone in my courses". | |
1861 | if (empty($CFG->messagingallusers)) { | |
1862 | $defaultprefvalue = self::MESSAGE_PRIVACY_COURSEMEMBER; | |
1863 | } else { | |
1864 | $defaultprefvalue = self::MESSAGE_PRIVACY_SITE; | |
1865 | } | |
d3d95d5d RW |
1866 | if ($userid == $USER->id) { |
1867 | $user = $USER; | |
1868 | } else { | |
1869 | $user = $userid; | |
1870 | } | |
1871 | $privacypreference = get_user_preferences('message_blocknoncontacts', $defaultprefvalue, $user); | |
f7dfa9ba SA |
1872 | |
1873 | // When the $CFG->messagingallusers privacy setting is disabled, MESSAGE_PRIVACY_SITE is | |
1874 | // also disabled, so it has to be replaced to MESSAGE_PRIVACY_COURSEMEMBER. | |
1875 | if (empty($CFG->messagingallusers) && $privacypreference == self::MESSAGE_PRIVACY_SITE) { | |
1876 | $privacypreference = self::MESSAGE_PRIVACY_COURSEMEMBER; | |
1877 | } | |
1878 | ||
1879 | return $privacypreference; | |
1880 | } | |
1881 | ||
5b0769db MN |
1882 | /** |
1883 | * Checks if the recipient is allowing messages from users that aren't a | |
1884 | * contact. If not then it checks to make sure the sender is in the | |
1885 | * recipient's contacts. | |
1886 | * | |
e5eba802 | 1887 | * @deprecated since 3.6 |
5b0769db MN |
1888 | * @param \stdClass $recipient The user object. |
1889 | * @param \stdClass|null $sender The user object. | |
1890 | * @return bool true if $sender is blocked, false otherwise. | |
1891 | */ | |
1892 | public static function is_user_non_contact_blocked($recipient, $sender = null) { | |
e5eba802 SA |
1893 | debugging('\core_message\api::is_user_non_contact_blocked() is deprecated', DEBUG_DEVELOPER); |
1894 | ||
f7dfa9ba | 1895 | global $USER, $CFG; |
5b0769db MN |
1896 | |
1897 | if (is_null($sender)) { | |
1898 | // The message is from the logged in user, unless otherwise specified. | |
1899 | $sender = $USER; | |
1900 | } | |
1901 | ||
f7dfa9ba SA |
1902 | $privacypreference = self::get_user_privacy_messaging_preference($recipient->id); |
1903 | switch ($privacypreference) { | |
1904 | case self::MESSAGE_PRIVACY_SITE: | |
1905 | if (!empty($CFG->messagingallusers)) { | |
1906 | // Users can be messaged without being contacts or members of the same course. | |
1907 | break; | |
1908 | } | |
1909 | // When the $CFG->messagingallusers privacy setting is disabled, continue with the next | |
1910 | // case, because MESSAGE_PRIVACY_SITE is replaced to MESSAGE_PRIVACY_COURSEMEMBER. | |
1911 | case self::MESSAGE_PRIVACY_COURSEMEMBER: | |
1912 | // Confirm the sender and the recipient are both members of the same course. | |
1913 | if (enrol_sharing_course($recipient, $sender)) { | |
1914 | // All good, the recipient and the sender are members of the same course. | |
1915 | return false; | |
1916 | } | |
1917 | case self::MESSAGE_PRIVACY_ONLYCONTACTS: | |
1918 | // True if they aren't contacts (they can't send a message because of the privacy settings), false otherwise. | |
1919 | return !self::is_contact($sender->id, $recipient->id); | |
5b0769db MN |
1920 | } |
1921 | ||
1922 | return false; | |
1923 | } | |
1924 | ||
1925 | /** | |
1926 | * Checks if the recipient has specifically blocked the sending user. | |
1927 | * | |
1928 | * Note: This function will always return false if the sender has the | |
1929 | * readallmessages capability at the system context level. | |
1930 | * | |
c886e2c9 | 1931 | * @deprecated since 3.6 |
26dca05d JP |
1932 | * @param int $recipientid User ID of the recipient. |
1933 | * @param int $senderid User ID of the sender. | |
5b0769db MN |
1934 | * @return bool true if $sender is blocked, false otherwise. |
1935 | */ | |
26dca05d | 1936 | public static function is_user_blocked($recipientid, $senderid = null) { |
c886e2c9 MN |
1937 | debugging('\core_message\api::is_user_blocked is deprecated and should not be used.', |
1938 | DEBUG_DEVELOPER); | |
1939 | ||
1940 | global $USER; | |
5b0769db | 1941 | |
26dca05d | 1942 | if (is_null($senderid)) { |
5b0769db | 1943 | // The message is from the logged in user, unless otherwise specified. |
26dca05d | 1944 | $senderid = $USER->id; |
5b0769db MN |
1945 | } |
1946 | ||
1947 | $systemcontext = \context_system::instance(); | |
26dca05d | 1948 | if (has_capability('moodle/site:readallmessages', $systemcontext, $senderid)) { |
5b0769db MN |
1949 | return false; |
1950 | } | |
1951 | ||
f219eac7 | 1952 | if (self::is_blocked($recipientid, $senderid)) { |
26dca05d | 1953 | return true; |
5b0769db MN |
1954 | } |
1955 | ||
1956 | return false; | |
1957 | } | |
20ab51fd AA |
1958 | |
1959 | /** | |
1960 | * Get specified message processor, validate corresponding plugin existence and | |
1961 | * system configuration. | |
1962 | * | |
1963 | * @param string $name Name of the processor. | |
1964 | * @param bool $ready only return ready-to-use processors. | |
1965 | * @return mixed $processor if processor present else empty array. | |
1966 | * @since Moodle 3.2 | |
1967 | */ | |
1968 | public static function get_message_processor($name, $ready = false) { | |
1969 | global $DB, $CFG; | |
1970 | ||
1971 | $processor = $DB->get_record('message_processors', array('name' => $name)); | |
1972 | if (empty($processor)) { | |
1973 | // Processor not found, return. | |
1974 | return array(); | |
1975 | } | |
1976 | ||
1977 | $processor = self::get_processed_processor_object($processor); | |
1978 | if ($ready) { | |
1979 | if ($processor->enabled && $processor->configured) { | |
1980 | return $processor; | |
1981 | } else { | |
1982 | return array(); | |
1983 | } | |
1984 | } else { | |
1985 | return $processor; | |
1986 | } | |
1987 | } | |
1988 | ||
1989 | /** | |
1990 | * Returns weather a given processor is enabled or not. | |
1991 | * Note:- This doesn't check if the processor is configured or not. | |
1992 | * | |
1993 | * @param string $name Name of the processor | |
1994 | * @return bool | |
1995 | */ | |
1996 | public static function is_processor_enabled($name) { | |
1997 | ||
1998 | $cache = \cache::make('core', 'message_processors_enabled'); | |
1999 | $status = $cache->get($name); | |
2000 | ||
2001 | if ($status === false) { | |
2002 | $processor = self::get_message_processor($name); | |
2003 | if (!empty($processor)) { | |
2004 | $cache->set($name, $processor->enabled); | |
2005 | return $processor->enabled; | |
2006 | } else { | |
2007 | return false; | |
2008 | } | |
2009 | } | |
2010 | ||
2011 | return $status; | |
2012 | } | |
2013 | ||
2014 | /** | |
2015 | * Set status of a processor. | |
2016 | * | |
2017 | * @param \stdClass $processor processor record. | |
2018 | * @param 0|1 $enabled 0 or 1 to set the processor status. | |
2019 | * @return bool | |
2020 | * @since Moodle 3.2 | |
2021 | */ | |
2022 | public static function update_processor_status($processor, $enabled) { | |
2023 | global $DB; | |
2024 | $cache = \cache::make('core', 'message_processors_enabled'); | |
2025 | $cache->delete($processor->name); | |
2026 | return $DB->set_field('message_processors', 'enabled', $enabled, array('id' => $processor->id)); | |
2027 | } | |
2028 | ||
2029 | /** | |
2030 | * Given a processor object, loads information about it's settings and configurations. | |
2031 | * This is not a public api, instead use @see \core_message\api::get_message_processor() | |
2032 | * or @see \get_message_processors() | |
2033 | * | |
2034 | * @param \stdClass $processor processor object | |
2035 | * @return \stdClass processed processor object | |
2036 | * @since Moodle 3.2 | |
2037 | */ | |
2038 | public static function get_processed_processor_object(\stdClass $processor) { | |
2039 | global $CFG; | |
2040 | ||
2041 | $processorfile = $CFG->dirroot. '/message/output/'.$processor->name.'/message_output_'.$processor->name.'.php'; | |
2042 | if (is_readable($processorfile)) { | |
2043 | include_once($processorfile); | |
2044 | $processclass = 'message_output_' . $processor->name; | |
2045 | if (class_exists($processclass)) { | |
2046 | $pclass = new $processclass(); | |
2047 | $processor->object = $pclass; | |
2048 | $processor->configured = 0; | |
2049 | if ($pclass->is_system_configured()) { | |
2050 | $processor->configured = 1; | |
2051 | } | |
2052 | $processor->hassettings = 0; | |
2053 | if (is_readable($CFG->dirroot.'/message/output/'.$processor->name.'/settings.php')) { | |
2054 | $processor->hassettings = 1; | |
2055 | } | |
2056 | $processor->available = 1; | |
2057 | } else { | |
2058 | print_error('errorcallingprocessor', 'message'); | |
2059 | } | |
2060 | } else { | |
2061 | $processor->available = 0; | |
2062 | } | |
2063 | return $processor; | |
2064 | } | |
883ce421 MN |
2065 | |
2066 | /** | |
2067 | * Retrieve users blocked by $user1 | |
2068 | * | |
2069 | * @param int $userid The user id of the user whos blocked users we are returning | |
2070 | * @return array the users blocked | |
2071 | */ | |
2072 | public static function get_blocked_users($userid) { | |
2073 | global $DB; | |
2074 | ||
2075 | $userfields = \user_picture::fields('u', array('lastaccess')); | |
2076 | $blockeduserssql = "SELECT $userfields | |
f219eac7 | 2077 | FROM {message_users_blocked} mub |
883ce421 | 2078 | INNER JOIN {user} u |
f219eac7 | 2079 | ON u.id = mub.blockeduserid |
883ce421 | 2080 | WHERE u.deleted = 0 |
f219eac7 | 2081 | AND mub.userid = ? |
883ce421 MN |
2082 | GROUP BY $userfields |
2083 | ORDER BY u.firstname ASC"; | |
2084 | return $DB->get_records_sql($blockeduserssql, [$userid]); | |
2085 | } | |
2086 | ||
2087 | /** | |
2088 | * Mark a single message as read. | |
2089 | * | |
2090 | * @param int $userid The user id who marked the message as read | |
548936a6 | 2091 | * @param \stdClass $message The message |
883ce421 MN |
2092 | * @param int|null $timeread The time the message was marked as read, if null will default to time() |
2093 | */ | |
548936a6 | 2094 | public static function mark_message_as_read($userid, $message, $timeread = null) { |
883ce421 MN |
2095 | global $DB; |
2096 | ||
2097 | if (is_null($timeread)) { | |
2098 | $timeread = time(); | |
2099 | } | |
2100 | ||
5aac33c7 MN |
2101 | $mua = new \stdClass(); |
2102 | $mua->userid = $userid; | |
2103 | $mua->messageid = $message->id; | |
2104 | $mua->action = self::MESSAGE_ACTION_READ; | |
2105 | $mua->timecreated = $timeread; | |
2106 | $mua->id = $DB->insert_record('message_user_actions', $mua); | |
2107 | ||
2108 | // Get the context for the user who received the message. | |
2109 | $context = \context_user::instance($userid, IGNORE_MISSING); | |
2110 | // If the user no longer exists the context value will be false, in this case use the system context. | |
2111 | if ($context === false) { | |
2112 | $context = \context_system::instance(); | |
883ce421 | 2113 | } |
5aac33c7 MN |
2114 | |
2115 | // Trigger event for reading a message. | |
2116 | $event = \core\event\message_viewed::create(array( | |
2117 | 'objectid' => $mua->id, | |
2118 | 'userid' => $userid, // Using the user who read the message as they are the ones performing the action. | |
2119 | 'context' => $context, | |
2120 | 'relateduserid' => $message->useridfrom, | |
2121 | 'other' => array( | |
2122 | 'messageid' => $message->id | |
2123 | ) | |
2124 | )); | |
2125 | $event->trigger(); | |
883ce421 MN |
2126 | } |
2127 | ||
2128 | /** | |
2129 | * Mark a single notification as read. | |
2130 | * | |
548936a6 | 2131 | * @param \stdClass $notification The notification |
883ce421 MN |
2132 | * @param int|null $timeread The time the message was marked as read, if null will default to time() |
2133 | */ | |
548936a6 | 2134 | public static function mark_notification_as_read($notification, $timeread = null) { |
883ce421 MN |
2135 | global $DB; |
2136 | ||
2137 | if (is_null($timeread)) { | |
2138 | $timeread = time(); | |
2139 | } | |
2140 | ||
883ce421 MN |
2141 | if (is_null($notification->timeread)) { |
2142 | $updatenotification = new \stdClass(); | |
2143 | $updatenotification->id = $notification->id; | |
2144 | $updatenotification->timeread = $timeread; | |
2145 | ||
2146 | $DB->update_record('notifications', $updatenotification); | |
376a79c2 MN |
2147 | |
2148 | // Trigger event for reading a notification. | |
2149 | \core\event\notification_viewed::create_from_ids( | |
2150 | $notification->useridfrom, | |
2151 | $notification->useridto, | |
2152 | $notification->id | |
2153 | )->trigger(); | |
883ce421 MN |
2154 | } |
2155 | } | |
2156 | ||
2157 | /** | |
2158 | * Checks if a user can delete a message. | |
2159 | * | |
2160 | * @param int $userid the user id of who we want to delete the message for (this may be done by the admin | |
2161 | * but will still seem as if it was by the user) | |
2162 | * @param int $messageid The message id | |
2163 | * @return bool Returns true if a user can delete the message, false otherwise. | |
2164 | */ | |
2165 | public static function can_delete_message($userid, $messageid) { | |
2166 | global $DB, $USER; | |
2167 | ||
08cb8a34 MN |
2168 | $systemcontext = \context_system::instance(); |
2169 | ||
2170 | $conversationid = $DB->get_field('messages', 'conversationid', ['id' => $messageid], MUST_EXIST); | |
2171 | ||
2172 | if (has_capability('moodle/site:deleteanymessage', $systemcontext)) { | |
2173 | return true; | |
883ce421 MN |
2174 | } |
2175 | ||
08cb8a34 MN |
2176 | if (!self::is_user_in_conversation($userid, $conversationid)) { |
2177 | return false; | |
2178 | } | |
883ce421 | 2179 | |
08cb8a34 MN |
2180 | if (has_capability('moodle/site:deleteownmessage', $systemcontext) && |
2181 | $USER->id == $userid) { | |
883ce421 MN |
2182 | return true; |
2183 | } | |
2184 | ||
2185 | return false; | |
2186 | } | |
2187 | ||
2188 | /** | |
2189 | * Deletes a message. | |
2190 | * | |
2191 | * This function does not verify any permissions. | |
2192 | * | |
2193 | * @param int $userid the user id of who we want to delete the message for (this may be done by the admin | |
2194 | * but will still seem as if it was by the user) | |
2195 | * @param int $messageid The message id | |
2196 | * @return bool | |
2197 | */ | |
2198 | public static function delete_message($userid, $messageid) { | |
14de10c4 | 2199 | global $DB, $USER; |
883ce421 | 2200 | |
14de10c4 MN |
2201 | if (!$DB->record_exists('messages', ['id' => $messageid])) { |
2202 | return false; | |
2203 | } | |
883ce421 MN |
2204 | |
2205 | // Check if the user has already deleted this message. | |
2206 | if (!$DB->record_exists('message_user_actions', ['userid' => $userid, | |
2207 | 'messageid' => $messageid, 'action' => self::MESSAGE_ACTION_DELETED])) { | |
2208 | $mua = new \stdClass(); | |
2209 | $mua->userid = $userid; | |
2210 | $mua->messageid = $messageid; | |
2211 | $mua->action = self::MESSAGE_ACTION_DELETED; | |
2212 | $mua->timecreated = time(); | |
2213 | $mua->id = $DB->insert_record('message_user_actions', $mua); | |
2214 | ||
2215 | // Trigger event for deleting a message. | |
14de10c4 MN |
2216 | \core\event\message_deleted::create_from_ids($userid, $USER->id, |
2217 | $messageid, $mua->id)->trigger(); | |
883ce421 MN |
2218 | |
2219 | return true; | |
2220 | } | |
2221 | ||
2222 | return false; | |
2223 | } | |
2224 | ||
2225 | /** | |
2226 | * Returns the conversation between two users. | |
2227 | * | |
b2cd17e6 | 2228 | * @param array $userids |
883ce421 MN |
2229 | * @return int|bool The id of the conversation, false if not found |
2230 | */ | |
b2cd17e6 | 2231 | public static function get_conversation_between_users(array $userids) { |
883ce421 MN |
2232 | global $DB; |
2233 | ||
0866b336 RW |
2234 | $conversations = self::get_individual_conversations_between_users([$userids]); |
2235 | $conversation = $conversations[0]; | |
883ce421 | 2236 | |
0866b336 | 2237 | if ($conversation) { |
883ce421 MN |
2238 | return $conversation->id; |
2239 | } | |
2240 | ||
2241 | return false; | |
2242 | } | |
2243 | ||
0866b336 RW |
2244 | /** |
2245 | * Returns the conversations between sets of users. | |
2246 | * | |
2247 | * The returned array of results will be in the same order as the requested | |
2248 | * arguments, null will be returned if there is no conversation for that user | |
2249 | * pair. | |
2250 | * | |
2251 | * For example: | |
2252 | * If we have 6 users with ids 1, 2, 3, 4, 5, 6 where only 2 conversations | |
2253 | * exist. One between 1 and 2 and another between 5 and 6. | |
2254 | * | |
2255 | * Then if we call: | |
2256 | * $conversations = get_individual_conversations_between_users([[1,2], [3,4], [5,6]]); | |
2257 | * | |
2258 | * The conversations array will look like: | |
2259 | * [<conv_record>, null, <conv_record>]; | |
2260 | * | |
2261 | * Where null is returned for the pairing of [3, 4] since no record exists. | |
2262 | * | |
2263 | * @param array $useridsets An array of arrays where the inner array is the set of user ids | |
2264 | * @return stdClass[] Array of conversation records | |
2265 | */ | |
2266 | public static function get_individual_conversations_between_users(array $useridsets) : array { | |
2267 | global $DB; | |
2268 | ||
2269 | if (empty($useridsets)) { | |
2270 | return []; | |
2271 | } | |
2272 | ||
2273 | $hashes = array_map(function($userids) { | |
2274 | return helper::get_conversation_hash($userids); | |
2275 | }, $useridsets); | |
2276 | ||
2277 | list($inorequalsql, $params) = $DB->get_in_or_equal($hashes); | |
2278 | array_unshift($params, self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL); | |
2279 | $where = "type = ? AND convhash ${inorequalsql}"; | |
2280 | $conversations = array_fill(0, count($hashes), null); | |
2281 | $records = $DB->get_records_select('message_conversations', $where, $params); | |
2282 | ||
2283 | foreach (array_values($records) as $record) { | |
2284 | $index = array_search($record->convhash, $hashes); | |
2285 | if ($index !== false) { | |
2286 | $conversations[$index] = $record; | |
2287 | } | |
2288 | } | |
2289 | ||
2290 | return $conversations; | |
2291 | } | |
2292 | ||
883ce421 MN |
2293 | /** |
2294 | * Creates a conversation between two users. | |
2295 | * | |
a66ae849 | 2296 | * @deprecated since 3.6 |
b2cd17e6 | 2297 | * @param array $userids |
883ce421 MN |
2298 | * @return int The id of the conversation |
2299 | */ | |
b2cd17e6 | 2300 | public static function create_conversation_between_users(array $userids) { |
a66ae849 MN |
2301 | debugging('\core_message\api::create_conversation_between_users is deprecated, please use ' . |
2302 | '\core_message\api::create_conversation instead.', DEBUG_DEVELOPER); | |
2303 | ||
f2ac0a3e MN |
2304 | // This method was always used for individual conversations. |
2305 | $conversation = self::create_conversation(self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $userids); | |
2306 | ||
2307 | return $conversation->id; | |
2308 | } | |
2309 | ||
2310 | /** | |
2311 | * Creates a conversation with selected users and messages. | |
2312 | * | |
2313 | * @param int $type The type of conversation | |
2314 | * @param int[] $userids The array of users to add to the conversation | |
76540bec MN |
2315 | * @param string|null $name The name of the conversation |
2316 | * @param int $enabled Determines if the conversation is created enabled or disabled | |
2317 | * @param string|null $component Defines the Moodle component which the conversation belongs to, if any | |
2318 | * @param string|null $itemtype Defines the type of the component | |
2319 | * @param int|null $itemid The id of the component | |
2320 | * @param int|null $contextid The id of the context | |
f2ac0a3e MN |
2321 | * @return \stdClass |
2322 | */ | |
76540bec MN |
2323 | public static function create_conversation(int $type, array $userids, string $name = null, |
2324 | int $enabled = self::MESSAGE_CONVERSATION_ENABLED, string $component = null, | |
2325 | string $itemtype = null, int $itemid = null, int $contextid = null) { | |
2326 | ||
883ce421 MN |
2327 | global $DB; |
2328 | ||
869eac82 MN |
2329 | $validtypes = [ |
2330 | self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, | |
2331 | self::MESSAGE_CONVERSATION_TYPE_GROUP | |
2332 | ]; | |
2333 | ||
2334 | if (!in_array($type, $validtypes)) { | |
2335 | throw new \moodle_exception('An invalid conversation type was specified.'); | |
2336 | } | |
2337 | ||
f2ac0a3e MN |
2338 | // Sanity check. |
2339 | if ($type == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) { | |
2340 | if (count($userids) > 2) { | |
2341 | throw new \moodle_exception('An individual conversation can not have more than two users.'); | |
2342 | } | |
2343 | } | |
2344 | ||
883ce421 | 2345 | $conversation = new \stdClass(); |
f2ac0a3e MN |
2346 | $conversation->type = $type; |
2347 | $conversation->name = $name; | |
2348 | $conversation->convhash = null; | |
2349 | if ($type == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) { | |
2350 | $conversation->convhash = helper::get_conversation_hash($userids); | |
2351 | } | |
76540bec MN |
2352 | $conversation->component = $component; |
2353 | $conversation->itemtype = $itemtype; | |
2354 | $conversation->itemid = $itemid; | |
2355 | $conversation->contextid = $contextid; | |
2356 | $conversation->enabled = $enabled; | |
883ce421 | 2357 | $conversation->timecreated = time(); |
76540bec | 2358 | $conversation->timemodified = $conversation->timecreated; |
883ce421 MN |
2359 | $conversation->id = $DB->insert_record('message_conversations', $conversation); |
2360 | ||
f2ac0a3e MN |
2361 | // Add users to this conversation. |
2362 | $arrmembers = []; | |
b2cd17e6 MN |
2363 | foreach ($userids as $userid) { |
2364 | $member = new \stdClass(); | |
2365 | $member->conversationid = $conversation->id; | |
2366 | $member->userid = $userid; | |
2367 | $member->timecreated = time(); | |
f2ac0a3e MN |
2368 | $member->id = $DB->insert_record('message_conversation_members', $member); |
2369 | ||
2370 | $arrmembers[] = $member; | |
b2cd17e6 | 2371 | } |
883ce421 | 2372 | |
f2ac0a3e MN |
2373 | $conversation->members = $arrmembers; |
2374 | ||
2375 | return $conversation; | |
883ce421 | 2376 | } |
0b3eadcd | 2377 | |
e66b915c MN |
2378 | /** |
2379 | * Checks if a user can create a group conversation. | |
2380 | * | |
2381 | * @param int $userid The id of the user attempting to create the conversation | |
2382 | * @param \context $context The context they are creating the conversation from, most likely course context | |
2383 | * @return bool | |
2384 | */ | |
2385 | public static function can_create_group_conversation(int $userid, \context $context) : bool { | |
2386 | global $CFG; | |
2387 | ||
2388 | // If we can't message at all, then we can't create a conversation. | |
2389 | if (empty($CFG->messaging)) { | |
2390 | return false; | |
2391 | } | |
2392 | ||
2393 | // We need to check they have the capability to create the conversation. | |
2394 | return has_capability('moodle/course:creategroupconversations', $context, $userid); | |
883ce421 | 2395 | } |
0b3eadcd | 2396 | |
0d203bbf MN |
2397 | /** |
2398 | * Checks if a user can create a contact request. | |
2399 | * | |
2400 | * @param int $userid The id of the user who is creating the contact request | |
2401 | * @param int $requesteduserid The id of the user being requested | |
2402 | * @return bool | |
2403 | */ | |
2404 | public static function can_create_contact(int $userid, int $requesteduserid) : bool { | |
2405 | global $CFG; | |
2406 | ||
2407 | // If we can't message at all, then we can't create a contact. | |
2408 | if (empty($CFG->messaging)) { | |
2409 | return false; | |
2410 | } | |
2411 | ||
2412 | // If we can message anyone on the site then we can create a contact. | |
2413 | if ($CFG->messagingallusers) { | |
2414 | return true; | |
2415 | } | |
2416 | ||
2417 | // We need to check if they are in the same course. | |
2418 | return enrol_sharing_course($userid, $requesteduserid); | |
2419 | } | |
2420 | ||
0b3eadcd MN |
2421 | /** |
2422 | * Handles creating a contact request. | |
2423 | * | |
2424 | * @param int $userid The id of the user who is creating the contact request | |
2425 | * @param int $requesteduserid The id of the user being requested | |
0866b336 | 2426 | * @return \stdClass the request |
0b3eadcd | 2427 | */ |
0866b336 | 2428 | public static function create_contact_request(int $userid, int $requesteduserid) : \stdClass { |
0b3eadcd MN |
2429 | global $DB; |
2430 | ||
2431 | $request = new \stdClass(); | |
2432 | $request->userid = $userid; | |
2433 | $request->requesteduserid = $requesteduserid; | |
2434 | $request->timecreated = time(); | |
2435 | ||
0866b336 RW |
2436 | $request->id = $DB->insert_record('message_contact_requests', $request); |
2437 | ||
2438 | return $request; | |
0b3eadcd MN |
2439 | } |
2440 | ||
2441 | ||
2442 | /** | |
2443 | * Handles confirming a contact request. | |
2444 | * | |
2445 | * @param int $userid The id of the user who created the contact request | |
2446 | * @param int $requesteduserid The id of the user confirming the request | |
2447 | */ | |
2448 | public static function confirm_contact_request(int $userid, int $requesteduserid) { | |
2449 | global $DB; | |
2450 | ||
2451 | if ($request = $DB->get_record('message_contact_requests', ['userid' => $userid, | |
2452 | 'requesteduserid' => $requesteduserid])) { | |
2453 | self::add_contact($userid, $requesteduserid); | |
2454 | ||
2455 | $DB->delete_records('message_contact_requests', ['id' => $request->id]); | |
2456 | } | |
2457 | } | |
2458 | ||
2459 | /** | |
2460 | * Handles declining a contact request. | |
2461 | * | |
2462 | * @param int $userid The id of the user who created the contact request | |
2463 | * @param int $requesteduserid The id of the user declining the request | |
2464 | */ | |
2465 | public static function decline_contact_request(int $userid, int $requesteduserid) { | |
2466 | global $DB; | |
2467 | ||
2468 | if ($request = $DB->get_record('message_contact_requests', ['userid' => $userid, | |
2469 | 'requesteduserid' => $requesteduserid])) { | |
2470 | $DB->delete_records('message_contact_requests', ['id' => $request->id]); | |
2471 | } | |
2472 | } | |
2473 | ||
2474 | /** | |
2475 | * Handles returning the contact requests for a user. | |
2476 | * | |
2477 | * This also includes the user data necessary to display information | |
2478 | * about the user. | |
2479 | * | |
2480 | * It will not include blocked users. | |
2481 | * | |
2482 | * @param int $userid | |
accd6482 MN |
2483 | * @param int $limitfrom |
2484 | * @param int $limitnum | |
0b3eadcd MN |
2485 | * @return array The list of contact requests |
2486 | */ | |
accd6482 | 2487 | public static function get_contact_requests(int $userid, int $limitfrom = 0, int $limitnum = 0) : array { |
0b3eadcd MN |
2488 | global $DB; |
2489 | ||
daa33803 MN |
2490 | $sql = "SELECT mcr.userid |
2491 | FROM {message_contact_requests} mcr | |
0b3eadcd | 2492 | LEFT JOIN {message_users_blocked} mub |
daa33803 | 2493 | ON (mub.userid = ? AND mub.blockeduserid = mcr.userid) |
0b3eadcd | 2494 | WHERE mcr.requesteduserid = ? |
0b3eadcd | 2495 | AND mub.id is NULL |
accd6482 MN |
2496 | ORDER BY mcr.timecreated ASC"; |
2497 | if ($contactrequests = $DB->get_records_sql($sql, [$userid, $userid], $limitfrom, $limitnum)) { | |
daa33803 MN |
2498 | $userids = array_keys($contactrequests); |
2499 | return helper::get_member_info($userid, $userids); | |
2500 | } | |
0b3eadcd | 2501 | |
daa33803 | 2502 | return []; |
0b3eadcd MN |
2503 | } |
2504 | ||
0866b336 RW |
2505 | /** |
2506 | * Count how many contact requests the user has received. | |
2507 | * | |
2508 | * @param \stdClass $user The user to fetch contact requests for | |
2509 | * @return int The count | |
2510 | */ | |
2511 | public static function count_received_contact_requests(\stdClass $user) : int { | |
2512 | global $DB; | |
2513 | return $DB->count_records('message_contact_requests', ['requesteduserid' => $user->id]); | |
2514 | } | |
2515 | ||
0b3eadcd MN |
2516 | /** |
2517 | * Handles adding a contact. | |
2518 | * | |
2519 | * @param int $userid The id of the user who requested to be a contact | |
2520 | * @param int $contactid The id of the contact | |
2521 | */ | |
2522 | public static function add_contact(int $userid, int $contactid) { | |
2523 | global $DB; | |
2524 | ||
2525 | $messagecontact = new \stdClass(); | |
2526 | $messagecontact->userid = $userid; | |
2527 | $messagecontact->contactid = $contactid; | |
2528 | $messagecontact->timecreated = time(); | |
2529 | $messagecontact->id = $DB->insert_record('message_contacts', $messagecontact); | |
2530 | ||
2531 | $eventparams = [ | |
2532 | 'objectid' => $messagecontact->id, | |
2533 | 'userid' => $userid, | |
2534 | 'relateduserid' => $contactid, | |
2535 | 'context' => \context_user::instance($userid) | |
2536 | ]; | |
2537 | $event = \core\event\message_contact_added::create($eventparams); | |
2538 | $event->add_record_snapshot('message_contacts', $messagecontact); | |
2539 | $event->trigger(); | |
2540 | } | |
2541 | ||
2542 | /** | |
2543 | * Handles removing a contact. | |
2544 | * | |
2545 | * @param int $userid The id of the user who is removing a user as a contact | |
2546 | * @param int $contactid The id of the user to be removed as a contact | |
2547 | */ | |
2548 | public static function remove_contact(int $userid, int $contactid) { | |
2549 | global $DB; | |
2550 | ||
2551 | if ($contact = self::get_contact($userid, $contactid)) { | |
2552 | $DB->delete_records('message_contacts', ['id' => $contact->id]); | |
2553 | ||
2554 | $event = \core\event\message_contact_removed::create(array( | |
2555 | 'objectid' => $contact->id, | |
2556 | 'userid' => $userid, | |
2557 | 'relateduserid' => $contactid, | |
2558 | 'context' => \context_user::instance($userid) | |
2559 | )); | |
2560 | $event->add_record_snapshot('message_contacts', $contact); | |
2561 | $event->trigger(); | |
2562 | } | |
2563 | } | |
2564 | ||
2565 | /** | |
2566 | * Handles blocking a user. | |
2567 | * | |
2568 | * @param int $userid The id of the user who is blocking | |
2569 | * @param int $usertoblockid The id of the user being blocked | |
2570 | */ | |
2571 | public static function block_user(int $userid, int $usertoblockid) { | |
2572 | global $DB; | |
2573 | ||
2574 | $blocked = new \stdClass(); | |
2575 | $blocked->userid = $userid; | |
2576 | $blocked->blockeduserid = $usertoblockid; | |
2577 | $blocked->timecreated = time(); | |
2578 | $blocked->id = $DB->insert_record('message_users_blocked', $blocked); | |
2579 | ||
2580 | // Trigger event for blocking a contact. | |
2581 | $event = \core\event\message_user_blocked::create(array( | |
2582 | 'objectid' => $blocked->id, | |
2583 | 'userid' => $userid, | |
2584 | 'relateduserid' => $usertoblockid, | |
2585 | 'context' => \context_user::instance($userid) | |
2586 | )); | |
2587 | $event->add_record_snapshot('message_users_blocked', $blocked); | |
2588 | $event->trigger(); | |
2589 | } | |
2590 | ||
2591 | /** | |
2592 | * Handles unblocking a user. | |
2593 | * | |
2594 | * @param int $userid The id of the user who is unblocking | |
2595 | * @param int $usertounblockid The id of the user being unblocked | |
2596 | */ | |
2597 | public static function unblock_user(int $userid, int $usertounblockid) { | |
2598 | global $DB; | |
2599 | ||
2600 | if ($blockeduser = $DB->get_record('message_users_blocked', | |
2601 | ['userid' => $userid, 'blockeduserid' => $usertounblockid])) { | |
2602 | $DB->delete_records('message_users_blocked', ['id' => $blockeduser->id]); | |
2603 | ||
2604 | // Trigger event for unblocking a contact. | |
2605 | $event = \core\event\message_user_unblocked::create(array( | |
2606 | 'objectid' => $blockeduser->id, | |
2607 | 'userid' => $userid, | |
2608 | 'relateduserid' => $usertounblockid, | |
2609 | 'context' => \context_user::instance($userid) | |
2610 | )); | |
2611 | $event->add_record_snapshot('message_users_blocked', $blockeduser); | |
2612 | $event->trigger(); | |
2613 | } | |
2614 | } | |
2615 | ||
2616 | /** | |
2617 | * Checks if users are already contacts. | |
2618 | * | |
2619 | * @param int $userid The id of one of the users | |
2620 | * @param int $contactid The id of the other user | |
2621 | * @return bool Returns true if they are a contact, false otherwise | |
2622 | */ | |
2623 | public static function is_contact(int $userid, int $contactid) : bool { | |
2624 | global $DB; | |
2625 | ||
2626 | $sql = "SELECT id | |
2627 | FROM {message_contacts} mc | |
2628 | WHERE (mc.userid = ? AND mc.contactid = ?) | |
2629 | OR (mc.userid = ? AND mc.contactid = ?)"; | |
2630 | return $DB->record_exists_sql($sql, [$userid, $contactid, $contactid, $userid]); | |
2631 | } | |
2632 | ||
2633 | /** | |
2634 | * Returns the row in the database table message_contacts that represents the contact between two people. | |
2635 | * | |
2636 | * @param int $userid The id of one of the users | |
2637 | * @param int $contactid The id of the other user | |
2638 | * @return mixed A fieldset object containing the record, false otherwise | |
2639 | */ | |
2640 | public static function get_contact(int $userid, int $contactid) { | |
2641 | global $DB; | |
2642 | ||
2643 | $sql = "SELECT mc.* | |
2644 | FROM {message_contacts} mc | |
2645 | WHERE (mc.userid = ? AND mc.contactid = ?) | |
2646 | OR (mc.userid = ? AND mc.contactid = ?)"; | |
2647 | return $DB->get_record_sql($sql, [$userid, $contactid, $contactid, $userid]); | |
2648 | } | |
2649 | ||
2650 | /** | |
2651 | * Checks if a user is already blocked. | |
2652 | * | |
0b3eadcd MN |
2653 | * @param int $userid |
2654 | * @param int $blockeduserid | |
2655 | * @return bool Returns true if they are a blocked, false otherwise | |
2656 | */ | |
2657 | public static function is_blocked(int $userid, int $blockeduserid) : bool { | |
2658 | global $DB; | |
2659 | ||
2660 | return $DB->record_exists('message_users_blocked', ['userid' => $userid, 'blockeduserid' => $blockeduserid]); | |
2661 | } | |
2662 | ||
0866b336 RW |
2663 | /** |
2664 | * Get contact requests between users. | |
2665 | * | |
2666 | * @param int $userid The id of the user who is creating the contact request | |
2667 | * @param int $requesteduserid The id of the user being requested | |
2668 | * @return \stdClass[] | |
2669 | */ | |
2670 | public static function get_contact_requests_between_users(int $userid, int $requesteduserid) : array { | |
2671 | global $DB; | |
2672 | ||
2673 | $sql = "SELECT * | |
2674 | FROM {message_contact_requests} mcr | |
2675 | WHERE (mcr.userid = ? AND mcr.requesteduserid = ?) | |
2676 | OR (mcr.userid = ? AND mcr.requesteduserid = ?)"; | |
2677 | return $DB->get_records_sql($sql, [$userid, $requesteduserid, $requesteduserid, $userid]); | |
2678 | } | |
2679 | ||
0b3eadcd MN |
2680 | /** |
2681 | * Checks if a contact request already exists between users. | |
2682 | * | |
2683 | * @param int $userid The id of the user who is creating the contact request | |
2684 | * @param int $requesteduserid The id of the user being requested | |
2685 | * @return bool Returns true if a contact request exists, false otherwise | |
2686 | */ | |
2687 | public static function does_contact_request_exist(int $userid, int $requesteduserid) : bool { | |
2688 | global $DB; | |
2689 | ||
2690 | $sql = "SELECT id | |
2691 | FROM {message_contact_requests} mcr | |
2692 | WHERE (mcr.userid = ? AND mcr.requesteduserid = ?) | |
2693 | OR (mcr.userid = ? AND mcr.requesteduserid = ?)"; | |
2694 | return $DB->record_exists_sql($sql, [$userid, $requesteduserid, $requesteduserid, $userid]); | |
2695 | } | |
08cb8a34 MN |
2696 | |
2697 | /** | |
2698 | * Checks if a user is already in a conversation. | |
2699 | * | |
2700 | * @param int $userid The id of the user we want to check if they are in a group | |
2701 | * @param int $conversationid The id of the conversation | |
2702 | * @return bool Returns true if a contact request exists, false otherwise | |
2703 | */ | |
2704 | public static function is_user_in_conversation(int $userid, int $conversationid) : bool { | |
2705 | global $DB; | |
2706 | ||
2707 | return $DB->record_exists('message_conversation_members', ['conversationid' => $conversationid, | |
2708 | 'userid' => $userid]); | |
7983fb83 SA |
2709 | } |
2710 | ||
2711 | /** | |
2712 | * Checks if the sender can message the recipient. | |
2713 | * | |
9f82758c JD |
2714 | * @param int $recipientid |
2715 | * @param int $senderid | |
7983fb83 SA |
2716 | * @return bool true if recipient hasn't blocked sender and sender can contact to recipient, false otherwise. |
2717 | */ | |
9f82758c JD |
2718 | protected static function can_contact_user(int $recipientid, int $senderid) : bool { |
2719 | if (has_capability('moodle/site:messageanyuser', \context_system::instance(), $senderid)) { | |
7983fb83 SA |
2720 | // The sender has the ability to contact any user across the entire site. |
2721 | return true; | |
2722 | } | |
2723 | ||
2724 | // The initial value of $cancontact is null to indicate that a value has not been determined. | |
2725 | $cancontact = null; | |
2726 | ||
9f82758c | 2727 | if (self::is_blocked($recipientid, $senderid)) { |
7983fb83 SA |
2728 | // The recipient has specifically blocked this sender. |
2729 | $cancontact = false; | |
2730 | } | |
2731 | ||
2732 | $sharedcourses = null; | |
2733 | if (null === $cancontact) { | |
2734 | // There are three user preference options: | |
2735 | // - Site: Allow anyone not explicitly blocked to contact me; | |
2736 | // - Course members: Allow anyone I am in a course with to contact me; and | |
2737 | // - Contacts: Only allow my contacts to contact me. | |
2738 | // | |
2739 | // The Site option is only possible when the messagingallusers site setting is also enabled. | |
2740 | ||
9f82758c | 2741 | $privacypreference = self::get_user_privacy_messaging_preference($recipientid); |
7983fb83 SA |
2742 | if (self::MESSAGE_PRIVACY_SITE === $privacypreference) { |
2743 | // The user preference is to allow any user to contact them. | |
2744 | // No need to check anything else. | |
2745 | $cancontact = true; | |
2746 | } else { | |
2747 | // This user only allows their own contacts, and possibly course peers, to contact them. | |
2748 | // If the users are contacts then we can avoid the more expensive shared courses check. | |
9f82758c | 2749 | $cancontact = self::is_contact($senderid, $recipientid); |
7983fb83 SA |
2750 | |
2751 | if (!$cancontact && self::MESSAGE_PRIVACY_COURSEMEMBER === $privacypreference) { | |
2752 | // The users are not contacts and the user allows course member messaging. | |
2753 | // Check whether these two users share any course together. | |
9f82758c | 2754 | $sharedcourses = enrol_get_shared_courses($recipientid, $senderid, true); |
7983fb83 SA |
2755 | $cancontact = (!empty($sharedcourses)); |
2756 | } | |
2757 | } | |
2758 | } | |
2759 | ||
2760 | if (false === $cancontact) { | |
2761 | // At the moment the users cannot contact one another. | |
2762 | // Check whether the messageanyuser capability applies in any of the shared courses. | |
2763 | // This is intended to allow teachers to message students regardless of message settings. | |
2764 | ||
2765 | // Note: You cannot use empty($sharedcourses) here because this may be an empty array. | |
2766 | if (null === $sharedcourses) { | |
9f82758c | 2767 | $sharedcourses = enrol_get_shared_courses($recipientid, $senderid, true); |
7983fb83 SA |
2768 | } |
2769 | ||
2770 | foreach ($sharedcourses as $course) { | |
2771 | // Note: enrol_get_shared_courses will preload any shared context. | |
9f82758c | 2772 | if (has_capability('moodle/site:messageanyuser', \context_course::instance($course->id), $senderid)) { |
7983fb83 SA |
2773 | $cancontact = true; |
2774 | break; | |
2775 | } | |
2776 | } | |
2777 | } | |
08cb8a34 | 2778 | |
7983fb83 | 2779 | return $cancontact; |
08cb8a34 | 2780 | } |
67b81351 | 2781 | |
623048af AA |
2782 | /** |
2783 | * Add some new members to an existing conversation. | |
2784 | * | |
2785 | * @param array $userids User ids array to add as members. | |
2786 | * @param int $convid The conversation id. Must exists. | |
2787 | * @throws \dml_missing_record_exception If convid conversation doesn't exist | |
2788 | * @throws \dml_exception If there is a database error | |
83e3a4fe | 2789 | * @throws \moodle_exception If trying to add a member(s) to a non-group conversation |
623048af AA |
2790 | */ |
2791 | public static function add_members_to_conversation(array $userids, int $convid) { | |
2792 | global $DB; | |
2793 | ||
2794 | $conversation = $DB->get_record('message_conversations', ['id' => $convid], '*', MUST_EXIST); | |
2795 | ||
83e3a4fe MN |
2796 | // We can only add members to a group conversation. |
2797 | if ($conversation->type != self::MESSAGE_CONVERSATION_TYPE_GROUP) { | |
2798 | throw new \moodle_exception('You can not add members to a non-group conversation.'); | |
2799 | } | |
2800 | ||
623048af AA |
2801 | // Be sure we are not trying to add a non existing user to the conversation. Work only with existing users. |
2802 | list($useridcondition, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); | |
4abe3d25 | 2803 | $existingusers = $DB->get_fieldset_select('user', 'id', "id $useridcondition", $params); |
623048af AA |
2804 | |
2805 | // Be sure we are not adding a user is already member of the conversation. Take all the members. | |
2806 | $memberuserids = array_values($DB->get_records_menu( | |
2807 | 'message_conversation_members', ['conversationid' => $convid], 'id', 'id, userid') | |
2808 | ); | |
2809 | ||
2810 | // Work with existing new members. | |
2811 | $members = array(); | |
2812 | $newuserids = array_diff($existingusers, $memberuserids); | |
2813 | foreach ($newuserids as $userid) { | |
2814 | $member = new \stdClass(); | |
2815 | $member->conversationid = $convid; | |
2816 | $member->userid = $userid; | |
2817 | $member->timecreated = time(); | |
2818 | $members[] = $member; | |
2819 | } | |
2820 | ||
623048af | 2821 | $DB->insert_records('message_conversation_members', $members); |
623048af AA |
2822 | } |
2823 | ||
2824 | /** | |
2825 | * Remove some members from an existing conversation. | |
2826 | * | |
2827 | * @param array $userids The user ids to remove from conversation members. | |
2828 | * @param int $convid The conversation id. Must exists. | |
2829 | * @throws \dml_exception | |
83e3a4fe | 2830 | * @throws \moodle_exception If trying to remove a member(s) from a non-group conversation |
623048af AA |
2831 | */ |
2832 | public static function remove_members_from_conversation(array $userids, int $convid) { | |
2833 | global $DB; | |
2834 | ||
2835 | $conversation = $DB->get_record('message_conversations', ['id' => $convid], '*', MUST_EXIST); | |
2836 | ||
83e3a4fe MN |
2837 | if ($conversation->type != self::MESSAGE_CONVERSATION_TYPE_GROUP) { |
2838 | throw new \moodle_exception('You can not remove members from a non-group conversation.'); | |
2839 | } | |
2840 | ||
623048af AA |
2841 | list($useridcondition, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); |
2842 | $params['convid'] = $convid; | |
2843 | ||
623048af AA |
2844 | $DB->delete_records_select('message_conversation_members', |
2845 | "conversationid = :convid AND userid $useridcondition", $params); | |
623048af AA |
2846 | } |
2847 | ||
2848 | /** | |
2849 | * Count conversation members. | |
2850 | * | |
2851 | * @param int $convid The conversation id. | |
2852 | * @return int Number of conversation members. | |
2853 | * @throws \dml_exception | |
2854 | */ | |
2855 | public static function count_conversation_members(int $convid) : int { | |
2856 | global $DB; | |
08cb8a34 | 2857 | |
623048af | 2858 | return $DB->count_records('message_conversation_members', ['conversationid' => $convid]); |
08cb8a34 | 2859 | } |
55fda006 | 2860 | |
f0e137c5 MN |
2861 | /** |
2862 | * Checks whether or not a conversation area is enabled. | |
2863 | * | |
2864 | * @param string $component Defines the Moodle component which the area was added to. | |
2865 | * @param string $itemtype Defines the type of the component. | |
2866 | * @param int $itemid The id of the component. | |
2867 | * @param int $contextid The id of the context. | |
2868 | * @return bool Returns if a conversation area exists and is enabled, false otherwise | |
2869 | */ | |
2870 | public static function is_conversation_area_enabled(string $component, string $itemtype, int $itemid, int $contextid) : bool { | |
2871 | global $DB; | |
2872 | ||
76540bec | 2873 | return $DB->record_exists('message_conversations', |
f0e137c5 MN |
2874 | [ |
2875 | 'itemid' => $itemid, | |
2876 | 'contextid' => $contextid, | |
2877 | 'component' => $component, | |
2878 | 'itemtype' => $itemtype, | |
76540bec | 2879 | 'enabled' => self::MESSAGE_CONVERSATION_ENABLED |
f0e137c5 MN |
2880 | ] |
2881 | ); | |
2882 | } | |
2883 | ||
e7f46714 | 2884 | /** |
76540bec | 2885 | * Get conversation by area. |
e7f46714 | 2886 | * |
2887 | * @param string $component Defines the Moodle component which the area was added to. | |
2888 | * @param string $itemtype Defines the type of the component. | |
76540bec MN |
2889 | * @param int $itemid The id of the component. |
2890 | * @param int $contextid The id of the context. | |
2891 | * @return \stdClass | |
e7f46714 | 2892 | */ |
76540bec | 2893 | public static function get_conversation_by_area(string $component, string $itemtype, int $itemid, int $contextid) { |
e7f46714 | 2894 | global $DB; |
2895 | ||
76540bec | 2896 | return $DB->get_record('message_conversations', |
f0e137c5 MN |
2897 | [ |
2898 | 'itemid' => $itemid, | |
2899 | 'contextid' => $contextid, | |
2900 | 'component' => $component, | |
2901 | 'itemtype' => $itemtype | |
2902 | ] | |
2903 | ); | |
e7f46714 | 2904 | } |
2905 | ||
2906 | /** | |
76540bec | 2907 | * Enable a conversation. |
e7f46714 | 2908 | * |
76540bec | 2909 | * @param int $conversationid The id of the conversation. |
e7f46714 | 2910 | * @return void |
2911 | */ | |
76540bec | 2912 | public static function enable_conversation(int $conversationid) { |
e7f46714 | 2913 | global $DB; |
2914 | ||
76540bec MN |
2915 | $conversation = new \stdClass(); |
2916 | $conversation->id = $conversationid; | |
2917 | $conversation->enabled = self::MESSAGE_CONVERSATION_ENABLED; | |
2918 | $conversation->timemodified = time(); | |
2919 | $DB->update_record('message_conversations', $conversation); | |
e7f46714 | 2920 | } |
2921 | ||
2922 | /** | |
76540bec | 2923 | * Disable a conversation. |
e7f46714 | 2924 | * |
76540bec | 2925 | * @param int $conversationid The id of the conversation. |
e7f46714 | 2926 | * @return void |
2927 | */ | |
76540bec | 2928 | public static function disable_conversation(int $conversationid) { |
e7f46714 | 2929 | global $DB; |
2930 | ||
76540bec MN |
2931 | $conversation = new \stdClass(); |
2932 | $conversation->id = $conversationid; | |
2933 | $conversation->enabled = self::MESSAGE_CONVERSATION_DISABLED; | |
2934 | $conversation->timemodified = time(); | |
2935 | $DB->update_record('message_conversations', $conversation); | |
e7f46714 | 2936 | } |
2937 | ||
2938 | /** | |
2939 | * Update the name of a conversation. | |
2940 | * | |
76540bec | 2941 | * @param int $conversationid The id of a conversation. |
e7f46714 | 2942 | * @param string $name The main name of the area |
2943 | * @return void | |
2944 | */ | |
2945 | public static function update_conversation_name(int $conversationid, string $name) { | |
2946 | global $DB; | |
2947 | ||
2948 | if ($conversation = $DB->get_record('message_conversations', array('id' => $conversationid))) { | |
2949 | if ($name <> $conversation->name) { | |
2950 | $conversation->name = $name; | |
f0e137c5 | 2951 | $conversation->timemodified = time(); |
e7f46714 | 2952 | $DB->update_record('message_conversations', $conversation); |
2953 | } | |
2954 | } | |
2955 | } | |
6ecd6e5b MN |
2956 | |
2957 | /** | |
2958 | * Returns a list of conversation members. | |
2959 | * | |
2960 | * @param int $userid The user we are returning the conversation members for, used by helper::get_member_info. | |
2961 | * @param int $conversationid The id of the conversation | |
2962 | * @param bool $includecontactrequests Do we want to include contact requests with this data? | |
663ccd58 | 2963 | * @param bool $includeprivacyinfo Do we want to include privacy requests with this data? |
6ecd6e5b MN |
2964 | * @param int $limitfrom |
2965 | * @param int $limitnum | |
2966 | * @return array | |
2967 | */ | |
2968 | public static function get_conversation_members(int $userid, int $conversationid, bool $includecontactrequests = false, | |
663ccd58 RW |
2969 | bool $includeprivacyinfo = false, int $limitfrom = 0, |
2970 | int $limitnum = 0) : array { | |
6ecd6e5b MN |
2971 | global $DB; |
2972 | ||
2973 | if ($members = $DB->get_records('message_conversation_members', ['conversationid' => $conversationid], | |
2974 | 'timecreated ASC, id ASC', 'userid', $limitfrom, $limitnum)) { | |
2975 | $userids = array_keys($members); | |
663ccd58 | 2976 | $members = helper::get_member_info($userid, $userids, $includecontactrequests, $includeprivacyinfo); |
6ecd6e5b MN |
2977 | |
2978 | return $members; | |
2979 | } | |
2980 | ||
2981 | return []; | |
2982 | } | |
33022ee1 JD |
2983 | |
2984 | /** | |
2985 | * Get the unread counts for all conversations for the user, sorted by type, and including favourites. | |
2986 | * | |
2987 | * @param int $userid the id of the user whose conversations we'll check. | |
2988 | * @return array the unread counts for each conversation, indexed by type. | |
2989 | */ | |
2990 | public static function get_unread_conversation_counts(int $userid) : array { | |
2991 | global $DB; | |
2992 | ||
2993 | // Get all conversations the user is in, and check unread. | |
2994 | $unreadcountssql = 'SELECT conv.id, conv.type, indcounts.unreadcount | |
2995 | FROM {message_conversations} conv | |
2996 | INNER JOIN ( | |
2997 | SELECT m.conversationid, count(m.id) as unreadcount | |
2998 | FROM {messages} m | |
2999 | INNER JOIN {message_conversations} mc | |
3000 | ON mc.id = m.conversationid | |
3001 | INNER JOIN {message_conversation_members} mcm | |
3002 | ON m.conversationid = mcm.conversationid | |
3003 | LEFT JOIN {message_user_actions} mua | |
3004 | ON (mua.messageid = m.id AND mua.userid = ? AND | |
3005 | (mua.action = ? OR mua.action = ?)) | |
3006 | WHERE mcm.userid = ? | |
3007 | AND m.useridfrom != ? | |
3008 | AND mua.id is NULL | |
3009 | GROUP BY m.conversationid | |
3010 | ) indcounts | |
3011 | ON indcounts.conversationid = conv.id | |
3012 | WHERE conv.enabled = 1'; | |
3013 | ||
3014 | $unreadcounts = $DB->get_records_sql($unreadcountssql, [$userid, self::MESSAGE_ACTION_READ, self::MESSAGE_ACTION_DELETED, | |
3015 | $userid, $userid]); | |
3016 | ||
3017 | // Get favourites, so we can track these separately. | |
3018 | $service = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($userid)); | |
3019 | $favouriteconversations = $service->find_favourites_by_type('core_message', 'message_conversations'); | |
3020 | $favouriteconvids = array_flip(array_column($favouriteconversations, 'itemid')); | |
3021 | ||
3022 | // Assemble the return array. | |
3023 | $counts = ['favourites' => 0, 'types' => [ | |
3024 | self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, | |
3025 | self::MESSAGE_CONVERSATION_TYPE_GROUP => 0 | |
3026 | ]]; | |
3027 | foreach ($unreadcounts as $convid => $info) { | |
33022ee1 JD |
3028 | if (isset($favouriteconvids[$convid])) { |
3029 | $counts['favourites']++; | |
a5411535 | 3030 | continue; |
33022ee1 | 3031 | } |
a5411535 | 3032 | $counts['types'][$info->type]++; |
33022ee1 JD |
3033 | } |
3034 | ||
3035 | return $counts; | |
3036 | } | |
879e2bef | 3037 | } |