Привет, Хабр. Недавно я написал целый цикл статей по работе со смарт‑процессами, помогающий погрузить «непосвященного» человека в азы API коробочной версии, реализующего возможности управления смарт‑процессами и связанными с ними элементами. В рамках последней статьи, разъясняющей применение обработчиков события, от слушателей и интересующихся получил в личку много вопросов, связанных в целом с применением обработчиков событий в Битрикс24. В сегодняшней статье рассмотрим один из популярных практических кейсов — реализовать возможность управлять какими‑либо настройками обработчиков события из стандартной панели администратора Битрикс24.

Я понимаю, что руководств по сборке модулей Битрикс и на Хабре и в официальной документации достаточно много. Но на мой взгляд основная проблема таких руководств — это их объем (автор руководства пытается расписать все возможные файлы в составе модуля), что для начинающего неподготовленного пользователя (вспоминаю тут себя в начале карьеры Битрикс разработчика) может быть трудным к пониманию. Я ни в коем случае не утверждаю, что авторы данных материалов написали их плохо, на определенном этапе развития специалистам нужны и полные материалы целиком, поэтому своих читателей такие материалы конечно же найдут. Мои же статьи в данном цикле рассчитаны на аудиторию, только начинающую делать первые шаги в Битрикс разработке, потому упрощение пойдет им определенно на пользу.

Если что, основная статья начинается отсюда :-)

В сегодняшней статье‑туториале мы соберем модуль для Битрикс24, реализующий добавление и удаление обработчиков события, а также страницу настроек, позволяющую включать и отключать эти обработчики для определенных воронок сделки. Многоязычность пока для простоты понимания реализовывать не будем. Может, разберем данный вопрос в следующих статьях. Пишите в комментариях, если хотите что‑то разобрать в следующих статьях цикла.

Если обратиться к принятым стандартам разработки BitrixFramework, системные модули и модули, загружаемые из 1С‑Битрикс: Маркетплейс, располагаются в папке /bitrix/modules/. У нас же модуль полностью кастомный, его структуру мы будем создавать в папке /local/modules/.

Начинаем создание модуля

Как корабль назовешь — так он и поплывет пойдет, говорят моряки. Перед тем как собирать вместе файлы модуля, нам нужно определиться с его техническим названием, которое будет его однозначно идентифицировать в системе. Раз основная задача — научиться оперировать обработчиками события через модуль — назовем его learnevents.

Соответственно создаем папку для нашего модуля по пути /local/modules/learnevents.

Разбирать полный состав файлов модуля мы не будем, писал выше почему. Далее будем создавать минимальный набор файлов, который нам понадобится для решения нашей задачи.

Папка install

Первой создаем папку /local/modules/learnevents/install. В ней будут находиться файлы, отвечающие за установку и удаление нашего модуля.

Первым в папке создаем файл /local/modules/learnevents/install/version.php следующего содержания:

<?php
$arModuleVersion = [
	'VERSION' => '1.0.0',
	'VERSION_DATE' => '2025-05-13',
];

Нетрудно догадаться, что в данном файле мы указываем текущую версию модуля и дату его выпуска. Можно указать в принципе любые подходящие по формату данные.

Далее создаем основной файл установщика модуля /local/modules/learnevents/install/index.php. В коде будут поясняющие комментарии:

<?php

use Bitrix\Main\ModuleManager;

//Основной класс установщика модуля, должен наследоваться от системного класса CModule
class learnevents extends CModule
{
    //Системный идентификатор модуля, должен совпадать с названием класса
    public $MODULE_ID = 'learnevents';
    //Версия модуля и дата. Оставляем пустыми так как подгрузим нужные в конструкторе
    public $MODULE_VERSION;
    public $MODULE_VERSION_DATE;
    //Название модуля, выводимое в админ панели
    public $MODULE_NAME = 'Управляемые обработчики';
    //Описание модуля, выводимое в админ панели
    public $MODULE_DESCRIPTION = 'Тестовый модуль, показывающий возможности управления обработчиками события';
    //Адрес сайта разработчика модуля
    public $PARTNER_URI = 'https://site.ru';
    //Не использовать права доступа к модулю на основе групп пользователей
    public $MODULE_GROUP_RIGHTS = 'N';

    //Основной конструктор класса, в котором мы читаем файл version.php и берем оттуда версию модуля и дату релиза
    public function __construct()
    {
        $arModuleVersion = [];

        include __DIR__ . '/version.php';

        if (is_array($arModuleVersion) && array_key_exists('VERSION', $arModuleVersion)) {
            $this->MODULE_VERSION = $arModuleVersion['VERSION'];
            $this->MODULE_VERSION_DATE = $arModuleVersion['VERSION_DATE'];
        }
    }

    //Метод, устанавливающий обработчики события при установке модуля
    public function InstallEvents()
    {
        $eventManager = \Bitrix\Main\EventManager::getInstance();
        $eventManager->registerEventHandler("crm",
            "OnBeforeCrmDealAdd",//Событие, на которое вешаем обработчик
            $this->MODULE_ID,//Идентификатор модуля, берем текущий
            "Learn\\Event\\Main",//PHP Класс с неймспейсом, в котором реализован метод обработчика
            "RunEvent"//Название метода-обработчика
        );
        //Далее код других обработчиков
        return true;
    }

    //Метод, удаляющий обработчики события при удалении модуля
    public function UnInstallEvents()
    {
        $eventManager = \Bitrix\Main\EventManager::getInstance();
        $eventManager->unRegisterEventHandler("crm",
            "OnBeforeCrmDealAdd",//Событие, на которое вешаем обработчик
            $this->MODULE_ID,//Идентификатор модуля, берем текущий
            "Learn\\Event\\Main",//PHP Класс с неймспейсом, в котором реализован метод обработчика
            "RunEvent"//Название метода-обработчика
        );
        //Далее код других обработчиков
        return true;
    }

    //Метод, запускающийся при установке модуля
    public function DoInstall()
    {
        $this->InstallEvents();//Запускаем установку обработчиков
        ModuleManager::registerModule($this->MODULE_ID);//Регистрируем модуль в системе как установленный
    }


    //Метод, запускающийся при удалении модуля
    public function DoUninstall()
    {
        $this->UnInstallEvents();//Запускаем удаление обработчиков
        ModuleManager::unRegisterModule($this->MODULE_ID);//Снимаем регистрацию модуля в системе
    }


}

Далее добавляем файл /local/modules/learnevent/install/unstep1.php. Он будет отвечать за страничку, которая появляется перед удалением модуля в панели администратора:

<?
//Если пользователь не авторизован, прерываем метод
if(!check_bitrix_sessid()) return;
echo CAdminMessage::ShowNote('Управляемые обработчики');
?>
<form action="<?=$APPLICATION->GetCurPage(); ?>">
	<?=bitrix_sessid_post(); ?>
	<input type="hidden" name="step" value="2">
	<input type="hidden" name="id" value="learnevent">
	<input type="hidden" name="uninstall" value="Y">
	<input type="submit" name="nextstep" value="Далее">
	<input type="submit" name="cancel" value="Отменить">
</form>

Часто на данную страничку добавляют сообщения для тех, кто собирается удалить модуль, например «Пожалуйста, не удаляйте! Мы еще можем быть полезны».:-)

Папка lib

В данной папке традиционно размещаются классы модуля, которые будут автоматически подключены при его установке. В нашем случае создаем файл /local/modules/learnevent/lib/main.php:

namespace Learn\Event;

use Bitrix\Main\Config\Option;

class Main
{
     /**
     * @return array|false Массив направлений сделки
     * @description Возвращает массим текущих направлений сделок, поднадобится в админке
     */
    public static function getDealFunnels()
    {
        //Проверяем активен ли модуль CRM. Без него никакие Сделки доступны не будут
        if (\Bitrix\Main\Loader::includeModule('crm')) {
            //Получаем ООП Фабрику работы со сделками
            $factory = \Bitrix\Crm\Service\Container::getInstance()->getFactory(
                \CCrmOwnerType ::Deal
            );
            //Получаем массив всех объектов направлений и делаем перебор
            $categories = $factory->getCategories();
            $arCategories = [];
            foreach ($categories as $category) {
                //Получаем данные каждого направления и записываем в массив
                $arCategories[] = $category->getData();
            }
            return $arCategories;
        } else {
            return false;
        }
    }

     /**
     * @return array|false Результат обработчика
     * @description Обработчик, который мы подключаем через модуля
     */
    public static function RunEvent(&$arFields)
    {
        //Проверяем, установлен ли флажок "Активно" в админке
        if (Option::get("learnevent", "ACTIVE") == "Y") {
            global $USER;
            //Получаем список пользователей
            $userExeptions = unserialize(Option::get("learnevent", "USER_EXEPTIONS"));
            $pipeline = Option::get("learnevent", "PIPELINE");
            if ((is_array($userExeptions) && in_array($USER->GetID(), $userExeptions)) || $userExeptions == $USER->GetID()) {                
                    if (isset($arFields['CATEGORY_ID']) && !empty($arFields['CATEGORY_ID']) && ($arFields['CATEGORY_ID'] == $pipeline)) {
                        //Какие-то действия, если наше условие сработало. Например прерываем создание Сделки
                        $arFields['RESULT_MESSAGE'] = "Извините, данному пользователю запрещено создавать Сделку в данной воронке!";
                        return false;
                    }             
            }

        }
    }
}

В данном файле можно разместить методы для других обработчиков. Также никто не запрещает повесить несколько разных методов на одно событие.

Корневая папка модуля

Теперь перейдем к одному из важнейших файлов, который реализует окно настроек модуля в панели администрирования Битрикс24. Этот файл по стандарту лежит в папке модуля и носит название options.php. В нашем случае создаем его по пути /local/modules/learnevent/options.php и добавляем туда код:

<?php
//Подключаем нужные нам неймспейсы и скрипты
use \Bitrix\Main\Loader,
    \Bitrix\Main\Config\Option;

require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_admin_before.php';

require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_admin_after.php';

global $APPLICATION;

$moduleName = 'learnevent';
if (!Loader::IncludeModule($moduleName)) {
    return;
}

//Проверяем права доступа, если их нет - перекидываем на форму авторизации
$POST_RIGHT = $APPLICATION->GetGroupRight($moduleName);
if ($POST_RIGHT == "D")
    $APPLICATION->AuthForm(GetMessage("ACCESS_DENIED"));

//Получаем данные POST Запроса если настройки изменены, проводим фильтрацию данных для безопасности
$request = Bitrix\Main\Application::getInstance()->getContext()->getRequest();
$request->addFilter(new \Bitrix\Main\Web\PostDecodeFilter);

//Сохраняем настройки модуля в системные опции (таблица b_option в БД)
if ($POST_RIGHT == "W" && check_bitrix_sessid() && $request->isPost()) {
    COption::SetOptionString($moduleName, 'ACTIVE', $request->get('ACTIVE'));
    COption::SetOptionString($moduleName, 'PIPELINE', $request->get('PIPELINE'));
    
    //Так как у нас может быть множественное значение а база можно хранить только единицное - делаем сериализацию массива в строку
    COption::SetOptionString($moduleName, 'USER_EXEPTIONS', serialize($request->get('USER_EXEPTIONS')));
}

//Инициализируем табы админки, у нас таб будет один
$tabs = [
    [
        'DIV' => 'main',
        'TAB' => 'Настройки',
        'ICON' => '',
    ]
];
$tabControl = new \CAdminTabControl('tabControl', $tabs);
//Далее добавляем форму с полями настроек, которые будут выводиться в админке
?>

    <form method="POST"
          action="<? echo $APPLICATION->GetCurPage() ?>?mid=<?= urlencode($mid) ?>&lang=<? echo LANGUAGE_ID ?>"
          ENCTYPE="multipart/form-data" name="post_form">
        <?= bitrix_sessid_post() ?>
        <?php
        $tabControl->Begin();
        $tabControl->BeginNextTab();
        ?>
        <?//Поле "Обработчик активен"?>
        <tr>
            <td>Обработчик активен</td>
            <td><input type="checkbox" name="ACTIVE"
                       value="Y" <?php if (Option::get($moduleName, 'ACTIVE') == "Y") echo 'checked'; ?>/></td>
        </tr>
        <?//Поле "Воронка Сделки". Далее получаем массив воронок и текущую настройку?>
        <tr>
            <?php $allCategories = \Learn\Event\Main::getDealFunnels(); ?>
            <?php $currentCategory = Option::get($moduleName, 'PIPELINE'); ?>
            <td>Воронка Сделки</td>
            <td>
                <select name="PIPELINE">
                    <?php foreach ($allCategories as $allCategory) { ?>
                        <option value="<?= $allCategory['ID'] ?>"<?php if ($allCategory['ID'] == $currentCategory) echo ' selected'; ?>><?= $allCategory['NAME'] ?></option>
                    <?php } ?>
                </select>
            </td>
        </tr>
        <?//Поле "Для пользователей". Множественный выбор пользователей для которых будет срабатывать обработчик.?>
        <tr>
            <?php $userCurId = unserialize(Option::get($moduleName, 'USER_EXEPTIONS')); ?>
            <?php $order = array('sort' => 'asc');
            $tmp = 'sort'; // параметр проигнорируется методом, но обязан быть
            $rsUsers = \CUser::GetList($order, $tmp);
            $allUsers = [];
            while ($arUser = $rsUsers->Fetch()) {
                $allUsers[$arUser["ID"]] = "[" . $arUser["ID"] . "] " . $arUser["NAME"] . " " . $arUser["LAST_NAME"];
            } ?>
            <td>Для пользователей</td>
            <td>
                <select name="USER_EXEPTIONS[]" multiple>
                    <?php foreach ($allUsers as $userID => $userName) { ?>
                        <option value="<?= $userID ?>"<?php if ((is_array($userCurId) && in_array($userID, $userCurId)) || $userCurId == $userID) echo ' selected'; ?>><?= $userName ?></option>
                    <?php } ?>
                </select>
            </td>
        </tr>
        <? $tabControl->End(); ?>
        <input type="submit" value="Сохранить">
    </form>
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/epilog_admin.php';
?>

Данный файл реализует стандартную страницу настроек нашего модуля в разделе панели администратора Битрикс (Настройки — Настройки продукта — Настройки модулей — Управляемые обработчики).

Вместо заключения

Выше мы разобрали минимально необходимый набор файлов, реализующих модуль для Битрикс24, устанавливающий обработчики события, и хранящий настройки для них. Модуль содержит минимальный набор системных методов для того, чтобы Битрикс воспринимал его именно как модуль, а не как набор скриптов непонятного назначения. Всем спасибо за внимание и жду нашей встречи в следующих статьях!


Если вы работаете с коробочной версией Битрикс24 и хотите глубже разобраться в механике событий — их типах, регистрации и возможностях для управления сущностями, обратите внимание на предстоящий открытый урок «События в Битрикс24: Путеводитель по изменениям».

Разберёмся в архитектуре событийной модели и обсудим, как эффективно внедрять собственные сценарии обработки изменений. Старт 21 мая в 20:00 — участие бесплатное, нужно только зарегистрироваться.

А в календаре мероприятий можно записаться на открытые уроки по другим ИТ-направлениям.