execute($sql)) { return self::$db->insert_id(); } return false; } /** * Process email queue * @param int $limit Number of emails to process * @return array Results */ public static function processQueue($limit = 50) { self::init(); $limit = (int)$limit; // Get pending emails $sql = "SELECT * FROM db_email_queue WHERE status = 'pending' AND (send_at IS NULL OR send_at <= NOW()) ORDER BY priority ASC, created_at ASC LIMIT $limit"; $result = self::$db->execute($sql); if (!$result) { return ['sent' => 0, 'failed' => 0]; } $sent = 0; $failed = 0; while ($row = $result->FetchRow()) { $email_id = (int)$row['email_id']; // Mark as sending self::$db->execute("UPDATE db_email_queue SET status = 'sending' WHERE email_id = $email_id"); // Send email $success = self::sendEmail($row); if ($success) { self::$db->execute("UPDATE db_email_queue SET status = 'sent', sent_at = NOW() WHERE email_id = $email_id"); self::logEmail($email_id, $row, 'sent'); $sent++; } else { $attempts = (int)$row['attempts'] + 1; $max_attempts = 3; if ($attempts >= $max_attempts) { self::$db->execute("UPDATE db_email_queue SET status = 'failed', attempts = $attempts WHERE email_id = $email_id"); self::logEmail($email_id, $row, 'failed'); $failed++; } else { // Retry later self::$db->execute("UPDATE db_email_queue SET status = 'pending', attempts = $attempts WHERE email_id = $email_id"); } } } return ['sent' => $sent, 'failed' => $failed]; } /** * Send individual email * @param array $email_data Email data * @return bool Success */ private static function sendEmail($email_data) { if (self::$sendgrid_api_key) { return self::sendViaSendGrid($email_data); } else { return self::sendViaPHPMailer($email_data); } } /** * Send via SendGrid */ private static function sendViaSendGrid($email_data) { $url = 'https://api.sendgrid.com/v3/mail/send'; $data = [ 'personalizations' => [[ 'to' => [['email' => $email_data['to_email'], 'name' => $email_data['to_name']]] ]], 'from' => ['email' => self::$from_email, 'name' => self::$from_name], 'subject' => $email_data['subject'], 'content' => [ ['type' => 'text/html', 'value' => $email_data['body_html']], ['type' => 'text/plain', 'value' => $email_data['body_text']] ] ]; $json = json_encode($data); $ch = curl_init($url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $json); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . self::$sendgrid_api_key, 'Content-Type: application/json' ]); $result = curl_exec($ch); $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $status_code == 202 || $status_code == 200; } /** * Send via PHP mail (fallback) */ private static function sendViaPHPMailer($email_data) { $to = $email_data['to_email']; $subject = $email_data['subject']; $message = $email_data['body_html']; $headers = "MIME-Version: 1.0\r\n"; $headers .= "Content-type: text/html; charset=UTF-8\r\n"; $headers .= "From: " . self::$from_name . " <" . self::$from_email . ">\r\n"; return mail($to, $subject, $message, $headers); } /** * Log email event */ private static function logEmail($email_id, $email_data, $status) { $usr_id = $email_data['usr_id'] ?? 'NULL'; $to_email = VDatabase::escape($email_data['to_email']); $subject = VDatabase::escape($email_data['subject']); $status_safe = VDatabase::escape($status); $sql = "INSERT INTO db_email_logs (email_id, usr_id, to_email, subject, status, created_at) VALUES ($email_id, $usr_id, '$to_email', '$subject', '$status_safe', NOW())"; self::$db->execute($sql); } /** * Get user email preferences * @param int $usr_id User ID * @return array Preferences */ public static function getPreferences($usr_id) { self::init(); $usr_id = (int)$usr_id; $sql = "SELECT * FROM db_email_preferences WHERE usr_id = $usr_id"; $result = self::$db->execute($sql); if ($result && $result->RecordCount() > 0) { return $result->FetchRow(); } // Return defaults return [ 'digest_frequency' => 'weekly', 'notify_comments' => 1, 'notify_replies' => 1, 'notify_likes' => 1, 'notify_subscribers' => 1, 'notify_uploads' => 1, 'notify_live_streams' => 1, 'notify_mentions' => 1, 'notify_milestones' => 1, 'marketing_emails' => 1 ]; } /** * Update email preferences * @param int $usr_id User ID * @param array $preferences Preferences * @return bool Success */ public static function updatePreferences($usr_id, $preferences) { self::init(); $usr_id = (int)$usr_id; // Build SET clause $sets = []; foreach ($preferences as $key => $value) { $key_safe = VDatabase::escape($key); $value_safe = VDatabase::escape($value); $sets[] = "$key_safe = '$value_safe'"; } $setClause = implode(', ', $sets); $sql = "INSERT INTO db_email_preferences (usr_id, $setClause, updated_at) VALUES ($usr_id, NOW()) ON DUPLICATE KEY UPDATE $setClause, updated_at = NOW()"; return self::$db->execute($sql); } /** * Send templated email * @param string $template_name Template name * @param int $usr_id User ID * @param string $to_email Email address * @param array $variables Template variables * @return int Email ID */ public static function sendTemplate($template_name, $usr_id, $to_email, $variables = []) { self::init(); // Get template $template_safe = VDatabase::escape($template_name); $sql = "SELECT * FROM db_email_templates WHERE name = '$template_safe' AND is_active = 1"; $result = self::$db->execute($sql); if (!$result || $result->RecordCount() == 0) { return false; } $template = $result->FetchRow(); // Replace variables $subject = $template['subject']; $body_html = $template['body_html']; $body_text = $template['body_text']; foreach ($variables as $key => $value) { $subject = str_replace('{' . $key . '}', $value, $subject); $body_html = str_replace('{' . $key . '}', $value, $body_html); $body_text = str_replace('{' . $key . '}', $value, $body_text); } return self::queueEmail($usr_id, $to_email, $subject, $body_html, [ 'body_text' => $body_text, 'template_name' => $template_name, 'template_data' => $variables ]); } }