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