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 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 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.

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