From 17fcbe76e1fd0ef8138717028a3de407b79c48a6 Mon Sep 17 00:00:00 2001 From: John Quairia Date: Sat, 30 May 2026 08:51:45 +0200 Subject: [PATCH 1/4] deck: update faction filter --- plugins/core-altered-cards/pages/decks.php | 46 +++++++++++----------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/plugins/core-altered-cards/pages/decks.php b/plugins/core-altered-cards/pages/decks.php index 1d1b935..cf90a03 100644 --- a/plugins/core-altered-cards/pages/decks.php +++ b/plugins/core-altered-cards/pages/decks.php @@ -452,8 +452,12 @@ if (!in_array($pubOrder, $allowedPubOrders, true)) $pubOrder = 'updatedAt'; if (!in_array($pubDir, $allowedPubDirs, true)) $pubDir = 'desc'; + $pubFaction = $_GET['faction'] ?? ''; + if (!preg_match('/^[A-Z]{2}$/', $pubFaction)) $pubFaction = ''; + $apiParams = ['page' => $pubPage, 'itemsPerPage' => 21]; - if ($pubFormat !== '') $apiParams['format'] = strtolower($pubFormat); + if ($pubFormat !== '') $apiParams['format'] = strtolower($pubFormat); + if ($pubFaction !== '') $apiParams['faction'] = $pubFaction; $headers = ['Accept: application/json']; if ($isLoggedIn) { $token = deckApiToken(); @@ -493,11 +497,14 @@ if (!in_array($myOrder, ['createdAt', 'updatedAt', 'name'], true)) $myOrder = 'updatedAt'; if (!in_array($myDir, ['asc', 'desc'], true)) $myDir = 'desc'; + $myFaction = $_GET['faction'] ?? ''; + if (!preg_match('/^[A-Z]{2}$/', $myFaction)) $myFaction = ''; + $apiParams = ['page' => $myPage, 'itemsPerPage' => 21]; if ($myFormat !== '') $apiParams['format'] = $myFormat; if ($myIsPublic !== '') $apiParams['isPublic'] = $myIsPublic === '1' ? 'true' : 'false'; if ($myIsDraft !== '') $apiParams['isDraft'] = $myIsDraft === '1' ? 'true' : 'false'; - // $apiParams['faction'] = ...; // TODO: enable when API supports faction filtering + if ($myFaction !== '') $apiParams['faction'] = $myFaction; $myUrl = DECKS_API_URL . '/api/decks?' . http_build_query($apiParams) . '&order[' . $myOrder . ']=' . $myDir; $ch = curl_init($myUrl); @@ -873,7 +880,7 @@ class="form-control form-control-sm" style="width:220px" autocomplete="off"> var myGrid = document.getElementById('my-deck-grid'); var myPagination = document.getElementById('my-pagination'); var myCountEl = document.getElementById('my-deck-count'); - var myFactions = []; + var myFaction = ''; var myFormat = ''; var myVisibility = ''; var mySortVal = 'updatedAt:desc'; @@ -1146,6 +1153,7 @@ function loadMyDecks(p, scroll) { + '&dir=' + encodeURIComponent(sortParts[1] || 'desc'); if (myFormat) fetchUrl += '&format=' + encodeURIComponent(myFormat); if (myVisibility !== '') fetchUrl += '&isPublic=' + encodeURIComponent(myVisibility); + if (myFaction) fetchUrl += '&faction=' + encodeURIComponent(myFaction); var q = mySearch ? mySearch.value.trim() : ''; // name search is client-side only (API has no text search param) @@ -1158,14 +1166,6 @@ function loadMyDecks(p, scroll) { var decks = data.member || data.data || (Array.isArray(data) ? data : []); // Client-side name search filter if (q) decks = decks.filter(function(d) { return (d.name || '').toLowerCase().indexOf(q.toLowerCase()) >= 0; }); - // Client-side faction filter (API doesn't support it yet) - if (myFactions.length) { - decks = decks.filter(function(d) { - var ref = (d.stats && d.stats.hero && d.stats.hero.reference) || ''; - var fm2 = ref.match(/^ALT_[^_]+_[^_]+_([A-Z]{2})_/); - return fm2 && myFactions.indexOf(fm2[1]) >= 0; - }); - } if (!decks.length) { myEmpty.style.display = ''; return; } decks.forEach(function(deck) { myGrid.insertAdjacentHTML('beforeend', renderMyDeck(deck)); }); myAllItems = Array.from(myGrid.querySelectorAll('.my-deck-item')); @@ -1281,9 +1281,9 @@ function sectionLabel(label) { document.querySelectorAll('[data-my-faction]').forEach(function(btn) { btn.addEventListener('click', function() { - var v = btn.dataset.myFaction, idx = myFactions.indexOf(v); - if (idx >= 0) { myFactions.splice(idx, 1); btn.classList.remove('active'); } - else { myFactions.push(v); btn.classList.add('active'); } + var v = btn.dataset.myFaction; + if (myFaction === v) { myFaction = ''; btn.classList.remove('active'); } + else { document.querySelectorAll('[data-my-faction]').forEach(function(b) { b.classList.remove('active'); }); myFaction = v; btn.classList.add('active'); } loadMyDecks(1); }); }); @@ -1316,7 +1316,7 @@ function sectionLabel(label) { var pubNoMatch = document.getElementById('pub-no-match'); var pubGrid = document.getElementById('pub-grid'); var pubPagination = document.getElementById('pub-pagination'); - var pubFactions = []; + var pubFaction = ''; var pubFormat = ''; var pubSortVal = 'updatedAt:desc'; var pubAllItems = []; @@ -1414,9 +1414,8 @@ function filterPublic() { var q = pubSearch ? pubSearch.value.trim().toLowerCase() : ''; var visible = 0; pubAllItems.forEach(function (el) { - var show = (!q || (el.dataset.name || '').includes(q)) - && (!pubFormat || el.dataset.format === pubFormat) - && (!pubFactions.length || pubFactions.indexOf(el.dataset.faction) >= 0); + var show = (!q || (el.dataset.name || '').includes(q)) + && (!pubFormat || el.dataset.format === pubFormat); el.style.display = show ? '' : 'none'; if (show) visible++; }); @@ -1437,7 +1436,8 @@ function loadPublicDecks(p, scroll) { var fetchUrl = baseUrl + '/pages/decks?ajax=public&page=' + p + '&order=' + encodeURIComponent(pubSortParts[0]) + '&dir=' + encodeURIComponent(pubSortParts[1] || 'desc'); - if (pubFormat) fetchUrl += '&format=' + encodeURIComponent(pubFormat); + if (pubFormat) fetchUrl += '&format=' + encodeURIComponent(pubFormat); + if (pubFaction) fetchUrl += '&faction=' + encodeURIComponent(pubFaction); fetch(fetchUrl) .then(function (r) { return r.json(); }) .then(function (data) { @@ -1473,10 +1473,10 @@ function loadPublicDecks(p, scroll) { document.querySelectorAll('[data-pub-faction]').forEach(function (btn) { btn.addEventListener('click', function () { - var v = btn.dataset.pubFaction, idx = pubFactions.indexOf(v); - if (idx >= 0) { pubFactions.splice(idx, 1); btn.classList.remove('active'); } - else { pubFactions.push(v); btn.classList.add('active'); } - filterPublic(); + var v = btn.dataset.pubFaction; + if (pubFaction === v) { pubFaction = ''; btn.classList.remove('active'); } + else { document.querySelectorAll('[data-pub-faction]').forEach(function(b) { b.classList.remove('active'); }); pubFaction = v; btn.classList.add('active'); } + loadPublicDecks(1); }); }); From f5bf6acb3ad47c2552bf79b7a1d7f53ea6dd01f5 Mon Sep 17 00:00:00 2001 From: John Quairia Date: Sat, 30 May 2026 12:33:11 +0200 Subject: [PATCH 2/4] deck: filter by hero --- plugins/core-altered-cards/pages/decks.php | 120 +++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/plugins/core-altered-cards/pages/decks.php b/plugins/core-altered-cards/pages/decks.php index cf90a03..51e677d 100644 --- a/plugins/core-altered-cards/pages/decks.php +++ b/plugins/core-altered-cards/pages/decks.php @@ -60,6 +60,8 @@ 'search_ph' => 'Search a deck…', 'lbl_format' => 'Format', 'lbl_faction' => 'Faction', + 'lbl_hero' => 'Hero', + 'hero_all' => 'All heroes', 'lbl_visibility' => 'Visibility', 'lbl_sort' => 'Sort', 'sort_updated_desc' => 'Recently updated', @@ -157,6 +159,8 @@ 'search_ph' => 'Rechercher un deck…', 'lbl_format' => 'Format', 'lbl_faction' => 'Faction', + 'lbl_hero' => 'Héros', + 'hero_all' => 'Tous les héros', 'lbl_visibility' => 'Visibilité', 'lbl_sort' => 'Tri', 'sort_updated_desc' => 'Récemment modifié', @@ -439,6 +443,25 @@ exit; } +// AJAX proxy: heroes list +if ($_SERVER['REQUEST_METHOD'] === 'GET' && ($_GET['ajax'] ?? '') === 'heroes') { + header('Content-Type: application/json'); + $heroLocale = in_array($uiLang, ['fr', 'en', 'de', 'es', 'it'], true) ? $uiLang : 'en'; + $heroesUrl = 'https://deckbuilder.alteredcore.org/deck-api-proxy/decks/public/heroes?locale=' . $heroLocale; + $ch = curl_init($heroesUrl); + curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Accept: application/json'], CURLOPT_TIMEOUT => 10]); + $heroesResp = curl_exec($ch); + $heroesCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($heroesCode >= 200 && $heroesCode < 300 && $heroesResp) { + echo $heroesResp; + } else { + http_response_code($heroesCode ?: 500); + echo json_encode([]); + } + exit; +} + // aJAX proxy: public decks if ($_SERVER['REQUEST_METHOD'] === 'GET' && ($_GET['ajax'] ?? '') === 'public' && $publicDecksApiPath !== '') { header('Content-Type: application/json'); @@ -454,10 +477,13 @@ $pubFaction = $_GET['faction'] ?? ''; if (!preg_match('/^[A-Z]{2}$/', $pubFaction)) $pubFaction = ''; + $pubHero = $_GET['hero'] ?? ''; + if (!preg_match('/^[A-Z0-9_]+$/', $pubHero)) $pubHero = ''; $apiParams = ['page' => $pubPage, 'itemsPerPage' => 21]; if ($pubFormat !== '') $apiParams['format'] = strtolower($pubFormat); if ($pubFaction !== '') $apiParams['faction'] = $pubFaction; + if ($pubHero !== '') $apiParams['hero'] = $pubHero; $headers = ['Accept: application/json']; if ($isLoggedIn) { $token = deckApiToken(); @@ -499,12 +525,15 @@ $myFaction = $_GET['faction'] ?? ''; if (!preg_match('/^[A-Z]{2}$/', $myFaction)) $myFaction = ''; + $myHero = $_GET['hero'] ?? ''; + if (!preg_match('/^[A-Z0-9_]+$/', $myHero)) $myHero = ''; $apiParams = ['page' => $myPage, 'itemsPerPage' => 21]; if ($myFormat !== '') $apiParams['format'] = $myFormat; if ($myIsPublic !== '') $apiParams['isPublic'] = $myIsPublic === '1' ? 'true' : 'false'; if ($myIsDraft !== '') $apiParams['isDraft'] = $myIsDraft === '1' ? 'true' : 'false'; if ($myFaction !== '') $apiParams['faction'] = $myFaction; + if ($myHero !== '') $apiParams['hero'] = $myHero; $myUrl = DECKS_API_URL . '/api/decks?' . http_build_query($apiParams) . '&order[' . $myOrder . ']=' . $myDir; $ch = curl_init($myUrl); @@ -692,6 +721,12 @@ class="form-control form-control-sm" style="width:220px" autocomplete="off"> +
+ + +
+
+ + +
- + + + +
+ + +
+ +
- - -
+ +
+ +
+
+ + +
+
+ +
+
+
+ +
+
+ +
+
+ + + +
$_sv): ?>
- - -
+ + +
$_fv): ?> -
- - - - -
+ + + $_rv): ?> + + +
+ +
+
- - -
- - -