[orbis] Weekly backup 2026-07-07 — 318 files changed, 48597 insertions(+), 7 deletions(-)
@@ -0,0 +1,5 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
*.swp
|
||||
|
||||
uploads/
|
||||
@@ -0,0 +1,124 @@
|
||||
# ══════════════════════════════════════════════════════════
|
||||
# TomTomGames Security Configuration
|
||||
# ══════════════════════════════════════════════════════════
|
||||
|
||||
Options -Indexes -Includes
|
||||
ServerSignature Off
|
||||
|
||||
# ── Block all sensitive file types ───────────────────────
|
||||
<FilesMatch "\.(sql|env|log|sh|md|git|bak|backup|old|orig|tmp|swp|cfg|ini|conf|yaml|yml|json.bak)$">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
# ── Block direct access to sensitive PHP files ───────────
|
||||
<FilesMatch "^(phpcheck|test|test_mail|test_login|sgtest|install|config|db|auth|mailer|square|smtp)\.php$">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
# ── Block access to includes and vendor folders ──────────
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteRule ^includes/ - [F,L]
|
||||
RewriteRule ^vendor/ - [F,L]
|
||||
RewriteRule ^mail_queue/ - [F,L]
|
||||
RewriteRule ^\.git/ - [F,L]
|
||||
|
||||
# Block sensitive file extensions anywhere in the tree — the
|
||||
# <FilesMatch>/Order,Deny block above is not honored by this
|
||||
# OpenLiteSpeed vhost (confirmed: db/schema.sql was still
|
||||
# served 200 despite that rule), so enforce via RewriteRule,
|
||||
# the mechanism proven to work here (.git/, vendor/ etc. above).
|
||||
RewriteRule \.(sql|env|log|sh|bak|backup|old|orig|tmp|swp|cfg|ini|conf|yaml|yml)$ - [F,L]
|
||||
</IfModule>
|
||||
|
||||
# ── Block common attack vectors ──────────────────────────
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
|
||||
# Block SQL injection attempts in query strings
|
||||
RewriteCond %{QUERY_STRING} (union|select|insert|drop|delete|update|cast|exec|declare|char|convert|truncate).*= [NC,OR]
|
||||
RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR]
|
||||
RewriteCond %{QUERY_STRING} \.\./\.\. [NC,OR]
|
||||
RewriteCond %{QUERY_STRING} (javascript|vbscript|expression|applet|meta|xml|blink|link|iframe|input|embed|script|object|marquee) [NC]
|
||||
RewriteRule .* - [F,L]
|
||||
|
||||
# Block base64 encoded attacks
|
||||
RewriteCond %{QUERY_STRING} base64_encode.*\(.*\) [NC,OR]
|
||||
RewriteCond %{QUERY_STRING} base64_(en|de)code[^(]*\([^)]*\) [NC]
|
||||
RewriteRule .* - [F,L]
|
||||
|
||||
# Block common exploit scanners and bad bots
|
||||
RewriteCond %{HTTP_USER_AGENT} (nikto|sqlmap|havij|nessus|masscan|zgrab|python-requests/2\.6|libwww-perl|wget|curl\/7\.[0-4]) [NC]
|
||||
RewriteRule .* - [F,L]
|
||||
</IfModule>
|
||||
|
||||
# ── Block access to WordPress paths (scanners look for these) ──
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteRule ^wp-admin - [F,L]
|
||||
RewriteRule ^wp-login - [F,L]
|
||||
RewriteRule ^xmlrpc - [F,L]
|
||||
RewriteRule ^\.env - [F,L]
|
||||
RewriteRule ^composer\. - [F,L]
|
||||
</IfModule>
|
||||
|
||||
# ── Security Headers ──────────────────────────────────────
|
||||
<IfModule mod_headers.c>
|
||||
# Prevent MIME type sniffing
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
|
||||
# Prevent clickjacking
|
||||
Header always set X-Frame-Options "DENY"
|
||||
|
||||
# XSS protection
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
|
||||
# Referrer policy
|
||||
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
|
||||
# Permissions policy — disable dangerous browser features
|
||||
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=()"
|
||||
|
||||
# Content Security Policy
|
||||
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://web.squarecdn.com https://sandbox.web.squarecdn.com https://js.squareup.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; img-src 'self' data: blob: https:; connect-src 'self' https: wss:; frame-src 'none'; object-src 'none'"
|
||||
|
||||
# Strict Transport Security — force HTTPS for 1 year
|
||||
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||
|
||||
# Remove server info headers
|
||||
Header unset Server
|
||||
Header unset X-Powered-By
|
||||
</IfModule>
|
||||
|
||||
# ── Canonical HTTPS + non-www redirect ───────────────────
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
|
||||
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
|
||||
</IfModule>
|
||||
|
||||
# ── Block PHP execution in uploads folder (if it exists) ─
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteRule ^uploads/.*\.php$ - [F,L]
|
||||
</IfModule>
|
||||
|
||||
# ── Gzip compression ──────────────────────────────────────
|
||||
<IfModule mod_deflate.c>
|
||||
AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json image/svg+xml
|
||||
</IfModule>
|
||||
|
||||
# ── Browser caching ───────────────────────────────────────
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
ExpiresByType text/html "access plus 1 hour"
|
||||
ExpiresByType text/css "access plus 1 month"
|
||||
ExpiresByType application/javascript "access plus 1 month"
|
||||
ExpiresByType image/svg+xml "access plus 1 month"
|
||||
ExpiresByType image/png "access plus 1 month"
|
||||
ExpiresByType image/jpeg "access plus 1 month"
|
||||
ExpiresByType image/webp "access plus 1 month"
|
||||
ExpiresByType application/json "access plus 1 day"
|
||||
</IfModule>
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
if (isLoggedIn() && !empty($_SESSION['is_admin'])) {
|
||||
header('Location: /admin/index.php'); exit;
|
||||
}
|
||||
|
||||
$error = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$result = loginUser($_POST['username'] ?? '', $_POST['password'] ?? '');
|
||||
if ($result['success'] && !empty($_SESSION['is_admin'])) {
|
||||
header('Location: /admin/index.php'); exit;
|
||||
} elseif ($result['success']) {
|
||||
logoutUser();
|
||||
$error = 'Access denied. Admin account required.';
|
||||
} else {
|
||||
$error = $result['error'];
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="robots" content="noindex, nofollow, noarchive">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TomTomGames Admin Login</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Exo+2:wght@700;900&family=Rajdhani:wght@500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{background:#0a0a12;color:#e8e8f0;font-family:'Rajdhani',sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
|
||||
.box{background:#1a1a2e;border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:40px;width:100%;max-width:380px}
|
||||
.logo{font-family:'Exo 2',sans-serif;font-weight:900;font-size:28px;background:linear-gradient(135deg,#f0c040,#00e5ff);-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:4px}
|
||||
.sub{color:#8888aa;font-size:13px;margin-bottom:28px;letter-spacing:1px}
|
||||
.form-group{margin-bottom:16px}
|
||||
label{font-size:12px;font-weight:700;color:#8888aa;letter-spacing:1px;text-transform:uppercase;margin-bottom:8px;display:block}
|
||||
input{width:100%;background:#181828;border:1px solid rgba(255,255,255,.07);border-radius:8px;padding:13px 15px;color:#e8e8f0;font-family:'Rajdhani',sans-serif;font-size:16px;outline:none;transition:border-color .2s}
|
||||
input:focus{border-color:#00e5ff}
|
||||
.btn{width:100%;padding:15px;border:none;border-radius:8px;background:linear-gradient(135deg,#f0c040,#d4a017);color:#000;font-family:'Exo 2',sans-serif;font-weight:700;font-size:16px;letter-spacing:1px;cursor:pointer;margin-top:8px}
|
||||
.error{background:rgba(255,68,68,.1);border:1px solid rgba(255,68,68,.3);color:#ff4444;padding:12px 14px;border-radius:8px;font-size:14px;font-weight:600;margin-bottom:16px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<div class="logo" style="display:flex;align-items:center;gap:10px;justify-content:center"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="36" height="36" style="display:inline-block;vertical-align:middle;flex-shrink:0"><defs><linearGradient id="ll1" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#f0c040"/><stop offset="100%" stop-color="#ff6b35"/></linearGradient><linearGradient id="ll2" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#00e5ff"/><stop offset="100%" stop-color="#7b2fbe"/></linearGradient><filter id="gll"><feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><rect x="6" y="16" width="36" height="22" rx="11" fill="url(#ll1)" filter="url(#gll)"/><rect x="12" y="23" width="8" height="3" rx="1.5" fill="rgba(0,0,0,0.45)"/><rect x="15" y="20" width="3" height="8" rx="1.5" fill="rgba(0,0,0,0.45)"/><circle cx="32" cy="22" r="2.2" fill="#e63946" opacity=".9"/><circle cx="36" cy="25" r="2.2" fill="#2ec4b6" opacity=".9"/><circle cx="32" cy="28" r="2.2" fill="#7b2fbe" opacity=".9"/><circle cx="28" cy="25" r="2.2" fill="#f4a261" opacity=".9"/><rect x="21" y="24" width="6" height="3" rx="1.5" fill="rgba(0,0,0,0.3)"/><rect x="8" y="30" width="8" height="7" rx="4" fill="url(#ll2)" opacity=".7"/><rect x="32" y="30" width="8" height="7" rx="4" fill="url(#ll2)" opacity=".7"/><rect x="14" y="13" width="8" height="5" rx="2.5" fill="url(#ll1)" opacity=".8"/><rect x="26" y="13" width="8" height="5" rx="2.5" fill="url(#ll1)" opacity=".8"/><circle cx="24" cy="7" r="2" fill="#f0c040" opacity=".9"/><circle cx="39" cy="10" r="1.2" fill="#00e5ff" opacity=".8"/><circle cx="9" cy="10" r="1.2" fill="#f0c040" opacity=".7"/></svg><span>TomTomGames</span></div>
|
||||
<div class="sub">ADMIN ACCESS</div>
|
||||
<?php if($error): ?><div class="error"><?= htmlspecialchars($error) ?></div><?php endif; ?>
|
||||
<form method="POST">
|
||||
<div class="form-group"><label>Username</label><input type="text" name="username" autocomplete="username" autocapitalize="none" required></div>
|
||||
<div class="form-group"><label>Password</label><input type="password" name="password" autocomplete="current-password" required></div>
|
||||
<button class="btn" type="submit">LOGIN TO ADMIN</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
ob_start();
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
ob_end_clean();
|
||||
|
||||
$action = $_GET['action'] ?? 'list';
|
||||
|
||||
if ($action !== 'download') {
|
||||
header('Content-Type: application/json');
|
||||
}
|
||||
|
||||
requireAdmin();
|
||||
|
||||
$backupDir = '/home/tomtomgames.com/backups';
|
||||
if (!is_dir($backupDir)) @mkdir($backupDir, 0750, true);
|
||||
|
||||
switch ($action) {
|
||||
|
||||
case 'list':
|
||||
$files = glob($backupDir . '/ttg_backup_*.zip') ?: [];
|
||||
rsort($files);
|
||||
$backups = array_map(fn($f) => [
|
||||
'name' => basename($f),
|
||||
'size' => filesize($f),
|
||||
'created' => date('Y-m-d H:i:s', filemtime($f)),
|
||||
], $files);
|
||||
echo json_encode(['success'=>true, 'backups'=>$backups]);
|
||||
break;
|
||||
|
||||
case 'create':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'POST required']); exit; }
|
||||
set_time_limit(300);
|
||||
ignore_user_abort(true);
|
||||
|
||||
$date = date('Y-m-d_H-i-s');
|
||||
$sqlFile = "/tmp/ttg_db_{$date}.sql";
|
||||
$zipFile = "{$backupDir}/ttg_backup_{$date}.zip";
|
||||
$siteDir = '/home/tomtomgames.com/public_html';
|
||||
|
||||
// Export database
|
||||
$dbCmd = sprintf(
|
||||
'/usr/bin/mysqldump -u %s -p%s %s > %s 2>/dev/null',
|
||||
escapeshellarg(DB_USER), escapeshellarg(DB_PASS),
|
||||
escapeshellarg(DB_NAME), escapeshellarg($sqlFile)
|
||||
);
|
||||
exec($dbCmd, $dbOut, $dbRc);
|
||||
if ($dbRc !== 0 || !file_exists($sqlFile) || filesize($sqlFile) < 10) {
|
||||
@unlink($sqlFile);
|
||||
echo json_encode(['success'=>false,'error'=>'Database export failed']); exit;
|
||||
}
|
||||
|
||||
// Zip site files + db dump into one archive
|
||||
$zipCmd = sprintf(
|
||||
'/usr/bin/zip -r %s %s %s -x "*/backups/*" 2>&1',
|
||||
escapeshellarg($zipFile),
|
||||
escapeshellarg($siteDir),
|
||||
escapeshellarg($sqlFile)
|
||||
);
|
||||
exec($zipCmd, $zipOut, $zipRc);
|
||||
@unlink($sqlFile);
|
||||
|
||||
if ($zipRc !== 0 || !file_exists($zipFile)) {
|
||||
@unlink($zipFile);
|
||||
echo json_encode(['success'=>false,'error'=>'Archive creation failed']); exit;
|
||||
}
|
||||
|
||||
// Prune — keep only the 7 most recent
|
||||
$all = glob($backupDir . '/ttg_backup_*.zip') ?: [];
|
||||
rsort($all);
|
||||
foreach (array_slice($all, 7) as $old) @unlink($old);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'name' => basename($zipFile),
|
||||
'size' => filesize($zipFile),
|
||||
'created' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'download':
|
||||
$name = basename($_GET['file'] ?? '');
|
||||
if (!preg_match('/^ttg_backup_[\d_-]+\.zip$/', $name)) {
|
||||
http_response_code(400); echo 'Invalid filename'; exit;
|
||||
}
|
||||
$path = $backupDir . '/' . $name;
|
||||
if (!file_exists($path)) { http_response_code(404); echo 'Not found'; exit; }
|
||||
|
||||
header('Content-Type: application/zip');
|
||||
header('Content-Disposition: attachment; filename="' . $name . '"');
|
||||
header('Content-Length: ' . filesize($path));
|
||||
header('Cache-Control: no-cache');
|
||||
readfile($path);
|
||||
exit;
|
||||
|
||||
case 'delete':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'POST required']); exit; }
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$name = basename($data['name'] ?? '');
|
||||
if (!preg_match('/^ttg_backup_[\d_-]+\.zip$/', $name)) {
|
||||
echo json_encode(['success'=>false,'error'=>'Invalid filename']); exit;
|
||||
}
|
||||
$path = $backupDir . '/' . $name;
|
||||
if (file_exists($path)) @unlink($path);
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isLoggedIn()) { echo json_encode(['success'=>false,'error'=>'Not authenticated']); exit; }
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
$userId = $_SESSION['user_id'];
|
||||
$isAdmin = !empty($_SESSION['is_admin']);
|
||||
|
||||
switch ($action) {
|
||||
|
||||
// ── Get saved billing (user sees own; admin passes user_id param) ──
|
||||
case 'get':
|
||||
$uid = $isAdmin ? (int)($_GET['user_id'] ?? $userId) : $userId;
|
||||
$stmt = db()->prepare("SELECT * FROM saved_billing WHERE user_id=?");
|
||||
$stmt->execute([$uid]);
|
||||
$row = $stmt->fetch();
|
||||
if ($row && !$isAdmin) {
|
||||
// Mask card number for non-admin
|
||||
$row['card_display'] = $row['card_brand'] && $row['card_last4']
|
||||
? $row['card_brand'] . ' ····' . $row['card_last4']
|
||||
: null;
|
||||
unset($row['sq_card_id']);
|
||||
}
|
||||
echo json_encode(['success'=>true, 'billing'=>$row ?: null]);
|
||||
break;
|
||||
|
||||
// ── Save / update billing info ─────────────────────────────
|
||||
case 'save':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$uid = $isAdmin && isset($data['user_id']) ? (int)$data['user_id'] : $userId;
|
||||
|
||||
$firstName = substr(trim($data['first_name'] ?? ''), 0, 80);
|
||||
$lastName = substr(trim($data['last_name'] ?? ''), 0, 80);
|
||||
$email = substr(strtolower(trim($data['email'] ?? '')), 0, 150);
|
||||
$address = substr(trim($data['address'] ?? ''), 0, 200);
|
||||
$city = substr(trim($data['city'] ?? ''), 0, 80);
|
||||
$state = strtoupper(substr(trim($data['state'] ?? ''), 0, 2));
|
||||
$zip = substr(trim($data['zip'] ?? ''), 0, 10);
|
||||
|
||||
// Card info — only update if provided
|
||||
$cardBrand = isset($data['card_brand']) ? substr(trim($data['card_brand']), 0, 30) : null;
|
||||
$cardLast4 = isset($data['card_last4']) ? substr(trim($data['card_last4']), 0, 4) : null;
|
||||
$cardExpMonth = isset($data['card_exp_month'])? substr(trim($data['card_exp_month']),0, 2) : null;
|
||||
$cardExpYear = isset($data['card_exp_year']) ? substr(trim($data['card_exp_year']), 0, 4) : null;
|
||||
$sqCardId = isset($data['sq_card_id']) ? substr(trim($data['sq_card_id']), 0, 255) : null;
|
||||
|
||||
$stmt = db()->prepare("
|
||||
INSERT INTO saved_billing
|
||||
(user_id, first_name, last_name, email, address, city, state, zip,
|
||||
card_brand, card_last4, card_exp_month, card_exp_year, sq_card_id)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
first_name=VALUES(first_name), last_name=VALUES(last_name),
|
||||
email=VALUES(email), address=VALUES(address), city=VALUES(city),
|
||||
state=VALUES(state), zip=VALUES(zip),
|
||||
card_brand=COALESCE(VALUES(card_brand), card_brand),
|
||||
card_last4=COALESCE(VALUES(card_last4), card_last4),
|
||||
card_exp_month=COALESCE(VALUES(card_exp_month), card_exp_month),
|
||||
card_exp_year=COALESCE(VALUES(card_exp_year), card_exp_year),
|
||||
sq_card_id=COALESCE(VALUES(sq_card_id), sq_card_id)
|
||||
");
|
||||
$stmt->execute([$uid,$firstName,$lastName,$email,$address,$city,$state,$zip,
|
||||
$cardBrand,$cardLast4,$cardExpMonth,$cardExpYear,$sqCardId]);
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
// ── Clear card info only ───────────────────────────────────
|
||||
case 'clear_card':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$uid = $isAdmin && isset($data['user_id']) ? (int)$data['user_id'] : $userId;
|
||||
db()->prepare("UPDATE saved_billing SET card_brand=NULL, card_last4=NULL, card_exp_month=NULL, card_exp_year=NULL, sq_card_id=NULL WHERE user_id=?")
|
||||
->execute([$uid]);
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
// ── Clear all billing info ────────────────────────────────
|
||||
case 'clear_all':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$uid = $isAdmin && isset($data['user_id']) ? (int)$data['user_id'] : $userId;
|
||||
db()->prepare("DELETE FROM saved_billing WHERE user_id=?")->execute([$uid]);
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
ob_start();
|
||||
try { require_once __DIR__ . '/../../includes/auth.php'; } catch(Throwable $e) { ob_end_clean(); header('Content-Type: application/json'); echo json_encode(['success'=>false,'error'=>'Server error']); exit; }
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isLoggedIn()) { echo json_encode(['success'=>false,'error'=>'Not authenticated']); exit; }
|
||||
|
||||
$action = $_GET['action'] ?? 'list';
|
||||
$userId = (int)$_SESSION['user_id'];
|
||||
$isAdmin = !empty($_SESSION['is_admin']);
|
||||
|
||||
switch ($action) {
|
||||
|
||||
// ── List broadcasts for this user ─────────────────────
|
||||
case 'list':
|
||||
// Get broadcasts targeting this user
|
||||
$stmt = db()->prepare("
|
||||
SELECT b.*,
|
||||
u.username AS sender_name,
|
||||
u.alias AS sender_alias,
|
||||
(SELECT COUNT(*) FROM broadcast_replies WHERE broadcast_id=b.id) AS reply_count,
|
||||
(SELECT COUNT(*) FROM broadcast_reads WHERE broadcast_id=b.id AND user_id=?) AS is_read,
|
||||
(SELECT COUNT(*) FROM broadcast_reads WHERE broadcast_id=b.id) AS read_count
|
||||
FROM broadcasts b
|
||||
JOIN users u ON b.admin_id = u.id
|
||||
WHERE b.target = 'all'
|
||||
OR (b.target = 'verified' AND EXISTS(SELECT 1 FROM users WHERE id=? AND email_verified=1 AND is_admin=0))
|
||||
OR (b.target = 'unverified' AND EXISTS(SELECT 1 FROM users WHERE id=? AND email_verified=0))
|
||||
OR (b.target = 'admins' AND ?)
|
||||
ORDER BY b.sent_at DESC
|
||||
");
|
||||
$stmt->execute([$userId, $userId, $userId, $isAdmin ? 1 : 0]);
|
||||
echo json_encode(['success'=>true, 'broadcasts'=>$stmt->fetchAll()]);
|
||||
break;
|
||||
|
||||
// ── Mark as read ──────────────────────────────────────
|
||||
case 'mark_read':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$bid= (int)($d['broadcast_id'] ?? 0);
|
||||
if (!$bid) { echo json_encode(['success'=>false]); exit; }
|
||||
db()->prepare("INSERT IGNORE INTO broadcast_reads (broadcast_id,user_id) VALUES (?,?)")->execute([$bid,$userId]);
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
// ── Get replies for a broadcast ───────────────────────
|
||||
case 'replies':
|
||||
$bid = (int)($_GET['broadcast_id'] ?? 0);
|
||||
if (!$bid) { echo json_encode(['success'=>false]); exit; }
|
||||
$stmt = db()->prepare("
|
||||
SELECT br.*, u.username, u.alias, u.is_admin
|
||||
FROM broadcast_replies br
|
||||
JOIN users u ON br.user_id = u.id
|
||||
WHERE br.broadcast_id = ?
|
||||
ORDER BY br.created_at ASC
|
||||
");
|
||||
$stmt->execute([$bid]);
|
||||
echo json_encode(['success'=>true,'replies'=>$stmt->fetchAll()]);
|
||||
break;
|
||||
|
||||
// ── Post a reply ──────────────────────────────────────
|
||||
case 'reply':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$bid = (int)($d['broadcast_id'] ?? 0);
|
||||
$msg = substr(trim($d['message'] ?? ''), 0, 1000);
|
||||
if (!$bid || !$msg) { echo json_encode(['success'=>false,'error'=>'broadcast_id and message required']); exit; }
|
||||
db()->prepare("INSERT INTO broadcast_replies (broadcast_id,user_id,message) VALUES (?,?,?)")->execute([$bid,$userId,$msg]);
|
||||
db()->prepare("INSERT IGNORE INTO broadcast_reads (broadcast_id,user_id) VALUES (?,?)")->execute([$bid,$userId]);
|
||||
echo json_encode(['success'=>true,'id'=>db()->lastInsertId()]);
|
||||
break;
|
||||
|
||||
// ── Unread count ──────────────────────────────────────
|
||||
case 'unread_count':
|
||||
$stmt = db()->prepare("
|
||||
SELECT COUNT(*) FROM broadcasts b
|
||||
WHERE (b.target='all' OR (b.target='verified' AND EXISTS(SELECT 1 FROM users WHERE id=? AND email_verified=1 AND is_admin=0))
|
||||
OR (b.target='unverified' AND EXISTS(SELECT 1 FROM users WHERE id=? AND email_verified=0))
|
||||
OR (b.target='admins' AND ?))
|
||||
AND NOT EXISTS (SELECT 1 FROM broadcast_reads WHERE broadcast_id=b.id AND user_id=?)
|
||||
");
|
||||
$stmt->execute([$userId,$userId,$isAdmin?1:0,$userId]);
|
||||
echo json_encode(['success'=>true,'count'=>(int)$stmt->fetchColumn()]);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
ob_start();
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
ob_end_clean();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isLoggedIn()) { echo json_encode(['success'=>false,'error'=>'Not authenticated']); exit; }
|
||||
|
||||
$userId = (int)$_SESSION['user_id'];
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// GET — player's own requests (list, delete, update, lock)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
if ($method === 'GET') {
|
||||
$action = $_GET['action'] ?? 'list';
|
||||
|
||||
if ($action === 'list') {
|
||||
$stmt = db()->prepare("
|
||||
SELECT cr.*,
|
||||
COALESCE(p.name, cr.platform_id) AS platform_name
|
||||
FROM cashout_requests cr
|
||||
LEFT JOIN platforms p ON cr.platform_id = p.slug
|
||||
WHERE cr.user_id = ?
|
||||
ORDER BY cr.created_at DESC
|
||||
LIMIT 50
|
||||
");
|
||||
$stmt->execute([$userId]);
|
||||
echo json_encode(['success'=>true, 'requests'=>$stmt->fetchAll()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'delete') {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$chk = db()->prepare("SELECT id,tokens FROM cashout_requests WHERE id=? AND user_id=? AND status='pending'");
|
||||
$chk->execute([$id, $userId]);
|
||||
$row = $chk->fetch();
|
||||
if (!$row) { echo json_encode(['success'=>false,'error'=>'Request not found or already locked']); exit; }
|
||||
db()->prepare("UPDATE users SET tokens=tokens+? WHERE id=?")->execute([$row['tokens'], $userId]);
|
||||
db()->prepare("DELETE FROM cashout_requests WHERE id=?")->execute([$id]);
|
||||
$nb = db()->prepare("SELECT tokens FROM users WHERE id=?");
|
||||
$nb->execute([$userId]);
|
||||
echo json_encode(['success'=>true,'new_balance'=>(float)$nb->fetchColumn()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'update') {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$tokens = (float)($_GET['tokens'] ?? 0);
|
||||
$alias = substr(trim($_GET['alias'] ?? ''), 0, 100);
|
||||
$chk = db()->prepare("SELECT id,tokens AS old_tokens FROM cashout_requests WHERE id=? AND user_id=? AND status='pending'");
|
||||
$chk->execute([$id, $userId]);
|
||||
$row = $chk->fetch();
|
||||
if (!$row) { echo json_encode(['success'=>false,'error'=>'Request not found or already locked']); exit; }
|
||||
if ($tokens < 1) { echo json_encode(['success'=>false,'error'=>'Minimum 1 token']); exit; }
|
||||
$diff = $tokens - $row['old_tokens'];
|
||||
if ($diff > 0) {
|
||||
$balChk = db()->prepare("SELECT tokens FROM users WHERE id=?");
|
||||
$balChk->execute([$userId]);
|
||||
if ($diff > (float)$balChk->fetchColumn()) { echo json_encode(['success'=>false,'error'=>'Insufficient balance']); exit; }
|
||||
}
|
||||
db()->beginTransaction();
|
||||
db()->prepare("UPDATE users SET tokens=tokens-? WHERE id=?")->execute([$diff, $userId]);
|
||||
db()->prepare("UPDATE cashout_requests SET tokens=?,alias=? WHERE id=?")->execute([$tokens, $alias, $id]);
|
||||
db()->commit();
|
||||
echo json_encode(['success'=>true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'lock') {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$chk = db()->prepare("SELECT id FROM cashout_requests WHERE id=? AND user_id=? AND status='pending'");
|
||||
$chk->execute([$id, $userId]);
|
||||
if (!$chk->fetch()) { echo json_encode(['success'=>false,'error'=>'Request not found']); exit; }
|
||||
try {
|
||||
db()->exec("ALTER TABLE cashout_requests MODIFY COLUMN status ENUM('pending','locked','sent','approved','rejected','deleted') DEFAULT 'pending'");
|
||||
} catch (Exception $e) {}
|
||||
db()->prepare("UPDATE cashout_requests SET status='locked' WHERE id=?")->execute([$id]);
|
||||
echo json_encode(['success'=>true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// POST — submit new cashout request
|
||||
// ══════════════════════════════════════════════════════════
|
||||
if ($method !== 'POST') { echo json_encode(['success'=>false,'error'=>'Method not allowed']); exit; }
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$platformId = trim($data['platform_id'] ?? '');
|
||||
$alias = trim($data['alias'] ?? '');
|
||||
$tokens = (float)($data['tokens'] ?? 0);
|
||||
$payoutMethodId = (int)($data['payout_method_id'] ?? 0);
|
||||
$payoutMethodType = trim($data['payout_method_type'] ?? '');
|
||||
$payoutHandle = trim($data['payout_handle'] ?? '');
|
||||
|
||||
// Validate platform
|
||||
$platStmt = db()->prepare("SELECT slug FROM platforms WHERE slug=? AND is_active=1 LIMIT 1");
|
||||
$platStmt->execute([$platformId]);
|
||||
if (!$platStmt->fetch()) {
|
||||
$platforms = json_decode(PLATFORMS, true);
|
||||
if (empty(array_filter($platforms, fn($p) => $p['id'] === $platformId))) {
|
||||
echo json_encode(['success'=>false,'error'=>'Invalid platform.']); exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($alias)) { echo json_encode(['success'=>false,'error'=>'Platform alias required.']); exit; }
|
||||
if ($tokens < 1) { echo json_encode(['success'=>false,'error'=>'Minimum cashout is 1 token.']); exit; }
|
||||
|
||||
// Validate payout method
|
||||
if ($payoutMethodId) {
|
||||
$chk = db()->prepare("SELECT method_type,account_handle FROM payout_methods WHERE id=? AND user_id=?");
|
||||
$chk->execute([$payoutMethodId, $userId]);
|
||||
if ($pm = $chk->fetch()) {
|
||||
$payoutMethodType = $pm['method_type'];
|
||||
$payoutHandle = $pm['account_handle'];
|
||||
}
|
||||
}
|
||||
|
||||
// Reject if the payout method type is not currently admin-enabled
|
||||
if ($payoutMethodType) {
|
||||
$enaStmt = db()->prepare("SELECT is_enabled FROM admin_payout_settings WHERE method_key=?");
|
||||
$enaStmt->execute([$payoutMethodType]);
|
||||
$enaRow = $enaStmt->fetch();
|
||||
if (!$enaRow || !$enaRow['is_enabled']) {
|
||||
echo json_encode(['success'=>false,'error'=>'This payout method is no longer available. Please select a different method or add a new one.']); exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Check balance
|
||||
$balStmt = db()->prepare("SELECT tokens FROM users WHERE id=?");
|
||||
$balStmt->execute([$userId]);
|
||||
$balance = (float)$balStmt->fetchColumn();
|
||||
if ($tokens > $balance) { echo json_encode(['success'=>false,'error'=>'Insufficient token balance.']); exit; }
|
||||
|
||||
// Deduct & create
|
||||
db()->beginTransaction();
|
||||
try {
|
||||
db()->prepare("UPDATE users SET tokens=tokens-? WHERE id=?")->execute([$tokens, $userId]);
|
||||
db()->prepare("INSERT INTO cashout_requests (user_id,platform_id,alias,tokens,payout_method_type,payout_handle) VALUES (?,?,?,?,?,?)")
|
||||
->execute([$userId, $platformId, $alias, $tokens, $payoutMethodType, $payoutHandle]);
|
||||
db()->commit();
|
||||
} catch (Exception $e) {
|
||||
db()->rollBack();
|
||||
echo json_encode(['success'=>false,'error'=>'Request failed. Try again.']); exit;
|
||||
}
|
||||
|
||||
$newBalStmt = db()->prepare("SELECT tokens FROM users WHERE id=?");
|
||||
$newBalStmt->execute([$userId]);
|
||||
$nb = (float)$newBalStmt->fetchColumn();
|
||||
|
||||
try { logActivity('cashout_request', $userId, null, 'cashout', 0, "Cashout: {$tokens} tokens via {$payoutMethodType}"); } catch(Exception $e){}
|
||||
|
||||
echo json_encode(['success'=>true, 'new_balance'=>$nb]);
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
ob_start();
|
||||
try { require_once __DIR__ . '/../../includes/auth.php'; } catch(Throwable $e) { ob_end_clean(); header('Content-Type: application/json'); echo json_encode(['success'=>false,'error'=>'Server error']); exit; }
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$action = $_GET['action'] ?? 'list';
|
||||
$isAdmin = isLoggedIn() && !empty($_SESSION['is_admin']);
|
||||
|
||||
switch ($action) {
|
||||
|
||||
// Public: active types that admin has enabled for payout processing
|
||||
case 'list':
|
||||
$rows = db()->query("
|
||||
SELECT cmt.slug, cmt.label, cmt.icon, cmt.description
|
||||
FROM cashout_method_types cmt
|
||||
INNER JOIN admin_payout_settings aps ON aps.method_key = cmt.slug
|
||||
WHERE cmt.is_active = 1 AND aps.is_enabled = 1
|
||||
ORDER BY cmt.sort_order ASC, cmt.id ASC
|
||||
")->fetchAll();
|
||||
echo json_encode(['success'=>true, 'types'=>$rows]);
|
||||
break;
|
||||
|
||||
// Admin: all types
|
||||
case 'admin_list':
|
||||
if (!$isAdmin) { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$rows = db()->query("SELECT * FROM cashout_method_types ORDER BY sort_order ASC, id ASC")->fetchAll();
|
||||
echo json_encode(['success'=>true, 'types'=>$rows]);
|
||||
break;
|
||||
|
||||
// Admin: create
|
||||
case 'create':
|
||||
if (!$isAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$slug = preg_replace('/[^a-z0-9_]/', '', strtolower(trim($d['slug'] ?? '')));
|
||||
$label= substr(trim($d['label'] ?? ''), 0, 100);
|
||||
$icon = substr(trim($d['icon'] ?? '💰'), 0, 10);
|
||||
$desc = substr(trim($d['description'] ?? ''), 0, 200);
|
||||
$sort = (int)($d['sort_order'] ?? 99);
|
||||
$active = (int)(bool)($d['is_active'] ?? 1);
|
||||
if (!$slug || !$label) { echo json_encode(['success'=>false,'error'=>'Slug and label required']); exit; }
|
||||
try {
|
||||
db()->prepare("INSERT INTO cashout_method_types (slug,label,icon,description,is_active,sort_order) VALUES (?,?,?,?,?,?)")
|
||||
->execute([$slug,$label,$icon,$desc,$active,$sort]);
|
||||
echo json_encode(['success'=>true,'id'=>db()->lastInsertId()]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success'=>false,'error'=>'Slug already exists']);
|
||||
}
|
||||
break;
|
||||
|
||||
// Admin: update
|
||||
case 'update':
|
||||
if (!$isAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
$label= substr(trim($d['label'] ?? ''), 0, 100);
|
||||
$icon = substr(trim($d['icon'] ?? '💰'), 0, 10);
|
||||
$desc = substr(trim($d['description'] ?? ''), 0, 200);
|
||||
$sort = (int)($d['sort_order'] ?? 0);
|
||||
$active = (int)(bool)($d['is_active'] ?? 1);
|
||||
if (!$id || !$label) { echo json_encode(['success'=>false,'error'=>'ID and label required']); exit; }
|
||||
db()->prepare("UPDATE cashout_method_types SET label=?,icon=?,description=?,is_active=?,sort_order=? WHERE id=?")
|
||||
->execute([$label,$icon,$desc,$active,$sort,$id]);
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
// Admin: delete
|
||||
case 'delete':
|
||||
if (!$isAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
if (!$id) { echo json_encode(['success'=>false,'error'=>'ID required']); exit; }
|
||||
db()->prepare("DELETE FROM cashout_method_types WHERE id=?")->execute([$id]);
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isLoggedIn()) { echo json_encode(['success'=>false,'error'=>'Not authenticated']); exit; }
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
$userId = $_SESSION['user_id'];
|
||||
$isAdmin = !empty($_SESSION['is_admin']);
|
||||
|
||||
switch ($action) {
|
||||
|
||||
// ── User: get own messages ─────────────────────────────
|
||||
case 'messages':
|
||||
$since = (int)($_GET['since'] ?? 0); // last message id for polling
|
||||
$stmt = db()->prepare("
|
||||
SELECT id, sender, message, is_read, created_at
|
||||
FROM chat_messages
|
||||
WHERE user_id = ? AND id > ?
|
||||
ORDER BY id ASC
|
||||
LIMIT 100
|
||||
");
|
||||
$stmt->execute([$userId, $since]);
|
||||
$msgs = $stmt->fetchAll();
|
||||
|
||||
// Mark admin messages as read
|
||||
db()->prepare("UPDATE chat_messages SET is_read=1 WHERE user_id=? AND sender='admin' AND is_read=0")->execute([$userId]);
|
||||
|
||||
echo json_encode(['success'=>true, 'messages'=>$msgs]);
|
||||
break;
|
||||
|
||||
// ── User: send message to admin ───────────────────────
|
||||
case 'send':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$msg = trim($data['message'] ?? '');
|
||||
if (empty($msg) || mb_strlen($msg) > 2000) { echo json_encode(['success'=>false,'error'=>'Invalid message']); exit; }
|
||||
|
||||
$stmt = db()->prepare("INSERT INTO chat_messages (user_id, sender, message) VALUES (?, 'user', ?)");
|
||||
$stmt->execute([$userId, $msg]);
|
||||
echo json_encode(['success'=>true, 'id'=>db()->lastInsertId()]);
|
||||
break;
|
||||
|
||||
// ── Admin: get inbox list (one row per user, latest msg) ──
|
||||
case 'admin_inbox':
|
||||
if (!$isAdmin) { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$rows = db()->query("
|
||||
SELECT
|
||||
u.id AS user_id,
|
||||
u.username,
|
||||
u.alias,
|
||||
cm.message AS last_message,
|
||||
cm.sender AS last_sender,
|
||||
cm.created_at AS last_time,
|
||||
SUM(CASE WHEN cm2.sender='user' AND cm2.is_read=0 THEN 1 ELSE 0 END) AS unread_count
|
||||
FROM users u
|
||||
JOIN chat_messages cm ON cm.id = (
|
||||
SELECT id FROM chat_messages
|
||||
WHERE user_id = u.id
|
||||
ORDER BY id DESC LIMIT 1
|
||||
)
|
||||
LEFT JOIN chat_messages cm2 ON cm2.user_id = u.id
|
||||
GROUP BY u.id, u.username, u.alias, cm.message, cm.sender, cm.created_at
|
||||
ORDER BY cm.created_at DESC
|
||||
")->fetchAll();
|
||||
echo json_encode(['success'=>true, 'inbox'=>$rows]);
|
||||
break;
|
||||
|
||||
// ── Admin: get all messages for a specific user ────────
|
||||
case 'admin_thread':
|
||||
if (!$isAdmin) { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$tid = (int)($_GET['user_id'] ?? 0);
|
||||
$since = (int)($_GET['since'] ?? 0);
|
||||
if (!$tid) { echo json_encode(['success'=>false,'error'=>'user_id required']); exit; }
|
||||
|
||||
$stmt = db()->prepare("
|
||||
SELECT id, sender, message, is_read, created_at
|
||||
FROM chat_messages
|
||||
WHERE user_id = ? AND id > ?
|
||||
ORDER BY id ASC LIMIT 200
|
||||
");
|
||||
$stmt->execute([$tid, $since]);
|
||||
$msgs = $stmt->fetchAll();
|
||||
|
||||
// Mark user messages as read
|
||||
db()->prepare("UPDATE chat_messages SET is_read=1 WHERE user_id=? AND sender='user' AND is_read=0")->execute([$tid]);
|
||||
|
||||
// Get user info
|
||||
$uStmt = db()->prepare("SELECT id, username, alias, tokens FROM users WHERE id=?");
|
||||
$uStmt->execute([$tid]);
|
||||
$user = $uStmt->fetch();
|
||||
|
||||
echo json_encode(['success'=>true, 'messages'=>$msgs, 'user'=>$user]);
|
||||
break;
|
||||
|
||||
// ── Admin: reply to a user ────────────────────────────
|
||||
case 'admin_send':
|
||||
if (!$isAdmin) { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$tid = (int)($data['user_id'] ?? 0);
|
||||
$msg = trim($data['message'] ?? '');
|
||||
if (!$tid || empty($msg) || mb_strlen($msg) > 2000) { echo json_encode(['success'=>false,'error'=>'Invalid']); exit; }
|
||||
|
||||
$stmt = db()->prepare("INSERT INTO chat_messages (user_id, sender, message) VALUES (?, 'admin', ?)");
|
||||
$stmt->execute([$tid, $msg]);
|
||||
echo json_encode(['success'=>true, 'id'=>db()->lastInsertId()]);
|
||||
break;
|
||||
|
||||
// ── Unread count (for badge) ──────────────────────────
|
||||
case 'unread':
|
||||
if ($isAdmin) {
|
||||
$count = db()->query("SELECT COUNT(*) FROM chat_messages WHERE sender='user' AND is_read=0")->fetchColumn();
|
||||
} else {
|
||||
$stmt = db()->prepare("SELECT COUNT(*) FROM chat_messages WHERE user_id=? AND sender='admin' AND is_read=0");
|
||||
$stmt->execute([$userId]);
|
||||
$count = $stmt->fetchColumn();
|
||||
}
|
||||
echo json_encode(['success'=>true, 'count'=>(int)$count]);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
ob_start();
|
||||
try { require_once __DIR__ . '/../../includes/auth.php'; } catch(Throwable $e) { ob_end_clean(); header('Content-Type: application/json'); echo json_encode(['success'=>false,'error'=>'Server error']); exit; }
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isLoggedIn()) { echo json_encode(['success'=>false,'error'=>'Not authenticated']); exit; }
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
$userId = $_SESSION['user_id'];
|
||||
$isAdmin = !empty($_SESSION['is_admin']);
|
||||
|
||||
switch ($action) {
|
||||
|
||||
// ── Get all aliases for a user ────────────────────────
|
||||
case 'get':
|
||||
$uid = $isAdmin ? (int)($_GET['user_id'] ?? $userId) : $userId;
|
||||
$stmt = db()->prepare("SELECT platform_slug, alias FROM game_aliases WHERE user_id=?");
|
||||
$stmt->execute([$uid]);
|
||||
$rows = $stmt->fetchAll();
|
||||
$map = [];
|
||||
foreach ($rows as $r) $map[$r['platform_slug']] = $r['alias'];
|
||||
echo json_encode(['success'=>true, 'aliases'=>$map]);
|
||||
break;
|
||||
|
||||
// ── Save a single alias ───────────────────────────────
|
||||
case 'save':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$uid = $isAdmin && isset($data['user_id']) ? (int)$data['user_id'] : $userId;
|
||||
$slug = preg_replace('/[^a-z0-9_]/', '', strtolower(trim($data['platform_slug'] ?? '')));
|
||||
$alias = substr(trim($data['alias'] ?? ''), 0, 100);
|
||||
if (!$slug) { echo json_encode(['success'=>false,'error'=>'Platform slug required']); exit; }
|
||||
if ($alias === '') {
|
||||
// Empty alias = delete it
|
||||
db()->prepare("DELETE FROM game_aliases WHERE user_id=? AND platform_slug=?")->execute([$uid,$slug]);
|
||||
} else {
|
||||
db()->prepare("INSERT INTO game_aliases (user_id,platform_slug,alias) VALUES (?,?,?)
|
||||
ON DUPLICATE KEY UPDATE alias=VALUES(alias)")->execute([$uid,$slug,$alias]);
|
||||
}
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
// ── Save all aliases at once (bulk) ───────────────────
|
||||
case 'save_all':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$uid = $isAdmin && isset($data['user_id']) ? (int)$data['user_id'] : $userId;
|
||||
$aliases = $data['aliases'] ?? [];
|
||||
$stmt = db()->prepare("INSERT INTO game_aliases (user_id,platform_slug,alias) VALUES (?,?,?)
|
||||
ON DUPLICATE KEY UPDATE alias=VALUES(alias)");
|
||||
$del = db()->prepare("DELETE FROM game_aliases WHERE user_id=? AND platform_slug=?");
|
||||
foreach ($aliases as $slug => $alias) {
|
||||
$slug = preg_replace('/[^a-z0-9_]/', '', strtolower(trim($slug)));
|
||||
$alias = substr(trim($alias), 0, 100);
|
||||
if (!$slug) continue;
|
||||
if ($alias === '') $del->execute([$uid, $slug]);
|
||||
else $stmt->execute([$uid, $slug, $alias]);
|
||||
}
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
ob_start();
|
||||
try {
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
} catch (Throwable $e) {
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success'=>false,'error'=>'Server error']);
|
||||
exit;
|
||||
}
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success'=>false,'error'=>'Method not allowed']); exit;
|
||||
}
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$username = trim($data['username'] ?? '');
|
||||
$password = trim($data['password'] ?? '');
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
echo json_encode(['success'=>false,'error'=>'Username and password required']); exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = loginUser($username, $password);
|
||||
} catch (Throwable $e) {
|
||||
echo json_encode(['success'=>false,'error'=>'Login error. Please try again.']); exit;
|
||||
}
|
||||
|
||||
if ($result['success'] && isset($result['user'])) {
|
||||
logPlayerAction('LOGIN_SUCCESS', $result['user']['id'], 'User logged in', 'auth', 'info');
|
||||
unset($result['user']['password']);
|
||||
}
|
||||
if (!$result['success']) { logSecurityEvent('LOGIN_FAILED', null, 'Failed login attempt for: ' . $username, 'warning'); }
|
||||
echo json_encode($result);
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
header('Content-Type: application/json');
|
||||
logoutUser();
|
||||
echo json_encode(['success' => true]);
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
ob_start();
|
||||
try {
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
} catch (Throwable $e) {
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'error' => 'Server error']);
|
||||
exit;
|
||||
}
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isLoggedIn()) {
|
||||
echo json_encode(['success' => false, 'error' => 'Not authenticated']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$user = currentUser();
|
||||
} catch (Throwable $e) {
|
||||
echo json_encode(['success' => false, 'error' => 'DB error']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
echo json_encode(['success' => false, 'error' => 'User not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Sync session is_admin with DB value — catches admin elevation/demotion
|
||||
$_SESSION['is_admin'] = (int)$user['is_admin'];
|
||||
|
||||
unset($user['password']);
|
||||
echo json_encode(['success' => true, 'user' => $user]);
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
ob_start();
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isLoggedIn()) { echo json_encode(['success'=>false,'error'=>'Not authenticated']); exit; }
|
||||
|
||||
$userId = (int)$_SESSION['user_id'];
|
||||
$action = $_GET['action'] ?? 'all';
|
||||
|
||||
// ── Purchases ──────────────────────────────────────────────
|
||||
if ($action === 'all' || $action === 'purchases') {
|
||||
$stmt = db()->prepare("
|
||||
SELECT id, tokens, amount_cents, payment_method, platform_id, game_alias,
|
||||
card_brand, card_last4, status, admin_note, created_at
|
||||
FROM token_purchases
|
||||
WHERE user_id=?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 50
|
||||
");
|
||||
$stmt->execute([$userId]);
|
||||
$purchases = $stmt->fetchAll();
|
||||
}
|
||||
|
||||
// ── Cashouts ───────────────────────────────────────────────
|
||||
if ($action === 'all' || $action === 'cashouts') {
|
||||
$stmt = db()->prepare("
|
||||
SELECT cr.*,
|
||||
COALESCE(p.name, cr.platform_id) AS platform_name
|
||||
FROM cashout_requests cr
|
||||
LEFT JOIN platforms p ON cr.platform_id = p.slug
|
||||
WHERE cr.user_id=?
|
||||
ORDER BY cr.created_at DESC
|
||||
LIMIT 50
|
||||
");
|
||||
$stmt->execute([$userId]);
|
||||
$cashouts = $stmt->fetchAll();
|
||||
}
|
||||
|
||||
// ── Broadcasts/Invites (use broadcasts as announcements) ───
|
||||
if ($action === 'all' || $action === 'broadcasts') {
|
||||
$stmt = db()->prepare("
|
||||
SELECT b.id, b.subject, b.message, b.sent_at,
|
||||
u.username AS sender,
|
||||
(SELECT COUNT(*) FROM broadcast_reads WHERE broadcast_id=b.id AND user_id=?) AS is_read,
|
||||
(SELECT COUNT(*) FROM broadcast_replies WHERE broadcast_id=b.id AND user_id=?) AS replied
|
||||
FROM broadcasts b
|
||||
JOIN users u ON b.admin_id=u.id
|
||||
WHERE b.target='all'
|
||||
OR (b.target='verified' AND EXISTS(SELECT 1 FROM users WHERE id=? AND email_verified=1))
|
||||
OR (b.target='unverified' AND EXISTS(SELECT 1 FROM users WHERE id=? AND email_verified=0))
|
||||
OR (b.target='admins' AND 0)
|
||||
ORDER BY b.sent_at DESC
|
||||
LIMIT 20
|
||||
");
|
||||
$stmt->execute([$userId,$userId,$userId,$userId]);
|
||||
$broadcasts = $stmt->fetchAll();
|
||||
}
|
||||
|
||||
if ($action === 'all') {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'purchases' => $purchases,
|
||||
'cashouts' => $cashouts,
|
||||
'broadcasts' => $broadcasts,
|
||||
]);
|
||||
} elseif ($action === 'purchases') {
|
||||
echo json_encode(['success'=>true,'purchases'=>$purchases]);
|
||||
} elseif ($action === 'cashouts') {
|
||||
echo json_encode(['success'=>true,'cashouts'=>$cashouts]);
|
||||
} elseif ($action === 'broadcasts') {
|
||||
echo json_encode(['success'=>true,'broadcasts'=>$broadcasts]);
|
||||
} else {
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isLoggedIn()) { echo json_encode(['success'=>false,'error'=>'Not authenticated']); exit; }
|
||||
|
||||
$userId = $_SESSION['user_id'];
|
||||
$stmt = db()->prepare("
|
||||
SELECT id, tokens, amount_cents, payment_method, platform_id, game_alias,
|
||||
card_brand, card_last4, status, created_at
|
||||
FROM token_purchases
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 20
|
||||
");
|
||||
$stmt->execute([$userId]);
|
||||
echo json_encode(['success'=>true, 'purchases'=>$stmt->fetchAll()]);
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
ob_start();
|
||||
try { require_once __DIR__ . '/../../includes/auth.php'; } catch(Throwable $e) { ob_end_clean(); header('Content-Type: application/json'); echo json_encode(['success'=>false,'error'=>'Server error']); exit; }
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$action = $_GET['action'] ?? 'list';
|
||||
$isAdmin = isLoggedIn() && !empty($_SESSION['is_admin']);
|
||||
|
||||
switch ($action) {
|
||||
|
||||
// Public: get all enabled payment methods including card status
|
||||
case 'list':
|
||||
// Include card row (is_enabled controls whether card appears at checkout)
|
||||
$rows = db()->query("SELECT method_key, label, handle, instructions, is_enabled FROM payment_settings ORDER BY sort_order ASC, id ASC")->fetchAll();
|
||||
echo json_encode(['success'=>true, 'methods'=>$rows]);
|
||||
break;
|
||||
|
||||
// Admin: get all methods including disabled
|
||||
case 'admin_list':
|
||||
if (!$isAdmin) { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$rows = db()->query("SELECT * FROM payment_settings ORDER BY sort_order ASC, id ASC")->fetchAll();
|
||||
echo json_encode(['success'=>true, 'methods'=>$rows]);
|
||||
break;
|
||||
|
||||
// Admin: update a single method
|
||||
case 'update':
|
||||
if (!$isAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
$label= substr(trim($d['label']??''), 0, 100);
|
||||
$handle = substr(trim($d['handle']??''), 0, 200);
|
||||
$instructions = substr(trim($d['instructions']??''), 0, 500);
|
||||
$enabled = (int)(bool)($d['is_enabled'] ?? 1);
|
||||
$sort = (int)($d['sort_order'] ?? 0);
|
||||
if (!$id) { echo json_encode(['success'=>false,'error'=>'ID required']); exit; }
|
||||
db()->prepare("UPDATE payment_settings SET label=?,handle=?,instructions=?,is_enabled=?,sort_order=? WHERE id=?")
|
||||
->execute([$label,$handle,$instructions,$enabled,$sort,$id]);
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
ob_start();
|
||||
try { require_once __DIR__ . '/../../includes/auth.php'; } catch(Throwable $e) { ob_end_clean(); header('Content-Type: application/json'); echo json_encode(['success'=>false,'error'=>'Server error']); exit; }
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isLoggedIn()) { echo json_encode(['success'=>false,'error'=>'Not authenticated']); exit; }
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
$userId = (int)$_SESSION['user_id'];
|
||||
$isAdmin = !empty($_SESSION['is_admin']);
|
||||
|
||||
switch ($action) {
|
||||
|
||||
case 'list':
|
||||
$uid = $isAdmin ? (int)($_GET['user_id'] ?? $userId) : $userId;
|
||||
$rows = db()->prepare("
|
||||
SELECT pm.*,
|
||||
COALESCE(aps.is_enabled, 0) AS admin_enabled
|
||||
FROM payout_methods pm
|
||||
LEFT JOIN admin_payout_settings aps ON aps.method_key = pm.method_type
|
||||
WHERE pm.user_id = ?
|
||||
ORDER BY pm.is_default DESC, pm.id ASC
|
||||
");
|
||||
$rows->execute([$uid]);
|
||||
echo json_encode(['success'=>true, 'methods'=>$rows->fetchAll()]);
|
||||
break;
|
||||
|
||||
case 'add':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$uid = $isAdmin && isset($d['user_id']) ? (int)$d['user_id'] : $userId;
|
||||
$type = preg_replace('/[^a-z0-9_]/', '', strtolower(trim($d['method_type'] ?? '')));
|
||||
$label = substr(trim($d['label'] ?? ''), 0, 100);
|
||||
$handle= substr(trim($d['account_handle'] ?? ''), 0, 200);
|
||||
$def = (int)(bool)($d['is_default'] ?? 0);
|
||||
if (!$type || !$label || !$handle) { echo json_encode(['success'=>false,'error'=>'All fields required']); exit; }
|
||||
db()->beginTransaction();
|
||||
if ($def) db()->prepare("UPDATE payout_methods SET is_default=0 WHERE user_id=?")->execute([$uid]);
|
||||
// If first method, auto-set as default
|
||||
$count = db()->prepare("SELECT COUNT(*) FROM payout_methods WHERE user_id=?"); $count->execute([$uid]);
|
||||
if ((int)$count->fetchColumn() === 0) $def = 1;
|
||||
db()->prepare("INSERT INTO payout_methods (user_id,method_type,label,account_handle,is_default) VALUES (?,?,?,?,?)")
|
||||
->execute([$uid,$type,$label,$handle,$def]);
|
||||
$newId = db()->lastInsertId();
|
||||
db()->commit();
|
||||
echo json_encode(['success'=>true,'id'=>$newId]);
|
||||
break;
|
||||
|
||||
case 'set_default':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
// Verify ownership
|
||||
$chk = db()->prepare("SELECT user_id FROM payout_methods WHERE id=?"); $chk->execute([$id]);
|
||||
$row = $chk->fetch();
|
||||
if (!$row || ($row['user_id'] != $userId && !$isAdmin)) { echo json_encode(['success'=>false,'error'=>'Not found']); exit; }
|
||||
$uid = $row['user_id'];
|
||||
db()->prepare("UPDATE payout_methods SET is_default=0 WHERE user_id=?")->execute([$uid]);
|
||||
db()->prepare("UPDATE payout_methods SET is_default=1 WHERE id=?")->execute([$id]);
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
$chk = db()->prepare("SELECT user_id,is_default FROM payout_methods WHERE id=?"); $chk->execute([$id]);
|
||||
$row = $chk->fetch();
|
||||
if (!$row || ($row['user_id'] != $userId && !$isAdmin)) { echo json_encode(['success'=>false,'error'=>'Not found']); exit; }
|
||||
db()->prepare("DELETE FROM payout_methods WHERE id=?")->execute([$id]);
|
||||
// If deleted default, set next one as default
|
||||
if ($row['is_default']) {
|
||||
$next = db()->prepare("SELECT id FROM payout_methods WHERE user_id=? LIMIT 1"); $next->execute([$row['user_id']]);
|
||||
if ($n = $next->fetch()) db()->prepare("UPDATE payout_methods SET is_default=1 WHERE id=?")->execute([$n['id']]);
|
||||
}
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
ob_start();
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
require_once __DIR__ . '/../../includes/square.php';
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isLoggedIn() || empty($_SESSION['is_admin'])) {
|
||||
echo json_encode(['success'=>false,'error'=>'Forbidden']); exit;
|
||||
}
|
||||
|
||||
$adminId = (int)$_SESSION['user_id'];
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? 'list_settings';
|
||||
|
||||
// ── GET actions ──────────────────────────────────────────
|
||||
if ($method === 'GET') {
|
||||
if ($action === 'list_settings') {
|
||||
$rows = db()->query("SELECT * FROM admin_payout_settings ORDER BY sort_order ASC, id ASC")->fetchAll();
|
||||
echo json_encode(['success'=>true, 'settings'=>$rows]);
|
||||
exit;
|
||||
}
|
||||
if ($action === 'cashout_detail') {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$stmt = db()->prepare("
|
||||
SELECT cr.*, u.username, u.alias AS user_alias, u.email,
|
||||
pm.method_type AS saved_payout_type, pm.account_handle AS saved_payout_handle, pm.label AS saved_payout_label
|
||||
FROM cashout_requests cr
|
||||
JOIN users u ON cr.user_id = u.id
|
||||
LEFT JOIN payout_methods pm ON pm.user_id = cr.user_id AND pm.is_default = 1
|
||||
WHERE cr.id = ?
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
$row = $stmt->fetch();
|
||||
echo json_encode(['success'=>true, 'cashout'=>$row]);
|
||||
exit;
|
||||
}
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']); exit;
|
||||
}
|
||||
|
||||
if ($method !== 'POST') { echo json_encode(['success'=>false,'error'=>'Method not allowed']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
// ── Update payout settings ────────────────────────────────
|
||||
if ($action === 'update_setting') {
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
$handle = substr(trim($d['handle']??''), 0, 200);
|
||||
$instr = substr(trim($d['instructions']??''), 0, 500);
|
||||
$enabled = (int)(bool)($d['is_enabled']??1);
|
||||
$label = substr(trim($d['label']??''), 0, 100);
|
||||
$sort = (int)($d['sort_order']??0);
|
||||
db()->prepare("UPDATE admin_payout_settings SET label=?,handle=?,instructions=?,is_enabled=?,sort_order=? WHERE id=?")
|
||||
->execute([$label,$handle,$instr,$enabled,$sort,$id]);
|
||||
echo json_encode(['success'=>true]); exit;
|
||||
}
|
||||
|
||||
// ── Process cashout payout ────────────────────────────────
|
||||
if ($action === 'process_payout') {
|
||||
$cashoutId = (int)($d['cashout_id'] ?? 0);
|
||||
$payoutKey = trim($d['payout_method_key'] ?? '');
|
||||
$payoutType = trim($d['payout_type'] ?? 'manual'); // 'manual' or 'square_gift_card'
|
||||
$note = substr(trim($d['note'] ?? ''), 0, 500);
|
||||
|
||||
// Load cashout
|
||||
$stmt = db()->prepare("SELECT cr.*, u.username, u.alias, u.email FROM cashout_requests cr JOIN users u ON cr.user_id=u.id WHERE cr.id=? AND cr.status IN ('pending','locked')");
|
||||
$stmt->execute([$cashoutId]);
|
||||
$cashout = $stmt->fetch();
|
||||
if (!$cashout) { echo json_encode(['success'=>false,'error'=>'Cashout not found or already processed']); exit; }
|
||||
|
||||
// Amount in cents (1 token = $1)
|
||||
$amountCents = (int)round($cashout['tokens'] * 100);
|
||||
|
||||
// Load payout setting
|
||||
$pset = db()->prepare("SELECT * FROM admin_payout_settings WHERE method_key=? AND is_enabled=1");
|
||||
$pset->execute([$payoutKey]);
|
||||
$setting = $pset->fetch();
|
||||
|
||||
$squareTxnId = null;
|
||||
$giftCardGan = null;
|
||||
$giftCardBal = null;
|
||||
$txnStatus = 'completed';
|
||||
|
||||
if ($payoutType === 'square_gift_card') {
|
||||
// ── Square Gift Card Flow ─────────────────────────
|
||||
try {
|
||||
// 1. Create gift card
|
||||
$gcRes = SquareClient::post('/v2/gift-cards', [
|
||||
'idempotency_key' => 'gc-create-' . $cashoutId . '-' . time(),
|
||||
'location_id' => SQUARE_LOCATION_ID,
|
||||
'gift_card' => ['type' => 'DIGITAL'],
|
||||
]);
|
||||
|
||||
if (empty($gcRes['gift_card']['id'])) {
|
||||
throw new Exception('Failed to create gift card: ' . json_encode($gcRes));
|
||||
}
|
||||
|
||||
$gcId = $gcRes['gift_card']['id'];
|
||||
$gcGan = $gcRes['gift_card']['gan'] ?? '';
|
||||
|
||||
// 2. Activate gift card with balance
|
||||
$actRes = SquareClient::post('/v2/gift-card-activities', [
|
||||
'idempotency_key' => 'gc-activate-' . $cashoutId . '-' . time(),
|
||||
'gift_card_activity' => [
|
||||
'type' => 'ACTIVATE',
|
||||
'location_id' => SQUARE_LOCATION_ID,
|
||||
'gift_card_id' => $gcId,
|
||||
'activate_activity_details' => [
|
||||
'amount_money' => [
|
||||
'amount' => $amountCents,
|
||||
'currency' => 'USD',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
if (empty($actRes['gift_card_activity']['id'])) {
|
||||
throw new Exception('Failed to activate gift card: ' . json_encode($actRes));
|
||||
}
|
||||
|
||||
$giftCardGan = $gcGan;
|
||||
$giftCardBal = $amountCents;
|
||||
$squareTxnId = $actRes['gift_card_activity']['id'];
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success'=>false,'error'=>'Square error: ' . $e->getMessage()]); exit;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Mark cashout as sent ──────────────────────────────
|
||||
db()->beginTransaction();
|
||||
try {
|
||||
db()->prepare("UPDATE cashout_requests SET status='sent', admin_note=?, resolved_at=NOW() WHERE id=?")
|
||||
->execute([$note, $cashoutId]);
|
||||
|
||||
db()->prepare("INSERT INTO cashout_transactions (cashout_id,admin_id,payout_method,payout_type,amount_cents,square_txn_id,gift_card_gan,gift_card_balance,note,status)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,'completed')")
|
||||
->execute([$cashoutId, $adminId, $payoutKey, $payoutType, $amountCents, $squareTxnId, $giftCardGan, $giftCardBal, $note]);
|
||||
|
||||
db()->commit();
|
||||
} catch (Exception $e) {
|
||||
db()->rollBack();
|
||||
echo json_encode(['success'=>false,'error'=>'DB error: '.$e->getMessage()]); exit;
|
||||
}
|
||||
|
||||
$response = ['success'=>true, 'status'=>'sent', 'amount_cents'=>$amountCents];
|
||||
if ($giftCardGan) {
|
||||
$response['gift_card_gan'] = $giftCardGan;
|
||||
$response['gift_card_balance'] = $giftCardBal;
|
||||
}
|
||||
echo json_encode($response); exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
ob_start();
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
if (!isLoggedIn()) { echo json_encode(['success'=>false,'error'=>'Not authenticated']); exit; }
|
||||
$userId = (int)$_SESSION['user_id'];
|
||||
$isAdmin = !empty($_SESSION['is_admin']);
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? 'list';
|
||||
|
||||
if ($method === 'GET') {
|
||||
if ($action === 'list') {
|
||||
$uid = $isAdmin ? (int)($_GET['user_id'] ?? $userId) : $userId;
|
||||
$stmt = db()->prepare("SELECT pa.*, COALESCE(p.name, pa.platform_slug) AS platform_name, p.color
|
||||
FROM platform_accounts pa LEFT JOIN platforms p ON pa.platform_slug=p.slug
|
||||
WHERE pa.user_id=? ORDER BY pa.requested_at DESC");
|
||||
$stmt->execute([$uid]);
|
||||
$rows = $stmt->fetchAll();
|
||||
foreach ($rows as &$row) {
|
||||
if (!$isAdmin && $row['status'] !== 'approved') $row['platform_password'] = null;
|
||||
}
|
||||
echo json_encode(['success'=>true,'accounts'=>$rows]);
|
||||
} elseif ($action === 'check_onboarding') {
|
||||
$cnt = db()->prepare("SELECT COUNT(*) FROM platform_accounts WHERE user_id=?");
|
||||
$cnt->execute([$userId]);
|
||||
$hasAny = (int)$cnt->fetchColumn() > 0;
|
||||
// Check flag — graceful fallback if column doesn't exist
|
||||
$done = false;
|
||||
try {
|
||||
$s = db()->prepare("SELECT platform_onboarding_done FROM users WHERE id=?");
|
||||
$s->execute([$userId]);
|
||||
$r = $s->fetch(); $done = !empty($r['platform_onboarding_done']);
|
||||
} catch(Exception $e){}
|
||||
echo json_encode(['success'=>true,'needs_onboarding'=>(!$done && !$hasAny),'has_accounts'=>$hasAny]);
|
||||
} else {
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($method !== 'POST') { echo json_encode(['success'=>false,'error'=>'Method not allowed']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if ($action === 'request') {
|
||||
$slug = preg_replace('/[^a-z0-9_]/','',strtolower(trim($d['platform_slug']??'')));
|
||||
if (!$slug) { echo json_encode(['success'=>false,'error'=>'Platform required']); exit; }
|
||||
try {
|
||||
db()->prepare("INSERT INTO platform_accounts (user_id,platform_slug) VALUES (?,?)")->execute([$userId,$slug]);
|
||||
try { db()->prepare("UPDATE users SET platform_onboarding_done=1 WHERE id=?")->execute([$userId]); } catch(Exception $e){}
|
||||
echo json_encode(['success'=>true]);
|
||||
} catch(Exception $e) { echo json_encode(['success'=>false,'error'=>'Already requested for this platform']); }
|
||||
exit;
|
||||
}
|
||||
if ($action === 'dismiss_onboarding') {
|
||||
try { db()->prepare("UPDATE users SET platform_onboarding_done=1 WHERE id=?")->execute([$userId]); } catch(Exception $e){}
|
||||
echo json_encode(['success'=>true]);
|
||||
exit;
|
||||
}
|
||||
if (!$isAdmin) { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
if ($action === 'resolve') {
|
||||
$id=$d['id']??0; $status=$d['status']??'';
|
||||
$uname=substr(trim($d['platform_username']??''),0,100);
|
||||
$pass=substr(trim($d['platform_password']??''),0,200);
|
||||
$note=substr(trim($d['admin_note']??''),0,300);
|
||||
if (!in_array($status,['approved','denied','deleted'])){echo json_encode(['success'=>false,'error'=>'Invalid status']);exit;}
|
||||
$chk=db()->prepare("SELECT user_id,platform_slug FROM platform_accounts WHERE id=?");$chk->execute([$id]);$row=$chk->fetch();
|
||||
if (!$row){echo json_encode(['success'=>false,'error'=>'Not found']);exit;}
|
||||
db()->prepare("UPDATE platform_accounts SET status=?,platform_username=?,platform_password=?,admin_note=?,resolved_at=NOW(),admin_id=? WHERE id=?")
|
||||
->execute([$status,$uname,$pass,$note,(int)$_SESSION['user_id'],$id]);
|
||||
if ($status==='approved'&&$uname) {
|
||||
db()->prepare("INSERT INTO game_aliases (user_id,platform_slug,alias) VALUES (?,?,?) ON DUPLICATE KEY UPDATE alias=VALUES(alias)")
|
||||
->execute([$row['user_id'],$row['platform_slug'],$uname]);
|
||||
}
|
||||
echo json_encode(['success'=>true]);exit;
|
||||
}
|
||||
if ($action === 'update_credentials') {
|
||||
$id=$d['id']??0;
|
||||
$uname=substr(trim($d['platform_username']??''),0,100);
|
||||
$pass=substr(trim($d['platform_password']??''),0,200);
|
||||
$note=substr(trim($d['admin_note']??''),0,300);
|
||||
$chk=db()->prepare("SELECT user_id,platform_slug FROM platform_accounts WHERE id=?");$chk->execute([$id]);$row=$chk->fetch();
|
||||
if (!$row){echo json_encode(['success'=>false,'error'=>'Not found']);exit;}
|
||||
db()->prepare("UPDATE platform_accounts SET platform_username=?,platform_password=?,admin_note=? WHERE id=?")
|
||||
->execute([$uname,$pass,$note,$id]);
|
||||
if ($uname) {
|
||||
db()->prepare("INSERT INTO game_aliases (user_id,platform_slug,alias) VALUES (?,?,?) ON DUPLICATE KEY UPDATE alias=VALUES(alias)")
|
||||
->execute([$row['user_id'],$row['platform_slug'],$uname]);
|
||||
}
|
||||
echo json_encode(['success'=>true]);exit;
|
||||
}
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
ob_start();
|
||||
try { require_once __DIR__ . '/../../includes/auth.php'; } catch(Throwable $e) { ob_end_clean(); header('Content-Type: application/json'); echo json_encode(['success'=>false,'error'=>'Server error']); exit; }
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$action = $_GET['action'] ?? 'list';
|
||||
$isAdmin = isLoggedIn() && !empty($_SESSION['is_admin']);
|
||||
$isMasterAdmin = $isAdmin && (int)($_SESSION['user_id'] ?? 0) === MASTER_ADMIN_ID;
|
||||
|
||||
switch ($action) {
|
||||
|
||||
// ── Public: active platforms for player app ───────────
|
||||
case 'list':
|
||||
$stmt = db()->query("SELECT slug,name,player_url,url_alias_param,color,icon_path FROM platforms WHERE is_active=1 AND is_deleted=0 ORDER BY sort_order ASC, id ASC");
|
||||
$rows = $stmt->fetchAll();
|
||||
$out = array_map(fn($r) => [
|
||||
'id' => $r['slug'],
|
||||
'name' => $r['name'],
|
||||
'url' => $r['player_url'],
|
||||
'alias_param' => $r['url_alias_param'] ?? '',
|
||||
'color' => $r['color'],
|
||||
], $rows);
|
||||
echo json_encode(['success'=>true, 'platforms'=>$out]);
|
||||
break;
|
||||
|
||||
// ── Admin: full list including agent fields and inactive ─
|
||||
case 'admin_list':
|
||||
if (!$isAdmin) { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$rows = db()->query("SELECT * FROM platforms WHERE is_deleted=0 ORDER BY sort_order ASC, id ASC")->fetchAll();
|
||||
echo json_encode(['success'=>true, 'platforms'=>$rows]);
|
||||
break;
|
||||
|
||||
// ── Admin: create platform ────────────────────────────
|
||||
case 'create':
|
||||
if (!$isAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$slug = preg_replace('/[^a-z0-9_]/', '', strtolower(trim($d['slug'] ?? '')));
|
||||
$name = substr(trim($d['name'] ?? ''), 0, 100);
|
||||
$player_url = substr(trim($d['player_url'] ?? ''), 0, 500);
|
||||
$url_alias_param = preg_replace('/[^a-zA-Z0-9_\-]/', '', trim($d['url_alias_param'] ?? ''));
|
||||
$agent_link = substr(trim($d['agent_link'] ?? ''), 0, 500);
|
||||
$agent_login = substr(trim($d['agent_login'] ?? ''), 0, 200);
|
||||
$agent_password = substr(trim($d['agent_password'] ?? ''), 0, 200);
|
||||
$games_link = substr(trim($d['games_link'] ?? ''), 0, 500);
|
||||
$agent_guide = trim($d['agent_guide'] ?? '');
|
||||
$sub_agent_login = substr(trim($d['sub_agent_login'] ?? ''), 0, 200);
|
||||
$sub_agent_password = substr(trim($d['sub_agent_password'] ?? ''), 0, 200);
|
||||
$cashier_login = substr(trim($d['cashier_login'] ?? ''), 0, 200);
|
||||
$cashier_password = substr(trim($d['cashier_password'] ?? ''), 0, 200);
|
||||
$color = preg_match('/^#[0-9a-fA-F]{3,8}$/', $d['color'] ?? '') ? $d['color'] : '#f0c040';
|
||||
$sort_order = (int)($d['sort_order'] ?? 99);
|
||||
$is_active = isset($d['is_active']) ? (int)(bool)$d['is_active'] : 1;
|
||||
if (!$slug || !$name || !$player_url) { echo json_encode(['success'=>false,'error'=>'Slug, name, and player URL are required']); exit; }
|
||||
try {
|
||||
$stmt = db()->prepare("INSERT INTO platforms (slug,name,player_url,url_alias_param,agent_link,agent_login,agent_password,games_link,agent_guide,sub_agent_login,sub_agent_password,cashier_login,cashier_password,color,sort_order,is_active) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
|
||||
$stmt->execute([$slug,$name,$player_url,$url_alias_param,$agent_link,$agent_login,$agent_password,$games_link,$agent_guide,$sub_agent_login,$sub_agent_password,$cashier_login,$cashier_password,$color,$sort_order,$is_active]);
|
||||
echo json_encode(['success'=>true,'id'=>db()->lastInsertId()]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success'=>false,'error'=>'Slug already exists or DB error']);
|
||||
}
|
||||
break;
|
||||
|
||||
// ── Admin: update platform ────────────────────────────
|
||||
case 'update':
|
||||
if (!$isAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
$name = substr(trim($d['name'] ?? ''), 0, 100);
|
||||
$player_url = substr(trim($d['player_url'] ?? ''), 0, 500);
|
||||
$url_alias_param = preg_replace('/[^a-zA-Z0-9_\-]/', '', trim($d['url_alias_param'] ?? ''));
|
||||
$agent_link = substr(trim($d['agent_link'] ?? ''), 0, 500);
|
||||
$agent_login = substr(trim($d['agent_login'] ?? ''), 0, 200);
|
||||
$agent_password = substr(trim($d['agent_password'] ?? ''), 0, 200);
|
||||
$games_link = substr(trim($d['games_link'] ?? ''), 0, 500);
|
||||
$agent_guide = trim($d['agent_guide'] ?? '');
|
||||
$sub_agent_login = substr(trim($d['sub_agent_login'] ?? ''), 0, 200);
|
||||
$sub_agent_password = substr(trim($d['sub_agent_password'] ?? ''), 0, 200);
|
||||
$cashier_login = substr(trim($d['cashier_login'] ?? ''), 0, 200);
|
||||
$cashier_password = substr(trim($d['cashier_password'] ?? ''), 0, 200);
|
||||
$color = preg_match('/^#[0-9a-fA-F]{3,8}$/', $d['color'] ?? '') ? $d['color'] : '#f0c040';
|
||||
$sort_order = (int)($d['sort_order'] ?? 99);
|
||||
$is_active = (int)(bool)($d['is_active'] ?? 1);
|
||||
if (!$id || !$name || !$player_url) { echo json_encode(['success'=>false,'error'=>'ID, name, and player URL required']); exit; }
|
||||
if ($isMasterAdmin) {
|
||||
// Master admin: update all fields including agent info
|
||||
db()->prepare("UPDATE platforms SET name=?,player_url=?,url_alias_param=?,agent_link=?,agent_login=?,agent_password=?,games_link=?,agent_guide=?,sub_agent_login=?,sub_agent_password=?,cashier_login=?,cashier_password=?,color=?,sort_order=?,is_active=? WHERE id=?")
|
||||
->execute([$name,$player_url,$url_alias_param,$agent_link,$agent_login,$agent_password,$games_link,$agent_guide,$sub_agent_login,$sub_agent_password,$cashier_login,$cashier_password,$color,$sort_order,$is_active,$id]);
|
||||
} else {
|
||||
// Regular admin: update non-sensitive fields including alias param
|
||||
db()->prepare("UPDATE platforms SET name=?,player_url=?,url_alias_param=?,color=?,sort_order=?,is_active=? WHERE id=?")
|
||||
->execute([$name,$player_url,$url_alias_param,$color,$sort_order,$is_active,$id]);
|
||||
}
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
// ── Admin: delete platform ────────────────────────────
|
||||
case 'delete':
|
||||
if (!$isAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
if (!$id) { echo json_encode(['success'=>false,'error'=>'ID required']); exit; }
|
||||
db()->prepare("DELETE FROM platforms WHERE id=?")->execute([$id]);
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
// ── Admin: reorder platforms ──────────────────────────
|
||||
case 'reorder':
|
||||
if (!$isAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$order = $d['order'] ?? []; // array of IDs in desired order
|
||||
$stmt = db()->prepare("UPDATE platforms SET sort_order=? WHERE id=?");
|
||||
foreach ($order as $i => $pid) { $stmt->execute([$i, (int)$pid]); }
|
||||
echo json_encode(['success'=>true]);
|
||||
break;
|
||||
|
||||
// ── Admin: list credits for a platform ───────────────
|
||||
case 'credits_list':
|
||||
if (!$isAdmin) { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$pid = (int)($_GET['platform_id'] ?? 0);
|
||||
if (!$pid) { echo json_encode(['success'=>false,'error'=>'platform_id required']); exit; }
|
||||
$rows = db()->prepare("SELECT * FROM platform_credits WHERE platform_id=? ORDER BY credit_date DESC, id DESC");
|
||||
$rows->execute([$pid]);
|
||||
$credits = $rows->fetchAll();
|
||||
$total = db()->prepare("SELECT COALESCE(SUM(CASE WHEN type='debit' THEN -credits_purchased ELSE credits_purchased END),0) FROM platform_credits WHERE platform_id=?");
|
||||
$total->execute([$pid]);
|
||||
echo json_encode(['success'=>true,'credits'=>$credits,'total'=>(float)$total->fetchColumn()]);
|
||||
break;
|
||||
|
||||
// ── Admin: add credit entry ───────────────────────────
|
||||
case 'credits_create':
|
||||
if (!$isMasterAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$pid = (int)($d['platform_id'] ?? 0);
|
||||
$credits = (float)($d['credits_purchased'] ?? 0);
|
||||
$date = $d['credit_date'] ?? date('Y-m-d');
|
||||
$method = substr(trim($d['payment_method'] ?? ''), 0, 100);
|
||||
$notes = trim($d['notes'] ?? '');
|
||||
if (!$pid || $credits <= 0 || !$date) { echo json_encode(['success'=>false,'error'=>'platform_id, credits_purchased, and credit_date are required']); exit; }
|
||||
$stmt = db()->prepare("INSERT INTO platform_credits (platform_id,credits_purchased,credit_date,payment_method,notes) VALUES (?,?,?,?,?)");
|
||||
$stmt->execute([$pid,$credits,$date,$method,$notes]);
|
||||
$newId = db()->lastInsertId();
|
||||
$total = db()->prepare("SELECT COALESCE(SUM(credits_purchased),0) FROM platform_credits WHERE platform_id=?");
|
||||
$total->execute([$pid]);
|
||||
echo json_encode(['success'=>true,'id'=>$newId,'total'=>(float)$total->fetchColumn()]);
|
||||
break;
|
||||
|
||||
// ── Admin: update credit entry ────────────────────────
|
||||
case 'credits_update':
|
||||
if (!$isMasterAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
$credits = (float)($d['credits_purchased'] ?? 0);
|
||||
$date = $d['credit_date'] ?? date('Y-m-d');
|
||||
$method = substr(trim($d['payment_method'] ?? ''), 0, 100);
|
||||
$notes = trim($d['notes'] ?? '');
|
||||
if (!$id || $credits <= 0 || !$date) { echo json_encode(['success'=>false,'error'=>'id, credits_purchased, and credit_date are required']); exit; }
|
||||
db()->prepare("UPDATE platform_credits SET credits_purchased=?,credit_date=?,payment_method=?,notes=? WHERE id=?")
|
||||
->execute([$credits,$date,$method,$notes,$id]);
|
||||
$row = db()->prepare("SELECT platform_id FROM platform_credits WHERE id=?");
|
||||
$row->execute([$id]);
|
||||
$pid = (int)($row->fetchColumn() ?: 0);
|
||||
$total = db()->prepare("SELECT COALESCE(SUM(credits_purchased),0) FROM platform_credits WHERE platform_id=?");
|
||||
$total->execute([$pid]);
|
||||
echo json_encode(['success'=>true,'total'=>(float)$total->fetchColumn()]);
|
||||
break;
|
||||
|
||||
// ── Admin: delete credit entry ────────────────────────
|
||||
case 'credits_delete':
|
||||
if (!$isMasterAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
if (!$id) { echo json_encode(['success'=>false,'error'=>'ID required']); exit; }
|
||||
$row = db()->prepare("SELECT platform_id FROM platform_credits WHERE id=?");
|
||||
$row->execute([$id]);
|
||||
$pid = (int)($row->fetchColumn() ?: 0);
|
||||
db()->prepare("DELETE FROM platform_credits WHERE id=?")->execute([$id]);
|
||||
$total = db()->prepare("SELECT COALESCE(SUM(credits_purchased),0) FROM platform_credits WHERE platform_id=?");
|
||||
$total->execute([$pid]);
|
||||
echo json_encode(['success'=>true,'total'=>(float)$total->fetchColumn()]);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
require_once __DIR__ . '/../../includes/square.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isLoggedIn()) { echo json_encode(['success'=>false,'error'=>'Not authenticated']); exit; }
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Method not allowed']); exit; }
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$sourceId = trim($data['source_id'] ?? '');
|
||||
$tokens = (int)($data['tokens'] ?? 0);
|
||||
$priceCents = (int)($data['price_cents'] ?? 0);
|
||||
$method = trim($data['method'] ?? 'card');
|
||||
$platformId = trim($data['platform_id'] ?? '');
|
||||
$gameAlias = trim($data['game_alias'] ?? '');
|
||||
$playerName = trim($data['player_name'] ?? '');
|
||||
$isCustom = (bool)($data['is_custom'] ?? false);
|
||||
$billing = $data['billing'] ?? [];
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
// ─── Validate ─────────────────────────────────────────────
|
||||
if ($tokens < 1 || $priceCents < 100) {
|
||||
echo json_encode(['success'=>false,'error'=>'Minimum purchase is $1 (1 token).']); exit;
|
||||
}
|
||||
|
||||
// Validate preset packages (custom bypasses preset check)
|
||||
if (!$isCustom) {
|
||||
$packages = json_decode(TOKEN_PACKAGES, true);
|
||||
$validPkg = false;
|
||||
foreach ($packages as $pkg) {
|
||||
if ($pkg['tokens'] === $tokens && ($pkg['price'] * 100) === $priceCents) { $validPkg = true; break; }
|
||||
}
|
||||
if (!$validPkg) {
|
||||
echo json_encode(['success'=>false,'error'=>'Invalid token package.']); exit;
|
||||
}
|
||||
} else {
|
||||
// Custom: tokens must equal dollars (1:1 ratio), cap at $500
|
||||
if ($tokens !== ($priceCents / 100) || $tokens > 500) {
|
||||
echo json_encode(['success'=>false,'error'=>'Invalid custom amount.']); exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitize billing fields
|
||||
$billingFirst = substr(trim($billing['first_name'] ?? ''), 0, 80);
|
||||
$billingLast = substr(trim($billing['last_name'] ?? ''), 0, 80);
|
||||
$billingAddress = substr(trim($billing['address'] ?? ''), 0, 200);
|
||||
$billingCity = substr(trim($billing['city'] ?? ''), 0, 80);
|
||||
$billingState = strtoupper(substr(trim($billing['state'] ?? ''), 0, 2));
|
||||
$billingZip = substr(trim($billing['zip'] ?? ''), 0, 10);
|
||||
$billingEmail = substr(strtolower(trim($billing['email'] ?? '')), 0, 150);
|
||||
$cardholderName = trim("$billingFirst $billingLast");
|
||||
|
||||
$isManual = in_array($method, ['venmo','chime','cashapp','zelle']);
|
||||
|
||||
// ─── Manual payment ────────────────────────────────────────
|
||||
if ($isManual) {
|
||||
$stmt = db()->prepare("
|
||||
INSERT INTO token_purchases
|
||||
(user_id, tokens, amount_cents, payment_method, platform_id, game_alias,
|
||||
player_name, billing_name, billing_email, is_custom, status)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,'pending')
|
||||
");
|
||||
$stmt->execute([
|
||||
$userId, $tokens, $priceCents, $method, $platformId, $gameAlias,
|
||||
$playerName, $cardholderName, $billingEmail, $isCustom ? 1 : 0
|
||||
]);
|
||||
$pid = db()->lastInsertId();
|
||||
logActivity('manual_payment_pending', $userId, null, 'purchase', 0, "Manual payment pending: {$method} \$" . number_format($priceCents / 100, 2));
|
||||
echo json_encode(['success'=>true,'manual'=>true,'purchase_id'=>$pid,
|
||||
'message'=>"Request #{$pid} submitted! Tokens credited after payment verification."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ─── Card payment via Square ───────────────────────────────
|
||||
if (empty($sourceId)) { echo json_encode(['success'=>false,'error'=>'Card payment token required.']); exit; }
|
||||
|
||||
$square = new SquarePayment();
|
||||
$note = "TomGames {$tokens}tok | {$platformId} | {$gameAlias} | user#{$userId}" . ($isCustom ? ' [CUSTOM]' : '');
|
||||
|
||||
// Build buyer info for Square
|
||||
$buyerInfo = [];
|
||||
if ($cardholderName) $buyerInfo['buyer_email_address'] = $billingEmail ?: null;
|
||||
$billingAddr = [];
|
||||
if ($billingAddress) $billingAddr['address_line_1'] = $billingAddress;
|
||||
if ($billingCity) $billingAddr['locality'] = $billingCity;
|
||||
if ($billingState) $billingAddr['administrative_district_level_1'] = $billingState;
|
||||
if ($billingZip) $billingAddr['postal_code'] = $billingZip;
|
||||
$billingAddr['country'] = 'US';
|
||||
|
||||
$result = $square->charge($sourceId, $priceCents, $note, $cardholderName, $billingAddr, $billingEmail);
|
||||
|
||||
if (!$result['success']) {
|
||||
// Log failed attempt
|
||||
db()->prepare("INSERT INTO token_purchases (user_id,tokens,amount_cents,payment_method,platform_id,game_alias,player_name,billing_name,billing_email,is_custom,status,failure_reason) VALUES (?,?,?,'card',?,?,?,?,?,?,'failed',?)")
|
||||
->execute([$userId,$tokens,$priceCents,$platformId,$gameAlias,$playerName,$cardholderName,$billingEmail,$isCustom?1:0,$result['error']]);
|
||||
echo json_encode(['success'=>false,'error'=>$result['error']]); exit;
|
||||
}
|
||||
|
||||
// ─── Credit tokens ─────────────────────────────────────────
|
||||
db()->beginTransaction();
|
||||
try {
|
||||
db()->prepare("UPDATE users SET tokens=tokens+? WHERE id=?")->execute([$tokens,$userId]);
|
||||
$cardBrand = $result['card_brand'] ?? null;
|
||||
$cardLast4 = $result['last_4'] ?? null;
|
||||
$receiptUrl= $result['receipt_url'] ?? null;
|
||||
|
||||
db()->prepare("
|
||||
INSERT INTO token_purchases
|
||||
(user_id, tokens, amount_cents, payment_method, square_payment_id,
|
||||
platform_id, game_alias, player_name, billing_name, billing_address,
|
||||
billing_city, billing_state, billing_zip, billing_email,
|
||||
is_custom, card_brand, card_last4, receipt_url, status)
|
||||
VALUES (?,?,?,'card',?,?,?,?,?,?,?,?,?,?,?,?,?,?,'completed')
|
||||
")->execute([
|
||||
$userId, $tokens, $priceCents, $result['payment_id'],
|
||||
$platformId, $gameAlias, $playerName,
|
||||
$cardholderName, $billingAddress, $billingCity, $billingState, $billingZip, $billingEmail,
|
||||
$isCustom ? 1 : 0, $cardBrand, $cardLast4, $receiptUrl
|
||||
]);
|
||||
|
||||
db()->commit();
|
||||
} catch (Exception $e) {
|
||||
db()->rollBack();
|
||||
echo json_encode(['success'=>false,'error'=>'Token credit failed. Payment ID: '.$result['payment_id']]); exit;
|
||||
}
|
||||
|
||||
// ─── Update saved billing (separate try — must NOT roll back token credit) ──
|
||||
try {
|
||||
db()->prepare("
|
||||
INSERT INTO saved_billing (user_id,first_name,last_name,email,address,city,state,zip,card_brand,card_last4)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
card_brand=VALUES(card_brand), card_last4=VALUES(card_last4),
|
||||
first_name=COALESCE(NULLIF(VALUES(first_name),''),first_name),
|
||||
last_name=COALESCE(NULLIF(VALUES(last_name),''),last_name),
|
||||
email=COALESCE(NULLIF(VALUES(email),''),email),
|
||||
address=COALESCE(NULLIF(VALUES(address),''),address),
|
||||
city=COALESCE(NULLIF(VALUES(city),''),city),
|
||||
state=COALESCE(NULLIF(VALUES(state),''),state),
|
||||
zip=COALESCE(NULLIF(VALUES(zip),''),zip)
|
||||
")->execute([
|
||||
$userId, $billingFirst, $billingLast, $billingEmail,
|
||||
$billingAddress, $billingCity, $billingState, $billingZip,
|
||||
$cardBrand, $cardLast4
|
||||
]);
|
||||
} catch (Exception $e) { /* non-critical — tokens already credited */ }
|
||||
|
||||
$bal = db()->prepare("SELECT tokens FROM users WHERE id=?");
|
||||
$bal->execute([$userId]);
|
||||
$newBal = (float)$bal->fetchColumn();
|
||||
logActivity('token_purchase', $userId, null, 'purchase', (int)db()->lastInsertId(),
|
||||
"Bought {$tokens} tokens via {$method} for \$" . number_format($priceCents / 100, 2));
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'manual' => false,
|
||||
'tokens_added' => (int)$tokens,
|
||||
'new_balance' => $newBal,
|
||||
'payment_id' => $result['payment_id'],
|
||||
'card_brand' => $cardBrand,
|
||||
'card_last4' => $cardLast4,
|
||||
'receipt_url' => $receiptUrl,
|
||||
]);
|
||||
@@ -0,0 +1,362 @@
|
||||
<?php
|
||||
ob_start();
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
ob_end_clean();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isLoggedIn()) { echo json_encode(['success'=>false,'error'=>'Not authenticated']); exit; }
|
||||
|
||||
$userId = (int)$_SESSION['user_id'];
|
||||
$isAdmin = !empty($_SESSION['is_admin']);
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? 'status';
|
||||
|
||||
// ── GET actions ───────────────────────────────────────────
|
||||
if ($method === 'GET') {
|
||||
|
||||
if ($action === 'status') {
|
||||
// Player's referral dashboard data
|
||||
$user = db()->prepare("SELECT referral_code, referred_by FROM users WHERE id=?");
|
||||
$user->execute([$userId]);
|
||||
$u = $user->fetch();
|
||||
|
||||
// Auto-generate code if missing
|
||||
if (empty($u['referral_code'])) {
|
||||
$code = strtoupper(substr(md5($userId.uniqid()),0,8));
|
||||
db()->prepare("UPDATE users SET referral_code=? WHERE id=?")->execute([$code, $userId]);
|
||||
$u['referral_code'] = $code;
|
||||
}
|
||||
|
||||
// Count verified referrals
|
||||
$countStmt = db()->prepare("SELECT COUNT(*) FROM referrals WHERE referrer_id=? AND status='verified'");
|
||||
$countStmt->execute([$userId]);
|
||||
$verified = (int)$countStmt->fetchColumn();
|
||||
|
||||
// All referrals
|
||||
$refs = db()->prepare("
|
||||
SELECT r.*, u.username, u.alias, u.email_verified, u.created_at AS joined_at
|
||||
FROM referrals r
|
||||
JOIN users u ON r.referred_id = u.id
|
||||
WHERE r.referrer_id = ?
|
||||
ORDER BY r.created_at DESC
|
||||
");
|
||||
$refs->execute([$userId]);
|
||||
|
||||
// Current tier
|
||||
$tier = db()->query("
|
||||
SELECT * FROM referral_tiers
|
||||
WHERE is_active=1 AND min_referrals <= $verified
|
||||
ORDER BY min_referrals DESC LIMIT 1
|
||||
")->fetch();
|
||||
|
||||
// Next tier
|
||||
$nextTier = db()->query("
|
||||
SELECT * FROM referral_tiers
|
||||
WHERE is_active=1 AND min_referrals > $verified
|
||||
ORDER BY min_referrals ASC LIMIT 1
|
||||
")->fetch();
|
||||
|
||||
// Total tokens earned from referrals
|
||||
$earned = db()->prepare("SELECT COALESCE(SUM(tokens_awarded),0) FROM referrals WHERE referrer_id=? AND status='verified'");
|
||||
$earned->execute([$userId]);
|
||||
|
||||
// Social shares
|
||||
$shares = db()->prepare("SELECT * FROM referral_social_shares WHERE user_id=? ORDER BY created_at DESC");
|
||||
$shares->execute([$userId]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'referral_code' => $u['referral_code'] ?? '',
|
||||
'referral_url' => (defined('SITE_URL')?SITE_URL:'https://tomtomgames.com') . '/?ref=' . ($u['referral_code'] ?? ''),
|
||||
'verified_count' => $verified,
|
||||
'total_earned' => (float)$earned->fetchColumn(),
|
||||
'current_tier' => $tier,
|
||||
'next_tier' => $nextTier,
|
||||
'referrals' => $refs->fetchAll(),
|
||||
'social_shares' => $shares->fetchAll(),
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'tiers') {
|
||||
$rows = db()->query("SELECT * FROM referral_tiers WHERE is_active=1 ORDER BY min_referrals ASC")->fetchAll();
|
||||
echo json_encode(['success'=>true,'tiers'=>$rows]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'all_tiers' && $isAdmin) {
|
||||
$rows = db()->query("SELECT * FROM referral_tiers ORDER BY sort_order ASC, min_referrals ASC")->fetchAll();
|
||||
echo json_encode(['success'=>true,'tiers'=>$rows]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'admin_list' && $isAdmin) {
|
||||
$status = $_GET['status'] ?? 'pending';
|
||||
$stmt = db()->prepare("
|
||||
SELECT r.*,
|
||||
ru.username AS referrer_name, ru.alias AS referrer_alias,
|
||||
rd.username AS referred_name, rd.alias AS referred_alias, rd.email_verified,
|
||||
t.name AS tier_name
|
||||
FROM referrals r
|
||||
JOIN users ru ON r.referrer_id = ru.id
|
||||
JOIN users rd ON r.referred_id = rd.id
|
||||
LEFT JOIN referral_tiers t ON r.tier_id = t.id
|
||||
WHERE r.status = ?
|
||||
ORDER BY r.created_at DESC
|
||||
LIMIT 100
|
||||
");
|
||||
$stmt->execute([$status]);
|
||||
echo json_encode(['success'=>true,'referrals'=>$stmt->fetchAll()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'admin_shares' && $isAdmin) {
|
||||
$status = $_GET['status'] ?? 'pending';
|
||||
$stmt = db()->prepare("
|
||||
SELECT rs.*, u.username, u.alias
|
||||
FROM referral_social_shares rs
|
||||
JOIN users u ON rs.user_id = u.id
|
||||
WHERE rs.status = ?
|
||||
ORDER BY rs.created_at DESC
|
||||
");
|
||||
$stmt->execute([$status]);
|
||||
echo json_encode(['success'=>true,'shares'=>$stmt->fetchAll()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']); exit;
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────
|
||||
|
||||
function inferPlatformFromUrl(string $url): string {
|
||||
$host = strtolower(parse_url($url, PHP_URL_HOST) ?? '');
|
||||
if (str_contains($host, 'reddit.com')) return 'reddit';
|
||||
if (str_contains($host, 'twitter.com') || str_contains($host, 'x.com')) return 'twitter';
|
||||
if (str_contains($host, 'facebook.com')) return 'facebook';
|
||||
if (str_contains($host, 'instagram.com')) return 'instagram';
|
||||
if (str_contains($host, 'tiktok.com')) return 'tiktok';
|
||||
if (str_contains($host, 'youtube.com')) return 'youtube';
|
||||
if (str_contains($host, 'discord.com')) return 'discord';
|
||||
if (str_contains($host, 'twitch.tv')) return 'twitch';
|
||||
return 'other';
|
||||
}
|
||||
|
||||
function scrapeForReferralCode(string $url, string $code): array {
|
||||
// SSRF guard — only http/https, no private/reserved IPs
|
||||
$parsed = parse_url($url);
|
||||
if (!$parsed || !in_array(strtolower($parsed['scheme'] ?? ''), ['http','https'])) {
|
||||
return ['verified'=>false,'reason'=>'invalid_url'];
|
||||
}
|
||||
$host = $parsed['host'] ?? '';
|
||||
$ip = gethostbyname($host);
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
|
||||
return ['verified'=>false,'reason'=>'invalid_url'];
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 8,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 3,
|
||||
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_HTTPHEADER => ['Accept: text/html,application/xhtml+xml,*/*;q=0.8'],
|
||||
CURLOPT_BUFFERSIZE => 131072, // read up to 128KB
|
||||
]);
|
||||
$html = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlErr = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlErr || !$html) return ['verified'=>false,'reason'=>'unreachable'];
|
||||
if ($httpCode === 401 || $httpCode === 403) return ['verified'=>false,'reason'=>'login_required'];
|
||||
if ($httpCode >= 400) return ['verified'=>false,'reason'=>'unreachable'];
|
||||
|
||||
// Truncate to 512KB to avoid scanning huge pages
|
||||
$content = substr($html, 0, 524288);
|
||||
|
||||
if (stripos($content, $code) !== false) return ['verified'=>true, 'reason'=>'code_found'];
|
||||
if (stripos($content, 'tomtomgames.com') !== false) return ['verified'=>false,'reason'=>'site_found_no_code'];
|
||||
return ['verified'=>false,'reason'=>'not_found'];
|
||||
}
|
||||
|
||||
// ── POST actions ──────────────────────────────────────────
|
||||
if ($method !== 'POST') { echo json_encode(['success'=>false,'error'=>'Method not allowed']); exit; }
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if ($action === 'submit_share') {
|
||||
$url = trim($d['url'] ?? '');
|
||||
$platform = preg_replace('/[^a-z0-9_]/', '', strtolower(trim($d['platform'] ?? '')));
|
||||
|
||||
if (!$url) { echo json_encode(['success'=>false,'error'=>'Please paste the URL of your post']); exit; }
|
||||
if (!filter_var($url, FILTER_VALIDATE_URL)) { echo json_encode(['success'=>false,'error'=>'That doesn\'t look like a valid URL']); exit; }
|
||||
|
||||
if (!$platform) $platform = inferPlatformFromUrl($url);
|
||||
|
||||
// Get referrer's code for scraping
|
||||
$codeRow = db()->prepare("SELECT referral_code FROM users WHERE id=?");
|
||||
$codeRow->execute([$userId]);
|
||||
$referralCode = (string)$codeRow->fetchColumn();
|
||||
|
||||
// Reject duplicate URLs from same user
|
||||
$dup = db()->prepare("SELECT id FROM referral_social_shares WHERE user_id=? AND share_url=?");
|
||||
$dup->execute([$userId, $url]);
|
||||
if ($dup->fetchColumn()) { echo json_encode(['success'=>false,'error'=>'You already submitted this URL']); exit; }
|
||||
|
||||
$bonus = 5;
|
||||
$scrape = scrapeForReferralCode($url, $referralCode);
|
||||
$status = $scrape['verified'] ? 'approved' : 'pending';
|
||||
|
||||
$reasonMessages = [
|
||||
'code_found' => null, // auto-verified, no extra message needed
|
||||
'login_required' => 'This post requires a login to view — our team will review it manually.',
|
||||
'unreachable' => "We couldn't reach that URL — our team will review it manually.",
|
||||
'not_found' => "Your referral code wasn't found on that page — our team will review it manually.",
|
||||
'site_found_no_code'=> 'TomTomGames was mentioned but your referral code wasn\'t found — our team will review it manually.',
|
||||
'invalid_url' => 'That URL doesn\'t look valid — please check and try again.',
|
||||
];
|
||||
|
||||
if ($scrape['reason'] === 'invalid_url') {
|
||||
echo json_encode(['success'=>false,'error'=>$reasonMessages['invalid_url']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
db()->beginTransaction();
|
||||
db()->prepare("INSERT INTO referral_social_shares (user_id,platform,bonus_tokens,share_url,auto_verified,verify_result,status) VALUES (?,?,?,?,?,?,?)")
|
||||
->execute([$userId, $platform, $bonus, $url, $scrape['verified']?1:0, $scrape['reason'], $status]);
|
||||
$shareId = (int)db()->lastInsertId();
|
||||
|
||||
if ($scrape['verified']) {
|
||||
db()->prepare("UPDATE users SET tokens=tokens+? WHERE id=?")->execute([$bonus, $userId]);
|
||||
logAdminAction('SOCIAL_SHARE_AUTO_VERIFIED', (int)$_SESSION['user_id'], 'referral_share', $shareId,
|
||||
'Auto-verified share on '.$platform.' — awarded '.$bonus.' tokens. URL: '.$url, '', 'approved', 'info');
|
||||
}
|
||||
db()->commit();
|
||||
|
||||
$resp = ['success'=>true,'auto_verified'=>$scrape['verified'],'platform'=>$platform];
|
||||
if ($scrape['verified']) {
|
||||
$resp['tokens'] = $bonus;
|
||||
$resp['message'] = '🎉 Verified! +'.$bonus.' tokens awarded automatically.';
|
||||
} else {
|
||||
$resp['pending'] = true;
|
||||
$resp['message'] = $reasonMessages[$scrape['reason']] ?? 'Submitted for manual review.';
|
||||
}
|
||||
echo json_encode($resp);
|
||||
} catch(Exception $e) {
|
||||
db()->rollBack();
|
||||
echo json_encode(['success'=>false,'error'=>'Failed to submit share']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Admin only below ──────────────────────────────────────
|
||||
if (!$isAdmin) { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; }
|
||||
|
||||
if ($action === 'resolve_referral') {
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
$status = $d['status'] ?? '';
|
||||
$note = substr(trim($d['note'] ?? ''), 0, 300);
|
||||
if (!in_array($status, ['verified','denied','deleted'])) { echo json_encode(['success'=>false,'error'=>'Invalid status']); exit; }
|
||||
|
||||
$chk = db()->prepare("SELECT r.*, t.tokens_per_ref, t.min_referrals, t.bonus_tokens FROM referrals r LEFT JOIN referral_tiers t ON r.tier_id=t.id WHERE r.id=?");
|
||||
$chk->execute([$id]);
|
||||
$ref = $chk->fetch();
|
||||
if (!$ref) { echo json_encode(['success'=>false,'error'=>'Not found']); exit; }
|
||||
|
||||
if ($status === 'verified') {
|
||||
// Determine best tier for referrer
|
||||
$countStmt = db()->prepare("SELECT COUNT(*) FROM referrals WHERE referrer_id=? AND status='verified'");
|
||||
$countStmt->execute([$ref['referrer_id']]);
|
||||
$verifiedCount = (int)$countStmt->fetchColumn() + 1; // +1 for this one
|
||||
|
||||
$tier = db()->query("SELECT * FROM referral_tiers WHERE is_active=1 AND min_referrals <= $verifiedCount ORDER BY min_referrals DESC LIMIT 1")->fetch();
|
||||
$tokensToAward = $tier ? (float)$tier['tokens_per_ref'] : 5;
|
||||
|
||||
// Check if this hits a bonus milestone
|
||||
$bonusTokens = 0;
|
||||
if ($tier && $verifiedCount == (int)$tier['min_referrals']) {
|
||||
$bonusTokens = (float)$tier['bonus_tokens'];
|
||||
}
|
||||
|
||||
$totalAward = $tokensToAward + $bonusTokens;
|
||||
|
||||
db()->beginTransaction();
|
||||
try {
|
||||
db()->prepare("UPDATE referrals SET status='verified', tier_id=?, tokens_awarded=?, admin_id=?, admin_note=?, resolved_at=NOW() WHERE id=?")
|
||||
->execute([$tier['id'] ?? null, $totalAward, (int)$_SESSION['user_id'], $note, $id]);
|
||||
db()->prepare("UPDATE users SET tokens=tokens+? WHERE id=?")->execute([$totalAward, $ref['referrer_id']]);
|
||||
logAdminAction('REFERRAL_VERIFIED', (int)$_SESSION['user_id'], 'referral', $id, 'Verified referral #'.$id.' — awarded '.$totalAward.' tokens to user #'.$ref['referrer_id'], 'pending', 'verified', 'info');
|
||||
db()->commit();
|
||||
echo json_encode(['success'=>true,'tokens_awarded'=>$totalAward,'bonus'=>$bonusTokens,'tier'=>$tier['name']??'']);
|
||||
} catch(Exception $e) {
|
||||
db()->rollBack();
|
||||
echo json_encode(['success'=>false,'error'=>'DB error']);
|
||||
}
|
||||
} else {
|
||||
db()->prepare("UPDATE referrals SET status=?, admin_id=?, admin_note=?, resolved_at=NOW() WHERE id=?")
|
||||
->execute([$status, (int)$_SESSION['user_id'], $note, $id]);
|
||||
echo json_encode(['success'=>true]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'resolve_share') {
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
$status = $d['status'] ?? '';
|
||||
if (!in_array($status, ['approved','denied'])) { echo json_encode(['success'=>false,'error'=>'Invalid']); exit; }
|
||||
$chk = db()->prepare("SELECT * FROM referral_social_shares WHERE id=?"); $chk->execute([$id]); $share = $chk->fetch();
|
||||
if (!$share) { echo json_encode(['success'=>false,'error'=>'Not found']); exit; }
|
||||
db()->prepare("UPDATE referral_social_shares SET status=?,admin_id=?,resolved_at=NOW() WHERE id=?")->execute([$status,(int)$_SESSION['user_id'],$id]);
|
||||
if ($status === 'approved') {
|
||||
db()->prepare("UPDATE users SET tokens=tokens+? WHERE id=?")->execute([$share['bonus_tokens'],$share['user_id']]);
|
||||
logAdminAction('SOCIAL_SHARE_APPROVED', (int)$_SESSION['user_id'], 'referral_share', $id, 'Approved social share #'.$id.' — awarded '.$share['bonus_tokens'].' bonus tokens to user #'.$share['user_id'], '', 'approved', 'info');
|
||||
}
|
||||
echo json_encode(['success'=>true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Tier CRUD ─────────────────────────────────────────────
|
||||
if ($action === 'tier_create') {
|
||||
$name = substr(trim($d['name']??''),0,100);
|
||||
$minRefs = (int)($d['min_referrals']??1);
|
||||
$tokPer = (float)($d['tokens_per_ref']??5);
|
||||
$bonus = (float)($d['bonus_tokens']??0);
|
||||
$desc = substr(trim($d['description']??''),0,300);
|
||||
$sort = (int)($d['sort_order']??99);
|
||||
$active = (int)(bool)($d['is_active']??1);
|
||||
if (!$name) { echo json_encode(['success'=>false,'error'=>'Name required']); exit; }
|
||||
db()->prepare("INSERT INTO referral_tiers (name,min_referrals,tokens_per_ref,bonus_tokens,description,is_active,sort_order) VALUES (?,?,?,?,?,?,?)")
|
||||
->execute([$name,$minRefs,$tokPer,$bonus,$desc,$active,$sort]);
|
||||
echo json_encode(['success'=>true,'id'=>db()->lastInsertId()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'tier_update') {
|
||||
$id = (int)($d['id']??0);
|
||||
$name = substr(trim($d['name']??''),0,100);
|
||||
$minRefs = (int)($d['min_referrals']??1);
|
||||
$tokPer = (float)($d['tokens_per_ref']??5);
|
||||
$bonus = (float)($d['bonus_tokens']??0);
|
||||
$desc = substr(trim($d['description']??''),0,300);
|
||||
$sort = (int)($d['sort_order']??0);
|
||||
$active = (int)(bool)($d['is_active']??1);
|
||||
if (!$id||!$name) { echo json_encode(['success'=>false,'error'=>'ID and name required']); exit; }
|
||||
db()->prepare("UPDATE referral_tiers SET name=?,min_referrals=?,tokens_per_ref=?,bonus_tokens=?,description=?,is_active=?,sort_order=? WHERE id=?")
|
||||
->execute([$name,$minRefs,$tokPer,$bonus,$desc,$active,$sort,$id]);
|
||||
echo json_encode(['success'=>true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'tier_delete') {
|
||||
$id = (int)($d['id']??0);
|
||||
if (!$id) { echo json_encode(['success'=>false,'error'=>'ID required']); exit; }
|
||||
db()->prepare("DELETE FROM referral_tiers WHERE id=?")->execute([$id]);
|
||||
echo json_encode(['success'=>true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success'=>false,'error'=>'Unknown action']);
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success'=>false,'error'=>'Method not allowed']); exit;
|
||||
}
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$username = trim($data['username'] ?? '');
|
||||
$password = trim($data['password'] ?? '');
|
||||
$alias = trim($data['alias'] ?? '');
|
||||
$email = trim($data['email'] ?? '');
|
||||
$referralCode= trim($data['referral_code']?? '');
|
||||
|
||||
logSecurityEvent('REGISTER_ATTEMPT', null, 'Registration attempt for: ' . $email, 'info');
|
||||
$result = initiateRegistration($username, $password, $alias, $email, $referralCode);
|
||||
echo json_encode($result);
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success'=>false,'error'=>'Method not allowed']); exit;
|
||||
}
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$email = trim($data['email'] ?? '');
|
||||
|
||||
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
echo json_encode(['success'=>false,'error'=>'Valid email required']); exit;
|
||||
}
|
||||
|
||||
echo json_encode(resendVerification($email));
|
||||
@@ -0,0 +1,31 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<defs>
|
||||
<radialGradient id="bg" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#001a2e"/>
|
||||
<stop offset="100%" stop-color="#000810"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="cyan" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#00e5ff"/>
|
||||
<stop offset="100%" stop-color="#0066ff"/>
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="2" result="blur"/>
|
||||
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="120" height="120" rx="24" fill="url(#bg)"/>
|
||||
<!-- Grid lines -->
|
||||
<line x1="0" y1="40" x2="120" y2="40" stroke="#00e5ff" stroke-width="0.3" opacity="0.15"/>
|
||||
<line x1="0" y1="80" x2="120" y2="80" stroke="#00e5ff" stroke-width="0.3" opacity="0.15"/>
|
||||
<line x1="40" y1="0" x2="40" y2="120" stroke="#00e5ff" stroke-width="0.3" opacity="0.15"/>
|
||||
<line x1="80" y1="0" x2="80" y2="120" stroke="#00e5ff" stroke-width="0.3" opacity="0.15"/>
|
||||
<!-- Controller icon -->
|
||||
<text x="60" y="56" font-size="36" text-anchor="middle" filter="url(#glow)" font-family="Arial">🎮</text>
|
||||
<!-- eGame99 -->
|
||||
<text x="60" y="78" font-size="13" font-weight="900" text-anchor="middle" fill="url(#cyan)" font-family="Arial Black, sans-serif" letter-spacing="1" filter="url(#glow)">eGAME 99</text>
|
||||
<!-- Corner accents -->
|
||||
<path d="M10 10 L25 10 L25 14 L14 14 L14 25 L10 25 Z" fill="url(#cyan)" opacity="0.6"/>
|
||||
<path d="M110 10 L95 10 L95 14 L106 14 L106 25 L110 25 Z" fill="url(#cyan)" opacity="0.6"/>
|
||||
<path d="M10 110 L25 110 L25 106 L14 106 L14 95 L10 95 Z" fill="url(#cyan)" opacity="0.6"/>
|
||||
<path d="M110 110 L95 110 L95 106 L106 106 L106 95 L110 95 Z" fill="url(#cyan)" opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,30 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<defs>
|
||||
<radialGradient id="bg" cx="50%" cy="70%" r="60%">
|
||||
<stop offset="0%" stop-color="#2a0800"/>
|
||||
<stop offset="100%" stop-color="#0a0200"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="fire" x1="0%" y1="100%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#FF2200"/>
|
||||
<stop offset="50%" stop-color="#FF7700"/>
|
||||
<stop offset="100%" stop-color="#FFDD00"/>
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="2.5" result="blur"/>
|
||||
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="120" height="120" rx="24" fill="url(#bg)"/>
|
||||
<rect x="2" y="2" width="116" height="116" rx="22" fill="none" stroke="url(#fire)" stroke-width="1.5" opacity="0.6"/>
|
||||
<!-- Fire wave bottom -->
|
||||
<path d="M0 100 Q20 85 40 95 Q60 105 80 90 Q100 75 120 88 L120 120 L0 120 Z" fill="#FF2200" opacity="0.25"/>
|
||||
<!-- Fire emoji -->
|
||||
<text x="60" y="58" font-size="40" text-anchor="middle" filter="url(#glow)" font-family="Arial">🔥</text>
|
||||
<!-- KIRIN text -->
|
||||
<text x="60" y="82" font-size="13" font-weight="900" text-anchor="middle" fill="url(#fire)" font-family="Arial Black, sans-serif" letter-spacing="2" filter="url(#glow)">KIRIN</text>
|
||||
<!-- Sparks -->
|
||||
<circle cx="35" cy="35" r="2" fill="#FF7700" opacity="0.7"/>
|
||||
<circle cx="85" cy="28" r="1.5" fill="#FFDD00" opacity="0.8"/>
|
||||
<circle cx="25" cy="60" r="1.5" fill="#FF2200" opacity="0.6"/>
|
||||
<circle cx="95" cy="55" r="2" fill="#FF7700" opacity="0.7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,38 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
|
||||
<defs>
|
||||
<linearGradient id="g1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#f0c040"/>
|
||||
<stop offset="100%" stop-color="#ff6b35"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="g2" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#00e5ff"/>
|
||||
<stop offset="100%" stop-color="#7b2fbe"/>
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="1.5" result="blur"/>
|
||||
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<!-- Controller body -->
|
||||
<rect x="6" y="16" width="36" height="22" rx="11" fill="url(#g1)" filter="url(#glow)"/>
|
||||
<!-- D-pad left side -->
|
||||
<rect x="12" y="23" width="8" height="3" rx="1.5" fill="rgba(0,0,0,0.5)"/>
|
||||
<rect x="15" y="20" width="3" height="8" rx="1.5" fill="rgba(0,0,0,0.5)"/>
|
||||
<!-- Buttons right side -->
|
||||
<circle cx="32" cy="22" r="2" fill="#e63946" opacity="0.85"/>
|
||||
<circle cx="36" cy="25" r="2" fill="#2ec4b6" opacity="0.85"/>
|
||||
<circle cx="32" cy="28" r="2" fill="#7b2fbe" opacity="0.85"/>
|
||||
<circle cx="28" cy="25" r="2" fill="#f4a261" opacity="0.85"/>
|
||||
<!-- Center connector / menu button -->
|
||||
<rect x="21" y="24" width="6" height="3" rx="1.5" fill="rgba(0,0,0,0.35)"/>
|
||||
<!-- Handle grips (left and right) -->
|
||||
<rect x="8" y="30" width="8" height="6" rx="4" fill="url(#g2)" opacity="0.7"/>
|
||||
<rect x="32" y="30" width="8" height="6" rx="4" fill="url(#g2)" opacity="0.7"/>
|
||||
<!-- Top bumper buttons -->
|
||||
<rect x="14" y="13" width="8" height="5" rx="2.5" fill="url(#g1)" opacity="0.8"/>
|
||||
<rect x="26" y="13" width="8" height="5" rx="2.5" fill="url(#g1)" opacity="0.8"/>
|
||||
<!-- Stars / sparkles -->
|
||||
<circle cx="24" cy="8" r="1.5" fill="#f0c040" opacity="0.9"/>
|
||||
<circle cx="38" cy="10" r="1" fill="#00e5ff" opacity="0.8"/>
|
||||
<circle cx="10" cy="10" r="1" fill="#f0c040" opacity="0.7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<defs>
|
||||
<radialGradient id="bg" cx="40%" cy="40%" r="60%">
|
||||
<stop offset="0%" stop-color="#1a0a40"/>
|
||||
<stop offset="60%" stop-color="#0a0520"/>
|
||||
<stop offset="100%" stop-color="#050210"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="purple" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#c77dff"/>
|
||||
<stop offset="100%" stop-color="#7b2fbe"/>
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="3" result="blur"/>
|
||||
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="120" height="120" rx="24" fill="url(#bg)"/>
|
||||
<!-- Galactic swirl -->
|
||||
<ellipse cx="60" cy="60" rx="38" ry="12" fill="none" stroke="url(#purple)" stroke-width="1.5" opacity="0.5" transform="rotate(-30 60 60)"/>
|
||||
<ellipse cx="60" cy="60" rx="28" ry="8" fill="none" stroke="#c77dff" stroke-width="1" opacity="0.4" transform="rotate(20 60 60)"/>
|
||||
<!-- Stars -->
|
||||
<circle cx="30" cy="25" r="1.5" fill="white" opacity="0.9"/>
|
||||
<circle cx="88" cy="30" r="1" fill="white" opacity="0.7"/>
|
||||
<circle cx="20" cy="70" r="1" fill="white" opacity="0.6"/>
|
||||
<circle cx="95" cy="65" r="1.5" fill="white" opacity="0.8"/>
|
||||
<circle cx="50" cy="15" r="1" fill="white" opacity="0.5"/>
|
||||
<circle cx="75" cy="100" r="1.5" fill="white" opacity="0.7"/>
|
||||
<circle cx="40" cy="95" r="1" fill="#c77dff" opacity="0.9"/>
|
||||
<circle cx="100" cy="45" r="1" fill="#c77dff" opacity="0.6"/>
|
||||
<!-- Galaxy emoji + text -->
|
||||
<text x="60" y="56" font-size="34" text-anchor="middle" filter="url(#glow)" font-family="Arial">🌌</text>
|
||||
<text x="60" y="80" font-size="11" font-weight="700" text-anchor="middle" fill="url(#purple)" font-family="Arial, sans-serif" letter-spacing="1.5">MILKY WAY</text>
|
||||
<!-- Border glow -->
|
||||
<rect x="2" y="2" width="116" height="116" rx="22" fill="none" stroke="url(#purple)" stroke-width="1" opacity="0.4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,31 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<defs>
|
||||
<radialGradient id="bg" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#1a1400"/>
|
||||
<stop offset="100%" stop-color="#0a0800"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="gold" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#FFD700"/>
|
||||
<stop offset="50%" stop-color="#FFF0A0"/>
|
||||
<stop offset="100%" stop-color="#CC9900"/>
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="3" result="blur"/>
|
||||
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="120" height="120" rx="24" fill="url(#bg)"/>
|
||||
<!-- Diamond border -->
|
||||
<rect x="8" y="8" width="104" height="104" rx="18" fill="none" stroke="url(#gold)" stroke-width="1.5" opacity="0.7"/>
|
||||
<rect x="4" y="4" width="112" height="112" rx="20" fill="none" stroke="#FFD700" stroke-width="0.5" opacity="0.3"/>
|
||||
<!-- Crown -->
|
||||
<text x="60" y="54" font-size="38" text-anchor="middle" filter="url(#glow)" font-family="Arial">👑</text>
|
||||
<!-- 777 NOBLE text -->
|
||||
<text x="60" y="76" font-size="18" font-weight="900" text-anchor="middle" fill="url(#gold)" font-family="Arial Black, sans-serif" letter-spacing="3" filter="url(#glow)">777</text>
|
||||
<text x="60" y="92" font-size="10" font-weight="700" text-anchor="middle" fill="#CC9900" font-family="Arial, sans-serif" letter-spacing="3" opacity="0.9">NOBLE</text>
|
||||
<!-- Corner diamonds -->
|
||||
<polygon points="20,12 24,16 20,20 16,16" fill="#FFD700" opacity="0.7"/>
|
||||
<polygon points="100,12 104,16 100,20 96,16" fill="#FFD700" opacity="0.7"/>
|
||||
<polygon points="20,108 24,104 20,100 16,104" fill="#FFD700" opacity="0.7"/>
|
||||
<polygon points="100,108 104,104 100,100 96,104" fill="#FFD700" opacity="0.7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,46 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" width="1200" height="630">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#0a0a12"/>
|
||||
<stop offset="100%" stop-color="#1a1228"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="gold" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#f0c040"/>
|
||||
<stop offset="100%" stop-color="#ff6b35"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="cyan" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#00e5ff"/>
|
||||
<stop offset="100%" stop-color="#7b2fbe"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Background -->
|
||||
<rect width="1200" height="630" fill="url(#bg)"/>
|
||||
<!-- Grid lines -->
|
||||
<line x1="0" y1="315" x2="1200" y2="315" stroke="rgba(240,192,64,0.05)" stroke-width="1"/>
|
||||
<line x1="600" y1="0" x2="600" y2="630" stroke="rgba(240,192,64,0.05)" stroke-width="1"/>
|
||||
<!-- Glow circle -->
|
||||
<circle cx="600" cy="315" r="280" fill="rgba(240,192,64,0.03)" stroke="rgba(240,192,64,0.08)" stroke-width="1"/>
|
||||
<!-- Gamepad icon (simplified) -->
|
||||
<g transform="translate(540,180) scale(2.5)">
|
||||
<rect x="6" y="16" width="36" height="22" rx="11" fill="url(#gold)" opacity="0.9"/>
|
||||
<rect x="12" y="23" width="8" height="3" rx="1.5" fill="rgba(0,0,0,0.5)"/>
|
||||
<rect x="15" y="20" width="3" height="8" rx="1.5" fill="rgba(0,0,0,0.5)"/>
|
||||
<circle cx="32" cy="22" r="2.2" fill="#e63946"/>
|
||||
<circle cx="36" cy="25" r="2.2" fill="#2ec4b6"/>
|
||||
<circle cx="32" cy="28" r="2.2" fill="#7b2fbe"/>
|
||||
<circle cx="28" cy="25" r="2.2" fill="#f4a261"/>
|
||||
<rect x="8" y="30" width="8" height="7" rx="4" fill="url(#cyan)" opacity="0.8"/>
|
||||
<rect x="32" y="30" width="8" height="7" rx="4" fill="url(#cyan)" opacity="0.8"/>
|
||||
</g>
|
||||
<!-- Logo text -->
|
||||
<text x="600" y="375" font-family="Georgia,serif" font-size="64" font-weight="700" fill="#f0c040" text-anchor="middle">TomTomGames</text>
|
||||
<!-- Tagline -->
|
||||
<text x="600" y="425" font-family="Arial,sans-serif" font-size="24" fill="#8888aa" text-anchor="middle">Buy tokens for VBlink777 · Fire Kirin · Milky Way · Ultra Panda & more</text>
|
||||
<!-- Bottom badges -->
|
||||
<rect x="350" y="470" width="160" height="40" rx="8" fill="rgba(240,192,64,0.1)" stroke="rgba(240,192,64,0.3)" stroke-width="1"/>
|
||||
<text x="430" y="496" font-family="Arial,sans-serif" font-size="16" fill="#f0c040" text-anchor="middle" font-weight="700">⚡ Instant Delivery</text>
|
||||
<rect x="520" y="470" width="160" height="40" rx="8" fill="rgba(0,229,255,0.07)" stroke="rgba(0,229,255,0.2)" stroke-width="1"/>
|
||||
<text x="600" y="496" font-family="Arial,sans-serif" font-size="16" fill="#00e5ff" text-anchor="middle" font-weight="700">🔒 SSL Secured</text>
|
||||
<rect x="690" y="470" width="160" height="40" rx="8" fill="rgba(0,230,118,0.07)" stroke="rgba(0,230,118,0.2)" stroke-width="1"/>
|
||||
<text x="770" y="496" font-family="Arial,sans-serif" font-size="16" fill="#00e676" text-anchor="middle" font-weight="700">💬 24/7 Support</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,27 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<defs>
|
||||
<radialGradient id="bg" cx="50%" cy="30%" r="70%">
|
||||
<stop offset="0%" stop-color="#0a1628"/>
|
||||
<stop offset="100%" stop-color="#040810"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="blue" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#64b5f6"/>
|
||||
<stop offset="100%" stop-color="#1565c0"/>
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="2" result="blur"/>
|
||||
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="120" height="120" rx="24" fill="url(#bg)"/>
|
||||
<!-- Crown at top -->
|
||||
<path d="M42 28 L50 20 L60 26 L70 20 L78 28 L74 38 L46 38 Z" fill="#FFD700" opacity="0.9" filter="url(#glow)"/>
|
||||
<circle cx="50" cy="20" r="3" fill="#FFD700"/>
|
||||
<circle cx="60" cy="16" r="3.5" fill="#FFD700"/>
|
||||
<circle cx="70" cy="20" r="3" fill="#FFD700"/>
|
||||
<!-- Panda + paw -->
|
||||
<text x="60" y="68" font-size="36" text-anchor="middle" filter="url(#glow)" font-family="Arial">🐾</text>
|
||||
<!-- MASTER text -->
|
||||
<text x="60" y="90" font-size="11" font-weight="900" text-anchor="middle" fill="url(#blue)" font-family="Arial Black, sans-serif" letter-spacing="2" filter="url(#glow)">MASTER</text>
|
||||
<rect x="2" y="2" width="116" height="116" rx="22" fill="none" stroke="url(#blue)" stroke-width="1" opacity="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,30 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<defs>
|
||||
<radialGradient id="bg" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#1a1a2e"/>
|
||||
<stop offset="100%" stop-color="#080814"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="orange" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#FFB347"/>
|
||||
<stop offset="100%" stop-color="#FF6B00"/>
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="2" result="blur"/>
|
||||
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="120" height="120" rx="24" fill="url(#bg)"/>
|
||||
<rect x="2" y="2" width="116" height="116" rx="22" fill="none" stroke="url(#orange)" stroke-width="1.5" opacity="0.5"/>
|
||||
<!-- Bamboo decorations -->
|
||||
<rect x="12" y="30" width="5" height="65" rx="2" fill="#2d5a27" opacity="0.5"/>
|
||||
<rect x="103" y="25" width="5" height="65" rx="2" fill="#2d5a27" opacity="0.5"/>
|
||||
<rect x="10" y="48" width="9" height="3" rx="1" fill="#2d5a27" opacity="0.5"/>
|
||||
<rect x="101" y="44" width="9" height="3" rx="1" fill="#2d5a27" opacity="0.5"/>
|
||||
<!-- Panda emoji -->
|
||||
<text x="60" y="60" font-size="42" text-anchor="middle" filter="url(#glow)" font-family="Arial">🐼</text>
|
||||
<!-- ULTRA text -->
|
||||
<text x="60" y="85" font-size="12" font-weight="900" text-anchor="middle" fill="url(#orange)" font-family="Arial Black, sans-serif" letter-spacing="2" filter="url(#glow)">ULTRA</text>
|
||||
<!-- Stars -->
|
||||
<text x="35" y="35" font-size="12" text-anchor="middle" fill="#FFD700" opacity="0.8">★</text>
|
||||
<text x="85" y="35" font-size="12" text-anchor="middle" fill="#FFD700" opacity="0.8">★</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,27 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<defs>
|
||||
<radialGradient id="bg" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#1a0a2e"/>
|
||||
<stop offset="100%" stop-color="#0d0620"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="gold" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#FFD700"/>
|
||||
<stop offset="100%" stop-color="#FF6B00"/>
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="2" result="blur"/>
|
||||
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="120" height="120" rx="24" fill="url(#bg)"/>
|
||||
<rect x="2" y="2" width="116" height="116" rx="22" fill="none" stroke="url(#gold)" stroke-width="1.5" opacity="0.6"/>
|
||||
<!-- Slot machine reel symbols -->
|
||||
<text x="60" y="52" font-size="36" text-anchor="middle" fill="url(#gold)" filter="url(#glow)" font-family="Arial">🎰</text>
|
||||
<!-- 777 text -->
|
||||
<text x="60" y="84" font-size="22" font-weight="900" text-anchor="middle" fill="url(#gold)" font-family="Arial Black, sans-serif" filter="url(#glow)" letter-spacing="2">777</text>
|
||||
<!-- Stars -->
|
||||
<circle cx="22" cy="22" r="2" fill="#FFD700" opacity="0.8"/>
|
||||
<circle cx="98" cy="22" r="2" fill="#FFD700" opacity="0.8"/>
|
||||
<circle cx="22" cy="98" r="2" fill="#FF6B00" opacity="0.8"/>
|
||||
<circle cx="98" cy="98" r="2" fill="#FF6B00" opacity="0.8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* TomTomGames Version Manager
|
||||
* Run after uploading a new build to update the DB version.
|
||||
* Usage: https://tomtomgames.com/bump_version.php?key=ADMIN_KEY&version=1.0.2¬es=Your+notes+here
|
||||
*
|
||||
* Or auto-bump: ?key=ADMIN_KEY&bump=patch (1.0.1 → 1.0.2)
|
||||
* ?key=ADMIN_KEY&bump=minor (1.0.1 → 1.1.0)
|
||||
* ?key=ADMIN_KEY&bump=major (1.0.1 → 2.0.0)
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../includes/config.php'; // BUMP_KEY now defined there, not in this web-reachable file
|
||||
|
||||
if (($_GET['key'] ?? '') !== BUMP_KEY) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Forbidden']);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../includes/db.php';
|
||||
|
||||
// Get current version
|
||||
$current = db()->query("SELECT version FROM app_version ORDER BY id DESC LIMIT 1")->fetchColumn() ?: '1.0.0';
|
||||
[$major, $minor, $patch] = array_map('intval', explode('.', $current));
|
||||
|
||||
// Determine new version
|
||||
if (!empty($_GET['version'])) {
|
||||
$newVersion = trim($_GET['version']);
|
||||
} elseif (!empty($_GET['bump'])) {
|
||||
switch ($_GET['bump']) {
|
||||
case 'major': $newVersion = ($major+1).'.0.0'; break;
|
||||
case 'minor': $newVersion = $major.'.'.($minor+1).'.0'; break;
|
||||
default: $newVersion = $major.'.'.$minor.'.'.($patch+1); break;
|
||||
}
|
||||
} else {
|
||||
// Default: bump patch
|
||||
$newVersion = $major.'.'.$minor.'.'.($patch+1);
|
||||
}
|
||||
|
||||
$notes = trim($_GET['notes'] ?? 'Build ' . date('Y-m-d H:i:s'));
|
||||
|
||||
db()->prepare("INSERT INTO app_version (version, notes) VALUES (?, ?)")
|
||||
->execute([$newVersion, $notes]);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'previous' => $current,
|
||||
'new_version' => $newVersion,
|
||||
'notes' => $notes,
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
@@ -0,0 +1,91 @@
|
||||
docRoot $VH_ROOT/public_html
|
||||
vhDomain $VH_NAME
|
||||
vhAliases www.$VH_NAME
|
||||
adminEmails admin@tomtomgames.com
|
||||
enableGzip 1
|
||||
enableIpGeo 1
|
||||
|
||||
index {
|
||||
useServer 0
|
||||
indexFiles index.php, index.html
|
||||
}
|
||||
|
||||
errorlog $VH_ROOT/logs/$VH_NAME.error_log {
|
||||
useServer 0
|
||||
logLevel WARN
|
||||
rollingSize 10M
|
||||
}
|
||||
|
||||
accesslog $VH_ROOT/logs/$VH_NAME.access_log {
|
||||
useServer 0
|
||||
logFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i""
|
||||
logHeaders 5
|
||||
rollingSize 10M
|
||||
keepDays 10
|
||||
compressArchive 1
|
||||
}
|
||||
|
||||
scripthandler {
|
||||
add lsapi:tomto8868 php
|
||||
}
|
||||
|
||||
extprocessor tomto8868 {
|
||||
type lsapi
|
||||
address UDS://tmp/lshttpd/tomto8868.sock
|
||||
maxConns 10
|
||||
env LSAPI_CHILDREN=10
|
||||
initTimeout 600
|
||||
retryTimeout 0
|
||||
persistConn 1
|
||||
pcKeepAliveTimeout 1
|
||||
respBuffer 0
|
||||
autoStart 1
|
||||
path /usr/local/lsws/lsphp85/bin/lsphp
|
||||
extUser tomto8868
|
||||
extGroup tomto8868
|
||||
memSoftLimit 1024M
|
||||
memHardLimit 1024M
|
||||
procSoftLimit 400
|
||||
procHardLimit 500
|
||||
}
|
||||
|
||||
phpIniOverride {
|
||||
|
||||
}
|
||||
|
||||
module cache {
|
||||
storagePath /usr/local/lsws/cachedata/$VH_NAME
|
||||
}
|
||||
|
||||
rewrite {
|
||||
enable 1
|
||||
autoLoadHtaccess 1
|
||||
}
|
||||
|
||||
context /.well-known/acme-challenge {
|
||||
location /usr/local/lsws/Example/html/.well-known/acme-challenge
|
||||
allowBrowse 1
|
||||
|
||||
rewrite {
|
||||
enable 0
|
||||
}
|
||||
addDefaultCharset off
|
||||
|
||||
phpIniOverride {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vhssl {
|
||||
keyFile /etc/letsencrypt/live/tomtomgames.com/privkey.pem
|
||||
certFile /etc/letsencrypt/live/tomtomgames.com/fullchain.pem
|
||||
certChain 1
|
||||
sslProtocol 24
|
||||
enableECDHE 1
|
||||
renegProtection 1
|
||||
sslSessionCache 1
|
||||
enableSpdy 15
|
||||
enableStapling 1
|
||||
ocspRespMaxAge 86400
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
adminemails admin@tomtomgames.com
|
||||
enablegzip 1
|
||||
vhaliases www.$VH_NAME
|
||||
docroot $VH_ROOT/public_html
|
||||
vhdomain $VH_NAME
|
||||
enableipgeo 1
|
||||
phpinioverride
|
||||
|
||||
scripthandler {
|
||||
add lsapi:tomto8868 php
|
||||
}
|
||||
|
||||
extprocessor tomto8868 {
|
||||
type lsapi
|
||||
address UDS://tmp/lshttpd/tomto8868.sock
|
||||
maxconns 10
|
||||
env LSAPI_CHILDREN=10
|
||||
inittimeout 600
|
||||
retrytimeout 0
|
||||
persistconn 1
|
||||
pckeepalivetimeout 1
|
||||
respbuffer 0
|
||||
autostart 1
|
||||
path /usr/local/lsws/lsphp85/bin/lsphp
|
||||
extuser tomto8868
|
||||
extgroup tomto8868
|
||||
memsoftlimit 1024M
|
||||
memhardlimit 1024M
|
||||
procsoftlimit 400
|
||||
prochardlimit 500
|
||||
}
|
||||
|
||||
errorlog $VH_ROOT/logs/$VH_NAME.error_log {
|
||||
useserver 0
|
||||
loglevel WARN
|
||||
rollingsize 10M
|
||||
}
|
||||
|
||||
vhssl {
|
||||
keyfile /etc/letsencrypt/live/tomtomgames.com/privkey.pem
|
||||
certfile /etc/letsencrypt/live/tomtomgames.com/fullchain.pem
|
||||
certchain 1
|
||||
sslprotocol 24
|
||||
enableecdhe 1
|
||||
renegprotection 1
|
||||
sslsessioncache 1
|
||||
enablespdy 15
|
||||
enablestapling 1
|
||||
ocsprespmaxage 86400
|
||||
}
|
||||
|
||||
index {
|
||||
useserver 0
|
||||
indexfiles index.php, index.html
|
||||
}
|
||||
|
||||
accesslog $VH_ROOT/logs/$VH_NAME.access_log {
|
||||
keepdays 10
|
||||
compressarchive 1
|
||||
logformat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i""
|
||||
logheaders 5
|
||||
rollingsize 10M
|
||||
useserver 0
|
||||
}
|
||||
|
||||
module cache {
|
||||
param storagepath /usr/local/lsws/cachedata/$VH_NAME
|
||||
unknownkeywords storagepath /usr/local/lsws/cachedata/$VH_NAME
|
||||
}
|
||||
|
||||
rewrite {
|
||||
enable 1
|
||||
autoloadhtaccess 1
|
||||
}
|
||||
|
||||
context /.well-known/acme-challenge {
|
||||
adddefaultcharset off
|
||||
phpinioverride
|
||||
location /usr/local/lsws/Example/html/.well-known/acme-challenge
|
||||
allowbrowse 1
|
||||
|
||||
rewrite {
|
||||
enable 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
docRoot $VH_ROOT/public_html
|
||||
vhDomain $VH_NAME
|
||||
vhAliases www.$VH_NAME
|
||||
adminEmails admin@tomtomgames.com
|
||||
enableGzip 1
|
||||
enableIpGeo 1
|
||||
|
||||
index {
|
||||
useServer 0
|
||||
indexFiles index.php, index.html
|
||||
}
|
||||
|
||||
errorlog $VH_ROOT/logs/$VH_NAME.error_log {
|
||||
useServer 0
|
||||
logLevel WARN
|
||||
rollingSize 10M
|
||||
}
|
||||
|
||||
accesslog $VH_ROOT/logs/$VH_NAME.access_log {
|
||||
useServer 0
|
||||
logFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i""
|
||||
logHeaders 5
|
||||
rollingSize 10M
|
||||
keepDays 10
|
||||
compressArchive 1
|
||||
}
|
||||
|
||||
scripthandler {
|
||||
add lsapi:tomto8868 php
|
||||
}
|
||||
|
||||
extprocessor tomto8868 {
|
||||
type lsapi
|
||||
address UDS://tmp/lshttpd/tomto8868.sock
|
||||
maxConns 10
|
||||
env LSAPI_CHILDREN=10
|
||||
initTimeout 600
|
||||
retryTimeout 0
|
||||
persistConn 1
|
||||
pcKeepAliveTimeout 1
|
||||
respBuffer 0
|
||||
autoStart 1
|
||||
path /usr/local/lsws/lsphp85/bin/lsphp
|
||||
extUser tomto8868
|
||||
extGroup tomto8868
|
||||
memSoftLimit 1024M
|
||||
memHardLimit 1024M
|
||||
procSoftLimit 400
|
||||
procHardLimit 500
|
||||
}
|
||||
|
||||
phpIniOverride {
|
||||
|
||||
}
|
||||
|
||||
module cache {
|
||||
storagePath /usr/local/lsws/cachedata/$VH_NAME
|
||||
}
|
||||
|
||||
rewrite {
|
||||
enable 1
|
||||
autoLoadHtaccess 1
|
||||
}
|
||||
|
||||
context /.well-known/acme-challenge {
|
||||
location /usr/local/lsws/Example/html/.well-known/acme-challenge
|
||||
allowBrowse 1
|
||||
|
||||
rewrite {
|
||||
enable 0
|
||||
}
|
||||
addDefaultCharset off
|
||||
|
||||
phpIniOverride {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vhssl {
|
||||
keyFile /etc/letsencrypt/live/tomtomgames.com/privkey.pem
|
||||
certFile /etc/letsencrypt/live/tomtomgames.com/fullchain.pem
|
||||
certChain 1
|
||||
sslProtocol 24
|
||||
enableECDHE 1
|
||||
renegProtection 1
|
||||
sslSessionCache 1
|
||||
enableSpdy 15
|
||||
enableStapling 1
|
||||
ocspRespMaxAge 86400
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
head 1.2;
|
||||
access;
|
||||
symbols;
|
||||
locks
|
||||
root:1.2; strict;
|
||||
comment @# @;
|
||||
|
||||
|
||||
1.2
|
||||
date 2026.05.16.13.11.17; author root; state Exp;
|
||||
branches;
|
||||
next 1.1;
|
||||
|
||||
1.1
|
||||
date 2026.05.15.20.01.17; author root; state Exp;
|
||||
branches;
|
||||
next ;
|
||||
|
||||
|
||||
desc
|
||||
@/usr/local/lsws/conf/vhosts/tomtomgames.com/vhost.conf0
|
||||
@
|
||||
|
||||
|
||||
1.2
|
||||
log
|
||||
@Update
|
||||
@
|
||||
text
|
||||
@docRoot $VH_ROOT/public_html
|
||||
vhDomain $VH_NAME
|
||||
vhAliases www.$VH_NAME
|
||||
adminEmails admin@@tomtomgames.com
|
||||
enableGzip 1
|
||||
enableIpGeo 1
|
||||
|
||||
index {
|
||||
useServer 0
|
||||
indexFiles index.php, index.html
|
||||
}
|
||||
|
||||
errorlog $VH_ROOT/logs/$VH_NAME.error_log {
|
||||
useServer 0
|
||||
logLevel WARN
|
||||
rollingSize 10M
|
||||
}
|
||||
|
||||
accesslog $VH_ROOT/logs/$VH_NAME.access_log {
|
||||
useServer 0
|
||||
logFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i""
|
||||
logHeaders 5
|
||||
rollingSize 10M
|
||||
keepDays 10
|
||||
compressArchive 1
|
||||
}
|
||||
|
||||
scripthandler {
|
||||
add lsapi:tomto8868 php
|
||||
}
|
||||
|
||||
extprocessor tomto8868 {
|
||||
type lsapi
|
||||
address UDS://tmp/lshttpd/tomto8868.sock
|
||||
maxConns 10
|
||||
env LSAPI_CHILDREN=10
|
||||
initTimeout 600
|
||||
retryTimeout 0
|
||||
persistConn 1
|
||||
pcKeepAliveTimeout 1
|
||||
respBuffer 0
|
||||
autoStart 1
|
||||
path /usr/local/lsws/lsphp85/bin/lsphp
|
||||
extUser tomto8868
|
||||
extGroup tomto8868
|
||||
memSoftLimit 1024M
|
||||
memHardLimit 1024M
|
||||
procSoftLimit 400
|
||||
procHardLimit 500
|
||||
}
|
||||
|
||||
phpIniOverride {
|
||||
|
||||
}
|
||||
|
||||
module cache {
|
||||
storagePath /usr/local/lsws/cachedata/$VH_NAME
|
||||
}
|
||||
|
||||
rewrite {
|
||||
enable 1
|
||||
autoLoadHtaccess 1
|
||||
}
|
||||
|
||||
context /.well-known/acme-challenge {
|
||||
location /usr/local/lsws/Example/html/.well-known/acme-challenge
|
||||
allowBrowse 1
|
||||
|
||||
rewrite {
|
||||
enable 0
|
||||
}
|
||||
addDefaultCharset off
|
||||
|
||||
phpIniOverride {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vhssl {
|
||||
keyFile /etc/letsencrypt/live/tomtomgames.com/privkey.pem
|
||||
certFile /etc/letsencrypt/live/tomtomgames.com/fullchain.pem
|
||||
certChain 1
|
||||
sslProtocol 24
|
||||
enableECDHE 1
|
||||
renegProtection 1
|
||||
sslSessionCache 1
|
||||
enableSpdy 15
|
||||
enableStapling 1
|
||||
ocspRespMaxAge 86400
|
||||
}
|
||||
@
|
||||
|
||||
|
||||
1.1
|
||||
log
|
||||
@Update
|
||||
@
|
||||
text
|
||||
@d79 13
|
||||
@
|
||||
@@ -0,0 +1,453 @@
|
||||
/*M!999999\- enable the sandbox mode */
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
DROP TABLE IF EXISTS `activity_log`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `activity_log` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) DEFAULT NULL,
|
||||
`admin_id` int(11) DEFAULT NULL,
|
||||
`action` varchar(80) NOT NULL,
|
||||
`category` varchar(40) DEFAULT 'general',
|
||||
`entity_type` varchar(40) DEFAULT NULL,
|
||||
`entity_id` int(11) DEFAULT NULL,
|
||||
`detail` text DEFAULT NULL,
|
||||
`old_value` text DEFAULT NULL,
|
||||
`new_value` text DEFAULT NULL,
|
||||
`ip` varchar(45) DEFAULT NULL,
|
||||
`user_agent` varchar(300) DEFAULT NULL,
|
||||
`page` varchar(200) DEFAULT NULL,
|
||||
`session_id` varchar(64) DEFAULT NULL,
|
||||
`severity` enum('info','warning','critical') DEFAULT 'info',
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_created` (`created_at`),
|
||||
KEY `idx_user` (`user_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=356 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `admin_payout_settings`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `admin_payout_settings` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`method_key` varchar(50) NOT NULL,
|
||||
`label` varchar(100) NOT NULL,
|
||||
`method_type` enum('manual','square_gift_card') DEFAULT 'manual',
|
||||
`is_enabled` tinyint(1) DEFAULT 1,
|
||||
`handle` varchar(200) DEFAULT NULL,
|
||||
`instructions` text DEFAULT NULL,
|
||||
`sort_order` int(11) DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `method_key` (`method_key`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `app_version`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `app_version` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`version` varchar(20) NOT NULL,
|
||||
`notes` text DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `broadcast_reads`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `broadcast_reads` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`broadcast_id` int(11) NOT NULL,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`read_at` datetime DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uq_br` (`broadcast_id`,`user_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
CONSTRAINT `broadcast_reads_ibfk_1` FOREIGN KEY (`broadcast_id`) REFERENCES `broadcasts` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `broadcast_reads_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `broadcast_replies`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `broadcast_replies` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`broadcast_id` int(11) NOT NULL,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`message` text NOT NULL,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `broadcast_id` (`broadcast_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
CONSTRAINT `broadcast_replies_ibfk_1` FOREIGN KEY (`broadcast_id`) REFERENCES `broadcasts` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `broadcast_replies_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `broadcasts`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `broadcasts` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`admin_id` int(11) NOT NULL,
|
||||
`subject` varchar(200) NOT NULL,
|
||||
`message` text NOT NULL,
|
||||
`target` enum('all','verified','unverified','admins') DEFAULT 'all',
|
||||
`sent_at` datetime DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `admin_id` (`admin_id`),
|
||||
CONSTRAINT `broadcasts_ibfk_1` FOREIGN KEY (`admin_id`) REFERENCES `users` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `cashout_method_types`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cashout_method_types` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`slug` varchar(50) NOT NULL,
|
||||
`label` varchar(100) NOT NULL,
|
||||
`icon` varchar(10) DEFAULT '?',
|
||||
`description` varchar(200) DEFAULT NULL,
|
||||
`is_active` tinyint(1) DEFAULT 1,
|
||||
`sort_order` int(11) DEFAULT 0,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `slug` (`slug`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `cashout_requests`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cashout_requests` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`platform_id` varchar(50) NOT NULL,
|
||||
`alias` varchar(100) NOT NULL,
|
||||
`payout_method_type` varchar(50) DEFAULT NULL,
|
||||
`payout_handle` varchar(200) DEFAULT NULL,
|
||||
`tokens` decimal(10,2) NOT NULL,
|
||||
`status` enum('pending','approved','sent','rejected','deleted') DEFAULT 'pending',
|
||||
`admin_note` text DEFAULT NULL,
|
||||
`sent_note` text DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
`resolved_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
CONSTRAINT `cashout_requests_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `cashout_transactions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cashout_transactions` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`cashout_id` int(11) NOT NULL,
|
||||
`admin_id` int(11) NOT NULL,
|
||||
`payout_method` varchar(50) NOT NULL,
|
||||
`payout_type` enum('manual','square_gift_card') DEFAULT 'manual',
|
||||
`amount_cents` int(11) NOT NULL,
|
||||
`square_txn_id` varchar(200) DEFAULT NULL,
|
||||
`gift_card_gan` varchar(100) DEFAULT NULL,
|
||||
`gift_card_balance` int(11) DEFAULT NULL,
|
||||
`note` text DEFAULT NULL,
|
||||
`status` enum('pending','completed','failed') DEFAULT 'pending',
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `cashout_id` (`cashout_id`),
|
||||
CONSTRAINT `cashout_transactions_ibfk_1` FOREIGN KEY (`cashout_id`) REFERENCES `cashout_requests` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `chat_messages`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `chat_messages` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`sender` enum('user','admin') NOT NULL,
|
||||
`message` text NOT NULL,
|
||||
`is_read` tinyint(1) DEFAULT 0,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
CONSTRAINT `chat_messages_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `game_aliases`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `game_aliases` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`platform_slug` varchar(50) NOT NULL,
|
||||
`alias` varchar(100) NOT NULL,
|
||||
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uq_user_platform` (`user_id`,`platform_slug`),
|
||||
CONSTRAINT `game_aliases_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `payment_settings`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `payment_settings` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`method_key` varchar(50) NOT NULL,
|
||||
`label` varchar(100) NOT NULL,
|
||||
`is_enabled` tinyint(1) DEFAULT 1,
|
||||
`handle` varchar(200) DEFAULT NULL,
|
||||
`instructions` text DEFAULT NULL,
|
||||
`sort_order` int(11) DEFAULT 0,
|
||||
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `method_key` (`method_key`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `payout_methods`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `payout_methods` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`method_type` varchar(50) NOT NULL,
|
||||
`label` varchar(100) NOT NULL,
|
||||
`account_handle` varchar(200) NOT NULL,
|
||||
`is_default` tinyint(1) DEFAULT 0,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
CONSTRAINT `payout_methods_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `pending_registrations`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `pending_registrations` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(50) NOT NULL,
|
||||
`password` varchar(255) NOT NULL,
|
||||
`alias` varchar(100) NOT NULL,
|
||||
`email` varchar(150) NOT NULL,
|
||||
`token` varchar(64) NOT NULL,
|
||||
`expires_at` datetime NOT NULL,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
`referred_by` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `token` (`token`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `platform_accounts`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `platform_accounts` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`platform_slug` varchar(50) NOT NULL,
|
||||
`requested_at` datetime DEFAULT current_timestamp(),
|
||||
`status` enum('pending','approved','denied','deleted') DEFAULT 'pending',
|
||||
`platform_username` varchar(100) DEFAULT NULL,
|
||||
`platform_password` varchar(200) DEFAULT NULL,
|
||||
`admin_note` varchar(300) DEFAULT NULL,
|
||||
`resolved_at` datetime DEFAULT NULL,
|
||||
`admin_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uq_user_platform` (`user_id`,`platform_slug`),
|
||||
CONSTRAINT `platform_accounts_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `platforms`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `platforms` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`slug` varchar(50) NOT NULL,
|
||||
`name` varchar(100) NOT NULL,
|
||||
`player_url` varchar(500) NOT NULL,
|
||||
`console_url` varchar(500) DEFAULT NULL,
|
||||
`color` varchar(20) DEFAULT '#f0c040',
|
||||
`icon_path` varchar(200) DEFAULT NULL,
|
||||
`is_active` tinyint(1) DEFAULT 1,
|
||||
`sort_order` int(11) DEFAULT 0,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `slug` (`slug`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `platform_credits`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `platform_credits` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`platform_id` int(11) NOT NULL,
|
||||
`credits_purchased` decimal(12,2) NOT NULL DEFAULT 0.00,
|
||||
`credit_date` date NOT NULL,
|
||||
`payment_method` varchar(100) DEFAULT NULL,
|
||||
`notes` text DEFAULT NULL,
|
||||
`type` enum('credit','debit') NOT NULL DEFAULT 'credit',
|
||||
`purchase_ref_id` int(11) DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `platform_id` (`platform_id`),
|
||||
KEY `credit_date` (`credit_date`),
|
||||
KEY `idx_pc_type` (`type`),
|
||||
KEY `idx_pc_ref` (`purchase_ref_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `referral_social_shares`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `referral_social_shares` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`platform` varchar(50) NOT NULL,
|
||||
`bonus_tokens` decimal(10,2) DEFAULT 0.00,
|
||||
`share_url` varchar(500) DEFAULT NULL,
|
||||
`auto_verified` tinyint(1) DEFAULT 0,
|
||||
`verify_result` varchar(100) DEFAULT NULL,
|
||||
`status` enum('pending','approved','denied') DEFAULT 'pending',
|
||||
`admin_id` int(11) DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
`resolved_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
CONSTRAINT `referral_social_shares_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `referral_tiers`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `referral_tiers` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) NOT NULL,
|
||||
`min_referrals` int(11) NOT NULL DEFAULT 1,
|
||||
`tokens_per_ref` decimal(10,2) NOT NULL DEFAULT 10.00,
|
||||
`bonus_tokens` decimal(10,2) NOT NULL DEFAULT 0.00,
|
||||
`description` varchar(300) DEFAULT NULL,
|
||||
`is_active` tinyint(1) DEFAULT 1,
|
||||
`sort_order` int(11) DEFAULT 0,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `referrals`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `referrals` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`referrer_id` int(11) NOT NULL,
|
||||
`referred_id` int(11) NOT NULL,
|
||||
`tier_id` int(11) DEFAULT NULL,
|
||||
`status` enum('pending','verified','denied','deleted') DEFAULT 'pending',
|
||||
`tokens_awarded` decimal(10,2) DEFAULT 0.00,
|
||||
`admin_id` int(11) DEFAULT NULL,
|
||||
`admin_note` varchar(300) DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
`resolved_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `referred_id` (`referred_id`),
|
||||
KEY `referrer_id` (`referrer_id`),
|
||||
CONSTRAINT `referrals_ibfk_1` FOREIGN KEY (`referrer_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `referrals_ibfk_2` FOREIGN KEY (`referred_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `saved_billing`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `saved_billing` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`first_name` varchar(80) DEFAULT NULL,
|
||||
`last_name` varchar(80) DEFAULT NULL,
|
||||
`email` varchar(150) DEFAULT NULL,
|
||||
`address` varchar(200) DEFAULT NULL,
|
||||
`city` varchar(80) DEFAULT NULL,
|
||||
`state` varchar(2) DEFAULT NULL,
|
||||
`zip` varchar(10) DEFAULT NULL,
|
||||
`card_brand` varchar(30) DEFAULT NULL,
|
||||
`card_last4` varchar(4) DEFAULT NULL,
|
||||
`card_exp_month` varchar(2) DEFAULT NULL,
|
||||
`card_exp_year` varchar(4) DEFAULT NULL,
|
||||
`sq_card_id` varchar(255) DEFAULT NULL,
|
||||
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `user_id` (`user_id`),
|
||||
CONSTRAINT `saved_billing_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `token_purchases`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `token_purchases` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`tokens` int(11) NOT NULL,
|
||||
`amount_cents` int(11) NOT NULL,
|
||||
`payment_method` varchar(20) DEFAULT 'card',
|
||||
`square_payment_id` varchar(255) DEFAULT NULL,
|
||||
`platform_id` varchar(50) DEFAULT NULL,
|
||||
`game_alias` varchar(100) DEFAULT NULL,
|
||||
`player_name` varchar(100) DEFAULT NULL,
|
||||
`billing_name` varchar(160) DEFAULT NULL,
|
||||
`billing_address` varchar(200) DEFAULT NULL,
|
||||
`billing_city` varchar(80) DEFAULT NULL,
|
||||
`billing_state` varchar(2) DEFAULT NULL,
|
||||
`billing_zip` varchar(10) DEFAULT NULL,
|
||||
`billing_email` varchar(150) DEFAULT NULL,
|
||||
`is_custom` tinyint(1) DEFAULT 0,
|
||||
`failure_reason` text DEFAULT NULL,
|
||||
`card_brand` varchar(30) DEFAULT NULL,
|
||||
`card_last4` varchar(4) DEFAULT NULL,
|
||||
`receipt_url` varchar(512) DEFAULT NULL,
|
||||
`status` enum('pending','completed','failed') DEFAULT 'pending',
|
||||
`admin_note` text DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
CONSTRAINT `token_purchases_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `users` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(50) NOT NULL,
|
||||
`password` varchar(255) NOT NULL,
|
||||
`alias` varchar(100) NOT NULL,
|
||||
`email` varchar(150) DEFAULT NULL,
|
||||
`email_verified` tinyint(1) DEFAULT 0,
|
||||
`tokens` decimal(10,2) DEFAULT 0.00,
|
||||
`is_admin` tinyint(1) DEFAULT 0,
|
||||
`status` enum('active','suspended') DEFAULT 'active',
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
`last_login` datetime DEFAULT NULL,
|
||||
`onboarding_done` tinyint(1) DEFAULT 0,
|
||||
`platform_onboarding_done` tinyint(1) DEFAULT 0,
|
||||
`referral_code` varchar(20) DEFAULT NULL,
|
||||
`referred_by` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`),
|
||||
UNIQUE KEY `email` (`email`),
|
||||
UNIQUE KEY `referral_code` (`referral_code`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
|
After Width: | Height: | Size: 136 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="6" fill="#0a0a12"/>
|
||||
<circle cx="16" cy="16" r="13" fill="#f0c040"/>
|
||||
<text x="16" y="22" text-anchor="middle" font-family="Arial" font-weight="bold" font-size="18" fill="#000">T</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 290 B |
@@ -0,0 +1,232 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large">
|
||||
<title>Buy Fish Table Game Tokens | VBlink777, Fire Kirin, Milky Way | TomTomGames</title>
|
||||
<meta name="description" content="Buy tokens for the best fish table and skill games online — VBlink777, Fire Kirin, Milky Way, Ultra Panda, Panda Master, Noble777, eGame99. Instant delivery, secure payments, 24/7 support. Join TomTomGames today.">
|
||||
<meta name="keywords" content="buy fish table tokens, VBlink777 tokens buy, Fire Kirin game tokens, Milky Way game credits, Ultra Panda tokens, Panda Master game, Noble777 tokens, eGame99 buy, fish table game portal, online skill games tokens, game tokens fast delivery">
|
||||
<link rel="canonical" href="https://tomtomgames.com/games/">
|
||||
|
||||
<meta property="og:title" content="Buy Fish Table Game Tokens | TomTomGames">
|
||||
<meta property="og:description" content="The #1 token portal for VBlink777, Fire Kirin, Milky Way, Ultra Panda, Panda Master, Noble777 & eGame99. Buy tokens in minutes.">
|
||||
<meta property="og:url" content="https://tomtomgames.com/games/">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://tomtomgames.com/assets/img/og-image.png">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "ItemList",
|
||||
"name": "Fish Table & Skill Game Platforms",
|
||||
"description": "Buy tokens for these top fish table and skill game platforms through TomTomGames",
|
||||
"url": "https://tomtomgames.com/games/",
|
||||
"itemListElement": [
|
||||
{"@type":"ListItem","position":1,"name":"VBlink777","description":"Buy VBlink777 tokens instantly. Top-rated fish table game with fast payouts.","url":"https://tomtomgames.com/#vblink777"},
|
||||
{"@type":"ListItem","position":2,"name":"Fire Kirin","description":"Fire Kirin game tokens — one of the most popular fish table games online.","url":"https://tomtomgames.com/#firekirin"},
|
||||
{"@type":"ListItem","position":3,"name":"Milky Way","description":"Milky Way game credits. Secure, instant token delivery for Milky Way 777.","url":"https://tomtomgames.com/#milkyway"},
|
||||
{"@type":"ListItem","position":4,"name":"Ultra Panda","description":"Ultra Panda tokens — buy game credits fast for one of the top skill games.","url":"https://tomtomgames.com/#ultrapanda"},
|
||||
{"@type":"ListItem","position":5,"name":"Panda Master","description":"Panda Master game tokens. Instant delivery, multiple payment methods.","url":"https://tomtomgames.com/#pandamaster"},
|
||||
{"@type":"ListItem","position":6,"name":"Noble 777","description":"Noble777 tokens — buy game credits securely through TomTomGames.","url":"https://tomtomgames.com/#noble777"},
|
||||
{"@type":"ListItem","position":7,"name":"eGame99","description":"eGame99 token purchases. Fast credits, 24/7 support.","url":"https://tomtomgames.com/#egame99"}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:'Segoe UI',Arial,sans-serif;background:#0a0a12;color:#e8e8f0;line-height:1.6}
|
||||
a{color:#f0c040;text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
.wrap{max-width:960px;margin:0 auto;padding:0 20px}
|
||||
header{background:linear-gradient(135deg,#0a0a12,#1a1228);border-bottom:1px solid rgba(240,192,64,.2);padding:16px 0}
|
||||
.logo{font-family:'Georgia',serif;font-size:24px;font-weight:700;color:#f0c040}
|
||||
.logo span{color:#00e5ff}
|
||||
nav{display:flex;gap:20px;align-items:center;margin-top:8px;font-size:14px}
|
||||
.hero{background:linear-gradient(135deg,#1a1228,#0d1a2e);padding:60px 0 50px;text-align:center;border-bottom:1px solid rgba(240,192,64,.15)}
|
||||
h1{font-size:clamp(26px,5vw,42px);font-weight:800;line-height:1.2;margin-bottom:16px;color:#fff}
|
||||
h1 em{color:#f0c040;font-style:normal}
|
||||
.hero p{font-size:18px;color:#aab0c0;max-width:620px;margin:0 auto 28px}
|
||||
.cta-btn{display:inline-block;background:linear-gradient(135deg,#f0c040,#d4a017);color:#000;font-weight:700;font-size:16px;padding:14px 32px;border-radius:8px;transition:transform .15s}
|
||||
.cta-btn:hover{transform:translateY(-2px);text-decoration:none}
|
||||
.trust-bar{display:flex;flex-wrap:wrap;justify-content:center;gap:28px;margin-top:32px;font-size:13px;color:#8888aa}
|
||||
.trust-bar span{display:flex;align-items:center;gap:6px}
|
||||
section{padding:52px 0}
|
||||
h2{font-size:28px;font-weight:700;color:#fff;margin-bottom:8px}
|
||||
.section-sub{color:#8888aa;margin-bottom:32px;font-size:16px}
|
||||
.games-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:20px}
|
||||
.game-card{background:#111827;border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:24px;transition:transform .2s,border-color .2s}
|
||||
.game-card:hover{transform:translateY(-3px);border-color:rgba(240,192,64,.3)}
|
||||
.game-card h3{font-size:18px;color:#fff;margin-bottom:8px}
|
||||
.game-card p{font-size:14px;color:#8888aa;margin-bottom:16px}
|
||||
.game-card .buy-link{font-size:13px;font-weight:700;color:#f0c040;border:1px solid rgba(240,192,64,.3);padding:8px 16px;border-radius:6px;display:inline-block}
|
||||
.game-card .buy-link:hover{background:rgba(240,192,64,.1);text-decoration:none}
|
||||
.color-dot{width:12px;height:12px;border-radius:50%;display:inline-block;margin-right:8px;vertical-align:middle}
|
||||
.steps-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:20px}
|
||||
.step{background:#111827;border-radius:12px;padding:24px;text-align:center}
|
||||
.step-num{width:40px;height:40px;border-radius:50%;background:linear-gradient(135deg,#f0c040,#d4a017);color:#000;font-weight:800;font-size:18px;display:flex;align-items:center;justify-content:center;margin:0 auto 14px}
|
||||
.step h3{font-size:16px;color:#fff;margin-bottom:6px}
|
||||
.step p{font-size:13px;color:#8888aa}
|
||||
.pay-methods{display:flex;flex-wrap:wrap;gap:12px;margin-top:16px}
|
||||
.pay-badge{background:#111827;border:1px solid rgba(255,255,255,.08);border-radius:8px;padding:10px 18px;font-size:14px;font-weight:600;color:#e8e8f0}
|
||||
.faq{max-width:720px}
|
||||
.faq-item{border-bottom:1px solid rgba(255,255,255,.06);padding:20px 0}
|
||||
.faq-item h3{font-size:17px;color:#fff;margin-bottom:8px}
|
||||
.faq-item p{font-size:14px;color:#aab0c0}
|
||||
footer{background:#060608;border-top:1px solid rgba(255,255,255,.06);padding:32px 0;font-size:13px;color:#555;text-align:center}
|
||||
footer a{color:#777}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<div class="wrap" style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px">
|
||||
<a href="https://tomtomgames.com/" class="logo">TomTom<span>Games</span></a>
|
||||
<nav>
|
||||
<a href="https://tomtomgames.com/">🎮 Play Now</a>
|
||||
<a href="https://tomtomgames.com/">Create Account</a>
|
||||
<a href="https://tomtomgames.com/" style="background:rgba(240,192,64,.1);border:1px solid rgba(240,192,64,.3);padding:7px 16px;border-radius:6px;color:#f0c040;font-weight:700">Buy Tokens →</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="hero">
|
||||
<div class="wrap">
|
||||
<h1>Buy <em>Game Tokens</em> for Fish Table & Skill Games</h1>
|
||||
<p>The fastest, most trusted way to load up on tokens for VBlink777, Fire Kirin, Milky Way, Ultra Panda, Panda Master, Noble777, and eGame99.</p>
|
||||
<a href="https://tomtomgames.com/" class="cta-btn">🪙 Buy Tokens Now</a>
|
||||
<div class="trust-bar">
|
||||
<span>🔒 SSL Secured</span>
|
||||
<span>⚡ Instant Delivery</span>
|
||||
<span>💳 Multiple Payment Methods</span>
|
||||
<span>💬 24/7 Support</span>
|
||||
<span>🎮 7 Game Platforms</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style="background:#080812;border-bottom:1px solid rgba(255,255,255,.04)">
|
||||
<div class="wrap">
|
||||
<h2>Supported Game Platforms</h2>
|
||||
<p class="section-sub">Buy tokens for all major fish table and skill game platforms in one place.</p>
|
||||
<div class="games-grid">
|
||||
<div class="game-card">
|
||||
<h3><span class="color-dot" style="background:#FF6B35"></span>VBlink777</h3>
|
||||
<p>One of the most popular fish table games. Buy VBlink777 tokens securely and get them credited fast to your game account.</p>
|
||||
<a href="https://tomtomgames.com/" class="buy-link">Buy VBlink777 Tokens →</a>
|
||||
</div>
|
||||
<div class="game-card">
|
||||
<h3><span class="color-dot" style="background:#E63946"></span>Fire Kirin</h3>
|
||||
<p>Fire Kirin is a top-rated skill game with exciting fish table gameplay. Purchase Fire Kirin tokens through TomTomGames for instant delivery.</p>
|
||||
<a href="https://tomtomgames.com/" class="buy-link">Buy Fire Kirin Tokens →</a>
|
||||
</div>
|
||||
<div class="game-card">
|
||||
<h3><span class="color-dot" style="background:#7B2FBE"></span>Milky Way</h3>
|
||||
<p>Milky Way 777 game credits — buy tokens for one of the best online fish table platforms. Secure payment, fast top-up.</p>
|
||||
<a href="https://tomtomgames.com/" class="buy-link">Buy Milky Way Tokens →</a>
|
||||
</div>
|
||||
<div class="game-card">
|
||||
<h3><span class="color-dot" style="background:#F4A261"></span>Ultra Panda</h3>
|
||||
<p>Ultra Panda game tokens for sale. Top up your account instantly through our secure portal and start playing right away.</p>
|
||||
<a href="https://tomtomgames.com/" class="buy-link">Buy Ultra Panda Tokens →</a>
|
||||
</div>
|
||||
<div class="game-card">
|
||||
<h3><span class="color-dot" style="background:#457B9D"></span>Panda Master</h3>
|
||||
<p>Panda Master tokens — buy game credits quickly and safely. Multiple payment methods accepted including card, Venmo, and Cash App.</p>
|
||||
<a href="https://tomtomgames.com/" class="buy-link">Buy Panda Master Tokens →</a>
|
||||
</div>
|
||||
<div class="game-card">
|
||||
<h3><span class="color-dot" style="background:#FFD700"></span>Noble 777</h3>
|
||||
<p>Noble 777 game token purchases made easy. Register, select your package, and have tokens in your account within minutes.</p>
|
||||
<a href="https://tomtomgames.com/" class="buy-link">Buy Noble 777 Tokens →</a>
|
||||
</div>
|
||||
<div class="game-card">
|
||||
<h3><span class="color-dot" style="background:#2EC4B6"></span>eGame99</h3>
|
||||
<p>eGame99 tokens for sale at the best rates. Fast crediting, 24/7 customer support, and a seamless buying experience.</p>
|
||||
<a href="https://tomtomgames.com/" class="buy-link">Buy eGame99 Tokens →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="wrap">
|
||||
<h2>How to Buy Tokens in 3 Steps</h2>
|
||||
<p class="section-sub">Get tokens credited to your game account in minutes.</p>
|
||||
<div class="steps-grid">
|
||||
<div class="step">
|
||||
<div class="step-num">1</div>
|
||||
<h3>Create Account</h3>
|
||||
<p>Register free in under 60 seconds. Verify your email and log in.</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-num">2</div>
|
||||
<h3>Select Game & Package</h3>
|
||||
<p>Choose your game platform, enter your in-game alias, and pick a token package — or enter a custom amount.</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-num">3</div>
|
||||
<h3>Pay & Play</h3>
|
||||
<p>Pay securely by card or manual transfer. Tokens are credited to your game account fast.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:32px">
|
||||
<h3 style="color:#fff;margin-bottom:12px">Accepted Payment Methods</h3>
|
||||
<div class="pay-methods">
|
||||
<div class="pay-badge">💳 Credit / Debit Card</div>
|
||||
<div class="pay-badge">💙 Venmo</div>
|
||||
<div class="pay-badge">💚 Cash App</div>
|
||||
<div class="pay-badge">🟢 Chime</div>
|
||||
<div class="pay-badge">💜 Zelle</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style="background:#080812;border-top:1px solid rgba(255,255,255,.04)">
|
||||
<div class="wrap">
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
<p class="section-sub">Everything you need to know about buying game tokens.</p>
|
||||
<div class="faq">
|
||||
<div class="faq-item">
|
||||
<h3>What is TomTomGames?</h3>
|
||||
<p>TomTomGames is a token portal that lets you purchase game credits for popular fish table and skill games including VBlink777, Fire Kirin, Milky Way, Ultra Panda, Panda Master, Noble777, and eGame99. We handle the token purchase securely so you can focus on playing.</p>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<h3>How quickly are tokens delivered?</h3>
|
||||
<p>Card payments are processed instantly through Square. Manual payments (Venmo, Zelle, Cash App, Chime) are credited within a few minutes after we confirm receipt of your payment.</p>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<h3>How much do tokens cost?</h3>
|
||||
<p>Tokens are priced at $1 per token. We offer packages starting from 5 tokens ($5) up to 100 tokens ($100), or you can enter a custom amount up to $500. Volume packages are available — contact support for details.</p>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<h3>Is my payment information secure?</h3>
|
||||
<p>Yes. All card transactions are processed through Square, a fully PCI-compliant payment processor. We never store your full card number. Manual payment methods require no card details at all.</p>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<h3>What if I need help?</h3>
|
||||
<p>Our support team is available 24/7 through the live chat feature inside the app. You can also send a message through your account and we'll respond within minutes.</p>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<h3>Can I cash out my tokens?</h3>
|
||||
<p>Yes. You can request a cashout through your account and receive your funds via your preferred payment method. Cashouts are processed by our team promptly.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:36px;text-align:center">
|
||||
<a href="https://tomtomgames.com/" class="cta-btn">Get Started — Create Free Account</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="wrap">
|
||||
<p><strong style="color:#777">TomTomGames</strong> — Your trusted token portal for fish table and skill games.</p>
|
||||
<p style="margin-top:8px"><a href="https://tomtomgames.com/">Home</a> · <a href="https://tomtomgames.com/games/">Games</a> · <a href="https://tomtomgames.com/">Support</a></p>
|
||||
<p style="margin-top:12px">© <?= date('Y') ?> TomTomGames. All rights reserved. Game tokens are for entertainment purposes on supported platforms only. Please play responsibly.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* TomGames — Square Location ID Finder
|
||||
* Upload this file, visit it in your browser ONCE to get your Location ID.
|
||||
* Then paste the ID into includes/config.php and DELETE this file.
|
||||
*/
|
||||
|
||||
$token = 'EAAAl1ECweOVgNiwhC2SuA56QFjlfRLkYxo4xe4r2fMLvqwLT0IKGUZNNOYy1NXn';
|
||||
$locations = [];
|
||||
$error = '';
|
||||
|
||||
$ch = curl_init('https://connect.squareup.com/v2/locations');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Square-Version: 2024-01-18',
|
||||
'Content-Type: application/json',
|
||||
],
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($code === 200) {
|
||||
$data = json_decode($resp, true);
|
||||
$locations = $data['locations'] ?? [];
|
||||
} else {
|
||||
$error = "HTTP $code: " . htmlspecialchars($resp);
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Square Location ID Finder</title>
|
||||
<style>
|
||||
body{font-family:'Segoe UI',sans-serif;background:#0a0a12;color:#e8e8f0;max-width:600px;margin:40px auto;padding:20px}
|
||||
h1{font-size:22px;background:linear-gradient(135deg,#f0c040,#00e5ff);-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:6px}
|
||||
.sub{color:#8888aa;font-size:13px;margin-bottom:28px}
|
||||
.loc{background:#1a1a2e;border:1px solid rgba(255,255,255,.1);border-radius:12px;padding:20px;margin-bottom:16px}
|
||||
.loc-name{font-size:18px;font-weight:700;color:#e8e8f0;margin-bottom:6px}
|
||||
.loc-id-wrap{display:flex;align-items:center;gap:10px;margin:10px 0}
|
||||
.loc-id{font-family:monospace;font-size:18px;font-weight:700;color:#f0c040;background:#0a0a12;border:1px solid rgba(240,192,64,.4);border-radius:8px;padding:10px 16px;flex:1;letter-spacing:1px}
|
||||
.copy-btn{padding:10px 16px;background:linear-gradient(135deg,#f0c040,#d4a017);border:none;border-radius:8px;color:#000;font-weight:700;font-size:13px;cursor:pointer}
|
||||
.copy-btn:hover{opacity:.9}
|
||||
.loc-meta{font-size:12px;color:#8888aa;margin-top:6px}
|
||||
.next{background:rgba(0,229,255,.08);border:1px solid rgba(0,229,255,.2);border-radius:12px;padding:18px;margin-top:24px}
|
||||
.next h2{color:#00e5ff;font-size:15px;margin-bottom:10px}
|
||||
.next ol{padding-left:18px;line-height:2;color:#c0c0d8;font-size:13px}
|
||||
.next code{background:#0a0a12;border:1px solid rgba(255,255,255,.15);border-radius:4px;padding:2px 7px;font-family:monospace;color:#f0c040}
|
||||
.err{background:rgba(255,68,68,.1);border:1px solid rgba(255,68,68,.3);border-radius:10px;padding:16px;color:#ff6666}
|
||||
.warn{background:rgba(255,214,10,.08);border:1px solid rgba(255,214,10,.2);border-radius:8px;padding:12px;margin-top:20px;font-size:12px;color:#ffd60a}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🔑 Square Location Finder</h1>
|
||||
<div class="sub">TomGames — Run once, then delete this file</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="err"><strong>Error fetching locations:</strong><br><?= $error ?></div>
|
||||
<?php elseif (empty($locations)): ?>
|
||||
<div class="err">No locations found on this Square account.</div>
|
||||
<?php else: ?>
|
||||
<p style="color:#8888aa;font-size:13px;margin-bottom:16px">Found <?= count($locations) ?> location(s). Copy the ID for your main location:</p>
|
||||
<?php foreach ($locations as $loc): ?>
|
||||
<div class="loc">
|
||||
<div class="loc-name"><?= htmlspecialchars($loc['name']) ?> <?= $loc['status'] === 'ACTIVE' ? '✅' : '⚠️ ' . $loc['status'] ?></div>
|
||||
<div class="loc-id-wrap">
|
||||
<div class="loc-id" id="id-<?= $loc['id'] ?>"><?= htmlspecialchars($loc['id']) ?></div>
|
||||
<button class="copy-btn" onclick="copyId('<?= $loc['id'] ?>', this)">COPY</button>
|
||||
</div>
|
||||
<div class="loc-meta">
|
||||
<?= htmlspecialchars($loc['address']['address_line_1'] ?? '') ?>
|
||||
<?= htmlspecialchars($loc['address']['city'] ?? '') ?> ·
|
||||
Currency: <?= htmlspecialchars($loc['currency'] ?? 'USD') ?> ·
|
||||
Country: <?= htmlspecialchars($loc['country'] ?? '—') ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="next">
|
||||
<h2>📋 Next Steps</h2>
|
||||
<ol>
|
||||
<li>Copy your Location ID above</li>
|
||||
<li>Open <code>includes/config.php</code></li>
|
||||
<li>Replace <code>YOUR_LOCATION_ID</code> with your copied ID</li>
|
||||
<li>Also update <code>DB_PASS</code> with your MySQL password</li>
|
||||
<li><strong>Delete this file from your server!</strong></li>
|
||||
</ol>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="warn">⚠️ <strong>Security:</strong> Delete <code>get_location.php</code> from your server after use. It exposes your access token.</div>
|
||||
|
||||
<script>
|
||||
function copyId(id, btn) {
|
||||
navigator.clipboard.writeText(id).then(() => {
|
||||
btn.textContent = 'COPIED!';
|
||||
btn.style.background = '#00e676';
|
||||
setTimeout(() => { btn.textContent = 'COPY'; btn.style.background = ''; }, 2000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "TomTomGames — Game Token Portal",
|
||||
"short_name": "TomTomGames",
|
||||
"description": "Buy tokens for VBlink777, Fire Kirin, Milky Way, Ultra Panda, Panda Master, Noble777 and eGame99. Fast, secure, mobile-first.",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#0a0a12",
|
||||
"theme_color": "#f0c040",
|
||||
"orientation": "portrait",
|
||||
"scope": "/",
|
||||
"lang": "en-US",
|
||||
"categories": ["games", "entertainment"],
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/img/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Buy Tokens",
|
||||
"short_name": "Buy",
|
||||
"description": "Buy game tokens instantly",
|
||||
"url": "/?action=buy",
|
||||
"icons": [{ "src": "/assets/img/icon-192.png", "sizes": "192x192" }]
|
||||
},
|
||||
{
|
||||
"name": "Support",
|
||||
"short_name": "Help",
|
||||
"description": "Contact TomTomGames support",
|
||||
"url": "/?action=chat",
|
||||
"icons": [{ "src": "/assets/img/icon-192.png", "sizes": "192x192" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
|
||||
$token = trim($_GET['token'] ?? '');
|
||||
$error = '';
|
||||
$success = false;
|
||||
$pending = null;
|
||||
|
||||
// Look up the reset token (username='__reset__' distinguishes from email-verification rows)
|
||||
if ($token) {
|
||||
$stmt = db()->prepare(
|
||||
"SELECT * FROM pending_registrations WHERE token=? AND username='__reset__' AND expires_at > NOW()"
|
||||
);
|
||||
$stmt->execute([$token]);
|
||||
$pending = $stmt->fetch();
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$token = trim($_POST['token'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$confirm = $_POST['confirm'] ?? '';
|
||||
|
||||
// Re-fetch pending row inside POST to prevent token reuse after expiry
|
||||
$stmt = db()->prepare(
|
||||
"SELECT * FROM pending_registrations WHERE token=? AND username='__reset__' AND expires_at > NOW()"
|
||||
);
|
||||
$stmt->execute([$token]);
|
||||
$pending = $stmt->fetch();
|
||||
|
||||
if (!$pending) {
|
||||
$error = 'This reset link has expired or already been used. Please request a new one.';
|
||||
} elseif (strlen($password) < 6) {
|
||||
$error = 'Password must be at least 6 characters.';
|
||||
} elseif ($password !== $confirm) {
|
||||
$error = 'Passwords do not match.';
|
||||
} else {
|
||||
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 8]);
|
||||
$updated = db()->prepare("UPDATE users SET password=? WHERE email=?")
|
||||
->execute([$hash, $pending['email']]);
|
||||
db()->prepare("DELETE FROM pending_registrations WHERE token=?")->execute([$token]);
|
||||
$success = true;
|
||||
$pending = null;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= SITE_NAME ?> — Reset Password</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Exo+2:wght@400;700;900&family=Rajdhani:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{background:#0a0a12;color:#e8e8f0;font-family:'Rajdhani',sans-serif;min-height:100dvh;display:flex;align-items:center;justify-content:center;padding:24px}
|
||||
body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(rgba(0,229,255,0.03) 1px,transparent 1px),linear-gradient(90deg,rgba(0,229,255,0.03) 1px,transparent 1px);background-size:40px 40px;pointer-events:none}
|
||||
.card{background:#1a1a2e;border:1px solid rgba(255,255,255,0.08);border-radius:20px;padding:40px 32px;max-width:420px;width:100%;text-align:center;position:relative;z-index:1}
|
||||
.icon{font-size:64px;margin-bottom:20px;display:block}
|
||||
.title{font-family:'Exo 2',sans-serif;font-weight:900;font-size:26px;margin-bottom:10px}
|
||||
.title.success{background:linear-gradient(135deg,#f0c040,#00e5ff);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.title.error{color:#ff4444}
|
||||
.msg{font-size:15px;color:#aaaacc;line-height:1.7;margin-bottom:28px}
|
||||
.logo{font-family:'Exo 2',sans-serif;font-weight:900;font-size:20px;background:linear-gradient(135deg,#f0c040,#00e5ff);-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:28px;letter-spacing:1px}
|
||||
.btn{display:block;width:100%;padding:15px;border:none;border-radius:10px;font-family:'Exo 2',sans-serif;font-weight:700;font-size:15px;letter-spacing:1px;cursor:pointer;text-decoration:none;text-align:center;transition:all .2s}
|
||||
.btn-gold{background:linear-gradient(135deg,#f0c040,#d4a017);color:#000;box-shadow:0 4px 20px rgba(240,192,64,.4)}
|
||||
.btn-outline{background:transparent;border:1.5px solid rgba(255,255,255,.2);color:#aaaacc;margin-top:10px}
|
||||
.form-group{text-align:left;margin-bottom:18px}
|
||||
.form-group label{display:block;font-size:13px;color:#8888aa;margin-bottom:6px;letter-spacing:.5px}
|
||||
.form-group input{width:100%;background:#111122;border:1.5px solid rgba(255,255,255,.12);border-radius:10px;padding:13px 16px;color:#e8e8f0;font-family:'Rajdhani',sans-serif;font-size:15px;outline:none;transition:border-color .2s}
|
||||
.form-group input:focus{border-color:#f0c040}
|
||||
.alert-err{background:rgba(255,68,68,.1);border:1px solid rgba(255,68,68,.3);color:#ff9999;padding:12px 16px;border-radius:10px;font-size:14px;margin-bottom:20px;text-align:left}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="logo">🎮 <?= SITE_NAME ?></div>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<span class="icon">✅</span>
|
||||
<div class="title success">Password Updated!</div>
|
||||
<p class="msg">Your password has been reset successfully. You can now log in with your new password.</p>
|
||||
<a href="/" class="btn btn-gold">BACK TO LOGIN</a>
|
||||
|
||||
<?php elseif (!$token || (!$pending && !$error)): ?>
|
||||
<span class="icon">❌</span>
|
||||
<div class="title error">Invalid Link</div>
|
||||
<p class="msg">This password reset link is invalid or has expired.<br>Please request a new one from the app.</p>
|
||||
<a href="/" class="btn btn-gold">BACK TO HOME</a>
|
||||
|
||||
<?php else: ?>
|
||||
<div class="title" style="color:#f0c040;margin-bottom:8px;">Reset Password</div>
|
||||
<p class="msg" style="margin-bottom:24px;">Enter a new password for your account.</p>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert-err"><?= htmlspecialchars($error) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="token" value="<?= htmlspecialchars($token) ?>">
|
||||
<div class="form-group">
|
||||
<label>NEW PASSWORD</label>
|
||||
<input type="password" name="password" placeholder="At least 6 characters" required minlength="6" autofocus>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>CONFIRM PASSWORD</label>
|
||||
<input type="password" name="confirm" placeholder="Repeat new password" required minlength="6">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-gold">SET NEW PASSWORD</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Allow: /assets/
|
||||
Disallow: /admin/
|
||||
Disallow: /api/
|
||||
Disallow: /install.php
|
||||
Disallow: /test.php
|
||||
Disallow: /test_login.php
|
||||
Disallow: /get_location.php
|
||||
Disallow: /?q=
|
||||
|
||||
# Block AI training bots (optional but protects content)
|
||||
User-agent: GPTBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: Google-Extended
|
||||
Disallow: /
|
||||
|
||||
User-agent: CCBot
|
||||
Disallow: /
|
||||
|
||||
# Sitemap
|
||||
Sitemap: https://tomtomgames.com/sitemap.xml
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
# TomTomGames automated backup — runs daily at 2 AM via cron
|
||||
# Cron entry: 0 2 * * * /usr/local/bin/ttg-backup.sh >> /home/tomtomgames.com/backups/backup.log 2>&1
|
||||
|
||||
BACKUP_DIR="/home/tomtomgames.com/backups"
|
||||
SITE_DIR="/home/tomtomgames.com/public_html"
|
||||
DB_NAME="tomt_ttg_db"
|
||||
DB_USER="tomt_ttg_user"
|
||||
DB_PASS='q#q+mrOcozsa7I6J'
|
||||
DATE=$(date +%Y-%m-%d_%H-%M-%S)
|
||||
SQL_FILE="/tmp/ttg_db_${DATE}.sql"
|
||||
ZIP_FILE="${BACKUP_DIR}/ttg_backup_${DATE}.zip"
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting backup..."
|
||||
|
||||
# Export database
|
||||
/usr/bin/mysqldump -u "$DB_USER" "-p${DB_PASS}" "$DB_NAME" > "$SQL_FILE" 2>/dev/null
|
||||
if [ $? -ne 0 ] || [ ! -s "$SQL_FILE" ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Database export failed"
|
||||
rm -f "$SQL_FILE"
|
||||
exit 1
|
||||
fi
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Database exported ($(du -sh "$SQL_FILE" | cut -f1))"
|
||||
|
||||
# Create zip archive (site files + db dump)
|
||||
/usr/bin/zip -r "$ZIP_FILE" "$SITE_DIR" "$SQL_FILE" -x "*/backups/*" > /dev/null 2>&1
|
||||
RC=$?
|
||||
rm -f "$SQL_FILE"
|
||||
|
||||
if [ $RC -ne 0 ] || [ ! -f "$ZIP_FILE" ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Archive creation failed"
|
||||
rm -f "$ZIP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Archive created: $(basename "$ZIP_FILE") ($(du -sh "$ZIP_FILE" | cut -f1))"
|
||||
|
||||
# Keep only the 7 most recent backups
|
||||
ls -t "${BACKUP_DIR}"/ttg_backup_*.zip 2>/dev/null | tail -n +8 | while read old; do
|
||||
rm -f "$old"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Pruned old backup: $(basename "$old")"
|
||||
done
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup complete."
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<!-- Homepage / App -->
|
||||
<url>
|
||||
<loc>https://tomtomgames.com/</loc>
|
||||
<lastmod>2026-05-19</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
|
||||
<!-- SEO Landing Page -->
|
||||
<url>
|
||||
<loc>https://tomtomgames.com/games/</loc>
|
||||
<lastmod>2026-05-19</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
|
||||
</urlset>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
|
||||
$token = $_GET['token'] ?? '';
|
||||
$result = verifyEmailToken($token);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= SITE_NAME ?> — Email Verified</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Exo+2:wght@400;700;900&family=Rajdhani:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{background:#0a0a12;color:#e8e8f0;font-family:'Rajdhani',sans-serif;min-height:100dvh;display:flex;align-items:center;justify-content:center;padding:24px}
|
||||
body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(rgba(0,229,255,0.03) 1px,transparent 1px),linear-gradient(90deg,rgba(0,229,255,0.03) 1px,transparent 1px);background-size:40px 40px;pointer-events:none}
|
||||
.card{background:#1a1a2e;border:1px solid rgba(255,255,255,0.08);border-radius:20px;padding:40px 32px;max-width:420px;width:100%;text-align:center;position:relative;z-index:1}
|
||||
.icon{font-size:64px;margin-bottom:20px;display:block}
|
||||
.title{font-family:'Exo 2',sans-serif;font-weight:900;font-size:26px;margin-bottom:10px}
|
||||
.title.success{background:linear-gradient(135deg,#f0c040,#00e5ff);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.title.error{color:#ff4444}
|
||||
.msg{font-size:15px;color:#aaaacc;line-height:1.7;margin-bottom:28px}
|
||||
.msg strong{color:#e8e8f0}
|
||||
.btn{display:block;width:100%;padding:15px;border:none;border-radius:10px;font-family:'Exo 2',sans-serif;font-weight:700;font-size:15px;letter-spacing:1px;cursor:pointer;text-decoration:none;text-align:center;transition:all .2s}
|
||||
.btn-gold{background:linear-gradient(135deg,#f0c040,#d4a017);color:#000;box-shadow:0 4px 20px rgba(240,192,64,.4)}
|
||||
.btn-outline{background:transparent;border:1.5px solid rgba(255,255,255,.2);color:#aaaacc;margin-top:10px}
|
||||
.logo{font-family:'Exo 2',sans-serif;font-weight:900;font-size:20px;background:linear-gradient(135deg,#f0c040,#00e5ff);-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:28px;letter-spacing:1px}
|
||||
.countdown{font-size:13px;color:#8888aa;margin-top:16px}
|
||||
</style>
|
||||
<?php if ($result['success']): ?>
|
||||
<script>
|
||||
// Auto-redirect to app after 4 seconds
|
||||
setTimeout(() => { window.location.href = '/'; }, 4000);
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="logo" style="display:flex;align-items:center;gap:10px;justify-content:center"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="36" height="36" style="display:inline-block;vertical-align:middle;flex-shrink:0"><defs><linearGradient id="ll1" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#f0c040"/><stop offset="100%" stop-color="#ff6b35"/></linearGradient><linearGradient id="ll2" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#00e5ff"/><stop offset="100%" stop-color="#7b2fbe"/></linearGradient><filter id="gll"><feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><rect x="6" y="16" width="36" height="22" rx="11" fill="url(#ll1)" filter="url(#gll)"/><rect x="12" y="23" width="8" height="3" rx="1.5" fill="rgba(0,0,0,0.45)"/><rect x="15" y="20" width="3" height="8" rx="1.5" fill="rgba(0,0,0,0.45)"/><circle cx="32" cy="22" r="2.2" fill="#e63946" opacity=".9"/><circle cx="36" cy="25" r="2.2" fill="#2ec4b6" opacity=".9"/><circle cx="32" cy="28" r="2.2" fill="#7b2fbe" opacity=".9"/><circle cx="28" cy="25" r="2.2" fill="#f4a261" opacity=".9"/><rect x="21" y="24" width="6" height="3" rx="1.5" fill="rgba(0,0,0,0.3)"/><rect x="8" y="30" width="8" height="7" rx="4" fill="url(#ll2)" opacity=".7"/><rect x="32" y="30" width="8" height="7" rx="4" fill="url(#ll2)" opacity=".7"/><rect x="14" y="13" width="8" height="5" rx="2.5" fill="url(#ll1)" opacity=".8"/><rect x="26" y="13" width="8" height="5" rx="2.5" fill="url(#ll1)" opacity=".8"/><circle cx="24" cy="7" r="2" fill="#f0c040" opacity=".9"/><circle cx="39" cy="10" r="1.2" fill="#00e5ff" opacity=".8"/><circle cx="9" cy="10" r="1.2" fill="#f0c040" opacity=".7"/></svg><span><?= SITE_NAME ?></span></div>
|
||||
|
||||
<?php if ($result['success']): ?>
|
||||
<span class="icon">🎉</span>
|
||||
<div class="title success">Account Verified!</div>
|
||||
<p class="msg">
|
||||
Welcome to <?= SITE_NAME ?>, <strong><?= htmlspecialchars($result['alias']) ?></strong>!<br><br>
|
||||
Your account is active and you've been automatically logged in. Let's play!
|
||||
</p>
|
||||
<a href="/" class="btn btn-gold">🎮 ENTER TOMTOMGAMES</a>
|
||||
<p class="countdown" id="countdown">Redirecting in 4 seconds...</p>
|
||||
<script>
|
||||
let s = 4;
|
||||
const el = document.getElementById('countdown');
|
||||
setInterval(() => { s--; if(s>0) el.textContent = `Redirecting in ${s} second${s!==1?'s':''}...`; else el.textContent = 'Redirecting...'; }, 1000);
|
||||
</script>
|
||||
|
||||
<?php else: ?>
|
||||
<span class="icon">❌</span>
|
||||
<div class="title error">Verification Failed</div>
|
||||
<p class="msg"><?= htmlspecialchars($result['error']) ?></p>
|
||||
<a href="/" class="btn btn-gold">REGISTER AGAIN</a>
|
||||
<a href="/" class="btn btn-outline">BACK TO HOME</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Auto-Deploy Webhook
|
||||
* Verifies GitHub HMAC signature, then queues the repo for git pull.
|
||||
* A root cron job (/usr/local/bin/jarvis-deploy.sh) processes the queue every minute.
|
||||
*
|
||||
* WEBHOOK_SECRET is loaded from api/config.php (gitignored) — never hardcoded here.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../api/config.php';
|
||||
if (!defined('WEBHOOK_SECRET')) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Webhook not configured']);
|
||||
exit;
|
||||
}
|
||||
define('DEPLOY_QUEUE', '/tmp/jarvis-deploy-queue.txt');
|
||||
define('DEPLOY_LOG', '/home/tomtomgames.com/logs/deploy.log');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$payload = file_get_contents('php://input');
|
||||
$sig = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
|
||||
$expected = 'sha256=' . hash_hmac('sha256', $payload, WEBHOOK_SECRET);
|
||||
|
||||
if (!hash_equals($expected, $sig)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Invalid signature']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = json_decode($payload, true);
|
||||
$repo = $data['repository']['name'] ?? '';
|
||||
$ref = $data['ref'] ?? '';
|
||||
$pusher = $data['pusher']['name'] ?? 'unknown';
|
||||
|
||||
// Only deploy on pushes to main
|
||||
if ($ref !== 'refs/heads/main') {
|
||||
echo json_encode(['ok' => true, 'skipped' => "ref $ref is not main"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$repoMap = [
|
||||
'tomtomgames' => '/home/tomtomgames.com/public_html',
|
||||
'tomsjavajive' => '/home/tomsjavajive.com/public_html',
|
||||
'epictravelexpeditions' => '/home/epictravelexpeditions.com/public_html',
|
||||
'parkerslingshotrentals' => '/home/parkerslingshotrentals.com/public_html',
|
||||
'orbishosting' => '/home/orbishosting.com/public_html',
|
||||
'orbis-hosting-portal' => '/home/orbis.orbishosting.com/public_html',
|
||||
];
|
||||
|
||||
if (!isset($repoMap[$repo])) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => "Unknown repo: $repo"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$path = $repoMap[$repo];
|
||||
$ts = date('Y-m-d H:i:s');
|
||||
|
||||
file_put_contents(DEPLOY_QUEUE, $path . "\n", FILE_APPEND | LOCK_EX);
|
||||
file_put_contents(DEPLOY_LOG, "[$ts] Queued deploy: $repo by $pusher -> $path\n", FILE_APPEND | LOCK_EX);
|
||||
|
||||
echo json_encode(['ok' => true, 'queued' => $repo, 'path' => $path]);
|
||||