Sindbad~EG File Manager
<?php
class EmailService {
private $db;
private $settings;
public function __construct() {
$this->db = Database::getInstance()->getConnection();
$this->loadSettings();
}
private function loadSettings() {
$stmt = $this->db->query("SELECT * FROM email_settings ORDER BY id DESC LIMIT 1");
$this->settings = $stmt->fetch();
// Default settings if none exist
if (!$this->settings) {
$this->settings = [
'is_enabled' => 0,
'smtp_host' => 'localhost',
'smtp_port' => 587,
'smtp_username' => '',
'smtp_password' => '',
'smtp_encryption' => 'tls',
'from_email' => 'noreply@localhost',
'from_name' => 'Church Management System',
'send_welcome_user' => 1,
'send_welcome_member' => 1,
'user_welcome_subject' => 'Welcome to our Church Management System',
'user_welcome_template' => '<h2>Welcome!</h2><p>Dear {name}, your account has been created.</p>',
'member_welcome_subject' => 'Welcome to our Church Community',
'member_welcome_template' => '<h2>Welcome!</h2><p>Dear {name}, welcome to our church community!</p>'
];
}
}
public function isEnabled() {
return $this->settings['is_enabled'] == 1;
}
public function queueEmail($recipientEmail, $recipientName, $subject, $body, $emailType = 'general') {
if (!$this->isEnabled()) {
return false;
}
try {
$stmt = $this->db->prepare("
INSERT INTO email_queue (recipient_email, recipient_name, subject, body, email_type)
VALUES (:email, :name, :subject, :body, :type)
");
return $stmt->execute([
'email' => $recipientEmail,
'name' => $recipientName,
'subject' => $subject,
'body' => $body,
'type' => $emailType
]);
} catch (Exception $e) {
error_log("Queue email error: " . $e->getMessage());
return false;
}
}
/**
* Send email instantly without queueing (for time-sensitive emails like 2FA)
*/
public function sendInstantEmail($recipientEmail, $recipientName, $subject, $body) {
if (!$this->isEnabled()) {
error_log("Email service is not enabled");
return false;
}
try {
// Create email data array
$emailData = [
'recipient_email' => $recipientEmail,
'recipient_name' => $recipientName,
'subject' => $subject,
'body' => $body,
'id' => null // No queue ID for instant send
];
// Load Symfony Mailer if not already loaded
if (!class_exists('Symfony\Component\Mailer\Mailer')) {
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
require_once __DIR__ . '/../vendor/autoload.php';
}
}
// Use Symfony Mailer if available
if (class_exists('Symfony\Component\Mailer\Mailer')) {
return $this->sendWithSymfonyMailer($emailData);
} else {
// Fallback to simulation mode
error_log("Symfony Mailer not available, simulating send");
return $this->simulateEmailSend($emailData);
}
} catch (Exception $e) {
error_log("Instant email send error: " . $e->getMessage());
return false;
}
}
public function sendWelcomeUserEmail($userData) {
if (!$this->isEnabled() || !$this->settings['send_welcome_user']) {
return false;
}
$subject = $this->settings['user_welcome_subject'];
$template = $this->settings['user_welcome_template'];
// Replace template variables
$variables = [
'{name}' => $userData['full_name'] ?? $userData['first_name'] . ' ' . $userData['last_name'],
'{email}' => $userData['email'],
'{username}' => $userData['username'] ?? $userData['email'],
'{login_url}' => BASE_URL . 'login.php'
];
$body = str_replace(array_keys($variables), array_values($variables), $template);
// Send instantly - welcome emails should arrive immediately
return $this->sendInstantEmail(
$userData['email'],
$variables['{name}'],
$subject,
$body
);
}
public function sendWelcomeMemberEmail($memberData) {
if (!$this->isEnabled() || !$this->settings['send_welcome_member']) {
return false;
}
$subject = $this->settings['member_welcome_subject'];
$template = $this->settings['member_welcome_template'];
// Replace template variables
$variables = [
'{name}' => $memberData['first_name'] . ' ' . $memberData['last_name'],
'{email}' => $memberData['email'],
'{member_id}' => $memberData['membershipcard_id'] ?? $memberData['id'],
'{phone}' => $memberData['phone'] ?? 'N/A'
];
$body = str_replace(array_keys($variables), array_values($variables), $template);
// Send instantly - welcome emails should arrive immediately
return $this->sendInstantEmail(
$memberData['email'],
$variables['{name}'],
$subject,
$body
);
}
/**
* Send password reset email instantly
*/
public function sendPasswordResetEmail($email, $name, $resetToken, $userType = 'admin') {
if (!$this->isEnabled()) {
error_log("Password Reset Email Failed: Email service is not enabled");
return false;
}
$resetUrl = BASE_URL . 'reset-password.php?token=' . $resetToken . '&type=' . $userType;
$subject = "Password Reset Request";
$message = "
<h2>Password Reset Request</h2>
<p>Dear {$name},</p>
<p>We received a request to reset your password. Click the link below to reset your password:</p>
<p style='margin: 20px 0;'>
<a href='{$resetUrl}' style='display: inline-block; padding: 12px 24px; background: linear-gradient(135deg, #1E40AF 0%, #9333EA 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: bold;'>
Reset Password
</a>
</p>
<p>Or copy and paste this link in your browser:</p>
<p style='word-break: break-all; color: #1E40AF;'>{$resetUrl}</p>
<p>This link will expire in 1 hour.</p>
<p>If you didn't request a password reset, please ignore this email or contact support if you have concerns.</p>
<hr style='margin: 20px 0; border: none; border-top: 1px solid #e5e7eb;'>
<p style='font-size: 12px; color: #6b7280;'>This is an automated email. Please do not reply.</p>
";
error_log("Attempting to send password reset email to: " . $email);
// Send instantly - password reset is time-sensitive
$result = $this->sendInstantEmail($email, $name, $subject, $message);
if ($result) {
error_log("Password reset email sent successfully to: " . $email);
} else {
error_log("Password reset email failed to send to: " . $email);
}
return $result;
}
public function processPendingEmails($limit = 10) {
if (!$this->isEnabled()) {
return 0;
}
$stmt = $this->db->prepare("
SELECT * FROM email_queue
WHERE status = 'pending' AND attempts < 3
ORDER BY created_at ASC
LIMIT :limit
");
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->execute();
$pendingEmails = $stmt->fetchAll();
$processed = 0;
foreach ($pendingEmails as $email) {
if ($this->sendEmail($email)) {
$this->updateEmailStatus($email['id'], 'sent');
$processed++;
} else {
$this->updateEmailStatus($email['id'], 'failed', 'SMTP send failed');
}
}
return $processed;
}
private function sendEmail($emailData) {
try {
// Load composer autoload if available
if (!class_exists('Symfony\Component\Mailer\Mailer')) {
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
require_once __DIR__ . '/../vendor/autoload.php';
}
}
// Use Symfony Mailer if available, otherwise simulate
if (class_exists('Symfony\Component\Mailer\Mailer')) {
return $this->sendWithSymfonyMailer($emailData);
} else {
// Fallback to simulation mode
return $this->simulateEmailSend($emailData);
}
} catch (Exception $e) {
error_log("Email send error: " . $e->getMessage());
return false;
}
}
private function sendWithSymfonyMailer($emailData) {
try {
// Build DSN (Data Source Name) for SMTP
// Format: smtp://username:password@host:port or smtps:// for SSL
$protocol = ($this->settings['smtp_encryption'] === 'ssl') ? 'smtps' : 'smtp';
// Trim and clean all SMTP settings
$smtpHost = trim($this->settings['smtp_host']);
$smtpPort = (int)trim($this->settings['smtp_port']);
$smtpUsername = trim($this->settings['smtp_username'] ?? '');
$smtpPassword = trim($this->settings['smtp_password'] ?? '');
// Build authentication part
$auth = '';
if (!empty($smtpUsername)) {
$auth = urlencode($smtpUsername) . ':' . urlencode($smtpPassword) . '@';
}
// Build base DSN
$dsn = sprintf(
'%s://%s%s:%d',
$protocol,
$auth,
$smtpHost,
$smtpPort
);
// Add query parameters for TLS if needed
if ($this->settings['smtp_encryption'] === 'tls' && $protocol === 'smtp') {
$dsn .= '?encryption=tls';
}
// Log DSN (without password) for debugging
$debugDsn = preg_replace('/:[^:@]+@/', ':****@', $dsn);
error_log("Attempting to send email with DSN: " . $debugDsn);
// Create transport
$transport = \Symfony\Component\Mailer\Transport::fromDsn($dsn);
// Create mailer
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
// Create email message with trimmed addresses
$fromEmail = trim($this->settings['from_email']);
$fromName = trim($this->settings['from_name']);
$toEmail = trim($emailData['recipient_email']);
$toName = trim($emailData['recipient_name']);
$email = (new \Symfony\Component\Mime\Email())
->from(new \Symfony\Component\Mime\Address($fromEmail, $fromName))
->to(new \Symfony\Component\Mime\Address($toEmail, $toName))
->subject($emailData['subject'])
->html($emailData['body'])
->text(strip_tags($emailData['body']));
// Send email
$mailer->send($email);
// Update sent timestamp only if this is a queued email
if (isset($emailData['id']) && $emailData['id'] !== null) {
$stmt = $this->db->prepare("UPDATE email_queue SET sent_at = NOW() WHERE id = :id");
$stmt->execute(['id' => $emailData['id']]);
}
error_log("Email sent successfully via Symfony Mailer to: " . $emailData['recipient_email']);
return true;
} catch (\Symfony\Component\Mailer\Exception\TransportExceptionInterface $e) {
error_log("Symfony Mailer Transport Error: " . $e->getMessage());
throw new Exception("SMTP Error: " . $e->getMessage());
} catch (Exception $e) {
error_log("Symfony Mailer Error: " . $e->getMessage());
throw new Exception("Failed to send email: " . $e->getMessage());
}
}
private function simulateEmailSend($emailData) {
// Simulation mode (for testing without SMTP)
usleep(100000); // 0.1 second
// Log the email
error_log("Email simulated to: " . $emailData['recipient_email'] . " - Subject: " . $emailData['subject']);
// Update sent timestamp
$stmt = $this->db->prepare("UPDATE email_queue SET sent_at = NOW() WHERE id = :id");
$stmt->execute(['id' => $emailData['id']]);
return true;
}
private function updateEmailStatus($emailId, $status, $errorMessage = null) {
$stmt = $this->db->prepare("
UPDATE email_queue
SET status = :status,
attempts = attempts + 1,
error_message = :error,
updated_at = NOW()
WHERE id = :id
");
return $stmt->execute([
'id' => $emailId,
'status' => $status,
'error' => $errorMessage
]);
}
public function getEmailStats() {
$stmt = $this->db->query("
SELECT
COUNT(*) as total,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
SUM(CASE WHEN status = 'sent' THEN 1 ELSE 0 END) as sent,
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed
FROM email_queue
");
return $stmt->fetch();
}
public function getRecentEmails($limit = 20) {
$stmt = $this->db->prepare("
SELECT * FROM email_queue
ORDER BY created_at DESC
LIMIT :limit
");
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
public function getFailedEmails($limit = 50) {
$stmt = $this->db->prepare("
SELECT * FROM email_queue
WHERE status = 'failed'
ORDER BY updated_at DESC
LIMIT :limit
");
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
public function retryEmail($emailId) {
// Reset email status to pending and reset attempts
$stmt = $this->db->prepare("
UPDATE email_queue
SET status = 'pending',
attempts = 0,
error_message = NULL,
updated_at = NOW()
WHERE id = :id
");
return $stmt->execute(['id' => $emailId]);
}
public function retryAllFailed() {
// Reset all failed emails to pending
$stmt = $this->db->prepare("
UPDATE email_queue
SET status = 'pending',
attempts = 0,
error_message = NULL,
updated_at = NOW()
WHERE status = 'failed'
");
$result = $stmt->execute();
// Return count of reset emails
if ($result) {
return $stmt->rowCount();
}
return 0;
}
public function deleteEmail($emailId) {
$stmt = $this->db->prepare("DELETE FROM email_queue WHERE id = :id");
return $stmt->execute(['id' => $emailId]);
}
public function deleteAllFailed() {
$stmt = $this->db->prepare("DELETE FROM email_queue WHERE status = 'failed'");
$result = $stmt->execute();
if ($result) {
return $stmt->rowCount();
}
return 0;
}
}
?>
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists