Виджеты в админке или как добавить свою кнопку
В этой заметке хочу поделится вариантом того, как можно добавить кнопку в админку для выполнения какого-либо действия по запросу администратора. Для этого не потребуется ExtJS и в целом, в зависимости от задачи, можно будет обойтись минимумом кода. В процессе мы создадим виджет, подключим очереди и Server Sent Events.
Общаяя задача:
Выполнять полную синхронизацию каталога товаров с Битрикс24 по нажатию на кнопки.
Решение будет иметь 3 уровня сложности. Для первого уровня предположим, что каталог небольшой и скрипт синхронизации будет работать недолго, до 30 секунд.
Создадим виджет.
Для этого переходим в «Панели управления»

Выбираем вкладку «Виджеты» и жмём кнопку «Создать»

Указываем название (оно будет отображаться в шапке), тип виджета — файл, в содержимом указываем путь к файлу виджета.

Затем сохраняем виджет и переходим обратно в «Панели управления» и открываем панель, в которую хотим добавить наш виджет, на редактирование

Добавляем наш виджет и сохраняем изменения

Чтобы виджет начал работать создадим файл core/elements/widgets/sync_bitrix.php со следующим содержимым
Название класса может быть любым, главное, чтобы он наследовал modDashboardWidgetInterface и реализовывал метод render(). Как видите реализация крайне проста: я просто получаю сервис pdoTools и обрабатываю с его помощью чанк widgets/sync_bitrix.tpl. Код чанка такой
Я, конечно же, использую SendIt для отправки запроса на сервер, вы можете написать свою реализацию. Если же будете использовать SendIt, то убедитесь, что у вас установлена версия не ниже 2.1.7. Пресет sync_bitrix выглядит так:
Также нужно подключить JavaScript компонента SendIt, скрипты обработки ответа и ещё скрипт для работы с SSE, который понадобится если вы дойдёте до третьего уровня сложности. Подключать скрипты можно в чанке, но, поскольку для моего решение скрипты нужны на всех страницах админки, я подключаю их в плагине
Таким образом, при нажатии на кнопку «Выполнить синхронизацию» будет отправлен запрос на сервер, который запустит сниппет snippets/sync_bitrix.php. В сниппете у меня вот такой код
Тут мы подключаем класс SyncBitrix и вызываем переданный в пресете метод addToQueueSync. Однако, вы можете сделать проще и написать код синхронизации прямо в сниппете без подключения классов. На этом первый уровень сложности заканчивается.
Второй уровень сложности подразумевает, что синхронизация длится дольше 30 секунд. Можно было бы добавить лимит и оффсет и отправлять запросы на сервер пока оффсет не превысит общее количество товаров, но у меня уже был код синхронизации, который запускался по крону, а так же был код для работы с очередями, поэтому я решил ничего не переписывать, а использовать, что есть.
Всё, что мы делали до этого, остаётся без изменений, но вместо того, чтобы сразу запускать синхронизацию, мы будем добавлять задачу в очередь в методе addToQueueSync
Затем нужно добавить задание в планировщик, в котором мы будем читать данные из очереди раз в минуту. Если в очереди есть задание на синхронизацию, будет вызван метод syncSiteProductsWithBitrix класса CustomServices\SyncBitrix, потому что именно их мы передавали в качестве сообщения очереди.
На этом можно было бы остановиться, но у нас нет никаких ограничений на количество нажатий на кнопку, т.е. нервный админ может заспамить очередь. Также у нас нет уведомления о том, что синхронизация завершена. Если для ограничения количества нажатий, можно просто блокировать кнопку на N минут, то вот чтобы сообщить пользователю об окончании синхронизации, нам понадобятся Server Sent Events. Тут и начинается третий уровень сложности.
После окончания синхронизации, мы должны добавить сообщение в очередь, при этом имя очереди равно session_id текущего пользователя, его мы так же передавали в сообщении с задачей на запуск синхронизации.
В результате виджет будет выглядеть так

В итоге, после нажатия на кнопку в очередь 'CustomServices' добавляется сообщение с указанием класса и метода, который следует вызвать, а так же с session_id пользователя инициировавшего синхронизацию. После обработки этого сообщения, будет запущена синхронизация, по завершению которой в другую очередь будет добавлено сообщение об окончании синхронизации. Это сообщение будет показано тому, кто запустил синхронизацию вне зависимости от того, на какой странице админки он будет находится. А находиться админ может где угодно, так как после нажатия на кнопку 'Выполнить синхронизацию' админка не блокируется и можно выполнять другие задачи.
Весь код можно найти в репозитории
Спасибо за внимание!
Общаяя задача:
Выполнять полную синхронизацию каталога товаров с Битрикс24 по нажатию на кнопки.
Решение будет иметь 3 уровня сложности. Для первого уровня предположим, что каталог небольшой и скрипт синхронизации будет работать недолго, до 30 секунд.
Создадим виджет.
Для этого переходим в «Панели управления»

Выбираем вкладку «Виджеты» и жмём кнопку «Создать»

Указываем название (оно будет отображаться в шапке), тип виджета — файл, в содержимом указываем путь к файлу виджета.

Затем сохраняем виджет и переходим обратно в «Панели управления» и открываем панель, в которую хотим добавить наш виджет, на редактирование

Добавляем наш виджет и сохраняем изменения

Чтобы виджет начал работать создадим файл core/elements/widgets/sync_bitrix.php со следующим содержимым
<?php
class modDashboardWidgetSyncBitrix extends modDashboardWidgetInterface
{
public function render()
{
$pdoTools = $this->modx->getService('pdoTools');
return $pdoTools->getChunk('@FILE chunks/widgets/sync_bitrix.tpl', []);
}
}
return 'modDashboardWidgetSyncBitrix';
Название класса может быть любым, главное, чтобы он наследовал modDashboardWidgetInterface и реализовывал метод render(). Как видите реализация крайне проста: я просто получаю сервис pdoTools и обрабатываю с его помощью чанк widgets/sync_bitrix.tpl. Код чанка такой
<style>
[data-si-preset="sync_bitrix"]:disabled{
opacity: 0.5;
pointer-events: none;
}
</style>
<form action="" data-si-form>
<button type="button" data-si-event="click" data-si-preset="sync_bitrix"
class="x-btn x-btn-small x-btn-icon-small-left primary-button x-btn-noicon">
Выполнить синхронизацию
</button>
</form>
Я, конечно же, использую SendIt для отправки запроса на сервер, вы можете написать свою реализацию. Если же будете использовать SendIt, то убедитесь, что у вас установлена версия не ниже 2.1.7. Пресет sync_bitrix выглядит так:
<?php
return [
'sync_bitrix' => [
'hooks' => '',
'method' => 'addToQueueSync',
'snippet' => '@FILE snippets/sync_bitrix.php',
]
];
Также нужно подключить JavaScript компонента SendIt, скрипты обработки ответа и ещё скрипт для работы с SSE, который понадобится если вы дойдёте до третьего уровня сложности. Подключать скрипты можно в чанке, но, поскольку для моего решение скрипты нужны на всех страницах админки, я подключаю их в плагине
<?php
/**
* @var modX $modx
*/
require_once MODX_CORE_PATH . 'services/vendor/autoload.php';
require_once MODX_CORE_PATH . 'components/sendit/services/sendit.class.php';
switch ($modx->event->name) {
case 'OnManagerPageInit':
$jsConfigPath = $modx->getOption('si_js_config_path', '', './sendit.inc.js');
$cookies = !empty($_COOKIE['SendIt']) ? json_decode($_COOKIE['SendIt'], 1) : [];
$data = [
'sitoken' => md5($_SERVER['REMOTE_ADDR'] . time()),
'sitrusted' => '0',
'sijsconfigpath' => $jsConfigPath
];
SendIt::setSession($modx, [
'sitoken' => $data['sitoken'],
'sendingLimits' => []
]);
$data = array_merge($cookies, $data);
setcookie('SendIt', json_encode($data), 0, '/');
$modx->regClientStartupHTMLBlock(
'
<script type="module" src="/assets/project_files/js/mgr/sse.js"></script>
'
);
$modx->regClientStartupHTMLBlock(
'
<script type="module" src="/assets/components/sendit/js/web/sendit.js"></script>
'
);
$modx->regClientStartupHTMLBlock(
'
<script type="module" src="/assets/project_files/js/mgr/admin.js"></script>
'
);
break;
}
Таким образом, при нажатии на кнопку «Выполнить синхронизацию» будет отправлен запрос на сервер, который запустит сниппет snippets/sync_bitrix.php. В сниппете у меня вот такой код
<?php
use CustomServices\SyncBitrix;
require_once MODX_CORE_PATH . 'services/vendor/autoload.php';
/**
* @var modX $modx
* @var array $scriptProperties
* @var SendIt $SendIt
* @var string $method
*/
$ManageUsers = new SyncBitrix($modx);
$method = $scriptProperties['method'];
if(!method_exists($ManageUsers, $method)){
return $SendIt->error('Метод '.$method.' не найден', []);
}
$result = $ManageUsers->$method($_POST, $scriptProperties);
if($SendIt){
if($result['success']){
return $SendIt->success($result['message'], $result['data']);
}else{
return $SendIt->error($result['message'], $result['data']);
}
}
return $result;
Тут мы подключаем класс SyncBitrix и вызываем переданный в пресете метод addToQueueSync. Однако, вы можете сделать проще и написать код синхронизации прямо в сниппете без подключения классов. На этом первый уровень сложности заканчивается.
Второй уровень сложности подразумевает, что синхронизация длится дольше 30 секунд. Можно было бы добавить лимит и оффсет и отправлять запросы на сервер пока оффсет не превысит общее количество товаров, но у меня уже был код синхронизации, который запускался по крону, а так же был код для работы с очередями, поэтому я решил ничего не переписывать, а использовать, что есть.
Всё, что мы делали до этого, остаётся без изменений, но вместо того, чтобы сразу запускать синхронизацию, мы будем добавлять задачу в очередь в методе addToQueueSync
public function addToQueueSync(array $data, array $properties = []): array
{
$queueData = [
'className' => 'CustomServices\SyncBitrix',
'method' => 'syncSiteProductsWithBitrix',
'session_id' => session_id(),
];
$this->qm->addToQueue('CustomServices', $queueData);
return ['success' => true, 'message' => 'Синхронизация начата!', 'data' => []];
}
Затем нужно добавить задание в планировщик, в котором мы будем читать данные из очереди раз в минуту. Если в очереди есть задание на синхронизацию, будет вызван метод syncSiteProductsWithBitrix класса CustomServices\SyncBitrix, потому что именно их мы передавали в качестве сообщения очереди.
На этом можно было бы остановиться, но у нас нет никаких ограничений на количество нажатий на кнопку, т.е. нервный админ может заспамить очередь. Также у нас нет уведомления о том, что синхронизация завершена. Если для ограничения количества нажатий, можно просто блокировать кнопку на N минут, то вот чтобы сообщить пользователю об окончании синхронизации, нам понадобятся Server Sent Events. Тут и начинается третий уровень сложности.
После окончания синхронизации, мы должны добавить сообщение в очередь, при этом имя очереди равно session_id текущего пользователя, его мы так же передавали в сообщении с задачей на запуск синхронизации.
public function syncSiteProductsWithBitrix(?array $data = []): array
{
// код синхронизации
$queueData = [
'eventName' => 'sync:bitrix:finished',
'message' => 'Синхронизация данных курсов с Б24 завершена!',
];
$this->qm->addToQueue($data['session_id'], $queueData);
}
Для работы с этим типом очередей, т.е. с персональными уведомлениями для конкретного пользовтаеля, создадим новый обработчик<?php
/**
* @var \modX $modx
*/
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
echo "retry: 5000" . "\n\n";
$basePath = dirname(__FILE__, 4);
$path = $basePath . '/core/services/vendor/autoload.php';
if (!file_exists($path)) {
echo 'data: {"error":"File `autoload.php` not found"}' . "\n\n";
}
define('MODX_API_MODE', true);
require_once $basePath . '/index.php';
require_once $path;
$modx->getService('error', 'error.modError');
$modx->setLogLevel(\modX::LOG_LEVEL_ERROR);
$qm = new CustomServices\QueueManager($modx);
$token = $_COOKIE['PHPSESSID'] ?? '';
/**
* @param array $messages
*/
function sendMessages(array $messages): void
{
global $modx;
foreach ($messages as $id => $message) {
echo "data: " . $message . "\n\n";
echo "id: " . $id . "\n\n";
ob_flush();
flush();
}
}
if ($messages = $qm->getMessages($token, true)) {
sendMessages($messages);
}
В результате виджет будет выглядеть так

В итоге, после нажатия на кнопку в очередь 'CustomServices' добавляется сообщение с указанием класса и метода, который следует вызвать, а так же с session_id пользователя инициировавшего синхронизацию. После обработки этого сообщения, будет запущена синхронизация, по завершению которой в другую очередь будет добавлено сообщение об окончании синхронизации. Это сообщение будет показано тому, кто запустил синхронизацию вне зависимости от того, на какой странице админки он будет находится. А находиться админ может где угодно, так как после нажатия на кнопку 'Выполнить синхронизацию' админка не блокируется и можно выполнять другие задачи.
Весь код можно найти в репозитории
Спасибо за внимание!
Поблагодарить автора
Отправить деньги
Комментарии: 4
Очень хороший комплексный материал. Низкий поклон. Давненько такого не было.
Есть скрины шагов, но нет итогового скрина, что получилось. С демо картинкой веселее.
Добавил)
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.