Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
chatprovider.php
1<?php
3
19
21{
22 protected const ENTITY_ID = 'im-chat';
23
24 protected const MAX_CHATS_IN_SAMPLE = 100;
25 protected const MAX_CHATS_IN_RECENT_TAB = 50;
26
27
28 protected static function getSearchableChatTypes(): array
29 {
30 return [
34 ];
35 }
36
37 protected static function getEntityId(): string
38 {
39 return 'im-chat';
40 }
41
42 public function __construct(array $options = [])
43 {
44 parent::__construct();
45
46 if (isset($options['searchableChatTypes']) && is_array($options['searchableChatTypes']))
47 {
48 foreach ($options['searchableChatTypes'] as $chatType)
49 {
50 if (in_array($chatType, static::getSearchableChatTypes(), true))
51 {
52 $this->options['searchableChatTypes'][] = $chatType;
53 }
54 }
55 }
56
57 $this->options['fillDialog'] = true;
58 if (isset($options['fillDialog']) && is_bool($options['fillDialog']))
59 {
60 $this->options['fillDialog'] = $options['fillDialog'];
61 }
62
63 $this->options['fillDialogWithDefaultValues'] = true;
64 if (isset($options['fillDialogWithDefaultValues']) && is_bool($options['fillDialogWithDefaultValues']))
65 {
66 $this->options['fillDialogWithDefaultValues'] = $options['fillDialogWithDefaultValues'];
67 }
68 }
69
70 public function isAvailable(): bool
71 {
72 return $GLOBALS['USER']->isAuthorized();
73 }
74
75 public function doSearch(SearchQuery $searchQuery, Dialog $dialog): void
76 {
77 $items = $this->getChatItems([
78 'searchQuery' => $searchQuery->getQuery(),
79 'limit' => static::MAX_CHATS_IN_SAMPLE
80 ]);
81
82 $isLimitExceeded = static::MAX_CHATS_IN_SAMPLE <= count($items);
83 $isTooSmallToken = mb_strlen($searchQuery->getQuery()) < Filter\Helper::getMinTokenSize();
84 if ($isLimitExceeded || $isTooSmallToken)
85 {
86 $searchQuery->setCacheable(false);
87 }
88
89 $dialog->addItems($items);
90 }
91
92 public function getItems(array $ids): array
93 {
94 if (!$this->shouldFillDialog())
95 {
96 return [];
97 }
98
99 return $this->getChatItems([
100 'chatIds' => $ids,
101 ]);
102 }
103
104 public function getSelectedItems(array $ids): array
105 {
106 return $this->getItems($ids);
107 }
108
109 public function getChatItems(array $options = []): array
110 {
111 return $this->makeChatItems($this->getChatCollection($options), $options);
112 }
113
114 public function makeChatItems(array $chats, array $options = []): array
115 {
116 return static::makeItems($chats, array_merge($this->getOptions(), $options));
117 }
118
119 public function getChatCollection(array $options = []): array
120 {
121 $options = array_merge($this->getOptions(), $options);
122
123 return static::getChats($options);
124 }
125
126 public static function getChats(array $options = []): array
127 {
128 if (isset($options['chatIds']) && is_array($options['chatIds']))
129 {
130 return static::getChatsByIds($options);
131 }
132
133 $groupChatAndOpenLineIds = static::getChatIds($options, [Chat::TYPE_GROUP, Chat::TYPE_OPEN_LINE]);
134 $openChatIds = static::getChatIds($options, [Chat::TYPE_OPEN]);
135 $options['chatIds'] = array_merge($groupChatAndOpenLineIds, $openChatIds);
136
137 return static::getChatsByIds($options);
138 }
139
140 private static function getChatsByIds(array $options = []): array
141 {
142 if (empty($options['chatIds']))
143 {
144 return [];
145 }
146
147 $currentUserId = User::getInstance()->getId();
148
149 $query = ChatTable::query();
150 $query
151 ->addSelect('*')
152 ->addSelect('RELATION.USER_ID', 'RELATION_USER_ID')
153 ->addSelect('RELATION.NOTIFY_BLOCK', 'RELATION_NOTIFY_BLOCK')
154 //->addSelect('RELATION.COUNTER', 'RELATION_COUNTER')
155 ->addSelect('RELATION.START_COUNTER', 'RELATION_START_COUNTER')
156 //->addSelect('RELATION.LAST_ID', 'RELATION_LAST_ID')
157 //->addSelect('RELATION.STATUS', 'RELATION_STATUS')
158 //->addSelect('RELATION.UNREAD_ID', 'RELATION_UNREAD_ID')
159 ->addSelect('ALIAS.ALIAS', 'ALIAS_NAME')
160 ;
161
162 $query->registerRuntimeField(
163 'RELATION',
164 new Reference(
165 'RELATION',
166 RelationTable::class,
167 Join::on('this.ID', 'ref.CHAT_ID')
168 ->where('ref.USER_ID', $currentUserId),
169 )
170 );
171 $query->whereIn('ID', $options['chatIds']);
172 $query->where(Query::filter()
173 ->logic('or')
174 ->where(Query::filter()
175 ->logic('and')
176 ->where('TYPE','O')
177 ->where('USER_COUNT', '>', 0)
178 )
179 ->where('RELATION.USER_ID', $currentUserId)
180 );
181
182 if (isset($options['order']) && is_array($options['order']))
183 {
184 $query->setOrder($options['order']);
185 }
186 else
187 {
188 $query->setOrder(['LAST_MESSAGE_ID' => 'DESC']);
189 }
190
191 if (isset($options['limit']))
192 {
193 $query->setLimit($options['limit']);
194 }
195
196 $chatsRaw = $query->exec()->fetchAll();
197
198 return Chat::fillCounterData($chatsRaw);
199 }
200
201 private static function getChatIds(array $options, array $chatTypes): array
202 {
203 if (!isset($options['searchableChatTypes']) || !is_array($options['searchableChatTypes']))
204 {
205 return [];
206 }
207
208 $currentUserId = User::getInstance()->getId();
209
210 $query = ChatTable::query();
211 $query->addSelect('ID');
212
213 $searchQuery = $options['searchQuery'] ?? '';
214 if (self::isValidSearchQuery($searchQuery))
215 {
216 $query->registerRuntimeField(
217 'CHAT_INDEX',
218 new Reference(
219 'CHAT_INDEX',
220 ChatIndexTable::class,
221 Join::on('this.ID', 'ref.CHAT_ID'),
222 ['join_type' => Join::TYPE_INNER]
223 )
224 );
225 }
226
227 $filteredChatTypes = [];
228 $relationJoinType = Join::TYPE_INNER;
229 if (
230 count($chatTypes) === 1
231 && in_array(Chat::TYPE_OPEN, $chatTypes, true)
232 && static::shouldSearchChatType(Chat::TYPE_OPEN, $options)
233 )
234 {
235 $relationJoinType = Join::TYPE_LEFT;
236 $filteredChatTypes[] = Chat::TYPE_OPEN;
237 }
238 $query->registerRuntimeField(
239 'RELATION',
240 new Reference(
241 'RELATION',
242 RelationTable::class,
243 Join::on('this.ID', 'ref.CHAT_ID')->where('ref.USER_ID', $currentUserId),
244 ['join_type' => $relationJoinType]
245 )
246 );
247
248 if (self::isValidSearchQuery($searchQuery))
249 {
250 $filter = Query::filter()->logic('and');
251 static::addFilterBySearchQuery($filter, $searchQuery);
252 $query->where($filter);
253 }
254
255 $chatTypesFilter = Query::filter()->logic('or');
256 if (
257 static::shouldSearchChatType(Chat::TYPE_GROUP, $options)
258 && in_array(Chat::TYPE_GROUP, $chatTypes, true)
259 )
260 {
261 $groupChatFilter =
262 Query::filter()
263 ->logic('and')
264 ->where('TYPE', '=', Chat::TYPE_GROUP)
265 ->where(Query::filter()
266 ->logic('or')
267 ->where('ENTITY_TYPE', '!=', 'SUPPORT24_QUESTION')
268 ->whereNull('ENTITY_TYPE')
269 )
270 ->where('RELATION.USER_ID', '=', $currentUserId)
271 ;
272
273 $chatTypesFilter->where($groupChatFilter);
274 $filteredChatTypes[] = Chat::TYPE_GROUP;
275 }
276
277
278 if (
279 static::shouldSearchChatType(Chat::TYPE_OPEN_LINE, $options)
280 && in_array(Chat::TYPE_OPEN_LINE, $chatTypes, true)
281 )
282 {
283 $openLineFilter =
284 Query::filter()
285 ->logic('and')
286 ->where('TYPE', '=', Chat::TYPE_OPEN_LINE)
287 ->where('RELATION.USER_ID', '=', $currentUserId)
288 ;
289
290 $chatTypesFilter->where($openLineFilter);
291 $filteredChatTypes[] = Chat::TYPE_OPEN_LINE;
292 }
293
294 if (
295 static::shouldSearchChatType(Chat::TYPE_OPEN, $options)
296 && in_array(Chat::TYPE_OPEN, $chatTypes, true)
297 )
298 {
299 $channelFilter =
300 Query::filter()
301 ->logic('and')
302 ->where('TYPE', '=', Chat::TYPE_OPEN)
303 ;
304
305 $chatTypesFilter->where($channelFilter);
306 $filteredChatTypes[] = Chat::TYPE_OPEN;
307 }
308 if (empty($filteredChatTypes))
309 {
310 return [];
311 }
312
313 $query->where($chatTypesFilter);
314
315 if (isset($options['chatIds']) && is_array($options['chatIds']))
316 {
317 $query->whereIn('ID', $options['chatIds']);
318 }
319
320 if (isset($options['order']) && is_array($options['order']))
321 {
322 $query->setOrder($options['order']);
323 }
324 else
325 {
326 $query->setOrder(['LAST_MESSAGE_ID' => 'DESC']);
327 }
328
329 if (isset($options['limit']))
330 {
331 $query->setLimit($options['limit']);
332 }
333
334 $chatIdList = [];
335 foreach ($query->exec() as $chat)
336 {
337 $chatIdList[] = $chat['ID'];
338 }
339
340 return $chatIdList;
341 }
342
343 public static function makeItems(array $chats, array $options = []): array
344 {
345 $result = [];
346 foreach ($chats as $chat)
347 {
348 $result[] = static::makeItem($chat, $options);
349 }
350
351 return $result;
352 }
353
354 public static function makeItem(array $chat, array $options = []): Item
355 {
356 return new Item([
357 'id' => (int)$chat['ID'],
358 'entityId' => static::getEntityId(),
359 'entityType' => Helper\Chat::getSelectorEntityType($chat),
360 'title' => $chat['TITLE'],
361 'avatar' => \CIMChat::GetAvatarImage($chat['AVATAR'], 200, false),
362 'customData' => [
363 'imChat' => Chat::formatChatData($chat),
364 ],
365 ]);
366 }
367
368 protected static function shouldSearchChatType(string $chatType, array $options = []): bool
369 {
370 if (
371 !isset($options['searchableChatTypes'])
372 || !is_array($options['searchableChatTypes'])
373 )
374 {
375 return false;
376 }
377
378 $isExtranetUserRequest = User::getInstance()->isExtranet();
379 if (
380 $isExtranetUserRequest
381 && in_array($chatType, [Chat::TYPE_OPEN_LINE, Chat::TYPE_OPEN], true)
382 )
383 {
384 return false;
385 }
386
387 return in_array($chatType, $options['searchableChatTypes'], true);
388 }
389
390 public function shouldFillDialog(): bool
391 {
392 return $this->getOption('fillDialog', true);
393 }
394
395 public function fillDialog(Dialog $dialog): void
396 {
397 if (!$this->shouldFillDialog())
398 {
399 return;
400 }
401
402 if (!$this->getOption('fillDialogWithDefaultValues', true))
403 {
404 $recentChats = [];
405 $recentItems = $dialog->getRecentItems()->getEntityItems(static::getEntityId());
406 $recentIds = array_map('intval', array_keys($recentItems));
407 $recentChats = $this->fillRecentChats($recentChats, $recentIds, []);
408
409 $dialog->addRecentItems($this->makeChatItems($recentChats));
410
411 return;
412 }
413
414 // Preload chats ('doSearch' method has to have the same filter).
415 $preloadedChats = $this->getPreloadedChatsCollection();
416 $recentChats = [];
417
418 // Recent Items
419 $recentItems = $dialog->getRecentItems()->getEntityItems(static::getEntityId());
420 $recentIds = array_map('intval', array_keys($recentItems));
421 $recentChats = $this->fillRecentChats($recentChats, $recentIds, $preloadedChats);
422
423 // Global Recent Items
424 if (count($recentChats) < self::MAX_CHATS_IN_RECENT_TAB)
425 {
426 $recentGlobalItems = $dialog->getGlobalRecentItems()->getEntityItems(static::getEntityId());
427 $recentGlobalIds = [];
428
429 if (!empty($recentGlobalItems))
430 {
431 $recentGlobalIds = array_map('intval', array_keys($recentGlobalItems));
432 $recentChatsIdList = array_column($recentChats, 'ID');
433 $recentGlobalIds = array_values(array_diff($recentGlobalIds, $recentChatsIdList));
434 $recentGlobalIds = array_slice(
435 $recentGlobalIds,
436 0,
437 self::MAX_CHATS_IN_RECENT_TAB - count($recentChats)
438 );
439 }
440
441 $recentChats = $this->fillRecentChats($recentChats, $recentGlobalIds, $preloadedChats);
442 }
443
444 // The rest of preloaded chats
445 foreach ($preloadedChats as $preloadedChat)
446 {
447 $recentChats[] = $preloadedChat;
448 }
449
450 $dialog->addRecentItems($this->makeChatItems($recentChats));
451 }
452
453 protected function getPreloadedChatsCollection(): array
454 {
455 return $this->getChatCollection([
456 'order' => ['ID' => 'DESC'],
457 'limit' => self::MAX_CHATS_IN_RECENT_TAB
458 ]);
459 }
460
461 private function fillRecentChats(array $recentChats, array $recentIds, array $preloadedChats): array
462 {
463 if (count($recentIds) < 1)
464 {
465 return [];
466 }
467
468 $chatIds = array_values(array_diff($recentIds, array_column($preloadedChats, 'ID')));
469 if (!empty($chatIds))
470 {
471 $chats = $this->getChatCollection(['chatIds' => $chatIds]);
472 foreach ($chats as $chat)
473 {
474 $preloadedChats[] = $chat;
475 }
476 }
477
478 foreach ($recentIds as $recentId)
479 {
480 $chat = $preloadedChats[$recentId] ?? null;
481 if ($chat)
482 {
483 $recentChats[] = $chat;
484 }
485 }
486
487 return $recentChats;
488 }
489
490 protected static function isFulltextIndexExist(): bool
491 {
492 $isFulltextIndexExist = Option::get('im', 'search_title_fulltext_index_created', 'N') === 'Y';
493
494 if ($isFulltextIndexExist)
495 {
496 return true;
497 }
498
499 $connection = \Bitrix\Main\Application::getConnection();
500 if ($connection->getType() == 'mysql')
501 {
502 $result = $connection->query("SHOW INDEX FROM b_im_chat_index where Index_type = 'FULLTEXT' and Column_name = 'SEARCH_TITLE'");
503 }
504 elseif ($connection->getType() == 'pgsql')
505 {
506 $result = $connection->query("select indexname from pg_indexes where tablename = 'b_im_chat_index' and indexdef like '%to_tsvector%search_title%'");
507 }
508 else
509 {
510 return false;
511 }
512
513 if ($result->fetch())
514 {
515 Option::set('im', 'search_title_fulltext_index_created', 'Y');
516
517 return true;
518 }
519
520 return false;
521 }
522
523
524 protected static function addFilterBySearchQuery(Filter\ConditionTree $filter, string $searchQuery): void
525 {
526 $searchQuery = trim($searchQuery);
527
528 if (empty($searchQuery) || mb_strlen($searchQuery) < Filter\Helper::getMinTokenSize())
529 {
530 return;
531 }
532
533 if (!static::isFulltextIndexExist())
534 {
535 $filter->whereLike('CHAT_INDEX.SEARCH_TITLE', $searchQuery . '%');
536
537 return;
538 }
539
540 $searchText = Filter\Helper::matchAgainstWildcard(Content::prepareStringToken($searchQuery));
541 $filter->whereMatch('CHAT_INDEX.SEARCH_TITLE', $searchText);
542 }
543
544 private static function isValidSearchQuery(string $searchQuery): bool
545 {
546 $searchQuery = trim($searchQuery);
547 if ($searchQuery === '')
548 {
549 return false;
550 }
551
552 if (mb_strlen($searchQuery) < Filter\Helper::getMinTokenSize())
553 {
554 return false;
555 }
556
557 return true;
558 }
559}
static fillCounterData(array $chats)
Definition chat.php:1406
static formatChatData($chat)
Definition chat.php:994
const TYPE_OPEN
Definition chat.php:22
const TYPE_OPEN_LINE
Definition chat.php:26
const TYPE_GROUP
Definition chat.php:24
static addFilterBySearchQuery(Filter\ConditionTree $filter, string $searchQuery)
static makeItems(array $chats, array $options=[])
doSearch(SearchQuery $searchQuery, Dialog $dialog)
static shouldSearchChatType(string $chatType, array $options=[])
static makeItem(array $chat, array $options=[])
static getInstance($userId=null)
Definition user.php:44
getOption(string $option, $defaultValue=null)
$GLOBALS['____1444769544']
Definition license.php:1