From 9a7e7c6bc0651bd961d61354d80ca3ff293bea68 Mon Sep 17 00:00:00 2001 From: Tadas Labutis Date: Tue, 5 May 2026 13:56:38 +0300 Subject: [PATCH 1/3] DGS-427 Improve logs section: collapse request/response into modal viewer Long DPD API request URLs rendered inline in the Logs list table caused the table to overflow horizontally and squeeze other columns off-screen. Adopted the same modal pattern already used in SaferPay, Square, Klarna and Mollie modules: request/response cells now render a "View" button that opens a modal loaded via AJAX with the formatted payload. Pretty-prints JSON when applicable; otherwise splits URL query strings into one decoded key = value per line so long DPD shipment URLs become readable. --- CHANGELOG.md | 3 +- .../admin/AdminDPDBalticsLogsController.php | 104 +++++++++++++--- views/css/admin/logs_tab.css | 111 ++++++++++++++++++ views/js/admin/log.js | 90 ++++++++++++++ views/templates/admin/logs/index.php | 30 +++++ views/templates/admin/logs/log_modal.tpl | 40 +++++++ 6 files changed, 361 insertions(+), 17 deletions(-) create mode 100644 views/css/admin/logs_tab.css create mode 100644 views/js/admin/log.js create mode 100644 views/templates/admin/logs/index.php create mode 100644 views/templates/admin/logs/log_modal.tpl diff --git a/CHANGELOG.md b/CHANGELOG.md index f2a49027..0a5d3123 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -185,4 +185,5 @@ ## [3.3.0] - Added PrestaShop 9 compatibility - Fixed issue with price rule "All" -- Fixed other minor issues \ No newline at end of file +- Fixed other minor issues +- Improved Logs section by collapsing request/response into a modal viewer to fix table overflow on long URLs \ No newline at end of file diff --git a/controllers/admin/AdminDPDBalticsLogsController.php b/controllers/admin/AdminDPDBalticsLogsController.php index 1924b2aa..aedece24 100644 --- a/controllers/admin/AdminDPDBalticsLogsController.php +++ b/controllers/admin/AdminDPDBalticsLogsController.php @@ -19,7 +19,7 @@ */ use Invertus\dpdBaltics\Controller\AbstractAdminController; -use Invertus\dpdBaltics\Logger\Logger; +use Invertus\dpdBaltics\Infrastructure\Bootstrap\ModuleTabs; require_once dirname(__DIR__).'/../vendor/autoload.php'; @@ -29,6 +29,11 @@ class AdminDPDBalticsLogsController extends AbstractAdminController { + const FILE_NAME = 'AdminDPDBalticsLogsController'; + + const LOG_INFORMATION_TYPE_REQUEST = 'request'; + const LOG_INFORMATION_TYPE_RESPONSE = 'response'; + public function __construct() { $this->className = 'DPDProduct'; @@ -46,6 +51,50 @@ public function initToolbar() unset($this->toolbar_btn['new']); } + public function setMedia($isNewTheme = false) + { + parent::setMedia($isNewTheme); + + Media::addJsDef([ + 'dpdbaltics' => [ + 'logsUrl' => $this->context->link->getAdminLink(ModuleTabs::ADMIN_LOGS_CONTROLLER), + ], + ]); + + $this->addCSS($this->module->getPathUri() . 'views/css/admin/logs_tab.css'); + $this->addJS($this->module->getPathUri() . 'views/js/admin/log.js'); + } + + public function printRequestButton($request, $data) + { + return $this->getDisplayButton($data['id_dpd_log'], $request, self::LOG_INFORMATION_TYPE_REQUEST); + } + + public function printResponseButton($response, $data) + { + return $this->getDisplayButton($data['id_dpd_log'], $response, self::LOG_INFORMATION_TYPE_RESPONSE); + } + + public function displayAjaxGetLog() + { + $logId = (int) Tools::getValue('log_id'); + $log = new DPDLog($logId); + + if (!Validate::isLoadedObject($log)) { + $this->ajaxDie(json_encode([ + 'error' => true, + 'message' => $this->module->l('No log information found.', self::FILE_NAME), + ])); + } + + $this->ajaxDie(json_encode([ + 'error' => false, + 'log' => [ + self::LOG_INFORMATION_TYPE_REQUEST => $log->request, + self::LOG_INFORMATION_TYPE_RESPONSE => $log->response, + ], + ])); + } private function initList() { @@ -53,30 +102,53 @@ private function initList() $this->fields_list = [ 'id_dpd_log' => [ - 'title' => $this->module->l('ID'), - 'type' => 'text', - 'havingFilter' => true + 'title' => $this->module->l('ID', self::FILE_NAME), + 'align' => 'text-center', + 'class' => 'fixed-width-xs', + 'havingFilter' => true, ], 'request' => [ - 'title' => $this->module->l('request'), - 'type' => 'text', - 'havingFilter' => true + 'title' => $this->module->l('Request', self::FILE_NAME), + 'align' => 'text-center', + 'callback' => 'printRequestButton', + 'orderby' => false, + 'search' => false, + 'remove_onclick' => true, ], 'response' => [ - 'title' => $this->module->l('response'), - 'type' => 'text', - 'havingFilter' => true + 'title' => $this->module->l('Response', self::FILE_NAME), + 'align' => 'text-center', + 'callback' => 'printResponseButton', + 'orderby' => false, + 'search' => false, + 'remove_onclick' => true, ], 'status' => [ - 'title' => $this->module->l('status'), - 'type' => 'text', - 'havingFilter' => true + 'title' => $this->module->l('Status', self::FILE_NAME), + 'havingFilter' => true, ], 'date_add' => [ - 'title' => $this->module->l('Created date'), + 'title' => $this->module->l('Created date', self::FILE_NAME), + 'align' => 'right', 'type' => 'datetime', - 'havingFilter' => true - ] + 'havingFilter' => true, + ], ]; } + + private function getDisplayButton($logId, $data, $logInformationType) + { + if (empty($data)) { + return '--'; + } + + $this->context->smarty->assign([ + 'log_id' => $logId, + 'log_information_type' => $logInformationType, + ]); + + return $this->context->smarty->fetch( + $this->module->getLocalPath() . 'views/templates/admin/logs/log_modal.tpl' + ); + } } diff --git a/views/css/admin/logs_tab.css b/views/css/admin/logs_tab.css new file mode 100644 index 00000000..7be801c8 --- /dev/null +++ b/views/css/admin/logs_tab.css @@ -0,0 +1,111 @@ +/** + * NOTICE OF LICENSE + * + * @author INVERTUS, UAB www.invertus.eu + * @copyright Copyright (c) permanent, INVERTUS, UAB + * @license Addons PrestaShop license limitation + * @see /LICENSE + * + * International Registered Trademark & Property of INVERTUS, UAB + */ +table.dpd_log td { + word-break: break-all; + vertical-align: middle; +} + +.button { + cursor: pointer; +} + +.log-modal-overlay { + transition: opacity 0.2s ease-out; + pointer-events: none; + background: rgba(15, 23, 42, 0.8); + position: fixed; + opacity: 0; + bottom: 0; + right: 0; + left: 0; + top: 0; + z-index: 1040; +} + +.modal.open .log-modal-overlay { + pointer-events: all; + opacity: 0.5; +} + +.log-modal-window { + position: relative; + width: 60%; + margin: 5% auto; + + background: #ffffff; + border-radius: 0.5em; + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); + pointer-events: all; + text-align: left; + max-height: 90vh; + min-height: 50vh; + display: flex; + flex-direction: column; + + overflow: hidden; + z-index: 1050; +} + +.modal.open { + display: block; +} + +.log-modal-title { + color: #111827; + padding: 10px 15px; + border-bottom: solid 1px #e0e0e0; + pointer-events: all; + display: flex; + justify-content: center; + flex-shrink: 0; +} + +.log-modal-content { + padding: 15px; + flex: 1; + overflow: auto; +} + +.log-modal-content-data { + white-space: pre-wrap; + word-break: break-all; + margin: 0; +} + +.log-modal-content-spinner { + min-height: 200px; +} + +.log-modal-content-spinner:not(.hidden) { + display: flex; + justify-content: center; + align-items: center; +} + +.log-modal-content-spinner::after { + content: ""; + width: 40px; + height: 40px; + border: 2px solid #f3f3f3; + border-top: 3px solid #DC0032; + border-radius: 100%; + will-change: transform; + animation: dpd-log-spin 1s infinite linear; +} + +@keyframes dpd-log-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/views/js/admin/log.js b/views/js/admin/log.js new file mode 100644 index 00000000..8a3ea875 --- /dev/null +++ b/views/js/admin/log.js @@ -0,0 +1,90 @@ +/** + * NOTICE OF LICENSE + * + * @author INVERTUS, UAB www.invertus.eu + * @copyright Copyright (c) permanent, INVERTUS, UAB + * @license Addons PrestaShop license limitation + * @see /LICENSE + * + * International Registered Trademark & Property of INVERTUS, UAB + */ +$(document).ready(function () { + $('.log-modal-overlay').on('click', function (event) { + $('.modal.open').removeClass('open'); + event.preventDefault(); + }); + + $('.js-log-button').on('click', function (event) { + var logId = $(this).data('log-id'); + var informationType = $(this).data('information-type'); + + $('#' + $(this).data('target')).addClass('open'); + + var $contentData = $('#log-modal-' + logId + '-' + informationType + ' .log-modal-content-data'); + + if (!$contentData.hasClass('hidden')) { + return; + } + + var $spinner = $('#log-modal-' + logId + '-' + informationType + ' .log-modal-content-spinner'); + $spinner.removeClass('hidden'); + + $.ajax({ + type: 'POST', + url: dpdbaltics.logsUrl, + data: { + ajax: true, + action: 'getLog', + log_id: logId + } + }) + .then(function (response) { return jQuery.parseJSON(response); }) + .then(function (data) { + $spinner.addClass('hidden'); + + if (data.error) { + $contentData.removeClass('hidden').text(data.message || ''); + return; + } + + $('#log-modal-' + logId + '-request .log-modal-content-data').removeClass('hidden').text(formatLogPayload(data.log.request)); + $('#log-modal-' + logId + '-response .log-modal-content-data').removeClass('hidden').text(formatLogPayload(data.log.response)); + }); + }); +}); + +function formatLogPayload(payload) { + if (payload === null || typeof payload === 'undefined' || payload === '') { + return ''; + } + + try { + return JSON.stringify(JSON.parse(payload), null, 2); + } catch (e) { + // not JSON + } + + var queryIndex = payload.indexOf('?'); + if (queryIndex !== -1 && payload.indexOf('&') !== -1) { + var base = payload.substring(0, queryIndex); + var query = payload.substring(queryIndex + 1); + var parts = query.split('&').map(function (part) { + var eq = part.indexOf('='); + if (eq === -1) { + return decodeURIComponentSafe(part); + } + return decodeURIComponentSafe(part.substring(0, eq)) + ' = ' + decodeURIComponentSafe(part.substring(eq + 1)); + }); + return base + '\n\n' + parts.join('\n'); + } + + return payload; +} + +function decodeURIComponentSafe(value) { + try { + return decodeURIComponent(value.replace(/\+/g, ' ')); + } catch (e) { + return value; + } +} diff --git a/views/templates/admin/logs/index.php b/views/templates/admin/logs/index.php new file mode 100644 index 00000000..58b8b473 --- /dev/null +++ b/views/templates/admin/logs/index.php @@ -0,0 +1,30 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + + +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/views/templates/admin/logs/log_modal.tpl b/views/templates/admin/logs/log_modal.tpl new file mode 100644 index 00000000..779589f4 --- /dev/null +++ b/views/templates/admin/logs/log_modal.tpl @@ -0,0 +1,40 @@ +{* +* NOTICE OF LICENSE +* +* @author INVERTUS, UAB www.invertus.eu +* @copyright Copyright (c) permanent, INVERTUS, UAB +* @license Addons PrestaShop license limitation +* @see /LICENSE +* +* International Registered Trademark & Property of INVERTUS, UAB +*} +
+ {l s='View' mod='dpdbaltics'} +
+ + From 5124f10dbfb580586f7a49eea3db95f712c67985 Mon Sep 17 00:00:00 2001 From: Tadas Labutis Date: Tue, 5 May 2026 15:24:15 +0300 Subject: [PATCH 2/3] DGS-427 Restructure logs list and JSON-encode payloads - Reorder log columns to ID | Severity (1-4) | Message | Request | Response | Context | Date with severity badge, message preview and endpoint context callbacks. - Encode logged request as structured JSON (endpoint + params) and mask username/password in stored payload. - Encode logged response as JSON envelope with level and message so the modal renders pretty-printed JSON. - Refine modal layout: wider window, centered title with id prefix, absolute close button and Esc key support. --- .../admin/AdminDPDBalticsLogsController.php | 84 ++++++++++++++++++- src/Logger/Logger.php | 72 +++++++++++++++- views/css/admin/logs_tab.css | 77 +++++++++++++++-- views/js/admin/log.js | 8 +- views/templates/admin/logs/log_modal.tpl | 3 +- 5 files changed, 229 insertions(+), 15 deletions(-) diff --git a/controllers/admin/AdminDPDBalticsLogsController.php b/controllers/admin/AdminDPDBalticsLogsController.php index aedece24..c90754ba 100644 --- a/controllers/admin/AdminDPDBalticsLogsController.php +++ b/controllers/admin/AdminDPDBalticsLogsController.php @@ -75,6 +75,63 @@ public function printResponseButton($response, $data) return $this->getDisplayButton($data['id_dpd_log'], $response, self::LOG_INFORMATION_TYPE_RESPONSE); } + public function printSeverity($severity, $data) + { + $level = strtolower((string) $severity); + $levelMap = [ + 'emergency' => 1, 'alert' => 1, 'critical' => 1, 'error' => 1, + 'warning' => 2, + 'notice' => 3, 'info' => 3, + 'debug' => 4, + ]; + $num = isset($levelMap[$level]) ? $levelMap[$level] : null; + $cssClass = $num ? 'dpd-log-severity dpd-log-severity-' . $num : 'dpd-log-severity'; + $label = $level !== '' ? ucfirst($level) : '--'; + + return sprintf( + '%s', + htmlspecialchars($cssClass, ENT_QUOTES, 'UTF-8'), + $num ? sprintf('%d · %s', $num, htmlspecialchars($label, ENT_QUOTES, 'UTF-8')) : htmlspecialchars($label, ENT_QUOTES, 'UTF-8') + ); + } + + public function printMessage($message, $data) + { + if ($message === null || $message === '') { + return '--'; + } + $value = (string) $message; + $decoded = json_decode($value, true); + if (json_last_error() === JSON_ERROR_NONE && is_array($decoded) && isset($decoded['message'])) { + $value = (string) $decoded['message']; + } + $clean = trim(preg_replace('/\s+/', ' ', strip_tags($value))); + if (function_exists('mb_strlen') && mb_strlen($clean) > 90) { + $clean = mb_substr($clean, 0, 87) . '...'; + } + return htmlspecialchars($clean, ENT_QUOTES, 'UTF-8'); + } + + public function printContext($context, $data) + { + if ($context === null || $context === '') { + return '--'; + } + $endpoint = ''; + $decoded = json_decode((string) $context, true); + if (json_last_error() === JSON_ERROR_NONE && is_array($decoded) && !empty($decoded['endpoint'])) { + $endpoint = (string) $decoded['endpoint']; + } else { + $endpoint = (string) $context; + } + $endpoint = strtok($endpoint, '?'); + $endpoint = $endpoint !== false ? basename($endpoint) : ''; + if ($endpoint === '') { + return '--'; + } + return htmlspecialchars($endpoint, ENT_QUOTES, 'UTF-8'); + } + public function displayAjaxGetLog() { $logId = (int) Tools::getValue('log_id'); @@ -99,6 +156,7 @@ public function displayAjaxGetLog() private function initList() { $this->list_no_link = true; + $this->_select = 'a.status AS severity, a.response AS message, a.request AS context'; $this->fields_list = [ 'id_dpd_log' => [ @@ -107,6 +165,21 @@ private function initList() 'class' => 'fixed-width-xs', 'havingFilter' => true, ], + 'severity' => [ + 'title' => $this->module->l('Severity (1-4)', self::FILE_NAME), + 'align' => 'text-center', + 'callback' => 'printSeverity', + 'search' => false, + 'orderby' => false, + 'remove_onclick' => true, + ], + 'message' => [ + 'title' => $this->module->l('Message', self::FILE_NAME), + 'callback' => 'printMessage', + 'search' => false, + 'orderby' => false, + 'remove_onclick' => true, + ], 'request' => [ 'title' => $this->module->l('Request', self::FILE_NAME), 'align' => 'text-center', @@ -123,12 +196,15 @@ private function initList() 'search' => false, 'remove_onclick' => true, ], - 'status' => [ - 'title' => $this->module->l('Status', self::FILE_NAME), - 'havingFilter' => true, + 'context' => [ + 'title' => $this->module->l('Context', self::FILE_NAME), + 'callback' => 'printContext', + 'search' => false, + 'orderby' => false, + 'remove_onclick' => true, ], 'date_add' => [ - 'title' => $this->module->l('Created date', self::FILE_NAME), + 'title' => $this->module->l('Date', self::FILE_NAME), 'align' => 'right', 'type' => 'datetime', 'havingFilter' => true, diff --git a/src/Logger/Logger.php b/src/Logger/Logger.php index ecaa083a..e4dc9eaa 100644 --- a/src/Logger/Logger.php +++ b/src/Logger/Logger.php @@ -178,10 +178,78 @@ public function log($level, $message, array $context = []): void } $log = new DPDLog(); - $log->response = $message; - $log->request = !empty($context['request']) ? $this->logsService->hideUsernameAndPasswordFromRequest($context['request']) : null; + $log->response = $this->encodeResponse($level, $message, $context); + $log->request = !empty($context['request']) + ? $this->encodeRequest($this->logsService->hideUsernameAndPasswordFromRequest($context['request'])) + : null; $log->status = $level; $log->add(); } + + private function encodeRequest($raw): ?string + { + if ($raw === null || $raw === '') { + return null; + } + + if (is_array($raw)) { + return $this->jsonEncode($raw); + } + + $rawString = (string) $raw; + $parsed = parse_url($rawString); + + if (!$parsed || empty($parsed['scheme']) || empty($parsed['host'])) { + return $this->jsonEncode(['raw' => $rawString]); + } + + $endpoint = $parsed['scheme'] . '://' . $parsed['host']; + if (isset($parsed['path'])) { + $endpoint .= $parsed['path']; + } + + $params = []; + if (!empty($parsed['query'])) { + parse_str($parsed['query'], $params); + if (array_key_exists('password', $params)) { + $params['password'] = '***'; + } + if (array_key_exists('username', $params) && $params['username'] !== '') { + $params['username'] = '***'; + } + } + + return $this->jsonEncode([ + 'endpoint' => $endpoint, + 'params' => $params, + ]); + } + + private function encodeResponse($level, $message, array $context): ?string + { + $payload = [ + 'level' => (string) $level, + ]; + + $decoded = is_string($message) ? json_decode($message, true) : null; + if (json_last_error() === JSON_ERROR_NONE && (is_array($decoded) || is_object($decoded))) { + $payload['body'] = $decoded; + } else { + $payload['message'] = is_scalar($message) || $message === null ? (string) $message : $message; + } + + $extra = $context; + unset($extra['request']); + if (!empty($extra)) { + $payload['context'] = $extra; + } + + return $this->jsonEncode($payload); + } + + private function jsonEncode($value): string + { + return json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } } diff --git a/views/css/admin/logs_tab.css b/views/css/admin/logs_tab.css index 7be801c8..4c41b115 100644 --- a/views/css/admin/logs_tab.css +++ b/views/css/admin/logs_tab.css @@ -9,7 +9,7 @@ * International Registered Trademark & Property of INVERTUS, UAB */ table.dpd_log td { - word-break: break-all; + word-break: break-word; vertical-align: middle; } @@ -17,6 +17,34 @@ table.dpd_log td { cursor: pointer; } +.dpd-log-severity { + display: inline-block; + padding: 3px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 600; + line-height: 1; + color: #ffffff; + background: #6b7280; + white-space: nowrap; +} + +.dpd-log-severity-1 { + background: #dc3545; +} + +.dpd-log-severity-2 { + background: #f0ad4e; +} + +.dpd-log-severity-3 { + background: #0d6efd; +} + +.dpd-log-severity-4 { + background: #6c757d; +} + .log-modal-overlay { transition: opacity 0.2s ease-out; pointer-events: none; @@ -37,8 +65,9 @@ table.dpd_log td { .log-modal-window { position: relative; - width: 60%; - margin: 5% auto; + width: 90%; + max-width: 880px; + margin: 5vh auto; background: #ffffff; border-radius: 0.5em; @@ -59,18 +88,45 @@ table.dpd_log td { } .log-modal-title { + position: relative; color: #111827; - padding: 10px 15px; - border-bottom: solid 1px #e0e0e0; + padding: 14px 48px; + border-bottom: solid 1px #e5e7eb; pointer-events: all; display: flex; + align-items: center; justify-content: center; flex-shrink: 0; } +.log-modal-title-text { + margin: 0; + font-size: 15px; + font-weight: 600; + text-align: center; +} + +.log-modal-close { + position: absolute; + top: 50%; + right: 14px; + transform: translateY(-50%); + background: transparent; + border: 0; + font-size: 24px; + line-height: 1; + color: #6b7280; + padding: 0 6px; + cursor: pointer; +} + +.log-modal-close:hover { + color: #111827; +} + .log-modal-content { - padding: 15px; - flex: 1; + padding: 18px; + flex: 1 1 auto; overflow: auto; } @@ -78,6 +134,13 @@ table.dpd_log td { white-space: pre-wrap; word-break: break-all; margin: 0; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 13px; + color: #111827; + background: #f9fafb; + border: 1px solid #e5e7eb; + border-radius: 6px; + padding: 14px 16px; } .log-modal-content-spinner { diff --git a/views/js/admin/log.js b/views/js/admin/log.js index 8a3ea875..285108ef 100644 --- a/views/js/admin/log.js +++ b/views/js/admin/log.js @@ -9,11 +9,17 @@ * International Registered Trademark & Property of INVERTUS, UAB */ $(document).ready(function () { - $('.log-modal-overlay').on('click', function (event) { + $(document).on('click', '.log-modal-overlay, .js-log-modal-close', function (event) { $('.modal.open').removeClass('open'); event.preventDefault(); }); + $(document).on('keydown', function (event) { + if (event.key === 'Escape') { + $('.modal.open').removeClass('open'); + } + }); + $('.js-log-button').on('click', function (event) { var logId = $(this).data('log-id'); var informationType = $(this).data('information-type'); diff --git a/views/templates/admin/logs/log_modal.tpl b/views/templates/admin/logs/log_modal.tpl index 779589f4..f9b392af 100644 --- a/views/templates/admin/logs/log_modal.tpl +++ b/views/templates/admin/logs/log_modal.tpl @@ -23,13 +23,14 @@
-

+

{if $log_information_type === 'request'} {$log_id|escape:'htmlall':'UTF-8'}: {l s='Request data' mod='dpdbaltics'} {elseif $log_information_type === 'response'} {$log_id|escape:'htmlall':'UTF-8'}: {l s='Response data' mod='dpdbaltics'} {/if}

+
From be643c3c8b3907ea97daefe73717e310c379cc23 Mon Sep 17 00:00:00 2001 From: Tadas Labutis Date: Tue, 5 May 2026 15:44:29 +0300 Subject: [PATCH 3/3] DGS-427 Override logs CSV export with diagnostic header - Replace PrestaShop default exporter with a clean fputcsv stream so JSON request/response columns are not HTML-mangled. - Prepend store info, module configuration and PrestaShop settings so support can read environment context without follow-up. - Derive Severity, Message and Context columns directly from the JSON payloads stored in the log table. --- .../admin/AdminDPDBalticsLogsController.php | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/controllers/admin/AdminDPDBalticsLogsController.php b/controllers/admin/AdminDPDBalticsLogsController.php index c90754ba..e289518f 100644 --- a/controllers/admin/AdminDPDBalticsLogsController.php +++ b/controllers/admin/AdminDPDBalticsLogsController.php @@ -18,6 +18,7 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ +use Invertus\dpdBaltics\Config\Config; use Invertus\dpdBaltics\Controller\AbstractAdminController; use Invertus\dpdBaltics\Infrastructure\Bootstrap\ModuleTabs; @@ -132,6 +133,106 @@ public function printContext($context, $data) return htmlspecialchars($endpoint, ENT_QUOTES, 'UTF-8'); } + public function processExport($textDelimiter = '"') + { + if (ob_get_level() && ob_get_length() > 0) { + ob_clean(); + } + + $fileName = sprintf('dpdbaltics_logs_%s.csv', date('Y-m-d_His')); + header('Content-Type: text/csv; charset=utf-8'); + header('Content-Disposition: attachment; filename=' . $fileName); + header('Cache-Control: no-store, no-cache'); + + $fd = fopen('php://output', 'wb'); + + $storeInfo = [ + 'PrestaShop Version' => _PS_VERSION_, + 'PHP Version' => phpversion(), + 'Module Version' => $this->module->version, + 'MySQL Version' => Db::getInstance()->getVersion(), + 'Shop URL' => $this->context->shop ? $this->context->shop->getBaseURL(true) : '', + 'Shop Name' => Configuration::get('PS_SHOP_NAME'), + ]; + + $moduleConfigurations = [ + 'Test mode' => Configuration::get(Config::SHIPMENT_TEST_MODE) ? 'Yes' : 'No', + 'API country' => Configuration::get(Config::WEB_SERVICE_COUNTRY), + 'Track logs' => Configuration::get(Config::TRACK_LOGS) ? 'Yes' : 'No', + ]; + + $psSettings = [ + 'Default country' => Configuration::get('PS_COUNTRY_DEFAULT'), + 'Default currency' => Configuration::get('PS_CURRENCY_DEFAULT'), + 'Default language' => Configuration::get('PS_LANG_DEFAULT'), + 'Round mode' => Configuration::get('PS_PRICE_ROUND_MODE'), + 'Round type' => Configuration::get('PS_ROUND_TYPE'), + 'PHP memory limit' => ini_get('memory_limit'), + ]; + + fputcsv($fd, array_keys($storeInfo), ';', $textDelimiter); + fputcsv($fd, array_values($storeInfo), ';', $textDelimiter); + fputcsv($fd, [], ';', $textDelimiter); + + $moduleConfigInfo = "**Module configurations:**\n"; + foreach ($moduleConfigurations as $key => $value) { + $moduleConfigInfo .= '- ' . $key . ': ' . $value . "\n"; + } + + $psSettingsInfo = "**Prestashop settings:**\n"; + foreach ($psSettings as $key => $value) { + $psSettingsInfo .= '- ' . $key . ': ' . $value . "\n"; + } + + fputcsv($fd, [$moduleConfigInfo], ';', $textDelimiter); + fputcsv($fd, [$psSettingsInfo], ';', $textDelimiter); + fputcsv($fd, [], ';', $textDelimiter); + + fputcsv($fd, [ + $this->module->l('ID', self::FILE_NAME), + $this->module->l('Severity', self::FILE_NAME), + $this->module->l('Message', self::FILE_NAME), + $this->module->l('Request', self::FILE_NAME), + $this->module->l('Response', self::FILE_NAME), + $this->module->l('Context', self::FILE_NAME), + $this->module->l('Date', self::FILE_NAME), + ], ';', $textDelimiter); + + $rows = Db::getInstance()->executeS( + 'SELECT id_dpd_log, request, response, status, date_add FROM `' . _DB_PREFIX_ . pSQL($this->table) . '` ORDER BY id_dpd_log ASC' + ); + if ($rows === false) { + $rows = []; + } + + foreach ($rows as $row) { + $message = (string) ($row['response'] ?? ''); + $decodedRes = $message !== '' ? json_decode($message, true) : null; + if (json_last_error() === JSON_ERROR_NONE && is_array($decodedRes) && isset($decodedRes['message'])) { + $message = (string) $decodedRes['message']; + } + + $endpoint = ''; + $decodedReq = isset($row['request']) ? json_decode((string) $row['request'], true) : null; + if (json_last_error() === JSON_ERROR_NONE && is_array($decodedReq) && !empty($decodedReq['endpoint'])) { + $endpoint = basename(strtok((string) $decodedReq['endpoint'], '?')); + } + + fputcsv($fd, [ + $row['id_dpd_log'], + $row['status'], + $message, + $row['request'], + $row['response'], + $endpoint, + $row['date_add'], + ], ';', $textDelimiter); + } + + fclose($fd); + exit; + } + public function displayAjaxGetLog() { $logId = (int) Tools::getValue('log_id');