Настройки для своего модуля 1С-Битрикс

30.09.2021

С момента написания первой верии модуля для 1С-Битрикс прошло около года, но именно теперь потребовалось внедрить поддержку мультисайтовости. Первым делом нужно было переделать настройки, но по ходу дела этот вопрос был раскрыт более шире.

Источники

Здесь вскользь что-то там упоминается про настройки. Этого недостаточно, пришлось искать на сторонних сайтах как сделать настройки для своего модуля 1С-Битрикс.

Оказалось не сложно ... до тех пор пока не потребовалась поддержка мультисайтовости. Но это можно подсмотреть в модулях "из коробки", например хороший пример модуль бизнес процессы файл настроек которого располагается по пути bitrix/modules/bizproc/options.php.

В общем, обширным источником знаний об организации настроек в своем модуле для 1С-Битрикс являются исходники модулей из коробки.

Дополнительно можно посмотреть написанный мной модуль для 1С-Битрикс (одна из причин появления этой статьи).

Базовый вариант для одного сайта

Настройки для одного сайта на движке 1С-Битрикс
Настройки для одного сайта на движке 1С-Битрикс

Организация настроек модуля храниться в файле options.php в директории модуля.

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

Это выглядит как-то не консистентно, с одной стороны инфраструктура движка предоставляет богатый функционал для работы с движком и модулем интернет-магазина (есть даже почти PSR4) с возможностью отделить логику от представления, с другой стороны настройки модулей без таких возможностей.

Однако, по ходу реализации настроек модуля можно попытаться отделить логику от представления, но не будем пока отходить от принятой концепции :)

Файл будет содрежать следующие разделы кода:

  • инициализация
  • проверка и сохранение настроек
  • рендер настроек

Инициализация

Потребуется 3 объекта ядра движка запрос, настроки, загрузчик:

•••
php
use Bitrix\Main\HttpApplication; use Bitrix\Main\Config\Option; use Bitrix\Main\Loader;

Затем необходимо получить текущий запрос и загрузить модуль если в логике настроек (сохранение/проверка) используется код модуля, как было в моем случае:

•••
php
$request = HttpApplication::getInstance()->getContext()->getRequest(); // идентификатор модуля это название его директории, а файл настроек находится по пути idModule/options.php $idModule = dirname(__DIR__); Loader::includeModule($idModule);

Теперь массив настроек:

•••
php
$aTabs = [ [ "DIV" => "css класс", "TAB" => "Название вкладки настроек модуля", "OPTIONS" => [ [ "id_point", // идентификатор пункта настроек "Label", // надпись возле пункта настроек "default", // значение по умолчанию // опиание GUI элемента, для каждого вида свое описание [ "text", 11 ] ], ... ] ] ];

Страница настроек модуля состоит из вкладок, в массиве $aTabs, каждая вкладка должна располагаться в отдельном массиве.

В ключе OPTIONS располагаются непосредственно настройки вкладки. Рассмотрим несколько видов GUI элементов:

  • text - тестовое поле
•••
php
[ "token", "Токен компании:", "", // по умолчанию пусто [ "text", 20 // размер строки ] ]
  • checkbox, принимает значения Y (отмечено) или N (не отмечено)
•••
php
[ "only2", "Создавать только второй чек:", "N", // по умолчанию не отмечено ["checkbox"] ]
  • selectbox - выдападающий список
•••
php
/* $aPaySystems - ассоциативный массив систем оплат где: - ключ - идентификатор системы оплаты - значение - название */ [ "paysystem", "Создавать чек для оплаты:", array_keys($aPaySystems)[0], [ "selectbox", $aPaySystems ] ]

Проверка и сохранение настроек

Отправка настроек осуществляется POST запросом на тот же файл options.php (в следующем разделе разберем этот момент). Поэтому следующим разделом в файле options.php будет обработка отправленных настроек.

Для начала нужно проверить что пришел POST запрос с актуальным идентификатором сессии:

•••
php
if($request->isPost() && check_bitrix_sessid()) { // принятие // проверка // сохранение }

Теперь внутри мы можем получить отправленные настройки проверить и сохранить. Разделим эти процессы и сначала соберем настройки на основании массива настроек $aTabs["OPTIONS"]:

•••
php
$aOptions = []; foreach($aTab["OPTIONS"] as $aOption) { if(!is_array($aOption)) continue; if($request["apply"]) { if($type == "checkbox") $aOptions[$aOption[0]] = $request->getPost($aOption[0]); else $aOptions[$aOption[0]] = trim($request->getPost($aOption[0])); } else if($request["default"]) $aOptions[$aOption[0]] = trim($aOption[2]); }

Теперь имея массив отправленных юзером данных, можно проверить корректность настроек, если есть в этом необходимость. Мне надо было проверить авторизационные данные клиента внешнего сервиса.

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

И наконец сохранение:

•••
php
// сохранение настроек foreach($aOptions as $sKey => $sValue) Option::set($idModule, $sKey, $sValue); // перенаправление юзера на страницу настроек модуля, иначе при обновлении этой страницы с модулем форма будет отправлена повторно LocalRedirect($APPLICATION->GetCurPage()."?mid=".$idModule."&lang=".LANG);

Рендер настроек

Здесь логика смешивается с представлением ...

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

Также не забываем про безопасность, которая обеспечивается вставкой скрытого поля с идентификатором сессии bitrix_sessid_post, чтобы потом при приеме данных из формы можно было использовать проверку этой сесси на возможность записи настроек check_bitrix_sessid (упоминало выше).
•••
php
$oTabControl = new CAdminTabControl("tabControl", $aTabs); $oTabControl->Begin(); ?><form action="<?php echo($APPLICATION->GetCurPage()); ?>?mid=<?php echo($idModule); ?>&lang=<?php echo(LANG); ?>" method="post"> <?php $oTabControl->BeginNextTab(); foreach($aTabs as $aTab) { if($aTab["OPTIONS"]) { $oTabControl->BeginNextTab(); __AdmSettingsDrawList($idModule, $aTab["OPTIONS"]); } } $oTabControl->Buttons(); ?><input type="submit" name="apply" value="Применить" class="adm-btn-save" /> <?php echo(bitrix_sessid_post()); ?> </form> <?php $oTabControl->End();

Мультисайтовость

Настройки для нескольких сайтов на одном инстансе движка 1С-Битрикс (мультисайтовость)
Настройки для нескольких сайтов на одном инстансе движка 1С-Битрикс (мультисайтовость)

А вот здесь начинается интересное ...

В самом начале был упомянут модуль бизнес процессы, файл настроек которого расположен по пути bitrix/modules/bizproc/options.php. Это наиболее простой и наглядный пример организации настроек для модуля с поддержкой мультисайтовости 1С-Битрикс.

Что же меняется в базовой реализации настроек модуля при поддержке мультисайтовости? Немного:

  • массив $aTabs остается как и прежде
  • для валидации, сохранения и рендера потребуется информация об имеющихся сайтах на инстансе движка:
•••
php
$aSiteIds = GetSites(); // ... // получить массив сайтов [lid => name, ...] function GetSites() { $dbRes = Bitrix\Main\SiteTable::getList(); $aSites = []; while ($aSite = $dbRes->fetch()) $aSites[$aSite["LID"]] = $aSite["NAME"]; return $aSites; }
  • в рендере нужно самостоятельно писать верстку формы настроек с учетом мультисайтовости
  • при обработке форм так же нужно учитывать особенности реализации формы
Информацию о сайтах на инстансе 1С-Битрикс можно посмотреть в БД в таблицах b_lang и b_landing_site.

Рендер

Основная обертка формы и вкладки настроек такая же. Однако, кроме вкладок настроек модуля, теперь нужны подвкладки внутри вкладок, создать их можно при помощи CAdminViewTabControl, документацию по которой не удалось найти. Создаем подвкладки:

•••
php
foreach($aSiteIds as $sSiteId => $sSiteName) { $aSubTabs[] = [ "DIV" => "opt_site_$sSiteId", "TAB" => "$sSiteName ($sSiteId)", 'TITLE' => '', "OPTIONS" => $aTabs["OPTIONS"] ]; } $subTabControl = new CAdminViewTabControl("subTabControl", $aSubTabs);

__AdmSettingsDrawList здесь уже не подходит, так как она рендерит верстку вне подвкладок. Штатного средства 1С-Битрикс найти не удалось, поэтому была написана своя функция рендера настроек (очень не хотелось писать верстку, лучше генератор верстки) RenderSettings:

•••
php
function RenderSettings($idModule, $aSettings, $sSiteId) { $a = []; foreach($aSettings as $value) { $name = $value[0]; $name2 = $value[0]."_".$sSiteId; $desc = $value[1]; $default = $value[2]; $type = $value[3][0]; $val = $value[3][1]; //$realval = Option::get($idModule, $name, $default, $sSiteId); $realval = settings::get($name, $sSiteId); $realval = ($realval !== null ? $realval : $default); $inner = ""; switch($type) { case "text": $inner = '<input type="text" size="'.$val.'" maxlength="255" value="'.$realval.'" name="'.$name2.'">'; break; case "selectbox": $aOptions = []; foreach($val as $key2 => $val2) $aOptions[] = '<option value="'.$key2.'"'.($realval == $key2 ? 'selected=""' : '').'>'.$val2.'</option>'; $inner = '<select name="'.$name2.'">'.implode("\n", $aOptions).'</select>'; break; case "checkbox": $inner = '<input type="checkbox" id="'.$name2.'" name="'.$name2.'" value="'.$realval.'"'.($realval == "Y" ? 'checked=""' : '').' class="adm-designed-checkbox"> <label class="adm-designed-checkbox-label" for="'.$name2.'" title=""></label>'; break; default: break; } $a[] = '<div style="display: block; margin-bottom: 5px;"><label style="display: inline-block;width:50%;text-align: right;">'.$desc.'</label>'.$inner.'</div>'; } return implode("\n", $a); }

Функция может обработать только text, select, checkbox, другое пока не понадобилось. На вход принимает 3 аргумента:

  • $idModule - идентификатор модуля
  • $aSettings - $aTabs["OPTIONS"]
  • $sSiteId - LID сайта
Функция вернет рендер GUI элементов, при этом к названию элементов добавляться постфикс _LID, чтобы их можно было как-то отличить между сайтами.

Прежде чем пройтись циклом по вкладкам массива $aTabs необходимо стартовать процесс обработки вкладок и затем отрендерить настройки:

•••
php
$subTabControl->Begin(); foreach ($aSiteIds as $sSiteId => $sSiteName) { $subTabControl->BeginNextTab(); foreach($aTabs as $aTab) { if($aTab["OPTIONS"]) echo RenderSettings($idModule, $aTab["OPTIONS"], $sSiteId); } } $subTabControl->End();

Прием настроек при сохранении

Имея мультисайтовость имеем одинаковые имена настроек для каждого сайт, но разные значения. Названия GUI элементов у нас имеют постфикс с LID сайта, значит проходимся по массиву сайтов, используя LID сайта проходимся по массиву настроек и пробуем извлечь настройки:

•••
php
$aOptions = []; foreach($aSiteIds as $sSiteId => $name) { $aOptions[$sSiteId] = []; foreach($aTabs as $aTab) { foreach($aTab["OPTIONS"] as $aOption) { if(!is_array($aOption)) continue; $type = $aOption[3][0]; if($oRequest["apply"]) { if($type == "checkbox") $aOptions[$sSiteId][$aOption[0]] = $oRequest->getPost($aOption[0]."_".$sSiteId) !== null ? "Y": "N"; else $aOptions[$sSiteId][$aOption[0]] = trim($oRequest->getPost($aOption[0]."_".$sSiteId)); } else if($oRequest["default"]) $aOptions[$sSiteId][$aOption[0]] = trim($aOption[2]); } } }

Важный момент с checkbox, если он не отмечен на стороне клиента, то при отправке формы название элемента не будет в массиве данных, поэтому checkbox обрабатываем по особенному.