Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
RecentProvider.php
1<?php
2
4
11use Bitrix\Im\V2\Common\ContextCustomer;
33
35{
36 use ContextCustomer;
37
38 private const LIMIT = 30;
39 private const ENTITY_ID = 'im-recent-v2';
40 private const ENTITY_TYPE_USER = 'im-user';
41 private const ENTITY_TYPE_CHAT = 'im-chat';
42 private const WITH_CHAT_BY_USERS_OPTION = 'withChatByUsers';
43 private const WITH_CHAT_BY_USERS_DEFAULT = false;
44
45 private string $preparedSearchString;
46 private string $originalSearchString;
47 private array $userIds;
48 private array $chatIds;
49 private bool $sortEnable = true;
50
51 public function __construct(array $options = [])
52 {
53 $this->options[self::WITH_CHAT_BY_USERS_OPTION] = self::WITH_CHAT_BY_USERS_DEFAULT;
54 if (isset($options[self::WITH_CHAT_BY_USERS_OPTION]) && is_bool($options[self::WITH_CHAT_BY_USERS_OPTION]))
55 {
56 $this->options[self::WITH_CHAT_BY_USERS_OPTION] = $options[self::WITH_CHAT_BY_USERS_OPTION];
57 }
58 parent::__construct();
59 }
60
61 public function isAvailable(): bool
62 {
63 global $USER;
64
65 return $USER->IsAuthorized();
66 }
67
68 public function doSearch(SearchQuery $searchQuery, Dialog $dialog): void
69 {
70 $this->originalSearchString = $searchQuery->getQuery();
71 $this->preparedSearchString = $this->prepareSearchString($searchQuery->getQuery());
72 if (!Content::canUseFulltextSearch($this->preparedSearchString))
73 {
74 return;
75 }
76 $searchQuery->setCacheable(false);
77 $items = $this->getSortedLimitedBlankItems();
78 $this->fillItems($items);
79 $dialog->addItems($items);
80 }
81
82 public function fillDialog(Dialog $dialog): void
83 {
84 if (!Loader::includeModule('intranet'))
85 {
86 return;
87 }
88
89 $requiredCountToFill = self::LIMIT - $dialog->getRecentItems()->count();
90
91 if ($requiredCountToFill <= 0)
92 {
93 return;
94 }
95
96 $result = \CIntranetUtils::getDepartmentColleagues(null, true, false, 'Y', ['ID']);
97 $colleaguesIds = [];
98
99 while (($row = $result->Fetch()))
100 {
101 $colleaguesIds[] = (int)$row['ID'];
102 }
103
104 rsort($colleaguesIds);
105 $colleaguesIds = array_slice($colleaguesIds, 0, $requiredCountToFill);
106
107 foreach ($colleaguesIds as $userId)
108 {
109 $dialog->getRecentItems()->add(new RecentItem(['id' => $userId, 'entityId' => self::ENTITY_ID]));
110 }
111 }
112
113 public function getItems(array $ids): array
114 {
115 $this->sortEnable = false;
116 $ids = array_slice($ids, 0, self::LIMIT);
117 $this->setUserAndChatIds($ids);
118 $items = $this->getItemsWithDates();
119 $this->fillItems($items);
120
121 return $items;
122 }
123
124 public function getPreselectedItems(array $ids): array
125 {
126 /*$this->sortEnable = false;
127 $ids = array_slice($ids, 0, self::LIMIT);
128 $this->setUserAndChatIds($ids);
129 $foundItems = $this->getItemsWithDates();
130 $foundItemsDialogId = array_keys($foundItems);
131 $otherItemsDialogId = array_diff($ids, $foundItemsDialogId);
132 $otherItems = $this->getBlankItems($otherItemsDialogId);
133 $items = $this->mergeByKey($foundItems, $otherItems);
134 $this->fillItems($items);*/
135
136 return $this->getItems($ids);
137 }
138
139 private function setUserAndChatIds(array $ids): void
140 {
141 foreach ($ids as $id)
142 {
143 if ($this->isChatId($id))
144 {
145 $chatId = substr($id, 4);
146 $this->chatIds[$chatId] = $chatId;
147 }
148 else
149 {
150 $this->userIds[$id] = $id;
151 }
152 }
153 }
154
155 private function getBlankItems(array $ids, array $datesUpdate = [], array $datesCreate = []): array
156 {
157 $result = [];
158
159 foreach ($ids as $id)
160 {
161 $result[] = $this->getBlankItem($id, $datesUpdate[$id] ?? null, $datesCreate[$id] ?? null);
162 }
163
164 return $result;
165 }
166
167 private function getBlankItem(string $dialogId, ?DateTime $dateMessage = null, ?DateTime $dateCreate = null): Item
168 {
169 $id = $dialogId;
170 $entityType = self::ENTITY_TYPE_USER;
171 if ($this->isChatId($dialogId))
172 {
173 $id = substr($dialogId, 4);
174 $entityType = self::ENTITY_TYPE_CHAT;
175 }
176 $customData = ['id' => $id];
177 $sort = 0;
178 $customData['dateMessage'] = $dateMessage;
179 $customData['dateCreateTs'] = $dateCreate instanceof DateTime ? $dateCreate->getTimestamp() : 0;
180 if (isset($dateMessage))
181 {
182 if ($this->sortEnable)
183 {
184 $sort = $dateMessage->getTimestamp();
185 }
186 }
187
188 return new Item([
189 'id' => $dialogId,
190 'entityId' => self::ENTITY_ID,
191 'entityType' => $entityType,
192 'sort' => $sort,
193 'customData' => $customData,
194 ]);
195 }
196
201 private function fillItems(array $items): void
202 {
203 $userIds = [];
204 $chats = [];
205 foreach ($items as $item)
206 {
207 $id = $item->getCustomData()->get('id');
208 if ($item->getEntityType() === self::ENTITY_TYPE_USER)
209 {
210 $userIds[] = $id;
211 }
212 else
213 {
214 $chats[$id] = Chat::getInstance($id);
215 }
216 }
217 $users = new UserCollection($userIds);
218 $users->fillOnlineData();
219 Chat::fillRole($chats);
220 foreach ($items as $item)
221 {
222 $customData = $item->getCustomData()->getValues();
223 if ($item->getEntityType() === self::ENTITY_TYPE_USER)
224 {
225 $user = $users->getById($customData['id']);
226 $customData = array_merge($customData, $user->toRestFormat());
227 $item->setTitle($user->getName())->setAvatar($user->getAvatar())->setCustomData($customData);
228 }
229 if ($item->getEntityType() === self::ENTITY_TYPE_CHAT)
230 {
231 $chat = $chats[$customData['id']] ?? null;
232 if ($chat === null)
233 {
234 continue;
235 }
236 $customData = array_merge($customData, $chat->toRestFormat(['CHAT_SHORT_FORMAT' => true]));
237 $item->setTitle($chat->getTitle())->setAvatar($chat->getAvatar())->setCustomData($customData);
238 }
239 }
240 }
241
242 private function getItemsWithDates(): array
243 {
244 $userItemsWithDate = $this->getUserItemsWithDate();
245 $chatItemsWithDate = $this->getChatItemsWithDate();
246
247 return $this->mergeByKey($userItemsWithDate, $chatItemsWithDate);
248 }
249
250 private function getSortedLimitedBlankItems(): array
251 {
252 $items = $this->getItemsWithDates();
253 usort($items, function(Item $a, Item $b) {
254 if ($b->getSort() === $a->getSort())
255 {
256 if (!$this->isChatId($b->getId()) && !$this->isChatId($a->getId()))
257 {
258 $bUser = User::getInstance($b->getId());
259 $aUser = User::getInstance($a->getId());
260 if ($aUser->isExtranet() === $bUser->isExtranet())
261 {
262 return $bUser->getId() <=> $aUser->getId();
263 }
264
265 return $aUser->isExtranet() <=> $bUser->isExtranet();
266 }
267 return (int)$b->getCustomData()->get('dateCreateTs') <=> (int)$a->getCustomData()->get('dateCreateTs');
268 }
269 return $b->getSort() <=> $a->getSort();
270 });
271
272 return array_slice($items, 0, self::LIMIT);
273 }
274
275 private function getChatItemsWithDate(): array
276 {
277 if (isset($this->preparedSearchString))
278 {
279 return $this->mergeByKey(
280 $this->getChatItemsWithDateByUsers(),
281 $this->getChatItemsWithDateByTitle()
282 );
283 }
284
285 if (isset($this->chatIds) && !empty($this->chatIds))
286 {
287 return $this->getChatItemsWithDateByIds();
288 }
289
290 return [];
291 }
292
293 private function getChatItemsWithDateByIds(): array
294 {
295 if (!isset($this->chatIds) || empty($this->chatIds))
296 {
297 return [];
298 }
299
300 $result = $this->getCommonChatQuery()->whereIn('ID', $this->chatIds)->fetchAll();
301
302 return $this->getChatItemsByRawResult($result);
303 }
304
305 private function getChatItemsWithDateByTitle(): array
306 {
307 if (!isset($this->preparedSearchString))
308 {
309 return [];
310 }
311
312 $result = $this
313 ->getCommonChatQuery()
314 ->whereMatch('INDEX.SEARCH_TITLE', $this->preparedSearchString)
315 ->setOrder(['LAST_MESSAGE_ID' => 'DESC', 'DATE_CREATE' => 'DESC'])
316 ->fetchAll()
317 ;
318
319 return $this->getChatItemsByRawResult($result, ['byUser' => false]);
320 }
321
322 private function getChatItemsWithDateByUsers(): array
323 {
324 if (!isset($this->preparedSearchString) || !$this->withChatByUsers())
325 {
326 return [];
327 }
328
329 $result = $this
330 ->getCommonChatQuery(Join::TYPE_INNER)
331 ->setOrder(['LAST_MESSAGE_ID' => 'DESC', 'DATE_CREATE' => 'DESC'])
332 ->registerRuntimeField(
333 'CHAT_SEARCH',
334 (new Reference(
335 'CHAT_SEARCH',
336 Entity::getInstanceByQuery($this->getChatsByUserNameQuery()),
337 Join::on('this.ID', 'ref.CHAT_ID')
338 ))->configureJoinType(Join::TYPE_INNER)
339 )
340 ->fetchAll()
341 ;
342
343 return $this->getChatItemsByRawResult($result, ['byUser' => true]);
344 }
345
346 private function getChatsByUserNameQuery(): Query
347 {
348 return RelationTable::query()
349 ->setSelect(['CHAT_ID'])
350 ->registerRuntimeField(
351 'USER',
352 (new Reference(
353 'USER',
354 \Bitrix\Main\UserTable::class,
355 Join::on('this.USER_ID', 'ref.ID'),
356 ))->configureJoinType(Join::TYPE_INNER)
357 )
358 ->registerRuntimeField(
359 'USER_INDEX',
360 (new Reference(
361 'USER_INDEX',
362 UserIndexTable::class,
363 Join::on('this.USER_ID', 'ref.USER_ID'),
364 ))->configureJoinType(Join::TYPE_INNER)
365 )
366 ->whereIn('MESSAGE_TYPE', [Chat::IM_TYPE_CHAT, Chat::IM_TYPE_OPEN])
367 ->where('USER.IS_REAL_USER', 'Y')
368 ->whereMatch('USER_INDEX.SEARCH_USER_CONTENT', $this->preparedSearchString)
369 ->setGroup(['CHAT_ID'])
370 ;
371 }
372
373 private function getChatItemsByRawResult(array $raw, array $additionalCustomData = []): array
374 {
375 $result = [];
376
377 foreach ($raw as $row)
378 {
379 $dialogId = 'chat' . $row['ID'];
380 $item = $this->getBlankItem($dialogId, $row['MESSAGE_DATE_CREATE'], $row['DATE_CREATE']);
381 if (!empty($additionalCustomData))
382 {
383 $customData = $item->getCustomData()->getValues();
384 $item->setCustomData(array_merge($customData, $additionalCustomData));
385 }
386 $result[$dialogId] = $item;
387 }
388
389 return $result;
390 }
391
392 private function getCommonChatQuery(string $joinType = Join::TYPE_LEFT): Query
393 {
394 $query = ChatTable::query()
395 ->setSelect(['ID', 'MESSAGE_DATE_CREATE' => 'MESSAGE.DATE_CREATE', 'DATE_CREATE'])
396 ->registerRuntimeField(new Reference(
397 'RELATION',
398 RelationTable::class,
399 Join::on('this.ID', 'ref.CHAT_ID')
400 ->where('ref.USER_ID', $this->getContext()->getUserId())
401 ->where('ref.MESSAGE_TYPE', Chat::IM_TYPE_CHAT),
402 ['join_type' => $joinType]
403 )
404 )
405 ->registerRuntimeField(
406 new Reference(
407 'MESSAGE',
408 MessageTable::class,
409 Join::on('this.LAST_MESSAGE_ID', 'ref.ID'),
410 ['join_type' => Join::TYPE_LEFT]
411 )
412 )
413 ->setLimit(self::LIMIT)
414 ->whereIn('TYPE', [Chat::IM_TYPE_CHAT, Chat::IM_TYPE_OPEN])
415 ;
416 if ($joinType === Join::TYPE_LEFT)
417 {
418 $query->where($this->getRelationFilter());
419 }
420
421 return $query;
422 }
423
424 private function getRelationFilter(): ConditionTree
425 {
426 if (User::getCurrent()->isExtranet())
427 {
428 return Query::filter()->whereNotNull('RELATION.USER_ID');
429 }
430
431 return Query::filter()
432 ->logic('or')
433 ->whereNotNull('RELATION.USER_ID')
434 ->where('TYPE', Chat::IM_TYPE_OPEN)
435 ;
436 }
437
438 private function getUserItemsWithDate(): array
439 {
440 $result = [];
441 $query = UserTable::query()
442 ->setSelect(['ID', 'DATE_MESSAGE' => 'RECENT.DATE_MESSAGE', 'IS_INTRANET_USER', 'DATE_CREATE' => 'DATE_REGISTER'])
443 ->where('ACTIVE', true)
444 ->registerRuntimeField(
445 'RECENT',
446 new Reference(
447 'RECENT',
448 RecentTable::class,
449 Join::on('this.ID', 'ref.ITEM_ID')
450 ->where('ref.USER_ID', $this->getContext()->getUserId())
451 ->where('ref.ITEM_TYPE', Chat::IM_TYPE_PRIVATE),
452 ['join_type' => Join::TYPE_LEFT]
453 )
454 )
455 ->setLimit(self::LIMIT)
456 ;
457
458 if (isset($this->preparedSearchString))
459 {
460 $query
461 ->whereMatch('INDEX.SEARCH_USER_CONTENT', $this->preparedSearchString)
462 ->setOrder(['RECENT.DATE_MESSAGE' => 'DESC', 'IS_INTRANET_USER' => 'DESC', 'DATE_CREATE' => 'DESC'])
463 ;
464 }
465 elseif (isset($this->userIds) && !empty($this->userIds))
466 {
467 $query->whereIn('ID', $this->userIds);
468 }
469 else
470 {
471 return [];
472 }
473
474 $query->where($this->getIntranetFilter());
475
476 $raw = $query->fetchAll();
477
478 foreach ($raw as $row)
479 {
480 if ($this->isHiddenBot((int)$row['ID']))
481 {
482 continue;
483 }
484
485 $result[(int)$row['ID']] = $this->getBlankItem((int)$row['ID'], $row['DATE_MESSAGE'], $row['DATE_CREATE']);
486 }
487
488 $result = $this->getAdditionalUsers($result);
489
490 return $result;
491 }
492
493 private function getAdditionalUsers(array $foundUserItems): array
494 {
495 if ($this->needAddFavoriteChat($foundUserItems))
496 {
497 $foundUserItems[$this->getContext()->getUserId()] = $this->getFavoriteChatUserItem();
498 }
499
500 return $foundUserItems;
501 }
502
503 private function getFavoriteChatUserItem(): Item
504 {
505 $userId = $this->getContext()->getUserId();
506 $row = ChatTable::query()
507 ->setSelect(['DATE_MESSAGE' => 'MESSAGE.DATE_CREATE', 'DATE_CREATE'])
508 ->registerRuntimeField(
509 new Reference(
510 'MESSAGE',
511 MessageTable::class,
512 Join::on('this.LAST_MESSAGE_ID', 'ref.ID'),
513 ['join_type' => Join::TYPE_LEFT]
514 )
515 )
516 ->where('ENTITY_TYPE', Chat::ENTITY_TYPE_FAVORITE)
517 ->where('ENTITY_ID', $userId)
518 ->fetch() ?: []
519 ;
520 $dateMessage = $row['DATE_MESSAGE'] ?? null;
521 $dateCreate = $row['DATE_CREATE'] ?? null;
522
523 return $this->getBlankItem($this->getContext()->getUserId(), $dateMessage, $dateCreate);
524 }
525
526 private function needAddFavoriteChat(array $foundUserItems): bool
527 {
528 return
529 !isset($foundUserItems[$this->getContext()->getUserId()])
530 && isset($this->originalSearchString)
531 && static::isPhraseFoundBySearchQuery(Chat\FavoriteChat::getTitlePhrase(), $this->originalSearchString)
532 ;
533 }
534
535 private static function isPhraseFoundBySearchQuery(string $phrase, string $searchQuery): bool
536 {
537 $searchWords = explode(' ', $searchQuery);
538 $phraseWords = explode(' ', $phrase);
539
540 foreach ($searchWords as $searchWord)
541 {
542 $searchWordLowerCase = mb_strtolower($searchWord);
543 $found = false;
544 foreach ($phraseWords as $phraseWord)
545 {
546 $phraseWordLowerCase = mb_strtolower($phraseWord);
547 if (str_starts_with($phraseWordLowerCase, $searchWordLowerCase))
548 {
549 $found = true;
550 break;
551 }
552 }
553 if (!$found)
554 {
555 return false;
556 }
557 }
558
559 return true;
560 }
561
562 private function isHiddenBot(int $userId): bool
563 {
564 $user = User::getInstance($userId);
565
566 if ($user instanceof UserBot && $user->isBot())
567 {
568 $botData = $user->getBotData()->toRestFormat();
569 if ($botData['isHidden'])
570 {
571 return true;
572 }
573 }
574
575 return false;
576 }
577
578 private function getIntranetFilter(): ConditionTree
579 {
580 $filter = Query::filter();
581 if (!Loader::includeModule('intranet'))
582 {
583 return $filter->where($this->getRealUserOrBotCondition());
584 }
585
586 $subQuery = $this->getExtranetUsersQuery();
587 if (!User::getCurrent()->isExtranet())
588 {
589 $filter->logic('or');
590 $filter->where('IS_INTRANET_USER', true);
591 if ($subQuery !== null)
592 {
593 $filter->whereIn('ID', $subQuery);
594 }
595 return $filter;
596 }
597
598 $filter->where($this->getRealUserOrBotCondition());
599 if ($subQuery !== null)
600 {
601 $filter->whereIn('ID', $subQuery);
602 }
603 else
604 {
605 $filter->where(new ExpressionField('EMPTY_LIST', '1'), '!=', 1);
606 }
607
608 return $filter;
609 }
610
611 private function getRealUserOrBotCondition(): ConditionTree
612 {
613 return Query::filter()
614 ->logic('or')
615 ->whereNotIn('EXTERNAL_AUTH_ID', UserTable::filterExternalUserTypes(['bot']))
616 ->whereNull('EXTERNAL_AUTH_ID')
617 ;
618 }
619
620 private function getExtranetUsersQuery(): ?Query
621 {
622 if (!Loader::includeModule('socialnetwork'))
623 {
624 return null;
625 }
626
627 $extranetSiteId = Option::get('extranet', 'extranet_site');
628 $extranetSiteId = ($extranetSiteId && ModuleManager::isModuleInstalled('extranet') ? $extranetSiteId : false);
629
630 if (
631 !$extranetSiteId
632 || \CSocNetUser::isCurrentUserModuleAdmin()
633 )
634 {
635 return null;
636 }
637
639 $extranetRoles = [
640 \Bitrix\Socialnetwork\UserToGroupTable::ROLE_USER,
641 \Bitrix\Socialnetwork\UserToGroupTable::ROLE_OWNER,
642 \Bitrix\Socialnetwork\UserToGroupTable::ROLE_MODERATOR,
643 \Bitrix\Socialnetwork\UserToGroupTable::ROLE_REQUEST,
644 ];
645
646 $query = \Bitrix\Socialnetwork\UserToGroupTable::query();
647 $query->addSelect(new ExpressionField('DISTINCT_USER_ID', 'DISTINCT %s', 'USER.ID'));
648 $query->whereIn('ROLE', $extranetRoles);
649 $query->registerRuntimeField(
650 new Reference(
651 'GS',
652 \Bitrix\Socialnetwork\WorkgroupSiteTable::class,
653 Join::on('ref.GROUP_ID', 'this.GROUP_ID')->where('ref.SITE_ID', $extranetSiteId),
654 ['join_type' => 'INNER']
655 )
656 );
657
658 $query->registerRuntimeField(
659 new Reference(
660 'UG_MY',
661 \Bitrix\Socialnetwork\UserToGroupTable::class,
662 Join::on('ref.GROUP_ID', 'this.GROUP_ID')
663 ->where('ref.USER_ID', $this->getContext()->getUserId())
664 ->whereIn('ref.ROLE', $extranetRoles),
665 ['join_type' => 'INNER']
666 )
667 );
668
669 return $query;
670 }
671
672 private function mergeByKey(array ...$arrays): array
673 {
674 $result = [];
675 foreach ($arrays as $array)
676 {
677 foreach ($array as $key => $value)
678 {
679 $result[$key] = $value;
680 }
681 }
682
683 return $result;
684 }
685
686 private function isChatId(string $id): bool
687 {
688 return substr($id, 0, 4) === 'chat';
689 }
690
691 private function withChatByUsers(): bool
692 {
693 return $this->options[self::WITH_CHAT_BY_USERS_OPTION] ?? self::WITH_CHAT_BY_USERS_DEFAULT;
694 }
695
696 private function prepareSearchString(string $searchString): string
697 {
698 $searchString = trim($searchString);
699
700 return Helper::matchAgainstWildcard(Content::prepareStringToken($searchString));
701 }
702}
static getInstance($userId=null)
Definition user.php:44
static fillRole(array $chats, ?int $userId=null)
Definition Chat.php:661
const IM_TYPE_PRIVATE
Definition Chat.php:55
const IM_TYPE_OPEN
Definition Chat.php:61
const IM_TYPE_CHAT
Definition Chat.php:56
const ENTITY_TYPE_FAVORITE
Definition Chat.php:92
doSearch(SearchQuery $searchQuery, Dialog $dialog)
static isModuleInstalled($moduleName)