attept 2 at site fix
This commit is contained in:
parent
60bcc67056
commit
cc7bbafb63
23 changed files with 360 additions and 75 deletions
|
|
@ -26,7 +26,9 @@ Panel/modules/website/
|
|||
forgot_password.php
|
||||
reset_password.php
|
||||
account.php
|
||||
my_account.php
|
||||
orders.php
|
||||
my_orders.php
|
||||
invoices.php
|
||||
my_servers.php
|
||||
staff.php
|
||||
|
|
@ -115,6 +117,9 @@ website sales, catalog, pricing, coupons, invoices, payments, PayPal settings,
|
|||
and the paid-order provisioning queue. Access currently requires a Panel admin
|
||||
account.
|
||||
|
||||
Staff links must point to `staff.php` and related `staff_*.php` website pages.
|
||||
Do not use the Panel activity logger as the website staff dashboard.
|
||||
|
||||
## Shared Accounts
|
||||
|
||||
The Panel user table is the identity source for the website. Website login checks
|
||||
|
|
@ -129,6 +134,10 @@ sessions. Control Panel and staff links point directly to the configured Panel U
|
|||
`sso.php` endpoints are compatibility redirects only. They do not create tokens or
|
||||
sessions.
|
||||
|
||||
Successful website login redirects to `my_account.php` by default, or to a saved
|
||||
safe internal return path such as `cart.php?checkout=1`. `account.php` remains as
|
||||
a compatibility route.
|
||||
|
||||
## Ordering
|
||||
|
||||
The active catalog route is `serverlist.php`. Order buttons point to the website
|
||||
|
|
|
|||
|
|
@ -89,9 +89,9 @@ textarea {
|
|||
|
||||
.header-shell {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
gap: 18px;
|
||||
min-height: 76px;
|
||||
}
|
||||
|
||||
|
|
@ -130,15 +130,31 @@ textarea {
|
|||
|
||||
.primary-nav {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.nav-group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.nav-group + .nav-group {
|
||||
padding-left: 10px;
|
||||
border-left: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
padding: 10px 12px;
|
||||
padding: 9px 10px;
|
||||
border-radius: 6px;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nav-link:hover,
|
||||
|
|
@ -149,8 +165,9 @@ textarea {
|
|||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.button {
|
||||
|
|
@ -693,6 +710,25 @@ textarea {
|
|||
}
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.header-shell {
|
||||
grid-template-columns: 1fr;
|
||||
justify-items: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.brand {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.primary-nav {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hero-layout,
|
||||
.panel-preview,
|
||||
.footer-grid {
|
||||
|
|
@ -750,10 +786,33 @@ textarea {
|
|||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.nav-group,
|
||||
.nav-group + .nav-group {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
justify-items: stretch;
|
||||
gap: 4px;
|
||||
padding-left: 0;
|
||||
padding-top: 8px;
|
||||
border-left: 0;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.nav-group:first-child {
|
||||
border-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
min-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
grid-column: 1 / -1;
|
||||
display: none;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
|
|
@ -762,7 +821,7 @@ textarea {
|
|||
}
|
||||
|
||||
.header-actions .button {
|
||||
flex: 1 1 180px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hero,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ require_once __DIR__ . '/includes/bootstrap.php';
|
|||
$message = '';
|
||||
$error = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (website_request_method() === 'POST') {
|
||||
$action = (string)($_POST['action'] ?? '');
|
||||
if (!website_verify_csrf()) {
|
||||
$error = 'Your form expired. Please try again.';
|
||||
|
|
|
|||
|
|
@ -18,14 +18,15 @@ if ($assetPath === null || !is_readable($assetPath)) {
|
|||
exit;
|
||||
}
|
||||
|
||||
$mimeType = match (strtolower(pathinfo($assetPath, PATHINFO_EXTENSION))) {
|
||||
$extension = strtolower(pathinfo($assetPath, PATHINFO_EXTENSION));
|
||||
$mimeTypes = [
|
||||
'png' => 'image/png',
|
||||
'jpg', 'jpeg' => 'image/jpeg',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'webp' => 'image/webp',
|
||||
default => 'application/octet-stream',
|
||||
};
|
||||
];
|
||||
$mimeType = $mimeTypes[$extension] ?? 'application/octet-stream';
|
||||
|
||||
header('Content-Type: ' . $mimeType);
|
||||
header('Content-Length: ' . (string)filesize($assetPath));
|
||||
readfile($assetPath);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ require_once __DIR__ . '/includes/bootstrap.php';
|
|||
$message = '';
|
||||
$error = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (website_request_method() === 'POST') {
|
||||
if (!website_verify_csrf()) {
|
||||
$error = 'Your form expired. Please try again.';
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -340,7 +340,16 @@ function website_fetch_remote_servers(): array
|
|||
return [];
|
||||
}
|
||||
$rows = [];
|
||||
$result = @$db->query("SELECT `remote_server_id`, `remote_server_name`, `agent_ip`, `enabled` FROM `{$table}` ORDER BY `remote_server_name` ASC");
|
||||
$columns = website_table_columns($table);
|
||||
$nameColumn = isset($columns['remote_server_name']) ? 'remote_server_name' : (isset($columns['hostname']) ? 'hostname' : '');
|
||||
$agentColumn = isset($columns['agent_ip']) ? 'agent_ip' : (isset($columns['display_public_ip']) ? 'display_public_ip' : '');
|
||||
$enabledSql = isset($columns['enabled']) ? '`enabled`' : '1 AS `enabled`';
|
||||
if ($nameColumn === '' || $agentColumn === '') {
|
||||
return [];
|
||||
}
|
||||
$safeNameColumn = str_replace('`', '``', $nameColumn);
|
||||
$safeAgentColumn = str_replace('`', '``', $agentColumn);
|
||||
$result = @$db->query("SELECT `remote_server_id`, `{$safeNameColumn}` AS `remote_server_name`, `{$safeAgentColumn}` AS `agent_ip`, {$enabledSql} FROM `{$table}` ORDER BY `{$safeNameColumn}` ASC");
|
||||
if ($result instanceof mysqli_result) {
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$rows[] = $row;
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ function website_start_session(): void
|
|||
$_SESSION['website_last_seen_at'] = $now;
|
||||
}
|
||||
|
||||
function website_config(?string $key = null, mixed $default = null): mixed
|
||||
function website_config(?string $key = null, $default = null)
|
||||
{
|
||||
global $websiteConfig;
|
||||
|
||||
|
|
@ -121,7 +121,53 @@ function website_log(string $message): void
|
|||
error_log('[website] ' . $message);
|
||||
}
|
||||
|
||||
function website_escape(mixed $value): string
|
||||
function website_error_reference(): string
|
||||
{
|
||||
try {
|
||||
return strtoupper(bin2hex(random_bytes(4)));
|
||||
} catch (Throwable $e) {
|
||||
return strtoupper(substr(md5((string)microtime(true)), 0, 8));
|
||||
}
|
||||
}
|
||||
|
||||
function website_render_fatal_error(string $reference): void
|
||||
{
|
||||
if (!headers_sent()) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
}
|
||||
|
||||
echo '<!doctype html><html lang="en"><head><meta charset="utf-8">';
|
||||
echo '<meta name="viewport" content="width=device-width, initial-scale=1">';
|
||||
echo '<title>Website Error - Gameservers.World</title>';
|
||||
echo '<link rel="stylesheet" href="' . website_escape(website_asset('css/site.css')) . '">';
|
||||
echo '</head><body><main class="site-main"><section class="page-heading"><div class="container">';
|
||||
echo '<h1>Something went wrong</h1>';
|
||||
echo '<p>We could not load this page. Please try again or contact support with reference ';
|
||||
echo website_escape($reference) . '.</p>';
|
||||
echo '</div></section></main></body></html>';
|
||||
}
|
||||
|
||||
register_shutdown_function(static function (): void {
|
||||
$error = error_get_last();
|
||||
if (!is_array($error)) {
|
||||
return;
|
||||
}
|
||||
$fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR];
|
||||
if (!in_array((int)$error['type'], $fatalTypes, true)) {
|
||||
return;
|
||||
}
|
||||
$reference = website_error_reference();
|
||||
website_log('Fatal error [' . $reference . '] ' . ($error['message'] ?? 'unknown') . ' in ' . ($error['file'] ?? 'unknown') . ':' . (string)($error['line'] ?? '0'));
|
||||
if (!headers_sent()) {
|
||||
while (ob_get_level() > 0) {
|
||||
ob_end_clean();
|
||||
}
|
||||
website_render_fatal_error($reference);
|
||||
}
|
||||
});
|
||||
|
||||
function website_escape($value): string
|
||||
{
|
||||
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
|
@ -151,6 +197,11 @@ function website_request_scheme(): string
|
|||
return 'http';
|
||||
}
|
||||
|
||||
function website_request_method(): string
|
||||
{
|
||||
return strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? 'GET'));
|
||||
}
|
||||
|
||||
function website_base_path(): string
|
||||
{
|
||||
static $basePath = null;
|
||||
|
|
@ -563,12 +614,12 @@ function website_log_activity(string $message, int $userId = 0, string $eventTyp
|
|||
|
||||
function website_safe_return_path(string $returnPath, string $default = 'index.php'): string
|
||||
{
|
||||
if ($returnPath === '' || preg_match('#^[a-z][a-z0-9+.-]*://#i', $returnPath) === 1 || str_starts_with($returnPath, '//')) {
|
||||
if ($returnPath === '' || preg_match('#^[a-z][a-z0-9+.-]*://#i', $returnPath) === 1 || strpos($returnPath, '//') === 0) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$returnPath = ltrim($returnPath, '/');
|
||||
if (str_contains($returnPath, "\0") || str_starts_with($returnPath, '../') || str_contains($returnPath, '/../')) {
|
||||
if (strpos($returnPath, "\0") !== false || strpos($returnPath, '../') === 0 || strpos($returnPath, '/../') !== false) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
|
|
@ -589,7 +640,7 @@ function website_control_panel_url(string $returnPath = 'home.php?m=dashboard&p=
|
|||
return panel_url(website_safe_return_path($returnPath, 'home.php?m=dashboard&p=dashboard'));
|
||||
}
|
||||
|
||||
function website_order_url(int|string $serviceId): string
|
||||
function website_order_url($serviceId): string
|
||||
{
|
||||
return website_url('order.php?service_id=' . rawurlencode((string)$serviceId));
|
||||
}
|
||||
|
|
@ -627,14 +678,21 @@ function website_fetch_service_by_id(int $serviceId): ?array
|
|||
}
|
||||
|
||||
$safeServiceTable = $db->real_escape_string($serviceTable);
|
||||
$safeConfigTable = $db->real_escape_string($prefix . 'config_homes');
|
||||
$stmt = $db->prepare(
|
||||
"SELECT bs.*, ch.game_name AS cfg_game_name, ch.game_key AS cfg_game_key, ch.home_cfg_file AS cfg_file
|
||||
FROM `{$safeServiceTable}` bs
|
||||
LEFT JOIN `{$safeConfigTable}` ch ON ch.home_cfg_id = bs.home_cfg_id
|
||||
WHERE bs.service_id = ?
|
||||
LIMIT 1"
|
||||
);
|
||||
$configTable = $prefix . 'config_homes';
|
||||
if (website_table_exists($configTable) && website_column_exists($serviceTable, 'home_cfg_id')) {
|
||||
$safeConfigTable = $db->real_escape_string($configTable);
|
||||
$sql = "SELECT bs.*, ch.game_name AS cfg_game_name, ch.game_key AS cfg_game_key, ch.home_cfg_file AS cfg_file
|
||||
FROM `{$safeServiceTable}` bs
|
||||
LEFT JOIN `{$safeConfigTable}` ch ON ch.home_cfg_id = bs.home_cfg_id
|
||||
WHERE bs.service_id = ?
|
||||
LIMIT 1";
|
||||
} else {
|
||||
$sql = "SELECT bs.*, '' AS cfg_game_name, '' AS cfg_game_key, '' AS cfg_file
|
||||
FROM `{$safeServiceTable}` bs
|
||||
WHERE bs.service_id = ?
|
||||
LIMIT 1";
|
||||
}
|
||||
$stmt = $db->prepare($sql);
|
||||
if (!$stmt) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -822,16 +880,36 @@ function website_fetch_services(int $limit = 0, bool $includeDisabled = false):
|
|||
|
||||
$prefix = website_table_prefix();
|
||||
|
||||
$sql = "SELECT bs.*,
|
||||
ch.game_name AS cfg_game_name,
|
||||
ch.game_key AS cfg_game_key,
|
||||
ch.home_cfg_file AS cfg_file
|
||||
FROM `{$prefix}billing_services` bs
|
||||
LEFT JOIN `{$prefix}config_homes` ch ON ch.home_cfg_id = bs.home_cfg_id
|
||||
WHERE " . ($includeDisabled ? '1 = 1' : "bs.enabled = 1
|
||||
AND bs.remote_server_id <> ''
|
||||
AND bs.remote_server_id IS NOT NULL") . "
|
||||
ORDER BY bs.service_name ASC";
|
||||
$serviceTable = $prefix . 'billing_services';
|
||||
$configTable = $prefix . 'config_homes';
|
||||
if (!website_table_exists($serviceTable)) {
|
||||
return [];
|
||||
}
|
||||
$serviceColumns = website_table_columns($serviceTable);
|
||||
$hasEnabled = isset($serviceColumns['enabled']);
|
||||
$hasRemoteServerId = isset($serviceColumns['remote_server_id']);
|
||||
$where = $includeDisabled ? '1 = 1' : '1 = 1';
|
||||
if (!$includeDisabled && $hasEnabled) {
|
||||
$where .= ' AND bs.enabled = 1';
|
||||
}
|
||||
if (!$includeDisabled && $hasRemoteServerId) {
|
||||
$where .= " AND bs.remote_server_id <> '' AND bs.remote_server_id IS NOT NULL";
|
||||
}
|
||||
if (website_table_exists($configTable) && isset($serviceColumns['home_cfg_id'])) {
|
||||
$sql = "SELECT bs.*,
|
||||
ch.game_name AS cfg_game_name,
|
||||
ch.game_key AS cfg_game_key,
|
||||
ch.home_cfg_file AS cfg_file
|
||||
FROM `{$serviceTable}` bs
|
||||
LEFT JOIN `{$configTable}` ch ON ch.home_cfg_id = bs.home_cfg_id
|
||||
WHERE {$where}
|
||||
ORDER BY bs.service_name ASC";
|
||||
} else {
|
||||
$sql = "SELECT bs.*, '' AS cfg_game_name, '' AS cfg_game_key, '' AS cfg_file
|
||||
FROM `{$serviceTable}` bs
|
||||
WHERE {$where}
|
||||
ORDER BY bs.service_name ASC";
|
||||
}
|
||||
|
||||
if ($limit > 0) {
|
||||
$sql .= ' LIMIT ' . max(1, $limit);
|
||||
|
|
|
|||
|
|
@ -5,27 +5,32 @@ declare(strict_types=1);
|
|||
$activePage = $activePage ?? '';
|
||||
$currentUser = website_current_user();
|
||||
$cartCount = website_cart_count();
|
||||
$navLinks = [
|
||||
$publicLinks = [
|
||||
['key' => 'home', 'label' => 'Home', 'href' => website_url('index.php')],
|
||||
['key' => 'servers', 'label' => 'Game Servers', 'href' => website_url('serverlist.php')],
|
||||
['key' => 'docs', 'label' => 'Documentation', 'href' => website_url('docs.php')],
|
||||
['key' => 'pricing', 'label' => 'Pricing', 'href' => website_url('pricing.php')],
|
||||
['key' => 'locations', 'label' => 'Locations', 'href' => website_url('locations.php')],
|
||||
['key' => 'docs', 'label' => 'Documentation', 'href' => website_url('docs.php')],
|
||||
['key' => 'support', 'label' => 'Support', 'href' => website_url('support.php')],
|
||||
];
|
||||
if ($currentUser) {
|
||||
$navLinks[] = ['key' => 'account', 'label' => 'My Account', 'href' => website_url('account.php')];
|
||||
$navLinks[] = ['key' => 'orders', 'label' => 'My Orders', 'href' => website_url('orders.php')];
|
||||
$navLinks[] = ['key' => 'invoices', 'label' => 'My Invoices', 'href' => website_url('invoices.php')];
|
||||
$navLinks[] = ['key' => 'servers', 'label' => 'My Servers', 'href' => website_url('my_servers.php')];
|
||||
if (website_current_user_is_staff()) {
|
||||
$navLinks[] = ['key' => 'staff', 'label' => 'Staff Dashboard', 'href' => website_url('staff.php')];
|
||||
}
|
||||
$accountLinks = [
|
||||
['key' => 'account', 'label' => 'My Account', 'href' => website_url('my_account.php')],
|
||||
['key' => 'orders', 'label' => 'My Orders', 'href' => website_url('my_orders.php')],
|
||||
['key' => 'servers', 'label' => 'My Servers', 'href' => website_url('my_servers.php')],
|
||||
['key' => 'cart', 'label' => 'Cart' . ($cartCount > 0 ? ' (' . $cartCount . ')' : ''), 'href' => website_cart_url()],
|
||||
['key' => 'logout', 'label' => 'Logout', 'href' => website_url('logout.php')],
|
||||
];
|
||||
} else {
|
||||
$navLinks[] = ['key' => 'account', 'label' => 'Login', 'href' => website_login_url()];
|
||||
$navLinks[] = ['key' => 'register', 'label' => 'Create Account', 'href' => website_register_url()];
|
||||
$accountLinks = [
|
||||
['key' => 'account', 'label' => 'Login', 'href' => website_login_url()],
|
||||
['key' => 'register', 'label' => 'Create Account', 'href' => website_register_url()],
|
||||
['key' => 'cart', 'label' => 'Cart' . ($cartCount > 0 ? ' (' . $cartCount . ')' : ''), 'href' => website_cart_url()],
|
||||
];
|
||||
}
|
||||
$navLinks[] = ['key' => 'cart', 'label' => 'Cart' . ($cartCount > 0 ? ' (' . $cartCount . ')' : ''), 'href' => website_cart_url()];
|
||||
$staffLinks = ($currentUser && website_current_user_is_staff()) ? [
|
||||
['key' => 'staff', 'label' => 'Staff Dashboard', 'href' => website_url('staff.php')],
|
||||
] : [];
|
||||
?>
|
||||
<header class="site-header">
|
||||
<div class="container header-shell">
|
||||
|
|
@ -44,20 +49,35 @@ $navLinks[] = ['key' => 'cart', 'label' => 'Cart' . ($cartCount > 0 ? ' (' . $ca
|
|||
<span class="sr-only">Toggle navigation</span>
|
||||
</button>
|
||||
|
||||
<nav class="primary-nav" id="primary-nav" data-nav-menu>
|
||||
<?php foreach ($navLinks as $link): ?>
|
||||
<nav class="primary-nav" id="primary-nav" data-nav-menu aria-label="Primary navigation">
|
||||
<div class="nav-group nav-group-public">
|
||||
<?php foreach ($publicLinks as $link): ?>
|
||||
<a class="nav-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
|
||||
<?= website_escape($link['label']) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="nav-group nav-group-account" aria-label="Account navigation">
|
||||
<?php foreach ($accountLinks as $link): ?>
|
||||
<a class="nav-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
|
||||
<?= website_escape($link['label']) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php if (!empty($staffLinks)): ?>
|
||||
<div class="nav-group nav-group-staff" aria-label="Staff navigation">
|
||||
<?php foreach ($staffLinks as $link): ?>
|
||||
<a class="nav-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
|
||||
<?= website_escape($link['label']) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
|
||||
<div class="header-actions" data-header-actions>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_custom_project_url()) ?>">Custom Projects</a>
|
||||
<a class="button button-primary" href="<?= website_escape(website_control_panel_url()) ?>">Control Panel</a>
|
||||
<?php if ($currentUser): ?>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_url('logout.php')) ?>">Logout</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
|
||||
$error = '';
|
||||
$returnPath = website_safe_return_path((string)($_GET['return'] ?? $_POST['return'] ?? $_SESSION['website_login_return'] ?? 'account.php'), 'account.php');
|
||||
$returnPath = website_safe_return_path((string)($_GET['return'] ?? $_POST['return'] ?? $_SESSION['website_login_return'] ?? 'my_account.php'), 'my_account.php');
|
||||
|
||||
if (website_is_logged_in()) {
|
||||
if ($returnPath === 'panel') {
|
||||
|
|
@ -16,7 +16,7 @@ if (website_is_logged_in()) {
|
|||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (website_request_method() === 'POST') {
|
||||
if (!website_verify_csrf()) {
|
||||
$error = 'Your form expired. Please try again.';
|
||||
} else {
|
||||
|
|
|
|||
4
Panel/modules/website/my_account.php
Normal file
4
Panel/modules/website/my_account.php
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/account.php';
|
||||
4
Panel/modules/website/my_orders.php
Normal file
4
Panel/modules/website/my_orders.php
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/orders.php';
|
||||
|
|
@ -28,7 +28,7 @@ $locations = website_service_locations($service);
|
|||
$minSlots = website_service_min_slots($service);
|
||||
$maxSlots = website_service_max_slots($service);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (website_request_method() === 'POST') {
|
||||
$slots = filter_input(INPUT_POST, 'slots', FILTER_VALIDATE_INT);
|
||||
$locationId = trim((string)($_POST['location_id'] ?? ''));
|
||||
$durationMonths = filter_input(INPUT_POST, 'duration_months', FILTER_VALIDATE_INT);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ $isStaff = website_current_user_is_staff();
|
|||
<section class="page-heading">
|
||||
<div class="container">
|
||||
<h1>My Account</h1>
|
||||
<p>Signed in as <?= website_escape($userName) ?>. Use these links to manage servers, order hosting, request support, or open the GSP control panel.</p>
|
||||
<p>Signed in as <?= website_escape($userName) ?>. Use these links for website billing, server orders, support, staff tools, and the GSP control panel.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -16,10 +16,13 @@ $isStaff = website_current_user_is_staff();
|
|||
<div class="container">
|
||||
<div class="summary-grid">
|
||||
<article class="summary-card">
|
||||
<h3>My Servers</h3>
|
||||
<p>Open the GSP control panel to manage assigned game servers, files, logs, updates, backups, and server state.</p>
|
||||
<h3>Customer Account</h3>
|
||||
<p>Review orders, invoices, cart items, and servers tied to your Gameservers.World account.</p>
|
||||
<div class="card-actions">
|
||||
<a class="button button-primary" href="<?= website_escape(panel_url('home.php?m=gamemanager&p=game_monitor')) ?>">Open My Servers</a>
|
||||
<a class="button button-primary" href="<?= website_escape(website_url('orders.php')) ?>">My Orders</a>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_url('invoices.php')) ?>">My Invoices</a>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_url('my_servers.php')) ?>">My Servers</a>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_cart_url()) ?>">Cart</a>
|
||||
</div>
|
||||
</article>
|
||||
<article class="summary-card">
|
||||
|
|
@ -39,13 +42,23 @@ $isStaff = website_current_user_is_staff();
|
|||
</article>
|
||||
<?php if ($isStaff): ?>
|
||||
<article class="summary-card">
|
||||
<h3>Staff Access</h3>
|
||||
<p>Staff links are shown only after account role checks. Server-side authorization still applies inside the Panel.</p>
|
||||
<h3>Website Staff</h3>
|
||||
<p>Manage website sales, catalog, prices, locations, orders, invoices, payments, coupons, PayPal settings, and provisioning.</p>
|
||||
<div class="card-actions">
|
||||
<a class="button button-secondary" href="<?= website_escape(panel_url('home.php?m=administration&p=watch_logger')) ?>">Panel Administration</a>
|
||||
<a class="button button-primary" href="<?= website_escape(website_url('staff.php')) ?>">Staff Dashboard</a>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_url('staff_services.php')) ?>">Manage Services</a>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_url('staff_orders.php')) ?>">Manage Orders</a>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_url('staff_invoices.php')) ?>">Manage Invoices</a>
|
||||
</div>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
<article class="summary-card">
|
||||
<h3>Game Server Panel</h3>
|
||||
<p>Open the GSP control panel for live server controls, files, logs, updates, backups, and operational management.</p>
|
||||
<div class="card-actions">
|
||||
<a class="button button-primary" href="<?= website_escape(website_control_panel_url()) ?>">Open Control Panel</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,6 @@ declare(strict_types=1);
|
|||
</form>
|
||||
<p><a href="<?= website_escape(website_url('forgot_password.php')) ?>">Forgot your password?</a></p>
|
||||
<p class="muted">Gameservers.World and the GSP Panel use the same account credentials, but they keep separate secure sessions. You may be asked to log in separately when opening the Panel.</p>
|
||||
<p class="muted">Need an account? <a href="<?= website_escape(website_register_url($returnPath)) ?>">Create one through the GSP Panel registration page.</a></p>
|
||||
<p class="muted">Need an account? <a href="<?= website_escape(website_register_url($returnPath)) ?>">Create one for Gameservers.World.</a></p>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ require_once __DIR__ . '/includes/bootstrap.php';
|
|||
$error = '';
|
||||
$returnPath = website_safe_return_path((string)($_GET['return'] ?? $_POST['return'] ?? $_SESSION['website_login_return'] ?? 'account.php'), 'account.php');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (website_request_method() === 'POST') {
|
||||
if (!website_verify_csrf()) {
|
||||
$error = 'Your form expired. Please try again.';
|
||||
} else {
|
||||
|
|
@ -44,7 +44,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
$insertColumns[] = 'users_theme';
|
||||
$values[] = 'SimpleBootstrap';
|
||||
}
|
||||
$columnSql = '`' . implode('`, `', array_map(static fn($column) => str_replace('`', '``', $column), $insertColumns)) . '`';
|
||||
$safeColumns = array_map(static function ($column) {
|
||||
return str_replace('`', '``', $column);
|
||||
}, $insertColumns);
|
||||
$columnSql = '`' . implode('`, `', $safeColumns) . '`';
|
||||
$placeholders = implode(', ', array_fill(0, count($values), '?'));
|
||||
$stmt = $db->prepare("INSERT INTO `{$usersTable}` ({$columnSql}) VALUES ({$placeholders})");
|
||||
if ($stmt) {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ if ($db instanceof mysqli && $tokenHash !== '' && website_table_exists(website_t
|
|||
}
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $valid) {
|
||||
if (website_request_method() === 'POST' && $valid) {
|
||||
if (!website_verify_csrf()) {
|
||||
$error = 'Your form expired. Please try again.';
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/bootstrap.php'; website_require_staff();
|
||||
$db=website_db(); $message=''; $error=''; $table=website_table('billing_coupons');
|
||||
if($_SERVER['REQUEST_METHOD']==='POST'){ if(!website_verify_csrf())$error='Invalid CSRF token.'; elseif($db instanceof mysqli && website_table_exists($table)){ $code=strtoupper(trim((string)$_POST['code'])); $name=trim((string)$_POST['name']); $value=(float)$_POST['discount_value']; $active=!empty($_POST['is_active'])?1:0; $max=($_POST['max_uses']===''?null:(int)$_POST['max_uses']); $stmt=$db->prepare("INSERT INTO `{$table}` (`code`,`name`,`discount_type`,`discount_value`,`is_active`,`max_uses`,`created_at`) VALUES (?,?,'percent',?,?,?,NOW()) ON DUPLICATE KEY UPDATE `name`=VALUES(`name`),`discount_value`=VALUES(`discount_value`),`is_active`=VALUES(`is_active`),`max_uses`=VALUES(`max_uses`)"); if($stmt){$stmt->bind_param('ssdii',$code,$name,$value,$active,$max);$stmt->execute();$stmt->close();$message='Coupon saved.';}}}
|
||||
if(website_request_method()==='POST'){ if(!website_verify_csrf())$error='Invalid CSRF token.'; elseif($db instanceof mysqli && website_table_exists($table)){ $code=strtoupper(trim((string)$_POST['code'])); $name=trim((string)$_POST['name']); $value=(float)$_POST['discount_value']; $active=!empty($_POST['is_active'])?1:0; $max=($_POST['max_uses']===''?null:(int)$_POST['max_uses']); $stmt=$db->prepare("INSERT INTO `{$table}` (`code`,`name`,`discount_type`,`discount_value`,`is_active`,`max_uses`,`created_at`) VALUES (?,?,'percent',?,?,?,NOW()) ON DUPLICATE KEY UPDATE `name`=VALUES(`name`),`discount_value`=VALUES(`discount_value`),`is_active`=VALUES(`is_active`),`max_uses`=VALUES(`max_uses`)"); if($stmt){$stmt->bind_param('ssdii',$code,$name,$value,$active,$max);$stmt->execute();$stmt->close();$message='Coupon saved.';}}}
|
||||
$coupons=[]; if($db instanceof mysqli && website_table_exists($table)){ $r=$db->query("SELECT * FROM `{$table}` ORDER BY `coupon_id` DESC"); while($r instanceof mysqli_result && ($row=$r->fetch_assoc()))$coupons[]=$row; }
|
||||
website_render('staff_coupons.php',['activePage'=>'staff','pageTitle'=>'Coupons - Gameservers.World','canonicalPath'=>'staff_coupons.php','message'=>$message,'error'=>$error,'coupons'=>$coupons]);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ declare(strict_types=1);
|
|||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
website_require_staff();
|
||||
$ran = false; $errors = [];
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (website_request_method() === 'POST') {
|
||||
if (website_verify_csrf()) { $errors = website_run_billing_migrations(); $ran = true; }
|
||||
else { $errors = ['Invalid CSRF token.']; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ declare(strict_types=1);
|
|||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
website_require_staff();
|
||||
$message=''; $error='';
|
||||
if ($_SERVER['REQUEST_METHOD']==='POST') {
|
||||
if (website_request_method()==='POST') {
|
||||
if(!website_verify_csrf()) $error='Invalid CSRF token.';
|
||||
else {
|
||||
website_set_setting('paypal_enabled', !empty($_POST['enabled'])?'1':'0');
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
<?php
|
||||
declare(strict_types=1); require_once __DIR__ . '/includes/bootstrap.php'; website_require_staff(); $db=website_db(); $message=''; $table=website_table('billing_orders'); if($_SERVER['REQUEST_METHOD']==='POST' && website_verify_csrf() && $db instanceof mysqli && website_table_exists($table)){ $orderId=(int)$_POST['order_id']; $claim=bin2hex(random_bytes(12)); $stmt=$db->prepare("UPDATE `{$table}` SET `status`='paid', `provisioning_claim`=NULL, `provisioning_error`=NULL WHERE `order_id`=? AND `status` IN ('failed','paid')"); if($stmt){$stmt->bind_param('i',$orderId);$stmt->execute();$stmt->close();$message='Order queued for provisioning retry.';}} $rows=[]; if($db instanceof mysqli && website_table_exists($table)){ $r=$db->query("SELECT * FROM `{$table}` WHERE `status` IN ('paid','provisioning','failed','installed','active') ORDER BY `order_id` DESC LIMIT 100"); while($r instanceof mysqli_result && ($row=$r->fetch_assoc()))$rows[]=$row; } website_render('staff_provisioning.php',['activePage'=>'staff','pageTitle'=>'Provisioning Queue - Gameservers.World','canonicalPath'=>'staff_provisioning.php','orders'=>$rows,'message'=>$message]);
|
||||
declare(strict_types=1); require_once __DIR__ . '/includes/bootstrap.php'; website_require_staff(); $db=website_db(); $message=''; $table=website_table('billing_orders'); if(website_request_method()==='POST' && website_verify_csrf() && $db instanceof mysqli && website_table_exists($table)){ $orderId=(int)$_POST['order_id']; $claim=bin2hex(random_bytes(12)); $stmt=$db->prepare("UPDATE `{$table}` SET `status`='paid', `provisioning_claim`=NULL, `provisioning_error`=NULL WHERE `order_id`=? AND `status` IN ('failed','paid')"); if($stmt){$stmt->bind_param('i',$orderId);$stmt->execute();$stmt->close();$message='Order queued for provisioning retry.';}} $rows=[]; if($db instanceof mysqli && website_table_exists($table)){ $r=$db->query("SELECT * FROM `{$table}` WHERE `status` IN ('paid','provisioning','failed','installed','active') ORDER BY `order_id` DESC LIMIT 100"); while($r instanceof mysqli_result && ($row=$r->fetch_assoc()))$rows[]=$row; } website_render('staff_provisioning.php',['activePage'=>'staff','pageTitle'=>'Provisioning Queue - Gameservers.World','canonicalPath'=>'staff_provisioning.php','orders'=>$rows,'message'=>$message]);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ declare(strict_types=1);
|
|||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
website_require_staff();
|
||||
$db = website_db(); $message=''; $error='';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (website_request_method() === 'POST') {
|
||||
if (!website_verify_csrf()) { $error='Invalid CSRF token.'; }
|
||||
elseif ($db instanceof mysqli && isset($_POST['service']) && is_array($_POST['service'])) {
|
||||
$table = website_table('billing_services');
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ Website login verifies credentials against the existing Panel password hash form
|
|||
|
||||
`Panel/modules/website/sso.php` and `Panel/sso.php` are retained only as compatibility redirects for old links. Active navigation must not depend on them.
|
||||
|
||||
Successful website login redirects to `my_account.php` unless a safe internal
|
||||
return path was stored, such as `cart.php?checkout=1`. `account.php` is retained
|
||||
as a compatibility account entry point.
|
||||
|
||||
## Ordering
|
||||
|
||||
The current public catalog route is `serverlist.php`. Customer-facing Order buttons must use:
|
||||
|
|
@ -73,7 +77,17 @@ Website footer account links are state-aware:
|
|||
- logged in: `My Account`, `Order a Server`, `Control Panel`, `My Servers`, `Log Out`
|
||||
- staff-only links appear only for Panel admin users and still enforce website staff authorization server-side
|
||||
|
||||
The website main navigation also includes visible `Login`, `Create Account`, and `Cart` entries when appropriate. Control Panel links point directly to the configured Panel domain. `My Servers` opens a website customer page that summarizes website orders and links to the Panel for live server controls. Staff Dashboard opens the website sales/billing staff area, not Panel activity logging.
|
||||
The shared header groups navigation by purpose:
|
||||
|
||||
- public: Home, Game Servers, Pricing, Locations, Documentation, Support
|
||||
- account: Login/Create Account/Cart or My Account/My Orders/My Servers/Cart/Logout
|
||||
- staff: Staff Dashboard, only for authorized website staff
|
||||
- actions: Custom Projects and Control Panel
|
||||
|
||||
Control Panel links point directly to the configured Panel domain. `My Servers`
|
||||
opens a website customer page that summarizes website orders and links to the
|
||||
Panel for live server controls. Staff Dashboard opens the website sales/billing
|
||||
staff area, not Panel activity logging.
|
||||
|
||||
## Deployment
|
||||
|
||||
|
|
@ -100,7 +114,9 @@ Recommended:
|
|||
- `forgot_password.php`
|
||||
- `reset_password.php`
|
||||
- `account.php`
|
||||
- `my_account.php`
|
||||
- `orders.php`
|
||||
- `my_orders.php`
|
||||
- `invoices.php`
|
||||
- `my_servers.php`
|
||||
- `order.php`
|
||||
|
|
|
|||
|
|
@ -216,3 +216,72 @@ Customer-facing account pages:
|
|||
- Paid order appears in provisioning queue.
|
||||
- Customer orders, invoices, and My Servers pages show only the authenticated user's records.
|
||||
- No hardcoded credentials or raw PHP errors are displayed.
|
||||
|
||||
## HTTP 500 Troubleshooting
|
||||
|
||||
### Login remains on `login.php`
|
||||
|
||||
Check:
|
||||
|
||||
- submitted field names are `login`, `password`, and `csrf_token`
|
||||
- CSRF token validates
|
||||
- the Panel user exists in the configured `users` table
|
||||
- `users_passwd` matches the current Panel-compatible hash
|
||||
- account role is not `banned`
|
||||
- no output is sent before the redirect
|
||||
- successful login redirects to `my_account.php` unless a safe internal return path is stored
|
||||
|
||||
The website also provides `account.php` and `my_account.php` as compatible account routes.
|
||||
|
||||
### `staff.php` HTTP 500 or blank output
|
||||
|
||||
Common causes:
|
||||
|
||||
- PHP 8-only syntax deployed on a PHP 7.x host
|
||||
- missing production files from `Panel/modules/website/pages/`
|
||||
- missing `includes/billing.php`
|
||||
- fatal error suppressed by production `display_errors=0`
|
||||
- non-admin account expecting staff access
|
||||
|
||||
The website bootstrap avoids PHP 8-only constructs and installs a shutdown handler that logs fatal errors with a short reference ID. Staff pages must render through shared website templates and must not point to the Panel activity logger.
|
||||
|
||||
### `staff_services.php` HTTP 500
|
||||
|
||||
Check:
|
||||
|
||||
- `billing_services` exists
|
||||
- staff migrations have been run
|
||||
- expected catalog columns exist or can be added by the idempotent migration runner
|
||||
- `remote_servers` schema is the current Panel schema
|
||||
- the page is not assuming `remote_servers.enabled` exists
|
||||
|
||||
The current helper treats missing `remote_servers.enabled` as enabled for display and lets staff assign locations from current Panel remote-server rows.
|
||||
|
||||
### `order.php?service_id=...` HTTP 500
|
||||
|
||||
Check:
|
||||
|
||||
- `billing_services` exists
|
||||
- the requested `service_id` exists
|
||||
- the service is enabled if the `enabled` column exists
|
||||
- `remote_server_id` contains at least one valid location ID
|
||||
- configured slot columns are present or defaults are acceptable
|
||||
- `config_homes` exists when game metadata is needed
|
||||
|
||||
The order page no longer requires `config_homes`; it can render from the service row alone. Missing or disabled services produce a styled unavailable page instead of blank output.
|
||||
|
||||
### Wrong Panel Administration URL
|
||||
|
||||
Website staff administration must link to:
|
||||
|
||||
- `staff.php`
|
||||
|
||||
It must not link to:
|
||||
|
||||
- `home.php?m=administration&p=watch_logger`
|
||||
|
||||
The Panel activity logger is operational administration, not website sales/billing administration.
|
||||
|
||||
### Log Locations
|
||||
|
||||
Production PHP or web-server logs are deployment-specific. Check Apache, Nginx, PHP-FPM, cPanel `error_log`, and PHP `error_log` paths. The local repository does not contain live web-server error logs.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue