Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
DeleteService.php
1<?php
2
4
5use Bitrix\Disk\SystemUser;
14use Bitrix\Im\V2\Common\ContextCustomer;
30
32{
33 use ContextCustomer;
34
35 public const DELETE_NONE = 0; // cannot be deleted
36 public const DELETE_SOFT = 1; // replacement with the text "message deleted"
37 public const DELETE_HARD = 2; // complete removal if no one has read
38 public const DELETE_COMPLETE = 3; // unconditional deletion
39
40 private const MESSAGE_OWN_SELF = self::DELETE_NONE;
41 private const MESSAGE_OWN_OTHER = 4;
42
43 private const ROLE_USER = self::DELETE_SOFT;
44 private const ROLE_MANAGER = self::DELETE_HARD;
45 private const ROLE_OWNER = self::DELETE_COMPLETE;
46
47 public const EVENT_AFTER_MESSAGE_DELETE = 'OnAfterMessagesDelete';
48 public const OPTION_KEY_DELETE_AFTER = 'complete_delete_message_start_date';
53
54 private Message $message;
55 private Chat $chat;
56 private ?array $chatLastMessage = null;
57 private ?int $chatPrevMessageId = null;
58 private int $mode = self::MODE_AUTO;
59 private bool $needUpdateRecent = false;
60 private array $counters;
61 private array $lastMessageViewers;
62 private int $previousMessageId;
63
64 public function __construct(Message $message)
65 {
66 $this->message = $message;
67 Chat::cleanCache($this->message->getChatId());
68 $this->chat = Chat\ChatFactory::getInstance()->getChatById($this->message->getChatId());
69 }
70
71 public function setMessage(Message $message): self
72 {
73 $this->message = $message;
74 Chat::cleanCache($this->message->getChatId());
75 $this->chat = Chat\ChatFactory::getInstance()->getChatById($this->message->getChatId());
76
77 return $this;
78 }
79
84 public function setMode(int $mode): self
85 {
86 if (in_array($mode, [self::MODE_AUTO, self::MODE_SOFT, self::MODE_HARD, self::MODE_COMPLETE], true))
87 {
88 $this->mode = $mode;
89 }
90
91 return $this;
92 }
93
97 public function delete(): Result
98 {
99 $messageFields = $this->message->toArray();
100 $messageFields['PARAMS'] = $this->message->getParams()->toRestFormat();
101 $messageFields['CHAT_ENTITY_TYPE'] = $this->chat->getEntityType();
102 $messageFields['CHAT_ENTITY_ID'] = $this->chat->getEntityId();
103
104 if (!$this->mode)
105 {
106 $this->mode = $this->canDelete();
107 }
108
109 $files = $this->message->getFiles();
110
111 switch ($this->mode)
112 {
114 $result = $this->deleteSoft();
115 $this->fireEventAfterMessageDelete($messageFields);
116 break;
118 $this->getChatPreviousMessages();
119 $result = $this->deleteHard();
120 $this->fireEventAfterMessageDelete($messageFields, true);
121 break;
123 $this->getChatPreviousMessages();
124 $result = $this->deleteHard(true);
125 $this->fireEventAfterMessageDelete($messageFields, true);
126 break;
127 default:
129 }
130
131 if (Option::get('im', 'message_history_index'))
132 {
133 MessageIndexTable::delete($this->message->getId());
134 }
135
136 (new UrlService())->deleteUrlsByMessage($this->message);
137 foreach ($files as $file)
138 {
139 $file->getDiskFile()->delete(SystemUser::SYSTEM_USER_ID);
140 }
141
142 return $result;
143 }
144
154 public function canDelete(): int
155 {
156 if ($this->getContext()->getUser()->isSuperAdmin())
157 {
159 }
160
161 $userId = $this->getContext()->getUserId();
162
163 // not chat access
164 if (!$this->chat->hasAccess($userId))
165 {
166 return self::DELETE_NONE;
167 }
168
169 // get user role in this chat
170 $removerRole = self::ROLE_USER;
171 if ($userId === $this->chat->getAuthorId())
172 {
173 if ($this->chat->getType() !== Chat::IM_TYPE_PRIVATE)
174 {
175 $removerRole = self::ROLE_OWNER;
176 }
177 }
178 else
179 {
180 $relation = $this->chat->getSelfRelation();
181 if ($relation && $relation->getManager())
182 {
183 $removerRole = self::ROLE_MANAGER;
184 }
185 elseif ($relation->getUser()->getExternalAuthId() === Bot::EXTERNAL_AUTH_ID)
186 {
188 }
189 }
190
191 // determine the owner of the message
192 $messageOwner = self::MESSAGE_OWN_OTHER;
193 if ($messageAuthor = $this->message->getAuthor())
194 {
195 if ($messageAuthor->getId() === $userId)
196 {
197 $messageOwner = self::MESSAGE_OWN_SELF;
198 }
199 elseif($messageAuthor->getId() === $this->chat->getAuthorId())
200 {
201 $messageOwner = self::ROLE_OWNER;
202 }
203 else
204 {
205 $relations = $this->chat->getRelations(['USER_ID' => $messageAuthor->getId()]);
206 if ($user = $relations->getByUserId($messageAuthor->getId(), $this->chat->getChatId()))
207 {
208 $messageOwner = self::ROLE_USER;
209 if ($user->getManager())
210 {
211 $messageOwner = self::ROLE_MANAGER;
212 }
213 }
214 }
215 }
216
217 if ($removerRole <= $messageOwner)
218 {
219 return self::DELETE_NONE;
220 }
221
222 if (
223 $messageAuthor === self::ROLE_OWNER
224 && in_array($this->chat->getType(), [Chat::IM_TYPE_OPEN, Chat::IM_TYPE_CHANNEL], true)
225 )
226 {
228 }
229
230 // message was read by someone other than the author
231 if ($this->message->isViewedByOthers())
232 {
233 return self::DELETE_SOFT;
234 }
235
236 return self::DELETE_HARD;
237 }
238
239 private function deleteSoft(): Result
240 {
241 $date = FormatDate('FULL', $this->message->getDateCreate()->getTimestamp() + \CTimeZone::GetOffset());
242
243 $this->message->setMessage(Loc::getMessage('IM_MESSAGE_DELETED'));
244 $this->message->setMessageOut(Loc::getMessage('IM_MESSAGE_DELETED_OUT', ['#DATE#' => $date]));
245 $this->message->resetParams([
246 'IS_DELETED' => 'Y'
247 ]);
248 $this->message->save();
249 if (!$this->chat instanceof Chat\OpenLineChat)
250 {
251 Sync\Logger::getInstance()->add(
252 new Sync\Event(Sync\Event::DELETE_EVENT, Sync\Event::MESSAGE_ENTITY, $this->message->getId()),
253 fn () => $this->chat->getRelations()->getUserIds()
254 );
255 }
256
257 $this->sendPullMessage();
258
259 return new Result();
260 }
261
262 private function deleteHard($removeAny = false): Result
263 {
264 if (!$removeAny)
265 {
266 $deleteAfter = \COption::GetOptionInt('im', self::OPTION_KEY_DELETE_AFTER);
267 if ($deleteAfter > $this->message->getDateCreate()->getTimestamp())
268 {
270 }
271 }
272
273 $this->deleteLinks();
274 $this->recountChat();
275 $this->sendPullMessage(true);
276 $this->message->delete();
277 if (!$this->chat instanceof Chat\OpenLineChat)
278 {
279 Sync\Logger::getInstance()->add(
280 new Sync\Event(Sync\Event::COMPLETE_DELETE_EVENT, Sync\Event::MESSAGE_ENTITY, $this->message->getId()),
281 fn () => $this->chat->getRelations()->getUserIds()
282 );
283 }
284
285 return new Result();
286 }
287
288 private function sendPullMessage(bool $completeDelete = false): Result
289 {
290 $pullMessage = $this->getFormatPullMessage($completeDelete);
291
292 if ($this->chat instanceof Chat\PrivateChat)
293 {
294 $userId = $this->message->getAuthorId();
295 $companionUserId = $this->chat->getCompanion($userId)->getId();
296 $this->sendPullMessagePrivate($userId, $companionUserId, $pullMessage, $completeDelete);
297 $this->sendPullMessagePrivate($companionUserId, $userId, $pullMessage, $completeDelete);
298 }
299 else
300 {
301 $groupedPullMessage = $this->groupPullByCounter($pullMessage, $completeDelete);
302 foreach ($groupedPullMessage as $pullForGroup)
303 {
304 Event::add($pullForGroup['users'], $pullForGroup['event']);
305 }
306
307 if (in_array($this->chat->getType(), [Chat::IM_TYPE_OPEN, Chat::IM_TYPE_OPEN_LINE], true))
308 {
309 \CPullWatch::AddToStack('IM_PUBLIC_' . $this->chat->getChatId(), $pullMessage);
310 }
311 }
312
313 return new Result;
314 }
315
316 private function sendPullMessagePrivate(int $fromUser, int $toUser, array $pullMessage, bool $completeDelete): void
317 {
318 $isMuted = false;
319 $relation = $this->chat->getRelations()->getByUserId($toUser, $this->chat->getChatId());
320 if ($relation !== null)
321 {
322 $isMuted = $relation->getNotifyBlock() ?? false;
323 }
324 $pullMessage['params']['dialogId'] = $fromUser;
325 $pullMessage['params']['fromUserId'] = $fromUser;
326 $pullMessage['params']['toUserId'] = $toUser;
327 $pullMessage['params']['counter'] = $this->getCounter($toUser);
328 $pullMessage['params']['unread'] = Recent::isUnread($toUser, $this->chat->getType(), $fromUser);
329 $pullMessage['params']['muted'] = $isMuted;
330 if ($completeDelete && $this->needUpdateRecent)
331 {
332 $pullMessage['params']['lastMessageViews'] = $this->getLastViewers($toUser);
333 }
334 Event::add($toUser, $pullMessage);
335 }
336
337 public function getFormatPullMessage(bool $completeDelete): array
338 {
339 $params = [
340 'id' => (int)$this->message->getId(),
341 'type' => $this->chat->getType() === Chat::IM_TYPE_PRIVATE ? 'private' : 'chat',
342 'text' => Loc::getMessage('IM_MESSAGE_DELETED'),
343 'senderId' => $this->message->getAuthorId(),
344 'params' => ['IS_DELETED' => 'Y', 'URL_ID' => [], 'FILE_ID' => [], 'KEYBOARD' => 'N', 'ATTACH' => []],
345 'chatId' => $this->chat->getChatId(),
346 ];
347
348 if (!$this->chat instanceof Chat\PrivateChat)
349 {
350 $params['dialogId'] = $this->chat->getDialogId();
351 }
352
353 if ($completeDelete && $this->needUpdateRecent)
354 {
355 if ($this->chatLastMessage['ID'] !== 0)
356 {
357 $newLastMessage = new Message($this->chatLastMessage['ID']);
358 if ($newLastMessage->getId())
359 {
360 $params['newLastMessage'] = $this->formatNewLastMessage($newLastMessage);
361 }
362 }
363 else
364 {
365 $params['newLastMessage'] = ['id' => 0];
366 }
367 }
368
369 return [
370 'module_id' => 'im',
371 'command' => $completeDelete ? 'messageDeleteComplete' : 'messageDelete',
372 'params' => $params,
373 'push' => $completeDelete ? ['badge' => 'Y'] : [],
374 'extra' => Common::getPullExtra()
375 ];
376 }
377
378 private function groupPullByCounter(array $pullMessage, bool $completeDelete): array
379 {
380 $events = [];
382 $relations = $this->chat->getRelations();
383 $unreadList = Recent::getUnread($this->chat->getType(), $this->chat->getDialogId());
384 foreach ($relations as $relation)
385 {
386 $user = $relation->getUser();
387 if (
388 (!$user->isActive() && $user->getExternalAuthId() !== \Bitrix\Im\Bot::EXTERNAL_AUTH_ID)
389 || ($this->chat->getEntityType() === Chat::ENTITY_TYPE_LINE && $user->getExternalAuthId() === 'imconnector')
390 )
391 {
392 continue;
393 }
394
395 $userId = $relation->getUserId();
396
397 $pullMessage['params']['unread'] = $unreadList[$userId] ?? false;
398 $pullMessage['params']['muted'] = $relation->getNotifyBlock() ?? false;
399
400 $events[$userId] = $pullMessage;
401
402 $count = 0;
403 if ($this->needUpdateRecent && $completeDelete)
404 {
405 $lastMessageViews = $this->getLastViewers($userId);
406 $events[$userId]['params']['lastMessageViews'] = $lastMessageViews;
407 $count = $lastMessageViews['countOfViewers'] ?? 0;
408 }
409
410 $unreadGroupFlag = $pullMessage['params']['unread'] ? 1 : 0;
411 $mutedGroupFlag = $pullMessage['params']['muted'] ? 1 : 0;
412
413 $events[$userId]['params']['counter'] = $this->getCounter($userId);
414 $events[$userId]['groupId'] =
415 'im_chat_'
416 . $this->chat->getChatId()
417 . '_'. $this->message->getMessageId()
418 . '_'. $events[$userId]['params']['counter']
419 . '_'. $count
420 . '_'. $unreadGroupFlag
421 . '_'. $mutedGroupFlag
422 ;
423 }
424
425 return (new Message\Send\PushService())->getEventByCounterGroup($events);
426 }
427
428 private function deleteLinks()
429 {
430 $connection = Application::getConnection();
431
432 // delete chats with PARENT_MID
433 $childChatResult = Chat\ChatFactory::getInstance()->findChat(['PARENT_MID' => $this->message->getId()]);
434 if ($childChatResult->hasResult())
435 {
436 $childChat = Chat\ChatFactory::getInstance()->getChat($childChatResult->getResult());
437 $childChat->deleteChat();
438 }
439
440 (new \Bitrix\Im\V2\Link\Favorite\FavoriteService())->unmarkMessageAsFavoriteForAll($this->message);
441 (new \Bitrix\Im\V2\Message\ReadService())->deleteByMessageId(
442 $this->message->getMessageId(),
443 $this->chat->getRelations()->getUserIds()
444 );
445 $this->message->unpin();
446
447 if (Loader::includeModule('tasks'))
448 {
449 $taskItem = TaskItem::getByMessageId($this->message->getMessageId());
450 if ($taskItem !== null)
451 {
452 $taskItem->setMessageId(0);
453 (new TaskService())->updateTaskLink($taskItem);
454 }
455 }
456
457 if (Loader::includeModule('calendar'))
458 {
459 $calendarItem = CalendarItem::getByMessageId($this->message->getMessageId());
460 if ($calendarItem !== null)
461 {
462 $calendarItem->setMessageId(0);
463 (new CalendarService())->updateCalendarLink($calendarItem);
464 }
465 }
466
467 $this->message->getParams()->delete();
468
469 // delete unused rows in db
470 $tablesToDeleteRow = [
471 'b_im_message_uuid',
472 'b_im_message_favorite',
473 'b_im_message_disappearing',
474 'b_im_message_index',
475 'b_im_link_reminder',
476 'b_imconnectors_delivery_mark',
477 ];
478
479 foreach ($tablesToDeleteRow as $table)
480 {
481 $connection->query("DELETE FROM " . $table . " WHERE MESSAGE_ID = " . $this->message->getId());
482 }
483 }
484
485 private function fireEventAfterMessageDelete(array $messageFields, bool $completeDelete = false): Result
486 {
487 $result = new Result;
488
489 foreach(GetModuleEvents('im', self::EVENT_AFTER_MESSAGE_DELETE, true) as $event)
490 {
491 $deleteFlags = [
492 'ID' => $messageFields['ID'],
493 'USER_ID' => 0,
494 'COMPLETE_DELETE' => $completeDelete,
495 'BY_EVENT' => false
496 ];
497
498 ExecuteModuleEventEx($event, [$messageFields['ID'], $messageFields, $deleteFlags]);
499 }
500
501 return $result;
502 }
503
504 private function recountChat(): void
505 {
506 $this->updateRecent();
507 if (!is_null($this->chatLastMessage))
508 {
509 $isMessageRead = !!MessageViewedTable::query()
510 ->addFilter('MESSAGE_ID', $this->chatLastMessage['ID'])
511 ->fetch();
512
513 $this->chat->setLastMessageId((int)($this->chatLastMessage['ID'] ?? 0));
514 $this->chat->setLastMessageStatus($isMessageRead ? \IM_MESSAGE_STATUS_DELIVERED : \IM_MESSAGE_STATUS_RECEIVED);
515 }
516
517 $this->chat->setPrevMessageId($this->chatPrevMessageId ?? 0);
518
519 $this->chat->setMessageCount($this->chat->getMessageCount() - 1);
520 $this->chat->save();
521 $this->updateRelation();
522 }
523
524 private function updateRelation(): void
525 {
526 if ($this->needUpdateRecent)
527 {
528 $newLastId = $this->chatLastMessage['ID'] ?? 0;
529 }
530 else
531 {
532 $newLastId = $this->getPreviousMessageId();
533 }
535 UPDATE b_im_relation
536 SET LAST_ID = {$newLastId}
537 WHERE CHAT_ID = {$this->message->getChatId()} AND LAST_ID = {$this->message->getMessageId()}
538 ");
539 }
540
541 private function updateRecent(): void
542 {
543 if ($this->chatLastMessage && (int)$this->chatLastMessage['ID'] !== $this->message->getId())
544 {
545 $update = [
546 'DATE_MESSAGE' => $this->chatLastMessage['DATE_CREATE'],
547 'DATE_UPDATE' => $this->chatLastMessage['DATE_CREATE'],
548 'ITEM_MID' => $this->chatLastMessage['ID'] ?? 0,
549 ];
550
551 if ($this->chat instanceof Chat\PrivateChat || $this->chat->getType() === Chat::IM_TYPE_PRIVATE)
552 {
553 $userId = $this->getContext()->getUserId();
554 $opponentId = $this->chat->getCompanion($userId)->getId();
555 RecentTable::updateByFilter(
556 [
557 '=USER_ID' => $userId,
558 '=ITEM_TYPE' => Chat::IM_TYPE_PRIVATE,
559 '=ITEM_ID' => $opponentId
560 ],
561 $update
562 );
563 RecentTable::updateByFilter(
564 [
565 '=USER_ID' => $opponentId,
566 '=ITEM_TYPE' => Chat::IM_TYPE_PRIVATE,
567 '=ITEM_ID' => $userId
568 ],
569 $update
570 );
571 }
572 else
573 {
574 RecentTable::updateByFilter(
575 ['=ITEM_TYPE' => $this->chat->getType(), '=ITEM_ID' => $this->chat->getId()],
576 $update
577 );
578 }
579 }
580 }
581
582 private function getCounter(int $userId): int
583 {
584 $this->counters ??= (new Message\CounterService())
585 ->getByChatForEachUsers($this->chat->getChatId(), $this->chat->getRelations()->getUserIds())
586 ;
587
588 return $this->counters[$userId] ?? 0;
589 }
590
591 private function formatNewLastMessage(Message $message): array
592 {
593 $result = $message
594 ->setViewed(false) // todo: refactor this
595 ->toRestFormat()
596 ;
597
598 if ($message->getFiles()->count() <= 0)
599 {
600 return $result;
601 }
602
603 $file = $message->getFiles()->getAny();
604
605 if ($file === null)
606 {
607 return $result;
608 }
609
610 $result['file'] = ['type' => $file->getContentType(), 'name' => $file->getDiskFile()->getName()];
611
612 return $result;
613 }
614
615 private function getLastViewers(int $userId): array
616 {
617 $this->lastMessageViewers ??= $this->chat->getLastMessageViewsByGroups();
618
619 if (isset($this->lastMessageViewers['USERS'][$userId]))
620 {
621 return Common::toJson($this->lastMessageViewers['FOR_VIEWERS'] ?? []);
622 }
623
624 return Common::toJson($this->lastMessageViewers['FOR_NOT_VIEWERS'] ?? []);
625 }
626
627 private function getPreviousMessageId(): int
628 {
629 if (isset($this->previousMessageId))
630 {
631 return $this->previousMessageId;
632 }
633
634 $result = MessageTable::query()
635 ->setSelect(['ID'])
636 ->where('CHAT_ID', $this->chat->getChatId())
637 ->where('ID', '<', $this->message->getMessageId())
638 ->setOrder(['DATE_CREATE' => 'DESC', 'ID' => 'DESC'])
639 ->setLimit(1)
640 ->fetch()
641 ;
642 $this->previousMessageId = ($result && isset($result['ID'])) ? (int)$result['ID'] : 0;
643
644 return $this->previousMessageId;
645 }
646
647 private function getChatPreviousMessages(): ?array
648 {
649 $lastChatMessageId = $this->chat->getLastMessageId();
650 $prevChatMessageId = $this->chat->getPrevMessageId();
651
652 if (
653 !in_array(
654 $this->message->getId(),
655 [
656 $lastChatMessageId,
657 $prevChatMessageId,
658 0
659 ],
660 true
661 )
662 )
663 {
664 return $this->message->toArray();
665 }
666
667 $lastMessages = MessageTable::query()
668 ->setSelect(['ID', 'DATE_CREATE', 'MESSAGE'])
669 ->addFilter('CHAT_ID', $this->chat->getChatId())
670 ->setOrder(['DATE_CREATE' => 'DESC', 'ID' => 'DESC'])
671 ->setLimit(3)
672 ->fetchAll();
673
674 $this->chatPrevMessageId = (int)($lastMessages[2]['ID'] ?? 0);
675 $nullMessage = ['ID' => 0, 'DATE_CREATE' => (new DateTime()), 'MESSAGE' => ''];
676 if ($this->message->getId() === $lastChatMessageId)
677 {
678 $this->needUpdateRecent = true;
679 $this->chatLastMessage = $lastMessages[1] ?? $nullMessage;
680 }
681 else
682 {
683 $this->chatLastMessage = $lastMessages[0] ?? $nullMessage;
684 }
685
686 return $this->chatLastMessage;
687 }
688}
static toJson($array, $camelCase=true)
Definition common.php:90
static getPullExtra()
Definition common.php:128
static isUnread(int $userId, string $itemType, string $dialogId)
Definition recent.php:1425
static getUnread(string $itemType, string $dialogId)
Definition recent.php:1444
const IM_TYPE_CHANNEL
Definition Chat.php:60
const IM_TYPE_PRIVATE
Definition Chat.php:55
const IM_TYPE_OPEN_LINE
Definition Chat.php:58
const IM_TYPE_OPEN
Definition Chat.php:61
const ENTITY_TYPE_LINE
Definition Chat.php:97
static getConnection($name="")
static getMessage($code, $replace=null, $language=null)
Definition loc.php:29