mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-03 04:02:10 +02:00
feat(reseller): trial deep-links, voucher balance card, Host header guard
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.
This commit is contained in:
committed by
Divarion_D
parent
5c6f9f920c
commit
78d7b23212
@@ -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');
|
||||
|
||||
@@ -2324,5 +2324,6 @@ filter_by_owner = "تصفية حسب المالك"
|
||||
set_as_system_template = "تعيين كقالب للنظام"
|
||||
sub_resellers = "الموزعين الفرعيين"
|
||||
dashboard_nav_top = "يرجى تحديد خيار من شريط التنقل أعلاه للمتابعة."
|
||||
live_calculator = "حساب لحظي"
|
||||
close_ticket = "هل أنت متأكد من رغبتك في إغلاق هذه التذكرة؟"
|
||||
reopen_ticket = "هل أنت متأكد من رغبتك في إعادة فتح هذه التذكرة؟"
|
||||
|
||||
@@ -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?"
|
||||
|
||||
@@ -337,41 +337,72 @@ $dnsList = array_filter(array_map('trim', explode(',', (string)($rUserInfo['rese
|
||||
<!-- Right Column: Live Credit Calculator & Guidelines -->
|
||||
<div class="col-12 col-xl-4">
|
||||
<!-- Live Credit Calculator Card -->
|
||||
<div class="card shadow-sm border-0 mb-4 bg-primary text-white">
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center pb-3 border-bottom">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="avatar avatar-sm bg-label-primary rounded p-2">
|
||||
<i class="ti tabler-wallet icon-20px text-primary"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="card-title mb-0 fw-bold"><?= $language::get('ac_credit_accounting') ?></h6>
|
||||
<small class="text-muted"><?= $language::get('live_calculator') ?: 'Real-Time Calculation'; ?></small>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-label-primary rounded-pill px-3 py-1">
|
||||
<i class="ti tabler-sparkles me-1"></i><?= $language::get('live') ?: 'Live'; ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<span class="text-white-50 text-uppercase fw-semibold small"><?= $language::get('ac_credit_accounting') ?></span>
|
||||
<i class="ti tabler-wallet fs-3 text-white"></i>
|
||||
<!-- Current Balance Display -->
|
||||
<div class="d-flex align-items-center justify-content-between p-3 rounded-3 bg-label-primary mb-4">
|
||||
<div>
|
||||
<small class="text-muted text-uppercase fw-semibold d-block mb-1"><?= $language::get('ac_your_current_balance') ?></small>
|
||||
<h3 class="text-primary fw-bolder mb-0" id="card-current-balance"><?= number_format($userCredits, 2); ?> <span class="fs-6 fw-normal text-muted"><?= $language::get('ac_credits') ?></span></h3>
|
||||
</div>
|
||||
<div class="avatar avatar-md bg-primary text-white rounded-circle shadow-sm d-flex align-items-center justify-content-center">
|
||||
<i class="ti tabler-coins fs-3"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<small class="text-white-50 d-block"><?= $language::get('ac_your_current_balance') ?></small>
|
||||
<h2 class="text-white fw-bold mb-0" id="card-current-balance"><?= number_format($userCredits, 2); ?> <span class="fs-6 fw-normal"><?= $language::get('ac_credits') ?></span></h2>
|
||||
</div>
|
||||
|
||||
<div class="p-3 bg-white bg-opacity-10 rounded-3 mb-3">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="small text-white-50"><?= $language::get('ac_cost_per_voucher') ?>:</span>
|
||||
<span class="fw-semibold" id="card-cost-per-code">0.00 <?= $language::get('ac_credits') ?></span>
|
||||
<!-- Calculation Breakdown -->
|
||||
<div class="card bg-label-secondary border-0 rounded-3 p-3 mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">
|
||||
<span class="text-muted small d-flex align-items-center gap-2">
|
||||
<i class="ti tabler-ticket fs-5 text-secondary"></i>
|
||||
<?= $language::get('ac_cost_per_voucher') ?>:
|
||||
</span>
|
||||
<span class="fw-semibold text-heading" id="card-cost-per-code">0.00 <?= $language::get('ac_credits') ?></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="small text-white-50"><?= $language::get('ac_quantity') ?>:</span>
|
||||
<span class="fw-semibold" id="card-qty">1</span>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">
|
||||
<span class="text-muted small d-flex align-items-center gap-2">
|
||||
<i class="ti tabler-calculator fs-5 text-secondary"></i>
|
||||
<?= $language::get('ac_quantity') ?>:
|
||||
</span>
|
||||
<span class="badge bg-label-primary fw-bold fs-7 px-2.5 py-1" id="card-qty">1</span>
|
||||
</div>
|
||||
<hr class="border-white opacity-25 my-2">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="fw-bold"><?= $language::get('ac_total_cost') ?>:</span>
|
||||
<div class="d-flex justify-content-between align-items-center pt-1">
|
||||
<span class="fw-bold text-heading d-flex align-items-center gap-2">
|
||||
<i class="ti tabler-receipt-2 fs-5 text-warning"></i>
|
||||
<?= $language::get('ac_total_cost') ?>:
|
||||
</span>
|
||||
<span class="fs-5 fw-bold text-warning" id="card-total-cost">0.00 <?= $language::get('ac_credits') ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center p-2 rounded-3 bg-black bg-opacity-25" id="balance-after-box">
|
||||
<span class="small text-white-50"><?= $language::get('ac_balance_after_generation') ?>:</span>
|
||||
<span class="fw-bold" id="card-balance-after"><?= number_format($userCredits, 2); ?> <?= $language::get('ac_credits') ?></span>
|
||||
<!-- Balance After Generation Box -->
|
||||
<div class="d-flex justify-content-between align-items-center p-3 rounded-3 bg-body border" id="balance-after-box">
|
||||
<span class="small text-muted fw-semibold d-flex align-items-center gap-2">
|
||||
<i class="ti tabler-scale fs-5 text-info"></i>
|
||||
<?= $language::get('ac_balance_after_generation') ?>:
|
||||
</span>
|
||||
<span class="fw-bold text-heading fs-6" id="card-balance-after"><?= number_format($userCredits, 2); ?> <?= $language::get('ac_credits') ?></span>
|
||||
</div>
|
||||
|
||||
<div id="insufficient-balance-alert" class="alert alert-danger bg-danger text-white border-0 mt-3 d-none mb-0">
|
||||
<i class="ti tabler-alert-circle me-1"></i> <?= $language::get('ac_insufficient_balance') ?>
|
||||
<!-- Insufficient Balance Alert -->
|
||||
<div id="insufficient-balance-alert" class="alert alert-danger d-flex align-items-center mt-3 d-none mb-0" role="alert">
|
||||
<i class="ti tabler-alert-circle fs-4 me-2 flex-shrink-0"></i>
|
||||
<div><?= $language::get('ac_insufficient_balance') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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')];
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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')];
|
||||
|
||||
Reference in New Issue
Block a user