v1.0.4 - Clean start

This commit is contained in:
2026-05-15 16:46:08 -05:00
parent 28d2f8102d
commit 500a0219b8
17 changed files with 846 additions and 934 deletions
+75 -17
View File
@@ -1,37 +1,103 @@
Options -Indexes
# ══════════════════════════════════════════════════════════
# TomTomGames Security Configuration
# ══════════════════════════════════════════════════════════
Options -Indexes -Includes
ServerSignature Off
# ── Block sensitive files ────────────────────────────────
<FilesMatch "\.(sql|env|log|sh|md|git)$">
# ── 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 includes ──────────────────────
# ── 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]
</IfModule>
# ── Security headers ──────────────────────────────────────
# ── 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"
Header always set X-Frame-Options "SAMEORIGIN"
# 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"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
# 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 redirect ──────────────────────────────
# ── Canonical HTTPS + non-www redirect ───────────────────
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# Remove www (pick one: www or non-www, use non-www)
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
@@ -49,11 +115,3 @@ ServerSignature Off
ExpiresByType image/webp "access plus 1 month"
ExpiresByType application/json "access plus 1 day"
</IfModule>
# ── LiteSpeed cache rules ─────────────────────────────────
<IfModule LiteSpeed>
CacheEnable public /assets/
CacheEnable public /manifest.json
CacheEnable public /sitemap.xml
CacheEnable public /robots.txt
</IfModule>
File diff suppressed because it is too large Load Diff
+59 -3
View File
@@ -310,14 +310,16 @@ switch ($action) {
$stmt->execute([$uid]);
$current = $stmt->fetchColumn();
$new_val = $current ? 0 : 1;
// If granting admin, also set email_verified=1
if ($new_val) {
db()->prepare("UPDATE users SET is_admin=1, email_verified=1 WHERE id=?")->execute([$uid]);
} else {
db()->prepare("UPDATE users SET is_admin=0 WHERE id=?")->execute([$uid]);
}
logActivity($new_val?'admin_granted':'admin_revoked', $uid, (int)$_SESSION['user_id'], 'user', $uid, 'Admin status changed to '.($new_val?'admin':'player'));
echo json_encode(['success'=>true, 'is_admin'=>$new_val]);
// Force the affected user to re-login by invalidating their sessions
// Store a flag in DB that forces re-auth on next request
db()->prepare("UPDATE users SET last_login=last_login WHERE id=?")->execute([$uid]);
logActivity($new_val?'admin_granted':'admin_revoked', $uid, (int)$_SESSION['user_id'], 'user', $uid, 'Admin status changed to '.($new_val?'admin':'player'), '', 'admin', '', '', 'warning');
echo json_encode(['success'=>true, 'is_admin'=>$new_val, 'needs_relogin'=>true, 'message'=>$new_val ? 'Admin access granted. User must log out and back in.' : 'Admin access removed. User must log out and back in.']);
break;
// ─── TOGGLE SUSPEND ───────────────────────────────────────
@@ -439,6 +441,24 @@ switch ($action) {
echo json_encode(['success'=>true,'broadcasts'=>$rows]);
break;
case 'broadcast_list':
try {
$sql = "SELECT b.id, b.subject, b.message, b.target, b.sent_at,
u.username AS sender_name,
(SELECT COUNT(*) FROM broadcast_reads WHERE broadcast_id=b.id) AS read_count,
(SELECT COUNT(*) FROM broadcast_replies WHERE broadcast_id=b.id) AS reply_count,
(SELECT COUNT(*) FROM users WHERE status='active' AND is_admin=0) AS total_players
FROM broadcasts b
JOIN users u ON b.admin_id=u.id
ORDER BY b.sent_at DESC
LIMIT 100";
$stmt = db()->query($sql);
echo json_encode(['success'=>true,'broadcasts'=>$stmt->fetchAll()]);
} catch(Exception $e) {
echo json_encode(['success'=>false,'error'=>$e->getMessage()]);
}
break;
case 'broadcast_send':
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; }
$d = json_decode(file_get_contents('php://input'), true);
@@ -468,6 +488,42 @@ switch ($action) {
echo json_encode(['success'=>true]);
break;
case 'broadcast_edit':
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);
$subject = substr(trim($d['subject'] ?? ''), 0, 200);
$message = trim($d['message'] ?? '');
$target = in_array($d['target']??'', ['all','verified','unverified','admins']) ? $d['target'] : 'all';
if (!$id || !$subject || !$message) { echo json_encode(['success'=>false,'error'=>'Missing fields']); exit; }
db()->prepare("UPDATE broadcasts SET subject=?, message=?, target=? WHERE id=?")->execute([$subject, $message, $target, $id]);
logAdminAction('BROADCAST_EDITED', $adminId, 'broadcast', $id, 'Edited broadcast #'.$id, '', '', 'info');
echo json_encode(['success'=>true]);
break;
case 'broadcast_resend':
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);
if (!$id) { echo json_encode(['success'=>false,'error'=>'Missing ID']); exit; }
$bc = db()->prepare("SELECT * FROM broadcasts WHERE id=?");
$bc->execute([$id]);
$orig = $bc->fetch();
if (!$orig) { echo json_encode(['success'=>false,'error'=>'Broadcast not found']); exit; }
// Count recipients
$target = $orig['target'];
$countSql = "SELECT COUNT(*) FROM users WHERE status='active' AND is_admin=0";
if ($target === 'verified') $countSql .= " AND email_verified=1";
if ($target === 'unverified') $countSql .= " AND email_verified=0";
$recipientCount = (int)db()->query($countSql)->fetchColumn();
// Delete old reads so everyone sees it again
db()->prepare("DELETE FROM broadcast_reads WHERE broadcast_id=?")->execute([$id]);
// Update sent_at to now
db()->prepare("UPDATE broadcasts SET sent_at=NOW() WHERE id=?")->execute([$id]);
logAdminAction('BROADCAST_RESENT', $adminId, 'broadcast', $id, 'Resent broadcast #'.$id.' to '.$recipientCount.' players', '', '', 'info');
echo json_encode(['success'=>true,'recipient_count'=>$recipientCount]);
break;
case 'broadcast_reads':
$bid = (int)($_GET['broadcast_id']??0);
$rows = db()->prepare("SELECT br.read_at, u.username, u.alias FROM broadcast_reads br JOIN users u ON br.user_id=u.id WHERE br.broadcast_id=? ORDER BY br.read_at ASC");
+4 -1
View File
@@ -1,5 +1,5 @@
<?php
ob_start(); // Buffer any accidental output (PHP errors, notices, etc.)
ob_start();
try {
require_once __DIR__ . '/../../includes/auth.php';
} catch (Throwable $e) {
@@ -28,5 +28,8 @@ if (!$user) {
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]);
+9 -2
View File
@@ -20,6 +20,13 @@ if ($method === 'GET') {
$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]);
@@ -175,7 +182,7 @@ if ($action === 'resolve_referral') {
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');
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) {
@@ -199,7 +206,7 @@ if ($action === 'resolve_share') {
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');
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;
Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

+5
View File
@@ -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

-108
View File
@@ -1,108 +0,0 @@
<?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>
+274 -229
View File
File diff suppressed because it is too large Load Diff
-25
View File
@@ -1,25 +0,0 @@
<?php
// Admin only diagnostic - delete after use
$files = [
'../../includes/db.php',
'../../includes/auth.php',
'../api/admin.php',
];
foreach ($files as $f) {
$full = __DIR__ . '/' . $f;
$out = shell_exec("php -l " . escapeshellarg($full) . " 2>&1");
echo $f . ": " . trim($out) . "\n";
}
// Also test DB connection directly
try {
require_once __DIR__ . '/../../includes/config.php';
require_once __DIR__ . '/../../includes/db.php';
$v = db()->query("SELECT COUNT(*) FROM users")->fetchColumn();
echo "\nDB OK — users: $v\n";
$v2 = db()->query("SELECT version FROM app_version ORDER BY id DESC LIMIT 1")->fetchColumn();
echo "App version: $v2\n";
} catch (Throwable $e) {
echo "\nDB ERROR: " . $e->getMessage() . "\n";
echo "File: " . $e->getFile() . " line " . $e->getLine() . "\n";
}
-6
View File
@@ -1,6 +0,0 @@
<?php
echo json_encode([
'php' => PHP_VERSION,
'time' => date('Y-m-d H:i:s'),
'status' => 'ok'
]);
-18
View File
@@ -1,18 +0,0 @@
<?php
// POST test - simulates exactly what the app does
ob_start();
try { require_once __DIR__ . '/../includes/auth.php'; } catch(Throwable $e) { ob_end_clean(); die(json_encode(['boot_error'=>$e->getMessage()])); }
ob_end_clean();
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
$result = loginUser($data['username'] ?? '', $data['password'] ?? '');
if ($result['success']) unset($result['user']['password']);
echo json_encode($result);
exit;
}
// GET - show users
$users = db()->query("SELECT id,username,alias,is_admin,email_verified,status FROM users")->fetchAll();
echo json_encode(['users'=>$users,'session_id'=>session_id()]);
-91
View File
@@ -1,91 +0,0 @@
<?php
// Standalone email test - no auth required for diagnosis
// DELETE THIS FILE after confirming email works
$result = [];
$result['php_version'] = PHP_VERSION;
$result['mail_function_exists'] = function_exists('mail');
$result['server_software'] = $_SERVER['SERVER_SOFTWARE'] ?? 'unknown';
$result['hostname'] = gethostname();
// Check sendmail path
$sendmail = ini_get('sendmail_path');
$result['sendmail_path'] = $sendmail ?: 'not set';
// Check if we can detect SMTP settings
$result['smtp_host'] = ini_get('SMTP') ?: 'not set';
$result['smtp_port'] = ini_get('smtp_port') ?: 'not set';
$to = $_POST['to'] ?? '';
$sent = false;
$sendError = '';
if ($to && filter_var($to, FILTER_VALIDATE_EMAIL) && isset($_POST['send'])) {
$subject = 'TomTomGames Email Test';
$message = "This is a test email from TomTomGames.\n\nIf you received this, PHP mail() is working correctly on this server.";
$headers = "From: noreply@tomtomgames.com\r\n";
$headers .= "Reply-To: support@tomtomgames.com\r\n";
$headers .= "X-Mailer: PHP/" . PHP_VERSION;
// Capture any mail errors
set_error_handler(function($errno, $errstr) use (&$sendError) {
$sendError = $errstr;
});
$sent = mail($to, $subject, $message, $headers, '-fnoreply@tomtomgames.com');
restore_error_handler();
$result['mail_return'] = $sent;
$result['mail_error'] = $sendError ?: 'none';
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Email Test</title>
<style>
body{font-family:monospace;background:#0a0a12;color:#e8e8f0;padding:24px;max-width:600px;margin:0 auto}
h2{color:#f0c040}
.box{background:#1a1a2e;border:1px solid #333;border-radius:8px;padding:16px;margin:12px 0}
.ok{color:#00e676}.err{color:#ff4444}.warn{color:#f0c040}
input{background:#111;border:1px solid #444;color:#fff;padding:8px 12px;width:280px;border-radius:6px;font-size:14px}
button{background:#f0c040;color:#000;border:none;padding:10px 20px;border-radius:6px;font-weight:700;cursor:pointer;margin-left:8px}
label{display:block;margin-bottom:6px;color:#aaa;font-size:13px}
</style>
</head>
<body>
<h2>TomTomGames Email Diagnostics</h2>
<div class="box">
<b>Server Info:</b><br>
PHP: <span class="ok"><?= $result['php_version'] ?></span><br>
Server: <?= htmlspecialchars($result['server_software']) ?><br>
Hostname: <?= htmlspecialchars($result['hostname']) ?><br>
mail() function: <span class="<?= $result['mail_function_exists'] ? 'ok' : 'err' ?>"><?= $result['mail_function_exists'] ? 'EXISTS' : 'MISSING' ?></span><br>
sendmail_path: <span class="warn"><?= htmlspecialchars($result['sendmail_path']) ?></span><br>
SMTP: <span class="warn"><?= htmlspecialchars($result['smtp_host']) ?>:<?= htmlspecialchars($result['smtp_port']) ?></span>
</div>
<?php if ($to): ?>
<div class="box">
<b>Send Result:</b><br>
To: <?= htmlspecialchars($to) ?><br>
mail() returned: <span class="<?= $sent ? 'ok' : 'err' ?>"><?= $sent ? 'TRUE (queued for delivery)' : 'FALSE (failed)' ?></span><br>
Error: <span class="warn"><?= htmlspecialchars($result['mail_error']) ?></span><br>
<?php if ($sent): ?>
<br><span class="ok">Check your inbox (and spam folder). If nothing arrives in 5 minutes, the server is not sending outbound mail.</span>
<?php else: ?>
<br><span class="err">mail() returned false server cannot send email. You need to configure SMTP (PHPMailer) or enable sendmail.</span>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="box">
<form method="POST">
<label>Send test email to:</label>
<input type="email" name="to" value="<?= htmlspecialchars($to) ?>" placeholder="your@email.com" required>
<button type="submit" name="send" value="1">Send Test</button>
</form>
</div>
<p style="color:#555;font-size:11px">Delete test_mail.php after diagnosis is complete.</p>
</body>
</html>