Впроцессе кастомизации коробочной CRM Битрикс24 часто клиенты просят внедрить им права доступа. Захотелось внедрить с интерфейсом, как это выглядит в Задачах или Сделках. Изучил документацию — там всё изложено лишь в общем виде, пришлось анализировать исходники, сжечь несколько миллионов токенов различных нейронок, и даже после этого вникать в нюансы и дебажить код.
Демо‑пример
Мне всегда легче понять, как что‑то работает, когда, кроме сухой справки, есть живой пример, который можно пощупать. Поэтому был написан демо‑модуль, который сам сгенерирует некоторый список «План продаж» со статусами и разными ответственными. Да, пример несколько надуманный, однако позволяющий наглядно показать различные возможности UI компонента.
Не хочется читать статью дальше, а скорее запустить у себя, пожалуйста: ссылка на GitHub
Как велась работа
В документации есть пример компонента BX.UI.AccessRights, попробовали его — вывелось окно настройки прав, как в Задачах. Однако оно уже устарело и в сделках мы видим более новую версию данного компонента, документации по которому нет.
Встаем перед развилкой — искать и изучать код исходников Bitrix или написать вопрос в тех. поддержку Bitrix:
1) Вопрос в тех. поддержку:
-
Раньше — было несколько проще, пишешь обращение — ждешь 1–3 дня, тебе дает ответ сотрудник тех. поддержки с примером кода, или эскалирует твой вопрос на следующую линию поддержки — ждешь еще 1–3 дня ответа от L2 уровня тех. поддержки. Далее может начаться длительная переписка, однако в принципе запустив процесс параллельно, за месяц ответа можно добиться.
-
Сегодня — всё стало немного сложнее и дольше, обычная техподдержка L1 уровня ушла на L2, и на L1 уровне нужно пробиться через AI Виртуального помощника. Благо ждать его ответа приходится не 1–3 дня, однако и отвечает он тоже не моментально, раз в полчаса обновляешь страницу и проверяешь. Отвечает часто не впопад, и добиться полезного ответа можно только на самые простые вопросы, пока не взвоешь жалобно и 2–3 раза не попросишь передать вопрос живому человеку.
Итог: Всё равно лотерея — либо рабочий пример кода, либо предложение изучить всё самостоятельно, либо не сильно корректный ответ.
2) Другой вариант — искать и изучать исходный код. Рабочий вариант, однако сильно трудозатратный, не каждый клиентский бюджет потянет.
Любопытство победило, пришлось на энтузиазме дебажить по вечерам. Куда же без нейросетей, вначале мелькнула мысль, — попробуем напрячь топовые нейронки Qwen, DeepSeek и с платной подпиской Gemini, Claude, ChatGPT, в итоге исследование даже с ними всё равно затянулось на полтора месяца. Возможно, код компонентов Bitrix оказался достаточно большим, постоянно сталкивался с проблемой Lost in the Middle, когда случались затыки: после 4–6 неудачных итераций приходилось вздыхать и лезть разбираться самостоятельно. Жутко раздражало, когда решение было банально, и нужно было просто корректно передать имя переменной, а скормленные раз за разом исходники никак не помогали ИИ найти ответ самостоятельно (ни в режиме чата, ни в режиме агента с доступом к файлам).
Ниже сделан акцент на ряде решенных блокеров, где спасовали ИИ.
1. Внедряем BX.UI.AccessRights.V2
1.1. Как запустить
Для первой версии BX.UI.AccessRights из документации:
<?php\Bitrix\Main\Loader::includeModule('ui');\Bitrix\Main\UI\Extension::load(['ui.buttons', 'ui.icons', 'ui.notification', 'ui.accessrights']);?><script>let AccessRights = new BX.UI.AccessRights({...});AccessRights.draw();</script>
Первый блокер оказался сразу на старте: Почему данное окно выглядит не как в сделках CRM, как сделать так же? В документации ответа нет — нейросети, видимо, тоже не знают. Пойдем искать исходники, запускаем агента. Агент исследует код — Вы превысили лимит по тарифу, ждите N часов, или перейдите на более дорогой… пару раз так, идем искать исходный код самостоятельно.
Исходники компонента тут /bitrix/js/ui/accessrights. О! Внутри папка v2 — вероятно это вторая версия, а не настройка текущего компонента.
Сейчас‑то наконец как побежим! Вот тебе, ИИ, исходный код, вот нужная папка — исследуй.
ИИ: — Конечно, нужно вот так:
<?php\Bitrix\Main\Loader::includeModule('ui');\Bitrix\Main\UI\Extension::load(['ui.buttons', 'ui.icons', 'ui.notification', 'ui.accessrights.v2']);?><script>let AccessRights = new BX.UI.AccessRights.V2({...});AccessRights.draw();</script>
Пробуем, не работает, еще и еще. Идем смотреть исходники самостоятельно, итог:
let AccessRights = new BX.UI.AccessRights.V2.App({...});
Почему теперь App еще — так решили разработчики Bitrix.
1.2. Схема внедрения в собственный компонент
Для сделок CRM права доступа у Bitrix подключены через компонент crm.config.perms.v2. В красивом виде он загружается через BX.SidePanel.Instance.open. Можно написать собственный отдельный компонент для вызова настройки прав доступа, вначале мне это показалось излишним, создал в корне компонента sales.plan.matrix с основной логикой отдельный файл slider_access.php — тем самым изолировал логику вызова, и подключил этот файл в шаблоне компонента. Органично поместить кнопку в интерфейс Битрикс24 можно через Toolbar:
use Bitrix\Main\UI\Extension;use Bitrix\UI\Toolbar\Facade\Toolbar;use Bitrix\UI\Buttons\Button;use Bitrix\UI\Buttons\Color;use Bitrix\UI\Toolbar\ButtonLocation;Extension::load(['ui.bootstrap4', 'ui.toolbar', 'ui.buttons', 'ui.entity-selector']);...$sliderPath = $component->getPath() . '/slider_access.php?IBLOCK_ID=' . (int)$arResult['IBLOCK_ID'];$settingsBtn = new \Bitrix\UI\Buttons\Button(['color' => \Bitrix\UI\Buttons\Color::LIGHT_BORDER,'icon' => \Bitrix\UI\Buttons\Icon::SETTING,'text' => 'Права доступа','click' => new \Bitrix\UI\Buttons\JsCode("BX.SidePanel.Instance.open('" . CUtil::JSEscape($sliderPath) . "', {width: 1000,cacheable: false,allowChangeHistory: false,label: {text: 'Настройка прав доступа',bgColor: '#647285',}})"),]);Toolbar::addButton($settingsBtn);
Сокращенный вызов BX.UI.AccessRights.V2 тогда будет выглядеть так (полностью файл slider_access.php представлен в репозитории):
<?phprequire $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';use Bitrix\Main\UI\Extension;use Bitrix\Main\Web\Json;use Iliasa\SalesPlan\Access\SalesPlanAccessController;global $USER, $APPLICATION;$controller = new SalesPlanAccessController((int)$USER->GetID());$iblockId = (int)($_GET['IBLOCK_ID'] ?? 0);$data = [ 'userGroups' => $controller->getUserGroups($iblockId), 'accessRights' => $controller->getAccessRights($iblockId),];Extension::load([ 'ui.buttons', 'ui.icons', 'ui.notification', 'ui.accessrights.v2', 'ui.entity-selector', 'ui.sidepanel-content',]);?><!DOCTYPE html><html><head><?php $APPLICATION->ShowHead(); ?></head><body><div class="iliasa-sp-access-rights-container"></div><div id="iliasa-sp-access-search-container"></div><script>BX.ready(function () { window.IliasaSPAccessRights = new BX.UI.AccessRights.V2.App({ renderTo: document.getElementById('iliasa-sp-access-rights-container'), userGroups: <?= Json::encode($data['userGroups']) ?>, accessRights: <?= Json::encode($data['accessRights']) ?>, component: 'iliasa:sales.plan.access', actionSave: 'save', mode: 'class', additionalSaveParams: { iblockId: <?= (int)$iblockId ?> }, maxVisibleUserGroups: 10, searchContainerSelector: '#iliasa-sp-access-search-container', analytics: {} }); window.IliasaSPAccessRights.draw();});</script><?php//Важная часть, чтобы оживить кнопки Сохранить, Отмена$APPLICATION->IncludeComponent('bitrix:ui.button.panel', '', [ 'HIDE' => true, 'BUTTONS' => [ [ 'TYPE' => 'save', 'ONCLICK' => 'window.IliasaSPAccessRights.sendActionRequest()', ], [ 'TYPE' => 'custom', 'LAYOUT' => (new \Bitrix\UI\Buttons\Button()) ->setColor(\Bitrix\UI\Buttons\Color::LINK) ->setText('Отмена') ->bindEvent('click', new \Bitrix\UI\Buttons\JsCode('window.IliasaSPAccessRights.fireEventReset()')) ->render(), ], ],]);require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/epilog_after.php';?>
Отдельно обращу внимание на часть $APPLICATION->IncludeComponent(‘bitrix:ui.button.panel’, …); Пришлось сделать именно в таком виде, как в компоненте образце от Битрикс, нейросети тоже ни в какую не хотели делать так. ИИ считал можно же проще и по документации, кнопка отмены с ‘TYPE’ => ‘cancel’, и для save достаточно только ‘TYPE’. Однако идеально работают данные кнопки с BX.UI.AccessRights.V2 именно так.
Внимательный читатель заметит еще одну интересную строчку:
component: 'iliasa:sales.plan.access'
Как я упоминал выше, отдельный компонент не хотелось писать, показалось, что удобнее и в меру изолированно можно обойтись отдельным файлом slider_access.php — на самом деле, лучше делать свой отдельный компонент. Почему? BX.UI.AccessRights.V2.App умеет отправлять AJAX запросы только к компоненту, и не умеет в модуль. У меня вся логика хранения и работы с правами доступа была упакована в модуль, там же уже был подготовлен контроллер для взаимодействия, и логично было бы слать AJAX запросы сразу в него. Однако в данный момент в BX.UI.AccessRights.V2.App такого функционала нет — поручил исследование нейросетям, не поверил, изучил исходный код сам, — да, к сожалению, не умеет, только в контроллер компонента. В основной компонент вносить логику работы с AJAX‑данными от BX.UI.AccessRights.V2.App не захотелось, он же был вроде как изолирован у меня отдельным файлом. Поэтому пришлось создать прокси‑компонент sales.plan.access
Наконец, в slider_access.php у меня и html, и скрипты, и css код, что органично на самом деле ложится в шаблоны Bitrix с разделением по отдельным файлам. Понимаю, что текущее решение — это 100% технический долг, однако для наглядной демонстрации граблей, решил оставить именно так.
2. Описание прав доступа accessRights
BX.UI.AccessRights.V2.App ожидает на вход параметр accessRights — JSON строго определенной структуры. Есть три типа полей, и каждый со своим форматом данных:
|
Тип |
UI |
Что присылает JS |
|---|---|---|
|
|
вкл / выкл |
|
|
|
inline-выпадашка с ID-значениями |
строка-id выбранного варианта |
|
|
каскадный попап с |
массив id переменных |
Каждый тип прав доступа можно сгруппировать, тогда необходимо добавить ключ groupHead со значением True у родителя, и ключ group со значением ключа id родителя.
2.1. Переключатель (toggler) — самый простой случай
{"id": "settings","type": "toggler","title": "Настройка прав доступа","minValue": "0","maxValue": "1"}
2.2. Список значений (variables)
Тут становится гораздо интересней. Захотелось сделать общий переключатель права редактировать организации, а во вложенном списке перекрывать это значением по конкретной организации из списка. Если посмотреть на JSON настройки, покажется, что в нем набор ошибок:
{"id": "edit","type": "toggler","title": "Редактирование записей","hint": "Master-переключатель. Организации наследуют его значение по умолчанию","groupHead": true,"minValue": "0","maxValue": "1"},{"id": "edit_org_110","type": "variables","title": "ООО \"Альфа\"","group": "edit","variables": [{"id": "0", "title": "Наследовать"},{"id": "1", "title": "Редактировать"},{"id": "2", "title": "Нет доступа"}],"emptyValue": "0","minValue": "0","maxValue": "1"}
-
У edit_org_110, вроде есть значение в массиве variables «id»: «2», тогда почему maxValue — всего 1, а не 2.
-
Почему 0 — для edit, это Нет доступа, а для конкретной организации 0 — это Наследовать, а Нет доступа — это 2.
Пришлось хорошо проштудировать исходный код не только BX.UI.AccessRights.V2.App, но и \Bitrix\Main\Access\Permission\AccessPermissionTable прежде чем выстроилась эта, на первый взгляд нелогичная схема.
Дело вот в чем:
1) Логика для Наследовать у потомков:
-
0 значение не сохраняется в базе данных. 0 — значения нет. Логика, которую реализуешь по умолчанию часто 0 — это Нет доступа.
if (!array_key_exists($field->getName(), $data)|| !$data[$field->getName()] // ← loose boolean: !0 === true → запись отбрасывается) {return false;}
-
В данной группе визуально захотелось, чтобы у вложенных организаций по умолчанию было право доступа Наследовать, которое не хранится в базе данных. Чтобы его не задавать сразу всем организациям, которые могут еще и добавляться в будущем.
-
emptyValue — значение, которое будет показываться/выбираться у права по умолчанию, если значение не задано;
Тогда, если сделать более логично для вложенных организаций, мы бы сделали:
-
0 — Нет доступа;
-
1 — Редактировать;
-
2 — Наследовать;
И emptyValue установили на значение 2. Что по факту получилось визуально:
Установили Нет доступа -> Значение 0 -> В базе данных не сохраняется -> При загрузке значения нет -> Значит пусто -> emptyValue = 2 - видим Наследовать, общий переключатель стоит на Редактировать, т.е. доступ есть
Итог: рассинхронизация, поэтому получилось:
-
0 — Наследовать;
-
1 — Редактировать;
-
2 — Нет доступа;
2) «minValue»: «0», «maxValue»: «1» для списка значений, это не максимальное и минимальное значение, это то что будет выбрано для всех ролей при массовом действии. У меня сделано было для этих прав: 0 — Наследовать; 1 — Редактировать. Да, есть некоторый диссонанс с визуальной составляющей, однако это скорее дело вкуса, так мне показалось тут удобнее.
Главное, надеюсь, получилось объяснить логику ключей для управления этой части визуальной составляющей в BX.UI.AccessRights.V2.App — у себя можете реализовать по другому.
Лирическое отступление: пока сравнивал поведение с правами доступа в раздел CRM. Заметил, что у Bitrix — это сделано по другому, и там показалось, что Нет доступа — при пустом значении сохраняется. Что оказалось: при переходе в CRM на BX.UI.AccessRights.V2.App, похоже отказались от \Bitrix\Main\Access\Permission\AccessPermissionTable, которая описана в документации. Там используется Bitrix\Crm\Security\Role\Model\RolePermissionTable унаследованная на 2 класса раньше, в ней нет такой проверки на пустое значение, и они для значения «Нет доступа» сохраняют в базу данных пустую строку ».
2.3. Зависимые списки значений (dependent_variables)
Новинка, появившаяся только в BX.UI.AccessRights.V2.App:
Каждая переменная описывается с двумя массивами связей:
-
requires— какие переменные нужно включить вместе с этой; -
conflictsWith— какие переменные необходимо выключить.
secondary — если нам нужно отправить настройку в нижнюю часть, используется для переключателя Наследовать.
{"id": "view_status_114","type": "dependent_variables","title": "Просмотр плана продаж: Предварительный","group": "view","variables": [{"id": "0","title": "Нет доступа","conflictsWith": ["svoi", "dept", "subdept", "vse", "inherit"]},{"id": "svoi","title": "Только свои","conflictsWith": ["0", "inherit"]},{"id": "dept","title": "Своего отдела","requires": ["svoi"],"conflictsWith": ["0", "inherit"]},{"id": "subdept","title": "Подотделов отдела","requires": ["svoi", "dept"],"conflictsWith": ["0", "inherit"]},{"id": "vse","title": "Все","requires": ["subdept", "dept", "svoi"],"conflictsWith": ["0", "inherit"]},{"id": "inherit","title": "Наследовать","secondary": true,"conflictsWith": ["0", "svoi", "dept", "subdept", "vse"]}],"minValue": ["0"],"maxValue": ["vse", "subdept", "dept", "svoi"]}
Тип dependent_variables — это зависимые списки значений. Например, когда мы хотим дать право видеть сделки «Подотделов отдела», этот переключатель автоматически включает права «Только свои» и «Своего отдела». Выключение права «Своего отдела» приводит к выключению права «Подотделов отдела». Право «Нет доступа» — выключает переключатели всех остальных прав, как и право «Наследовать». Однако тут оно реализовано с другим значением, нежели в описанном выше примере для типа variables, и для прав внутри группы можно наглядно наблюдать диссонанс, описанный выше. Специально оставлю в таком виде, — это очень хорошо демонстрирует возможные различные подходы к решению.
Подводя итог: для BX.UI.AccessRights.V2.App реализована Data‑Driven архитектура, когда данные определяют его внешний вид и поведение.
3. Особенности реализации Rule-классов
Реализация собственных классов и таблиц для хранения прав доступа была сделана в соответствии с официальной документацией Bitrix без особых проблем, наглядно можно изучить в репозитории. Однако в процессе тестирования проверки прав выявили, что права Edit, View — работают корректно, а право AccessSetting (доступ к настройке прав доступа) — никак не хотело подхватываться. Опять найденные и отданные исходники ИИ приводили к многострочному неработающему коду — стало даже обидно, смотрю — а там всего 4 файла, три по 1 КБ, один — 2 КБ, это совсем мизер. Вникаю в логику, там обычная завязка, часто используемая Bitrix:
Код права доступа + SUFFIX (Rule) — это имя файла и класса. Привожу в соответствие через переименование, всё начинает работать из коробки. Пример реализации класса прав доступа:
namespace Iliasa\SalesPlan\Access\Rule;use Bitrix\Main\Access\AccessibleItem;use Bitrix\Main\Access\Rule\AbstractRule;use Iliasa\SalesPlan\Access\Permission\PermissionDictionary;/** * Правило: доступ к настройке прав. * Имя класса (AccessSettingsRule) выводится автоматически из action 'access_settings'. * 'access_settings' → split '_' → ['access', 'settings'] → ucfirst → 'Access' + 'Settings' + 'Rule' */class AccessSettingsRule extends AbstractRule{ public function execute(AccessibleItem $item = null, $params = null): bool { if ($this->user->isAdmin()) { return true; } return (int)$this->user->getPermission(PermissionDictionary::PERMISSION_SETTINGS) === 1; }}
4 Бонус: работа с ui.entity‑selector
В модуле представлен наглядный пример работы с еще одним UI компонентом Bitrix, а именно с Диалогом выбора сущностей (ui.entity‑selector) с собственным Провайдером данных. Параметры его настройки и реализации есть в документации, однако нейронки почему‑то в 9 из 10 случаев любят 2 обязательных параметра разместить на уровень ниже, из‑за чего данный компонент не оживает:
'dialogOptions' => [ 'context' => 'ILIASA_SALESPLAN_ORG_FILTER', 'entities' => [[ 'id' => 'salesplan-org', 'options' => ['iblockId' => $iblockId], 'dynamicLoad' => true, // ← без этого AJAX к провайдеру не уйдёт 'dynamicSearch' => true, // ← без этого AJAX к провайдеру не уйдёт ]],],
Постоянно 2 параметра dynamicLoad и dynamicSearch ИИ обожают засовывать внутрь options.
5. Схема целиком
Визуальное представление описанного выше взаимодействия, наглядно можно предоставить в виде схемы в формате mermaid.live:
Хочется рассмотреть подробнее, пожалуйста, просто скопируйте код схемы, перейдите по ссылке в сервис mermaid.live и вставьте его там:
flowchart TD %% Стили style ProxyComp fill:#fff,stroke:#666,stroke-width:2px style Controller fill:#fff,stroke:#666,stroke-width:2px style AccessCore fill:#bbf,stroke:#333,stroke-width:2px style JsApp fill:#ff9,stroke:#333,stroke-width:2px User([Пользователь<br/>с правом settings]) -->|клик| Toolbar[Кнопка<br/>«Права доступа»] Toolbar -->|открывает| Slider[SidePanel:<br/>slider_access.php] subgraph Frontend ["Frontend (Browser)"] Slider --> JsApp[BX.UI.AccessRights.V2.App<br/>draw / sendActionRequest] end JsApp -->|AJAX component_ajax=Y| ProxyComp subgraph Backend ["Backend (PHP / Bitrix)"] ProxyComp[iliasa:sales.plan.access<br/>Controllerable component] Controller[Iliasa\\SalesPlan\\Controller\\<br/>SalesPlanAccess] AccessCore[SalesPlanAccessController<br/>+ ViewRule / EditRule /<br/>AccessSettingsRule] ORM[(b_iliasa_sp_role<br/>b_iliasa_sp_permission<br/>b_iliasa_sp_role_relation)] ProxyComp -->|delegate| Controller Controller -->|getUserGroups<br/>getAccessRights| AccessCore Controller -->|save / delete| ORM AccessCore -->|read| ORM end AccessCore -.->|UserModel<br/>getPermission| TargetComp[Целевой компонент:<br/>iliasa:sales.plan.matrix] User -->|открывает страницу| TargetComp
Заключение — разворачивание модуля и пользовательское тестирование
В модуле реализованы идемпотентные генераторы демо‑данных:
-
Заполнение универсального списка плана продаж;
-
Структура компании из 9 отделов и 15 сотрудников;
-
7 эталонных ролей для покрытия комбинаций прав доступа;
В силу этого рекомендуется производить разворачивание на копии системы для разработки или чистой копии развернутой через BitrixSetup скрипт.
Либо подключите git репозиторий сразу в папке /local/, либо скопируйте скачанную папку /local/ в корень коробочного портала. Дальше рекомендуемый порядок разворачивания такой:
1) Установите модуль. При установке, будет предложено установить и заполнить за 1 месяц универсальный список План продаж — удобнее не соглашаться, почему так — смотрите далее;
2) Перейдите в настройки модуля:
3) Установите список «План продаж» из настроек без заполнения;
4) После чего создайте структуру компании и сотрудников;
5) Затем создайте набор базовых ролей;
6) Только после этого сгенерируйте данные, так как тогда они будут привязаны к сотрудникам сразу и Вы сможете нагляднее произвести дальнейшую процедуру тестирования;
7) Установите визуальный демо‑компонент и перейдите по указанному для него пути;
Протестируйте права доступа, авторизовываясь за других пользователей, для экспресс-проверки прав реализована 2-я вкладка настроек модуля:
Как и в любом туториале по недокументированному API — рабочий код в репозитории первичен, статья помогает быстрее в нём сориентироваться. Берите шаблон, режьте под свой модуль — буду рад обратной связи.
Репозиторий: ссылка на GitHub
Связь: @Ilya_Sosnin в Telegram
ссылка на оригинал статьи https://habr.com/ru/articles/1043436/