UI 26.693.0: загрузчик называет причину отказа, а вьювер прячет кнопку скачивания
В ui 26.693.0 загрузчик файлов перед сохранением ещё раз проверяет временный файл и при отказе отдаёт текст ошибки из CFile::CheckFile, а при превышении дисковой квоты кладёт в ошибку причину disk_quota_exceeded. Вьювер теперь прячет кнопку скачивания у отдельного файла, а FeaturePromoter.show() принимает ширину слайдера. API не ломается, но если у вас свои UploaderController или e2e-тесты на Input из расширения ui.system.input (BX.UI.System.Input.Input), прочитайте раздел про проверки.
Новое
Спрячьте кнопку скачивания во вьювере
У BX.UI.Viewer.Item появилась опция downloadDisabled: true, в разметке ей соответствует атрибут data-viewer-download-disabled="true". Для такого элемента вьювер снимает с кнопки скачивания href и прячет её, пропускает runAction('download') и не берёт ссылку из action download. Флаг читается методом isDownloadDisabled(). Если вьювер на странице ещё не подключён, подключите его: \Bitrix\Main\UI\Extension::load('ui.viewer');.
<img
src="/upload/contracts/42-preview.jpg"
data-viewer
data-viewer-type="image"
data-src="/upload/contracts/42.jpg"
data-title="Договор 42"
data-viewer-download-disabled="true"
>
Если элементы собираете в JS:
const item = new BX.UI.Viewer.Image({
src: '/upload/contracts/42.jpg',
title: 'Договор 42',
downloadDisabled: true,
});
BX.UI.Viewer.Instance.setItems([item]).then(() => BX.UI.Viewer.Instance.open(0));
Флаг меняет только интерфейс и файл не защищает. Без downloadUrl метод getDownloadUrl() по-прежнему отдаёт src, то есть ту же ссылку, по которой вьювер открыл файл. Доступ закрывайте на сервере. Для элементов из DOM работает только атрибут, потому что setPropertiesByNode() перезаписывает флаг значением из ноды и опция конструктора там не действует.
Узнайте, что загрузку отклонила квота
Перед TempFile::commit() загрузчик теперь сам прогоняет CFile::CheckFile по имени, размеру и MIME временного файла. Проверка стоит на всех трёх путях финализации: последний чанк обычной загрузки, completePresigned() и finalizeParallel(). Отказ приходит ошибкой UploaderError::CHECK_FILE_FAILED, а при нехватке места у неё в customData['reason'] лежит UploaderError::FILE_VALIDATION_REASON_DISK_QUOTA, то есть строка disk_quota_exceeded.
Пользователь это заметит, если место кончилось уже во время загрузки. Квоту CheckFile проверял и раньше, но только на старте, по заявленному размеру. Если к финишу места не оставалось, SaveFile молча возвращал false, и человек видел общее «Не удалось сохранить файл на сервере.». Теперь в этом случае он видит текст причины из CFile::CheckFile. Квота проверяется, только если в настройках главного модуля задано ограничение дискового пространства (disk_space).
Код причины пригодится, чтобы узнать о закончившемся месте раньше, чем придут жалобы:
<?php declare(strict_types=1);
namespace Vendor\Docs\Integration\UI;
use Bitrix\Main\Diag\Logger;
use Bitrix\UI\FileUploader\UploaderController;
use Bitrix\UI\FileUploader\UploaderError;
use Bitrix\UI\FileUploader\UploadResult;
final class ContractUploaderController extends UploaderController
{
// isAvailable(), getConfiguration(), canUpload() и остальные методы как были
public function onUploadError(UploadResult $uploadResult): void
{
foreach ($uploadResult->getErrors() as $error) {
$reason = $error->getCustomData()['reason'] ?? null;
if (
$error->getCode() === UploaderError::CHECK_FILE_FAILED
&& $reason === UploaderError::FILE_VALIDATION_REASON_DISK_QUOTA
) {
// логгер vendor.docs.quota описан в секции loggers файла .settings.php
Logger::create('vendor.docs.quota')?->warning('Upload refused: disk quota exceeded');
}
}
}
}
Квоту ядро распознаёт по строке. Ошибку CFile::CheckFile оно сравнивает с GetMessage('FILE_BAD_QUOTA'), а в комментарии рядом объясняют, что CheckFile сообщает причину только локализованной строкой. JS-часть disk_quota_exceeded пока не читает, значение встречается только в Uploader.php и UploaderError.php. Похоже на заготовку под отдельный интерфейс для превышения квоты.
Задайте ширину слайдера с промо
FeaturePromoter.show() принимает { width }. Слайдер берёт её, если это положительное число, иначе остаётся ширина провайдера, по умолчанию 700. Ширину заодно начал учитывать и BX.UI.InfoHelper.show(code, { width }). Если промо открывается попапом (PopupProvider), опция ни на что не влияет.
// в PHP: \Bitrix\Main\UI\Extension::load('ui.info-helper');
const button = document.querySelector('[data-promo-code]');
const promoter = new BX.UI.FeaturePromoter({ code: button.dataset.promoCode });
promoter.show({ width: 960 });
// или через старый вход
BX.UI.InfoHelper.show(button.dataset.promoCode, { width: 960 });
Проверьте e2e-селекторы и свой UploaderController
Затронет немногих, но поведение поменялось в трёх местах.
Inputиз расширенияui.system.input(BX.UI.System.Input.Input) с опциейdataTestIdвыводит у<input>атрибутdata-testidвместоdata-test-id. Упадут только автотесты с селекторами[data-test-id=...]. Многострочный вариант с<textarea>этот атрибут не выводил и не выводит.- Если
commit()или новая проверкаCheckFileотказывают вcompletePresigned()илиfinalizeParallel(), ошибка теперь идёт черезhandleUploadError()и вызываетonUploadError()вашего контроллера. Раньше на этих путях её возвращали без хука, так что уведомлений из хука станет больше. На обычной чанковой загрузке хук вызывался и раньше, но отказ по квоте или имени файла на финише теперь приходит кодомCHECK_FILE_FAILEDвместоSAVE_FILE_FAILED. Если хук ловитSAVE_FILE_FAILED, добавьте второй код. Uploader::remove()сообщает о неудаче: если временный файл не удалился, вRemoveResultпридётUploaderError::FILE_REMOVE_FAILED. Раньше результат удаления игнорировался.
Мелочи и находки
- В наборе Solid три новые иконки:
BX.UI.IconSet.Solid.BITRIX_GPT,SPEAKERиVIBECODE, с классами.ui-icon-set.--s-bitrix-gpt,--s-speaker,--s-vibecode. Первая и последняя нарисованы под BitrixGPT и Vibecode. - Во вьювере появились атрибуты для автотестов:
data-testid="ui-viewer"на корне,ui-viewer-close-btnиui-viewer-download-btnна кнопках. - Бандл
ui.info-helperвdistне совпадает сsrc. Раньше он был минифицирован, теперь это обычная сборка, и в ней естьActions.showDemoActivationError()сMessageBoxи обработка отказаui.infoHelper.activateDemoLicense. При этом контроллер полеerrorне возвращает, lang-ключа в эталоне нет, а вrelнетui.buttonsиui.dialogs.messagebox. Видимо, бандл собрали из более новой ветки, и это фронт под ошибки активации демо, которую в ui 26.687.0 научили запускать триал VibePlus.