im 26.900.0 + socialnetwork 26.150.0: инициатор в событиях внешних чатов и установщик на миграциях
Ломающее обновление
Удалены или изменены публичные API — прикладной код может перестать работать.
В im 26.900.0 папки чатов научились работать с dialogId, мобильная синхронизация узнаёт об уволенных сотрудниках, у чата появился бэкенд навигации по датам, а установщик модуля переехал с SQL-файлов на UpdateSystem\Migration. Ломаются конструкторы десяти событий внешних чатов, несколько флагов в Features и REST-формат папок. socialnetwork 26.150.0 едет в том же пакете ради одной строки, которая создаёт Copilot-чат проекта в системном контексте, и im к этому подготовили.
Что сломается
Передайте инициатора в события внешних чатов
В конструкторы десяти событий Bitrix\Im\V2\Chat\ExternalChat\Event\* вторым параметром, сразу после ExternalChat $chat, встал обязательный int $initiatorId. Это AfterDeleteMessagesEvent, AfterSendMessageEvent, AfterUpdateMessageEvent, AfterUsersAddEvent, AfterUsersDeleteEvent, AfterUsersHideEvent, BeforeMessageSendEvent, BeforeUsersAddEvent, BeforeUsersDeleteEvent и BeforeUsersHideEvent. Сам ExternalChat передаёт туда id пользователя из контекста чата.
Подписчиков правка не задевает. Сломается код, который создаёт такие события сам, например в тестах или в своей обёртке над внешним чатом:
<?php declare(strict_types=1);
use Bitrix\Im\V2\Chat\ExternalChat\Event\BeforeUsersAddEvent;
// до 26.900.0 было: new BeforeUsersAddEvent($chat, $userIds, $config)
$event = new BeforeUsersAddEvent($chat, $initiatorId, $userIds, $config);
Все десять событий реализуют новый интерфейс InitiatedByUserInterface с единственным методом getInitiatorId(): int, поэтому инициатора можно достать, не перебирая классы:
<?php declare(strict_types=1);
use Bitrix\Im\V2\Chat\ExternalChat\Event\InitiatedByUserInterface;
function externalChatInitiator(object $event): ?int
{
return $event instanceof InitiatedByUserInterface ? $event->getInitiatorId() : null;
}
Проверьте флаги в Features
Из Bitrix\Im\V2\Application\Features удалены три static-метода. isCopilotSelectModelAvailable() и isCopilotReasoningAvailable() и так всегда возвращали true, так что проверки на них можно убрать. Параметр Copilot reasoning сервер теперь принимает без условий. Третий, isUnreadRecentModeAvailable(), читал опцию unread_recent_mode_available.
isChatFoldersAvailable() переименован в isChatFoldersWebAvailable() и смотрит на другую опцию модуля: chat_folders_web_available вместо chat_folders_available. Если вы включали папки старой опцией, после обновления они выключатся, пока не выставите новую:
<?php declare(strict_types=1);
use Bitrix\Main\Config\Option;
Option::set('im', 'chat_folders_web_available', 'Y');
С включённым флагом левое меню мессенджера скрывает пункты «Чаты», задачи, Copilot, коллабы, каналы и открытые линии.
Конструктор Features перестроили. Из него убраны шесть свойств (isCopilotSelectModelAvailable, unreadRecentModeAvailable, isCopilotReasoningAvailable, isMessageBuilderAvailable, isAddingUserByMentionAvailable, isExternalChatMessageForwardingAvailable), одно переименовано, а перед isReplyWithMediaAvailable вставлено isMessageDateNavigationAvailable. Позиционная сборка объекта сломается. Static-методы isMessageBuilderAvailable(), isAddingUserByMentionAvailable() и isExternalChatMessageForwardingAvailable() остались, флаги читайте через них.
Уберите BuilderError::BLOCK_NOT_AVAILABLE
Конструктор блоков сообщений больше не выключается флагом. Проверки isMessageBuilderAvailable() убраны из BuilderService, из валидатора блоков и из экшенов append, delete и update контроллера Controller\Chat\Message\Block\Element. Вместе с ними удалён код ошибки BuilderError::BLOCK_NOT_AVAILABLE, и обращение к этой константе в вашем коде теперь упадёт.
Обновите клиентов папок чатов
В веб-интерфейсе папки чатов пока за флагом, но если вы уже работаете с их REST, формат поменялся:
- у личной папки
definition.chatIds(массив чисел) заменён наdefinition.chats, массив пар{chatId, dialogId}; - у личных и системных папок появилось поле
sort; - системная папка открытых линий отдаёт
code: "lines", а вdefinition.recentSectionвместоopenlinesтеперь тожеlines(хранимый код осталсяopenlines). Клиент, который сравниваетrecentSectionсо строкой'openlines', совпадения не получит; im.v2.Folder.addпринимает вfieldsне толькоchatIds, но иdialogIds;im.v2.Folder.addChatsиim.v2.Folder.deleteChatsпринимаютdialogIdsрядом сchatIds, оба параметра необязательные;im.v2.Folder.updateумеет менять состав папки через поляchatIds/dialogIds, а название стало необязательным. Строка'N'вместо массива очищает папку.
В PHP сменились сигнатуры DTO. FolderFields::fromArray() и UpdateFields::fromArray() теперь требуют DialogChatIdResolver, id текущего пользователя идёт третьим необязательным параметром (int $currentUserId = 0). Конструктор UpdateFields принимает (?string $title, ?ChatIds $chatIds = null).
Проверьте наследников SyncService
SyncService выбирает события строго после курсора (ID > lastId, раньше было >=), а курсор следующей страницы берёт по последней строке среза. Лог читается двумя запросами, по текущему пользователю и по глобальным событиям с USER_ID = 0, и потом сливается. Если вы наследовались от SyncService, проверьте getNavigationData(), у него новая сигнатура (array $logEvents, DateTime $incomingLastDate, ?int $incomingLastId, bool $hasMore). Мобильная ревизия API Revision::MOBILE поднята с 25 до 26.
Что ещё ведёт себя иначе
PrivateChat::getDialogIds()теперь возвращает чат «заметок» (переписку с самим собой) с собственным userId. Раньше контекстный пользователь исключался из выборки. Если в чате есть собеседник, выигрывает он.RecentItemдля личного чата кладёт вdialogIdid собеседника, а неchat<ID>. Комментарий// TODO: replaceрядом с этой строкой удалили.- Из
NotifyChatиReading\Notification\Readerубраны вызовы\Bitrix\Pull\MobileCounter::send(). Куда переехала отправка мобильного счётчика, по диффу не видно. - В письме о непрочитанных сообщениях
CIMMailстроки больше не проходят черезnl2br().
Новое
Навигация по датам, пока только бэкенд
Появились REST-методы im.v2.Chat.Message.DateNavigation.getCalendar и im.v2.Chat.Message.DateNavigation.resolveDate. Первый возвращает дни, в которые в чате есть видимые сообщения, и границы истории (firstAvailableDate, lastAvailableDate). Второй находит первое сообщение нужной даты, а если в этот день сообщений нет, переходит к ближайшему следующему дню с сообщениями. Учитываются startId пользователя в чате (его берут по контекстному пользователю объекта Chat) и тарифное ограничение истории.
Методы закрыты флагом Features::isMessageDateNavigationAvailable() (опция message_date_navigation_available, по умолчанию N), фронтенда в релизе нет. Флаг проверяет только контроллер, поэтому сервис DateNavigationService можно вызывать из своего кода уже сейчас. Доступ к чату сервис не проверяет, это остаётся за вами:
<?php declare(strict_types=1);
namespace Vendor\Chat\Report;
use Bitrix\Im\V2\Chat;
use Bitrix\Im\V2\Message\DateNavigation\DateNavigationService;
final class ChatActivityCalendar
{
/** @return string[] дни в формате Y-m-d */
public function activeDays(Chat $chat, int $userId, string $from, string $to): array
{
// startId и границы истории сервис берёт по контекстному пользователю чата
$chat = $chat->withContextUser($userId);
if (!$chat->checkAccess($userId)->isSuccess()) {
return [];
}
$fromDate = DateNavigationService::parseDate($from);
$toDate = DateNavigationService::parseDate($to);
if ($fromDate === null || $toDate === null) {
return [];
}
$result = DateNavigationService::createForClientOffset($chat, null)
->getCalendar($fromDate, $toDate);
return $result->isSuccess() ? $result->getResult()->getDays() : [];
}
}
Диапазон не шире 62 дней, иначе вернётся ошибка DateNavigationError::WRONG_RANGE. null вместо смещения клиента означает смещение из \CTimeZone::GetOffset(). Даты ожидаются в формате Y-m-d, на кривой строке parseDate() вернёт null.
Папки понимают dialogId
Под это завели пару резолверов. DialogChatIdResolver превращает dialogId в id чатов: chat42 разбирается без запроса, числовые id пользователей уходят одним пакетным запросом, остальное (crm|…, sg…) идёт через Dialog::getChatId(). ChatDialogIdResolver делает обратное. Контроллер папок берёт резолвер из сервис-локатора, так же можно и в своём коде:
<?php declare(strict_types=1);
use Bitrix\Im\V2\Chat\DialogChatIdResolver;
use Bitrix\Main\DI\ServiceLocator;
use Bitrix\Main\Loader;
Loader::requireModule('im');
$resolver = ServiceLocator::getInstance()->get(DialogChatIdResolver::class);
$chatIds = $resolver->resolve([], ['chat42', '17'], $currentUserId, createMissing: false);
// $chatIds->values: уникальные положительные id чатов
По умолчанию createMissing равен true, и для пользователя без личного чата резолвер этот чат создаст. Если нужно только найти существующие чаты, передавайте false, как это делает сам deleteChatsAction().
Лимиты папок собраны в FolderLimits: 20 папок на пользователя, 50 чатов в папке, 30 символов в названии. Размер запрошенного состава проверяется до резолва, чтобы длинный список dialogIds не наплодил личных чатов, которые в папку всё равно не влезут. Если при обновлении папки непустой состав не разрешился ни в один чат, запрос отклоняется с кодом FOLDER_CHAT_NOT_ELIGIBLE, и папка остаётся как была.
Когда корневой чат прикрепляют к родителю, он пропадает из личных папок всех пользователей (FolderCascadeHandler::onChatAttachedToParent()).
Мобильный клиент узнаёт об уволенных
Sync\UserLifecycleService пишет в лог синхронизации глобальные события: userFired, когда пользователя деактивируют, userRestored, когда возвращают, и событие полного удаления. Его вызывают CIMEvent::OnAfterUserUpdate и CIMEvent::OnUserDelete. Служебные и внешние учётки (боты, email, imconnector, гости и другие) он пропускает. Записи уходят в фоне через Logger::addGlobal() с USER_ID = 0.
В ответе синхронизации появился блок userSync с updatedUsers и deletedUsers. Список обновлённых фильтруется по видимости. Интранет-пользователь видит всех, экстранет и гости получают только доступных им, а без контекстного пользователя список пустой.
Для тех, кого уволили до обновления, есть одноразовый агент Sync\Agent\FiredUsersAgent. Он порциями по 500 проходит неактивных реальных пользователей с последней активностью начиная с 2023-01-01 (или без неё) и дописывает им userFired.
Участники по группам
im.v2.Chat.Member.tail принимает withGroupSort. С ним участники идут группами: владелец, менеджеры, коллаберы, остальные. Сайдбар включает этот режим для коллаб.
Остальное
MessageHistoryFilterполучил последний параметрbool $includeSystem = false. Сtrueпровайдер истории отдаёт системные и приветственные сообщения; удалённые и сообщения об ошибках не попадают туда никогда.GuestNetworkPortalRegistry::getShortLinkBaseUrl()выбирает базу гостевых ссылок:https://bitrix24.net/gi/, если сеть портала смотрит на bitrix24.net, иначеhttps://b24.to/gi/. Раньшеb24.toбыл зашит константой вGuestChatLink.Extranet::filterUserList()и соседние методы принимаютbool $filterActiveUser = true.DocumentSign::isAvailable()кеширует ответ модуля sign на сутки для каждого пользователя.- Два новых стикерпака:
aiHelp(10 стикеров) иladyBoss(44).
БД: схема та же, таблицы теперь создаёт UpdateSystem\Migration
InstallDB() вызывает installMigrations(), UnInstallDB() вызывает uninstallMigrations(), а файлы install/db/mysql/*.sql и install/db/pgsql/*.sql удалены. Вместо них:
install/migrations/tables.phpописывает схему черезUpdateSystem\Migration, один файл на MySQL и PostgreSQL. С удалённым MySQL-скриптом он совпадает по набору таблиц (69) и колонок в каждой, типы построчно не сверялись. Пять полнотекстовых индексов из бывшегоinstall_ft.sqlобъявлены здесь же;install/migrations/events.phpрегистрирует те же 42 обработчика, что раньшеInstallDB();install/migrations/agents.phpсодержит прежние агенты и новыйFiredUsersAgent;migration_config.jsonзадаётdefaultTableName: b_im_chatи маппинг каталогов установки.
Самих installMigrations() и Migration в диффе нет, они живут в main.
Проверьте savedata в скриптах удаления
Раньше UnInstallDB() сохранял таблицы при любом истинном значении savedata. Условие стало таким:
$needDropTables = !array_key_exists('savedata', $arParams) || $arParams['savedata'] != 'Y';
Теперь 'N' или 1 удаляют таблицы. Если у вас есть скрипты, которые переустанавливают im с сохранением данных, передавайте именно 'Y'.
Пять агентов старый InstallDB() регистрировал с ведущим \ (например, \Bitrix\Im\V2\Recent\Initializer::executeAgent();), а в agents.php те же имена записаны без него. Как uninstallMigrations() ищет агенты, по диффу не видно, так что после удаления модуля загляните в список агентов.
Мелочи и находки
- Правка в socialnetwork одна.
ProjectCopilotChatProviderсоздаёт Copilot-чат проекта черезwithContextUser(0)вместо контекста пользователя. По комментарию в коде, коллабер не видит бота Copilot, фильтры видимости выкинули бы бота из состава, и чат остался бы без того, кто в нём отвечает. В im под это доработалиCopilotChat:sendBanner()без автора берёт автора чата, а номер в названии чата в системном контексте идёт из счётчика автора. - Имя агента Copilot для незападной зоны сменилось с
BitrixGPT 5.5 AgentнаBitrixGPT 5.6 Agent 1M. - В
UIPromotionServiceпочистили промо. Условия для промо видеозаметок и стикеров читалиFeatures::get()->videoNoteAvailableи->stickersAvailable, а таких свойств вFeaturesнет. Теперь этих двух промо нет совсем, а промо смены движка и роли Copilot добавляется без условий и живёт до 30.04.2027. UpdateFieldsделает частичное обновление через неинициализированное readonly-свойство.ValidationServiceпроверяет только инициализированные свойства, поэтому непереданныйtitleвалидацию пропускает. Приём можно взять и в свои Request DTO.DateNavigationService::parseDate()проверяет дату обратным форматированием, чтобы2026-02-31молча не превратилась в мартовскую дату.- Кеш
Extranetраньше не различал выборку с неактивными пользователями и без, потому что ключim_sonet_extranet_v3_<userId>не зависел от$filterActiveUser. Теперь к нему добавляется_activeили_all. - В комментариях всплывают внутренние коды задач:
ALG-01в синхронизации и сортировке участников,RF-2в фильтре видимости. - Комментарий в
RecentItemобещает, что для открытых линий, каналов и коллаб вывод «byte-identical to master».
Что делать
Перед обновлением поищите в своём коде ExternalChat\Event\, isChatFoldersAvailable, isCopilotSelectModelAvailable, isCopilotReasoningAvailable, isUnreadRecentModeAvailable, BLOCK_NOT_AVAILABLE, definition.chatIds и 'openlines'. После обновления проверьте опцию chat_folders_web_available, если пользуетесь папками, и savedata в скриптах удаления.
Устаревшие и удалённые API в этой версии
| Символ | Статус | Чем заменять |
|---|---|---|
Bitrix\Im\V2\Application\Features::isChatFoldersAvailable()
|
Удалено |
Bitrix\Im\V2\Application\Features::isChatFoldersWebAvailable()
|
Bitrix\Im\V2\Application\Features::isCopilotSelectModelAvailable()
|
Удалено | — |
Bitrix\Im\V2\Application\Features::isCopilotReasoningAvailable()
|
Удалено | — |
Bitrix\Im\V2\Application\Features::isUnreadRecentModeAvailable()
|
Удалено | — |
Bitrix\Im\V2\Application\Features::$isChatFoldersAvailable
|
Удалено |
Bitrix\Im\V2\Application\Features::$isChatFoldersWebAvailable
|
Bitrix\Im\V2\Application\Features::$isCopilotSelectModelAvailable
|
Удалено | — |
Bitrix\Im\V2\Application\Features::$isCopilotReasoningAvailable
|
Удалено | — |
Bitrix\Im\V2\Application\Features::$unreadRecentModeAvailable
|
Удалено | — |
Bitrix\Im\V2\Application\Features::$isMessageBuilderAvailable
|
Удалено |
Bitrix\Im\V2\Application\Features::isMessageBuilderAvailable()
|
Bitrix\Im\V2\Application\Features::$isAddingUserByMentionAvailable
|
Удалено |
Bitrix\Im\V2\Application\Features::isAddingUserByMentionAvailable()
|
Bitrix\Im\V2\Application\Features::$isExternalChatMessageForwardingAvailable
|
Удалено |
Bitrix\Im\V2\Application\Features::isExternalChatMessageForwardingAvailable()
|
Bitrix\Im\V2\Message\BlocksBuilder\BuilderError::BLOCK_NOT_AVAILABLE
|
Удалено | — |