ui · UI-библиотека 26.600.0 Ломающее

UI 26.600.0: параллельная и presigned-загрузка файлов, поле rich_text и Bootstrap JS 4.6

7 мин чтения

Ломающее обновление

Удалены или изменены публичные API — прикладной код может перестать работать.

Дифф UI 26.600.0 даже без языковых файлов и .min.js весит 19 МБ, и почти весь этот объём занимают пересобранные бандлы и вендорные библиотеки (один mermaid дал 136 тысяч строк). Из содержательного загрузчик файлов научился принимать части параллельно и отдавать их браузеру для загрузки прямо в S3. Ещё в релизе тип пользовательского поля rich_text, серверный BBCode-конвертер, новая таблица в БД и Bootstrap JS 4.6.2 вместо 4.1.3 в ui.bootstrap4. Удалённого PHP API нет, но в нескольких местах поменялось поведение.

Проверьте страницы на ui.bootstrap4

В ui.bootstrap4 обновили только JavaScript: bootstrap.js и bootstrap.bundle.js теперь версии 4.6.2 вместо 4.1.3. CSS расширения в релизе не менялся, и в нём по-прежнему лежит Bootstrap v5.0.0-alpha1, так что скрипты и стили расходились по версиям и до обновления. Если шаблон сайта подключает ui.bootstrap4 и пользуется его JS-компонентами, откройте после обновления страницы с модальными окнами, дропдаунами и тултипами и убедитесь, что они ведут себя как раньше.

Атрибуты из BBCode больше не попадают в HTML

ui.bbcode.formatter.html-formatter раньше переносил атрибуты BBCode-узла в создаваемый DOM-элемент. В форматтерах b, i, u, s, списков и пунктов списка для этого стоял ...node.getAttributes(), а форматтер ссылок сначала складывал атрибуты в const nodeAttributes = node.getAttributes() и разворачивал их через ...nodeAttributes. Эти строки удалили. Теперь у <b>, <li> или <a> остаются только атрибуты, которые ставит сам форматтер: класс типографики, href, target и атрибуты из настроек ссылок.

По виду правки похоже, что закрыли перенос произвольных атрибутов из пользовательского BBCode в DOM, но сам дифф мотив не называет. Если ваш JS искал в отрендеренном HTML атрибуты, пришедшие из BBCode, их там больше не будет.

Если вы наследовали Uploader

Bitrix\UI\FileUploader\Uploader не объявлен final, а у его upload() появился третий параметр:

        public function upload(Chunk $chunk, ?string $token = null, ?string $strategy = null): UploadResult

    

Наследник со старым upload(Chunk $chunk, string $token = null) станет несовместим с родителем, и PHP упадёт с fatal error на загрузке класса. У контроллера Bitrix\UI\Controller\FileUploader в uploadAction() тоже добавился ?string $strategy = null. Чтобы починить, допишите параметр в переопределение и передайте его в parent::.

Что ещё ведёт себя иначе

  • Из пути временных файлов загрузчика пропал уровень file-uploader/. Раньше TempFile передавал в CTempFile::getDirectoryName() и CCloudTempFile::getDirectoryName() подкаталог file-uploader/<32 случайных символа>, теперь не передаёт ничего. Судя по коду main и clouds в эталоне, без подкаталога ядро само создаёт внутри часового каталога случайную папку (32 символа у CTempFile, md5 у CCloudTempFile). Скрипты очистки или мониторинга, которые ищут file-uploader в пути, перестанут его находить. Подкаталог убрали, потому что из-за него CCloudTempFile опрашивал через ListFiles() каждый часовой каталог, по сетевому запросу на час.
  • CopilotName::BITRIX_GPT_AGENT теперь равен 'BitrixGPT 5.6 Agent 1M' вместо 'BitrixGPT 5.5 Agent'. Если вы где-то сохраняли строковое значение этого enum, CopilotName::from() на старой строке бросит ValueError.
  • Button::setMenu() с непустым списком items теперь снимает с кнопки системное меню (о нём ниже).
  • LockedButton и DocumentButton добавляют свои расширения к списку родителя, раньше они его заменяли.

Загрузчик научился грузить части параллельно и прямо в S3

Почти весь новый PHP в релизе приходится на Bitrix\UI\FileUploader: около тысячи строк в Uploader.php и ещё почти четыреста в TempFile.php. К обычной последовательной загрузке кусками добавились две стратегии.

  • parallel. Браузер шлёт части в любом порядке. Сервер пишет их в разреженный файл по смещению (partNo - 1) * PART_SIZE или в multipart-сессию облака. Последняя принятая часть запускает сборку файла.
  • presigned. Сервер открывает multipart-сессию в S3 и раздаёт подписанные URL, браузер кладёт части прямо в бакет, и байты через PHP не идут. Изображения так не загружаются, их отсекает \CFile::isImage().

Под это в контроллере Bitrix\UI\Controller\FileUploader появились действия uploadPart, initPresigned, refreshPresigned, registerPart, registerParts и completePresigned. В JS-расширении ui.uploader.core добавились ParallelUploadController и PresignedUploadController.

Обе стратегии по умолчанию выключены. Настройки читает Configuration::getGlobalSettings(), сам метод в релизе не менялся:

Ключ По умолчанию Что задаёт
parallelChunkUpload false включает parallel; на Windows метод всегда отвечает false
maxParallelChunks 2 сколько частей грузится одновременно, в обеих стратегиях
presignedChunkUpload false включает presigned; без модуля clouds всегда false
presignedThreshold не задан минимальный размер файла для presigned, понимает запись вида 100M
presignedUrlTtl 1800 время жизни подписанного URL в секундах, от 60 секунд до 7 дней
presignedUrlBatchLimit 100 сколько URL подписать заранее при старте; тот же лимит режет каждый refreshPresigned и пачку в registerParts
presignedRegisterInterval 15000 как часто браузер отчитывается серверу об ETag частей, мс

Если presigned включён, а presignedThreshold не задан, JS отправит этим путём любой файл больше одного чанка, кроме изображений. С заданным порогом туда пойдут файлы не меньше порога, изображения по-прежнему нет.

Перед включением проверьте:

  • Сервер не проверяет parallelChunkUpload. Метод isParallelChunkUploadEnabled() новый, и в PHP его вызывает только config.php расширения ui.uploader.core, чтобы передать флаг в браузер. Uploader::upload() примет strategy=parallel от любого клиента, в том числе на Windows. У presigned серверная проверка есть, её делает isPresignedAvailable() (настройка плюс Loader::includeModule('clouds')).
  • Presigned вызывает методы clouds, которых в диффе ui нет: supportsPresignedUrls() у бакета, presignPart() и setParts() у CCloudStorageUpload. В clouds они появились в 26.100.0, так что ставьте ui 26.600.0 не раньше этой версии clouds.
  • Если у вас свой UploaderController, в режиме presigned его canUpload() получает имя, тип и размер со слов клиента, байтов сервер не видит. Изображения отсекаются ещё до этой проверки, а у остальных файлов ширина и высота обнуляются, так что габариты в UploadRequest всегда 0×0. PHPDoc validatePresignedRequest() предупреждает, что для строгой гарантии файл нужно перепроверять после коммита.
  • Локальный parallel пишет разреженные файлы. В комментарии к writePartLocal() названы ext4, xfs и apfs, а про NFS и SMB сказано «may misbehave». Если временный каталог лежит на сетевой шаре, parallel лучше не включать.

Проверьте миграцию БД

В install.sql у b_ui_file_uploader_temp_file четыре новые колонки: STRATEGY, PART_SIZE, PART_COUNT, UPLOAD_ID. Рядом появилась таблица b_ui_file_uploader_temp_file_part с уникальным индексом по (TEMP_FILE_ID, PART_NO). Внешних ключей нет, части удаляет обработчик TempFileTable::onDelete.

TempFileTable::getMap() уже описывает новые колонки. Если на существующей установке миграция по какой-то причине не прошла, ORM-запросы к временным файлам загрузчика, скорее всего, начнут падать на несуществующих полях. Скрипт миграции в снапшот модуля не попадает, по диффу его не проверить, так что после обновления посмотрите сами:

        <?php declare(strict_types=1);

use Bitrix\Main\Application;

$connection = Application::getConnection();
$columns = $connection->getTableFields('b_ui_file_uploader_temp_file');

$ok = $connection->isTableExists('b_ui_file_uploader_temp_file_part')
    && array_key_exists('STRATEGY', $columns)
    && array_key_exists('UPLOAD_ID', $columns);

echo $ok ? "ui 26.600.0: схема на месте\n" : "ui 26.600.0: миграция не прошла\n";

    

Появилось поле rich_text

Bitrix\UI\UserField\Types\RichTextType наследует StringType, хранит BBCode в строке и рендерится компонентом bitrix:ui.field.richtext (шаблоны для формы, просмотра, фильтра, админского списка и настроек поля). Тип регистрируется обработчиком main:OnUserTypeBuildList в install/index.php модуля.

Перед сохранением значение проходит Whitelist::normalize(). Он вырезает запрещённые теги (quote, code, table, spoiler, user, font, size, color, align и ещё несколько), а одиночные квадратные скобки превращает в &#91; и &#93;. В поисковый индекс попадает plain text.

Показ типа в интерфейсе закрыт фича-флагом Bitrix\UI\Config\Feature\RichTextUserFieldFlag, по умолчанию выключенным. Флаг проверяет только JS-фабрика полей ui.userfieldfactory, и от него зависит лишь то, появится ли rich_text в её списке типов. В самом RichTextType проверки флага нет.

Чтобы вывести такое значение в своём компоненте, есть Bitrix\UI\Format\BBCode\Converter:

        <?php declare(strict_types=1);

use Bitrix\Main\Loader;
use Bitrix\UI\Format\BBCode\Converter;

Loader::requireModule('ui');

$bb = '[b]Скидка 20%[/b] на [url=https://example.com/catalog/]весь каталог[/url]';

$html = Converter::toHtml($bb);        // для страницы
$text = Converter::toPlainText($bb);   // для meta description, писем, поиска

    

toHtml() пропускает BBCode через CTextParser и CBXSanitizer с белым списком тегов. В списке есть iframe (под видео) и атрибут style у p и img. Если результат выводится туда, где iframe недопустим, фильтруйте его дополнительно.

Кнопку Copilot в редакторе поля включает новое событие ui:onRichTextUserFieldGetCopilotOptions. Шаблон main.edit передаёт в него userField, берёт первый успешный EventResult, в параметрах которого лежит массив copilot, и добавляет кнопку в тулбар. Какие ключи ждёт сам массив, из релиза не видно, поэтому в примере он вынесен в заглушку:

        <?php declare(strict_types=1);

namespace Vendor\Catalog\Integration\Ui;

use Bitrix\Main\Event;
use Bitrix\Main\EventResult;

final class RichTextCopilotHandler
{
    public static function handle(Event $event): ?EventResult
    {
        $userField = (array)$event->getParameter('userField');
        if (($userField['FIELD_NAME'] ?? '') !== 'UF_PRODUCT_DESCRIPTION') {
            return null;
        }

        return new EventResult(EventResult::SUCCESS, ['copilot' => self::copilotOptions()]);
    }

    private static function copilotOptions(): array
    {
        return []; // формат опций в релизе не описан
    }
}

    

Регистрируйте обработчик в install/index.php своего модуля через EventManager::registerEventHandler('ui', 'onRichTextUserFieldGetCopilotOptions', ...). Пустой массив copilot шаблон тоже примет и покажет кнопку.

Кнопкам дали системное меню

Bitrix\UI\Buttons\Button::setSystemMenu(?array $options) (или ключ systemMenu в параметрах кнопки) вешает на кнопку меню из ui.system.menu вместо main.popup. Меню у кнопки одно. Если заданы оба, системное побеждает, а про выброшенный menu в лог пишется строка через AddMessage2Log. Туда же уходит предупреждение, если в опциях нет непустого списка items, и меню тогда не ставится вовсе. null снимает системное меню. Стрелку дропдауна метод при этом убирает, только если сам её добавил.

На стороне JS за это отвечают новые MenuController и SystemMenuController в ui.buttons. Сам ui.system.menu заодно получил клавиатурную навигацию: стрелки, Home/End и поиск по первым буквам без учёта диакритики.

Группу прав можно закрыть от правок

UserGroup из ui.accessrights.v2 получил setIsReadOnly(bool) и isReadOnly(), флаг уходит в toArray(). В JS-сторе редактора прав такую группу нельзя переименовать, удалить или поменять её участников, каждая попытка заканчивается console.warn. Смену значений прав для неё стор пропускает молча, потому что массовые действия, как сказано в комментарии к коду, законно задевают все показанные группы. В PHP-части ui это только флаг в DTO, серверной проверки при сохранении в диффе нет.

Что добавилось в JS

  • ui.system.popover и ui.system.popover.vue — новый поповер на TypeScript поверх ui.floating-ui. В модуле ui его пока никто не подключает.
  • ui.floating-ui упаковывает @floating-ui/dom 1.8.0 в BX.UI.FloatingUi. README называет расширение внутренней библиотекой с единственным потребителем, поповером.
  • ui.mermaid делает то же с mermaid 11.15.0 (BX.UI.Mermaid), бандл на 133 тысячи строк. Тоже пока без потребителей.
  • ui.entity-selector и ui.uploader.core переписаны на TypeScript и получили .d.ts. Экспорты entity-selector те же, у uploader добавились два контроллера, удалённых нет.
  • ui.a11y получил focus trap и проверку интерактивности элементов, его подключили ui.counterpanel, ui.navigationpanel, ui.system.menu и ui.uploader.tile-widget. В entity-selector появился announcer, который объявляет скринридеру результаты поиска.

Удалённых расширений нет. Из 70 удалённых файлов 55 приходятся на старые .js-исходники ui.entity-selector и ui.uploader.core, переписанные на .ts. Остальные 15 оказались юнит-тестами (entity-selector/test/*, uploader/core/test/*.test.js), и их убрали без .ts-замены.

Мелочи и находки

  • Parsedown 1.8.0 лежит в ui/vendor и регистрируется в include.php модуля как глобальный класс Parsedown. В коде ui он пока не используется. В ui.mermaid есть комментарий про markdown viewer, который вставляет SVG-диаграммы в sandbox-iframe без скриптов, так что похоже на заготовку под просмотр markdown. Если у проекта свой erusev/parsedown, проверьте, какой из двух классов Parsedown у вас реально загружается.
  • В комментарии к writePartCloud() описан off-by-one. CCloudStorageUpload::Part ждёт индекс с нуля, а номера частей идут с единицы. Без конвертации части уехали бы в S3 под номерами 2..N+1, и строгие провайдеры отклонили бы CompleteMultipartUpload.
  • Гонку за финализацию разводит TempFileTable::updateByFilter(). Атомарный UPDATE с условием UPLOADED = false возвращает число затронутых строк, и коммитит тот, кому досталась одна. В PHPDoc метода написано «not exposed by stock DataManager yet».
  • writePartLocal() игнорирует Content-Range от клиента и считает смещение сам, чтобы часть нельзя было подложить в произвольное место файла.
  • Playwright-фикстуру авторизации ui/test/e2e/auth/index.ts переписали, файл вырос с 31 до 286 строк, а в поставку добавились e2e-хелперы поповера и tile-widget. Паролей в коде нет, они читаются из переменных окружения.
  • ui.floating-ui везёт в дистрибутиве собственный rollup-плагин сборки, и chef собрал для него .min.js и .map.js.
  • Кнопки ui.button.panel получили data-testid. CHTTP::urlAddParams заменён на Bitrix\Main\Web\Uri в Manual и InfoHelper, а CHTTP::urlDeleteParams в админском шаблоне ui.toolbar.
  • VERSION_DATE у 26.600.0 стоит 23 июня 2026, на месяц раньше, чем у предыдущей 26.550.0 (23 июля).

Что делать

  • После обновления проверьте, что в БД есть b_ui_file_uploader_temp_file_part и новые колонки b_ui_file_uploader_temp_file.
  • Если сайт подключает ui.bootstrap4, пройдитесь по страницам с его JS-компонентами: скрипт стал 4.6.2, а CSS остался прежним (5.0.0-alpha1).
  • Найдите наследников Uploader и Controller\FileUploader и добавьте в upload() / uploadAction() параметр ?string $strategy = null.
  • Parallel и presigned включайте после проверки версии clouds, файловой системы временного каталога и canUpload() своих контроллеров. Запрос со strategy=parallel сервер примет и при выключенной настройке.

Читайте дальше

ui 26.650.0 Рутинное Свежее

UI 26.650.0: клавиатура в пикере реакций и аудио во вьюере

Релиз на 2 МБ, из PHP в нём только `config.php` расширений и номер версии. Пикером реакций теперь можно управлять с клавиатуры (роль `menu`, стрелки, Escape), у него появились опции `priorityReaction` и `contextAction` и методы `focus()` и `destroy()`. Если пикер привязан к кнопке, ссылке или элемен...

1 мин
ui 26.675.0 Ломающее Свежее

UI 26.675.0: ui.actionpanel переехал в бандл, а rich_text по-новому обходится с квадратными скобками

Модуль ui 26.675.0 собран 26 августа 2026. Из PHP поменялись `Converter` и `Whitelist` в `Bitrix\UI\Format\BBCode`, и пользовательские поля типа `rich_text` теперь иначе сохраняют и индексируют текст с квадратными скобками. На фронтенде старую панель групповых действий `ui.actionpanel` перевели на б...

4 мин
ui 26.687.0 Рутинное Свежее

UI 26.687.0: триал VibePlus в облаке и дизайн чипа TintedBitrixGpt

`Bitrix\UI\Controller\InfoHelper` при активации демо сначала спрашивает у модуля bitrix24, включён ли старт VibePlus, и если да, запускает триал VibePlus вместо обычного демо тарифа. Ветка срабатывает только при подключённом модуле bitrix24 и определённой константе `BX24_HOST_NAME`. В `ui.system.chi...

1 мин
voximplant 26.700.0 Ломающее Свежее

Voximplant 26.700.0: оценку качества связи убрали, у notifyAdmins() появился тип

Модуль телефонии voximplant 26.700.0 вышел в коробочный Битрикс24 11 сентября 2026 года, по данным официального канала «Битрикс24 changelog». Из карточки звонка убрали оценку качества связи вместе с её JS-событиями, у `Im::notifyAdmins()` появился тип параметра, а публичные константы `SipStatusInfor...

3 мин
Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

AI Домовой История

на связи

пишет…
Нет истории чатов
AI Домовой

Нужна авторизация

Войдите, чтобы задавать вопросы AI Домовому.

Войти