uplabteam / uplab.api
HTTP API для «1С-Битрикс: Управление сайтом» — отдаёт инфоблоки, highload-блоки, веб-формы, меню, поиск и карту сайта headless-фронтенду с учётом прав и кеша
Requires
- php: >=8.1
- ext-json: *
- ext-mbstring: *
- composer/installers: ^1.0 || ^2.0
Requires (Dev)
- phpunit/phpunit: ^10.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-08-24 22:14:01 UTC
README
Модуль для «1С-Битрикс: Управление сайтом», предоставляющий HTTP API для чтения данных инфоблоков и highload-блоков, работы с веб-формами, меню, поиском, картой сайта и статическим контентом.
- GitHub: github.com/uplab-dev/uplab.api
- Composer: packagist.org/packages/uplabteam/uplab.api
Модуль предоставляется «как есть», без каких-либо гарантий. Вы используете его на свой страх и риск и сами отвечаете за последствия — см. раздел «Гарантии и ответственность».
Модуль не превращает API в публичный автоматически: доступ определяется правами текущего пользователя Битрикс и настройками Origin/IP. Перед размещением в интернете ознакомьтесь с разделом «Безопасность» ниже.
Возможности и требования
- ID модуля:
uplab.api - Неймспейс:
Uplab\Api\ - PHP ≥ 8.1
- Язык по умолчанию:
ru - Обязательные модули Битрикс зависят от используемых endpoint-ов:
iblock,highloadblock,form,search.
Установка
Через Composer
Модуль объявлен как "type": "bitrix-module" и требует composer/installers, поэтому раскладывается не в vendor/, а в каталог модулей Битрикса. Путь задаёт проект-потребитель: без настройки плагин использует каталог по умолчанию и модуль встанет в bitrix/modules/uplab.api (работать будет, но bitrix/ обычно исключён из репозитория проекта). Чтобы модуль ставился в local/modules, добавьте в корневой composer.json проекта:
{
"extra": {
"installer-paths": {
"local/modules/{$name}/": ["type:bitrix-module"]
}
},
"config": {
"allow-plugins": {
"composer/installers": true
}
}
}
composer require uplabteam/uplab.api
Модуль установится в local/modules/uplab.api. Дальше — шаги 2–4 из раздела «Вручную».
Альтернатива:
"extra": {"bitrix-dir": "local"}— ключ, специфичный для Bitrix-установщика, даёт тот же результат.installer-pathsпредпочтительнее: это стандартный механизмcomposer/installersи он явно показывает целевой путь.
Вручную
- Скопируйте репозиторий в
<DOCUMENT_ROOT>/local/modules/uplab.api. - Установите модуль в административной части: Marketplace → Установленные решения.
- Подключите маршруты модуля в пользовательском файле маршрутов, например
/local/routes/uplab_api.php:
<?php use Bitrix\Main\Routing\RoutingConfigurator; use Uplab\Api\Routing\Configurator; return static function (RoutingConfigurator $routes): void { Configurator::set($routes); };
- Добавьте имя файла в секцию
routingфайла/local/.settings.php:
'routing' => [ 'value' => [ 'config' => ['uplab_api.php'], ], 'readonly' => true, ],
После установки встроенная документация доступна в административном меню Сервисы → API Документация.
Безопасность
- Публичные выборки инфоблоков выполняются с
CHECK_PERMISSIONS=Y; кеш разделяется по группам текущего пользователя. - HL-блок публичен, пока никому не выдано право чтения
hl_element_read; как только право выдано, читают только его обладатели и администратор. Кеш также разделяется по группам. Origin— управляемый клиентом заголовок и не является аутентификацией. Для закрытого API используйте права Битрикс, IP allowlist и/или авторизацию на веб-сервере.- Сообщайте об уязвимостях приватно по правилам из SECURITY.md, а не через публичный issue.
Лицензия
Модуль распространяется по проприетарной лицензии — LICENSE.txt. Правообладатель — ООО «Аплэб». Коротко:
- можно безвозмездно — устанавливать и использовать Модуль на любом числе проектов, включая коммерческие; изучать код; настраивать поведение штатными средствами (наследование классов, события, точки расширения); изменять Модуль под нужды своих проектов и проектов заказчиков, в том числе исправлять ошибки; передавать Модуль (исходный или изменённый) заказчику в составе результата работ или иным лицам — безвозмездно, с сохранением текста лицензии и указаний на авторство и с пометкой, что копия изменена;
- можно брать плату — за работы по разработке, внедрению, настройке, доработке и поддержке проектов, в которых используется Модуль: это не продажа Модуля;
- нельзя — продавать Модуль или его изменённые версии, сдавать в аренду, сублицензировать и иначе брать плату за сам Модуль или право его использования; включать Модуль в платные тиражные решения, шаблоны и сборки; размещать его от своего имени в каталогах решений и магазинах приложений; распространять Модуль и производные от него работы под другой лицензией; выдавать за собственную разработку и снимать указания на авторство.
Изменение Модуля не даёт прав на Модуль: исключительные права остаются у правообладателя. Право на продажу и распространение на иных условиях предоставляется по отдельному договору: info@uplab.ru. Исправления и улучшения присылайте pull request'ом — они попадут в общий релиз, и правку не придётся поддерживать у себя.
Гарантии и ответственность
Модуль распространяется бесплатно и предоставляется «как есть» (as is). Правообладатель не даёт никаких гарантий: ни того, что модуль подходит для вашей задачи, ни того, что в нём нет ошибок или уязвимостей, ни того, что он будет работать без сбоев на вашей конфигурации.
- Вы используете модуль на свой страх и риск. Решение об установке принимаете вы, и вся ответственность за работу сайта после установки — на вас.
- Правообладатель не отвечает за последствия использования или невозможности использования модуля: простой сайта, потерю или порчу данных, недополученную прибыль, утечку данных и взлом сайта — в том числе если вектором атаки стала ошибка или уязвимость в модуле.
- Проверяйте перед боевым запуском. Разверните модуль на тестовом контуре, пройдите разделы «Безопасность» и «Доступ к API», оцените риски применительно к своим данным. Публичный API отдаёт наружу ровно то, что вы ему разрешите настройками и правами Битрикс.
- Если такой режим ответственности вас не устраивает — не устанавливайте модуль. Промежуточных условий нет: использование означает согласие с ними.
- Поддержка не гарантирована. Правообладатель не обязан выпускать обновления, исправлять ошибки и отвечать на обращения. Об уязвимостях сообщайте приватно по правилам из SECURITY.md — исправления выпускаются по возможности.
- За чужие правки ответственности нет. Если модуль изменяли (вы или подрядчик), последствия таких изменений — вне зоны ответственности правообладателя.
Полные условия — в LICENSE.txt, раздел 4 (пункты 4.1–4.6).
Настройки модуля
Настройки доступны в Битрикс: Настройки → Настройки продукта → Настройки модулей → uplab.api.
Вкладка «Общие настройки»
| Параметр | По умолчанию | Описание |
|---|---|---|
SHOW_PAGE_CONTENT_TAB |
N |
Выводить вкладку «Текстовый контент» на странице элемента инфоблока в административном интерфейсе. |
Вкладка «Доступ к API»
Управляет ограничением доступа к публичным API endpoint-ам. По умолчанию всё отключено — никаких ограничений нет.
| Параметр | По умолчанию | Описание |
|---|---|---|
API_ACCESS_ENABLED |
N |
Включить проверку доступа. При выключенном чекбоксе (значение по умолчанию) все запросы пропускаются; при включённом, но с двумя пустыми списками — тоже. |
API_ALLOWED_ORIGINS |
(пусто) | Разрешённые значения заголовка Origin (по одному на строку) — для браузерных запросов. Схема необязательна: site.ru и https://site.ru равнозначны. |
API_ALLOWED_IPS |
(пусто) | Разрешённые значения REMOTE_ADDR (по одному на строку) — для серверных запросов без Origin (Nuxt SSR и т.п.). Пример: 127.0.0.1. |
Логика проверки (lib/ActionFilter/ApiAccessFilter.php):
- Если
API_ACCESS_ENABLED = N→ запрос пропускается (значение по умолчанию: API открыт). - Если оба списка пусты → запрос пропускается: правил нет, ограничивать нечем.
- Если
REMOTE_ADDRесть в IP-списке → запрос пропускается. - Иначе, если заголовок
Originесть в Origin-списке → запрос пропускается. - Не прошёл ни по одному заданному списку →
403 Access denied.
Списки проверяются по «ИЛИ»: браузерные запросы проходят по Origin, серверные (без заголовка) — по IP. Поэтому рабочая конфигурация для схемы «браузер + SSR» — заполнить оба списка. Обхода «снять заголовок Origin» нет: запрос без заголовка обязан пройти по IP-списку, а если он пуст — не проходит.
При работе через reverse proxy убедитесь, что REMOTE_ADDR содержит адрес доверенного прокси, либо ограничивайте доступ на самом прокси. Заголовки X-Forwarded-For модуль намеренно не принимает без модели доверенных прокси.
Разработка
В
composer.jsonобъявленconfig.allow-pluginsдляcomposer/installers: без него Composer 2.2+ блокирует плагин иcomposer installв самом репозитории модуля падает (в том числе на CI).
composer install # локально: с dev-зависимостями (нужны для юнит-тестов) composer install --no-dev # на сервере: без PHPUnit и прочего dev-окружения composer update # обновить зависимости
composer.lock в репозиторий не коммитится (.gitignore): prod-зависимостей у модуля нет, а lock пакета потребители всё равно не читают — при установке решает lock корневого проекта. Локально он создаётся сам и фиксирует версии dev-инструментов только на вашей машине.
Сборка и линтер не настроены (линтер php -l выполняется в CI).
Dev-файлы (tests/, phpunit.xml.dist, CI-конфиг) помечены export-ignore в .gitattributes — в дистрибутив пакета они не попадают. Если модуль разворачивается копированием репозитория, папка tests/ окажется на сервере: она лежит внутри /bitrix/modules/, куда штатный /bitrix/modules/.htaccess (Deny from All) закрывает веб-доступ, а tests/bootstrap.php дополнительно прерывает выполнение вне CLI. На nginx убедитесь, что доступ к /bitrix/modules/ закрыт серверным конфигом (в bitrix-env это так по умолчанию).
Юнит-тесты (только локально, dev)
Юнит-тесты покрывают логику модуля и запускаются без ядра Bitrix — на машине разработчика, без сайта и базы.
composer install # PHPUnit ставится как dev-зависимость composer test # то же самое, что vendor/bin/phpunit
Инфраструктура:
phpunit.xml.dist— конфиг (свой локальный можно положить вphpunit.xml, он в.gitignore);tests/bootstrap.php— заглушки Bitrix, нужные только для загрузки классов (Loader,Loc,CIBlockRights::PUBLIC_READ, константаLANGUAGE_ID). К базе заглушки не обращаются;tests/Unit/— сами тесты; PSR-4 автозагрузкаUplab\Api\→lib/иUplab\Api\Tests\→tests/объявлена вautoload-dev, поэтому на прод (composer install --no-dev) ничего из этого не попадает.
Как писать тесты без ядра. Обращения к API Bitrix вынесены в тонкие методы-обёртки (в IblockProperty это блок «Обращения к API Битрикс»: fetchPropertyValueRows(), fetchSections(), fetchEnumValues() и т.п.). Тест наследует класс, подменяет обёртки готовыми массивами строк выборки и проверяет чистую логику — см. tests/Unit/Iblock/FakeIblockProperty.php. Новую логику размещайте так же: запрос к Bitrix — в отдельной обёртке, преобразование данных — в отдельном методе.
Синхронизация документации (ОБЯЗАТЕЛЬНО)
Документация и тесты существуют в трёх местах и должны поддерживаться в актуальном состоянии при каждом изменении API-кода (эндпоинты, параметры, конфиги, значения по умолчанию, фильтры/права, формат ответа, события):
README.md— этот файл (справочник по эндпоинтам).- Встроенная документация в админке —
lib/Admin/Docs.php(структурированные данные всех эндпоинтов/событий, отображаются в Сервисы → API Документация). - Тестовая коллекция —
tests/uplab.api.postman_collection.json(Postman/Insomnia): запросы и проверки (pm.test) по всем эндпоинтам.
Данные в Docs.php намеренно не парсятся из README — при правке кода обновляйте оба источника согласованно (параметры, значения по умолчанию, примеры, коды ответов, права/фильтры). Коллекцию тестов синхронизируйте с новым поведением (новые запросы, актуальные проверки статуса/формата). PR с изменением поведения API без обновления README, Docs.php и тестов считается неполным.
Версионирование и релизы
Используется семантическое версионирование (MAJOR.MINOR.PATCH).
При каждом изменении версии ОБЯЗАТЕЛЬНО обновлять оба файла:
install/version.php— поднятьVERSIONи проставить актуальнуюVERSION_DATE.CHANGELOG.md— добавить новую секцию## [VERSION] - YYYY-MM-DDс описанием изменений (### Added/### Changed/### Fixed/### Removed) и ссылками на изменённые файлы. Пропускать CHANGELOG нельзя — он часть релиза наравне сversion.php.
Публикация в GitHub
Разработка ведётся во внутреннем репозитории (полная история, MR, CI), в публичный репозиторий GitHub уходит один коммит-снимок на релиз — внутренняя история и ключи задач не публикуются. Публичной историей служит CHANGELOG.md.
Один раз добавьте remote:
git remote add github git@github.com:<org>/uplab.api.git
Публикация после того, как релиз влит в master и версия поднята:
tools/publish-github.sh --dry-run # показать, что уйдёт, без отправки tools/publish-github.sh # собрать снимок и запушить в github/master
Скрипт берёт версию из install/version.php, собирает дерево HEAD без внутренних файлов (.gitlab-ci.yml, сам скрипт), делает коммит uplab.api X.Y.Z поверх предыдущего опубликованного состояния и отправляет его в github/master. Рабочая копия при этом не меняется. Автором публичного коммита указывается организация, а не разработчик, — личные адреса из локальной конфигурации git в снимок не попадают (переопределяется переменными PUBLISH_AUTHOR_NAME и PUBLISH_AUTHOR_EMAIL). Тег и релиз создаются на стороне GitHub (командой gh release create, которую скрипт печатает после пуша) — так имя публичного тега совпадает с внутренним, а локальные теги не конфликтуют.
Теги релизов — только аннотированные (annotated), не lightweight:
git tag -a vX.Y.Z -m "vX.Y.Z — краткое описание релиза"
git push origin vX.Y.Z
Тег ставится на master уже с влитыми изменениями. Lightweight-теги (git tag vX.Y.Z без -a) для релизов использовать нельзя — у них нет тегера, даты и аннотации.
Архитектура
Три слоя:
- Контроллеры (
lib/Controller/) — принимают HTTP-запрос, создают объект конфига из JSON body и делегируют в сервис. - Сервисы (
lib/Model/) — бизнес-логика, запросы к Bitrix API, кеширование. - Конфиги (
lib/Utils/Config/) — Fluent-объекты, описывающие параметры запроса. Создаются черезClassName::create(array|null $body).
Точка входа: include.php → EventManager::bindEvents(). Все маршруты — lib/Routing/Configurator.php.
Все параметры передаются через JSON body (Content-Type: application/json), в том числе для ANY/GET запросов — читаются из тела запроса (HttpRequest::getInput()).
Тело с Content-Type: application/x-www-form-urlencoded или multipart/form-data как JSON не разбирается — такие поля читает сам эндпоинт из запроса (это отправка веб-формы, POST /api/forms/{code}/add). Любое другое тело разбирается как JSON независимо от заголовка: валидный JSON-объект принимается и без Content-Type. Заголовок влияет только на реакцию на ошибку разбора — если клиент объявил JSON (application/json, *+json), некорректное или не-объектное тело даёт 400; если тип не объявлен или не JSON, такое тело игнорируется и параметры берутся по умолчанию.
Ограничения запросов
Некорректные или чрезмерно большие параметры отклоняются до обращения к БД:
| Ограничение | Значение |
|---|---|
| Тело запроса | не более 64 КиБ, глубина JSON не более 32; превышение размера — 413 |
Content-Type |
тело формы (x-www-form-urlencoded, multipart/form-data) как JSON не разбирается; валидный JSON принимается и без заголовка; объявленный JSON с некорректным телом — 400 |
limit / offset |
1…500 / 0…100000 |
filter и другие массивы условий |
не более 1000 элементов суммарно (с учётом вложенных), глубина не более 5 |
select / propertyCode |
не более 100 полей |
order |
не более 10 полей; направления ASC, DESC, ASC,NULLS, DESC,NULLS, NULLS,ASC, NULLS,DESC |
siteId |
1–32 символа: латиница, цифры, _ и -; пустая строка = фильтр по сайту не применяется |
Нарушение лимитов параметров возвращает 400 Bad Request. Эти ограничения защищают БД и кеш от случайных и намеренно дорогих запросов; при проектном расширении API не обходите конфиг-классы.
Переопределение лимитов запросов
Лимиты заданы константами MAX_* в Uplab\Api\Utils\Config\BaseConfig, а проверки обращаются к ним через static::, поэтому проект меняет любой лимит наследованием — тела проверок копировать не нужно. Наследник конфига подключается фабрикой класса конфига в контроллере (get*ConfigClass()), контроллер — своим маршрутом.
class ProjectListConfig extends \Uplab\Api\Utils\Config\Iblock\IblockListElementsConfig { public const MAX_LIMIT = 2000; } class ProjectIblockController extends \Uplab\Api\Controller\IblockController { protected function getElementListConfigClass(): string { return ProjectListConfig::class; } }
Лимит можно как поднять, так и ужесточить; остальные эндпоинты продолжают работать со штатными значениями — переопределение действует только там, где подключён наследник. Помните, зачем лимиты существуют: limit и глубина filter напрямую определяют стоимость запроса к БД, а разнообразие параметров — число вариантов кеша.
| Константа | По умолчанию | На что влияет |
|---|---|---|
MAX_LIMIT |
500 |
limit |
MAX_OFFSET |
100000 |
offset |
MAX_FILTER_ITEMS |
1000 |
число элементов в filter и других массивах условий |
MAX_FILTER_DEPTH |
5 |
глубина вложенности массивов условий |
MAX_SELECT_FIELDS |
100 |
select, propertyCode |
MAX_ORDER_FIELDS |
10 |
order |
MAX_FIELD_NAME_LENGTH |
128 |
длина имени поля в filter, select, order, propertyCode, а также длина кода в пути ({iblockCode} и т.п.) |
MAX_STRING_VALUE_LENGTH |
4096 |
длина строкового значения в массивах условий |
ORDER_DIRECTIONS |
ASC, DESC, ASC,NULLS, DESC,NULLS, NULLS,ASC, NULLS,DESC |
допустимые направления сортировки |
Фабрики класса конфига:
| Фабрика | Контроллер | Эндпоинт |
|---|---|---|
getIblockConfigClass() |
IblockController |
/api/iblock/{code} |
getElementListConfigClass() |
IblockController |
/api/iblock/{code}/elements |
getElementDetailConfigClass() |
IblockController |
/api/iblock/{code}/element/code|id/{...} |
getSectionListConfigClass() |
IblockController |
/api/iblock/{code}/sections |
getSectionDetailConfigClass() |
IblockController |
/api/iblock/{code}/section/code|id/{...} |
getElementFilterConfigClass() |
IblockController |
/api/iblock/{code}/filter |
getIblockPropertiesConfigClass() |
IblockController |
/api/iblock/{code}/properties |
getElementListConfigClass() |
HlblockController |
/api/hlblock/{code}/elements |
getElementFilterConfigClass() |
HlblockController |
/api/hlblock/{code}/filter |
getFieldsConfigClass() |
HlblockController |
/api/hlblock/{code}/fields |
getPageConfigClass() |
PageController |
/api/pages/{code} |
getMenuConfigClass() |
MenuController |
/api/menus/{code} |
Объект конфига создаётся в BaseController::createConfig(), так что переопределять экшены не требуется.
У поиска и административных эндпоинтов контента объекта конфига нет — там лимиты меняются переопределением методов модели или контроллера:
| Метод | Класс | Что задаёт |
|---|---|---|
getMaxLimit() |
Uplab\Api\Model\Search |
limit в /api/search |
getMaxOffset() |
Uplab\Api\Model\Search |
offset в /api/search |
getMaxPageSize() |
Uplab\Api\Controller\ContentController |
pageSize в POST /api/content/admin/{pageCode} |
getMaxOffset() |
Uplab\Api\Controller\ContentController |
максимальное смещение там же |
getMaxBulkIds() |
Uplab\Api\Controller\ContentController |
число ids в POST /api/content/bulk |
Наследник Search подключается через SearchController::getModelClass(), как остальные модели.
Тексты ошибок
Сообщения ошибок приходят клиенту из языковых сообщений модуля (UPLAB_API.ERROR_*, файлы lang/ru/include.php и lang/en/include.php), а не из строк в коде. Язык выбирается штатным механизмом Битрикс, лимиты подставляются в текст через плейсхолдер #MAX#:
{ "type": "about:blank", "title": "Bad Request", "status": 400, "detail": "Параметр limit должен быть от 1 до 500." }
Чтобы изменить формулировку на проекте, переопределите нужное сообщение штатным способом Битрикс — через /local/php_interface/ или языковые файлы проекта; правка модуля не требуется. Если проект поднял лимит наследованием конфига, в текст подставится лимит наследника.
HTTP-статусы задаются кейсами Uplab\Api\Enums\HttpStatus, а не числами: ApiException принимает и кейс, и int (для обратной совместимости), а наружу отдаёт число.
Формат ответа
Сервисные эндпоинты (инфоблоки, highload-блоки, страницы, формы, меню, поиск, карта сайта) возвращают сырые данные эндпоинта в теле ответа — без обёртки:
{ "items": [ /* ... */ ], "meta": { "total": 128, "limit": 10, "offset": 0 } }
Ожидаемые ошибки (не найдено, не установлен зависимый модуль и т.п.) — соответствующий HTTP-статус (404, 424 и т.п.) и тело:
{ "message": "Инфоблок не найден." }
Намеренные клиентские ошибки. Код может бросить Uplab\Api\Exception\ApiException($message, $httpStatus) — ответ будет с указанным 4xx-статусом и телом в формате RFC 9457 (Content-Type: application/problem+json):
{ "type": "about:blank", "title": "Not Found", "status": 404, "detail": "Форма не найдена." }
Внутренние ошибки (необработанные исключения) — HTTP 500 и тело RFC 9457, без раскрытия классов, сигнатур и стек-трейса:
{ "type": "about:blank", "title": "Internal Server Error", "status": 500, "detail": "Внутренняя ошибка сервера.", "code": "UPLAB_API_INTERNAL_ERROR" }
Полные детали (класс, сообщение, файл:строка, трейс, URL) пишутся в Журнал событий (тип UPLAB_API_INTERNAL_ERROR), администратору показывается уведомление со ссылкой на журнал. Перехват централизован в BaseController::getActionResponse() (плюс страховка runProcessingException/runProcessingError для исключений вне тела экшена).
nginx. На bitrix-env по умолчанию
fastcgi_intercept_errors on, поэтому тело любого5xx-ответа nginx подменяет своей HTML-страницей — наHTTP 500problem+jsonдо клиента не дойдёт (код500и запись в лог при этом корректны). Чтобы тело500доходило — отключите перехват для/api/(см. раздел ниже). Ответы4xx(включаяApiException) проходят всегда.
Исключение — административные эндпоинты контента (POST /api/content/admin/{pageCode}, POST /api/content/bulk, DELETE /api/content/admin/{elementId}): они построены на стандартном контроллере Bitrix и используют конверт { "status": "success"|"error", "data": …, "errors": [] }.
Опционально: настоящий HTTP 500 для ошибок (nginx)
По умолчанию bitrix-env включает fastcgi_intercept_errors on; и error_page 500 …, поэтому любой ответ с кодом 5xx от PHP nginx подменяет своей HTML-страницей. Если для /api/ вам нужен именно HTTP 500 с JSON-телом — на стороне nginx отключите перехват для этого префикса (требуется доступ к конфигу сервера):
location /api/ { fastcgi_intercept_errors off; # или proxy_intercept_errors off; — в зависимости от схемы # ... остальная проксирующая/fastcgi-конфигурация как в основном location ... }
Без этой правки HTTP-код 500 и запись в Журнал событий остаются корректными, но тело 500-ответа заменяется HTML-страницей nginx. Ответы 4xx (в т.ч. ApiException) не перехватываются и доходят с JSON всегда.
API Endpoints
ANYозначает, что маршрут принимает любой HTTP-метод (GET, POST и т.д.).
Общий параметр для всех эндпоинтов
| Параметр | Тип | Описание |
|---|---|---|
siteId |
string |
ID сайта Bitrix. По умолчанию: SITE_ID текущего сайта. |
Инфоблоки — /api/iblock
Поведение по умолчанию (списки и детальные)
Базовый фильтр. Ко всем выборкам элементов/разделов инфоблока всегда применяется базовый фильтр: ACTIVE=Y, IBLOCK_ACTIVE=Y, CHECK_PERMISSIONS=Y, MIN_PERMISSION=PUBLIC_READ, привязка к инфоблоку (IBLOCK_ID) и к сайту (IBLOCK_LID=siteId). Переданный параметр filter только добавляет условия к базовому и не может переопределить эти ключи — проверка прав, активность и привязка к инфоблоку/сайту сохраняются всегда (это защита от обхода прав через тело запроса).
Управляющие ключи фильтра игнорируются. В фильтре Битрикс есть ключи, которые не добавляют условие, а меняют способ сборки запроса, поэтому порядок слияния от них не защищает. Такие ключи вырезаются из переданного filter на верхнем уровне: LOGIC, CHECK_PERMISSIONS, MIN_PERMISSION, PERMISSIONS_BY, SHOW_HISTORY, SHOW_NEW, SHOW_BP_NEW, CHECK_BP_PERMISSIONS (сравнение не зависит от префикса-оператора: !CHECK_PERMISSIONS тоже вырезается). Без этого {"LOGIC":"OR"} превращал базовые условия в альтернативы (снимая привязку к инфоблоку, сайту и ACTIVE=Y), PERMISSIONS_BY заставлял ядро проверять права от имени другого пользователя, а SHOW_HISTORY/SHOW_NEW отдавали черновики и записи истории документооборота. Во вложенных группах фильтра LOGIC работает как обычно: такая группа присоединяется к запросу через AND и может только сузить выборку.
{ "filter": { "PROPERTY_BRAND": 42, "0": { "LOGIC": "OR", "NAME": "a", "CODE": "b" } } }
Сортировка по умолчанию. Для списков элементов/разделов, если order не передан, применяется {"SORT":"ASC","NAME":"ASC"}; для списка свойств — {"sort":"asc","name":"asc"}. Переданный order заменяет сортировку целиком.
Пагинация по умолчанию. limit=10, offset=0 (для эндпоинтов со списками элементов/разделов).
Кеш и права. Ключ кеша включает отсортированный набор групп текущего пользователя — так же, как в штатных компонентах Битрикс при CACHE_GROUPS=Y. Ответ, сформированный для администратора или закрытой группы, не будет отдан анонимному пользователю.
Для инфоблоков в расширенном режиме прав (RIGHTS_MODE = E) групп недостаточно: там права выдаются ещё и персонально (код доступа U<ID>), по отделу и на «создателя» (CR), поэтому двум пользователям с одинаковыми группами доступны разные наборы элементов. Для таких инфоблоков в ключ кеша добавляется и ID пользователя. В простом режиме (RIGHTS_MODE = S) видимость — функция набора групп, и ключ остаётся групповым, чтобы кеш не дробился по каждому авторизованному пользователю. Для анонимных запросов оба варианта дают один общий кеш.
Точка расширения: IblockService::getAccessCacheId() и IblockProperty::getAccessCacheId() — переопределите, если наследник добавляет в ответ данные, зависящие от пользователя.
propertyCode необязателен. Если propertyCode не передан (и в инфоблоке включены «Свойства инфоблоков»/property features), набор свойств определяется автоматически:
- для списков элементов — свойства с включённым флагом «Показывать на странице списка элементов» (
getListPageShowPropertyCodes); - для детальных карточек элемента — свойства с включённым флагом «Показывать на детальной странице элемента» (
getDetailPageShowPropertyCodes).
Передайте "propertyCode": ["*"], чтобы получить все свойства, либо явный список кодов — чтобы ограничить выборку.
ANY /api/iblock/{iblockCode}
Информация об инфоблоке.
Контроллер: IblockController::getIblockAction
Модель: IblockService
Конфиг: IblockDetailConfig
| Параметр | Тип | Описание |
|---|---|---|
siteId |
string |
ID сайта |
filter |
array |
Дополнительный фильтр |
select |
string[] |
Список полей для выборки |
ANY /api/iblock/{iblockCode}/elements
Список элементов инфоблока с пагинацией.
Контроллер: IblockController::getElementListAction
Модель: IblockService
Конфиг: IblockListElementsConfig
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
siteId |
string |
SITE_ID |
ID сайта |
filter |
array |
[] |
Фильтр в формате Bitrix ({"ACTIVE": "Y"}) |
select |
string[] |
[] |
Список полей |
order |
array |
{"SORT":"ASC","NAME":"ASC"} |
Сортировка. Если не передать — применяется значение по умолчанию |
limit |
int |
10 |
Кол-во элементов на странице |
offset |
int |
0 |
Смещение |
propertyCode |
string[] |
(см. «Поведение по умолчанию») | Символьные коды свойств. Необязателен: без него — свойства с флагом «Показывать на странице списка элементов» |
SHOW_TOTAL |
'Y'|'N' |
'Y' |
Включить total в ответ |
SHOW_IPROPERTY_VALUES |
'Y'|'N' |
'N' |
Выводить SEO-поля (iPropertyValues) |
HIDE_DISPLAY_VALUES |
'Y'|'N' |
'N' |
Скрыть DISPLAY_VALUES у свойств (уменьшает объём ответа) |
SHOW_PREFIX_FIELD_DATA |
'Y'|'N' |
'N' |
Показывать поля с префиксом ~ |
GET_PROPERTIES_PROPERTY |
object |
{} |
Свойства связанных элементов: {"VENDORS": ["LOGO"]} |
activeDateFormat |
string |
null |
Формат даты ACTIVE_FROM (напр. d.m.Y) |
activeToDateFormat |
string |
null |
Формат даты ACTIVE_TO |
parentSectionId |
int |
null |
Фильтр по ID родительского раздела |
parentSectionCode |
string |
null |
Фильтр по символьному коду родительского раздела |
Случайная сортировка (RAND)
При передаче "order": {"RAND": "ASC"} ответ кешируется на 30 секунд вместо стандартных ~24 часов. Это позволяет возвращать разные случайные наборы элементов, не нагружая базу данных: максимум 1 запрос к БД в 30 секунд на каждую уникальную комбинацию параметров.
{ "order": { "RAND": "ASC" }, "limit": 5 }
ANY /api/iblock/{iblockCode}/element/code/{elementCode}
Детальная карточка элемента инфоблока по символьному коду.
Контроллер: IblockController::getElementDetailByCodeAction
Модель: IblockService
Конфиг: IblockDetailElementConfig
| Параметр | Тип | Описание |
|---|---|---|
siteId |
string |
ID сайта |
filter |
array |
Дополнительный фильтр |
select |
string[] |
Список полей |
propertyCode |
string[] |
Символьные коды свойств. Необязателен: без него — свойства с флагом «Показывать на детальной странице элемента» |
activeDateFormat |
string |
Формат даты ACTIVE_FROM |
activeToDateFormat |
string |
Формат даты ACTIVE_TO |
HIDE_DISPLAY_VALUES |
'Y'|'N' |
Скрыть DISPLAY_VALUES у свойств |
SHOW_PREFIX_FIELD_DATA |
'Y'|'N' |
Показывать поля с ~ |
GET_PROPERTIES_PROPERTY |
object |
Свойства связанных элементов |
parentSectionId |
int |
ID родительского раздела |
parentSectionCode |
string |
Символьный код родительского раздела |
ANY /api/iblock/{iblockCode}/element/id/{elementId}
Детальная карточка элемента инфоблока по ID.
Контроллер: IblockController::getElementDetailByIdAction
Конфиг: IblockDetailElementConfig — параметры идентичны /element/code/{elementCode}.
ANY /api/iblock/{iblockCode}/sections
Список разделов инфоблока с пагинацией.
Контроллер: IblockController::getSectionListAction
Модель: IblockService
Конфиг: IblockListSectionsConfig
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
siteId |
string |
SITE_ID |
ID сайта |
filter |
array |
[] |
Фильтр |
select |
string[] |
[] |
Список полей |
order |
array |
null |
Сортировка |
limit |
int |
10 |
Кол-во на странице |
offset |
int |
0 |
Смещение |
bIncCnt |
bool|array |
null |
Параметр bIncCnt для CIBlockSection::GetList() |
SHOW_TOTAL |
'Y'|'N' |
'Y' |
Включить total в ответ |
parentSectionId |
int |
null |
ID родительского раздела |
parentSectionCode |
string |
null |
Символьный код родительского раздела |
ANY /api/iblock/{iblockCode}/section/code/{sectionCode}
Детальная информация о разделе инфоблока по символьному коду.
Контроллер: IblockController::getSectionDetailByCodeAction
Модель: IblockService
Конфиг: IblockDetailSectionConfig
| Параметр | Тип | Описание |
|---|---|---|
siteId |
string |
ID сайта |
filter |
array |
Дополнительный фильтр |
select |
string[] |
Список полей |
parentSectionId |
int |
ID родительского раздела |
parentSectionCode |
string |
Символьный код родительского раздела |
ANY /api/iblock/{iblockCode}/section/id/{sectionId}
Детальная информация о разделе инфоблока по ID.
Контроллер: IblockController::getSectionDetailByIdAction
Конфиг: IblockDetailSectionConfig — параметры идентичны /section/code/{sectionCode}.
Хлебные крошки (BREADCRUMBS) в детальных ответах
Детальные эндпоинты инфоблока добавляют в ответ готовую цепочку навигации — ключ BREADCRUMBS верхнего уровня. Параметрами не управляется, формируется всегда и кешируется вместе с ответом.
Где есть: /element/code/{elementCode}, /element/id/{elementId}, /section/code/{sectionCode}, /section/id/{sectionId}.
Где нет: в списках (/elements, /sections) и в /api/pages/{code} — PageService цепочку не строит (если в ответе страницы встречается BREADCRUMBS, это обычное свойство инфоблока внутри PROPERTIES).
Формат — массив звеньев в порядке от корня к текущей странице:
{
"BREADCRUMBS": [
{ "TEXT": "Каталог", "HREF": "/catalog/" },
{ "TEXT": "Ноутбуки", "HREF": "/catalog/noutbuki/" },
{ "TEXT": "ThinkPad X1" }
]
}
Детальная элемента (lib/Model/Iblock/IblockService.php:543):
- Инфоблок —
TEXT= название инфоблока,HREF=LIST_PAGE_URLэлемента. Звено добавляется, только если инфоблок определён. - Цепочка разделов от корня до раздела элемента (
CIBlockSection::GetNavChainпоIBLOCK_SECTION_ID) —TEXT= SEO-заголовок разделаIPROPERTY_VALUES.SECTION_PAGE_TITLE, при пустом —NAME;HREF=SECTION_PAGE_URL. Если элемент вне разделов, звеньев нет. - Сам элемент —
TEXT=IPROPERTY_VALUES.ELEMENT_PAGE_TITLE, при пустом —NAME. КлючаHREFу последнего звена нет (не пустая строка, а отсутствующий ключ).
Попутно детальная элемента отдаёт SECTION_URL — URL последнего раздела цепочки (пустая строка, если элемент вне разделов).
Детальная раздела (lib/Model/Iblock/IblockService.php:865):
- Инфоблок —
TEXT= название инфоблока,HREF=LIST_PAGE_URLраздела. - Цепочка навигации, включая сам раздел:
TEXT=IPROPERTY_VALUES.SECTION_PAGE_TITLEилиNAME;HREF=SECTION_PAGE_URL, а у звена самого текущего разделаHREF— пустая строка (ключ присутствует).
ANY /api/iblock/{iblockCode}/filter
Данные для построения фильтра по элементам инфоблока: значения свойств, у которых в настройках инфоблока включено «Показывать в умном фильтре». Выводятся только значения, реально использованные в доступных элементах.
Контроллер: IblockController::getElementFilterAction
Модель: IblockProperty
Конфиг: IblockFilterConfig
| Параметр | Тип | Описание |
|---|---|---|
siteId |
string |
ID сайта |
propertyCode |
string[] |
Символьные коды свойств для фильтра |
filter |
array |
Дополнительный фильтр. Сужает набор значений: значения собираются только по элементам, попадающим под фильтр |
select |
string[] |
Список полей |
parentSectionId |
int |
ID раздела |
parentSectionCode |
string |
Символьный код раздела |
Сужение значений переданным filter
Значения каждого свойства собираются по элементам, отобранным с учётом filter. Условие по самому свойству (PROPERTY_<КОД>) при сборке его значений из фильтра исключается — иначе свойство сужало бы собственный список и в фильтре оставался бы только уже выбранный вариант. Переданный filter входит в ключ кеша.
Исключение работает по точному совпадению ключа: PROPERTY_COLOR из сборки значений свойства COLOR убирается, а ключ с префиксом-оператором (>=PROPERTY_PRICE, !PROPERTY_COLOR) — нет, такое условие сузит список значений своего же свойства. Для фасетов передавайте условия по свойству без операторов.
{ "filter": { "PROPERTY_BRAND": 42 } }
Проверка прав. Значения собираются с CHECK_PERMISSIONS=Y и MIN_PERMISSION=PUBLIC_READ для всех типов свойств — значения из недоступных элементов в фильтр не попадают. Это же относится к вторичным выборкам: названия связанных элементов и разделов (свойства E и G) берутся с проверкой прав на инфоблок-справочник, поэтому из закрытого инфоблока названия не утекают.
Кеш. Ответ кешируется на сутки с тегом инфоблока (iblock_id_{ID}), а для свойств-справочников дополнительно с тегом HL-блока (hlblock_id_{ID}) — правка записей справочника в админке сбрасывает кеш фильтра.
Поддерживаемые типы свойств: L (список), E (привязка к элементу), G (привязка к разделу), S (строка, в том числе directory — справочник HL-блока). Свойство, у которого в выборке нет значений, в ответ не попадает. Числовые свойства (N) и любые пользовательские типы сторонних модулей модуль из коробки не обрабатывает — они добавляются на проекте, см. «Расширение через наследование (IblockProperty)».
Формат items: для списка (L) value — ID значения перечисления (ENUM_ID), text — его название; для привязок (E, G) — ID элемента/раздела и его NAME; для справочника (directory) — UF_XML_ID и название значения справочника.
[
{
"id": "105",
"propertyType": "L",
"code": "COLOR",
"name": "Цвет",
"multiple": "N",
"displayType": "F",
"items": [
{ "id": "12", "value": "12", "text": "Красный" },
{ "id": "13", "value": "13", "text": "Синий" }
]
}
]
ANY /api/iblock/{iblockCode}/properties
Все свойства инфоблока.
Контроллер: IblockController::getIblockPropertiesAction
Конфиг: IblockListConfig
| Параметр | Тип | Описание |
|---|---|---|
siteId |
string |
ID сайта |
filter |
array |
Фильтр |
select |
string[] |
Список полей |
order |
array |
Сортировка |
Расширение через наследование (IblockService)
Кешируемые выборки инфоблоков имеют точки расширения — проект наследует IblockService и переопределяет только нужный хук, не копируя тела методов. Наследник подключается через getModelClass() своего контроллера (аналогично SitemapController, см. раздел «Карта сайта»).
Хуки payload — вызываются внутри кешируемой области, перед сохранением в кеш (поэлементные — в цикле выборки); всё добавленное попадает в кеш (по умолчанию ~24 часа, тег iblock_id_{ID}), поэтому не полагайтесь в них на per-request данные (текущий пользователь, время и т.п.):
Порядок параметров единый: данные → сырые поля → сырые свойства → объект выборки → конфиг (конфиг всегда последним, как в остальных методах сервиса).
| Метод | Область | Эндпоинт |
|---|---|---|
appendElementListData(array $data, $config): array |
весь payload (items + meta) | /api/iblock/{code}/elements |
appendElementListItemData(array $arItem, array $rawFields, array $rawProperties, $obj, $config): array |
один элемент списка | /api/iblock/{code}/elements |
appendElementDetailData(array $data, array $rawFields, array $rawProperties, $obj, $config): array |
детальная карточка | /api/iblock/{code}/element/code|id/{...} |
appendSectionListData(array $data, $config): array |
весь payload (items + meta) | /api/iblock/{code}/sections |
appendSectionListItemData(array $arSection, array $rawFields, $config): array |
один раздел списка | /api/iblock/{code}/sections |
appendSectionDetailData(array $data, array $rawFields, $obj, $config): array |
детальная раздела | /api/iblock/{code}/section/code|id/{...} |
Дополнительные аргументы хуков:
$rawFields— снимок полей сразу после выборки (GetFields()/GetNext()), до преобразований для фронта: с~-полями и системными полями (EXTERNAL_ID,IBLOCK_TYPE_IDи т.п.), которые из итогового$dataвырезаются. Для разделов включает UF-поля.$rawProperties— свойства элемента из$obj->GetProperties()до преобразований (convertPropertiesDataForFront). Заполнены, только если свойства запрошены (propertyCodeпередан или коды определены через property features); иначе пустой массив — при необходимости вызовите$obj->GetProperties()сами.$obj— объект строки выборки (_CIBElement), где есть: доступны его методы без повторного запроса к БД.
Хуки ключа кеша — getElementListCacheKeyParts(), getElementDetailCacheKeyParts(), getSectionListCacheKeyParts(), getSectionDetailCacheKeyParts() (сигнатура (array $parts, $config): array). Если добавленные данные зависят от входных параметров — добавьте эти параметры к $parts, иначе разные входы склеятся в одну кеш-запись. Базовые части (включая static::class — он разводит кеш наследника и базового класса) не удаляйте.
Если данные для хука берутся из другого инфоблока — зарегистрируйте его тег, тег-кеш в момент вызова открыт: Application::getInstance()->getTaggedCache()->registerTag('iblock_id_' . $id).
class ProjectIblockService extends \Uplab\Api\Model\Iblock\IblockService { protected function appendSectionDetailData(array $data, array $rawFields, mixed $obj, mixed $iblockConfig): array { // $rawFields — исходные поля выборки (включая '~'-поля), $obj — объект строки выборки $data['MY_PARAM'] = $this->calcBusinessLogic((int)$data['ID'], $rawFields); // попадёт в кеш return $data; } } class ProjectIblockController extends \Uplab\Api\Controller\IblockController { protected function getModelClass() { return new ProjectIblockService(); } }
Расширение через наследование (IblockProperty)
Значения умного фильтра (/api/iblock/{code}/filter) собираются по карте «тип свойства → метод-резолвер». Проект наследует IblockProperty, дописывает карту и реализует только свой метод — остальные типы продолжают работать без копирования кода.
Подключение наследника — через IblockService::getPropertyModel(), экшен переопределять не нужно. Фабрика без параметров (как getModelClass() в контроллерах): инфоблок, сайт и раздел модели задаёт сам сервис сеттерами, поэтому сигнатура не меняется при появлении новых данных.
class ProjectIblockService extends \Uplab\Api\Model\Iblock\IblockService { /** * @return \Uplab\Api\Model\Iblock\IblockProperty */ protected function getPropertyModel() { return new ProjectIblockProperty(); } } class ProjectIblockController extends \Uplab\Api\Controller\IblockController { protected function getModelClass() { return new ProjectIblockService(); } }
protected function getValueResolvers(): array { return [ 'L' => 'getEnumValues', 'S' => 'getStringValues', 'E' => 'getElementLinkValues', 'G' => 'getSectionLinkValues', ]; }
Список типов, которые вообще попадают в фильтр, выводится из этой же карты — отдельного перечня типов, способного с ней разойтись, больше нет. Модуль поддерживает только штатные типы Bitrix; числовые свойства (N) и типы сторонних модулей — задача проекта (готовый пример ниже).
Свой USER_TYPE. Пользовательский тип свойства в Bitrix живёт поверх базового (S, N, …), поэтому переопределяйте резолвер соответствующего базового типа и опирайтесь на parent:: для штатного поведения:
class ProjectIblockProperty extends \Uplab\Api\Model\Iblock\IblockProperty { protected function getStringValues(array $arProperty, array $customFilter): array { if (($arProperty['USER_TYPE'] ?? '') === 'my_type') { // $customFilter — фильтр запроса без условия по самому свойству return $this->buildMyTypeItems($arProperty, $customFilter); } return parent::getStringValues($arProperty, $customFilter); } }
Свой базовый тип (которого нет в карте) регистрируется добавлением ключа: return parent::getValueResolvers() + ['F' => 'getFileValues'];.
Сигнатура резолвера: (array $arProperty, array $customFilter): array. Возвращает список значений [['id' => …, 'value' => …, 'text' => …], …]; пустой массив означает «свойство в фильтр не попадает». Исключение внутри резолвера не роняет весь фильтр: оно пишется в Журнал событий (тип UPLAB_API_FILTER_ERROR), свойство отдаёт пустой набор.
| Метод | Назначение |
|---|---|
getValueResolvers() |
Карта «PROPERTY_TYPE → метод». Задаёт и список поддерживаемых типов |
getEnumValues() / getStringValues() / getElementLinkValues() / getSectionLinkValues() |
Резолверы штатных типов (L, S, E, G) |
getDirectoryValues() / getPlainStringValues() |
Ветки строкового типа: справочник HL-блока и значения свойства как есть |
buildPropertyFilter() |
Фильтр запроса без условия по самому свойству |
buildElementsFilter() |
Базовый (защитный) фильтр выборки: ACTIVE, IBLOCK_ID, CHECK_PERMISSIONS, MIN_PERMISSION, раздел |
getEnumPropertyItems() / getGroupPropertyItems() |
Значения свойства, реально использованные в доступных элементах |
logPropertyError() |
Запись ошибки резолвера в Журнал событий и уведомление администратору |
Настройка под конкретное свойство конкретного инфоблока
Сортировку, формат значений и фильтр можно менять точечно — без копирования тела резолвера. Все хуки получают $arProperty (там CODE, ID, USER_TYPE, IBLOCK_ID), а инфоблок доступен через $this->getIblockId().
| Хук | Что задаёт | По умолчанию |
|---|---|---|
getEnumOrder($arProperty) |
Сортировка значений перечисления (L) |
{"SORT":"ASC","VALUE":"ASC"} |
getSectionsOrder($arProperty) |
Сортировка разделов (G) |
{"SORT":"ASC","NAME":"ASC"} |
getElementsOrder($arProperty) |
Сортировка элементов (E) |
{"SORT":"ASC","NAME":"ASC"} |
getDirectoryOrder($arProperty) |
Сортировка записей справочника | [] (без ORDER BY) |
getSectionsSelect($arProperty) |
Поля выборки разделов (G) |
['ID','NAME','IBLOCK_ID','CODE'] |
getElementsSelect($arProperty) |
Поля выборки элементов (E), можно добавить PROPERTY_<КОД> |
['ID','NAME'] |
getLinkedSectionsFilter($iblockId, $ids, $arProperty) |
Фильтр выборки связанных разделов (G) |
ID, ACTIVE=Y, IBLOCK_ID, CHECK_PERMISSIONS=Y, MIN_PERMISSION=PUBLIC_READ |
getLinkedElementsFilter($iblockId, $ids, $arProperty) |
Фильтр выборки связанных элементов (E) |
то же |
buildEnumItems($rows, $arProperty) |
Формат значений перечисления | id/value = ENUM_ID, text = VALUE |
buildSectionItems($rows, $arProperty) |
Формат значений-разделов (G и проектные типы с разделами) |
id/value = ID, text = NAME |
buildElementItems($rows, $arProperty) |
Формат значений-элементов (E) |
id/value = ID, text = NAME |
buildDirectoryItems($rows, $nameFields, $arProperty) |
Формат значений справочника | value = UF_XML_ID, text = локализованное имя |
buildPlainStringItems($arProperty, $rows) |
Формат значений строкового свойства | value = text = значение свойства |
class ProjectIblockProperty extends \Uplab\Api\Model\Iblock\IblockProperty { // Сортировка — только для COLOR в инфоблоке 17 protected function getEnumOrder(array $arProperty): array { if ($this->getIblockId() === 17 && $arProperty['CODE'] === 'COLOR') { return ['VALUE' => 'DESC']; } return parent::getEnumOrder($arProperty); } // Формат — добавляем своё поле, выборку не копируем protected function buildEnumItems(array $rows, array $arProperty): array { $items = parent::buildEnumItems($rows, $arProperty); if ($arProperty['CODE'] === 'COLOR') { foreach ($items as &$item) { $item['hex'] = $this->resolveHex($item['value']); } } return $items; } }
Фильтр. buildElementsFilter() вызывается из общих методов подсчёта значений и мета-данных свойства параметром не получает, поэтому текущее свойство доступно через getCurrentProperty() — он заполнен, пока работает резолвер, и сбрасывается после (в том числе при исключении):
protected function buildElementsFilter(array $additionalFilter = []): array { $filter = parent::buildElementsFilter($additionalFilter); if (($this->getCurrentProperty()['CODE'] ?? '') === 'COLOR') { $filter['!PROPERTY_HIDE_VALUE'] = false; } return $filter; }
Защитные ключи (CHECK_PERMISSIONS, MIN_PERMISSION, ACTIVE, IBLOCK_ID) базовая реализация проставляет последними — переопределение их не отменяет, если вы вызываете parent:: и добавляете условия к результату.
Обращения к API Bitrix вынесены в отдельные обёртки (fetchPropertyValueRows(), fetchPropertyValueRowsWithRaw(), fetchSections(), fetchElements(), fetchEnumValues(), fetchHlblockFields(), fetchDirectoryItems()). Обёртки получают готовые фильтр, сортировку и поля выборки — сами ничего не решают; переопределять их в проекте обычно не нужно, они существуют, чтобы логику можно было покрыть юнит-тестами без ядра Bitrix (см. «Юнит-тесты»).
Если значения берутся из другого инфоблока — зарегистрируйте его тег кеша: $this->addIblockTags($iblockId). Для сущностей, у которых тег не сводится к iblock_id_{ID} (например записи HL-блока), есть $this->addCacheTag($tag) — так модуль регистрирует hlblock_id_{ID} для свойств-справочников.
Пример: свои типы свойств (модуль asd.iblock)
Типы SASDCheckboxNum и SASDSection принадлежат сторонему модулю asd.iblock, поэтому uplab.api их не обрабатывает — проект добавляет их сам. Пример полный, ничего кроме него писать не нужно:
namespace Project\Api; use Uplab\Api\Model\Iblock\IblockProperty; class ProjectIblockProperty extends IblockProperty { protected function getValueResolvers(): array { // Пользовательский тип живёт поверх базового, поэтому регистрируем базовый тип «число» return parent::getValueResolvers() + ['N' => 'getAsdNumberValues']; } protected function getAsdNumberValues(array $arProperty, array $customFilter): array { return match ($arProperty['USER_TYPE'] ?? '') { 'SASDCheckboxNum' => $this->buildAsdCheckboxItems($arProperty), 'SASDSection' => $this->getAsdSectionValues($arProperty, $customFilter), default => [], }; } /** Значения берутся из настроек свойства — запрос к БД не нужен. */ protected function buildAsdCheckboxItems(array $arProperty): array { $items = []; foreach ((array)($arProperty['USER_TYPE_SETTINGS']['VIEW'] ?? []) as $key => $label) { $items[] = [ 'id' => $arProperty['CODE'] . $key, 'value' => $key, 'text' => $label, ]; } return $items; } protected function getAsdSectionValues(array $arProperty, array $customFilter): array { // Штатный метод модуля: отбирает реально использованные значения свойства // с базовым (защитным) фильтром, проверкой прав и переданным filter $groupItems = $this->getGroupPropertyItems($arProperty['CODE'], $customFilter); if (!$groupItems) { return []; } // Штатный формат значений-разделов — id/value/text return $this->buildSectionItems($this->fetchAsdSections(array_keys($groupItems)), $arProperty); } protected function fetchAsdSections(array $ids): array { $res = \Bitrix\Iblock\SectionTable::getList([ 'filter' => [ 'GLOBAL_ACTIVE' => 'Y', 'IBLOCK_ID' => $this->getIblockId(), 'ID' => $ids, ], ]); $rows = []; while ($row = $res->fetch()) { $rows[] = $row; } return $rows; } }
Подключение — через getPropertyModel() (см. выше). Штатные типы (L, S, E, G) продолжают работать: их код не копируется.
Что здесь переиспользуется из модуля и не требует своей реализации: getGroupPropertyItems() (отбор использованных значений с базовым фильтром, проверкой прав и учётом переданного filter), buildSectionItems() (формат значений), getIblockId(), а также перехват ошибок — исключение внутри резолвера попадёт в Журнал событий, остальные свойства фильтра не пострадают.
Этот же класс продублирован в юнит-тесте tests/Unit/Iblock/ProjectCustomTypeExampleTest.php — если пример разойдётся с кодом модуля, тесты упадут.
Пример: строковое свойство со своим USER_TYPE
Свойство типа S с пользовательским типом (в примере — проектный выбор цвета) сейчас попадает в штатную ветку простых строк и отдаёт «сырые» значения. Чтобы подменить только его, переопределите резолвер строкового типа и оставьте остальные строковые свойства базовой реализации:
protected function getStringValues(array $arProperty, array $customFilter): array { if (($arProperty['USER_TYPE'] ?? '') !== 'ProjectColorPicker') { return parent::getStringValues($arProperty, $customFilter); } $labels = $this->loadColorLabels(); // ваш справочник: '#FF0000' => 'Красный' $items = []; // getGroupPropertyItems() вернёт «значение свойства => количество элементов», // уже с проверкой прав, базовым фильтром и учётом переданного filter foreach (array_keys($this->getGroupPropertyItems($arProperty['CODE'], $customFilter)) as $value) { $items[] = [ 'id' => $arProperty['ID'], 'value' => $value, // значение остаётся ключом фильтрации 'text' => $labels[$value] ?? $value, // неизвестное значение отдаём как есть ]; } return $items; }
Ключевое: value должен оставаться тем, что реально хранится в свойстве, иначе фронт не сможет фильтровать по нему через filter: {"PROPERTY_COLOR_HEX": "#FF0000"}. Меняйте text, а не value.
Если для своего типа нужен справочник в HL-блоке, посмотрите на getDirectoryValues() — там та же схема: значения свойства → выборка записей справочника → buildDirectoryItems().
Пример: привязка к элементу (E) со своим названием и сортировкой
Для E в значения по умолчанию попадает NAME связанного элемента. Чтобы вывести другое поле (например короткое название из свойства) и отсортировать по нему, нужны три хука — выборка и отбор значений остаются штатными:
// 1. Добавляем нужные поля в выборку связанных элементов protected function getElementsSelect(array $arProperty): array { if ($arProperty['CODE'] === 'VENDOR') { return ['ID', 'NAME', 'PROPERTY_SHORT_TITLE']; } return parent::getElementsSelect($arProperty); } // 2. Своя сортировка protected function getElementsOrder(array $arProperty): array { if ($arProperty['CODE'] === 'VENDOR') { return ['PROPERTY_SHORT_TITLE' => 'ASC']; } return parent::getElementsOrder($arProperty); } // 3. Свой «text» с фолбэком на имя элемента protected function buildElementItems(array $rows, array $arProperty): array { if ($arProperty['CODE'] !== 'VENDOR') { return parent::buildElementItems($rows, $arProperty); } $items = []; foreach ($rows as $row) { $items[] = [ 'id' => $row['ID'], 'value' => $row['ID'], 'text' => (string)($row['PROPERTY_SHORT_TITLE_VALUE'] ?? '') !== '' ? $row['PROPERTY_SHORT_TITLE_VALUE'] : $row['NAME'], ]; } return $items; }
Условие можно сузить до конкретного инфоблока — $this->getIblockId() === 17 && $arProperty['CODE'] === 'VENDOR'.
Что делает модуль сам: отбирает ID связанных элементов, реально использованных в доступных элементах текущего инфоблока (с базовым фильтром, правами и переданным filter), берёт LINK_IBLOCK_ID из настроек свойства, выбирает только активные элементы связанного инфоблока и регистрирует его тег кеша (iblock_id_{LINK_IBLOCK_ID}) — сброс кеша при правке справочника-инфоблока работает без вашего участия.
Разделы (G) настраиваются симметрично: getSectionsSelect(), getSectionsOrder(), buildSectionItems().
Оба примера тоже покрыты тестами в tests/Unit/Iblock/ProjectCustomTypeExampleTest.php.
Highload-блоки — /api/hlblock
Доступ к данным HL-блоков
HL-блок отдаётся публично, пока на нём никому не выдано право чтения: вкладка «Права доступа» в форме HL-блока по умолчанию пуста, и это означает «ограничение чтения не настроено», а не «доступ запрещён».
Чтобы закрыть блок — выдайте на нём штатную операцию hl_element_read нужным группам (Настройки → Highload-блоки → блок → Права доступа). С этого момента все три endpoint-а (/elements, /filter, /fields) читают только обладатели права и администратор, остальные получают 403 Forbidden. Права на запись публичность не отбирают — ограничение включает именно выдача права чтения.
Как снова открыть блок, если право чтения уже выдано. Два способа:
- выдать чтение группе «Все пользователи» (код доступа
G2) — тогда блок доступен и анонимным запросам, а остальные выданные права продолжают работать. Это то, что нужно, если права на блоке заведены осознанно; - либо снять с блока все записи с правом чтения — блок вернётся в состояние «ограничение не настроено» и снова станет публичным.
Порядок действий: Настройки → Highload-блоки → нужный блок → вкладка «Права доступа» → добавить строку, выбрать «Все пользователи» и уровень доступа, включающий операцию чтения элементов (hl_element_read; операция входит в штатную задачу модуля highloadblock с буквой R), сохранить. Права на изменение и удаление (hl_element_write, hl_element_delete, задача с буквой W) на публичность не влияют — они операции чтения не содержат, поэтому блок с одними лишь правами на запись остаётся открытым.
Проверка учитывает штатные коды доступа: группы (G<ID>, у анонимного пользователя это G2 — «Все пользователи, в том числе неавторизованные»), AU и U<ID>. Для авторизованных дополнительно работает штатный HighloadBlockRightsTable::getOperationsName() — он раскрывает коды отделов и соцгрупп. Кеш ответов разделяется по группам текущего пользователя.
ANY /api/hlblock/{hlblockCode}/elements
Список элементов highload-блока.
Контроллер: HlblockController::getElementListAction
Модель: HlblockService
Конфиг: HlblockListConfig
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
siteId |
string |
SITE_ID |
ID сайта |
filter |
array |
[] |
Фильтр (только переданный; собственного базового фильтра нет) |
select |
string[] |
['*'] |
Список полей |
order |
array |
{"ID":"ASC"} |
Сортировка. Если не передать — применяется значение по умолчанию |
limit |
int |
10 |
Кол-во элементов на странице, максимум 500 |
offset |
int |
0 |
Смещение, максимум 100000 |
SHOW_TOTAL |
'Y'|'N' |
'Y' |
Включить total в ответ (в meta.total) |
Ответ содержит
itemsиmeta(totalприSHOW_TOTAL=Y, а такжеlimitиoffset); тело сырое, без обёртки.
ANY /api/hlblock/{hlblockCode}/filter
Данные для фильтра элементов highload-блока.
Контроллер: HlblockController::getElementFilterAction
Конфиг: HlblockListConfig — параметры идентичны /elements.
ANY /api/hlblock/{hlblockCode}/fields
Список пользовательских полей highload-блока.
Контроллер: HlblockController::getFieldsAction
Конфиг: BaseConfig
| Параметр | Тип | Описание |
|---|---|---|
siteId |
string |
ID сайта |
Статические страницы — /api/pages
ANY /api/pages/{code}
Детальная страница из инфоблока Pages (статические страницы сайта).
Контроллер: PageController::getPageAction
Модель: PageService
Конфиг: IblockDetailElementConfig — параметры идентичны /api/iblock/{iblockCode}/element/code/{elementCode}.
Поле CONTENT в ответе
К ответу дополнительно подмешивается ключ CONTENT — статический контент этой страницы из HLBlock Content (PageService::getItemData(), lib/Model/Iblock/PageService.php:177). Это те же записи, что отдаёт /api/content/page/{pageCode}: оба эндпоинта вызывают ContentTable::getPageData(), поэтому отдельный запрос за контентом страницы фронтенду не нужен.
- Отбираются записи с
UF_PAGE=CODEнайденного элемента,UF_ACTIVE = trueиUF_LANG = siteId(условие по языку добавляется, только еслиsiteIdпередан). - Структура —
CONTENT[UF_BLOCK][UF_CODE] = UF_VALUE, значение#YEAR#заменяется на текущий год. - Если записей контента для страницы нет,
CONTENT— пустой объект. - Кеш у контента свой: 24 часа, каталог
/content/{pageCode}[/{siteId}], тегupdate_contenttable_{UF_PAGE}— правка контента в админке сбрасывает его независимо от кеша элемента инфоблока.
{
"NAME": "Вакансии",
"CODE": "vacancies",
"PROPERTIES": {
"SEO_H1": { "VALUE": "Вакансии" }
},
"CONTENT": {
"filter": { "reset": "Сбросить фильтр" },
"list": {
"empty.title": "Ничего не найдено",
"empty.text": "Попробуйте изменить параметры поиска"
}
}
}
Веб-формы — /api/forms
GET /api/forms/{code}/fields
Список полей веб-формы.
Контроллер: WebFormController::getWebFormFieldsAction
Конфиг: BaseConfig
| Параметр | Тип | Описание |
|---|---|---|
siteId |
string |
ID сайта |
POST /api/forms/{code}/add
Отправка результата веб-формы.
Контроллер: WebFormController::addNewResultAction
Конфиг: BaseConfig + $_REQUEST
Поля формы передаются в теле запроса и соответствуют полям Bitrix-формы. Перед сохранением вызывается событие uplab.api::OnBeforeCheckForm.
ANY /api/forms/{code}
Структура веб-формы: все вопросы и варианты ответов.
Контроллер: WebFormController::getWebFormAction
Конфиг: BaseConfig
| Параметр | Тип | Описание |
|---|---|---|
siteId |
string |
ID сайта |
Статический контент — /api/content
ANY /api/content/page/{pageCode}
Статический контент страницы из HLBlock Content (таблица b_hlbd_content).
Возвращает объект вида { "block": { "code": "value" } }. Значение #YEAR# автоматически заменяется на текущий год.
Для страниц инфоблока
Pagesотдельный запрос не нужен: те же данные приходят в ключеCONTENTответа/api/pages/{code}.
Контроллер: ContentPageController::getAction
Конфиг: BaseConfig
| Параметр | Тип | Описание |
|---|---|---|
siteId |
string |
ID сайта. Влияет на ключ кеша. |
POST /api/content/admin/{pageCode}
Список записей контента для административного интерфейса. Требует авторизации и прав hl_element_read на HLBlock.
Контроллер: ContentController::getListAction
| Query-параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
page |
int |
1 |
Номер страницы |
pageSize |
int |
20 |
Размер страницы |
DELETE /api/content/admin/{elementId}
Удалить запись контента. Требует авторизации, прав hl_element_delete на HLBlock и валидного CSRF-токена (sessid).
Контроллер: ContentController::deleteElementAction
CSRF. Мутирующее действие: требуется параметр
sessid(например?sessid=<bitrix_sessid>). Без валидного токена —Доступ запрещён.
POST /api/content/bulk
Групповые действия над записями контента. Требует авторизации, соответствующих прав на HLBlock и валидного CSRF-токена (sessid).
Контроллер: ContentController::bulkAction
| Параметр | Тип | Описание |
|---|---|---|
action |
'activate'|'deactivate'|'delete' |
Действие |
ids[] |
int[] |
Массив ID элементов |
sessid |
string |
CSRF-токен Bitrix (bitrix_sessid). Обязателен. |
activate/deactivate— требуют правhl_element_writedelete— требует правhl_element_delete
CSRF. Мутирующее действие: без валидного
sessidзапрос отклоняется (Доступ запрещён).
Меню — /api/menus
ANY /api/menus/{menuCode}
Иерархическое меню из инфоблока в формате для фронтенда.
Контроллер: MenuController::getAction
Модель: MenuService
Конфиг: MenuConfig
| Параметр | Тип | Описание |
|---|---|---|
siteId |
string |
ID сайта |
iblockType |
string |
Тип инфоблока Bitrix |
filter |
array |
Дополнительный фильтр |
select |
string[] |
Список полей |
order |
array |
Сортировка |
Поиск — /api/search
ANY /api/search
Полнотекстовый поиск через Bitrix CSearch. Поддерживает морфологию и повторный поиск без стемминга при пустом результате.
Контроллер: SearchController::getAction
Модель: Search
Параметры передаются напрямую как JSON body (без Config-объекта):
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
search |
string |
'' |
Поисковый запрос |
siteId |
string |
— | ID сайта для фильтрации результатов |
sort |
'rank'|'date' |
'rank' |
Тип сортировки |
type |
'full'|'short' |
'full' |
full — страница поиска, short — шапка сайта (приоритет TITLE_RANK) |
limit |
int |
10 |
Кол-во результатов, 1…500 |
offset |
int |
0 |
Смещение, 0…100000 |
Текст при пустом результате (message)
Если ничего не найдено, ответ содержит message.title и message.text. Источник текста (по приоритету):
- Статический контент — HLBlock
Content: запись сUF_PAGE = 'search',UF_BLOCK = 'empty',UF_CODE = 'title'иUF_CODE = 'text'(редактируется в админке, раздел контента; см./api/content/page/search). - Языковые сообщения модуля (фолбэк, если контент пуст) —
UPLAB_API.SEARCH_EMPTY_TITLEиUPLAB_API.SEARCH_EMPTY_TEXTв lang/ru/include.php / lang/en/include.php.
Чтобы задать свой текст: либо добавьте записи в контент (search/empty/title|text), либо переопределите указанные lang-сообщения.
Карта сайта — /api/sitemap
ANY /api/sitemap
Данные для генерации sitemap. Кешируется на 24 часа в разрезе сайта.
Контроллер: SitemapController::getAction
Модель: Sitemap (реализует SitemapInterface)
| Параметр | Тип | Описание |
|---|---|---|
siteId |
string |
ID сайта. Влияет на ключ кеша и фильтрацию (IBLOCK_LID). |
Расширение через наследование
Модель разбита на точечно переопределяемые хуки. Достаточно создать наследника Sitemap и зарегистрировать его через SitemapController::getModelClass().
class ProjectSitemapController extends SitemapController { protected function getModelClass() { return new ProjectSitemap(); } }
Свойства наследника:
| Свойство | Тип | Описание |
|---|---|---|
$domain |
string |
Домен перед каждым URL (по умолчанию пустая строка — относительные URL). |
$cacheTime |
int |
Время жизни кеша в секундах (по умолчанию 86400). |
$pagesIblockElementsCodes |
string[] |
Коды инфоблоков, детальные страницы элементов которых попадают в sitemap. |
$pagesIblockSectionsCodes |
string[] |
Коды инфоблоков, страницы разделов которых попадают в sitemap. |
Хуки для точечного переопределения:
| Метод | Описание |
|---|---|
getStaticPagesIblockCode() |
Код инфоблока статических страниц (по умолчанию 'pages'). |
getStaticPagesOrder() |
Сортировка статических страниц. |
getStaticPagesFilter(int $iblockId) |
Фильтр статических страниц. |
getStaticPagesSelect() |
Поля выборки статических страниц. |
buildStaticPageItem(array $el) |
Трансформация элемента → {loc, lastmod}. null = пропустить. |
getElementPagesOrder() |
Сортировка элементов инфоблоков. |
getElementPagesFilter() |
Фильтр элементов инфоблоков. |
buildElementPageItem(array $el) |
Трансформация элемента → {loc, lastmod}. null = пропустить. |
getSectionPagesOrder() |
Сортировка разделов инфоблоков. |
getSectionPagesFilter() |
Фильтр разделов инфоблоков. |
buildSectionPageItem(array $sect) |
Трансформация раздела → {loc, lastmod}. null = пропустить. |
getFormatDate(int $date) |
Формат даты (по умолчанию ISO 8601). |
getPages() |
Полный сбор страниц из всех источников. |
Система событий
Все события регистрируются на модуль uplab.api.
| Событие | Параметры (ExecuteModuleEventEx) |
Описание |
|---|---|---|
OnBeforeCheckForm |
BeforeCheckForm $event |
Перед сохранением результата веб-формы (POST /api/forms/{code}/add). Возврат EventResult::ERROR отклоняет форму. |
OnBeforeHlblockConvertFieldValue |
array $fields, array &$dataItem |
Для каждого элемента HL-блока перед конвертацией значений полей (GET /api/hlblock/{code}/elements). |
OnAfterHlblockConvertFieldValue |
array $fields, array &$dataItem |
Для каждого элемента HL-блока после конвертации значений полей. |
OnIblockConvertSectionDisplayValue |
array $sectionUfField, array &$arDisplayValue |
Для каждого UF-поля раздела инфоблока после сборки его отображаемого значения (эндпоинты разделов ИБ). |
OnIblockConvertPropertyDisplayValue |
array $data, array &$arDisplayValue |
Для каждого свойства элемента инфоблока после сборки его отображаемого значения (эндпоинты элементов ИБ). |
$eventManager->addEventHandler('uplab.api', 'OnBeforeCheckForm', [static::class, 'OnBeforeCheckForm']); public static function OnBeforeCheckForm(\Uplab\Api\EventResult\BeforeCheckForm $event): \Bitrix\Main\EventResult { // $event->request — данные из $_REQUEST if (empty($event->request['token'])) { return new \Bitrix\Main\EventResult( \Bitrix\Main\EventResult::ERROR, parameters: ['message' => 'Token required'], moduleId: 'uplab.api' ); } return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS); }
Конвертация значений полей HL-блока
При выдаче элементов HL-блока (GET /api/hlblock/{code}/elements) сервис преобразует значения пользовательских полей для фронтенда: поля-привязки (iblock_element, hlblock) и поля типа file дополняются читаемыми данными в подмассиве INFO (исходное значение поля сохраняется). Для file в INFO кладётся полный массив файла из CFile::GetFileArray() (SRC, FILE_NAME, FILE_SIZE, CONTENT_TYPE, размеры и т.д.): один массив для одиночного поля, массив таких массивов — для множественного.
События OnBeforeHlblockConvertFieldValue / OnAfterHlblockConvertFieldValue позволяют вмешаться в этот процесс. Оба вызываются для каждого элемента выборки и получают одинаковый набор параметров:
$fields— массив мета-данных всех пользовательских полей HL-блока;&$dataItem— данные одного элемента, передаются по ссылке — обработчик модифицирует их напрямую.
Результат конвертации кешируется вместе с ответом (по умолчанию 24 часа, тег HL-блока). Изменения, внесённые обработчиком, тоже попадают в кеш — не полагайтесь в них на per-request данные.
$eventManager->addEventHandler('uplab.api', 'OnAfterHlblockConvertFieldValue', [static::class, 'OnAfterHlblockConvertFieldValue']); public static function OnAfterHlblockConvertFieldValue(array $fields, array &$dataItem): void { // Например, добавить вычисляемое поле к каждому элементу $dataItem['INFO']['UF_FULL_URL'] = '/catalog/' . ($dataItem['UF_CODE'] ?? ''); }
Конвертация отображаемых значений инфоблока
При выдаче элементов и разделов инфоблока сервис собирает «человекочитаемое» представление каждого свойства/поля в DISPLAY_VALUE. Два события позволяют вмешаться в этот процесс — оба вызываются после штатной конвертации, поэлементно (на каждое свойство/поле), и принимают отображаемое значение по ссылке:
OnIblockConvertPropertyDisplayValue— для свойств элемента ИБ. Параметры:array $data(мета-данные свойства:PROPERTY_TYPE,USER_TYPE,MULTIPLE,VALUEи т.д.) иarray &$arDisplayValue(собранное отображаемое значение).OnIblockConvertSectionDisplayValue— для UF-полей раздела ИБ. Параметры:array $sectionUfField(мета-данные и значение поля) иarray &$arDisplayValue.
$eventManager->addEventHandler('uplab.api', 'OnIblockConvertPropertyDisplayValue', [static::class, 'OnIblockConvertPropertyDisplayValue']); public static function OnIblockConvertPropertyDisplayValue(array $data, array &$arDisplayValue): void { // Например, для своего пользовательского типа свойства сформировать своё отображение if (($data['USER_TYPE'] ?? '') === 'my_custom_type') { $arDisplayValue = array_map('mb_strtoupper', $arDisplayValue); } }