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']);