fix: billing cleanup, PayPal guard, docs nav prefix, coupon dark theme, XML guide link

Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/52e5015e-f5cf-42e2-bc32-b1c77193a13f

Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-05-06 15:24:27 +00:00 committed by GitHub
parent a45d102845
commit 9944b59332
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 158 additions and 37 deletions

View file

@ -55,8 +55,6 @@ function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
<a class="gsw-btn" href="admin_payments.php">Transaction Log</a>
<a class="gsw-btn" href="admin_coupons.php">Manage Coupons</a>
<a class="gsw-btn" href="admin_config.php">Edit Site Config</a>
<a class="gsw-btn" href="admin_xml_editor.php">XML Config Editor</a>
<a class="gsw-btn" href="docs/xml_notes.php">XML Config Guide</a>
</div>
<hr>

View file

@ -160,29 +160,120 @@ $coupons_result = mysqli_query($db, "SELECT * FROM {$table_prefix}billing_coupon
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/header.css">
<style>
.coupon-form { background: #f5f5f5; padding: 20px; margin: 20px 0; border-radius: 5px; }
/* Coupon admin — dark-theme overrides */
.coupon-form {
background: rgba(0,0,0,0.35);
border: 1px solid rgba(255,255,255,0.1);
padding: 20px;
margin: 20px 0;
border-radius: 8px;
}
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
.form-group input, .form-group select, .form-group textarea { width: 100%; padding: 8px; box-sizing: border-box; }
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: 600;
color: #e8e8e8;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 9px 10px;
box-sizing: border-box;
background: #11141f;
color: #f0f0f0;
border: 1px solid rgba(255,255,255,0.18);
border-radius: 5px;
font-size: 0.97rem;
}
.form-group input::placeholder,
.form-group textarea::placeholder {
color: rgba(255,255,255,0.4);
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102,126,234,0.25);
}
.form-group textarea { min-height: 60px; }
.game-checkboxes { max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; background: white; }
.game-checkboxes label { display: block; margin: 5px 0; font-weight: normal; }
.coupon-table { width: 100%; border-collapse: collapse; margin: 20px 0; }
.coupon-table th, .coupon-table td { border: 1px solid #ddd; padding: 10px; text-align: left; }
.coupon-table th { background: #4CAF50; color: white; }
.coupon-table tr:nth-child(even) { background: #f9f9f9; }
.btn { padding: 8px 16px; margin: 2px; cursor: pointer; border: none; border-radius: 3px; }
.btn-primary { background: #4CAF50; color: white; }
.btn-warning { background: #ff9800; color: white; }
.btn-danger { background: #f44336; color: white; }
.status { padding: 10px; margin: 10px 0; border-radius: 3px; }
.status.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.status.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.badge { padding: 3px 8px; border-radius: 3px; font-size: 0.85em; }
.badge-active { background: #28a745; color: white; }
.badge-inactive { background: #6c757d; color: white; }
.badge-onetime { background: #17a2b8; color: white; }
.badge-permanent { background: #ffc107; color: black; }
.game-checkboxes {
max-height: 200px;
overflow-y: auto;
border: 1px solid rgba(255,255,255,0.15);
padding: 10px;
background: rgba(0,0,0,0.4);
border-radius: 5px;
}
.game-checkboxes label {
display: block;
margin: 5px 0;
font-weight: normal;
color: #d0d0d0;
cursor: pointer;
}
.game-checkboxes input[type="checkbox"] {
width: auto;
margin-right: 6px;
background: #11141f;
border: 1px solid rgba(255,255,255,0.25);
}
.coupon-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
.coupon-table th,
.coupon-table td {
border: 1px solid rgba(255,255,255,0.1);
padding: 10px 12px;
text-align: left;
color: #e8e8e8;
}
.coupon-table th { background: rgba(76,175,80,0.25); color: #fff; }
.coupon-table tr:nth-child(even) td { background: rgba(255,255,255,0.03); }
.coupon-table tr:hover td { background: rgba(255,255,255,0.06); }
.btn { padding: 8px 16px; margin: 2px; cursor: pointer; border: none; border-radius: 4px; font-weight: 600; }
.btn-primary { background: linear-gradient(135deg,#667eea,#764ba2); color: #fff; }
.btn-warning { background: #ff9800; color: #fff; }
.btn-danger { background: #f44336; color: #fff; }
.status { padding: 10px 14px; margin: 10px 0; border-radius: 5px; }
.status.success { background: rgba(40,167,69,0.2); color: #8dffb0; border: 1px solid rgba(40,167,69,0.35); }
.status.error { background: rgba(220,53,69,0.2); color: #ffb3b8; border: 1px solid rgba(220,53,69,0.35); }
.badge { padding: 3px 8px; border-radius: 3px; font-size: 0.85em; font-weight: 600; }
.badge-active { background: #28a745; color: #fff; }
.badge-inactive { background: #6c757d; color: #fff; }
.badge-onetime { background: #17a2b8; color: #fff; }
.badge-permanent { background: #ffc107; color: #000; }
/* Inline select/option elements inside table rows */
.coupon-table select { background: #11141f; color: #f0f0f0; border: 1px solid rgba(255,255,255,0.18); border-radius: 4px; padding: 4px 6px; }
/* Mobile: stack table cells */
@media (max-width: 768px) {
.coupon-table, .coupon-table thead, .coupon-table tbody,
.coupon-table th, .coupon-table td, .coupon-table tr { display: block; }
.coupon-table thead tr { display: none; }
.coupon-table td {
position: relative;
padding-left: 45%;
border: none;
border-bottom: 1px solid rgba(255,255,255,0.07);
}
.coupon-table td::before {
position: absolute;
left: 10px;
width: 40%;
white-space: nowrap;
font-weight: 600;
color: rgba(255,255,255,0.55);
font-size: 0.82rem;
content: attr(data-label);
}
.coupon-table tr { border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; margin-bottom: 12px; }
}
</style>
<script>
function toggleGameFilter(selectEl) {
@ -313,35 +404,35 @@ include(__DIR__ . '/includes/menu.php');
?>
<!-- View Row -->
<tr id="view-row-<?php echo $coupon['coupon_id']; ?>">
<td><strong><?php echo h($coupon['code']); ?></strong></td>
<td><?php echo h($coupon['name']); ?></td>
<td><?php echo h($coupon['discount_percent']); ?>%</td>
<td>
<td data-label="Code"><strong><?php echo h($coupon['code']); ?></strong></td>
<td data-label="Name"><?php echo h($coupon['name']); ?></td>
<td data-label="Discount"><?php echo h($coupon['discount_percent']); ?>%</td>
<td data-label="Type">
<span class="badge badge-<?php echo $coupon['usage_type'] === 'permanent' ? 'permanent' : 'onetime'; ?>">
<?php echo h(ucfirst(str_replace('_', ' ', $coupon['usage_type']))); ?>
</span>
</td>
<td>
<td data-label="Games">
<?php if ($coupon['game_filter_type'] === 'all_games'): ?>
All Games
<?php else: ?>
<?php echo count((array)$games_filtered); ?> specific games
<?php endif; ?>
</td>
<td>
<td data-label="Uses">
<?php if ($coupon['max_uses']): ?>
<?php echo h($coupon['current_uses']); ?> / <?php echo h($coupon['max_uses']); ?>
<?php else: ?>
<?php echo h($coupon['current_uses']); ?> (unlimited)
<?php endif; ?>
</td>
<td><?php echo $coupon['expires'] ? h($coupon['expires']) : 'Never'; ?></td>
<td>
<td data-label="Expires"><?php echo $coupon['expires'] ? h($coupon['expires']) : 'Never'; ?></td>
<td data-label="Status">
<span class="badge badge-<?php echo $coupon['is_active'] ? 'active' : 'inactive'; ?>">
<?php echo $coupon['is_active'] ? 'Active' : 'Inactive'; ?>
</span>
</td>
<td>
<td data-label="Actions">
<button onclick="editCoupon(<?php echo $coupon['coupon_id']; ?>)" class="btn btn-warning">Edit</button>
<form method="POST" style="display:inline;" onsubmit="return confirm('Delete this coupon?');">
<input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">

View file

@ -509,7 +509,7 @@ $siteBase = $protocol . $host;
<!-- Favicon -->
<link rel="icon" href="images/logo-sm.png" type="image/png">
<link rel="apple-touch-icon" href="images/logo-sm.png">
<?php if (!$cart_empty): ?>
<?php if (!$cart_empty && !empty($client_id)): ?>
<script src="https://www.paypal.com/sdk/js?client-id=<?php echo htmlspecialchars($client_id); ?>&currency=USD&intent=capture"></script>
<?php endif; ?>
</head>
@ -643,11 +643,30 @@ $siteBase = $protocol . $host;
<?php else: ?>
<div class="checkout-section">
<h3>Checkout with PayPal</h3>
<?php if (empty($client_id)): ?>
<div class="alert alert-error">
<strong>Checkout Unavailable:</strong> PayPal has not been configured for this site.
Please contact the site administrator or try again later.
<?php
// Admin hint: only show config link if the current user is an admin
$cart_user_id_check = intval($_SESSION['website_user_id'] ?? 0);
$cart_is_admin = false;
if ($cart_user_id_check > 0 && $db) {
$ar = mysqli_query($db, "SELECT users_role FROM {$table_prefix}users WHERE user_id = " . $cart_user_id_check . " LIMIT 1");
if ($ar && ($arow = mysqli_fetch_assoc($ar))) {
$cart_is_admin = strtolower($arow['users_role'] ?? '') === 'admin';
}
}
if ($cart_is_admin):
?>
<br><small><em>Admin: set <code>$paypal_client_id</code> in <a href="/admin_config.php" style="color:inherit;text-decoration:underline;">Site Config</a>.</em></small>
<?php endif; ?>
</div>
<?php else: ?>
<p>Click the button below to complete your purchase securely through PayPal.</p>
<div id="paypal-button-container"></div>
<div id="status-message" class="status-message"></div>
<?php endif; ?>
<div class="action-buttons">
<a href="/order.php" class="btn btn-secondary">Continue Shopping</a>
<a href="/my_account.php" class="btn btn-secondary">My Account</a>
@ -665,7 +684,7 @@ $siteBase = $protocol . $host;
}
</script>
<?php if ($final_amount > 0.00): ?>
<?php if ($final_amount > 0.00 && !empty($client_id)): ?>
<script>
paypal.Buttons({
createOrder: function(data, actions) {

View file

@ -16,6 +16,7 @@ $nav_prefix = '';
$scriptName = $_SERVER['SCRIPT_NAME'] ?? '';
if (is_string($scriptName) && $scriptName !== '') {
if (preg_match('#/modules/billing/(.*)$#', $scriptName, $match)) {
// Panel-embedded or non-root deployment: depth relative to modules/billing/
$subPath = $match[1];
if ($subPath !== '') {
$depth = substr_count($subPath, '/');
@ -23,6 +24,17 @@ if (is_string($scriptName) && $scriptName !== '') {
$nav_prefix = str_repeat('../', $depth);
}
}
} else {
// Root deployment: compute prefix from the script's directory depth so that
// links such as index.php correctly resolve to /index.php even when the
// current page lives in a subdirectory like /docs/.
$dir = dirname($scriptName);
if ($dir !== '/' && $dir !== '' && $dir !== '.') {
$segments = array_filter(explode('/', $dir), static function ($s) { return $s !== ''; });
if (!empty($segments)) {
$nav_prefix = str_repeat('../', count($segments));
}
}
}
}
$nav_prefix = $nav_prefix ?: '';

View file

@ -1 +1 @@
Last Updated at 5:10pm on 2026-05-04
Last Updated at 3:18pm on 2026-05-06

View file

@ -550,6 +550,7 @@ function exec_ogp_module() {
$game_cfgs = $db->getGameCfgs();
echo "<h2>".get_lang('game_config_setup')."</h2>\n
<p>".get_lang_f("modify_configs_info",SERVER_CONFIG_LOCATION)."</p>\n
<p><a href='https://gameservers.world/docs/xml_notes.php' target='_blank' rel='noopener noreferrer' class='xml-jump-link'>&#x1F4D6; XML Config Reference Guide</a></p>\n
<form action='?m=config_games' method='post'>\n
<p><input id='reset_old_configs' type='checkbox' name='clear_old' value='yes' /><label for='reset_old_configs'>".get_lang('reset_old_configs')."</label></p>\n
<p class='note'>".get_lang('note').": ".get_lang('config_reset_warning')."</p>\n