UI 26.696.0–26.700.0: закрыты дыры в правах на карточки, Lexical 0.50 и подсказки ui.system
Обновление безопасности
Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.
Вышли две версии 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
|
Удалено | — |