ui · UI-библиотека 26.700.0 Безопасность

UI 26.696.0–26.700.0: закрыты дыры в правах на карточки, Lexical 0.50 и подсказки ui.system

2 мин чтения Устаревших API: 9
Узнавайте о ломающих изменениях Битрикса раньше, чем они доедут до прода: свежие разборы — в канале @bxmax_updates

Обновление безопасности

Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.

Вышли две версии ui: 26.696.0 научила холст ui.block-diagram копировать блоки, а 26.700.0 на 5290 файлов принесла расширения ui.system, Lexical 0.50 и права доступа V2 со значениями из entity-selector. Severity security из-за 26.700.0, где закрыты копирование чужого представления карточки, выдача подписи на создание пользовательских полей в карточке только для чтения, XSS в ui.tooltip и открытый доступ к REST-манифестам масок. Проверьте свои плагины Lexical, редакторы на ui.block-diagram и запись кодов доступа представлений. Прошлый разбор — ui 26.693.0.

Новое

Берите иллюстрации и подсказки из ui.system (26.700.0)

В ui.system появились четыре расширения, у каждого есть Vue-обёртка: ui.system.illustration (около 200 SVG, имена в enum IllustrationName), ui.system.tooltip, ui.system.hint (кнопка «?» с пузырём) и ui.system.callout (карточка с текстом и кнопками у элемента). Простому тултипу после Extension::load('ui.system.tooltip') хватит атрибута data-ui-tooltip="Скопировать ссылку", JS писать не нужно. Строку из content тултип выводит текстом, разметку включает только html: true.

        // PHP: \Bitrix\Main\UI\Extension::load(['ui.system.illustration', 'ui.system.hint', 'ui.system.callout']);
const { Illustration, IllustrationName } = BX.UI.System.Illustration;

new Illustration({
	name: IllustrationName.EMPTY_SEARCH,
	size: 120,
	ariaLabel: 'Договоры не найдены',
}).renderTo(document.querySelector('.vendor-contracts-empty'));

const hint = new BX.UI.System.Hint.Hint({
	tooltipOptions: { content: 'Сумма без НДС' },
});
document.querySelector('.vendor-total-label').append(hint.render());

const callout = new BX.UI.System.Callout.Callout({
	target: document.querySelector('.vendor-export-button'),
	title: 'Выгрузка в Excel теперь здесь',
	buttons: [{ text: 'Понятно', events: { click: () => callout.destroy() } }],
});
callout.show();

    

Ещё появился попап «Что нового» (ui.system.popups.whats-new), а в ui.icon-set набор solid вырос со 118 до 522 иконок, outline — с 651 до 775.

Сделайте левую панель слайдера резиновой (26.700.0)

Компонент bitrix:ui.sidepanel.wrapper получил параметр LEFT_PANEL_RESIZABLE. Ширину левой панели меняют мышью и с клавиатуры, с STORAGE она запоминается в localStorage. Пять размеров обязательны и должны быть именно int (строка '280' не пройдёт), INITIAL_WIDTH лежит между MIN_WIDTH и MAX_WIDTH. При ошибке в параметрах опция молча выключается. Панель, как и раньше, наполняется через $APPLICATION->SetViewTarget('left-panel').

        <?php declare(strict_types=1);

require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/header.php';

/** @var CMain $APPLICATION */
$APPLICATION->IncludeComponent('bitrix:ui.sidepanel.wrapper', '', [
	'POPUP_COMPONENT_NAME' => 'vendor:docs.browser',
	'POPUP_COMPONENT_TEMPLATE_NAME' => '',
	'POPUP_COMPONENT_PARAMS' => ['FOLDER_ID' => 42],
	'LEFT_PANEL_RESIZABLE' => [
		'INITIAL_WIDTH' => 280,
		'MIN_WIDTH' => 200,
		'MAX_WIDTH' => 480,
		'MIN_CONTENT_WIDTH' => 640,
		'KEYBOARD_STEP' => 16,
		'STORAGE' => ['TYPE' => 'localStorage', 'KEY' => 'vendor-docs-left-panel', 'VERSION' => 1],
	],
]);

require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/footer.php';

    

Подключите справочник к правам доступа V2 (26.700.0)

Раньше значения MultiVariables передавались статическим списком. Теперь их можно брать из сущности ui.entity-selector через VariablesSource, с ленивой загрузкой и поиском, и так уже устроены права на шаблоны в bizproc. Для значения, которое источник не назвал, задайте подпись с плейсхолдером #VALUE#. Готовые наборы значений зависимых переменных описывает RightItem\Preset, в попапе они становятся вкладками.

        <?php declare(strict_types=1);

use Bitrix\UI\AccessRights\V2\Control\MultiVariables;
use Bitrix\UI\AccessRights\V2\Control\Value\VariablesSource;
use Bitrix\UI\AccessRights\V2\Options\RightSection\RightItem;
use Bitrix\UI\AccessRights\V2\Options\RightSection\RightItem\Preset;
use Bitrix\UI\AccessRights\V2\Options\RightSection\RightItem\Type;

$contracts = (new MultiVariables())
	->enableSearch(true)
	->variablesSource(new VariablesSource(
		entityId: 'vendor-contract', // ваш провайдер ui.entity-selector
		providerOptions: ['onlyActive' => true],
	))
	->allSelectedTitle('Все договоры')
	->unloadedVariableTitlePattern('Договор №' . RightItem::UNLOADED_VARIABLE_VALUE_PLACEHOLDER);

$access = (new RightItem('CONTRACT_ACCESS', 'Доступ к договорам', Type::DependentVariables))
	->addPreset(
		(new Preset('managers', 'Менеджеры'))
			->setDescription('Читают и правят свои договоры')
			->setVariableIds(['read_own', 'edit_own'])
	);

    

Назначайте представления карточек на роли проекта (26.700.0)

Представление карточки в ui.form.config можно назначить на роль проекта: SG<id>_A, SG<id>_E или SG<id>_K (владелец; владелец и модераторы; все участники). Нужен модуль socialnetwork. Для этого в селекторе есть провайдер project-access-codes. Раньше prepareAccessCodes() отрезал роль и сохранял просто SG<id>.

Копируйте блоки на холсте ui.block-diagram (26.696.0)

useCopyPaste() копирует выделенные блоки со связями внутри выделения, сам вешает Mod+C/Mod+V и отдаёт пункты контекстного меню copyMenuItem и pasteMenuItem. Вызывайте его один раз на приложение диаграммы, иначе одно нажатие вставит фрагмент дважды. После вставки BlockDiagram шлёт pasteBlocks с { blocks, connections, point }, а endGesture приходит один раз на любой жест, изменивший модель, и по умолчанию входит в historyHooks.

        import { Text } from 'main.core';
import { useCopyPaste } from 'ui.block-diagram';

// setup() компонента внутри Vue-приложения диаграммы
const { copyMenuItem, pasteMenuItem } = useCopyPaste({
	createBlockId: () => `vendor_${Text.getRandom(8)}`,
	// parentId — ваше поле со ссылкой на другой блок
	cloneContent: (content, blockIdMap) => ({
		...content,
		blocks: content.blocks.map((block) => ({
			...block,
			parentId: blockIdMap.get(block.parentId) ?? null,
		})),
	}),
	canPaste: () => !props.readonly,
});

// в шаблоне: <BlockDiagram ... @pasteBlocks="onPaste" @endGesture="saveDraft" />

    

Переназначьте горячие клавиши холста (26.700.0)

У действий холста появились идентификаторы CANVAS_ACTION_ID (canvas.undo, canvas.toggleMap, canvas.fitToView и другие). Сочетания хранит ActionRegistry, свои передаются разницей с умолчаниями, а validate() заранее скажет, занята комбинация (OCCUPIED) или зарезервирована системой (RESERVED). Если одно сочетание достанется двум действиям, реестр вернётся к умолчаниям.

        import { useBlockDiagram, CANVAS_ACTION_ID, ComboVerdict, getCanvasActionDefaults, normalizeCombo } from 'ui.block-diagram';

const { actionRegistry } = useBlockDiagram();
const combo = normalizeCombo(['Mod', 'Shift', 'l']);

if (actionRegistry.validate(combo, CANVAS_ACTION_ID.TOGGLE_MAP) === ComboVerdict.FREE)
{
	actionRegistry.applyBindings(
		getCanvasActionDefaults(),
		new Map([[CANVAS_ACTION_ID.TOGGLE_MAP, combo]]),
	);
}

    

Подпись для кнопки отдаёт useActionShortcut(actionId).

Пишите плагины text-editor с зависимостями и уборкой (26.700.0)

У BX.UI.TextEditor.BasePlugin появились статические getDependencies(), getPeerDependencies() и getDefaultOptions(). Зависимости редактор подключит сам, а зависимость в removePlugins отвергнет с TypeError. Peer-зависимости задают только порядок инициализации. afterInit() может вернуть функцию очистки, таймеры из registerTimeout() и слушатели на getAbortSignal() снимутся при уничтожении плагина.

Сам ui.text-editor переписали на TypeScript, экспорт BX.UI.TextEditor не изменился.

        class SymbolCounterPlugin extends BX.UI.TextEditor.BasePlugin
{
	static getName()
	{
		return 'VendorSymbolCounter';
	}

	afterInit()
	{
		const counter = document.querySelector('.vendor-symbols');
		const unregister = this.getLexicalEditor().registerTextContentListener((text) => {
			counter.textContent = String(text.length);
		});

		return () => {
			unregister();
			counter.textContent = '';
		};
	}
}

const editor = new BX.UI.TextEditor.TextEditor({ extraPlugins: [SymbolCounterPlugin] });

    

Что увидит пользователь

На холсте ui.block-diagram, где работает дизайнер БП, блоки с 26.700.0 больше не ложатся друг на друга. При перетаскивании, вставке и ресайзе блок сдвигается на свободное место, а если места рядом нет, действие отменяется с сообщением.

С 26.700.0 холст панорамируется стрелками, Shift+1 показывает схему целиком. Ещё с 26.696.0 перетаскивание отменяется по Escape.

В 26.700.0 Битрикс прошёлся по доступности. Поля карточки ui.entity-editor в режиме просмотра получили role=button и aria-label, диалоги ui.text-editor держат фокус, ui.date-picker управляется с клавиатуры, а уведомления ui.notification зачитывает скринридер.

Что сломается

Плагины на Lexical (26.700.0)

Lexical обновили с 0.17.1 до 0.50.0. Из BX.UI.Lexical.List удалены insertList и removeList, из BX.UI.Lexical.Link — toggleLink. Замены $insertList, $removeList и $toggleLink лежат в тех же неймспейсах, и, как все функции с $, их вызывают внутри editor.update(). Коробка старые имена не использует, проверять нужно свои плагины BX.UI.TextEditor и прямые обращения к BX.UI.Lexical.*.

        // было: BX.UI.Lexical.List.insertList(editor, 'bullet');
editor.update(() => BX.UI.Lexical.List.$insertList('bullet'));

    

Свой редактор на ui.block-diagram (26.696.0)

Из ui.block-diagram (глобально BX.UI) убрали шесть композаблов: useMoveableBlock, useResizableBlock, useDragAndDrop, useGroupSelectionLogic, useGroupDragLogic и useAutoScroll. MoveableBlock и ResizableBlock больше не принимают prop highlighted, выделением управляет жест. Нажатие выбирает блок, нажатие на уже выделенный тащит всю группу, а запретить перемещение можно новым prop canStartMove у ResizableBlock. Из состояния useBlockDiagram() ушли shortcuts, mousePosition и isKeyboardInitialized. bizprocdesigner удалённые хуки не импортирует.

Коды доступа представлений карточек (26.700.0)

Bitrix\Ui\EntityForm\Scope::updateScopeAccessCodes() принимает только коды U, D, DR, SND, SNDR и SG<id> с ролью или без. Коды вроде G1 раньше записывались как есть, теперь отбрасываются. Если после фильтра ничего не осталось, метод возвращает текущих участников и ничего не меняет, хотя раньше пустой массив снимал всех.

Снять всех теперь можно новым replaceScopeAccessCodes($configId, []), он коды не фильтрует.

Загрузчик сверяет настоящий формат картинки (26.700.0)

ImageSizeFilter в ui.uploader определяет формат по содержимому файла: gif, png, bmp, jpeg или webp. Если формата нет в acceptedFileTypes, файл отклоняется с FILE_TYPE_NOT_ALLOWED, а при ignoreUnknownImageTypes загружается как обычный файл. Например, webp, переименованный в .jpg, в поле с ограничением по типам у пользователя больше не пройдёт.

Поле rich_text включено везде (26.700.0)

RichTextUserFieldFlag::enabledByDefault() возвращает true, и тип «rich_text» появился на всех порталах, где флаг явно не выключен. Значение сохраняется через Emoji::encode(), а новый onAfterFetch() делает Emoji::decode(), поэтому прямой запрос к таблице в обход UF-менеджера вернёт эмодзи закодированными. Кнопку смайлов из панели поля убрали.

        <?php declare(strict_types=1);

use Bitrix\Main\Config\Feature;
use Bitrix\Main\Text\Emoji;
use Bitrix\UI\Config\Feature\RichTextUserFieldFlag;

// если тип на портале не нужен
Feature::disable(RichTextUserFieldFlag::class);

// $row — строка из вашего прямого запроса к таблице UF
$note = Emoji::decode((string)$row['UF_VENDOR_NOTE']);

    

Права проверяются строже (26.700.0)

  • copyScopeAction() в ui.form.config требует права на чтение исходного представления (ScopeAccess::canRead), чужое представление с его кодами доступа больше не скопировать.
  • Карточка только для чтения на ProviderWithUserFieldsTrait отдаёт ENABLE_USER_FIELD_CREATION = false и пустую USER_FIELD_CREATE_SIGNATURE, так что подпись на изменение схемы полей не получает тот, кто может только смотреть. Трейт используют, например, складские провайдеры catalog.
  • ui.infoHelper.activateDemoLicense при установленном intranet пускает только интранет-пользователей, экстранет демо-режим не включит.
  • MaskManifest::onCheckAccess() раньше разрешал всё. Теперь доступен только манифест ui_masks и только авторизованному пользователю, ui_masks_system получает отказ. Проверьте экспорт и импорт масок через REST-конфигурации.

Deprecated

Собственный код ui в этих версиях ничего не помечает, все новые @deprecated пришли из Lexical 0.50. Если пишете плагины к ui.text-editor, вот основные замены:

  • $insertTableRow, $insertTableColumn, $deleteTableColumn → $insertTableRowAtSelection, $insertTableColumnAtSelection, $deleteTableColumnAtSelection (старые не поддерживают объединённые ячейки);
  • $wrapNodes → $setBlockTypes;
  • CAN_UNDO_COMMAND/CAN_REDO_COMMAND → сигналы canUndo/canRedo из HistoryExtension;
  • $findTextIntersectionFromCharacters собираются удалить, потому что он, по словам авторов Lexical, «has never worked correctly».

БД

В 26.700.0 b_ui_entity_editor_config_ac.ACCESS_CODE расширили с VARCHAR(10) до VARCHAR(11) в MySQL, PostgreSQL и ORM EntityFormConfigAcTable, иначе код вида SG1234567_K не помещался. ALTER для установленных порталов в снимке не виден, так что перед своей записью в таблицу проверьте колонку: SHOW COLUMNS FROM b_ui_entity_editor_config_ac LIKE 'ACCESS_CODE'.

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

  • В ui.tooltip (26.700.0) закрыт XSS. Должность экстранет-пользователя и пользователя портала без intranet, а также имена руководителей теперь экранируются, HTML в них выводится текстом.
  • У canvas.toggleMap (26.700.0) по умолчанию Mod+Shift+M, потому что Cmd+M на macOS сворачивает окно раньше, чем страница увидит нажатие.

Устаревшие и удалённые API в этой версии

Символ Статус Чем заменять
BX.UI.Lexical.List.insertList Удалено BX.UI.Lexical.List.$insertList
BX.UI.Lexical.List.removeList Удалено BX.UI.Lexical.List.$removeList
BX.UI.Lexical.Link.toggleLink Удалено BX.UI.Lexical.Link.$toggleLink
BX.UI.useMoveableBlock Удалено —
BX.UI.useResizableBlock Удалено —
BX.UI.useDragAndDrop Удалено —
BX.UI.useGroupSelectionLogic Удалено —
BX.UI.useGroupDragLogic Удалено —
BX.UI.useAutoScroll Удалено —

Новая версия вышла — разбор уже в Telegram

В канале @bxmax_updates публикуем каждый свежий разбор: что сломается, что устарело и что появилось в API. Узнаете до того, как обновите прод.

Подписаться

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

ui 26.693.0 Заметное Свежее

UI 26.693.0: загрузчик называет причину отказа, а вьювер прячет кнопку скачивания

В ui 26.693.0 загрузчик файлов перед сохранением ещё раз проверяет временный файл и при отказе отдаёт текст ошибки из `CFile::CheckFile`, а при превышении дисковой квоты кладёт в ошибку причину `disk_quota_exceeded`. Вьювер теперь прячет кнопку скачивания у отдельного файла, а `FeaturePromoter.show(…

3 мин
ui 26.600.0 Ломающее

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

Дифф UI 26.600.0 даже без языковых файлов и `.min.js` весит 19 МБ, и почти весь этот объём занимают пересобранные бандлы и вендорные библиотеки (один mermaid дал 136 тысяч строк). Из содержательного загрузчик файлов научился принимать части параллельно и отдавать их браузеру для загрузки прямо в S3….

7 мин
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 мин
Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

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

на связи

пишет…

Можно закрыть чат и перейти на другую страницу. Когда ответ будет готов, на кнопке Домового появится отметка.

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

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

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

Войти