'rebuilt missing sales and staff pages

This commit is contained in:
Frank Harris 2026-06-17 15:50:59 -05:00
parent 484a36ce11
commit 60bcc67056
680 changed files with 33650 additions and 43 deletions

View file

@ -0,0 +1,28 @@
# Website Includes Directory
This directory contains configuration and shared files for the standalone _website folder.
## config.inc.php
Central database configuration file for the website. This file contains the database connection settings that are used by all website PHP files through the `db.php` file.
**Important:** The values in this file should match the panel's database configuration in `/includes/config.inc.php` to ensure the website can access the same database as the panel.
### Configuration Variables
- `$db_host` - Database server hostname
- `$db_user` - Database username
- `$db_pass` - Database password
- `$db_name` - Database name
- `$table_prefix` - Table prefix (default: "ogp_")
- `$db_type` - Database type (default: "mysql")
### Usage
The website files include `db.php`, which in turn loads this configuration file:
```php
require_once('db.php'); // db.php loads includes/config.inc.php
```
This centralizes database credentials in one place, making the website easier to configure and maintain as a standalone site.

View file

@ -0,0 +1,68 @@
<?php
// Admin authorization include — include early (before output) on admin pages
if (session_status() === PHP_SESSION_NONE) {
session_name("gameservers_website");
session_start();
}
// If not logged in, redirect to login
if (empty($_SESSION['website_user_id'])) {
// Build absolute login URL to avoid browser-relative resolution issues
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$siteRoot = '/';
$pos = strpos($script, '/_website');
if ($pos !== false) {
$siteRoot = substr($script, 0, $pos + strlen('/_website'));
} else {
$siteRoot = rtrim(dirname($script), '/\\');
}
$loginUrl = $siteRoot . '/login.php';
$returnTo = $siteRoot . '/' . basename($_SERVER['PHP_SELF']);
header('Location: ' . $loginUrl . '?return_to=' . urlencode($returnTo));
exit();
}
// Require DB config and check role live from panel DB
require_once(__DIR__ . '/config.inc.php');
// Variables from config.inc.php (helps IDEs understand scope)
/** @var string $db_host Database host */
/** @var string $db_user Database user */
/** @var string $db_pass Database password */
/** @var string $db_name Database name */
/** @var string $table_prefix Table prefix for database tables */
// Use a local connection variable so we don't clash with pages that also use $db
$auth_db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name);
if (!$auth_db) {
// If DB unavailable, deny access gracefully
// Redirect to absolute login URL
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$pos = strpos($script, '/_website');
$siteRoot = $pos !== false ? substr($script, 0, $pos + strlen('/_website')) : rtrim(dirname($script), '/\\');
header('Location: ' . $siteRoot . '/login.php');
exit();
}
$uid = intval($_SESSION['website_user_id']);
$role = '';
$res = mysqli_query($auth_db, "SELECT users_role FROM {$table_prefix}users WHERE user_id = $uid LIMIT 1");
if ($res && mysqli_num_rows($res) === 1) {
$row = mysqli_fetch_assoc($res);
$role = (string)($row['users_role'] ?? '');
}
mysqli_close($auth_db);
if (strtolower($role) !== 'admin') {
// Not an admin — redirect to login or home
// Redirect to absolute login URL
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$pos = strpos($script, '/_website');
$siteRoot = $pos !== false ? substr($script, 0, $pos + strlen('/_website')) : rtrim(dirname($script), '/\\');
header('Location: ' . $siteRoot . '/login.php');
exit();
}
// If we reach here, user is an admin
?>

View file

@ -0,0 +1,22 @@
<?php
// Helper to read cart items stored in session and return count
// Non-invasive: reads $_SESSION['cart'] if present and returns total quantity or items count
if (session_status() === PHP_SESSION_NONE) {
@session_start();
}
function get_cart_count() {
if (!isset($_SESSION['cart']) || !is_array($_SESSION['cart'])) {
return 0;
}
$count = 0;
foreach ($_SESSION['cart'] as $item) {
if (is_array($item) && isset($item['quantity'])) {
$count += (int) $item['quantity'];
} else {
$count += 1;
}
}
return $count;
}

View file

@ -0,0 +1,32 @@
<?php
###############################################
# Website Database Configuration
# This file contains the database connection
# settings for the _website standalone site.
#
# These settings should match the panel's
# database configuration in includes/config.inc.php
###############################################
$db_host="mysql.iaregamer.com";
$db_user="remoteuser";
$db_pass="Pkloyn7yvpht!";
$db_name="panel";
$table_prefix="gsp_";
$db_type="mysql";
// Optional: base URL used by admin pages to build absolute image previews.
// Leave empty to prefer relative paths (local folder).
// To enable production base URL, uncomment and set it to your site, e.g.:
// $SITE_BASE_URL = 'https://gameservers.world/';
$SITE_BASE_URL = '';
// Normalize: ensure either empty or ends without trailing slash (we use join_base to handle joining)
$SITE_BASE_URL = trim((string)$SITE_BASE_URL);
// Site-wide background image (relative to site root). Change to your preferred background.
$SITE_BACKGROUND = 'images/dark.jpg';
// Normalize
$SITE_BACKGROUND = trim((string)$SITE_BACKGROUND);
// Data directory for persisted payment webhook JSON files (relative to repo root)
$SITE_DATA_DIR = realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'data';
?>

View file

@ -0,0 +1,32 @@
<?php
###############################################
# Website Database Configuration
# This file contains the database connection
# settings for the _website standalone site.
#
# These settings should match the panel's
# database configuration in includes/config.inc.php
###############################################
$db_host="localhost";
$db_user="localuser";
$db_pass="password123";
$db_name="panel";
$table_prefix="ogp_";
$db_type="mysql";
// Optional: base URL used by admin pages to build absolute image previews.
// Leave empty to prefer relative paths (local folder).
// To enable production base URL, uncomment and set it to your site, e.g.:
// $SITE_BASE_URL = 'https://gameservers.world/';
$SITE_BASE_URL = '';
// Normalize: ensure either empty or ends without trailing slash (we use join_base to handle joining)
$SITE_BASE_URL = trim((string)$SITE_BASE_URL);
// Site-wide background image (relative to site root). Change to your preferred background.
$SITE_BACKGROUND = 'images/dark.jpg';
// Normalize
$SITE_BACKGROUND = trim((string)$SITE_BACKGROUND);
// Data directory for persisted payment webhook JSON files (relative to repo root)
$SITE_DATA_DIR = realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'data';
?>

View file

@ -0,0 +1,21 @@
<?php
// Simple footer include
?>
<footer class="gsw-footer">
<div class="container-wide">
<a href="privacy.php">Privacy</a> | <a href="tos.php">TOS</a> | <a href="server_status.php">Server Status</a> | <a href="https://worlddomination.dev" target="_blank" rel="noopener">Worlddomination.dev</a>
</div>
<div class="last-updated" style="color:#999;font-size:0.9em;">
<?php
// Include the canonical billing timestamp text file (plain text).
$billing_ts = __DIR__ . '/../timestamp.txt';
if (file_exists($billing_ts)) {
echo trim(file_get_contents($billing_ts));
}
?>
</div>
</footer>
<!-- close site wrapper started in menu.php -->
</div>

View file

@ -0,0 +1,33 @@
<?php
// Simple site logging helper for _website
// Writes to _website/logs/YYYY-MM-DD.log
function site_log_dir(){
$d = __DIR__ . '/../logs';
if (!is_dir($d)) @mkdir($d, 0775, true);
return $d;
}
function site_log_filename(){
$d = site_log_dir();
return rtrim($d, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log';
}
function site_log($level, $message, $meta = null){
$ts = date('c');
$lvl = strtoupper(substr((string)$level,0,10));
$line = "[{$ts}] [{$lvl}] {$message}";
if ($meta !== null) {
if (!is_string($meta)) $meta = json_encode($meta, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
$line .= ' | ' . $meta;
}
$file = site_log_filename();
@file_put_contents($file, $line . PHP_EOL, FILE_APPEND | LOCK_EX);
}
// Convenience wrappers
function site_log_info($msg, $meta=null){ site_log('INFO', $msg, $meta); }
function site_log_warn($msg, $meta=null){ site_log('WARN', $msg, $meta); }
function site_log_error($msg, $meta=null){ site_log('ERROR', $msg, $meta); }
?>

View file

@ -0,0 +1,11 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_name("gameservers_website");
session_start();
}
// Debugging mode: do not enforce login redirects. Pages can load without authentication.
// If you later want to re-enable, restore the original redirect behavior.
// (This file intentionally left as a no-op during debugging.)
return;
?>

View file

@ -0,0 +1,124 @@
<?php
/**
* Navigation Menu for GameServers.World Website
* This file provides a consistent navigation menu across all website pages
*/
// Start the website session to check if user is logged in (if not already started)
if (session_status() === PHP_SESSION_NONE) {
session_name("gameservers_website");
session_start();
}
// Check login status
// Primary check uses website_user_id, but some remote deployments may only set website_username.
// Treat presence of website_username as a fallback to consider the user logged in for UI purposes.
$is_logged_in = (isset($_SESSION['website_user_id']) && !empty($_SESSION['website_user_id'])) || (isset($_SESSION['website_username']) && !empty($_SESSION['website_username']));
$username = '';
if (isset($_SESSION['website_username']) && !empty($_SESSION['website_username'])) {
$username = htmlspecialchars($_SESSION['website_username']);
} elseif (isset($_SESSION['website_user_id']) && !empty($_SESSION['website_user_id'])) {
// fetch username lazily if only user_id is present
$username = htmlspecialchars((string)($_SESSION['website_user_id']));
}
// Determine if the logged-in user is an admin by checking the panel DB
$is_admin = false;
if ($is_logged_in) {
// load DB credentials
require_once(__DIR__ . '/config.inc.php');
// Variables from config.inc.php (helps IDEs understand scope)
/** @var string $db_host Database host */
/** @var string $db_user Database user */
/** @var string $db_pass Database password */
/** @var string $db_name Database name */
/** @var string $table_prefix Table prefix for database tables */
// Prefer reusing an existing $db if present, otherwise open a local connection
$menu_db = null;
$menu_db_opened = false;
if (isset($db) && $db instanceof mysqli) {
$menu_db = $db;
} else {
$menu_db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name);
$menu_db_opened = true;
}
if ($menu_db) {
$uid = null;
if (isset($_SESSION['website_user_id']) && !empty($_SESSION['website_user_id'])) {
$uid = intval($_SESSION['website_user_id']);
}
if (!empty($uid)) {
$res = mysqli_query($menu_db, "SELECT users_role FROM {$table_prefix}users WHERE user_id = $uid LIMIT 1");
if ($res && mysqli_num_rows($res) === 1) {
$row = mysqli_fetch_assoc($res);
if (strtolower((string)($row['users_role'] ?? '')) === 'admin') $is_admin = true;
}
}
if ($menu_db_opened) {
if (function_exists('billing_maybe_close_db')) {
billing_maybe_close_db($menu_db);
} else {
@mysqli_close($menu_db);
}
}
}
}
?>
<link rel="stylesheet" href="css/header.css">
<!-- site wrapper for scoping styles -->
<div id="gsw-site">
<div class="gsw-header">
<div class="gsw-header-top">
<div class="gsw-header-left">
<a href="index.php" class="gsw-logo-link">
<img src="images/logo-sm.png" alt="GameServers.World" class="gsw-logo">
<span class="gsw-site-name">GameServers.World</span>
</a>
</div>
<div class="gsw-header-right">
<!-- Always show the user-info area (may be empty for guests) and an auth button -->
<?php
// Build a safe absolute return_to under this site so auth redirects stay within this module
$current = $_SERVER['REQUEST_URI'] ?? '/';
$return_to_param = $current;
?>
<?php if ($is_logged_in): ?>
<a href="my_account.php" class="gsw-user-info">Welcome, <?php echo $username; ?></a>
<a href="logout.php?return_to=<?php echo urlencode($return_to_param); ?>" class="gsw-header-btn">Logout</a>
<?php else: ?>
<a href="login.php?return_to=<?php echo urlencode($return_to_param); ?>" class="gsw-header-btn">Login</a>
<?php endif; ?>
</div>
</div>
<div class="gsw-header-bottom">
<nav class="gsw-header-nav">
<a href="index.php" class="gsw-nav-link">Home</a>
<a href="serverlist.php" class="gsw-nav-link">Game Servers</a>
<a href="docs.php" class="gsw-nav-link">Documentation</a>
<?php if ($is_logged_in): ?>
<!-- My Account as a regular nav link, not a prominent button -->
<a href="my_account.php" class="gsw-nav-link">My Account</a>
<a href="cart.php" class="gsw-nav-link">Cart
<?php
$cart_count = 0;
if (file_exists(__DIR__ . '/cart_helper.php')) {
include_once __DIR__ . '/cart_helper.php';
if (function_exists('get_cart_count')) $cart_count = (int) get_cart_count();
}
if ($cart_count > 0) echo ' <span class="cart-badge">' . intval($cart_count) . '</span>';
?>
</a>
<?php endif; ?>
<?php if ($is_logged_in && $is_admin): ?>
<a href="admin.php" class="gsw-nav-link">Admin</a>
<?php endif; ?>
<a href="http://panel.iaregamer.com" class="gsw-nav-link" target="_blank">Control Panel</a>
</nav>
</div>
</div>

View file

@ -0,0 +1,228 @@
<?php
/**
* Standalone Payment Processing Helper (module-local)
*
* This file is intentionally self-contained and does NOT include or rely on
* panel-wide files. Configure the DB connection information below (edit
* the $DB_* variables) or provide a module-local `config.local.php` that defines
* the same variables.
*
* Usage:
* require_once __DIR__ . '/payment_processor.php';
* process_payment_record($record);
*/
// Include the module-local config (must be present in the module includes folder).
// Use ONLY `config.inc.php` — do NOT fall back to other filenames.
$moduleConfig = __DIR__ . '/config.inc.php';
if (is_file($moduleConfig)) {
require_once $moduleConfig;
} else {
error_log('[payment_processor] Module config not found: expected ' . $moduleConfig);
}
// Variables from config.inc.php (helps IDEs understand scope)
/** @var string $db_host Database host */
/** @var string $db_user Database user */
/** @var string $db_pass Database password */
/** @var string $db_name Database name */
/** @var string $table_prefix Table prefix for database tables */
// Normalize table prefix variable: many files use $table_prefix (lowercase)
if (!isset($TABLE_PREFIX) && isset($table_prefix)) {
$TABLE_PREFIX = $table_prefix;
}
/**
* Create and return a mysqli connection using the site's config values.
* Returns mysqli or false on failure.
*/
function payment_db_connect() {
global $db_host, $db_user, $db_pass, $db_name, $db_port;
if (empty($db_host) || empty($db_user) || empty($db_name)) {
error_log('[payment_processor] DB globals not available from includes/config.inc.php');
return false;
}
$port = intval($db_port) ?: 3306;
$db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name, $port);
if (!$db) {
error_log('[payment_processor] mysqli_connect failed: ' . mysqli_connect_error());
return false;
}
mysqli_set_charset($db, 'utf8mb4');
return $db;
}
/**
* Process a payment record. Self-contained: uses module-local DB config.
* @param array $record { invoice, custom, resource_id, amount }
* @return bool true if at least one invoice processed
*/
function process_payment_record(array $record) {
global $TABLE_PREFIX;
// Normalize inputs
$invoice_ref = $record['invoice'] ?? null; // e.g. PayPal invoice id or custom label
$custom = $record['custom'] ?? null; // our custom_id (often invoice_id)
$txid = $record['resource_id'] ?? ($record['txid'] ?? null);
$amount = isset($record['amount']) ? floatval($record['amount']) : null;
$db = payment_db_connect();
if (!$db) return false;
$now = date('Y-m-d H:i:s');
$esc_txid = mysqli_real_escape_string($db, (string)$txid);
$invoices_to_process = [];
// 1) If custom is a single numeric invoice id, try to fetch that exact invoice
if ($custom && ctype_digit((string)$custom)) {
$invoice_id = intval($custom);
$sql = "SELECT * FROM `" . $TABLE_PREFIX . "billing_invoices` WHERE invoice_id = ? AND status = 'due' LIMIT 1";
if ($stmt = mysqli_prepare($db, $sql)) {
mysqli_stmt_bind_param($stmt, 'i', $invoice_id);
mysqli_stmt_execute($stmt);
$res = mysqli_stmt_get_result($stmt);
if ($res && $row = mysqli_fetch_assoc($res)) $invoices_to_process[] = $row;
mysqli_stmt_close($stmt);
}
}
// 2) If we didn't find by custom and invoice_ref is provided, match by description containing invoice_ref
if (empty($invoices_to_process) && $invoice_ref) {
$like = '%' . mysqli_real_escape_string($db, $invoice_ref) . '%';
$sql = "SELECT * FROM `" . $TABLE_PREFIX . "billing_invoices` WHERE status = 'due' AND description LIKE ?";
if ($stmt = mysqli_prepare($db, $sql)) {
mysqli_stmt_bind_param($stmt, 's', $like);
mysqli_stmt_execute($stmt);
$res = mysqli_stmt_get_result($stmt);
if ($res) {
while ($row = mysqli_fetch_assoc($res)) $invoices_to_process[] = $row;
}
mysqli_stmt_close($stmt);
}
}
// 3) As a fallback, match unpaid invoices by amount (exact match). Useful for simple carts.
if (empty($invoices_to_process) && $amount !== null) {
$sql = "SELECT * FROM `" . $TABLE_PREFIX . "billing_invoices` WHERE status = 'due' AND amount = ?";
if ($stmt = mysqli_prepare($db, $sql)) {
mysqli_stmt_bind_param($stmt, 'd', $amount);
mysqli_stmt_execute($stmt);
$res = mysqli_stmt_get_result($stmt);
if ($res) {
while ($row = mysqli_fetch_assoc($res)) $invoices_to_process[] = $row;
}
mysqli_stmt_close($stmt);
}
}
$processed_count = 0;
foreach ($invoices_to_process as $inv) {
$invoice_id = intval($inv['invoice_id']);
$order_id = intval($inv['order_id'] ?? 0);
$user_id = intval($inv['user_id']);
$service_id = intval($inv['service_id'] ?? 0);
$home_name = mysqli_real_escape_string($db, $inv['home_name'] ?? '');
$ip = intval($inv['ip'] ?? 0);
$max_players = intval($inv['max_players'] ?? 0);
$qty = intval($inv['qty'] ?? 1);
$duration = $inv['invoice_duration'] ?? 'month';
$invoice_amount = floatval($inv['amount'] ?? 0);
$rcon_pw = mysqli_real_escape_string($db, $inv['remote_control_password'] ?? '');
$ftp_pw = mysqli_real_escape_string($db, $inv['ftp_password'] ?? '');
// Mark invoice as paid
$upd = "UPDATE `" . $TABLE_PREFIX . "billing_invoices` SET status='paid', paid_date=?, payment_txid=?, payment_method='paypal' WHERE invoice_id = ? LIMIT 1";
if ($stmt = mysqli_prepare($db, $upd)) {
mysqli_stmt_bind_param($stmt, 'ssi', $now, $esc_txid, $invoice_id);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
}
// If invoice has a coupon, increment usage count
$coupon_id = intval($inv['coupon_id'] ?? 0);
if ($coupon_id > 0) {
$upd_coupon = "UPDATE `" . $TABLE_PREFIX . "billing_coupons` SET current_uses = current_uses + 1 WHERE coupon_id = ?";
if ($stmt = mysqli_prepare($db, $upd_coupon)) {
mysqli_stmt_bind_param($stmt, 'i', $coupon_id);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
}
}
// If this invoice already has an order -> treat as renewal
if ($order_id > 0) {
// compute months
$months = (stripos($duration, 'year') !== false) ? ($qty * 12) : $qty;
// fetch current end_date
$get = "SELECT end_date FROM `" . $TABLE_PREFIX . "billing_orders` WHERE order_id = ? LIMIT 1";
if ($stmt = mysqli_prepare($db, $get)) {
mysqli_stmt_bind_param($stmt, 'i', $order_id);
mysqli_stmt_execute($stmt);
$res = mysqli_stmt_get_result($stmt);
if ($res && $row = mysqli_fetch_assoc($res)) {
$current_end = $row['end_date'] ?? date('Y-m-d H:i:s');
$extend_from = (strtotime($current_end) > time()) ? $current_end : date('Y-m-d H:i:s');
$dt = new DateTime($extend_from);
if ($months > 0) $dt->modify('+' . intval($months) . ' months');
$new_end = $dt->format('Y-m-d H:i:s');
$update = "UPDATE `" . $TABLE_PREFIX . "billing_orders` SET end_date = ?, status='paid', payment_txid = ?, paid_ts = ? WHERE order_id = ?";
if ($u = mysqli_prepare($db, $update)) {
mysqli_stmt_bind_param($u, 'sssi', $new_end, $esc_txid, $now, $order_id);
mysqli_stmt_execute($u);
mysqli_stmt_close($u);
$processed_count++;
}
}
mysqli_stmt_close($stmt);
}
} else {
// Create new order
$months = (stripos($duration, 'year') !== false) ? ($qty * 12) : $qty;
$dt = new DateTime('now');
if ($months > 0) $dt->modify('+' . intval($months) . ' months');
$end_date = $dt->format('Y-m-d H:i:s');
// Simpler insert using properly escaped values
$esc_home = mysqli_real_escape_string($db, $home_name);
$esc_rcon = mysqli_real_escape_string($db, $rcon_pw);
$esc_ftp = mysqli_real_escape_string($db, $ftp_pw);
$esc_duration = mysqli_real_escape_string($db, $duration);
$price = number_format($invoice_amount, 2, '.', '');
$insert2 = sprintf(
"INSERT INTO `%s` (user_id, service_id, home_name, ip, max_players, qty, invoice_duration, price, remote_control_password, ftp_password, status, order_date, end_date, payment_txid, paid_ts) VALUES (%d, %d, '%s', %d, %d, %d, '%s', %s, '%s', '%s', 'paid', '%s', '%s', '%s', '%s')",
$TABLE_PREFIX . 'billing_orders',
$user_id, $service_id, $esc_home, $ip, $max_players, $qty, $esc_duration, $price, $esc_rcon, $esc_ftp, $now, $end_date, $esc_txid, $now
);
if (mysqli_query($db, $insert2)) {
$new_order_id = mysqli_insert_id($db);
$link = "UPDATE `" . $TABLE_PREFIX . "billing_invoices` SET order_id = ? WHERE invoice_id = ?";
if ($u = mysqli_prepare($db, $link)) {
mysqli_stmt_bind_param($u, 'ii', $new_order_id, $invoice_id);
mysqli_stmt_execute($u);
mysqli_stmt_close($u);
}
$processed_count++;
} else {
error_log('[payment_processor] Failed to insert order: ' . mysqli_error($db));
}
}
}
mysqli_close($db);
if ($processed_count > 0) {
error_log('[payment_processor] Processed ' . $processed_count . ' invoice(s)');
return true;
}
error_log('[payment_processor] No matching invoices processed for record: ' . json_encode($record));
return false;
}
?>

View file

@ -0,0 +1,6 @@
<?php
// top.php — reserved placeholder
// The site header (logo + title) has been moved into includes/menu.php
// Keep this file present so pages that include it continue to work.
// If you need to re-enable a separate top strip later, add markup here.
?>