true, 'stats' => [ 'total_users' => db()->query("SELECT COUNT(*) FROM users")->fetchColumn(), 'active_users' => db()->query("SELECT COUNT(*) FROM users WHERE status='active'")->fetchColumn(), 'pending_purchases' => db()->query("SELECT COUNT(*) FROM token_purchases WHERE status='pending'")->fetchColumn(), 'pending_cashouts' => db()->query("SELECT COUNT(*) FROM cashout_requests WHERE status='pending'")->fetchColumn(), 'pending_signups' => db()->query("SELECT COUNT(*) FROM pending_registrations WHERE expires_at > NOW() AND username != '__reset__'")->fetchColumn(), 'total_tokens_sold' => db()->query("SELECT COALESCE(SUM(tokens),0) FROM token_purchases WHERE status='completed'")->fetchColumn(), 'total_revenue' => db()->query("SELECT COALESCE(SUM(amount_cents),0)/100 FROM token_purchases WHERE status='completed'")->fetchColumn(), ]]); break; // ─── PENDING SIGNUPS ────────────────────────────────────── case 'pending_signups': $rows = db()->query("SELECT id,username,alias,email,expires_at,created_at FROM pending_registrations WHERE expires_at > NOW() AND username != '__reset__' ORDER BY created_at DESC")->fetchAll(); echo json_encode(['success'=>true,'pending'=>$rows]); break; case 'delete_pending': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $id = (int)($data['id'] ?? 0); db()->prepare("DELETE FROM pending_registrations WHERE id=?")->execute([$id]); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; case 'approve_pending': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $id = (int)($data['id'] ?? 0); // Fetch the pending record $stmt = db()->prepare("SELECT * FROM pending_registrations WHERE id=?"); $stmt->execute([$id]); $pending = $stmt->fetch(); if (!$pending) { echo json_encode(['success'=>false,'error'=>'Pending signup not found']); exit; } // Check username/email not already taken $chkUser = db()->prepare("SELECT id FROM users WHERE username=?"); $chkUser->execute([$pending['username']]); if ($chkUser->fetch()) { echo json_encode(['success'=>false,'error'=>'Username already taken']); exit; } if (!empty($pending['email'])) { $chkEmail = db()->prepare("SELECT id FROM users WHERE email=?"); $chkEmail->execute([$pending['email']]); if ($chkEmail->fetch()) { echo json_encode(['success'=>false,'error'=>'Email already registered']); exit; } } // Create the user account — bypass email verification db()->beginTransaction(); try { db()->prepare("INSERT INTO users (username,password,alias,email,email_verified,tokens,is_admin,status) VALUES (?,?,?,?,1,0,0,'active')") ->execute([$pending['username'],$pending['password'],$pending['alias'],$pending['email']]); db()->prepare("DELETE FROM pending_registrations WHERE id=?")->execute([$id]); db()->commit(); logActivity('account_approved', (int)db()->lastInsertId(), (int)$_SESSION['user_id'], 'user', 0, 'Account approved for '.$pending['username']); echo json_encode(['success'=>true,'username'=>$pending['username']]); } catch (Exception $e) { db()->rollBack(); echo json_encode(['success'=>false,'error'=>'Could not create account']); } break; // ─── PLATFORM STATS ────────────────────────────────────── case 'platform_stats': $rows = db()->query(" SELECT p.id, p.name, p.slug, p.color, COALESCE(SUM(CASE WHEN pc.type='debit' THEN -pc.credits_purchased ELSE pc.credits_purchased END),0) AS credits_balance, (SELECT COUNT(*) FROM token_purchases tp WHERE tp.platform_id=p.slug AND tp.status='completed') AS purchases, (SELECT COUNT(*) FROM cashout_requests cr WHERE cr.platform_id=p.slug AND cr.status IN ('sent','approved')) AS cashouts FROM platforms p LEFT JOIN platform_credits pc ON pc.platform_id=p.id WHERE p.is_deleted=0 AND p.is_active=1 GROUP BY p.id, p.name, p.slug, p.color ORDER BY p.sort_order, p.id ")->fetchAll(); echo json_encode(['success'=>true,'platforms'=>$rows]); break; // ─── PURCHASES ──────────────────────────────────────────── case 'purchases': $status = $_GET['status'] ?? 'pending'; if ($status === 'all') { $stmt = db()->query("SELECT tp.*, u.username, u.alias FROM token_purchases tp JOIN users u ON tp.user_id=u.id ORDER BY tp.created_at DESC LIMIT 200"); } else { $stmt = db()->prepare("SELECT tp.*, u.username, u.alias FROM token_purchases tp JOIN users u ON tp.user_id=u.id WHERE tp.status=? ORDER BY tp.created_at DESC LIMIT 200"); $stmt->execute([$status]); } echo json_encode(['success' => true, 'purchases' => $stmt->fetchAll()]); break; // ─── RESOLVE PURCHASE (approve manual / reject) ────────── case 'resolve_purchase': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $id = (int)($data['id'] ?? 0); $status = $data['status'] ?? ''; $note = trim($data['note'] ?? ''); if (!in_array($status, ['completed','failed'])) { echo json_encode(['success'=>false,'error'=>'Invalid status']); exit; } // Fetch purchase $row = db()->prepare("SELECT * FROM token_purchases WHERE id=? AND status='pending'"); $row->execute([$id]); $purchase = $row->fetch(); if (!$purchase) { echo json_encode(['success'=>false,'error'=>'Purchase not found or already resolved']); exit; } db()->beginTransaction(); try { if ($status === 'completed') { db()->prepare("UPDATE users SET tokens=tokens+? WHERE id=?")->execute([$purchase['tokens'], $purchase['user_id']]); } db()->prepare("UPDATE token_purchases SET status=?,admin_note=? WHERE id=?")->execute([$status, $note, $id]); db()->commit(); } catch (Exception $e) { db()->rollBack(); echo json_encode(['success'=>false,'error'=>'DB error']); exit; } // Insert debit entry into platform_credits when approved if ($status === 'completed' && !empty($purchase['platform_id'])) { $platRow = db()->prepare("SELECT id FROM platforms WHERE slug=?"); $platRow->execute([$purchase['platform_id']]); $platNumId = (int)$platRow->fetchColumn(); if ($platNumId) { $userRow = db()->prepare("SELECT username FROM users WHERE id=?"); $userRow->execute([$purchase['user_id']]); $username = $userRow->fetchColumn() ?: 'User#'.$purchase['user_id']; $amtDollars = number_format($purchase['amount_cents'] / 100, 2); $playerLabel = trim($purchase['player_name'] ?: $purchase['game_alias'] ?: $username); $debitNotes = "Purchase #{$id} · {$playerLabel} ({$username}) · {$purchase['tokens']} tokens · \${$amtDollars} via {$purchase['payment_method']}"; db()->prepare("INSERT INTO platform_credits (platform_id, credits_purchased, credit_date, payment_method, notes, type, purchase_ref_id) VALUES (?,?,CURDATE(),?,?,?,?)") ->execute([$platNumId, $purchase['tokens'], $purchase['payment_method'], $debitNotes, 'debit', $id]); } } echo json_encode(['success'=>true]); break; // ─── CASHOUTS ───────────────────────────────────────────── case 'cashouts': $status = $_GET['status'] ?? 'pending'; $valid = ['pending','sent','approved','rejected','deleted']; if (!in_array($status, $valid)) $status = 'pending'; if ($status === 'pending') { // Show both pending (player editing) and locked (submitted to admin) $stmt = db()->prepare("SELECT cr.*, u.username, u.alias AS user_alias FROM cashout_requests cr JOIN users u ON cr.user_id=u.id WHERE cr.status IN ('pending','locked') ORDER BY cr.status DESC, cr.created_at DESC"); $stmt->execute(); } elseif ($status === 'sent') { $stmt = db()->prepare("SELECT cr.*, u.username, u.alias AS user_alias FROM cashout_requests cr JOIN users u ON cr.user_id=u.id WHERE cr.status IN ('sent','approved') ORDER BY cr.created_at DESC"); $stmt->execute(); } else { $stmt = db()->prepare("SELECT cr.*, u.username, u.alias AS user_alias FROM cashout_requests cr JOIN users u ON cr.user_id=u.id WHERE cr.status=? ORDER BY cr.created_at DESC"); $stmt->execute([$status]); } echo json_encode(['success'=>true,'cashouts'=>$stmt->fetchAll()]); break; case 'resolve_cashout': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $id = (int)($data['id'] ?? 0); $status = $data['status'] ?? ''; $note = trim($data['note'] ?? ''); // 'approved' is treated as 'sent' (payment sent to player) if ($status === 'approved') $status = 'sent'; if (!in_array($status, ['sent','rejected','deleted'])) { echo json_encode(['success'=>false,'error'=>'Invalid status']); exit; } $r = db()->prepare("SELECT user_id,tokens FROM cashout_requests WHERE id=? AND status IN ('pending','locked')"); $r->execute([$id]); $req = $r->fetch(); if (!$req) { echo json_encode(['success'=>false,'error'=>'Not found or already resolved']); exit; } db()->beginTransaction(); try { // Return tokens to player if denied or deleted if (in_array($status, ['rejected','deleted'])) { db()->prepare("UPDATE users SET tokens=tokens+? WHERE id=?")->execute([$req['tokens'],$req['user_id']]); } db()->prepare("UPDATE cashout_requests SET status=?,admin_note=?,resolved_at=NOW() WHERE id=?")->execute([$status,$note,$id]); db()->commit(); logActivity('cashout_'.$status, $req['user_id'], (int)$_SESSION['user_id'], 'cashout', $id, 'Cashout '.$status.' by admin. Tokens: '.$req['tokens'].($note?' Note: '.$note:'')); echo json_encode(['success'=>true,'status'=>$status]); } catch (Exception $e) { db()->rollBack(); echo json_encode(['success'=>false,'error'=>'DB error']); } break; if (!in_array($status, ['approved','rejected'])) { echo json_encode(['success'=>false,'error'=>'Invalid status']); exit; } if ($status === 'rejected') { $r = db()->prepare("SELECT user_id,tokens FROM cashout_requests WHERE id=? AND status='pending'"); $r->execute([$id]); $req = $r->fetch(); if ($req) db()->prepare("UPDATE users SET tokens=tokens+? WHERE id=?")->execute([$req['tokens'],$req['user_id']]); } db()->prepare("UPDATE cashout_requests SET status=?,admin_note=?,resolved_at=NOW() WHERE id=?")->execute([$status,$note,$id]); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; // ─── USERS LIST ─────────────────────────────────────────── case 'users': $users = db()->query("SELECT id,username,alias,email,email_verified,tokens,is_admin,status,created_at,last_login FROM users ORDER BY created_at DESC")->fetchAll(); echo json_encode(['success'=>true,'users'=>$users]); break; // ─── SINGLE USER DETAIL ─────────────────────────────────── case 'user_detail': $uid = (int)($_GET['user_id'] ?? 0); if (!$uid) { echo json_encode(['success'=>false,'error'=>'user_id required']); exit; } $stmt = db()->prepare("SELECT id,username,alias,email,email_verified,tokens,is_admin,status,created_at,last_login FROM users WHERE id=?"); $stmt->execute([$uid]); $user = $stmt->fetch(); if (!$user) { echo json_encode(['success'=>false,'error'=>'User not found']); exit; } // Rich stats $s1 = db()->prepare("SELECT COALESCE(SUM(amount_cents),0)/100 FROM token_purchases WHERE user_id=? AND status='completed'"); $s1->execute([$uid]); $s2 = db()->prepare("SELECT COUNT(*) FROM token_purchases WHERE user_id=? AND status='completed'"); $s2->execute([$uid]); $s3 = db()->prepare("SELECT COUNT(*) FROM token_purchases WHERE user_id=? AND status='pending'"); $s3->execute([$uid]); $s4 = db()->prepare("SELECT COUNT(*) FROM token_purchases WHERE user_id=? AND status='failed'"); $s4->execute([$uid]); $s5 = db()->prepare("SELECT COUNT(*) FROM cashout_requests WHERE user_id=?"); $s5->execute([$uid]); $s6 = db()->prepare("SELECT COALESCE(SUM(tokens),0) FROM token_purchases WHERE user_id=? AND status='completed'"); $s6->execute([$uid]); $stats = [ 'total_spent' => $s1->fetchColumn(), 'completed_purchases'=> $s2->fetchColumn(), 'pending_purchases' => $s3->fetchColumn(), 'failed_purchases' => $s4->fetchColumn(), 'total_cashouts' => $s5->fetchColumn(), 'total_tokens_bought'=> $s6->fetchColumn(), ]; echo json_encode(['success'=>true,'user'=>$user,'stats'=>$stats]); break; // ─── USER PURCHASES ─────────────────────────────────────── case 'user_purchases': $uid = (int)($_GET['user_id'] ?? 0); $stmt = db()->prepare("SELECT 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,failure_reason,card_brand,card_last4,receipt_url,status,admin_note,created_at FROM token_purchases WHERE user_id=? ORDER BY created_at DESC LIMIT 100"); $stmt->execute([$uid]); echo json_encode(['success'=>true,'purchases'=>$stmt->fetchAll()]); break; // ─── USER CASHOUTS ──────────────────────────────────────── case 'user_cashouts': $uid = (int)($_GET['user_id'] ?? 0); $stmt = db()->prepare("SELECT * FROM cashout_requests WHERE user_id=? ORDER BY created_at DESC LIMIT 100"); $stmt->execute([$uid]); echo json_encode(['success'=>true,'cashouts'=>$stmt->fetchAll()]); break; // ─── ADJUST TOKENS ──────────────────────────────────────── case 'adjust_tokens': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $uid = (int)($data['user_id'] ?? 0); $amount = (float)($data['amount'] ?? 0); db()->prepare("UPDATE users SET tokens=tokens+? WHERE id=?")->execute([$amount,$uid]); $bal = db()->prepare("SELECT tokens FROM users WHERE id=?"); $bal->execute([$uid]); $newBal = $bal->fetchColumn(); echo json_encode(['success'=>true,'new_balance'=>$newBal]); break; // ─── SET EXACT TOKEN BALANCE ────────────────────────────── case 'set_tokens': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $uid = (int)($data['user_id'] ?? 0); $bal = (float)($data['balance'] ?? 0); if ($bal < 0) { echo json_encode(['success'=>false,'error'=>'Balance cannot be negative']); exit; } db()->prepare("UPDATE users SET tokens=? WHERE id=?")->execute([$bal,$uid]); echo json_encode(['success'=>true,'new_balance'=>$bal]); break; // ─── EDIT USER ──────────────────────────────────────────── case 'edit_user': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $uid = (int)($data['user_id'] ?? 0); $username = strtolower(trim($data['username'] ?? '')); $alias = trim($data['alias'] ?? ''); $email = strtolower(trim($data['email'] ?? '')); $password = trim($data['password'] ?? ''); if (!$uid || empty($username) || empty($alias)) { echo json_encode(['success'=>false,'error'=>'Username and alias required']); exit; } if (!preg_match('/^[a-z0-9_]{3,50}$/', $username)) { echo json_encode(['success'=>false,'error'=>'Invalid username format']); exit; } if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) { echo json_encode(['success'=>false,'error'=>'Invalid email address']); exit; } // Check username not taken by another user $chk = db()->prepare("SELECT id FROM users WHERE username=? AND id!=?"); $chk->execute([$username,$uid]); if ($chk->fetch()) { echo json_encode(['success'=>false,'error'=>'Username already taken']); exit; } if (!empty($email)) { $chk2 = db()->prepare("SELECT id FROM users WHERE email=? AND id!=?"); $chk2->execute([$email,$uid]); if ($chk2->fetch()) { echo json_encode(['success'=>false,'error'=>'Email already in use']); exit; } } if (!empty($password)) { if (strlen($password) < 6) { echo json_encode(['success'=>false,'error'=>'Password must be 6+ characters']); exit; } $hash = password_hash($password, PASSWORD_BCRYPT); db()->prepare("UPDATE users SET username=?,alias=?,email=?,password=? WHERE id=?")->execute([$username,$alias,$email,$hash,$uid]); } else { db()->prepare("UPDATE users SET username=?,alias=?,email=? WHERE id=?")->execute([$username,$alias,$email,$uid]); } echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; // ─── TOGGLE ADMIN ROLE ─────────────────────────────────── case 'toggle_admin': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $uid = (int)($data['user_id'] ?? 0); // Master admin (ID=1) can NEVER lose admin status if ($uid === MASTER_ADMIN_ID) { echo json_encode(['success'=>false,'error'=>'Master admin cannot be modified.']); exit; } // Cannot remove your own admin if ($uid === (int)$_SESSION['user_id']) { echo json_encode(['success'=>false,'error'=>'You cannot change your own admin status.']); exit; } // Only master admin can grant/revoke admin if ((int)$_SESSION['user_id'] !== MASTER_ADMIN_ID) { echo json_encode(['success'=>false,'error'=>'Only the master admin can change admin roles.']); exit; } $stmt = db()->prepare("SELECT is_admin FROM users WHERE id=?"); $stmt->execute([$uid]); $current = $stmt->fetchColumn(); $new_val = $current ? 0 : 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]); } // 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 ─────────────────────────────────────── case 'toggle_user': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $uid = (int)($data['user_id'] ?? 0); if ($uid === MASTER_ADMIN_ID) { echo json_encode(['success'=>false,'error'=>'Cannot suspend the master admin.']); exit; } logAdminAction('USER_STATUS_CHANGE', $adminId, 'user', isset($userId)?(int)$userId:0, 'Changed user status to: '.($data['status']??'unknown'), '', ($data['status']??''), 'warning'); db()->prepare("UPDATE users SET status=IF(status='active','suspended','active') WHERE id=?")->execute([$uid]); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; // ─── DELETE USER ────────────────────────────────────────── case 'delete_user': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $uid = (int)($data['user_id'] ?? 0); if (!$uid) { echo json_encode(['success'=>false,'error'=>'Invalid user']); exit; } // Prevent deleting own account if ($uid === MASTER_ADMIN_ID) { echo json_encode(['success'=>false,'error'=>'Cannot delete the master admin account.']); exit; } if ($uid === (int)$_SESSION['user_id']) { echo json_encode(['success'=>false,'error'=>'Cannot delete your own account']); exit; } db()->beginTransaction(); try { db()->prepare("DELETE FROM chat_messages WHERE user_id=?")->execute([$uid]); db()->prepare("DELETE FROM cashout_requests WHERE user_id=?")->execute([$uid]); db()->prepare("DELETE FROM token_purchases WHERE user_id=?")->execute([$uid]); db()->prepare("DELETE FROM users WHERE id=?")->execute([$uid]); db()->commit(); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); } catch (Exception $e) { db()->rollBack(); echo json_encode(['success'=>false,'error'=>'Delete failed']); } break; // ─── SEND PASSWORD RESET ────────────────────────────────── case 'send_password_reset': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $uid = (int)($data['user_id'] ?? 0); $stmt = db()->prepare("SELECT email,alias FROM users WHERE id=?"); $stmt->execute([$uid]); $user = $stmt->fetch(); if (!$user || empty($user['email'])) { echo json_encode(['success'=>false,'error'=>'No email on file']); exit; } // Generate reset token — reuse pending_registrations pattern $token = bin2hex(random_bytes(32)); $exp = date('Y-m-d H:i:s', time() + 3600); // 1 hour db()->prepare("INSERT INTO pending_registrations (username,password,alias,email,token,expires_at) VALUES ('__reset__','',?,?,?,?) ON DUPLICATE KEY UPDATE token=VALUES(token),expires_at=VALUES(expires_at)")->execute([$user['alias'],$user['email'],$token,$exp]); // Simple reset email $resetUrl = rtrim(SITE_URL,'/') . '/reset_password.php?token=' . urlencode($token); $subject = SITE_NAME . ' — Password Reset Request'; $body = "Hi {$user['alias']},\n\nA password reset was requested for your account.\n\nClick here to reset: {$resetUrl}\n\nExpires in 1 hour. If you didn't request this, ignore this email.\n\n— " . SITE_NAME; $sent = sendPasswordResetEmail($user['email'], $user['alias'], $resetUrl); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; // ─── PLATFORM ACCOUNTS ──────────────────────────────── case 'platform_accounts_list': $status = $_GET['status'] ?? 'pending'; $uid = (int)($_GET['user_id'] ?? 0); if ($uid) { $stmt = db()->prepare("SELECT pa.*, COALESCE(p.name,pa.platform_slug) AS platform_name, u.username, u.alias AS user_alias FROM platform_accounts pa LEFT JOIN platforms p ON pa.platform_slug=p.slug JOIN users u ON pa.user_id=u.id WHERE pa.user_id=? ORDER BY pa.requested_at DESC"); $stmt->execute([$uid]); } else { $valid = ['pending','approved','denied','deleted']; if (!in_array($status,$valid)) $status='pending'; $stmt = db()->prepare("SELECT pa.*, COALESCE(p.name,pa.platform_slug) AS platform_name, u.username, u.alias AS user_alias FROM platform_accounts pa LEFT JOIN platforms p ON pa.platform_slug=p.slug JOIN users u ON pa.user_id=u.id WHERE pa.status=? ORDER BY pa.requested_at DESC"); $stmt->execute([$status]); } echo json_encode(['success'=>true,'accounts'=>$stmt->fetchAll()]); break; case 'platform_account_resolve': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $d = json_decode(file_get_contents('php://input'), true); $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($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; case 'platform_account_update': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $d=json_decode(file_get_contents('php://input'),true); $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($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; $rows = db()->query(" SELECT b.*, 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 is_admin=0 AND status='active') AS total_players FROM broadcasts b JOIN users u ON b.admin_id=u.id ORDER BY b.sent_at DESC LIMIT 50 ")->fetchAll(); 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); $subject = substr(trim($d['subject']??''),0,200); $message = substr(trim($d['message']??''),0,5000); $target = in_array($d['target']??'',['all','verified','unverified','admins']) ? $d['target'] : 'all'; if (!$subject||!$message) { echo json_encode(['success'=>false,'error'=>'Subject and message required']); exit; } db()->prepare("INSERT INTO broadcasts (admin_id,subject,message,target) VALUES (?,?,?,?)") ->execute([$_SESSION['user_id'],$subject,$message,$target]); $bid = db()->lastInsertId(); // Count recipients $countQ = [ 'all' => "SELECT COUNT(*) FROM users WHERE status='active' AND is_admin=0", 'verified' => "SELECT COUNT(*) FROM users WHERE status='active' AND is_admin=0 AND email_verified=1", 'unverified' => "SELECT COUNT(*) FROM users WHERE status='active' AND is_admin=0 AND email_verified=0", 'admins' => "SELECT COUNT(*) FROM users WHERE is_admin=1", ]; $count = db()->query($countQ[$target])->fetchColumn(); echo json_encode(['success'=>true,'id'=>$bid,'recipient_count'=>(int)$count]); break; case 'broadcast_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); db()->prepare("DELETE FROM broadcasts WHERE id=?")->execute([$id]); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); 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($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); 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"); $rows->execute([$bid]); echo json_encode(['success'=>true,'reads'=>$rows->fetchAll()]); break; case 'broadcast_replies': $bid = (int)($_GET['broadcast_id']??0); $rows = 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"); $rows->execute([$bid]); echo json_encode(['success'=>true,'replies'=>$rows->fetchAll()]); break; case 'broadcast_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'=>'Required fields missing']); exit; } db()->prepare("INSERT INTO broadcast_replies (broadcast_id,user_id,message) VALUES (?,?,?)") ->execute([$bid,$_SESSION['user_id'],$msg]); echo json_encode(['success'=>true,'id'=>db()->lastInsertId()]); break; $rows = db()->query("SELECT * FROM cashout_method_types ORDER BY sort_order ASC, id ASC")->fetchAll(); echo json_encode(['success'=>true,'types'=>$rows]); break; case 'cashout_methods_create': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); 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; case 'cashout_methods_update': 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); $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($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; case 'cashout_methods_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); 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($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; // ── case 'platform_account_deny': 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);$nt=substr(trim($d['admin_note']??''),0,500); db()->prepare("UPDATE platform_accounts SET status='denied',admin_note=?,admin_id=? WHERE id=?")->execute([$nt,$_SESSION['user_id'],$id]); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; case 'platform_account_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); db()->prepare("DELETE FROM platform_accounts WHERE id=?")->execute([$id]); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; case 'platform_accounts_user': $uid=(int)($_GET['user_id']??0); $stmt=db()->prepare("SELECT pa.*,COALESCE(p.name,pa.platform_name,pa.platform_slug) AS display_name,p.color,p.player_url 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]); echo json_encode(['success'=>true,'accounts'=>$stmt->fetchAll()]); break; case 'activity_log': case 'activity_log_v2': $page = max(1, (int)($_GET['page']??1)); $limit = 20; $offset = ($page - 1) * $limit; $category = trim($_GET['category'] ?? ''); $severity = trim($_GET['severity'] ?? ''); $search = trim($_GET['search'] ?? ''); $date = trim($_GET['date'] ?? ''); $where = ["al.created_at >= DATE_SUB(NOW(), INTERVAL 90 DAY)"]; $params = []; if ($category) { $where[] = "al.category = ?"; $params[] = $category; } if ($severity) { $where[] = "al.severity = ?"; $params[] = $severity; } if ($date) { $where[] = "DATE(al.created_at) = ?"; $params[] = $date; } if ($search) { $where[] = "(al.action LIKE ? OR al.detail LIKE ? OR u.username LIKE ? OR u.alias LIKE ? OR al.ip LIKE ?)"; $s = '%'.$search.'%'; $params = array_merge($params, [$s,$s,$s,$s,$s]); } $whereStr = implode(' AND ', $where); $baseQuery = "FROM activity_log al LEFT JOIN users u ON al.user_id = u.id LEFT JOIN users a ON al.admin_id = a.id WHERE $whereStr"; $countStmt = db()->prepare("SELECT COUNT(*) $baseQuery"); $countStmt->execute($params); $total = (int)$countStmt->fetchColumn(); $dataStmt = db()->prepare("SELECT al.*, u.username, u.alias, a.username AS admin_username $baseQuery ORDER BY al.created_at DESC LIMIT $limit OFFSET $offset"); $dataStmt->execute($params); $events = $dataStmt->fetchAll(); // Stats for the current filter set $statsParams = $params; $statsStmt = db()->prepare("SELECT SUM(al.severity='critical') AS critical, SUM(al.severity='warning') AS warning, COUNT(DISTINCT al.ip) AS unique_ips $baseQuery"); $statsStmt->execute($statsParams); $stats = $statsStmt->fetch(); echo json_encode(['success'=>true,'events'=>$events,'total'=>$total,'page'=>$page,'stats'=>$stats]); break; case 'activity_log_csv': $category = trim($_GET['category'] ?? ''); $severity = trim($_GET['severity'] ?? ''); $search = trim($_GET['search'] ?? ''); $date = trim($_GET['date'] ?? ''); $where = ["al.created_at >= DATE_SUB(NOW(), INTERVAL 90 DAY)"]; $params = []; if ($category) { $where[] = "al.category = ?"; $params[] = $category; } if ($severity) { $where[] = "al.severity = ?"; $params[] = $severity; } if ($date) { $where[] = "DATE(al.created_at) = ?"; $params[] = $date; } if ($search) { $where[] = "(al.action LIKE ? OR al.detail LIKE ? OR u.username LIKE ?)"; $s = '%'.$search.'%'; $params = array_merge($params, [$s,$s,$s]); } $whereStr = implode(' AND ', $where); $stmt = db()->prepare("SELECT al.*, u.username, u.alias, a.username AS admin_username FROM activity_log al LEFT JOIN users u ON al.user_id=u.id LEFT JOIN users a ON al.admin_id=a.id WHERE $whereStr ORDER BY al.created_at DESC LIMIT 5000"); $stmt->execute($params); $rows = $stmt->fetchAll(); header('Content-Type: text/csv'); header('Content-Disposition: attachment; filename="tomtomgames_audit_' . date('Y-m-d') . '.csv"'); $out = fopen('php://output', 'w'); fputcsv($out, ['ID','Timestamp','Category','Severity','Action','Username','Alias','Admin','Detail','Old Value','New Value','IP','User Agent','Page','Session ID']); foreach ($rows as $r) { fputcsv($out, [$r['id'],$r['created_at'],$r['category'],$r['severity'],$r['action'], $r['username']??'',$r['alias']??'',$r['admin_username']??'', $r['detail']??'',$r['old_value']??'',$r['new_value']??'', $r['ip']??'',$r['user_agent']??'',$r['page']??'',$r['session_id']??'']); } fclose($out); exit; break; // ─── CASHOUT METHODS: list (admin) ──────────────────── case 'cashout_methods_list': $rows = db()->query("SELECT * FROM cashout_method_types ORDER BY sort_order ASC, id ASC")->fetchAll(); echo json_encode(['success'=>true,'types'=>$rows]); break; // ─── PAYMENT SETTINGS: list (admin) ─────────────────── case 'payment_settings_list': $rows = db()->query("SELECT * FROM payment_settings ORDER BY sort_order ASC, id ASC")->fetchAll(); echo json_encode(['success'=>true,'methods'=>$rows]); break; case 'payment_settings_update': 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); $label= substr(trim($d['label']??''), 0, 100); $handle = substr(trim($d['handle']??''), 0, 200); $inst = 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,$inst,$enabled,$sort,$id]); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; // ─── PAYOUT METHODS: get for user ──────────────────────── case 'payout_methods_get': $uid = (int)($_GET['user_id'] ?? 0); if (!$uid) { echo json_encode(['success'=>false,'error'=>'user_id required']); exit; } $rows = db()->prepare("SELECT * FROM payout_methods WHERE user_id=? ORDER BY is_default DESC, id ASC"); $rows->execute([$uid]); echo json_encode(['success'=>true,'methods'=>$rows->fetchAll()]); break; // ─── GAME ALIASES: get ──────────────────────────────── case 'game_aliases_get': $uid = (int)($_GET['user_id'] ?? 0); if (!$uid) { echo json_encode(['success'=>false,'error'=>'user_id required']); exit; } $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; // ─── GAME ALIASES: save all ─────────────────────────── case 'game_aliases_save_all': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $uid = (int)($data['user_id'] ?? 0); $aliases = $data['aliases'] ?? []; if (!$uid) { echo json_encode(['success'=>false,'error'=>'user_id required']); exit; } $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($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; // ─── PLATFORMS: admin list (active + inactive, no archived) ── case 'platforms_admin': $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; // ─── PLATFORMS: archived list ───────────────────────── case 'platforms_archived': $rows = db()->query("SELECT * FROM platforms WHERE is_deleted=1 ORDER BY deleted_at DESC")->fetchAll(); echo json_encode(['success'=>true,'platforms'=>$rows]); break; // ─── PLATFORMS: restore archived ────────────────────── case 'platforms_restore': 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'=>'ID required']); exit; } db()->prepare("UPDATE platforms SET is_deleted=0, deleted_at=NULL, updated_at=NOW() WHERE id=?")->execute([$id]); echo json_encode(['success'=>true]); break; // ─── PLATFORMS: permanent delete (archived only) ────── case 'platforms_purge': 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'=>'ID required']); exit; } db()->prepare("DELETE FROM platforms WHERE id=? AND is_deleted=1")->execute([$id]); echo json_encode(['success'=>true]); break; // ─── PLATFORMS: create ──────────────────────────────── case 'platforms_create': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } if ((int)($_SESSION['user_id'] ?? 0) !== MASTER_ADMIN_ID) { echo json_encode(['success'=>false,'error'=>'Only master admin can add games']); exit; } $d = json_decode(file_get_contents('php://input'), true); $isMasterAdmin = true; $slug = preg_replace('/[^a-z0-9_]/', '', strtolower(trim($d['slug'] ?? ''))); $name = substr(trim($d['name'] ?? ''), 0, 100); $purl = substr(trim($d['player_url'] ?? ''), 0, 500); $color = preg_match('/^#[0-9a-fA-F]{3,8}$/', $d['color']??'') ? $d['color'] : '#f0c040'; $sort = (int)($d['sort_order'] ?? 99); $active = (int)(bool)($d['is_active'] ?? 1); $agent_link = $isMasterAdmin ? substr(trim($d['agent_link'] ?? ''), 0, 500) : ''; $agent_login = $isMasterAdmin ? substr(trim($d['agent_login'] ?? ''), 0, 200) : ''; $agent_password = $isMasterAdmin ? substr(trim($d['agent_password'] ?? ''), 0, 200) : ''; $games_link = $isMasterAdmin ? substr(trim($d['games_link'] ?? ''), 0, 500) : ''; $agent_guide = $isMasterAdmin ? trim($d['agent_guide'] ?? '') : ''; $sub_agent_login = $isMasterAdmin ? substr(trim($d['sub_agent_login'] ?? ''), 0, 200) : ''; $sub_agent_password = $isMasterAdmin ? substr(trim($d['sub_agent_password'] ?? ''), 0, 200) : ''; $cashier_login = $isMasterAdmin ? substr(trim($d['cashier_login'] ?? ''), 0, 200) : ''; $cashier_password = $isMasterAdmin ? substr(trim($d['cashier_password'] ?? ''), 0, 200) : ''; if (!$slug||!$name||!$purl) { echo json_encode(['success'=>false,'error'=>'Slug, name, and player URL required']); exit; } // Check if slug belongs to an archived platform — reactivate it instead of inserting $existing = db()->prepare("SELECT id FROM platforms WHERE slug=? AND is_deleted=1 LIMIT 1"); $existing->execute([$slug]); $archivedId = $existing->fetchColumn(); if ($archivedId) { db()->prepare("UPDATE platforms SET name=?,player_url=?,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=?,is_deleted=0,deleted_at=NULL,updated_at=NOW() WHERE id=?") ->execute([$name,$purl,$agent_link,$agent_login,$agent_password,$games_link,$agent_guide,$sub_agent_login,$sub_agent_password,$cashier_login,$cashier_password,$color,$sort,$active,$archivedId]); echo json_encode(['success'=>true,'id'=>$archivedId,'restored'=>true]); } else { try { db()->prepare("INSERT INTO platforms (slug,name,player_url,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 (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)") ->execute([$slug,$name,$purl,$agent_link,$agent_login,$agent_password,$games_link,$agent_guide,$sub_agent_login,$sub_agent_password,$cashier_login,$cashier_password,$color,$sort,$active]); echo json_encode(['success'=>true,'id'=>db()->lastInsertId()]); } catch (Exception $e) { echo json_encode(['success'=>false,'error'=>'Slug already in use by an active game']); } } break; // ─── PLATFORMS: update ──────────────────────────────── case 'platforms_update': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $d = json_decode(file_get_contents('php://input'), true); $isMasterAdmin = (int)($_SESSION['user_id'] ?? 0) === MASTER_ADMIN_ID; $id = (int)($d['id'] ?? 0); $name = substr(trim($d['name'] ?? ''), 0, 100); $purl = substr(trim($d['player_url'] ?? ''), 0, 500); $color = preg_match('/^#[0-9a-fA-F]{3,8}$/', $d['color']??'') ? $d['color'] : '#f0c040'; $sort = (int)($d['sort_order'] ?? 99); $active = (int)(bool)($d['is_active'] ?? 1); if (!$id||!$name||!$purl) { echo json_encode(['success'=>false,'error'=>'ID, name, and URL required']); exit; } if ($isMasterAdmin) { $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); db()->prepare("UPDATE platforms SET name=?,player_url=?,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=?,updated_at=NOW() WHERE id=?") ->execute([$name,$purl,$agent_link,$agent_login,$agent_password,$games_link,$agent_guide,$sub_agent_login,$sub_agent_password,$cashier_login,$cashier_password,$color,$sort,$active,$id]); } else { db()->prepare("UPDATE platforms SET name=?,player_url=?,color=?,sort_order=?,is_active=?,updated_at=NOW() WHERE id=?") ->execute([$name,$purl,$color,$sort,$active,$id]); } echo json_encode(['success'=>true]); break; // ─── PLATFORMS: soft-delete (archive) ──────────────── case 'platforms_delete': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } if ((int)($_SESSION['user_id'] ?? 0) !== MASTER_ADMIN_ID) { echo json_encode(['success'=>false,'error'=>'Only master admin can archive games']); 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("UPDATE platforms SET is_deleted=1, deleted_at=NOW(), updated_at=NOW() WHERE id=?")->execute([$id]); echo json_encode(['success'=>true]); break; case 'billing_get': $uid = (int)($_GET['user_id'] ?? 0); if (!$uid) { echo json_encode(['success'=>false,'error'=>'user_id required']); exit; } $stmt = db()->prepare("SELECT * FROM saved_billing WHERE user_id=?"); $stmt->execute([$uid]); $row = $stmt->fetch(); // Admin sees masked card info if ($row) { $row['card_display'] = $row['card_brand'] && $row['card_last4'] ? $row['card_brand'] . ' ····' . $row['card_last4'] : null; // Don't expose raw sq_card_id unset($row['sq_card_id']); } echo json_encode(['success'=>true,'billing'=>$row ?: null]); break; // ─── BILLING: save/update ──────────────────────────────── case 'billing_save': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $uid = (int)($data['user_id'] ?? 0); if (!$uid) { echo json_encode(['success'=>false,'error'=>'user_id required']); exit; } $stmt = db()->prepare(" INSERT INTO saved_billing (user_id,first_name,last_name,email,address,city,state,zip) 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) "); $stmt->execute([ $uid, substr(trim($data['first_name']??''),0,80), substr(trim($data['last_name'] ??''),0,80), substr(strtolower(trim($data['email']??'')),0,150), substr(trim($data['address'] ??''),0,200), substr(trim($data['city'] ??''),0,80), strtoupper(substr(trim($data['state']??''),0,2)), substr(trim($data['zip'] ??''),0,10), ]); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; // ─── BILLING: clear card ───────────────────────────────── case 'billing_clear_card': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $uid = (int)($data['user_id'] ?? 0); 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($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; // ─── BILLING: clear all ────────────────────────────────── case 'billing_clear_all': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $uid = (int)($data['user_id'] ?? 0); db()->prepare("DELETE FROM saved_billing WHERE user_id=?")->execute([$uid]); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; // ─── RESEND VERIFICATION (from admin) ───────────────────── case 'resend_verification': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $data = json_decode(file_get_contents('php://input'), true); $uid = (int)($data['user_id'] ?? 0); $stmt = db()->prepare("SELECT email,alias FROM users WHERE id=?"); $stmt->execute([$uid]); $user = $stmt->fetch(); if (!$user) { echo json_encode(['success'=>false,'error'=>'User not found']); exit; } $result = resendVerification($user['email']); echo json_encode($result); break; // ─── CHAT: inbox list ────────────────────────────────── case 'chat_inbox': $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, (SELECT COUNT(*) FROM chat_messages WHERE user_id=u.id AND sender='user' AND is_read=0) AS unread_count FROM users u INNER JOIN chat_messages cm ON cm.id=( SELECT id FROM chat_messages WHERE user_id=u.id ORDER BY id DESC LIMIT 1 ) ORDER BY cm.created_at DESC ")->fetchAll(); echo json_encode(['success'=>true,'inbox'=>$rows]); break; // ─── CHAT: full thread for one user ─────────────────── case 'chat_thread': $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 300"); $stmt->execute([$tid, $since]); // Mark user messages read db()->prepare("UPDATE chat_messages SET is_read=1 WHERE user_id=? AND sender='user' AND is_read=0")->execute([$tid]); $uStmt = db()->prepare("SELECT id,username,alias,tokens FROM users WHERE id=?"); $uStmt->execute([$tid]); echo json_encode(['success'=>true,'messages'=>$stmt->fetchAll(),'user'=>$uStmt->fetch()]); break; // ─── CHAT: admin reply ──────────────────────────────── case 'chat_admin_send': 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)) { 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; // ─── CHAT: clear single user thread ─────────────────── case 'chat_clear_thread': 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); if (!$tid) { echo json_encode(['success'=>false,'error'=>'user_id required']); exit; } db()->prepare("DELETE FROM chat_messages WHERE user_id=?")->execute([$tid]); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; // ─── CHAT: clear ALL chats ──────────────────────────── case 'chat_clear_all': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } db()->exec("DELETE FROM chat_messages"); echo json_encode($sent ? ['success'=>true] : ['success'=>false,'error'=>'Failed to send reset email. Please try again.']); break; case 'chat_unread': $count = db()->query("SELECT COUNT(*) FROM chat_messages WHERE sender='user' AND is_read=0")->fetchColumn(); echo json_encode(['success'=>true,'count'=>(int)$count]); break; default: echo json_encode(['success'=>false,'error'=>'Unknown action']); }