В чем проблема с Ajax-запросами в 1С-Битрикс?

Pavel Khoroshkov
3 min readNov 25, 2020

Как известно в версии 17.5.10 главного модуля 1С-Битрикс появились нативные обработчики ajax-запросов у компонентов и модулей. В чем же проблема?

В данной статье я буду обсуждать только ajax-запросы к компонентам, так как проблема именно с ними, а именно, вызов метода компонента происходит статически, и мы не получаем параметров вызова компонента $arParams. Понятно, что информацию можно передавать в параметрах запроса, но что если параметров очень много или в них содержатся какие-либо секретные данные?

В этой статье я попробовал ответить на этот вопрос и придумать решение.

Если Bitrix не дает нам $arParams, получим его самостоятельно.

Исходный код из примеров ниже на GitHub Gist: https://gist.github.com/pgooood/cd5169265e5abf4af02859d075dafe5d

Что необходимо чтобы получить $arParams не имея экземпляра класса компонента?

  1. Путь к PHP файлу, в котором происходит вызов компонента
  2. Идентификатор компонента в файле

Эти два параметра придется передать в ajax-запросе, если реальный путь к файлу на сервере — это секретная информация, можно его зашифровать, в своем решении я использую обычный Base64, как защиту от дурака.

Теперь мы имеем все необходимое чтобы достать $arParams. Что из инструментария битрикса нам понадобится:

  • метод CBitrixComponent::GetEditAreaId($entryId) — это единственный способ, из тех что я нашел, позволяющий получить идентификатор компонента в рамках файла скрипта, так как другие свойства класса CBitrixComponent, которые могли помочь, объявлены как приватные.
  • метод PHPParser::ParseScript($scriptContent)- позволяет распарсить содержимое PHP файла и получить информацию о находящихся в нем компонентах, в том числе и желанный$arParams.

Первое что мы сделаем — это класс компонента реализующий интерфейс Bitrix\Main\Engine\Contract\Controllable, в котором объявим обработчик аякс-запроса exampleRequest:

<?php
use Bitrix\Main\Engine\Contract\Controllerable;
class ajaxTest extends \CBitrixComponent implements Controllerable{ function executeComponent(){
$this->includeComponentTemplate();
}
function configureActions(){
return [
'exampleRequest' => [
'prefilters' => []
,'postfilters' => []
]
];
}
function exampleRequestAction(){
return [
/* ответ на аякс-запрос */
];
}
}

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

protected function componentId(){
$entryId = 'sometext';
$m = null;
/* вычленим только уникальную цифровую часть идентификатора */
if(preg_match(
'/^bx_(.*)_'.$entryId.'$/'
,$this->getEditAreaId($entryId)
,$m
)){
return $m[1];
}
}

Добавим методы для получения информации о компонентах из файла скрипта:

  • статический метод pageComponents, который будет возвращать все компоненты в заданном PHP файле, добавив в них идентификаторы, по которым мы сможем найти нужный
  • и статический метод componentData возвращающий информацию о компоненте по его идентификатору
protected static function pageComponents($scriptPath){
if(is_file($absPath = $_SERVER['DOCUMENT_ROOT'].$scriptPath)){
$arCounter = [];
$fileContent = file_get_contents($absPath);
$arComponents = \PHPParser::ParseScript($fileContent);
foreach($arComponents as &$r){
$arCounter[$r['DATA']['COMPONENT_NAME']]++;
/* делаем ID как в методе getEditAreaId */
$r['ID'] = abs(crc32($r['DATA']['COMPONENT_NAME']
.'_'.$arCounter[$r['DATA']['COMPONENT_NAME']]));
}
return $arComponents;
}
throw new \Exception('File ['.$scriptPath.'] not found');
}
protected static function componentData($componentId,$scriptPath){
if($componentId
&& ($arComponents = self::pageComponents($scriptPath))
){
foreach($arComponents as $arData)
if($componentId == $arData['ID'])
return $arData;
}
}

Теперь добавим в $arResult необходимые данные: идентификатор компонента и путь к файлу скрипта, для этого изменим наш метод excecuteComponent следующим образом

function executeComponent(){
$this->arResult = [
'COMPONENT_ID' => $this->componentId()
,'SCRIPT_PATH' => $_SERVER['SCRIPT_NAME']
];
$this->includeComponentTemplate();
}

в примере выше, мы предполагаем, что компонент вызывается в файле, который является точкой входа для http-запроса (например, /index.php), если компонент расположен в другом месте, например в шаблоне сайта, то для значения SCRIPT_PATH необходимо задать соответствующий путь.

Перейдем к шаблону компонента, там выполним аякс-запрос сразу при загрузке страницы, передав в качестве параметров запроса идентификатор компонента componentId и закодированный в Base64 путь к файлу скрипта scriptCast, при этом не забудем подключить аякс-библиотеку битрикса CJSCore::Init(['ajax']);, например в файле component_epilog.php

<?php
if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
?><script>
BX.ajax.runComponentAction(
'ajaxTest'
,'exampleRequest'
,{
mode: 'class'
,data: {
componentId: <?=$arResult['COMPONENT_ID']?>
,scriptCast: '<?=base64_encode($arResult['SCRIPT_PATH'])?>'
}
})
.then(function(response){
if(response.status === 'success'){
/* запрос выполнен успешно */
}
});
</script>

Чтобы обработать этот запрос и при этом получить исходный массив $arParams изменим метод exampleRequestAction в классе компонента следующим образом:

function exampleRequestAction($componentId,$scriptCast){
if(($scriptPath = base64_decode($scriptCast))
&& ($arComponentData = self::componentData(
$componentId
,$scriptPath
))
){
/* полученный $arParams */
var_dump($arComponentData['DATA']['PARAMS']);
}
return [
/* ответ на аякс-запрос */
];
}

На этом все, еще раз ссылка на исходники: https://gist.github.com/pgooood/cd5169265e5abf4af02859d075dafe5d

--

--