Опыт разработки модуля для 1С-Битрикс

Категория: Заметки | Скилл: php , sql , js | Дата: 08.10.2020

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

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

Оформление модуля

На офсайте есть полезный раздел как разрабатывать свои модули. Там многое описано, поэтому остановимся только на интересных моментах (не описанных либо слабо задокументированных).

Если модуль разрабатывается индивидуально для одного/нескольких проектов (не для широкой аудитории и не для публичного доступа), то размещаться в маркетплейсе вовсе необязательно, однако обновлять модуль средствами движка не представляется возможным (точнее я не смог найти, кроме написания кода для самостоятельного обновления самим модулем). Файлово модуль можно разместить в local/modules/module_id если директория модуля будет называться просто module_id (название модуля), то его можно найти в админке Настройки - Модули, а настройки в Настройки - Настройки модулей. Если именование директории модуля будет по правилам тогда модуль можно найти в админке Marketplace - Установленные решения, а настройки как и раньше.

В ином случае на маркетплейсе надо размещаться. Процедура не сложная, всего лишь надо стать технологическим партнером, каких-то подтверждающих документов в нашем случае не потребовалось. Однако, каждый модуль проходит модерацию, этот процесс оказался непростым, так как очередной запрос на модерацию может длится от недели, а отвечают модераторы ... "крайне содержательно". Но в итоге, спустя примерно меся, наш модуль пропустиили в маркетплейс :)

Настройки модуля

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

Организация настроек модуля храниться в файле options.php в директории модуля. Честно, я долго думал как это все по уму организовать, но более элегантных вариантов (кроме как засунуть все в один файл, тем более что он не более 100 строк) не пришло на тот момент.

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

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

Затем необходимо получить текущий запрос и загрузить модуль:

$oRequest = HttpApplication::getInstance()->getContext()->getRequest();
/*$idModule - идентификатор модуля, надо самому вычислить, способов несколько
$idModule = htmlspecialcharsbx($oRequest["mid"] != "" ? $oRequest["mid"] : $oRequest["id"]);
$idModule = dirname(__DIR__);
*/
Loader::includeModule($idModule);

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

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

Затем необходимо сохранить настройки модуля и перенаправить (get запрос) опять на страницу настроек чтобы отобразилась форма ввода настроек. В общем-то проверка корректности настроек зависит от конкретного проекта, в нашем случае это было критически важно, но если валидация настроек не важна то можно не записывать данные настроек в промежуточный массив, а сразу сохранять (Option::set), но я сделал так:


//если это был POST запрос и сессия юзера валидна
if($oRequest->isPost() && check_bitrix_sessid())
{
  //сюда сложим все настройки, чтобы потом проверить их на корректность
	$aOptions = [];
	foreach($aTabs as $aTab)
	{
		foreach($aTab["OPTIONS"] as $aOption)
		{
			if(!is_array($aOption))
				continue;

			if($aOption["note"])
				continue;

			if($oRequest["apply"])
				$aOptions[$aOption[0]] = trim($oRequest->getPost($aOption[0]));
			else if($oRequest["default"])
				$aOptions[$aOption[0]] = trim($aOption[2]);
		}
	}
	
	//здесь должна быть проверка настроек перед сохранением и редиректом
	
	//сохранение настроек
	foreach($aOptions as $sKey => $sValue)
		Option::set($idModule, $sKey, $sValue);
		
	//редирект
	LocalRedirect($APPLICATION->GetCurPage()."?mid=".$idModule."&lang=".LANG);
}
	

А теперь отрисовка формы настроек ... это трэш ибо mvc шло нафиг:

$oTabControl = new CAdminTabControl("tabControl", $aTabs);
$oTabControl->Begin();

?><form action="<?php echo($APPLICATION->GetCurPage()); ?>?mid=<?php echo($idModule); ?>&lang=<?php echo(LANG); ?>" method="post">
<?php
    //это вывод ошибок, $sError заполняется на этапе проверки введенных настроек (если есть ошибки)
    //более правильного решения на момент разработки модуля я не нашел, но оно наверняка есть :)
	if(strlen($sError) > 0)
		echo "<div style='width: 100%; margin: 5px; padding: 5px; text-align: center; color: red; font-size: 18px;'>$sError</div>";
		
	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();

Разработка

БД

Есть специальное пространство имен, а есть глобальная переменная $DB - объект для работы с базой данных. Документация исчерпывающая, я использовал в основном чистый SQL и ForSql для экранирования.

Так как наша компания предоставляет онлайн-кассы с возможностью вывода из оборота маркированных товаров (передача кодов маркировки в "честный знак"), то для каждой позиции товара необходимо иметь поле ввода кода маркировки, что поддерживается в движке из коробки. Однако, не без приколов ... максимальная длина кода в таблице БД движка составляет 100 символов, этого не хватает для кодов маркировки обуви, которые состоят из 127 символов, и не достаточно для кодов маркировки ЕГАИС 3.0 размером 150 символов. Полазив по форумам, понял что эта проблема еще не решена, поэтому написал свое решение:

//global $DB;
$sSQL = "ALTER TABLE `b_sale_store_barcode` CHANGE `MARKING_CODE` `MARKING_CODE` VARCHAR(150) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL;";
$DB->Query($sSQL, false, "DB error(".__FILE__.":".__LINE__.")");

Вообще разбор кодов маркировки это отдельная история.

Крон

В 1С-Битрикс крон назвается агент. Документация исчерпывающая :)

События

В новой версии ядра рекомендуется использовать новый подход с использованием EventManager, а здесь список событий. Описание метода registerEventHandler скудное, поэтому пришлось собирать значения аргументов по интернету:

$oEventManager->registerEventHandler(
  "sale",  //id модуля в котором есть это событие
  "OnSalePaymentEntitySaved",  //название события
  $this->MODULE_ID,  //ид модуля который регистрирует обработчик события
  "events",  //название класса обработчика
  "OnSalePaymentEntitySaved"  //название статического метода обработчика
);

Функция обработчик выглядит примерно так:

public static function OnSalePaymentEntitySaved(Bitrix\Main\Event $oEvent)
{
  //извлечение сущности сообщения
  $oPayment = $oEvent->getParameter("ENTITY");
  ...
}

Удаление обработчика (например при деинсталяции модуля) производится методом unRegisterEventHandler, значение аргументов аналогично registerEventHandler.

Мне довелось использовать следующие события (изначально их было около десятка, но потом оказалось что всю логику модуля можно уместить в 2 собятия):

  • OnSalePaymentEntitySaved - после сохранения сущности при поступлении оплаты, а так можно извлечь объект оплаты:
    $oPayment = $oEvent->getParameter("ENTITY");
  • OnAdminContextMenuShow - при выводе кнопок в админском меню, где можно встраивать свои кнопки:
function OnAdminContextMenuShow(&$aItems)
{
  $aItems[] = [
		"TEXT" => "Текст на кнопке", 
		"LINK" => "javascript:js код",
		"TITLE" => "Всплывающая подсказка",
		//"MENU" => $array,  //массив вложенных кнопок, такого же формата
		"ICON" => "btn_delete"  //стиль кнопки 
	];
}

В некоторых событиях (но не во всех) можно генерировать ошибки, которые будут видны в админке при обработке этого запроса (в каких запросах это можно делать, надо выяснять опытным путем):

return new \Bitrix\Main\EventResult(
	\Bitrix\Main\EventResult::ERROR,
	new \Bitrix\Sale\ResultError("Текст ошибки"),
	'sale'  //видимо id модуля для которого генерируется ошибка :)
);

Подгрузка статики

А здесь не все так просто.

Чтобы использовать css или js на страницах сайта необходимо зарегистрировать свои расширения.

Изначально я предполагал что статика будет загружаться из каталога модуля, все так и было на реальном хостинге (где был nginx). Все работало. Но модераторы тестировали модуль в иной среде (в какой именно выяснить не удалось, на эти вопросы не было нормальных ответов) - на локальном сервере и только на apache (судя по всему), а в директории модулей есть конфиг .htaccess который запрещает туда обращаться:

Deny from All

Тогда я пошел смотреть как решают эту проблему другие модули и ... оказывается каждый модуль копирует свою статику в директорию /bitrix/js/module_id при этом не каждый удаляет эти данные при деинсталяции. А я то думал: почему на чистой установке движка >130.000 файлов

Всю статику пришлось перенести в директорию модуля install и при установке модуля (STATIC_DIR_NAME - директории для копирования):

CopyDirFiles(
	$_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/".$this->MODULE_ID."/install/static/js",
	$_SERVER["DOCUMENT_ROOT"]."/bitrix/js/".STATIC_DIR_NAME, true, true
);

При деинсталяции модуля удалять файлы так:

DeleteDirFilesEx("/bitrix/js/".STATIC_DIR_NAME);

А регистрация расширений выглядит так:

CJSCore::RegisterExt("название_расширения", ['js' => "/bitrix/js/".STATIC_DIR_NAME."/script.js"]);
CUtil::InitJSCore(["название_расширения"]);

Кстати регистрировал расширение в сообщении OnAdminContextMenuShow.

Popup/Ajax

Ajax я описывал в предыдущей статье. Для popup есть документация и вот еще как создать popup почти одной строкой :)

В моем случае при нажатии на кнопку открывался popup, юзер мог закрыть и снова нажать на кнопку (чтобы опять увидеть это окно), но почему то повторно окно не открывалось, пришлось каждый раз создавать popup заного.

Обновления

Это оказалось самым простым, в документации кратко написано как делать обновления. Только description.ru (описание изменений обновления на русском языке) необходимо писать в кодировке Windows1251.

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

Проекты
SkyXEngine, PHP-API, S4G
Категории
В разработке :)
Популярное
В разработке :)