catalog · Торговый каталог 26.400.0 Безопасность

Catalog 26.400.0: остатки по складам в REST теперь режутся правами

3 мин чтения Устаревших API: 1

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

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

Catalog 26.400.0 затрагивает 145 файлов и больше двадцати тысяч строк, но почти весь объём дали пересобранные JS-бандлы формы товаров и дубли файлов из install/. Содержательных изменений три. REST-выдача остатков и строк складских документов начала учитывать права на склады, установщик модуля переехал с install.sql на миграции ядра, а форма товаров научилась работать со ставками НДС. Первое касается интеграций, которые ходят в REST не под администратором на установке с модулем crm. Без crm выдача остатков правами не режется.

Проверьте REST-интеграции со складами

До этого релиза контроллер Bitrix\Catalog\Controller\StoreProduct брал listAction из общего трейта ListAction, и фильтра по складам в нём не было. Теперь listAction у контроллера свой. getAction по-прежнему приходит из трейта GetAction, но переопределённый protected get($id) читает запись через getByPrimary() с фильтром доступа. Оба пути добавляют к запросу getEntityFilter(ActionDictionary::ACTION_STORE_VIEW, StoreProductTable::class) текущего AccessController, общее число строк тоже считается по итоговому фильтру.

Для StoreProductTable класс StoreViewFilter сразу уходит в getProductFilter(), а тот возвращает пустой фильтр администратору и в legacy-режиме, то есть когда модуль crm не установлен или идёт установка. На редакциях без crm REST-выдача остатков правами не режется вообще. В режиме с CRM пользователь с доступом к двум складам из пяти получит остатки только по двум.

Со строками складских документов (Controller\Document\Element::listAction) обошлись строже. К фильтру по типам документов добавился фильтр по складам, а новый getList() обнуляет поля там, куда пользователю смотреть нельзя:

  • STORE_FROM, STORE_TO, AMOUNT и PURCHASING_PRICE становятся null, если склад строки не входит в разрешённые;
  • PURCHASING_PRICE становится null без права на закупочные цены (ACTION_PRODUCT_PURCHASE_INFO_VIEW).

Вдобавок RestView\DocumentElement вешает на эти поля атрибуты DISABLED_FILTER и DISABLED_ORDER для всех, у кого нет полного доступа к складам. Отфильтровать или отсортировать строки по AMOUNT такой пользователь уже не сможет.

Для остальных сущностей поменялась сама проверка прав. Раньше StoreViewFilter досрочно выходил только для администратора, а пользователь с правом на все склады получал пустой фильтр уже внутри getStoreFilter() и getDocumentFilter(). Теперь для всех сущностей, кроме StoreProductTable (документы и их строки, склады, отгрузки), решение принимает checkCompleteRight() контроллера доступа, который для админа и в legacy-режиме вызывает обычный check(). Условие legacy-режима вынесли в публичный isLegacyAccessMode().

Если вы читаете остатки через ORM в своём коде и хотите показывать то же, что отдаст REST, фильтр можно взять у того же контроллера доступа:

        <?php declare(strict_types=1);

use Bitrix\Catalog\Access\AccessController;
use Bitrix\Catalog\Access\ActionDictionary;
use Bitrix\Catalog\StoreProductTable;
use Bitrix\Main\Loader;

Loader::requireModule('catalog');

function fetchVisibleStock(int $productId): array
{
    $accessFilter = AccessController::getCurrent()->getEntityFilter(
        ActionDictionary::ACTION_STORE_VIEW,
        StoreProductTable::class,
    ) ?? [];

    return StoreProductTable::getList([
        'select' => ['STORE_ID', 'AMOUNT', 'QUANTITY_RESERVED'],
        // условия во вложенных массивах, чтобы OR из своего фильтра не обошёл доступ
        'filter' => [
            ['=PRODUCT_ID' => $productId],
            $accessFilter,
        ],
    ])->fetchAll();
}

    

Так же фильтр собирает само ядро. В прежней версии Document\Element это объяснял комментарий: условия объединяются через новый массив, чтобы OR не обходил фильтр доступа.

Не ищите install.sql

Файлы install/db/{mysql,pgsql}/install.sql и uninstall.sql удалены. InstallDB() теперь вызывает installMigrations(), UnInstallDB() вызывает uninstallMigrations(), оба метода модуль наследует от CModule. Таблицы описаны в install/migrations/tables.php, 72 обработчика событий лежат в events.php, агент SystemField::execAgent() в agents.php. В migration_config.json в качестве defaultTableName указана b_catalog_iblock.

Скрипты разворачивания или сравнения схемы, которые читали catalog/install/db/mysql/install.sql, после обновления его не найдут.

Итог строки в форме товаров теперь с НДС

BasketItem::getSum() в интеграции с JS-формой товаров считает итог строки от price, а не от priceExclusive. К методу добавили комментарий с примером: в режиме «НДС сверху» (taxIncluded = 'N') в priceExclusive лежит цена нетто, и строка с товаром за 100 и ставкой 22% показывала 100 вместо 122. В JS-модели заодно перестали умножать налог на количество, taxSum теперь хранится на всю строку.

Если вы передаёте ставки в форму из своего кода, переименуйте опцию. Вместо taxList форма ждёт taxRateList, массив объектов {taxId, value}. По комментарию в tax.js, value: null означает ставку «без НДС» (EXCLUDE_VAT = 'Y'). Тип BasketTax удалён. Событие changeTax по-прежнему отдаёт taxId и taxValue, но смысл полей другой: раньше в taxId приходил индекс ставки в массиве taxList, теперь приходит ID ставки, а taxValue может быть null. Список удобно собрать из справочника ставок каталога:

        <?php declare(strict_types=1);

use Bitrix\Catalog\VatTable;
use Bitrix\Main\Loader;

Loader::requireModule('catalog');

$taxRateList = array_map(
    static fn(array $vat): array => [
        'taxId' => (int)$vat['ID'],
        'value' => $vat['EXCLUDE_VAT'] === 'Y' ? null : (float)$vat['RATE'],
    ],
    VatTable::getList([
        'select' => ['ID', 'RATE', 'EXCLUDE_VAT'],
        'filter' => ['=ACTIVE' => 'Y'],
    ])->fetchAll(),
);

    

Сверьте ссылки в письмах о поступлении товара

Ссылки для писем подписки на товар строят SubscribeTable::prepareDataForNotice() и SubscribeManager. Макросы CHECKOUT_URL_PARAMETERS, UNSUBSCRIBE_URL_PARAMETERS и URL_PARAMETERS раньше строились через CHTTP::urlAddParams('', ...), теперь через Uri::getQuery(), а полные ссылки через (new Uri(...))->addParams().

Сам дифф разницу в формате не показывает, она видна по коду main в эталонной установке. CHTTP::urlAddParams('', ...) возвращал строку с ведущим ? и без кодирования значений, например ?userContact=user@example.com. Uri::getQuery() отдаёт её без ? и кодирует значения по RFC 3986, так что @ превращается в %40. Шаблон вида …/catalog/item/#URL_PARAMETERS# после обновления получит ссылку без вопросительного знака. Если эти макросы есть в ваших почтовых шаблонах, отправьте тестовое письмо и сравните ссылку с прежней.

Новое: настройки НДС в форме товаров

Пункты про налоги в настройках формы раньше были закомментированы, а showTaxBlock выключался принудительно. Теперь их включает опция showTaxSettingsSwitcher: 'Y'. По умолчанию она 'N', так что существующие формы выглядят как раньше. С опцией в настройках появляются пункты «Налог включен в цену» и «Показать налоги», а рядом со ставкой в строке выводится сумма налога. Первый добавленный товар задаёт форме режим TAX_INCLUDED. Цены следующих товаров пересчитываются под этот режим новым ProductCalculator.convertBasePriceForTaxIncluded(), и такая строка получает isCustomPrice: 'Y'.

Ещё в форме появились опция displayPrecision (по умолчанию 2 знака) и хелпер MoneyInput для полей цены, скидки и итога. Он чистит ввод, не сбивая курсор, меняет запятую на точку и отправляет значение с задержкой 500 мс.

БД

Схема не менялась. tables.php описывает те же 48 таблиц, что и старый MySQL-скрипт, с теми же колонками, типами, значениями по умолчанию и индексами. Одно расхождение касается PostgreSQL. В старом pgsql/install.sql у TIMESTAMP_X в b_catalog_price, b_catalog_product, b_catalog_discount и b_catalog_vat стоял DEFAULT CURRENT_TIMESTAMP, в миграции его нет. Какой DDL билдер соберёт для PostgreSQL, по диффу не видно. Существующих баз это не касается, ALTER в релизе нет.

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

  • При удалении модуля старый UnInstallDB() регистрировал обработчик PropertyFeature::OnPropertyFeatureBuildList заново, потому что в коде стоял registerEventHandler вместо unRegisterEventHandler. OnIBlockElementDelete снимали для класса CProduct, хотя вешали на CCatalogProduct, а OnGroupDelete не снимали вовсе. Теперь список обработчиков один, в events.php.
  • Вызовы CHTTP::urlAddParams() и urlDeleteParams() вычистили из семи файлов модуля в пользу Bitrix\Main\Web\Uri.
  • В core_tree.js (дерево условий правил) один объект параметров логики мутировался и уходил во все связки уровня. Теперь каждая связка получает свою копию.
  • Выбор вариантов в sku-tree размечен как ARIA radiogroup с навигацией стрелками, а product-selector возвращает фокус после выбора с клавиатуры. В комментариях к этим правкам встречаются метки «TPL-01» и «(B7)», похожие на номера задач.
  • VERSION_DATE у 26.400.0 указан 16 июля 2026, на три недели раньше, чем у предыдущей 26.300.100 (8 августа).
  • Deprecated в релизе нет.

Что делать

  • Если на установке есть модуль crm, прогоните REST-интеграции, которые читают StoreProduct и строки складских документов, под тем пользователем, под которым они работают. Проверьте, что выгрузка не опирается на фильтр или сортировку по AMOUNT, STORE_FROM, STORE_TO и PURCHASING_PRICE.
  • Поищите в скриптах и CI обращения к catalog/install/db/*/install.sql.
  • Если передаёте в catalog.product-form опцию taxList, замените её на taxRateList.
  • Если в шаблонах писем о поступлении товара есть *_URL_PARAMETERS, отправьте тестовое письмо.

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

Символ Статус Чем заменять
catalog.product-form: taxList Удалено taxRateList

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

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

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

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

3 мин
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 мин
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.650.0 Рутинное Свежее

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

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

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

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

на связи

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

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

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

Войти