Монета -> Журнал операций). * * Как файл попадает в админку: пункт подменю в `install/module.xml` (`podmenu_action` = `dir.moneta`) * ведёт на `admin.php?path=modules.dir.moneta`. `admpanel/admin.php` ядра режет путь по точкам * и подключает `admpanel/modules/admin_dir.php`, а тот — `modules/<третья часть пути>/admpanel/ * admin_<третья часть пути>.php`, то есть этот файл (имя файла менять нельзя). Перед этим ядро * заполняет глобальные `$TitlePage` и `$select_name` (заголовок и пункты подменю), после — вызывает * глобальную функцию `actionStart()`: имя тоже задаёт ядро (`$loader_function`). Так же в 6.7 и 7.0. * * Подпись запроса и код проверки целостности в журнал не пишутся. */ require_once dirname(__DIR__) . '/class/autoload.php'; use MonetaPhpShop\callback\CallbackEvent; use MonetaPhpShop\host\Database; use MonetaPhpShop\host\HostCharset; use MonetaPhpShop\host\OperationRepository; use MonetaPhpShop\protocol\TransactionId; function actionStart() { global $PHPShopInterface, $TitlePage, $select_name; // На страницу отдаём только 500 последних записей, новые сверху. Постраничный вывод, поиск // и сортировку делает DataTables, которым ядро оборачивает таблицу админки (admpanel/js/phpshop.js), // — в браузере, по уже выведенным строкам; без лимита страница росла бы вместе с журналом. // А он не чистится и растёт быстро: на каждое уведомление строка, на заказ их минимум две // (Check URL и Pay URL), проверочный запрос MONETA.RU может прислать несколько раз, а Pay URL // после ответа FAIL повторяет до суток. Старые записи остаются в таблице, на экран не выводятся. $limit = 500; $escape = static function (string $value): string { return htmlspecialchars($value, ENT_QUOTES, 'Windows-1251'); }; // На магазине с базой utf-8 ядро перекодирует заголовки setCaption(), но не ячейки setRow() // и не разметку панели — тексты модуля перекодируются здесь. Введённое в поиск и строки // журнала (ASCII) уже в кодировке страницы. $page = static function (string $text): string { return HostCharset::toPage($text); }; // Фильтр по заказу: номер (`33-57`) или целиком MNT_TRANSACTION_ID из кабинета MONETA.RU. // Ищет по всему журналу, а не только по выведенным строкам, как поиск DataTables. $input = isset($_GET['moneta_order']) && is_string($_GET['moneta_order']) ? trim($_GET['moneta_order']) : ''; $orderUid = $input !== '' ? TransactionId::orderUidFromInput($input) : null; $PHPShopInterface->checkbox_action = false; $PHPShopInterface->setActionPanel($TitlePage, $select_name, false); // Под панелью с заголовком, вне форм ядра: Compile() выводит actionPanel перед таблицей. $PHPShopInterface->actionPanel .= '
' . '' . ' ' . ' ' . ($input !== '' ? 'Сбросить' : '') . ($input !== '' && $orderUid === null ? ' Номер заказа не распознан — показан весь журнал' : '') . '
', ); $PHPShopInterface->setCaption( ['Дата', '12%'], ['Заказ', '8%'], ['Запрос', '8%'], ['Событие', '30%'], ['Ответ', '6%'], ['Сумма', '8%'], ['Номер операции', '8%'], ['Идентификатор заказа', '20%'] ); $rows = (new OperationRepository(Database::fromHost()))->latest($limit, $orderUid); foreach ($rows as $row) { $orderId = (int) $row['order_id']; $order = $orderId > 0 ? ['name' => $escape((string) $row['order_uid']), 'link' => '?path=order&id=' . $orderId] : $page('—'); $command = (string) $row['command'] === 'CHECK' ? 'Проверка' : 'Оплата'; if ((string) $row['test_mode'] === '1') { $command .= ' (тест)'; } $resultCode = $row['result_code'] === null ? null : (int) $row['result_code']; $icon = CallbackEvent::iconHtml((string) $row['event'], $resultCode); $event = ($icon === '' ? '' : $icon . ' ') . $escape(CallbackEvent::label((string) $row['event'])); $PHPShopInterface->setRow( PHPShopDate::get((int) $row['created_at'], true), $order, $page($command), $page($event), $row['result_code'] === null ? 'FAIL' : (string) $row['result_code'], ['name' => $escape((string) $row['amount']), 'align' => 'right'], $escape((string) $row['operation_id']), $escape((string) $row['transaction_id']) ); } $PHPShopInterface->Compile(); }