Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/im/lib/ |
Upload File : |
<?php namespace Bitrix\Im; use Bitrix\Im\Model\MessageParamTable; use Bitrix\Im\Model\MessageUnreadTable; use Bitrix\Im\Model\RecentTable; use Bitrix\Im\V2\Chat\Background\Background; use Bitrix\Im\V2\Application\Features; use Bitrix\Im\V2\Chat\Copilot\CopilotPopupItem; use Bitrix\Im\V2\Chat\Copilot\Entity; use Bitrix\Im\V2\Chat\EntityLink; use Bitrix\Im\V2\Chat\Param\Params; use Bitrix\Im\V2\Chat\MessagesAutoDelete\MessagesAutoDeleteConfigs; use Bitrix\Im\V2\Chat\TextField\TextFieldEnabled; use Bitrix\Im\V2\Integration\Socialnetwork\Collab\Collab; use Bitrix\Im\V2\Message\Counter\CounterType; use Bitrix\Im\V2\Entity\User\NullUser; use Bitrix\Im\V2\Permission; use Bitrix\Im\V2\Integration\AI\RoleManager; use Bitrix\Im\V2\Integration\Socialnetwork\Group; use Bitrix\Im\V2\Message; use Bitrix\Im\V2\Message\CounterService; use Bitrix\Im\V2\Entity\File\FileCollection; use Bitrix\Im\V2\Entity\File\FileItem; use Bitrix\Im\V2\Message\Param; use Bitrix\Im\V2\Message\MessagePopupItem; use Bitrix\Im\V2\Message\ReadService; use Bitrix\Im\V2\Message\Send\PushService; use Bitrix\Im\V2\Recent\Config\RecentConfigManager; use Bitrix\Im\V2\RelationCollection; use Bitrix\Im\V2\Rest\RestAdapter; use Bitrix\Im\V2\Settings\UserConfiguration; use Bitrix\Im\V2\Sync; use Bitrix\Imbot\Bot\CopilotChatBot; use Bitrix\Main\Application, Bitrix\Main\Localization\Loc; use Bitrix\Main\Config\Option; use Bitrix\Main\DB\SqlExpression; use Bitrix\Main\Engine\Response\Converter; use Bitrix\Main\Loader; use Bitrix\Main\ORM\Fields\ExpressionField; use Bitrix\Main\Type\DateTime; use Bitrix\Pull\Event; Loc::loadMessages(__FILE__); class Recent { private static array $unreadElementCache = []; private const PINNED_CHATS_LIMIT = 45; static private bool $limitError = false; public static function get($userId = null, $options = []) { $onlyOpenlinesOption = $options['ONLY_OPENLINES'] ?? null; $onlyCopilotOption = $options['ONLY_COPILOT'] ?? null; $skipOpenlinesOption = $options['SKIP_OPENLINES'] ?? null; $skipChat = $options['SKIP_CHAT'] ?? null; $skipDialog = $options['SKIP_DIALOG'] ?? null; $byChatIds = isset($options['CHAT_IDS']); if (isset($options['FORCE_OPENLINES']) && $options['FORCE_OPENLINES'] === 'Y') { $forceOpenlines = 'Y'; } else { $forceOpenlines = 'N'; } $userId = \Bitrix\Im\Common::getUserId($userId); if (!$userId) { return false; } $showOpenlines = ( \Bitrix\Main\Loader::includeModule('imopenlines') && ($onlyOpenlinesOption === 'Y' || $skipOpenlinesOption !== 'Y') ); if ( $showOpenlines && $forceOpenlines !== 'Y' && class_exists('\Bitrix\ImOpenLines\Recent') ) { return \Bitrix\ImOpenLines\Recent::getRecent($userId, $options); } $generalChatId = \CIMChat::GetGeneralChatId(); $ormParams = self::getOrmParams([ 'USER_ID' => $userId, 'SHOW_OPENLINES' => $showOpenlines, 'WITHOUT_COMMON_USERS' => true, 'CHAT_IDS' => $options['CHAT_IDS'] ?? null, ]); $lastSyncDateOption = $options['LAST_SYNC_DATE'] ?? null; if ($lastSyncDateOption) { $maxLimit = (new \Bitrix\Main\Type\DateTime())->add('-7 days'); if ($maxLimit > $options['LAST_SYNC_DATE']) { $options['LAST_SYNC_DATE'] = $maxLimit; } $ormParams['filter']['>=DATE_UPDATE'] = $options['LAST_SYNC_DATE']; } else if ($options['ONLY_OPENLINES'] !== 'Y' && !$byChatIds) { $ormParams['filter']['>=DATE_UPDATE'] = (new \Bitrix\Main\Type\DateTime())->add('-30 days'); } $skipTypes = []; if ($onlyCopilotOption === 'Y') { $ormParams['filter'][] = [ '=ITEM_TYPE' => \Bitrix\Im\V2\Chat::IM_TYPE_COPILOT ]; } elseif ($onlyOpenlinesOption === 'Y') { $ormParams['filter'][] = [ '=ITEM_TYPE' => IM_MESSAGE_OPEN_LINE ]; } elseif (!$byChatIds) { if (!Features::isCopilotInDefaultTabAvailable()) { $skipTypes[] = \Bitrix\Im\V2\Chat::IM_TYPE_COPILOT; } if ($options['SKIP_OPENLINES'] === 'Y') { $skipTypes[] = IM_MESSAGE_OPEN_LINE; } if ($skipChat === 'Y') { $skipTypes[] = IM_MESSAGE_OPEN; $skipTypes[] = IM_MESSAGE_CHAT; } if ($skipDialog === 'Y') { $skipTypes[] = IM_MESSAGE_PRIVATE; } if (!empty($skipTypes)) { $ormParams['filter'][] = [ '!@ITEM_TYPE' => $skipTypes ]; } } if (!isset($options['LAST_SYNC_DATE'])) { if (isset($options['OFFSET'])) { $ormParams['offset'] = $options['OFFSET']; } if (isset($options['LIMIT'])) { $ormParams['limit'] = $options['LIMIT']; } if (isset($options['ORDER'])) { $ormParams['order'] = $options['ORDER']; } } $result = []; $orm = \Bitrix\Im\Model\RecentTable::getList($ormParams); $rows = $orm->fetchAll(); $rows = self::prepareRows($rows, $userId); foreach ($rows as $row) { $isUser = $row['ITEM_TYPE'] == IM_MESSAGE_PRIVATE; $id = $isUser? (int)$row['ITEM_ID']: 'chat'.$row['ITEM_ID']; if ($isUser) { if (isset($result[$id]) && !$row['ITEM_MID']) { continue; } } else if (isset($result[$id])) { continue; } $item = self::formatRow($row, [ 'GENERAL_CHAT_ID' => $generalChatId, 'GET_ORIGINAL_TEXT' => $options['GET_ORIGINAL_TEXT'] ?? null, ]); if (!$item) { continue; } $result[$id] = $item; } $result = array_values($result); if ( $showOpenlines && !$options['ONLY_OPENLINES'] && class_exists('\Bitrix\ImOpenLines\Recent') ) { $options['ONLY_IN_QUEUE'] = true; $chatsInQueue = \Bitrix\ImOpenLines\Recent::getRecent($userId, $options); $result = array_merge($result, $chatsInQueue); } \Bitrix\Main\Type\Collection::sortByColumn( $result, ['PINNED' => SORT_DESC, 'MESSAGE' => SORT_DESC, 'ID' => SORT_DESC], [ 'ID' => function($row) { return $row; }, 'MESSAGE' => function($row) { return $row['DATE'] instanceof \Bitrix\Main\Type\DateTime ? $row['DATE']->getTimeStamp() : 0; }, ] ); if ($options['JSON']) { foreach ($result as $index => $item) { $result[$index] = self::jsonRow($item); } } return $result; } public static function getList($userId = null, $options = []) { $userId = \Bitrix\Im\Common::getUserId($userId); if (!$userId) { return false; } $generalChatId = \CIMChat::GetGeneralChatId(); $viewCommonUsers = Option::get('im', 'view_common_users', 'Y') === 'N' ? false : (bool)\CIMSettings::GetSetting(\CIMSettings::SETTINGS, 'viewCommonUsers') ; $onlyOpenlinesOption = $options['ONLY_OPENLINES'] ?? null; $onlyCopilotOption = $options['ONLY_COPILOT'] ?? null; $onlyChannelOption = $options['ONLY_CHANNEL'] ?? null; $canManageMessagesOption = $options['CAN_MANAGE_MESSAGES'] ?? null; $skipChatOption = $options['SKIP_CHAT'] ?? null; $skipDialogOption = $options['SKIP_DIALOG'] ?? null; $skipCollabOption = Collab::isAvailable() ? ($options['SKIP_COLLAB'] ?? null) : 'Y'; $lastMessageDateOption = $options['LAST_MESSAGE_DATE'] ?? null; $withoutCommonUsers = !$viewCommonUsers || $onlyOpenlinesOption === 'Y'; $unreadOnly = isset($options['UNREAD_ONLY']) && $options['UNREAD_ONLY'] === 'Y'; $shortInfo = isset($options['SHORT_INFO']) && $options['SHORT_INFO'] === 'Y'; $parseText = $options['PARSE_TEXT'] ?? null; $showOpenlines = ( \Bitrix\Main\Loader::includeModule('imopenlines') && ( $onlyOpenlinesOption === 'Y' || $options['SKIP_OPENLINES'] !== 'Y' ) ); $ormParams = self::getOrmParams([ 'USER_ID' => $userId, 'SHOW_OPENLINES' => $showOpenlines, 'WITHOUT_COMMON_USERS' => $withoutCommonUsers, 'UNREAD_ONLY' => $unreadOnly, 'SHORT_INFO' => $shortInfo, ]); if ($onlyCopilotOption === 'Y') { $ormParams['filter'][] = [ '=ITEM_TYPE' => \Bitrix\Im\V2\Chat::IM_TYPE_COPILOT ]; } elseif ($onlyOpenlinesOption === 'Y') { $ormParams['filter'][] = [ '=ITEM_TYPE' => IM_MESSAGE_OPEN_LINE ]; } elseif ($onlyChannelOption === 'Y') { $ormParams['filter'][] = [ '=ITEM_TYPE' => [\Bitrix\Im\V2\Chat::IM_TYPE_OPEN_CHANNEL, \Bitrix\Im\V2\Chat::IM_TYPE_CHANNEL], ]; } else { $skipTypes = []; if (!Features::isCopilotInDefaultTabAvailable()) { $skipTypes[] = \Bitrix\Im\V2\Chat::IM_TYPE_COPILOT; } if ($options['SKIP_OPENLINES'] === 'Y') { $skipTypes[] = IM_MESSAGE_OPEN_LINE; } if ($skipChatOption === 'Y') { $skipTypes[] = IM_MESSAGE_OPEN; $skipTypes[] = IM_MESSAGE_CHAT; } if ($skipDialogOption === 'Y') { $skipTypes[] = IM_MESSAGE_PRIVATE; } if ($skipCollabOption === 'Y') { $skipTypes[] = \Bitrix\Im\V2\Chat::IM_TYPE_COLLAB; } if (!RecentConfigManager::EXTERNAL_CHAT_USE_DEFAULT_RECENT_SECTION) { $skipTypes[] = \Bitrix\Im\V2\Chat::IM_TYPE_EXTERNAL; } if (!empty($skipTypes)) { $ormParams['filter'][] = [ '!@ITEM_TYPE' => $skipTypes ]; } } if ($lastMessageDateOption instanceof \Bitrix\Main\Type\DateTime) { $ormParams['filter']['<=DATE_LAST_ACTIVITY'] = $lastMessageDateOption; } else if (isset($options['OFFSET'])) { $ormParams['offset'] = $options['OFFSET']; } if (isset($options['LIMIT'])) { $ormParams['limit'] = (int)$options['LIMIT']; } else { $ormParams['limit'] = 50; } $sortOption = (new UserConfiguration((int)$userId))->getGeneralSettings()['pinnedChatSort']; if ($sortOption === 'byCost') { $ormParams['order'] = [ 'PINNED' => 'DESC', 'PIN_SORT' => 'ASC', 'DATE_LAST_ACTIVITY' => 'DESC', ]; } else { $ormParams['order'] = [ 'PINNED' => 'DESC', 'DATE_LAST_ACTIVITY' => 'DESC', ]; } if ($canManageMessagesOption === 'Y') { $ormParams = Permission\Filter::getRoleGetListFilter($ormParams, Permission\ActionGroup::ManageMessages, 'RELATION', 'CHAT'); } $orm = \Bitrix\Im\Model\RecentTable::getList($ormParams); $counter = 0; $result = []; $messageIdsWithCopilotRole = []; $copilotData = []; $chatsIds = []; $messagesAutoDeleteConfigs = []; $rows = $orm->fetchAll(); $rows = self::prepareRows($rows, $userId, $shortInfo); foreach ($rows as $row) { $counter++; $isUser = $row['ITEM_TYPE'] == IM_MESSAGE_PRIVATE; $id = $isUser? (int)$row['ITEM_ID']: 'chat'.$row['ITEM_ID']; if ($isUser) { if (isset($result[$id]) && !$row['ITEM_MID']) { continue; } } else if (isset($result[$id])) { continue; } $item = self::formatRow($row, [ 'GENERAL_CHAT_ID' => $generalChatId, 'WITHOUT_COMMON_USERS' => $withoutCommonUsers, 'GET_ORIGINAL_TEXT' => $options['GET_ORIGINAL_TEXT'] ?? null, 'SHORT_INFO' => $shortInfo, 'PARSE_TEXT' => $parseText, ]); if (!$item) { continue; } if ($row['ITEM_TYPE'] === \Bitrix\Im\V2\Chat::IM_TYPE_COPILOT) { $copilotChatRole = (new RoleManager())->getMainRole((int)$item['CHAT_ID']); if (isset($copilotChatRole)) { $copilotData['chats'][$item['ID']] = $copilotChatRole; } } if (!$shortInfo) { $chatsIds[] = (int)$item['CHAT_ID']; } if ( !$shortInfo && (isset($item['USER']['BOT']) && $item['USER']['BOT'] === true) && Loader::includeModule('imbot') && (int)$item['MESSAGE']['AUTHOR_ID'] === CopilotChatBot::getBotId() ) { $messageIdsWithCopilotRole[] = (int)$item['MESSAGE']['ID']; } if ($shortInfo && $options['JSON']) { $result[$id] = self::jsonRow($item); } else { $result[$id] = $item; } } if (!$shortInfo && !empty($messageIdsWithCopilotRole)) { $copilotMessageRoles = self::fillCopilotMessageRoles($messageIdsWithCopilotRole); foreach ($result as $item) { if (in_array((int)$item['MESSAGE']['ID'], $messageIdsWithCopilotRole, true)) { $copilotData['messages'][(int)$item['MESSAGE']['ID']] = $copilotMessageRoles[(int)$item['MESSAGE']['ID']] ?? RoleManager::getDefaultRoleCode() ; } } } $copilotData = !Features::isCopilotInDefaultTabAvailable() && $shortInfo ? [] : self::prepareCopilotData($copilotData, $userId, $shortInfo) ; if ($showOpenlines && !$onlyCopilotOption && Loader::includeModule('imopenlines')) { if (!isset($options['SKIP_UNDISTRIBUTED_OPENLINES']) || $options['SKIP_UNDISTRIBUTED_OPENLINES'] !== 'Y') { $recentOpenLines = \Bitrix\ImOpenLines\Recent::getRecent($userId, ['ONLY_IN_QUEUE' => true]); if (is_array($recentOpenLines)) { $result = array_merge($result, $recentOpenLines); } } } if (!$shortInfo) { $messagesAutoDeleteConfigs = (new MessagesAutoDeleteConfigs($chatsIds))->toRestFormat(); } $result = array_values($result); if ($options['JSON']) { if (!$shortInfo) { foreach ($result as $index => $item) { $result[$index] = self::jsonRow($item); } } $objectToReturn = [ 'items' => $result, 'hasMorePages' => $ormParams['limit'] == $counter, // TODO remove this later 'hasMore' => $ormParams['limit'] == $counter, 'copilot' => !empty($copilotData) ? $copilotData : null, 'messagesAutoDeleteConfigs' => $messagesAutoDeleteConfigs, ]; if (!isset($options['LAST_MESSAGE_DATE'])) { $objectToReturn['birthdayList'] = \Bitrix\Im\Integration\Intranet\User::getBirthdayForToday(); } return $objectToReturn; } $converter = new Converter(Converter::TO_SNAKE | Converter::TO_UPPER | Converter::KEYS); return [ 'ITEMS' => $result, 'HAS_MORE_PAGES' => $ormParams['limit'] == $counter, // TODO remove this later 'HAS_MORE' => $ormParams['limit'] == $counter, 'COPILOT' => !empty($copilotData) ? $converter->process($copilotData) : null, 'MESSAGES_AUTO_DELETE_CONFIGS' => $converter->process($messagesAutoDeleteConfigs) ?? [], ]; } private static function fillCopilotMessageRoles(array $messageIdsWithCopilotRole): array { $copilotMessageRoles = []; $collection = Param::getDataClass()::query() ->setSelect(['MESSAGE_ID', 'PARAM_VALUE']) ->whereIn('MESSAGE_ID', $messageIdsWithCopilotRole) ->where('PARAM_NAME', \Bitrix\Im\V2\Message\Params::COPILOT_ROLE) ->fetchCollection() ; foreach ($collection as $item) { $copilotMessageRoles[(int)$item->getMessageId()] = $item->getParamValue(); } return $copilotMessageRoles; } private static function prepareCopilotData(array $copilotData, int $userId, bool $shortInfo): array { $roleManager = (new RoleManager())->setContextUser($userId); $recentCopilotRoles = !$shortInfo ? $roleManager->getRecentKeyRoles() : []; $copilotRoles = array_values(array_merge( $copilotData['chats'] ?? [], $copilotData['messages'] ?? [], $recentCopilotRoles )); $chats = CopilotPopupItem::convertArrayData($copilotData['chats'] ?? [], Entity::Chats); $messages = CopilotPopupItem::convertArrayData($copilotData['messages'] ?? [], Entity::Messages); $roles = $shortInfo ? $roleManager->getRolesShort($copilotRoles) : $roleManager->getRoles($copilotRoles) ; return [ 'chats' => !empty($chats) ? $chats : null, 'messages' => !empty($messages) ? $messages : null, 'roles' => !empty($roles) ? $roles : null, 'recommendedRoles' => !empty($recentCopilotRoles) ? $recentCopilotRoles : null, ]; } public static function getElement($itemType, $itemId, $userId = null, $options = []) { $userId = \Bitrix\Im\Common::getUserId($userId); if (!$userId) { return false; } $generalChatId = \CIMChat::GetGeneralChatId(); $ormParams = self::getOrmParams([ 'USER_ID' => $userId, 'SHOW_OPENLINES' => $itemType === IM_MESSAGE_OPEN_LINE, 'WITHOUT_COMMON_USERS' => true, ]); $ormParams['filter']['=ITEM_TYPE'] = $itemType; $ormParams['filter']['=ITEM_ID'] = $itemId; $orm = \Bitrix\Im\Model\RecentTable::getList([ 'select' => $ormParams['select'], 'filter' => $ormParams['filter'], 'runtime' => $ormParams['runtime'], ]); $result = null; $rows = $orm->fetchAll(); $rows = self::prepareRows($rows, $userId); foreach ($rows as $row) { $isUser = $row['ITEM_TYPE'] == IM_MESSAGE_PRIVATE; if ($isUser) { if ($result && !$row['ITEM_MID']) { continue; } } else if ($result) { continue; } $item = self::formatRow($row, [ 'GENERAL_CHAT_ID' => $generalChatId, ]); if (!$item) { continue; } $result = $item; } $result = self::prepareRows([$result], $userId)[0]; if ($options['JSON']) { $result = self::jsonRow($result); } return $result; } private static function getOrmParams($params) { $userId = (int)$params['USER_ID']; $showOpenlines = \Bitrix\Main\Loader::includeModule('imopenlines') && $params['SHOW_OPENLINES'] !== false; $isIntranetInstalled = \Bitrix\Main\Loader::includeModule('intranet'); $isIntranet = $isIntranetInstalled && \Bitrix\Intranet\Util::isIntranetUser($userId); $withoutCommonUsers = $params['WITHOUT_COMMON_USERS'] === true || !$isIntranet; $unreadOnly = isset($params['UNREAD_ONLY']) && $params['UNREAD_ONLY'] === true; $shortInfo = isset($params['SHORT_INFO']) && $params['SHORT_INFO'] === true; $chatIds = $params['CHAT_IDS'] ?? null; $shortInfoFields = [ 'USER_ID', 'ITEM_TYPE', 'ITEM_ID', 'ITEM_MID', 'ITEM_CID', 'PINNED', 'UNREAD', 'DATE_MESSAGE', 'DATE_LAST_ACTIVITY', 'PIN_SORT', 'RELATION_ID' => 'RELATION.ID', 'RELATION_NOTIFY_BLOCK' => 'RELATION.NOTIFY_BLOCK', 'RELATION_IS_MANAGER' => 'RELATION.MANAGER', 'CHAT_ID' => 'CHAT.ID', 'CHAT_TITLE' => 'CHAT.TITLE', 'CHAT_TYPE' => 'CHAT.TYPE', 'CHAT_AVATAR' => 'CHAT.AVATAR', 'CHAT_AUTHOR_ID' => 'CHAT.AUTHOR_ID', 'CHAT_COLOR' => 'CHAT.COLOR', 'CHAT_ENTITY_TYPE' => 'CHAT.ENTITY_TYPE', 'CHAT_CAN_POST' => 'CHAT.CAN_POST', 'CHAT_EXTRANET' => 'CHAT.EXTRANET', 'USER_LAST_ACTIVITY_DATE' => 'USER.LAST_ACTIVITY_DATE', ]; $additionalInfoFields = [ 'ITEM_OLID', 'DATE_UPDATE', 'MESSAGE_ID' => 'MESSAGE.ID', 'MESSAGE_AUTHOR_ID' => 'MESSAGE.AUTHOR_ID', 'MESSAGE_TEXT' => 'MESSAGE.MESSAGE', 'MESSAGE_USER_LAST_ACTIVITY_DATE' => 'MESSAGE.AUTHOR.LAST_ACTIVITY_DATE', 'USER_EMAIL' => 'USER.EMAIL', 'MESSAGE_UUID_VALUE' => 'MESSAGE_UUID.UUID', 'CHAT_MANAGE_USERS_ADD' => 'CHAT.MANAGE_USERS_ADD', 'CHAT_MANAGE_USERS_DELETE' => 'CHAT.MANAGE_USERS_DELETE', 'CHAT_MANAGE_UI' => 'CHAT.MANAGE_UI', 'CHAT_MANAGE_SETTINGS' => 'CHAT.MANAGE_SETTINGS', 'CHAT_LAST_MESSAGE_STATUS_BOOL' => 'MESSAGE.NOTIFY_READ', 'RELATION_LAST_ID' => 'RELATION.LAST_ID', 'CHAT_PARENT_ID' => 'CHAT.PARENT_ID', 'CHAT_PARENT_MID' => 'CHAT.PARENT_MID', 'CHAT_ENTITY_ID' => 'CHAT.ENTITY_ID', 'CHAT_ENTITY_DATA_1' => 'CHAT.ENTITY_DATA_1', 'CHAT_ENTITY_DATA_2' => 'CHAT.ENTITY_DATA_2', 'CHAT_ENTITY_DATA_3' => 'CHAT.ENTITY_DATA_3', 'CHAT_DATE_CREATE' => 'CHAT.DATE_CREATE', 'CHAT_USER_COUNT' => 'CHAT.USER_COUNT', ]; $shortRuntime = [ new \Bitrix\Main\Entity\ReferenceField( 'USER', '\Bitrix\Main\UserTable', array("=this.ITEM_TYPE" => new \Bitrix\Main\DB\SqlExpression("?s", IM_MESSAGE_PRIVATE), "=ref.ID" => "this.ITEM_ID"), array("join_type"=>"LEFT") ), ]; if ($shortInfo) { $shortRuntime[] = new \Bitrix\Main\Entity\ReferenceField( 'CODE', '\Bitrix\Im\Model\MessageParamTable', [ "=ref.MESSAGE_ID" => "this.ITEM_MID", "=ref.PARAM_NAME" => new \Bitrix\Main\DB\SqlExpression("?s", "CODE") ], ["join_type" => "LEFT"] ); $shortInfoFields['MESSAGE_CODE'] = 'CODE.PARAM_VALUE'; } $unreadTable = MessageUnreadTable::getTableName(); $additionalRuntime = [ new ExpressionField( 'HAS_UNREAD_MESSAGE', "EXISTS(SELECT 1 FROM {$unreadTable} WHERE CHAT_ID = %s AND USER_ID = %s)", ['ITEM_CID', 'USER_ID'] ) ]; $select = $shortInfo ? $shortInfoFields : array_merge($shortInfoFields, $additionalInfoFields); $runtime = $shortInfo ? $shortRuntime : array_merge($shortRuntime, $additionalRuntime); if (!$withoutCommonUsers) { $select['INVITATION_ORIGINATOR_ID'] = 'INVITATION.ORIGINATOR_ID'; } if ($showOpenlines) { $select['LINES_ID'] = 'LINES.ID'; $select['LINES_STATUS'] = 'LINES.STATUS'; $select['LINES_DATE_CREATE'] = 'LINES.DATE_CREATE'; } if (!$withoutCommonUsers) { $runtime[] = new \Bitrix\Main\Entity\ReferenceField( 'INVITATION', '\Bitrix\Intranet\Internals\InvitationTable', array("=this.ITEM_TYPE" => new \Bitrix\Main\DB\SqlExpression("?s", IM_MESSAGE_PRIVATE), "=ref.USER_ID" => "this.ITEM_ID"), array("join_type"=>"LEFT") ); } if ($showOpenlines && !$shortInfo) { $runtime[] = new \Bitrix\Main\Entity\ReferenceField( 'LINES', '\Bitrix\ImOpenlines\Model\SessionTable', [">this.ITEM_OLID" => new \Bitrix\Main\DB\SqlExpression("?i", 0), "=ref.ID" => "this.ITEM_OLID"], ["join_type" => "LEFT"] ); } if ($withoutCommonUsers) { $filter = ['=USER_ID' => $userId]; } else { $filter = ['@USER_ID' => [$userId, 0]]; } if ($isIntranetInstalled && !$isIntranet) { $subQuery = Group::getExtranetAccessibleUsersQuery($userId); if ($subQuery !== null) { $filter[] = [ 'LOGIC' => 'OR', ['!=ITEM_TYPE' => 'P'], ['@USER.ID' => new SqlExpression($subQuery->getQuery())], ]; } } if ($unreadOnly) { $filter[] = [ 'LOGIC' => 'OR', ['==HAS_UNREAD_MESSAGE' => true], ['=UNREAD' => true], ]; } if ($chatIds) { $filter['@ITEM_CID'] = $chatIds; // todo: add index } return [ 'select' => $select, 'filter' => $filter, 'runtime' => $runtime, ]; } private static function formatRow($row, $options = []): ?array { $generalChatId = (int)$options['GENERAL_CHAT_ID']; $withoutCommonUsers = isset($options['WITHOUT_COMMON_USERS']) && $options['WITHOUT_COMMON_USERS'] === true; $shortInfo = isset($options['SHORT_INFO']) && $options['SHORT_INFO']; $isUser = $row['ITEM_TYPE'] == IM_MESSAGE_PRIVATE; $id = $isUser? (int)$row['ITEM_ID']: 'chat'.$row['ITEM_ID']; $row['MESSAGE_ID'] ??= null; if (!$isUser && ((!$row['MESSAGE_ID'] && !$shortInfo) || !$row['RELATION_ID'] || !$row['CHAT_ID'])) { return null; } $item = self::formatItem($row,$options, $shortInfo, $isUser, $id); if ($isUser) { if ( $withoutCommonUsers && ($row['USER_ID'] == 0 || $row['MESSAGE_CODE'] === 'USER_JOIN') ) { return null; } $row['INVITATION_ORIGINATOR_ID'] ??= null; if (!$row['USER_LAST_ACTIVITY_DATE'] && $row['INVITATION_ORIGINATOR_ID']) { $item['INVITED'] = [ 'ORIGINATOR_ID' => (int)$row['INVITATION_ORIGINATOR_ID'], 'CAN_RESEND' => !empty($row['USER_EMAIL']) ]; } $item['USER'] = [ 'ID' => (int)$row['ITEM_ID'], ]; $item['CHAT']['TEXT_FIELD_ENABLED'] = self::getTextFieldEnabled((int)$row['ITEM_CID']); $item['CHAT']['BACKGROUND_ID'] = self::getBackgroundId((int)$row['ITEM_CID']); } else { $item['CHAT'] = self::formatChat($row, $shortInfo, $generalChatId); if (!$shortInfo) { $item['AVATAR'] = [ 'URL' => $item['CHAT']['AVATAR'], 'COLOR' => $item['CHAT']['COLOR'], ]; $item['TITLE'] = $row['CHAT_TITLE']; } if ($row["CHAT_ENTITY_TYPE"] == 'LINES') { $item['LINES'] = [ 'ID' => (int)$row['LINES_ID'], 'STATUS' => (int)$row['LINES_STATUS'], 'DATE_CREATE' => $row['LINES_DATE_CREATE'] ?? $row['DATE_UPDATE'], ]; } $item['USER'] = [ 'ID' => (int)($row['MESSAGE_AUTHOR_ID'] ?? 0), ]; } if ($item['USER']['ID'] > 0) { $user = self::formatUser($row, $item, $shortInfo); if ($user === null) { return null; } $item['USER'] = $user; if ( !$shortInfo && $item['TYPE'] == 'user' && !empty($user) ) { $item['AVATAR'] = [ 'URL' => $user['AVATAR'], 'COLOR' => $user['COLOR'] ]; $item['TITLE'] = $user['NAME']; } } $item['OPTIONS'] = []; if ($row['USER_ID'] == 0 || $row['MESSAGE_CODE'] === 'USER_JOIN') { $item['OPTIONS']['DEFAULT_USER_RECORD'] = true; } return $item; } private static function formatMessage(array $row, array $options, bool $shortInfo): array { $row['DATE_MESSAGE'] ??= null; if ($shortInfo) { return [ 'ID' => (int)($row['ITEM_MID'] ?? 0), 'DATE' => $row['DATE_MESSAGE'] ?: $row['DATE_LAST_ACTIVITY'], ]; } if (!$row['ITEM_MID'] || !$row['MESSAGE_ID']) { return [ 'ID' => (int)($row['ITEM_MID'] ?? 0), 'TEXT' => "", 'FILE' => false, 'AUTHOR_ID' => 0, 'ATTACH' => false, 'DATE' => $row['DATE_MESSAGE']?: $row['DATE_UPDATE'], 'STATUS' => $row['CHAT_LAST_MESSAGE_STATUS'], ]; } $attach = false; if ($row['MESSAGE_ATTACH'] || $row["MESSAGE_ATTACH_JSON"]) { if (preg_match('/^(\d+)$/', $row['MESSAGE_ATTACH'])) { $attach = true; } else if ($row['MESSAGE_ATTACH'] === \CIMMessageParamAttach::FIRST_MESSAGE) { try { $value = \Bitrix\Main\Web\Json::decode($row["MESSAGE_ATTACH_JSON"]); $attachRestored = \CIMMessageParamAttach::PrepareAttach($value); $attach = $attachRestored['DESCRIPTION']; } catch (\Bitrix\Main\SystemException $e) { $attach = true; } } else if (!empty($row['MESSAGE_ATTACH'])) { $attach = $row['MESSAGE_ATTACH']; } else { $attach = true; } } $text = $row['MESSAGE_TEXT'] ?? ''; $getOriginalTextOption = $options['GET_ORIGINAL_TEXT'] ?? null; $parseText = $options['PARSE_TEXT'] ?? null; if ($parseText === 'Y') { $text = Text::parse($text); } elseif ($getOriginalTextOption === 'Y') { $text = Text::populateUserBbCode($text); } else { $text = Text::removeBbCodes( str_replace("\n", " ", $text), $row['MESSAGE_FILE'] > 0, $attach ); } return [ 'ID' => (int)$row['ITEM_MID'], 'TEXT' => $text, 'FILE' => $row['MESSAGE_FILE'], 'AUTHOR_ID' => (int)$row['MESSAGE_AUTHOR_ID'], 'ATTACH' => $attach, 'DATE' => $row['DATE_MESSAGE']?: $row['DATE_UPDATE'], 'STATUS' => $row['CHAT_LAST_MESSAGE_STATUS'], 'UUID' => $row['MESSAGE_UUID_VALUE'], ]; } private static function formatItem( array $row, array $options, bool $shortInfo, bool $isUser, mixed $id ): array { $message = self::formatMessage($row, $options, $shortInfo); if ($shortInfo) { return [ 'ID' => $id, 'CHAT_ID' => (int)$row['CHAT_ID'], 'TYPE' => $isUser ? 'user' : 'chat', 'MESSAGE' => $message, 'COUNTER' => (int)$row['COUNTER'], 'PINNED' => $row['PINNED'] === 'Y', 'UNREAD' => $row['UNREAD'] === 'Y', 'DATE_LAST_ACTIVITY' => $row['DATE_LAST_ACTIVITY'], ]; } return [ 'ID' => $id, 'CHAT_ID' => (int)$row['CHAT_ID'], 'TYPE' => $isUser ? 'user' : 'chat', 'AVATAR' => [], 'TITLE' => [], 'MESSAGE' => $message, 'COUNTER' => (int)$row['COUNTER'], 'LAST_ID' => (int)($row['RELATION_LAST_ID'] ?? 0), 'PINNED' => $row['PINNED'] === 'Y', 'UNREAD' => $row['UNREAD'] === 'Y', 'HAS_REMINDER' => isset($row['HAS_REMINDER']) && $row['HAS_REMINDER'] === 'Y', 'DATE_UPDATE' => $row['DATE_UPDATE'], 'DATE_LAST_ACTIVITY' => $row['DATE_LAST_ACTIVITY'], ]; } private static function formatChat(array $row, bool $shortInfo, int $generalChatId): array { $avatar = \CIMChat::GetAvatarImage($row['CHAT_AVATAR'], 200, false); $color = $row['CHAT_COLOR'] <> '' ? Color::getColor($row['CHAT_COLOR']) : Color::getColorByNumber( $row['ITEM_ID'] ); $chatType = \Bitrix\Im\Chat::getType($row); if ($generalChatId == $row['ITEM_ID']) { $row["CHAT_ENTITY_TYPE"] = 'GENERAL'; } $muteList = []; if ($row['RELATION_NOTIFY_BLOCK'] == 'Y') { $muteList = [$row['RELATION_USER_ID'] => true]; } if ($shortInfo) { return [ 'ID' => (int)$row['ITEM_CID'], 'NAME' => $row['CHAT_TITLE'], 'EXTRANET' => $row['CHAT_EXTRANET'] == 'Y', 'CONTAINS_COLLABER' => self::containsCollaber((int)$row['ITEM_CID']), 'AVATAR' => $avatar, 'COLOR' => $color, 'TYPE' => $chatType, 'ENTITY_TYPE' => (string)$row['CHAT_ENTITY_TYPE'], 'MUTE_LIST' => $muteList, 'ROLE' => self::getRole($row), 'TEXT_FIELD_ENABLED' => self::getTextFieldEnabled((int)$row['ITEM_CID']), 'BACKGROUND_ID' => self::getBackgroundId((int)$row['ITEM_CID']), 'PERMISSIONS' => [ 'MANAGE_MESSAGES' => mb_strtolower($row['CHAT_CAN_POST'] ?? ''), ], ]; } $publicOption = null; if ($row["CHAT_ENTITY_TYPE"] === \Bitrix\Im\V2\Chat::ENTITY_TYPE_VIDEOCONF) { $publicOption = \Bitrix\Im\V2\Chat::getInstance((int)$row['ITEM_CID'])->getPublicOption(); } $managerList = []; if ($row['CHAT_OWNER'] ?? null == $row['RELATION_USER_ID'] || $row['RELATION_IS_MANAGER'] == 'Y') { $managerList = [(int)$row['RELATION_USER_ID']]; } $chatOptions = \CIMChat::GetChatOptions(); $restrictions = $chatOptions['DEFAULT']; if ($row['CHAT_ENTITY_TYPE'] && array_key_exists($row['CHAT_ENTITY_TYPE'], $chatOptions)) { $restrictions = $chatOptions[$row['CHAT_ENTITY_TYPE']]; } return [ 'ID' => (int)$row['ITEM_CID'], 'PARENT_CHAT_ID' => (int)$row['CHAT_PARENT_ID'], 'PARENT_MESSAGE_ID' => (int)$row['CHAT_PARENT_MID'], 'NAME' => $row['CHAT_TITLE'], 'OWNER' => (int)$row['CHAT_AUTHOR_ID'], 'EXTRANET' => $row['CHAT_EXTRANET'] == 'Y', 'CONTAINS_COLLABER' => self::containsCollaber((int)$row['ITEM_CID']), 'AVATAR' => $avatar, 'COLOR' => $color, 'TYPE' => $chatType, 'ENTITY_TYPE' => (string)$row['CHAT_ENTITY_TYPE'], 'ENTITY_ID' => (string)$row['CHAT_ENTITY_ID'], 'ENTITY_DATA_1' => (string)$row['CHAT_ENTITY_DATA_1'], 'ENTITY_DATA_2' => (string)$row['CHAT_ENTITY_DATA_2'], 'ENTITY_DATA_3' => (string)$row['CHAT_ENTITY_DATA_3'], 'MUTE_LIST' => $muteList, 'MANAGER_LIST' => $managerList, 'DATE_CREATE' => $row['CHAT_DATE_CREATE'], 'MESSAGE_TYPE' => $row["CHAT_TYPE"], 'USER_COUNTER' => (int)$row['CHAT_USER_COUNT'], 'RESTRICTIONS' => $restrictions, 'ROLE' => self::getRole($row), 'TEXT_FIELD_ENABLED' => self::getTextFieldEnabled((int)$row['ITEM_CID']), 'BACKGROUND_ID' => self::getBackgroundId((int)$row['ITEM_CID']), 'ENTITY_LINK' => EntityLink::getInstance(\CIMChat::initChatByArray($row))->toArray(), 'PERMISSIONS' => [ 'MANAGE_USERS_ADD' => mb_strtolower($row['CHAT_MANAGE_USERS_ADD'] ?? ''), 'MANAGE_USERS_DELETE' => mb_strtolower($row['CHAT_MANAGE_USERS_DELETE'] ?? ''), 'MANAGE_UI' => mb_strtolower($row['CHAT_MANAGE_UI'] ?? ''), 'MANAGE_SETTINGS' => mb_strtolower($row['CHAT_MANAGE_SETTINGS'] ?? ''), 'MANAGE_MESSAGES' => mb_strtolower($row['CHAT_CAN_POST'] ?? ''), 'CAN_POST' => mb_strtolower($row['CHAT_CAN_POST'] ?? ''), ], 'PUBLIC' => $publicOption ?? '', ]; } private static function containsCollaber(int $chatId): bool { if ($chatId <= 0) { return false; } $paramsService = Params::getInstance($chatId); return (bool)$paramsService->get(Params::CONTAINS_COLLABER)?->getValue(); } private static function getTextFieldEnabled(int $chatId): bool { return (new TextFieldEnabled($chatId))->get(); } private static function getBackgroundId(int $chatId): ?string { return (new Background($chatId))->get(); } private static function getChatMessagesAutoDeleteConfigs(int $chatId): array { $config = (new MessagesAutoDeleteConfigs([$chatId]))->toRestFormat(); return (new Converter(Converter::TO_SNAKE | Converter::TO_UPPER | Converter::KEYS))->process($config); } private static function formatUser(array $row, array $item, bool $shortInfo): ?array { $userObject = \Bitrix\Im\V2\Entity\User\User::getInstance($item['USER']['ID']); if ($userObject instanceof NullUser) { return []; } $user = $userObject->getArray(['WITHOUT_ONLINE' => true, 'USER_SHORT_FORMAT' => $shortInfo]); if ($shortInfo) { if (!$userObject->isActive()) { return null; } return $user; } if ($item['TYPE'] == 'user') { if ( !empty($user['BOT_DATA']) && Loader::includeModule('imbot') && $user['BOT_DATA']['code'] === CopilotChatBot::BOT_CODE ) { return null; } if ( (!$user['ACTIVE'] && $item['COUNTER'] <= 0) && !$user['BOT'] && !$user['CONNECTOR'] && !$user['NETWORK'] ) { return null; } } if ($item['TYPE'] == 'user') { $lastActivityDate = $row['USER_LAST_ACTIVITY_DATE'] ?? null; } else { $lastActivityDate = $row['MESSAGE_USER_LAST_ACTIVITY_DATE'] ?? null; } $user['LAST_ACTIVITY_DATE'] = $lastActivityDate ?: false; $user['DESKTOP_LAST_DATE'] = false; $user['MOBILE_LAST_DATE'] = false; $user['IDLE'] = false; return $user; } private static function jsonRow($item) { if (!is_array($item)) { return $item; } foreach ($item as $key => $value) { if ($value instanceof \Bitrix\Main\Type\DateTime) { $item[$key] = date('c', $value->getTimestamp()); } else if (is_array($value)) { foreach ($value as $subKey => $subValue) { if ($subValue instanceof \Bitrix\Main\Type\DateTime) { $value[$subKey] = date('c', $subValue->getTimestamp()); } else if ( is_string($subValue) && $subValue && in_array($subKey, ['URL', 'AVATAR']) && mb_strpos($subValue, 'http') !== 0 ) { $value[$subKey] = \Bitrix\Im\Common::getPublicDomain().$subValue; } else if (is_array($subValue)) { $value[$subKey] = array_change_key_case($subValue, CASE_LOWER); } } $item[$key] = array_change_key_case($value, CASE_LOWER); } } return array_change_key_case($item, CASE_LOWER); } public static function pin($dialogId, $pin, $userId = null) { $userId = \Bitrix\Im\Common::getUserId($userId); if (!$userId) { return false; } $pinnedCount = \Bitrix\Im\Model\RecentTable::getCount(['=USER_ID' => $userId, '=PINNED' => 'Y']); self::$limitError = false; if ($pin && (int)$pinnedCount >= self::PINNED_CHATS_LIMIT) { self::$limitError = true; return false; } $pin = $pin === true? 'Y': 'N'; $id = $dialogId; $chatId = 0; if (mb_substr($dialogId, 0, 4) == 'chat') { $itemTypes = \Bitrix\Im\Chat::getTypes(); $id = mb_substr($dialogId, 4); $chatId = (int)$id; } else { $itemTypes = IM_MESSAGE_PRIVATE; $chatId = \Bitrix\Im\Dialog::getChatId($dialogId); } $element = \Bitrix\Im\Model\RecentTable::getList( [ 'select' => ['USER_ID', 'ITEM_TYPE', 'ITEM_ID', 'PINNED', 'PIN_SORT'], 'filter' => [ '=USER_ID' => $userId, '=ITEM_TYPE' => $itemTypes, '=ITEM_ID' => $id ] ] )->fetch(); if (!$element) { return false; // if (mb_substr($dialogId, 0, 4) == 'chat') // { // if (!\Bitrix\Im\Dialog::hasAccess($dialogId)) // { // return false; // } // // $missingChat = \Bitrix\Im\Model\ChatTable::getRowById($id); // $itemTypes = $missingChat['TYPE']; // } // $messageId = 0; // $relationId = 0; // if ($itemTypes !== IM_MESSAGE_OPEN) // { // } $relationData = \Bitrix\Im\Model\RelationTable::getList( [ 'select' => ['ID', 'LAST_MESSAGE_ID' => 'CHAT.LAST_MESSAGE_ID'], 'filter' => [ '=CHAT_ID' => $chatId, '=USER_ID' => $userId, ] ] )->fetchAll()[0]; $messageId = $relationData['LAST_MESSAGE_ID']; $relationId = $relationData['ID']; $addResult = \Bitrix\Im\Model\RecentTable::add( [ 'USER_ID' => $userId, 'ITEM_TYPE' => $itemTypes, 'ITEM_ID' => $id, 'ITEM_MID' => $messageId, 'ITEM_RID' => $relationId, 'ITEM_CID' => $chatId, 'DATE_UPDATE' => new \Bitrix\Main\Type\DateTime() ] ); if (!$addResult->isSuccess()) { return false; } // self::show($id); $element['USER_ID'] = $userId; $element['ITEM_TYPE'] = $itemTypes; $element['ITEM_ID'] = $id; } if ($element['PINNED'] == $pin) { return true; } $connection = Application::getConnection(); $connection->lock("PIN_SORT_CHAT_{$userId}", 10); if ($pin === 'Y') { self::increasePinSortCost($userId); } else { $pinSort = $element['PIN_SORT'] ? (int)$element['PIN_SORT'] : null; self::decreasePinSortCost($userId, $pinSort); } \Bitrix\Im\Model\RecentTable::update( [ 'USER_ID' => $element['USER_ID'], 'ITEM_TYPE' => $element['ITEM_TYPE'], 'ITEM_ID' => $element['ITEM_ID'], ], [ 'PINNED' => $pin, 'DATE_UPDATE' => new \Bitrix\Main\Type\DateTime(), 'PIN_SORT' => ($pin === 'Y') ? 1 : null, ] ); $connection->unlock("PIN_SORT_CHAT_{$userId}"); $chat = \Bitrix\Im\V2\Chat::getInstance($chatId); Sync\Logger::getInstance()->add( new Sync\Event(Sync\Event::ADD_EVENT, Sync\Event::CHAT_ENTITY, $chatId), $userId, $chat ); self::clearCache($element['USER_ID']); $pullInclude = \Bitrix\Main\Loader::includeModule("pull"); if ($pullInclude) { \Bitrix\Pull\Event::add( $userId, [ 'module_id' => 'im', 'command' => 'chatPin', 'expiry' => 3600, 'params' => [ 'dialogId' => $dialogId, 'active' => $pin == 'Y' ], 'extra' => \Bitrix\Im\Common::getPullExtra() ] ); } return true; } private static function increasePinSortCost(int $userId): void { $caseField = new SqlExpression('?# + 1', 'PIN_SORT'); RecentTable::updateByFilter( [ '=PINNED' => 'Y', '=USER_ID' => $userId, '>=PIN_SORT' => 1, ], ['PIN_SORT' => $caseField] ); } private static function decreasePinSortCost(int $userId, ?int $pinSort) { if (!isset($pinSort)) { return; } $caseField = new SqlExpression('?# - 1', 'PIN_SORT'); RecentTable::updateByFilter( [ '=PINNED' => 'Y', '=USER_ID' => $userId, '>PIN_SORT' => $pinSort, ], ['PIN_SORT' => $caseField] ); } public static function sortPin(\Bitrix\Im\V2\Chat $chat, int $newPosition, int $userId): void { $connection = Application::getConnection(); $connection->lock("PIN_SORT_CHAT_{$userId}", 10); $query = RecentTable::query() ->setSelect(['PIN_SORT']) ->setLimit(1) ->where('USER_ID', $userId) ->where('ITEM_CID', (int)$chat->getChatId()) ->where('PINNED', 'Y') ->fetch() ; if (!$query) { $connection->unlock("PIN_SORT_CHAT_{$userId}"); return; } $currentCost = (int)$query['PIN_SORT']; $query = RecentTable::query() ->setSelect(['PIN_SORT']) ->setOrder(['PIN_SORT']) ->setOffset($newPosition - 1) ->setLimit(1) ->where('PINNED', 'Y') ->where('USER_ID', $userId) ->fetch() ; if (!$query) { $connection->unlock("PIN_SORT_CHAT_{$userId}"); return; } $newCost = (int)$query['PIN_SORT']; if ($currentCost === $newCost) { $connection->unlock("PIN_SORT_CHAT_{$userId}"); return; } if ($currentCost < $newCost) { $caseField = new SqlExpression( "CASE WHEN ?# = ?i THEN ?i WHEN ?# > ?i AND ?# <= ?i THEN ?# - 1 END", 'PIN_SORT', $currentCost, $newCost, 'PIN_SORT', $currentCost, 'PIN_SORT', $newCost, 'PIN_SORT' ); $filter = [ '=PINNED' => 'Y', '=USER_ID' => $userId, '>=PIN_SORT' => $currentCost, '<=PIN_SORT' => $newCost, ]; } else { $caseField = new SqlExpression( "CASE WHEN ?# = ?i THEN ?i WHEN ?# >= ?i AND ?# < ?i THEN ?# + 1 END", 'PIN_SORT', $currentCost, $newCost, 'PIN_SORT', $newCost, 'PIN_SORT', $currentCost, 'PIN_SORT' ); $filter = [ '=PINNED' => 'Y', '=USER_ID' => $userId, '>=PIN_SORT' => $newCost, '<=PIN_SORT' => $currentCost, ]; } RecentTable::updateByFilter( $filter, ['PIN_SORT' => $caseField] ); $connection->unlock("PIN_SORT_CHAT_{$userId}"); } public static function getPinLimit(): int { return self::PINNED_CHATS_LIMIT ?? 25; } public static function updatePinSortCost(int $userId): void { $connection = Application::getConnection(); $connection->lock("PIN_SORT_CHAT_{$userId}", 10); $caseField = new SqlExpression('?#', 'ITEM_MID'); RecentTable::updateByFilter( [ '=PINNED' => 'Y', '=USER_ID' => $userId ], ['PIN_SORT' => $caseField] ); $connection->unlock("PIN_SORT_CHAT_{$userId}"); } public static function updateByFilter(array $filter, array $fields): void { RecentTable::updateByFilter($filter, $fields); } public static function raiseChat(\Bitrix\Im\V2\Chat $chat, RelationCollection $relations, ?DateTime $lastActivity = null): void { $userIds = $relations->getUserIds(); if (empty($userIds)) { return; } $message = new Message($chat->getLastMessageId()); $dateMessage = $message->getDateCreate() ?? new DateTime(); $dateCreate = $lastActivity ?? $dateMessage; $fields = []; foreach ($relations as $relation) { $userId = $relation->getUserId(); if ($userId) { $fields[] = [ 'USER_ID' => $userId, 'ITEM_TYPE' => $chat->getType(), 'ITEM_ID' => $chat->getId(), 'ITEM_MID' => $chat->getLastMessageId(), 'ITEM_CID' => $chat->getId(), 'ITEM_RID' => $relation->getId(), 'DATE_MESSAGE' => $dateMessage, 'DATE_UPDATE' => $dateCreate, 'DATE_LAST_ACTIVITY' => $dateCreate, ]; } } static::merge($fields, ['DATE_LAST_ACTIVITY' => $dateCreate, 'DATE_UPDATE' => $dateCreate]); Sync\Logger::getInstance()->add( new Sync\Event(Sync\Event::ADD_EVENT, Sync\Event::CHAT_ENTITY, $chat->getId()), $userIds, $chat ); static::sendPullRecentUpdate($chat, $userIds, $dateCreate); } public static function sendPullRecentUpdate(\Bitrix\Im\V2\Chat $chat, array $userIds, ?DateTime $lastCommentDate): void { $messages = new MessagePopupItem([$chat->getLastMessageId()], true); $restAdapter = new RestAdapter($messages); $pull = $restAdapter->toRestFormat([ 'WITHOUT_OWN_REACTIONS' => true, 'MESSAGE_ONLY_COMMON_FIELDS' => true, ]); $pull['chat'] = $chat->toPullFormat(); $pull['lastActivityDate'] = $lastCommentDate; $pull['counterType'] = $chat->getCounterType()->value; $pull['recentConfig'] = $chat->getRecentConfig()->toPullFormat(); $event = [ 'module_id' => 'im', 'command' => 'recentUpdate', 'params' => $pull, 'extra' => Common::getPullExtra() ]; $events = PushService::getEventGroups($event, $userIds, $chat->getId()); foreach ($events as $event) { Event::add($event['users'], $event['event']); } } public static function merge(array $fields, array $update): void { RecentTable::multiplyMerge($fields, $update, ['USER_ID', 'ITEM_TYPE', 'ITEM_ID']); } public static function getUsersOutOfRecent(\Bitrix\Im\V2\Chat $chat): array { $relations = $chat->getRelations()->filterActive(); $users = $relations->getUserIds(); $usersAlreadyInRecentRows = RecentTable::query() ->setSelect(['USER_ID']) ->where('ITEM_CID', $chat->getId()) ->whereIn('USER_ID', $users) ->fetchAll() ; foreach ($usersAlreadyInRecentRows as $row) { $userId = (int)$row['USER_ID']; unset($users[$userId]); } return $users; } public static function unread($dialogId, $unread, $userId = null, ?int $markedId = null, ?string $itemTypes = null) { $userId = \Bitrix\Im\Common::getUserId($userId); if (!$userId) { return false; } $unread = $unread === true? 'Y': 'N'; $element = self::getUnreadElement($userId, $itemTypes, $dialogId); if (!$element) { return false; } if ($element['UNREAD'] === $unread && !isset($markedId)) { return true; } self::$unreadElementCache[$userId][$dialogId] = null; $updatedFields = [ 'UNREAD' => $unread, 'DATE_UPDATE' => new \Bitrix\Main\Type\DateTime(), ]; if ($unread === 'N') { $markedId = 0; } if (isset($markedId)) { $updatedFields['MARKED_ID'] = $markedId; } \Bitrix\Im\Model\RecentTable::update( [ 'USER_ID' => $element['USER_ID'], 'ITEM_TYPE' => $element['ITEM_TYPE'], 'ITEM_ID' => $element['ITEM_ID'], ], $updatedFields ); self::clearCache($element['USER_ID']); //\Bitrix\Im\Counter::clearCache($element['USER_ID']); CounterService::clearCache((int)$element['USER_ID']); $chatId = (int)$element['ITEM_CID']; $chat = \Bitrix\Im\V2\Chat::getInstance($chatId); Sync\Logger::getInstance()->add( new Sync\Event(Sync\Event::ADD_EVENT, Sync\Event::CHAT_ENTITY, $chatId), $userId, $chat ); $pullInclude = \Bitrix\Main\Loader::includeModule("pull"); if ($pullInclude) { $readService = new ReadService($userId); $counter = $readService->getCounterService()->getByChatWithOverflow($chatId); \Bitrix\Pull\Event::add( $userId, [ 'module_id' => 'im', 'command' => 'chatUnread', 'expiry' => 3600, 'params' => [ 'chatId' => $chatId, 'dialogId' => $dialogId, 'active' => $unread === 'Y', 'muted' => $element['MUTED'] === 'Y', 'counter' => $counter, 'markedId' => $markedId ?? $element['MARKED_ID'], 'lines' => $element['ITEM_TYPE'] === IM_MESSAGE_OPEN_LINE, 'counterType' => $chat->getCounterType()->value, 'recentConfig' => $chat->getRecentConfig()->toPullFormat(), ], 'extra' => \Bitrix\Im\Common::getPullExtra() ] ); } return true; } private static function getUnreadElement(int $userId, ?string $itemTypes, $dialogId): array|false { if (self::$unreadElementCache[$userId][$dialogId] !== null) { return self::$unreadElementCache[$userId][$dialogId]; } $id = $dialogId; if (mb_substr($dialogId, 0, 4) === 'chat') { if ($itemTypes === null) { $itemTypes = \Bitrix\Im\Chat::getTypes(); } $id = mb_substr($dialogId, 4); } else { $itemTypes = IM_MESSAGE_PRIVATE; } self::$unreadElementCache[$userId][$dialogId] = \Bitrix\Im\Model\RecentTable::getList([ 'select' => [ 'USER_ID', 'ITEM_TYPE', 'ITEM_ID', 'UNREAD', 'MUTED' => 'RELATION.NOTIFY_BLOCK', 'ITEM_CID', 'MARKED_ID', 'ENTITY_TYPE' => 'CHAT.ENTITY_TYPE', ], 'filter' => [ '=USER_ID' => $userId, '=ITEM_TYPE' => $itemTypes, '=ITEM_ID' => $id ] ])->fetch(); return self::$unreadElementCache[$userId][$dialogId]; } public static function readAll(int $userId): void { \Bitrix\Im\Model\RecentTable::updateByFilter( [ '=UNREAD' => 'Y', '=USER_ID' => $userId, ], [ 'UNREAD' => 'N', 'MARKED_ID' => 0, ] ); } public static function isUnread(int $userId, string $itemType, string $dialogId): bool { $id = mb_strpos($dialogId, 'chat') === 0 ? mb_substr($dialogId, 4) : $dialogId; $element = \Bitrix\Im\Model\RecentTable::getList([ 'select' => ['USER_ID', 'ITEM_TYPE', 'ITEM_ID', 'UNREAD', 'MUTED' => 'RELATION.NOTIFY_BLOCK', 'ITEM_CID'], 'filter' => [ '=USER_ID' => $userId, '=ITEM_TYPE' => $itemType, '=ITEM_ID' => $id ] ])->fetch(); if (!$element) { return false; } return ($element['UNREAD'] ?? 'N') === 'Y'; } public static function getUnread(string $itemType, string $dialogId): array { $id = mb_strpos($dialogId, 'chat') === 0 ? mb_substr($dialogId, 4) : $dialogId; $queryResult = \Bitrix\Im\Model\RecentTable::getList([ 'select' => ['USER_ID', 'UNREAD',], 'filter' => [ '=ITEM_TYPE' => $itemType, '=ITEM_ID' => $id ] ])->fetchAll(); $result = []; foreach ($queryResult as $row) { $result[(int)$row['USER_ID']] = ($row['UNREAD'] ?? 'N') === 'Y'; } return $result; } public static function getMarkedId(int $userId, string $itemType, string $dialogId): int { $id = mb_strpos($dialogId, 'chat') === 0 ? mb_substr($dialogId, 4) : $dialogId; $element = \Bitrix\Im\Model\RecentTable::getList([ 'select' => ['MARKED_ID'], 'filter' => [ '=USER_ID' => $userId, '=ITEM_TYPE' => $itemType, '=ITEM_ID' => $id ] ])->fetch(); if (!$element) { return 0; } return (int)($element['MARKED_ID'] ?? 0); } public static function getMarkedIdByChatIds(int $userId, array $chatIds): array { if (empty($chatIds)) { return []; } $markedIdByChatIds = []; $result = RecentTable::query() ->setSelect(['ITEM_CID', 'MARKED_ID']) ->where('USER_ID', $userId) ->whereIn('ITEM_CID', $chatIds) ->fetchAll() ; foreach ($result as $row) { $markedIdByChatIds[(int)$row['ITEM_CID']] = (int)$row['MARKED_ID']; } return $markedIdByChatIds; } public static function hide($dialogId, $userId = null) { return \CIMContactList::DialogHide($dialogId, $userId); } public static function show($dialogId, $options = [], $userId = null) { $userId = Common::getUserId($userId); if (!$userId) { return false; } $chatId = Dialog::getChatId($dialogId, $userId); if (Common::isChatId($dialogId)) { $entityId = $chatId; } else { $entityId = (int)$dialogId; } $relation = \Bitrix\Im\Model\RelationTable::getList([ 'select' => [ 'ID', 'TYPE' => 'CHAT.TYPE', 'LAST_MESSAGE_ID' => 'CHAT.LAST_MESSAGE_ID', 'LAST_MESSAGE_DATE' => 'MESSAGE.DATE_CREATE' ], 'filter' => [ '=CHAT_ID' => $chatId, '=USER_ID' => $userId ], 'runtime' => [ new \Bitrix\Main\Entity\ReferenceField( 'MESSAGE', '\Bitrix\Im\Model\MessageTable', ["=ref.ID" => "this.CHAT.LAST_MESSAGE_ID"], ["join_type" => "LEFT"] ), ] ])->fetch(); if ($relation) { $relationId = $relation['ID']; $entityType = $relation['TYPE']; $messageId = $relation['LAST_MESSAGE_ID']; $messageDate = $relation['LAST_MESSAGE_DATE']; } else if ( isset($options['CHAT_DATA']['TYPE']) && isset($options['CHAT_DATA']['LAST_MESSAGE_ID']) ) { $relationId = 0; $entityType = $options['CHAT_DATA']['TYPE']; $messageId = $options['CHAT_DATA']['LAST_MESSAGE_ID']; $messageDate = $options['CHAT_DATA']['LAST_MESSAGE_DATE']; } else { $chat = \Bitrix\Im\Model\ChatTable::getList([ 'select' => [ 'TYPE', 'LAST_MESSAGE_ID', 'LAST_MESSAGE_DATE' => 'MESSAGE.DATE_CREATE' ], 'filter' => [ '=ID' => $chatId, ], 'runtime' => [ new \Bitrix\Main\Entity\ReferenceField( 'MESSAGE', '\Bitrix\Im\Model\MessageTable', ["=ref.ID" => "this.LAST_MESSAGE_ID"], ["join_type" => "LEFT"] ), ] ])->fetch(); if (!$chat) { return false; } $relationId = 0; $entityType = $chat['TYPE']; $messageId = $chat['LAST_MESSAGE_ID']; $messageDate = $chat['LAST_MESSAGE_DATE']; } $sessionId = 0; if ($entityType == IM_MESSAGE_OPEN_LINE) { if (isset($options['SESSION_ID'])) { $sessionId = (int)$options['SESSION_ID']; } else if (\Bitrix\Main\Loader::includeModule('imopenlines')) { $session = \Bitrix\ImOpenLines\Model\SessionTable::getList([ 'select' => ['ID'], 'filter' => ['=CHAT_ID' => $chatId], 'order' => ['ID' => 'DESC'], 'limit' => 1, ])->fetch(); if ($session) { $sessionId = $session['ID']; } } } \CIMContactList::SetRecent($temp = [ 'ENTITY_TYPE' => $entityType, 'ENTITY_ID' => $entityId, 'MESSAGE_ID' => $messageId, 'MESSAGE_DATE' => $messageDate, 'CHAT_ID' => $chatId, 'RELATION_ID' => $relationId, 'SESSION_ID' => $sessionId, 'USER_ID' => $userId, ]); if (!\Bitrix\Main\Loader::includeModule("pull")) { return true; } $data = \Bitrix\Im\Recent::getElement($entityType, $entityId, $userId, ['JSON' => true]); if ($data) { if ( !isset($data['message']) && $entityType === Chat::TYPE_OPEN_LINE && class_exists('\Bitrix\ImOpenLines\Recent') ) { $data = \Bitrix\ImOpenLines\Recent::getElement( (int)$entityId, (int)$userId, [ 'JSON' => true, 'fakeCounter' => 1 ] ); } \Bitrix\Pull\Event::add($userId, [ 'module_id' => 'im', 'command' => 'chatShow', 'params' => $data, 'extra' => \Bitrix\Im\Common::getPullExtra() ]); } return true; } public static function clearCache($userId = null) { $cache = Application::getInstance()->getCache(); $cache->cleanDir('/bx/imc/recent'.($userId ? Common::getCacheUserPostfix($userId) : '')); } protected static function prepareRows(array $rows, int $userId, bool $shortInfo = false): array { [$messageIds, $chatIds] = self::getKeysForFetchAdditionalEntities($rows); $counters = (new CounterService($userId))->getForEachChat($chatIds); $params = $shortInfo ? [] : self::getMessageParams($messageIds); return self::fillRows($rows, $params, $counters, $userId); } protected static function getKeysForFetchAdditionalEntities(array $rows): array { $messageIds = []; $chatIds = []; foreach ($rows as $row) { if (isset($row['ITEM_MID']) && $row['ITEM_MID'] > 0) { $messageIds[] = (int)$row['ITEM_MID']; } if (isset($row['ITEM_CID']) && $row['ITEM_CID'] > 0) { $chatIds[] = (int)$row['ITEM_CID']; } } return [$messageIds, $chatIds]; } protected static function getMessageParams(array $messageIds): array { $result = []; $fileIds = []; if (empty($messageIds)) { return $result; } $rows = MessageParamTable::query() ->setSelect(['*']) ->whereIn('MESSAGE_ID', $messageIds) ->exec() ; foreach ($rows as $item) { $messageId = (int)$item['MESSAGE_ID']; $paramName = $item['PARAM_NAME']; if ($paramName === 'CODE') { $result[$messageId]['CODE'] = $item['PARAM_VALUE']; } elseif ($paramName === 'ATTACH') { $result[$messageId]['ATTACH'] = [ 'VALUE' => $item['PARAM_VALUE'], 'JSON' => $item['PARAM_JSON'], ]; } elseif ($paramName === 'URL_ID') { $result[$messageId]['ATTACH'] = [ 'VALUE' => "", 'JSON' => true, ]; } elseif ($paramName === 'FILE_ID') { $fileIds[$messageId] = (int)$item['PARAM_VALUE']; $result[$messageId]['MESSAGE_FILE'] = true; } } return self::fillFiles($result, $fileIds); } protected static function fillFiles(array $params, array $fileIds): array { if (empty($fileIds)) { return $params; } if (Settings::isLegacyChatActivated()) { return $params; } $files = FileCollection::initByDiskFilesIds($fileIds); foreach ($fileIds as $messageId => $fileId) { $file = $files->getById($fileId); if (!$file instanceof FileItem) { $params[$messageId]['MESSAGE_FILE'] = false; } else { $params[$messageId]['MESSAGE_FILE'] = [ 'ID' => $file->getId(), 'TYPE' => $file->getContentType(), 'NAME' => $file->getDiskFile()->getName(), ]; } } return $params; } protected static function fillRows(array $rows, array $params, array $counters, int $userId): array { foreach ($rows as $key => $row) { $chatId = (int)($row['ITEM_CID'] ?? 0); $messageId = (int)($row['ITEM_MID'] ?? 0); $boolStatus = $row['CHAT_LAST_MESSAGE_STATUS_BOOL'] ?? 'N'; $rows[$key]['COUNTER'] = $counters[$chatId] ?? 0; $rows[$key]['CHAT_LAST_MESSAGE_STATUS'] = $boolStatus === 'Y' ? \IM_MESSAGE_STATUS_DELIVERED : \IM_MESSAGE_STATUS_RECEIVED; $rows[$key]['MESSAGE_CODE'] = $rows[$key]['MESSAGE_CODE'] ?? $params[$messageId]['CODE'] ?? null; $rows[$key]['MESSAGE_ATTACH'] = $params[$messageId]['ATTACH']['VALUE'] ?? null; $rows[$key]['MESSAGE_ATTACH_JSON'] = $params[$messageId]['ATTACH']['JSON'] ?? null; $rows[$key]['MESSAGE_FILE'] = $params[$messageId]['MESSAGE_FILE'] ?? false; $rows[$key]['RELATION_USER_ID'] = $row['RELATION_ID'] ? $userId : null; } return $rows; } /** * @see \Bitrix\Im\V2\Chat::getRole() * @param array $row * @return string */ protected static function getRole(array $row): string { if (!isset($row['RELATION_USER_ID'])) { return \Bitrix\Im\V2\Chat::ROLE_GUEST; } if ((int)$row['CHAT_AUTHOR_ID'] === (int)$row['RELATION_USER_ID']) { return \Bitrix\Im\V2\Chat::ROLE_OWNER; } if ($row['RELATION_IS_MANAGER'] === 'Y') { return \Bitrix\Im\V2\Chat::ROLE_MANAGER; } return \Bitrix\Im\V2\Chat::ROLE_MEMBER; } public static function isLimitError(): bool { return self::$limitError; } }