From 501f6c4b078dd31bfb2ac02f9d65bbd91971b8ec Mon Sep 17 00:00:00 2001 From: tiamak Date: Tue, 28 Apr 2026 07:54:10 +0000 Subject: [PATCH] Harden admin AJAX and improve mega menu links --- advancedmegamenu.php | 16 +++++++++ src/Classes/SpriteGenerator.php | 12 ++++++- views/css/megamenu.css | 55 +++++++++++++++++++++++-------- views/js/admin.js | 13 ++++---- views/templates/hook/megamenu.tpl | 54 +++++++++++++++--------------- 5 files changed, 102 insertions(+), 48 deletions(-) diff --git a/advancedmegamenu.php b/advancedmegamenu.php index fee21e1..b4817dc 100644 --- a/advancedmegamenu.php +++ b/advancedmegamenu.php @@ -387,6 +387,8 @@ class AdvancedMegaMenu extends Module implements WidgetInterface private function ajaxRouter(): void { + $this->assertAdminToken(); + $action = (string) Tools::getValue('action'); if ($action === 'searchProducts') { @@ -401,6 +403,20 @@ class AdvancedMegaMenu extends Module implements WidgetInterface exit(json_encode(['error' => true, 'message' => 'Unknown action'])); } + private function assertAdminToken(): void + { + $token = (string) Tools::getValue('token'); + $expectedToken = Tools::getAdminTokenLite('AdminModules'); + + if (!hash_equals($expectedToken, $token)) { + header('Content-Type: application/json', true, 403); + exit(json_encode([ + 'error' => true, + 'message' => $this->trans('Invalid admin token.', [], 'Modules.Advancedmegamenu.Admin'), + ])); + } + } + private function ajaxSearchProducts(): void { $term = pSQL((string) Tools::getValue('q')); diff --git a/src/Classes/SpriteGenerator.php b/src/Classes/SpriteGenerator.php index 6469e51..e81c951 100644 --- a/src/Classes/SpriteGenerator.php +++ b/src/Classes/SpriteGenerator.php @@ -101,9 +101,19 @@ class SpriteGenerator ); } - imagewebp($sprite, $spritePath, 85); + $spriteWritten = imagewebp($sprite, $spritePath, 85); imagedestroy($sprite); + if (!$spriteWritten || !is_file($spritePath)) { + foreach ($images as $iconImage) { + imagedestroy($iconImage['resource']); + } + + $this->cleanup($spritePath, $cssPath); + + return; + } + $version = is_file($spritePath) ? (string) filemtime($spritePath) : (string) time(); $css = []; $css[] = '.adv-megamenu__icon{display:inline-block;background-repeat:no-repeat;background-image:url("../../img/generated/menu-sprite.webp?v=' . $version . '");}'; diff --git a/views/css/megamenu.css b/views/css/megamenu.css index 3494a0d..73bde04 100644 --- a/views/css/megamenu.css +++ b/views/css/megamenu.css @@ -603,8 +603,29 @@ } .adv-megamenu__mobile-link-row--with-image { - grid-template-columns: 112px minmax(0, 1fr) auto; - column-gap: 0.75rem; + grid-template-columns: minmax(0, 1fr) auto; +} + +.adv-megamenu .adv-megamenu__mobile-main-link, +.adv-megamenu .adv-megamenu__mobile-main-link:link, +.adv-megamenu .adv-megamenu__mobile-main-link:visited { + display: block; + min-width: 0; + color: #2d241d; + text-decoration: none; +} + +.adv-megamenu .adv-megamenu__mobile-main-link--with-image, +.adv-megamenu .adv-megamenu__mobile-main-link--with-image:link, +.adv-megamenu .adv-megamenu__mobile-main-link--with-image:visited { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.adv-megamenu__mobile-main-link-label { + display: block; + min-width: 0; } .adv-megamenu__mobile-thumb { @@ -737,6 +758,23 @@ border: 1px solid #ebe2d6; box-shadow: 0 6px 16px rgba(45, 36, 29, 0.07); background: #fffdfa; + transition: box-shadow 160ms ease, transform 160ms ease; +} + +.adv-megamenu .adv-megamenu__node-card-link, +.adv-megamenu .adv-megamenu__node-card-link:link, +.adv-megamenu .adv-megamenu__node-card-link:visited { + display: block; + height: 100%; + color: inherit; + text-decoration: none; +} + +.adv-megamenu .adv-megamenu__node-card-link:hover .adv-megamenu__node-card, +.adv-megamenu .adv-megamenu__node-card-link:focus .adv-megamenu__node-card, +.adv-megamenu .adv-megamenu__node-card-link:focus-visible .adv-megamenu__node-card { + transform: translateY(-2px); + box-shadow: 0 10px 24px rgba(45, 36, 29, 0.12); } .adv-megamenu__node-card .card-img-top { @@ -792,17 +830,6 @@ overflow: hidden; } -.adv-megamenu .adv-megamenu__node-card .card-title a, -.adv-megamenu .adv-megamenu__node-card .card-title a:link, -.adv-megamenu .adv-megamenu__node-card .card-title a:visited { - color: #2d241d; - text-decoration: none; -} - -.adv-megamenu .adv-megamenu__node-card .card-title a:hover { - color: #8a3c1f; -} - .adv-megamenu__node-card .card-text { margin: 0; font-size: var(--adv-card-text-size); @@ -818,7 +845,7 @@ .adv-megamenu__panel-tree, .adv-megamenu__layout-card, .adv-megamenu__node-card, -.adv-megamenu__node-card .card-title a, +.adv-megamenu__node-card-link, .adv-megamenu__node-card .card-text, .adv-megamenu__layout-card .card-title, .adv-megamenu__layout-card .card-text { diff --git a/views/js/admin.js b/views/js/admin.js index e350b0a..c2c353c 100644 --- a/views/js/admin.js +++ b/views/js/admin.js @@ -9,6 +9,7 @@ var treeInput = document.getElementById('advmegamenu_tree_json'); var ajaxUrl = root.getAttribute('data-ajax-url'); var token = root.getAttribute('data-token'); + var defaultLangId = String(root.getAttribute('data-default-lang') || ''); var languages = parseJson(root.querySelector('.js-adv-languages-data'), []); var labels = parseJson(root.querySelector('.js-adv-i18n'), {}); var tree = parseJson(root.querySelector('.js-adv-tree-data'), []); @@ -605,7 +606,7 @@ } searchTimer = window.setTimeout(function () { - fetch(ajaxUrl + '&ajax=1&action=searchProducts&q=' + encodeURIComponent(value)) + fetch(ajaxUrl + '&ajax=1&action=searchProducts&token=' + encodeURIComponent(token) + '&q=' + encodeURIComponent(value)) .then(function (response) { return response.json(); }) @@ -690,9 +691,8 @@ } function getNodeTitle(node) { - var defaultLang = languages[0] ? String(languages[0].id_lang) : null; - if (defaultLang && node.lang[defaultLang] && node.lang[defaultLang].title) { - return node.lang[defaultLang].title; + if (defaultLangId && node.lang[defaultLangId] && node.lang[defaultLangId].title) { + return node.lang[defaultLangId].title; } var langIds = Object.keys(node.lang || {}); @@ -706,9 +706,8 @@ } function getNodeLink(node) { - var defaultLang = languages[0] ? String(languages[0].id_lang) : null; - if (defaultLang && node.lang[defaultLang] && node.lang[defaultLang].custom_link) { - return node.lang[defaultLang].custom_link; + if (defaultLangId && node.lang[defaultLangId] && node.lang[defaultLangId].custom_link) { + return node.lang[defaultLangId].custom_link; } var langIds = Object.keys(node.lang || {}); diff --git a/views/templates/hook/megamenu.tpl b/views/templates/hook/megamenu.tpl index 527a2d3..30de56a 100644 --- a/views/templates/hook/megamenu.tpl +++ b/views/templates/hook/megamenu.tpl @@ -33,23 +33,23 @@
{foreach from=$nodes item=treeNode} {/foreach}
@@ -185,16 +185,18 @@ {foreach from=$node.children item=child}