From 78d7b232122ed74c7107768f072c57cd4e2cff4d Mon Sep 17 00:00:00 2001 From: AbdoAhmedElbanaa Date: Tue, 22 Sep 2026 17:00:30 +0300 Subject: [PATCH] feat(reseller): trial deep-links, voucher balance card, Host header guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Safe subset of Rosmi720/XC_VM@8fa1e67f. The dashboard revamp and the sidebar restructure from that commit are deliberately left out — see below. - The sidebar's existing `?trial=1` links on Lines / MAG / Enigma now actually land on the Trial filter: each page picks the trial value from its own $rStatusFilters map (5 for lines, 4 for MAG and Enigma) when no explicit ?filter is given. - Restyle the voucher credit-accounting card in active_code.php onto standard Vuexy surface classes instead of a hand-tinted primary panel. Element ids and the calculation are untouched, so the existing calculator JS still drives it. - RequestGuard: fall back to '' for HTTP_HOST. A request can legitimately arrive without a Host header, and explode(':', null) is deprecated on PHP 8. - Add the live_calculator string the new card asks for. Without it Translator returns the key itself AND appends it to the ini, so the card would render the literal "live_calculator" (which is how the placeholder appeared upstream). The Arabic wording is a first pass and welcomes a correction from its author. Left out of this commit: - ResellerDashboardController / dashboard.php: three fallbacks drop tenant scoping when the scoped query comes back empty — top countries re-queries lines_activity server-wide, latest movies and live streams re-run without the stream_ids restriction, and series and episodes are never scoped at all. It also pins unknown connections to a hardcoded 'EG' country, and moves ~10 raw SQL queries into a controller. - The sidebar restructure: it replaces the LineService::canGenerateTrials() check with a plain create_line permission, which would show trial actions to resellers that may not generate trials. - The Redis mGet guard for ResellerTableRenderer::handleLiveConnections, which Admin\TableController already carries. The method sits at cc=53 with no coverage, so the CRAP ratchet rightly refuses the hardening until it is tested — worth doing as its own change. Verified: 857 tests, make gates, CRAP gate. --- src/Core/Http/RequestGuard.php | 4 +- src/Core/Localization/lang/ar.ini | 1 + src/Core/Localization/lang/en.ini | 1 + src/Public/Views/reseller/active_code.php | 79 ++++++++++++++++------- src/Public/Views/reseller/enigmas.php | 2 +- src/Public/Views/reseller/lines.php | 2 +- src/Public/Views/reseller/mags.php | 2 +- 7 files changed, 62 insertions(+), 29 deletions(-) diff --git a/src/Core/Http/RequestGuard.php b/src/Core/Http/RequestGuard.php index 37cb7d8c..b48e498f 100644 --- a/src/Core/Http/RequestGuard.php +++ b/src/Core/Http/RequestGuard.php @@ -31,9 +31,9 @@ use XcVm\Core\Logging\Logger; $rShowErrors = false; if (!isset($_SERVER['argc'])) { - $rIP = $_SERVER['REMOTE_ADDR']; + $rIP = $_SERVER['REMOTE_ADDR'] ?? ''; if (empty($rIP) || !file_exists(FLOOD_TMP_PATH . 'block_' . $rIP)) { - define('HOST', trim(explode(':', $_SERVER['HTTP_HOST'])[0])); + define('HOST', trim(explode(':', $_SERVER['HTTP_HOST'] ?? '')[0])); if (file_exists(CACHE_TMP_PATH . 'settings')) { $rData = file_get_contents(CACHE_TMP_PATH . 'settings'); diff --git a/src/Core/Localization/lang/ar.ini b/src/Core/Localization/lang/ar.ini index f32471e7..f836b71a 100644 --- a/src/Core/Localization/lang/ar.ini +++ b/src/Core/Localization/lang/ar.ini @@ -2324,5 +2324,6 @@ filter_by_owner = "تصفية حسب المالك" set_as_system_template = "تعيين كقالب للنظام" sub_resellers = "الموزعين الفرعيين" dashboard_nav_top = "يرجى تحديد خيار من شريط التنقل أعلاه للمتابعة." +live_calculator = "حساب لحظي" close_ticket = "هل أنت متأكد من رغبتك في إغلاق هذه التذكرة؟" reopen_ticket = "هل أنت متأكد من رغبتك في إعادة فتح هذه التذكرة؟" diff --git a/src/Core/Localization/lang/en.ini b/src/Core/Localization/lang/en.ini index 7f81bd23..495e0032 100644 --- a/src/Core/Localization/lang/en.ini +++ b/src/Core/Localization/lang/en.ini @@ -2353,5 +2353,6 @@ copied_success = "Copied!" custom_format_link = "Custom link" download = "Download" select_an_option = "Please select an option first" +live_calculator = "Real-Time Calculation" close_ticket = "Are you sure you want to close this ticket?" reopen_ticket = "Are you sure you want to reopen this ticket?" diff --git a/src/Public/Views/reseller/active_code.php b/src/Public/Views/reseller/active_code.php index 32484d32..ed4325da 100644 --- a/src/Public/Views/reseller/active_code.php +++ b/src/Public/Views/reseller/active_code.php @@ -337,41 +337,72 @@ $dnsList = array_filter(array_map('trim', explode(',', (string)($rUserInfo['rese
-
+
+
+
+
+ +
+
+
+ +
+
+ + + +
+
-
- - + +
+
+ +

+
+
+ +
-
- -

-
- -
-
- : - 0.00 + +
+
+ + + : + + 0.00
-
- : - 1 +
+ + + : + + 1
-
-
- : +
+ + + : + 0.00
-
- : - + +
+ + + : + +
-
- + +
diff --git a/src/Public/Views/reseller/enigmas.php b/src/Public/Views/reseller/enigmas.php index 42e5cf75..3d2129f4 100644 --- a/src/Public/Views/reseller/enigmas.php +++ b/src/Public/Views/reseller/enigmas.php @@ -36,7 +36,7 @@ $rCanKill = !empty($rPermissions['reseller_client_connection_logs']); // Deep-link owner filter (?owner=ID) — pre-selects the matching static option. $rSelectedOwner = RequestManager::has('owner') ? (string) RequestManager::get('owner') : ''; -$rSelectedFilter = RequestManager::has('filter') ? (int) RequestManager::get('filter') : 0; +$rSelectedFilter = RequestManager::has('filter') ? (int) RequestManager::get('filter') : (RequestManager::has('trial') && RequestManager::get('trial') == 1 ? 4 : 0); // Reseller Enigma2 status filter values handled by ResellerTableRenderer::handleEnigmas. $rStatusFilters = [1 => $language::get('active'), 2 => $language::get('disabled'), 3 => $language::get('expired'), 4 => $language::get('trial')]; diff --git a/src/Public/Views/reseller/lines.php b/src/Public/Views/reseller/lines.php index d480cf7c..d7111f40 100644 --- a/src/Public/Views/reseller/lines.php +++ b/src/Public/Views/reseller/lines.php @@ -42,7 +42,7 @@ if (empty($rSiteUrl)) { // Deep-link owner filter (?owner=ID) — pre-selects the matching static option. $rSelectedOwner = RequestManager::has('owner') ? (string) RequestManager::get('owner') : ''; -$rSelectedFilter = RequestManager::has('filter') ? (int) RequestManager::get('filter') : 0; +$rSelectedFilter = RequestManager::has('filter') ? (int) RequestManager::get('filter') : (RequestManager::has('trial') && RequestManager::get('trial') == 1 ? 5 : 0); // Reseller status filter values handled by ResellerTableRenderer::handleLines. $rStatusFilters = [1 => 'Active', 2 => 'Disabled', 3 => 'Banned', 4 => 'Expired', 5 => 'Trial']; diff --git a/src/Public/Views/reseller/mags.php b/src/Public/Views/reseller/mags.php index 0be07287..e68deea3 100644 --- a/src/Public/Views/reseller/mags.php +++ b/src/Public/Views/reseller/mags.php @@ -36,7 +36,7 @@ $rCanKill = !empty($rPermissions['reseller_client_connection_logs']); // Deep-link owner filter (?owner=ID) — pre-selects the matching static option. $rSelectedOwner = RequestManager::has('owner') ? (string) RequestManager::get('owner') : ''; -$rSelectedFilter = RequestManager::has('filter') ? (int) RequestManager::get('filter') : 0; +$rSelectedFilter = RequestManager::has('filter') ? (int) RequestManager::get('filter') : (RequestManager::has('trial') && RequestManager::get('trial') == 1 ? 4 : 0); // Reseller MAG status filter values handled by ResellerTableRenderer::handleMags. $rStatusFilters = [1 => $language::get('active'), 2 => $language::get('disabled'), 3 => $language::get('expired'), 4 => $language::get('trial')];