Виджеты в админке или как добавить свою кнопку
        В этой заметке хочу поделится вариантом того, как можно добавить кнопку в админку для выполнения какого-либо действия по запросу администратора. Для этого не потребуется 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 пользователя инициировавшего синхронизацию. После обработки этого сообщения, будет запущена синхронизация, по завершению которой в другую очередь будет добавлено сообщение об окончании синхронизации. Это сообщение будет показано тому, кто запустил синхронизацию вне зависимости от того, на какой странице админки он будет находится. А находиться админ может где угодно, так как после нажатия на кнопку 'Выполнить синхронизацию' админка не блокируется и можно выполнять другие задачи.
Весь код можно найти в репозитории
Спасибо за внимание!
            
                Поблагодарить автора            
            
                 Отправить деньги            
        
        
            Комментарии: 6
                Очень хороший комплексный материал. Низкий поклон. Давненько такого не было.            
                    
                Есть скрины шагов, но нет итогового скрина, что получилось. С демо картинкой веселее.            
                    
                Добавил)            
                    
                Было бы классно если бы еще были уроки по синхронизации с Битрикс24.
Т.к. в коде выше я так понял нет примеров самой синхронизации.
На modstore есть 2 плагина платных, но по ним есть вопросы — в комментариях видно что не все позволяет синхронизировать (последние комментарии от 2024 года висят без ответов).
Т.к. Битрикс24 одна из самых популярных и функциональных CRM сегодня.
                    Т.к. в коде выше я так понял нет примеров самой синхронизации.
На modstore есть 2 плагина платных, но по ним есть вопросы — в комментариях видно что не все позволяет синхронизировать (последние комментарии от 2024 года висят без ответов).
Т.к. Битрикс24 одна из самых популярных и функциональных CRM сегодня.
                Уроки по синхронизации с Битрикс24 имеют весьма опосредованное отношение к Modx. Да и основная сложность при работе с Битрикс24 это структура хранения данных в самой CRM. Например банковские реквизиты компании это отдельная сущность, т.е. сначала нужно создать компанию, потом создать реквизит. А перед этим ещё найти контакт по лиду или создать его.            
                    
                            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.