0) { try { $panelPdo = getPanelDBConnection(); $panelPdo->beginTransaction(); $panelPdo->prepare("DELETE FROM point_transactions WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM user_points WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM redemption_requests WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM support_tickets WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM support_messages WHERE sender_type = 'user' AND sender_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM user_profiler WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM profiler_completion WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM mobile_verifications WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM email_verifications WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM users WHERE id = ?")->execute([$member_id]); $panelPdo->commit(); logActivity($_SESSION['admin_id'], 'delete_member', "Deleted member #$member_id", 'member', $member_id); $success_message = 'Member deleted successfully!'; } catch (Exception $e) { $panelPdo->rollBack(); error_log("Delete member error: " . $e->getMessage()); $error_message = 'Error deleting member. Please try again.'; } } } // ========================================================= // HANDLE BULK VERIFICATION EMAIL SEND — batched AJAX endpoint // ========================================================= if (isset($_POST['send_verification_emails']) && isLoggedIn()) { @set_time_limit(120); ignore_user_abort(true); header('Content-Type: application/json'); $from_date = trim($_POST['from_date'] ?? ''); $to_date = trim($_POST['to_date'] ?? ''); $offset = max(0, intval($_POST['offset'] ?? 0)); $batch = 10; // emails per request if (empty($from_date) || empty($to_date)) { echo json_encode(['success' => false, 'message' => 'Please select both From and To dates.']); exit; } try { $panelPdo = getPanelDBConnection(); // Total count (always fresh so JS knows the full picture) $cntStmt = $panelPdo->prepare(" SELECT COUNT(*) FROM users WHERE email_verified = 0 AND DATE(created_at) >= ? AND DATE(created_at) <= ? "); $cntStmt->execute([$from_date, $to_date]); $total = (int)$cntStmt->fetchColumn(); if ($total === 0) { echo json_encode(['success' => true, 'done' => true, 'sent' => 0, 'failed' => 0, 'total' => 0, 'message' => 'No unverified members found in the selected date range.']); exit; } // Fetch this batch $stmt = $panelPdo->prepare(" SELECT id, email FROM users WHERE email_verified = 0 AND DATE(created_at) >= ? AND DATE(created_at) <= ? ORDER BY created_at ASC LIMIT {$batch} OFFSET {$offset} "); $stmt->execute([$from_date, $to_date]); $batchUsers = $stmt->fetchAll(); $sentCount = 0; $failedCount = 0; $firstError = null; foreach ($batchUsers as $user) { $panelPdo->prepare("DELETE FROM email_verifications WHERE user_id = ?") ->execute([$user['id']]); $token = bin2hex(random_bytes(32)); $expiresAt = date('Y-m-d H:i:s', strtotime('+48 hours')); $panelPdo->prepare(" INSERT INTO email_verifications (user_id, token, expires_at, created_at) VALUES (?, ?, ?, NOW()) ")->execute([$user['id'], $token, $expiresAt]); $verifyUrl = MEMBER_SITE_URL . '/verify.php?token=' . $token; $subject = 'Verify Your Email - Relevant Reflex'; $htmlBody = '

Relevant Reflex

Welcome to India\'s Trusted Survey Platform

Please Verify Your Email

To activate your account and start earning through paid surveys, please verify your email address:

Verify My Email Address

Note: This link expires in 48 hours.

If the button doesn\'t work: ' . $verifyUrl . '

Best regards,
The Relevant Reflex Team

'; $payload = [ 'personalizations' => [['to' => [['email' => $user['email']]], 'subject' => $subject]], 'from' => ['email' => SHOP_SENDER_EMAIL, 'name' => SHOP_SENDER_NAME], 'content' => [['type' => 'text/html', 'value' => $htmlBody]] ]; // Helper: send one email, with one retry on 429 rate-limit $sendOnce = function() use ($payload) { $ch = curl_init('https://api.sendgrid.com/v3/mail/send'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . SHOP_SENDGRID_API_KEY, 'Content-Type: application/json' ], CURLOPT_TIMEOUT => 20, CURLOPT_SSL_VERIFYPEER => true ]); $response = curl_exec($ch); $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return [$statusCode, $response]; }; [$statusCode, $response] = $sendOnce(); // If rate-limited (429), wait 2 seconds and retry once if ($statusCode === 429) { sleep(2); [$statusCode, $response] = $sendOnce(); } if ($statusCode === 202) { $sentCount++; } else { $failedCount++; $rawError = "HTTP $statusCode | Key(last6):" . substr(SHOP_SENDGRID_API_KEY, -6) . " | Body: $response"; error_log("SendGrid failed for {$user['email']}: $rawError"); if ($firstError === null) { $decoded = json_decode($response, true); $msg = $decoded['errors'][0]['message'] ?? $decoded['message'] ?? $response; $firstError = "HTTP $statusCode: $msg (raw: " . substr($response, 0, 200) . ")"; } } // 300ms pause between each email to stay within SendGrid rate limits usleep(300000); } $newOffset = $offset + count($batchUsers); $done = ($newOffset >= $total); if ($done) { logActivity($_SESSION['admin_id'], 'bulk_verification_email', "Bulk verification complete. Range: $from_date to $to_date. Total: $total"); } echo json_encode([ 'success' => true, 'done' => $done, 'sent' => $sentCount, 'failed' => $failedCount, 'processed' => $newOffset, 'total' => $total, 'firstError' => $firstError, ]); } catch (Exception $e) { error_log("Bulk verification email error: " . $e->getMessage()); echo json_encode(['success' => false, 'message' => 'Server error: ' . $e->getMessage()]); } exit; } // ========================================================= // HANDLE SINGLE MEMBER VERIFICATION EMAIL RESEND — AJAX // ========================================================= if (isset($_POST['resend_verification']) && isLoggedIn()) { header('Content-Type: application/json'); $member_id = intval($_POST['member_id'] ?? 0); if ($member_id <= 0) { echo json_encode(['success' => false, 'message' => 'Invalid member ID.']); exit; } try { $panelPdo = getPanelDBConnection(); $stmt = $panelPdo->prepare("SELECT id, email, email_verified FROM users WHERE id = ?"); $stmt->execute([$member_id]); $user = $stmt->fetch(); if (!$user) { echo json_encode(['success' => false, 'message' => 'Member not found.']); exit; } if ($user['email_verified']) { echo json_encode(['success' => false, 'message' => 'This member is already verified.']); exit; } // Fresh token $panelPdo->prepare("DELETE FROM email_verifications WHERE user_id = ?")->execute([$member_id]); $token = bin2hex(random_bytes(32)); $expiresAt = date('Y-m-d H:i:s', strtotime('+48 hours')); $panelPdo->prepare(" INSERT INTO email_verifications (user_id, token, expires_at, created_at) VALUES (?, ?, ?, NOW()) ")->execute([$member_id, $token, $expiresAt]); $verifyUrl = MEMBER_SITE_URL . '/verify.php?token=' . $token; $subject = 'Verify Your Email - Relevant Reflex'; $htmlBody = '

Relevant Reflex

Welcome to India\'s Trusted Survey Platform

Please Verify Your Email

To activate your account and start earning through paid surveys, please verify your email address:

Verify My Email Address

Note: This link expires in 48 hours.

If the button doesn\'t work: ' . $verifyUrl . '

Best regards,
The Relevant Reflex Team

'; $payload = [ 'personalizations' => [['to' => [['email' => $user['email']]], 'subject' => $subject]], 'from' => ['email' => SHOP_SENDER_EMAIL, 'name' => SHOP_SENDER_NAME], 'content' => [['type' => 'text/html', 'value' => $htmlBody]] ]; $ch = curl_init('https://api.sendgrid.com/v3/mail/send'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . SHOP_SENDGRID_API_KEY, 'Content-Type: application/json' ], CURLOPT_TIMEOUT => 20, CURLOPT_SSL_VERIFYPEER => true ]); $response = curl_exec($ch); $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($statusCode === 202) { logActivity($_SESSION['admin_id'], 'resend_verification', "Resent verification email to member #$member_id ({$user['email']})"); echo json_encode(['success' => true, 'message' => 'Verification email sent to ' . $user['email']]); } else { $decoded = json_decode($response, true); $msg = $decoded['errors'][0]['message'] ?? $decoded['message'] ?? $response; error_log("Resend verification failed for {$user['email']}: HTTP $statusCode — $response"); echo json_encode(['success' => false, 'message' => "Failed (HTTP $statusCode): $msg — (raw: " . substr($response, 0, 200) . ")"]); } } catch (Exception $e) { error_log("Resend verification error: " . $e->getMessage()); echo json_encode(['success' => false, 'message' => 'Server error: ' . $e->getMessage()]); } exit; } try { $panelPdo = getPanelDBConnection(); $stmt = $panelPdo->query(" SELECT u.*, up.points as current_points, up.total_earned, up.total_redeemed, mv.mobile_number, mv.is_verified as mobile_verified, ev.token as verify_token FROM users u LEFT JOIN user_points up ON u.id = up.user_id LEFT JOIN mobile_verifications mv ON u.id = mv.user_id LEFT JOIN email_verifications ev ON ev.id = ( SELECT id FROM email_verifications WHERE user_id = u.id ORDER BY created_at DESC LIMIT 1 ) ORDER BY u.created_at DESC "); $members = $stmt->fetchAll(); } catch (Exception $e) { $members = []; error_log("Fetch members error: " . $e->getMessage()); } include 'includes/header.php'; ?>
Total Members
$m['status'] === 'active')); ?>
Active Members
$m['email_verified'] == 1)); ?>
Email Verified
$m['mobile_verified'] == 1)); ?>
Mobile Verified
$m['onboarding_completed'] == 1)); ?>
Onboarded
!empty($m['isec_class']))); ?>
SEC Classified
Total Points

✉ Send Verification Emails to Unverified Members

Sends a fresh 48-hour verification link to all members who registered in the selected date range and have not yet verified their email.

" data-raw-created="">
ID Email Gen Date of Birth Postcode SEC Points Mobile Status Joined Actions
👥
No panel members found
Verified  📱
Verified  ✉
diff($dob)->y; ?>
format('d M Y'); ?>
y
'#059669','B'=>'#0d9488','C'=>'#2563eb','D'=>'#d97706','E'=>'#dc2626']; $bgc = $secColors[$member['isec_class']] ?? '#94a3b8'; ?>
✓ Verified
Unverified
👁