1019 lines
29 KiB
PHP
1019 lines
29 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/paths.php';
|
|
|
|
if (defined('GSP_WEBSITE_BOOTSTRAPPED')) {
|
|
return;
|
|
}
|
|
|
|
define('GSP_WEBSITE_BOOTSTRAPPED', true);
|
|
|
|
error_reporting(E_ALL);
|
|
ini_set('display_errors', '0');
|
|
|
|
$websiteConfig = [];
|
|
$websiteConfigFiles = [
|
|
WEBSITE_CONFIG_DIR . '/pricing.php',
|
|
WEBSITE_CONFIG_DIR . '/services.php',
|
|
WEBSITE_CONFIG_DIR . '/config.php',
|
|
WEBSITE_CONFIG_DIR . '/config.local.php',
|
|
];
|
|
|
|
foreach ($websiteConfigFiles as $configFile) {
|
|
if (!is_readable($configFile)) {
|
|
continue;
|
|
}
|
|
|
|
$loaded = require $configFile;
|
|
if (is_array($loaded)) {
|
|
$websiteConfig = array_replace_recursive($websiteConfig, $loaded);
|
|
}
|
|
}
|
|
|
|
if (is_readable(__DIR__ . '/billing.php')) {
|
|
require_once __DIR__ . '/billing.php';
|
|
}
|
|
|
|
$websiteDefaults = [
|
|
'site_name' => 'Gameservers.World',
|
|
'site_tagline' => 'Developer-backed game hosting for modern and legacy communities, with full server access, daily backups, and optional custom engineering help through Runlevel Systems.',
|
|
'meta_description' => 'Affordable game servers for modern and legacy communities, backed by developers, software engineers, and infrastructure specialists. Launch a standard server or get help with mods, automation, integrations, and custom tooling.',
|
|
'base_path' => null,
|
|
'public_base_url' => null,
|
|
'billing_base_url' => '/billing',
|
|
'panel_url' => 'https://panel.iaregamer.com/',
|
|
'login_url' => 'https://panel.iaregamer.com/',
|
|
'company' => [
|
|
'name' => 'Runlevel Systems',
|
|
'url' => 'https://runlevelsystems.com/',
|
|
'copyright' => "\u{00A9} 2026 Runlevel Systems",
|
|
],
|
|
'services' => [
|
|
'project_request_url' => 'https://runlevelsystems.com/start-project.php',
|
|
],
|
|
'discord_url' => null,
|
|
'support_url' => null,
|
|
'support_email' => null,
|
|
'admin_notice' => 'Server catalog is currently unavailable. Please contact support.',
|
|
'locations' => [
|
|
['name' => 'Los Angeles, USA', 'region' => 'West Coast coverage', 'host' => 'la-game-1.iaregamer.com'],
|
|
['name' => 'Kansas City, USA', 'region' => 'Central US coverage', 'host' => 'kc-game-2.iaregamer.com'],
|
|
['name' => 'Dallas, USA', 'region' => 'Southern US coverage', 'host' => 'dal-game-1.iaregamer.com'],
|
|
['name' => 'New York City, USA', 'region' => 'East Coast coverage', 'host' => 'nyc-game-1.iaregamer.com'],
|
|
['name' => 'Dublin, Ireland', 'region' => 'EU coverage', 'host' => 'dub-game-1.iaregamer.com'],
|
|
],
|
|
];
|
|
|
|
$websiteConfig = array_replace_recursive($websiteDefaults, $websiteConfig);
|
|
|
|
function website_start_session(): void
|
|
{
|
|
if (session_status() === PHP_SESSION_ACTIVE) {
|
|
return;
|
|
}
|
|
|
|
$secure = website_request_scheme() === 'https';
|
|
session_set_cookie_params([
|
|
'lifetime' => 0,
|
|
'path' => website_base_path() === '' ? '/' : website_base_path(),
|
|
'secure' => $secure,
|
|
'httponly' => true,
|
|
'samesite' => 'Lax',
|
|
]);
|
|
session_start();
|
|
|
|
$now = time();
|
|
$inactiveLimit = 3600;
|
|
$absoluteLimit = 43200;
|
|
if (!isset($_SESSION['website_session_started_at'])) {
|
|
$_SESSION['website_session_started_at'] = $now;
|
|
}
|
|
if (isset($_SESSION['website_last_seen_at']) && ($now - (int)$_SESSION['website_last_seen_at']) > $inactiveLimit) {
|
|
$_SESSION = [];
|
|
session_destroy();
|
|
session_start();
|
|
$_SESSION['website_session_started_at'] = $now;
|
|
}
|
|
if (($now - (int)($_SESSION['website_session_started_at'] ?? $now)) > $absoluteLimit) {
|
|
$_SESSION = [];
|
|
session_destroy();
|
|
session_start();
|
|
$_SESSION['website_session_started_at'] = $now;
|
|
}
|
|
$_SESSION['website_last_seen_at'] = $now;
|
|
}
|
|
|
|
function website_config(?string $key = null, $default = null)
|
|
{
|
|
global $websiteConfig;
|
|
|
|
if ($key === null) {
|
|
return $websiteConfig;
|
|
}
|
|
|
|
return $websiteConfig[$key] ?? $default;
|
|
}
|
|
|
|
function website_log(string $message): void
|
|
{
|
|
error_log('[website] ' . $message);
|
|
}
|
|
|
|
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');
|
|
}
|
|
|
|
function website_normalize_base_path(?string $path): string
|
|
{
|
|
$path = trim((string)$path);
|
|
if ($path === '' || $path === '/') {
|
|
return '';
|
|
}
|
|
|
|
return '/' . trim($path, '/');
|
|
}
|
|
|
|
function website_request_scheme(): string
|
|
{
|
|
$https = $_SERVER['HTTPS'] ?? '';
|
|
if ($https !== '' && strtolower((string)$https) !== 'off') {
|
|
return 'https';
|
|
}
|
|
|
|
$forwarded = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '';
|
|
if ($forwarded !== '') {
|
|
return strtolower((string)$forwarded) === 'https' ? 'https' : 'http';
|
|
}
|
|
|
|
return 'http';
|
|
}
|
|
|
|
function website_request_method(): string
|
|
{
|
|
return strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? 'GET'));
|
|
}
|
|
|
|
function website_base_path(): string
|
|
{
|
|
static $basePath = null;
|
|
if ($basePath !== null) {
|
|
return $basePath;
|
|
}
|
|
|
|
$configured = website_config('base_path');
|
|
if (is_string($configured) && $configured !== '') {
|
|
$basePath = website_normalize_base_path($configured);
|
|
return $basePath;
|
|
}
|
|
|
|
$scriptName = (string)($_SERVER['SCRIPT_NAME'] ?? '');
|
|
if ($scriptName === '') {
|
|
$basePath = '';
|
|
return $basePath;
|
|
}
|
|
|
|
$dir = str_replace('\\', '/', dirname($scriptName));
|
|
$basePath = ($dir === '/' || $dir === '.' || $dir === '') ? '' : website_normalize_base_path($dir);
|
|
return $basePath;
|
|
}
|
|
|
|
function website_public_base_url(): string
|
|
{
|
|
static $baseUrl = null;
|
|
if ($baseUrl !== null) {
|
|
return $baseUrl;
|
|
}
|
|
|
|
$configured = trim((string)website_config('public_base_url', ''));
|
|
if ($configured !== '') {
|
|
$baseUrl = rtrim($configured, '/');
|
|
return $baseUrl;
|
|
}
|
|
|
|
$host = trim((string)($_SERVER['HTTP_HOST'] ?? ''));
|
|
if ($host === '') {
|
|
$baseUrl = '';
|
|
return $baseUrl;
|
|
}
|
|
|
|
$baseUrl = website_request_scheme() . '://' . $host . website_base_path();
|
|
return $baseUrl;
|
|
}
|
|
|
|
function website_url(string $path = ''): string
|
|
{
|
|
$basePath = rtrim(website_base_path(), '/');
|
|
$path = ltrim($path, '/');
|
|
if ($path === '') {
|
|
return $basePath === '' ? '/' : $basePath . '/';
|
|
}
|
|
|
|
return ($basePath === '' ? '' : $basePath) . '/' . $path;
|
|
}
|
|
|
|
function website_asset(string $path): string
|
|
{
|
|
return website_url('assets/' . ltrim($path, '/'));
|
|
}
|
|
|
|
function website_join_external_url(string $base, string $path = ''): string
|
|
{
|
|
$base = trim($base);
|
|
if ($base === '') {
|
|
return website_url($path);
|
|
}
|
|
|
|
$base = rtrim($base, '/');
|
|
$path = ltrim($path, '/');
|
|
if ($path === '') {
|
|
return $base . '/';
|
|
}
|
|
|
|
return $base . '/' . $path;
|
|
}
|
|
|
|
function panel_url(string $path = ''): string
|
|
{
|
|
return website_join_external_url((string)website_config('panel_url', ''), $path);
|
|
}
|
|
|
|
function login_url(string $path = ''): string
|
|
{
|
|
return website_join_external_url((string)website_config('login_url', website_config('panel_url', '')), $path);
|
|
}
|
|
|
|
function billing_url(string $path = ''): string
|
|
{
|
|
return website_join_external_url((string)website_config('billing_base_url', ''), $path);
|
|
}
|
|
|
|
function documentation_url(?string $docSlug = null): string
|
|
{
|
|
if ($docSlug === null || $docSlug === '') {
|
|
return website_url('docs.php');
|
|
}
|
|
|
|
return website_url('docs.php?doc=' . rawurlencode($docSlug));
|
|
}
|
|
|
|
function website_canonical_url(string $path = ''): string
|
|
{
|
|
$base = website_public_base_url();
|
|
if ($base === '') {
|
|
return website_url($path);
|
|
}
|
|
|
|
$path = ltrim($path, '/');
|
|
if ($path === '') {
|
|
return $base . '/';
|
|
}
|
|
|
|
return rtrim($base, '/') . '/' . $path;
|
|
}
|
|
|
|
function website_read_php_assignments(string $filePath, array $variableNames): array
|
|
{
|
|
if (!is_readable($filePath)) {
|
|
return [];
|
|
}
|
|
|
|
$content = @file_get_contents($filePath);
|
|
if ($content === false) {
|
|
return [];
|
|
}
|
|
|
|
$result = [];
|
|
foreach ($variableNames as $variableName) {
|
|
$patternDouble = '/^\s*\$' . preg_quote($variableName, '/') . '\s*=\s*"([^"]*)"/m';
|
|
$patternSingle = '/^\s*\$' . preg_quote($variableName, '/') . "\s*=\s*'([^']*)'/m";
|
|
if (preg_match($patternDouble, $content, $match) === 1 || preg_match($patternSingle, $content, $match) === 1) {
|
|
$result[$variableName] = $match[1];
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
function website_database_settings(): ?array
|
|
{
|
|
static $settings = null;
|
|
static $resolved = false;
|
|
|
|
if ($resolved) {
|
|
return $settings;
|
|
}
|
|
|
|
$resolved = true;
|
|
$keys = ['db_host', 'db_port', 'db_user', 'db_pass', 'db_name', 'table_prefix', 'db_type'];
|
|
$merged = [];
|
|
|
|
$panelConfig = WEBSITE_PANEL_INCLUDE_DIR . '/config.inc.php';
|
|
if (is_readable($panelConfig)) {
|
|
$merged = array_replace($merged, website_read_php_assignments($panelConfig, $keys));
|
|
}
|
|
|
|
$billingConfig = WEBSITE_BILLING_ROOT . '/includes/config.inc.php';
|
|
if (is_readable($billingConfig)) {
|
|
$merged = array_replace($merged, website_read_php_assignments($billingConfig, $keys));
|
|
}
|
|
|
|
foreach (['db_host', 'db_user', 'db_name'] as $requiredKey) {
|
|
if (empty($merged[$requiredKey])) {
|
|
$settings = null;
|
|
return $settings;
|
|
}
|
|
}
|
|
|
|
$settings = $merged;
|
|
return $settings;
|
|
}
|
|
|
|
function website_billing_config_present(): bool
|
|
{
|
|
return is_readable(WEBSITE_BILLING_ROOT . '/includes/config.inc.php');
|
|
}
|
|
|
|
function website_db(): ?mysqli
|
|
{
|
|
static $connection = false;
|
|
if ($connection instanceof mysqli) {
|
|
return $connection;
|
|
}
|
|
if ($connection === null) {
|
|
return null;
|
|
}
|
|
|
|
$settings = website_database_settings();
|
|
if ($settings === null) {
|
|
$connection = null;
|
|
return null;
|
|
}
|
|
|
|
$port = isset($settings['db_port']) && $settings['db_port'] !== '' ? (int)$settings['db_port'] : null;
|
|
$mysqli = @mysqli_connect(
|
|
(string)$settings['db_host'],
|
|
(string)($settings['db_user'] ?? ''),
|
|
(string)($settings['db_pass'] ?? ''),
|
|
(string)$settings['db_name'],
|
|
$port
|
|
);
|
|
|
|
if (!$mysqli instanceof mysqli) {
|
|
website_log('Database connection failed for public website.');
|
|
$connection = null;
|
|
return null;
|
|
}
|
|
|
|
@mysqli_set_charset($mysqli, 'utf8mb4');
|
|
$connection = $mysqli;
|
|
return $connection;
|
|
}
|
|
|
|
function website_table_prefix(): string
|
|
{
|
|
$settings = website_database_settings();
|
|
return (string)($settings['table_prefix'] ?? '');
|
|
}
|
|
|
|
function website_billing_available(): bool
|
|
{
|
|
return website_db() instanceof mysqli;
|
|
}
|
|
|
|
function website_table_exists(string $tableName): bool
|
|
{
|
|
$db = website_db();
|
|
if (!$db instanceof mysqli || $tableName === '') {
|
|
return false;
|
|
}
|
|
|
|
$stmt = $db->prepare('SHOW TABLES LIKE ?');
|
|
if (!$stmt) {
|
|
return false;
|
|
}
|
|
$stmt->bind_param('s', $tableName);
|
|
$stmt->execute();
|
|
$result = $stmt->get_result();
|
|
$exists = $result instanceof mysqli_result && $result->num_rows > 0;
|
|
$stmt->close();
|
|
return $exists;
|
|
}
|
|
|
|
function website_table_columns(string $tableName): array
|
|
{
|
|
$db = website_db();
|
|
if (!$db instanceof mysqli || $tableName === '') {
|
|
return [];
|
|
}
|
|
|
|
$safeTable = str_replace('`', '``', $tableName);
|
|
$result = @$db->query("SHOW COLUMNS FROM `{$safeTable}`");
|
|
if (!$result instanceof mysqli_result) {
|
|
return [];
|
|
}
|
|
|
|
$columns = [];
|
|
while ($row = $result->fetch_assoc()) {
|
|
$columns[(string)$row['Field']] = true;
|
|
}
|
|
$result->free();
|
|
return $columns;
|
|
}
|
|
|
|
function website_panel_user_by_id(int $userId): ?array
|
|
{
|
|
$db = website_db();
|
|
$prefix = website_table_prefix();
|
|
if (!$db instanceof mysqli || $userId <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$table = $db->real_escape_string($prefix . 'users');
|
|
$stmt = $db->prepare("SELECT * FROM `{$table}` WHERE `user_id` = ? LIMIT 1");
|
|
if (!$stmt) {
|
|
return null;
|
|
}
|
|
$stmt->bind_param('i', $userId);
|
|
$stmt->execute();
|
|
$result = $stmt->get_result();
|
|
$user = $result instanceof mysqli_result ? $result->fetch_assoc() : null;
|
|
$stmt->close();
|
|
return is_array($user) ? $user : null;
|
|
}
|
|
|
|
function website_panel_user_by_login(string $login): ?array
|
|
{
|
|
$db = website_db();
|
|
$prefix = website_table_prefix();
|
|
if (!$db instanceof mysqli || $login === '') {
|
|
return null;
|
|
}
|
|
|
|
$table = $db->real_escape_string($prefix . 'users');
|
|
$stmt = $db->prepare("SELECT * FROM `{$table}` WHERE `users_login` = ? LIMIT 1");
|
|
if (!$stmt) {
|
|
return null;
|
|
}
|
|
$stmt->bind_param('s', $login);
|
|
$stmt->execute();
|
|
$result = $stmt->get_result();
|
|
$user = $result instanceof mysqli_result ? $result->fetch_assoc() : null;
|
|
$stmt->close();
|
|
return is_array($user) ? $user : null;
|
|
}
|
|
|
|
function website_verify_panel_password(array $user, string $password): bool
|
|
{
|
|
$hash = (string)($user['users_passwd'] ?? '');
|
|
if ($hash === '') {
|
|
return false;
|
|
}
|
|
|
|
return hash_equals($hash, md5($password));
|
|
}
|
|
|
|
function website_authenticate_user(string $login, string $password): ?array
|
|
{
|
|
$user = website_panel_user_by_login($login);
|
|
if (!$user || !website_verify_panel_password($user, $password)) {
|
|
return null;
|
|
}
|
|
if ((string)($user['users_role'] ?? '') === 'banned') {
|
|
return null;
|
|
}
|
|
|
|
return $user;
|
|
}
|
|
|
|
function website_set_user_session(array $user): void
|
|
{
|
|
website_start_session();
|
|
session_regenerate_id(true);
|
|
$_SESSION['website_user_id'] = (int)$user['user_id'];
|
|
$_SESSION['website_users_login'] = (string)$user['users_login'];
|
|
$_SESSION['website_users_role'] = (string)$user['users_role'];
|
|
$_SESSION['website_login_at'] = time();
|
|
}
|
|
|
|
function website_logout_user(): void
|
|
{
|
|
website_start_session();
|
|
$_SESSION = [];
|
|
if (ini_get('session.use_cookies')) {
|
|
$params = session_get_cookie_params();
|
|
setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'] ?? '', (bool)$params['secure'], (bool)$params['httponly']);
|
|
}
|
|
session_destroy();
|
|
}
|
|
|
|
function website_current_user(): ?array
|
|
{
|
|
website_start_session();
|
|
$userId = (int)($_SESSION['website_user_id'] ?? 0);
|
|
if ($userId <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$user = website_panel_user_by_id($userId);
|
|
if (!$user) {
|
|
website_logout_user();
|
|
return null;
|
|
}
|
|
|
|
return $user;
|
|
}
|
|
|
|
function website_is_logged_in(): bool
|
|
{
|
|
return website_current_user() !== null;
|
|
}
|
|
|
|
function website_current_user_is_staff(): bool
|
|
{
|
|
$user = website_current_user();
|
|
return $user !== null && (string)($user['users_role'] ?? '') === 'admin';
|
|
}
|
|
|
|
function website_log_activity(string $message, int $userId = 0, string $eventType = 'website'): void
|
|
{
|
|
$db = website_db();
|
|
$prefix = website_table_prefix();
|
|
if (!$db instanceof mysqli) {
|
|
return;
|
|
}
|
|
|
|
$table = $prefix . 'logger';
|
|
if (!website_table_exists($table)) {
|
|
return;
|
|
}
|
|
|
|
$safeTable = $db->real_escape_string($table);
|
|
$ip = substr((string)($_SERVER['REMOTE_ADDR'] ?? ''), 0, 255);
|
|
$stmt = $db->prepare(
|
|
"INSERT INTO `{$safeTable}` (`date`, `user_id`, `ip`, `message`, `source_type`, `category`, `event_type`, `severity`)
|
|
VALUES (FROM_UNIXTIME(UNIX_TIMESTAMP(), '%d-%m-%Y %H:%i:%s'), ?, ?, ?, 'website', 'authentication', ?, 'info')"
|
|
);
|
|
if (!$stmt) {
|
|
return;
|
|
}
|
|
$message = substr($message, 0, 1000);
|
|
$eventType = substr($eventType, 0, 80);
|
|
$stmt->bind_param('isss', $userId, $ip, $message, $eventType);
|
|
@$stmt->execute();
|
|
$stmt->close();
|
|
}
|
|
|
|
function website_safe_return_path(string $returnPath, string $default = 'index.php'): string
|
|
{
|
|
if ($returnPath === '' || preg_match('#^[a-z][a-z0-9+.-]*://#i', $returnPath) === 1 || strpos($returnPath, '//') === 0) {
|
|
return $default;
|
|
}
|
|
|
|
$returnPath = ltrim($returnPath, '/');
|
|
if (strpos($returnPath, "\0") !== false || strpos($returnPath, '../') === 0 || strpos($returnPath, '/../') !== false) {
|
|
return $default;
|
|
}
|
|
|
|
return $returnPath;
|
|
}
|
|
|
|
function website_login_url(string $returnPath = ''): string
|
|
{
|
|
$path = 'login.php';
|
|
if ($returnPath !== '') {
|
|
$path .= '?return=' . rawurlencode(website_safe_return_path($returnPath, 'index.php'));
|
|
}
|
|
return website_url($path);
|
|
}
|
|
|
|
function website_control_panel_url(string $returnPath = 'home.php?m=dashboard&p=dashboard'): string
|
|
{
|
|
return panel_url(website_safe_return_path($returnPath, 'home.php?m=dashboard&p=dashboard'));
|
|
}
|
|
|
|
function website_order_url($serviceId): string
|
|
{
|
|
return website_url('order.php?service_id=' . rawurlencode((string)$serviceId));
|
|
}
|
|
|
|
function website_cart_url(): string
|
|
{
|
|
return website_url('cart.php');
|
|
}
|
|
|
|
function website_checkout_url(): string
|
|
{
|
|
return website_url('cart.php?checkout=1');
|
|
}
|
|
|
|
function website_register_url(string $returnPath = 'cart.php'): string
|
|
{
|
|
$path = 'register.php';
|
|
if ($returnPath !== '') {
|
|
$path .= '?return=' . rawurlencode(website_safe_return_path($returnPath, 'cart.php'));
|
|
}
|
|
return website_url($path);
|
|
}
|
|
|
|
function website_fetch_service_by_id(int $serviceId): ?array
|
|
{
|
|
$db = website_db();
|
|
$prefix = website_table_prefix();
|
|
if (!$db instanceof mysqli || $serviceId <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$serviceTable = $prefix . 'billing_services';
|
|
if (!website_table_exists($serviceTable)) {
|
|
return null;
|
|
}
|
|
|
|
$safeServiceTable = $db->real_escape_string($serviceTable);
|
|
$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;
|
|
}
|
|
$stmt->bind_param('i', $serviceId);
|
|
$stmt->execute();
|
|
$result = $stmt->get_result();
|
|
$service = $result instanceof mysqli_result ? $result->fetch_assoc() : null;
|
|
$stmt->close();
|
|
|
|
if (!is_array($service)) {
|
|
return null;
|
|
}
|
|
|
|
if (array_key_exists('enabled', $service) && (int)$service['enabled'] !== 1) {
|
|
return null;
|
|
}
|
|
|
|
return $service;
|
|
}
|
|
|
|
function website_service_name(array $service): string
|
|
{
|
|
$name = trim((string)($service['cfg_game_name'] ?? ''));
|
|
if ($name === '') {
|
|
$name = trim((string)($service['service_name'] ?? ''));
|
|
}
|
|
return $name === '' ? 'Game Server' : $name;
|
|
}
|
|
|
|
function website_service_min_slots(array $service): int
|
|
{
|
|
foreach (['slot_min_qty', 'min_slots', 'minimum_slots', 'slots_min'] as $column) {
|
|
if (isset($service[$column]) && (int)$service[$column] > 0) {
|
|
return (int)$service[$column];
|
|
}
|
|
}
|
|
|
|
$pricing = website_config('pricing', []);
|
|
return max(1, (int)($pricing['standard_min_slots'] ?? 16));
|
|
}
|
|
|
|
function website_service_max_slots(array $service): int
|
|
{
|
|
foreach (['slot_max_qty', 'max_slots', 'maximum_slots', 'slots_max', 'max_players'] as $column) {
|
|
if (isset($service[$column]) && (int)$service[$column] > 0) {
|
|
return (int)$service[$column];
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
function website_service_locations(array $service): array
|
|
{
|
|
$raw = trim((string)($service['remote_server_id'] ?? ''));
|
|
if ($raw === '') {
|
|
return [];
|
|
}
|
|
|
|
$locations = [];
|
|
foreach (preg_split('/[\s,]+/', $raw) ?: [] as $remoteServerId) {
|
|
$remoteServerId = trim($remoteServerId);
|
|
if ($remoteServerId === '' || !ctype_digit($remoteServerId)) {
|
|
continue;
|
|
}
|
|
$locations[$remoteServerId] = 'Location ' . $remoteServerId;
|
|
}
|
|
|
|
return $locations;
|
|
}
|
|
|
|
function website_cart_items(): array
|
|
{
|
|
website_start_session();
|
|
return is_array($_SESSION['website_cart'] ?? null) ? $_SESSION['website_cart'] : [];
|
|
}
|
|
|
|
function website_cart_count(): int
|
|
{
|
|
return count(website_cart_items());
|
|
}
|
|
|
|
function website_cart_add(array $item): void
|
|
{
|
|
website_start_session();
|
|
if (!isset($_SESSION['website_cart']) || !is_array($_SESSION['website_cart'])) {
|
|
$_SESSION['website_cart'] = [];
|
|
}
|
|
|
|
$key = bin2hex(random_bytes(8));
|
|
$_SESSION['website_cart'][$key] = $item;
|
|
}
|
|
|
|
function website_cart_remove(string $key): void
|
|
{
|
|
website_start_session();
|
|
if (isset($_SESSION['website_cart'][$key])) {
|
|
unset($_SESSION['website_cart'][$key]);
|
|
}
|
|
}
|
|
|
|
function website_cart_total(): float
|
|
{
|
|
$total = 0.0;
|
|
foreach (website_cart_items() as $item) {
|
|
$total += (float)($item['line_total'] ?? $item['monthly_total'] ?? 0);
|
|
}
|
|
return $total;
|
|
}
|
|
|
|
function website_billing_docs_root(): ?string
|
|
{
|
|
if (is_dir(WEBSITE_BILLING_DOCS_DIR)) {
|
|
return WEBSITE_BILLING_DOCS_DIR;
|
|
}
|
|
|
|
$legacyDocs = WEBSITE_LEGACY_SITE_ROOT . '/docs';
|
|
if (is_dir($legacyDocs)) {
|
|
return $legacyDocs;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function website_is_valid_doc_slug(string $slug): bool
|
|
{
|
|
return (bool)preg_match('/^[a-z0-9][a-z0-9_-]*$/i', $slug);
|
|
}
|
|
|
|
function website_doc_path(string $slug, string $fileName = 'index.php'): ?string
|
|
{
|
|
if (!website_is_valid_doc_slug($slug)) {
|
|
return null;
|
|
}
|
|
|
|
$docsRoot = website_billing_docs_root();
|
|
if ($docsRoot === null) {
|
|
return null;
|
|
}
|
|
|
|
$candidate = realpath($docsRoot . '/' . $slug . '/' . $fileName);
|
|
if ($candidate === false || strpos($candidate, realpath($docsRoot) ?: $docsRoot) !== 0) {
|
|
return null;
|
|
}
|
|
|
|
return $candidate;
|
|
}
|
|
|
|
function website_doc_icon_url(string $slug): ?string
|
|
{
|
|
foreach (['icon.png', 'icon.jpg', 'icon.jpeg', 'icon.webp'] as $fileName) {
|
|
if (website_doc_path($slug, $fileName) !== null) {
|
|
return website_url('doc_asset.php?doc=' . rawurlencode($slug) . '&file=' . rawurlencode($fileName));
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function website_service_image_url(string $imageValue): string
|
|
{
|
|
$imageValue = trim($imageValue);
|
|
if ($imageValue === '') {
|
|
return website_asset('images/banner.png');
|
|
}
|
|
|
|
if (preg_match('#^https?://#i', $imageValue) === 1) {
|
|
return $imageValue;
|
|
}
|
|
|
|
$fileName = basename($imageValue);
|
|
if ($fileName === '') {
|
|
return website_asset('images/banner.png');
|
|
}
|
|
|
|
return website_asset('images/games/' . $fileName);
|
|
}
|
|
|
|
function website_fetch_services(int $limit = 0, bool $includeDisabled = false): array
|
|
{
|
|
$db = website_db();
|
|
if (!$db instanceof mysqli) {
|
|
return [];
|
|
}
|
|
|
|
$prefix = website_table_prefix();
|
|
|
|
$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);
|
|
}
|
|
|
|
$result = @$db->query($sql);
|
|
if (!$result instanceof mysqli_result) {
|
|
website_log('Failed to query billing services for website catalog.');
|
|
return [];
|
|
}
|
|
|
|
$rows = [];
|
|
while ($row = $result->fetch_assoc()) {
|
|
$rows[] = $row;
|
|
}
|
|
$result->free();
|
|
|
|
return $rows;
|
|
}
|
|
|
|
function website_custom_project_url(): string
|
|
{
|
|
$services = website_config('services', []);
|
|
$projectUrl = '';
|
|
if (is_array($services)) {
|
|
$projectUrl = trim((string)($services['project_request_url'] ?? ''));
|
|
}
|
|
if ($projectUrl !== '') {
|
|
return $projectUrl;
|
|
}
|
|
|
|
$supportUrl = trim((string)website_config('support_url', ''));
|
|
if ($supportUrl !== '') {
|
|
return $supportUrl;
|
|
}
|
|
|
|
$discordUrl = trim((string)website_config('discord_url', ''));
|
|
if ($discordUrl !== '') {
|
|
return $discordUrl;
|
|
}
|
|
|
|
return website_url('support.php');
|
|
}
|
|
|
|
function website_fetch_doc_index(): array
|
|
{
|
|
$docsRoot = website_billing_docs_root();
|
|
if ($docsRoot === null || !is_dir($docsRoot)) {
|
|
return [];
|
|
}
|
|
|
|
$entries = [];
|
|
foreach (array_diff(scandir($docsRoot) ?: [], ['.', '..']) as $folder) {
|
|
$docFolder = $docsRoot . '/' . $folder;
|
|
if (!is_dir($docFolder)) {
|
|
continue;
|
|
}
|
|
|
|
$indexPath = website_doc_path($folder, 'index.php');
|
|
$metadataPath = website_doc_path($folder, 'metadata.json');
|
|
if ($indexPath === null || $metadataPath === null) {
|
|
continue;
|
|
}
|
|
|
|
$metadataContent = @file_get_contents($metadataPath);
|
|
$metadataContent = $metadataContent === false ? '' : preg_replace('/^\xEF\xBB\xBF/', '', $metadataContent);
|
|
$metadata = json_decode((string)$metadataContent, true);
|
|
if (!is_array($metadata)) {
|
|
$metadata = [];
|
|
}
|
|
|
|
$entries[] = [
|
|
'slug' => $folder,
|
|
'name' => (string)($metadata['name'] ?? ucwords(str_replace(['-', '_'], ' ', $folder))),
|
|
'description' => (string)($metadata['description'] ?? ''),
|
|
'category' => (string)($metadata['category'] ?? 'other'),
|
|
'order' => (int)($metadata['order'] ?? 999),
|
|
'complete' => (bool)($metadata['complete'] ?? true),
|
|
'icon_url' => website_doc_icon_url($folder),
|
|
];
|
|
}
|
|
|
|
usort(
|
|
$entries,
|
|
static function (array $left, array $right): int {
|
|
if ($left['category'] !== $right['category']) {
|
|
return strcmp($left['category'], $right['category']);
|
|
}
|
|
if ($left['order'] !== $right['order']) {
|
|
return $left['order'] <=> $right['order'];
|
|
}
|
|
return strcasecmp($left['name'], $right['name']);
|
|
}
|
|
);
|
|
|
|
return $entries;
|
|
}
|
|
|
|
function website_render(string $pageTemplate, array $context = []): void
|
|
{
|
|
extract($context, EXTR_SKIP);
|
|
require WEBSITE_INCLUDE_DIR . '/header.php';
|
|
require WEBSITE_ROOT_DIR . '/pages/' . $pageTemplate;
|
|
require WEBSITE_INCLUDE_DIR . '/footer.php';
|
|
}
|
|
|
|
website_start_session();
|