diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php index ea488a3..e1f5ba7 100644 --- a/app/Controllers/AuthController.php +++ b/app/Controllers/AuthController.php @@ -7,16 +7,20 @@ use App\Models\User; use App\Models\Setting; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; +use App\Helpers\EmailHelper; +use App\Services\Logger; class AuthController extends Controller { private User $userModel; private Setting $settingModel; + private Logger $logger; public function __construct() { $this->userModel = new User(); $this->settingModel = new Setting(); + $this->logger = new Logger('auth'); } /** @@ -690,43 +694,21 @@ class AuthController extends Controller */ private function sendVerificationEmail($email, $fullName, $token) { - try { - $emailSettings = $this->settingModel->getEmailSettings(); - $appSettings = $this->settingModel->getAppSettings(); - - $verifyUrl = $appSettings['app_url'] . '/verify-email?token=' . $token; - - $mail = new PHPMailer(true); - $mail->isSMTP(); - $mail->Host = $emailSettings['mail_host']; - $mail->SMTPAuth = !empty($emailSettings['mail_username']); - $mail->Username = $emailSettings['mail_username']; - $mail->Password = $emailSettings['mail_password']; - $mail->SMTPSecure = $emailSettings['mail_encryption']; - $mail->Port = (int)$emailSettings['mail_port']; - $mail->CharSet = 'UTF-8'; - - $mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']); - $mail->addAddress($email, $fullName); - - $mail->isHTML(true); - $mail->Subject = 'Verify Your Email Address'; - $mail->Body = " -

Welcome to Domain Monitor!

-

Hello {$fullName},

-

Thank you for registering. Please click the link below to verify your email address:

-

Verify Email Address

-

Or copy and paste this URL into your browser:

-

{$verifyUrl}

-

This link will expire in 24 hours.

-

If you did not create an account, please ignore this email.

- "; - - $mail->send(); - - } catch (Exception $e) { + $result = EmailHelper::sendVerificationEmail($email, $fullName, $token); + + if (!$result['success']) { // Log error but don't fail the registration - error_log('Failed to send verification email: ' . $e->getMessage()); + $this->logger->error('Failed to send verification email', [ + 'email' => $email, + 'full_name' => $fullName, + 'debug_info' => $result['debug_info'] ?? null, + 'error' => $result['error'] ?? null + ]); + } else { + $this->logger->info('Verification email sent successfully', [ + 'email' => $email, + 'full_name' => $fullName + ]); } } @@ -735,43 +717,21 @@ class AuthController extends Controller */ private function sendPasswordResetEmail($email, $fullName, $token) { - try { - $emailSettings = $this->settingModel->getEmailSettings(); - $appSettings = $this->settingModel->getAppSettings(); - - $resetUrl = $appSettings['app_url'] . '/reset-password?token=' . $token; - - $mail = new PHPMailer(true); - $mail->isSMTP(); - $mail->Host = $emailSettings['mail_host']; - $mail->SMTPAuth = !empty($emailSettings['mail_username']); - $mail->Username = $emailSettings['mail_username']; - $mail->Password = $emailSettings['mail_password']; - $mail->SMTPSecure = $emailSettings['mail_encryption']; - $mail->Port = (int)$emailSettings['mail_port']; - $mail->CharSet = 'UTF-8'; - - $mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']); - $mail->addAddress($email, $fullName); - - $mail->isHTML(true); - $mail->Subject = 'Reset Your Password'; - $mail->Body = " -

Password Reset Request

-

Hello {$fullName},

-

We received a request to reset your password. Click the link below to create a new password:

-

Reset Password

-

Or copy and paste this URL into your browser:

-

{$resetUrl}

-

This link will expire in 1 hour.

-

If you did not request a password reset, please ignore this email and your password will remain unchanged.

- "; - - $mail->send(); - - } catch (Exception $e) { + $result = EmailHelper::sendPasswordResetEmail($email, $fullName, $token); + + if (!$result['success']) { // Log error - error_log('Failed to send password reset email: ' . $e->getMessage()); + $this->logger->error('Failed to send password reset email', [ + 'email' => $email, + 'full_name' => $fullName, + 'debug_info' => $result['debug_info'] ?? null, + 'error' => $result['error'] ?? null + ]); + } else { + $this->logger->info('Password reset email sent successfully', [ + 'email' => $email, + 'full_name' => $fullName + ]); } } diff --git a/app/Controllers/SettingsController.php b/app/Controllers/SettingsController.php index 4aa3172..6a436b9 100644 --- a/app/Controllers/SettingsController.php +++ b/app/Controllers/SettingsController.php @@ -5,15 +5,19 @@ namespace App\Controllers; use Core\Controller; use Core\Auth; use App\Models\Setting; +use App\Helpers\EmailHelper; +use App\Services\Logger; class SettingsController extends Controller { private Setting $settingModel; + private Logger $logger; public function __construct() { Auth::requireAdmin(); $this->settingModel = new Setting(); + $this->logger = new Logger('settings'); } public function index() @@ -250,12 +254,36 @@ class SettingsController extends Controller $this->verifyCsrf('/settings#email'); try { + $port = (int)trim($_POST['mail_port'] ?? '2525'); + $encryption = trim($_POST['mail_encryption'] ?? 'tls'); + + // Auto-detect encryption based on port if not explicitly set + $originalEncryption = $encryption; + if (empty($encryption) || $encryption === 'tls') { + if ($port === 465) { + $encryption = 'ssl'; // Port 465 should use SSL + $this->logger->info('Auto-detected SSL encryption for port 465', [ + 'port' => $port, + 'original_encryption' => $originalEncryption, + 'detected_encryption' => $encryption + ]); + } elseif ($port === 587) { + $encryption = 'tls'; // Port 587 should use TLS + $this->logger->info('Auto-detected TLS encryption for port 587', [ + 'port' => $port, + 'original_encryption' => $originalEncryption, + 'detected_encryption' => $encryption + ]); + } + // For other ports, keep the user's selection + } + $emailSettings = [ 'mail_host' => trim($_POST['mail_host'] ?? ''), - 'mail_port' => trim($_POST['mail_port'] ?? '2525'), + 'mail_port' => $port, 'mail_username' => trim($_POST['mail_username'] ?? ''), 'mail_password' => trim($_POST['mail_password'] ?? ''), - 'mail_encryption' => trim($_POST['mail_encryption'] ?? 'tls'), + 'mail_encryption' => $encryption, 'mail_from_address' => trim($_POST['mail_from_address'] ?? ''), 'mail_from_name' => trim($_POST['mail_from_name'] ?? 'Domain Monitor') ]; @@ -381,111 +409,31 @@ class SettingsController extends Controller return; } - try { - // Get current email settings - $emailSettings = $this->settingModel->getEmailSettings(); - $appSettings = $this->settingModel->getAppSettings(); - - // Create PHPMailer instance - $mail = new \PHPMailer\PHPMailer\PHPMailer(true); - - // Server settings - $mail->isSMTP(); - $mail->Host = $emailSettings['mail_host']; - $mail->SMTPAuth = !empty($emailSettings['mail_username']); - $mail->Username = $emailSettings['mail_username']; - $mail->Password = $emailSettings['mail_password']; - $mail->SMTPSecure = $emailSettings['mail_encryption']; - $mail->Port = $emailSettings['mail_port']; - - // Recipients - $mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']); - $mail->addAddress($testEmail); - - // Content - $mail->isHTML(true); - $mail->Subject = 'Test Email from ' . $appSettings['app_name']; + // Use EmailHelper to send test email + $result = EmailHelper::sendTestEmail($testEmail); + + if ($result['success']) { + $_SESSION['success'] = $result['message']; + $this->logger->info('Test email sent successfully', [ + 'email' => $testEmail + ]); + } else { + // Log detailed error information for debugging + $this->logger->error('Test email failed', [ + 'email' => $testEmail, + 'debug_info' => $result['debug_info'] ?? null, + 'error' => $result['error'] ?? null + ]); - $appName = htmlspecialchars($appSettings['app_name']); - $appUrl = htmlspecialchars($appSettings['app_url']); - $currentTime = date('F j, Y g:i A'); + $_SESSION['error'] = $result['message']; - $mail->Body = " - - - - - -
-
-

✅ Email Test Successful!

-
-
-
- Success! Your email configuration is working correctly. -
-

This is a test email from {$appName}.

-

If you're seeing this message, it means your SMTP settings are configured properly and emails are being delivered successfully.

- - - - - - - - - - - - - - - - - - - - - - -
SMTP Host:" . htmlspecialchars($emailSettings['mail_host']) . "
SMTP Port:" . htmlspecialchars($emailSettings['mail_port']) . "
Encryption:" . htmlspecialchars($emailSettings['mail_encryption'] ?: 'None') . "
From Address:" . htmlspecialchars($emailSettings['mail_from_address']) . "
Test Time:{$currentTime}
-
- -
- - - "; - - $mail->AltBody = "Email Test Successful!\n\n" . - "This is a test email from {$appName}.\n" . - "Your SMTP configuration is working correctly.\n\n" . - "SMTP Host: {$emailSettings['mail_host']}\n" . - "SMTP Port: {$emailSettings['mail_port']}\n" . - "From: {$emailSettings['mail_from_address']}\n" . - "Test Time: {$currentTime}"; - - $mail->send(); - - $_SESSION['success'] = "Test email sent successfully to {$testEmail}. Please check your inbox."; - $this->redirect('/settings#email'); - - } catch (\Exception $e) { - $_SESSION['error'] = "Failed to send test email: " . $e->getMessage(); - $this->redirect('/settings#email'); + // In development, show more detailed error + if (($_ENV['APP_ENV'] ?? 'production') === 'development') { + $_SESSION['error'] .= " (Debug: " . ($result['debug_info'] ?? $result['error']) . ")"; + } } + + $this->redirect('/settings#email'); } } diff --git a/app/Helpers/EmailHelper.php b/app/Helpers/EmailHelper.php new file mode 100644 index 0000000..e440af1 --- /dev/null +++ b/app/Helpers/EmailHelper.php @@ -0,0 +1,429 @@ +getEmailSettings(); + } + + /** + * Get app settings from database + */ + public static function getAppSettings(): array + { + return self::getSettingModel()->getAppSettings(); + } + + /** + * Create and configure a PHPMailer instance with proper settings + */ + public static function createMailer(): PHPMailer + { + $emailSettings = self::getEmailSettings(); + $mail = new PHPMailer(true); + + // Server settings + $mail->isSMTP(); + $mail->Host = $emailSettings['mail_host']; + $mail->SMTPAuth = !empty($emailSettings['mail_username']); + $mail->Username = $emailSettings['mail_username']; + $mail->Password = $emailSettings['mail_password']; + $mail->Port = (int)$emailSettings['mail_port']; + + // Configure encryption based on port + $port = (int)$emailSettings['mail_port']; + $encryption = $emailSettings['mail_encryption']; + + // Auto-detect encryption for common ports + if ($port === 465) { + // Port 465 typically uses SSL + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; + } elseif ($port === 587) { + // Port 587 typically uses TLS + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } elseif ($port === 25 || $port === 2525) { + // Port 25/2525 might use TLS or no encryption + if ($encryption === 'tls') { + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } else { + $mail->SMTPSecure = ''; + } + } else { + // Use configured encryption for other ports + if ($encryption === 'ssl') { + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; + } elseif ($encryption === 'tls') { + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } else { + $mail->SMTPSecure = ''; + } + } + + // Configure timeouts and SSL options + $mail->Timeout = 30; // 30 seconds total timeout + $mail->SMTPOptions = [ + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ] + ]; + + // Set character encoding + $mail->CharSet = 'UTF-8'; + + return $mail; + } + + /** + * Send a test email + */ + public static function sendTestEmail(string $toEmail): array + { + try { + $emailSettings = self::getEmailSettings(); + $appSettings = self::getAppSettings(); + $mail = self::createMailer(); + + // Set sender + $mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']); + $mail->addAddress($toEmail); + + // Content + $mail->isHTML(true); + $mail->Subject = 'Test Email from ' . $appSettings['app_name']; + + $appName = htmlspecialchars($appSettings['app_name']); + $appUrl = htmlspecialchars($appSettings['app_url']); + $currentTime = date('F j, Y g:i A'); + + $mail->Body = " + + + + + +
+
+

✅ Email Test Successful!

+
+
+
+ Success! Your email configuration is working correctly. +
+

This is a test email from {$appName}.

+

If you're seeing this message, it means your SMTP settings are configured properly and emails are being delivered successfully.

+ + + + + + + + + + + + + + + + + + + + + + +
SMTP Host:" . htmlspecialchars($emailSettings['mail_host']) . "
SMTP Port:" . htmlspecialchars($emailSettings['mail_port']) . "
Encryption:" . htmlspecialchars($emailSettings['mail_encryption'] ?: 'None') . "
From Address:" . htmlspecialchars($emailSettings['mail_from_address']) . "
Test Time:{$currentTime}
+
+ +
+ + + "; + + $mail->AltBody = "Email Test Successful!\n\n" . + "This is a test email from {$appName}.\n" . + "Your SMTP configuration is working correctly.\n\n" . + "SMTP Host: {$emailSettings['mail_host']}\n" . + "SMTP Port: {$emailSettings['mail_port']}\n" . + "From: {$emailSettings['mail_from_address']}\n" . + "Test Time: {$currentTime}"; + + $mail->send(); + + return [ + 'success' => true, + 'message' => "Test email sent successfully to {$toEmail}. Please check your inbox." + ]; + + } catch (Exception $e) { + $errorMessage = "Failed to send test email: " . $e->getMessage(); + $debugInfo = $mail->ErrorInfo ?? 'No debug info available'; + + // Log the error using the application's logger + self::getLogger()->error($errorMessage, [ + 'email' => $toEmail, + 'smtp_error' => $debugInfo, + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'success' => false, + 'message' => $errorMessage, + 'error' => $e->getMessage(), + 'debug_info' => $debugInfo + ]; + } + } + + /** + * Send a notification email + */ + public static function sendNotificationEmail(string $toEmail, string $subject, string $message, array $data = []): array + { + try { + $emailSettings = self::getEmailSettings(); + $appSettings = self::getAppSettings(); + $mail = self::createMailer(); + + // Set sender + $mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']); + $mail->addAddress($toEmail); + + // Content + $mail->isHTML(true); + $mail->Subject = $subject; + $mail->Body = self::formatHtmlBody($message, $data, $appSettings); + $mail->AltBody = strip_tags($message); + + $mail->send(); + + return [ + 'success' => true, + 'message' => "Email sent successfully to {$toEmail}" + ]; + + } catch (Exception $e) { + $errorMessage = "Failed to send email: " . $e->getMessage(); + $debugInfo = $mail->ErrorInfo ?? 'No debug info available'; + + // Log the error using the application's logger + self::getLogger()->error($errorMessage, [ + 'email' => $toEmail, + 'subject' => $subject, + 'smtp_error' => $debugInfo, + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'success' => false, + 'message' => $errorMessage, + 'error' => $e->getMessage(), + 'debug_info' => $debugInfo + ]; + } + } + + /** + * Format HTML email body + */ + private static function formatHtmlBody(string $message, array $data, array $appSettings): string + { + $messageHtml = nl2br(htmlspecialchars($message)); + $appName = htmlspecialchars($appSettings['app_name']); + $appUrl = htmlspecialchars($appSettings['app_url']); + + // Build domain link if domain ID is available + $domainLink = ''; + if (isset($data['domain_id'])) { + $domainUrl = rtrim($appUrl, '/') . '/domains/' . $data['domain_id']; + $domainLink = "

View Domain Details

"; + } + + return " + + + + + +
+
+

🔔 {$appName} Alert

+
+
+

$messageHtml

+ $domainLink +
+ +
+ + + "; + } + + /** + * Send verification email + */ + public static function sendVerificationEmail(string $email, string $fullName, string $token): array + { + try { + $appSettings = self::getAppSettings(); + $verifyUrl = $appSettings['app_url'] . '/verify-email?token=' . $token; + + $subject = 'Verify Your Email Address'; + $message = " +

Welcome to Domain Monitor!

+

Hello {$fullName},

+

Thank you for registering. Please click the link below to verify your email address:

+

Verify Email Address

+

Or copy and paste this URL into your browser:

+

{$verifyUrl}

+

This link will expire in 24 hours.

+

If you did not create an account, please ignore this email.

+ "; + + return self::sendNotificationEmail($email, $subject, $message); + + } catch (\Exception $e) { + $errorMessage = "Failed to send verification email: " . $e->getMessage(); + + // Log the error using the application's logger + self::getLogger()->error($errorMessage, [ + 'email' => $email, + 'full_name' => $fullName, + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'success' => false, + 'message' => $errorMessage, + 'error' => $e->getMessage() + ]; + } + } + + /** + * Send password reset email + */ + public static function sendPasswordResetEmail(string $email, string $fullName, string $token): array + { + try { + $appSettings = self::getAppSettings(); + $resetUrl = $appSettings['app_url'] . '/reset-password?token=' . $token; + + $subject = 'Reset Your Password'; + $message = " +

Password Reset Request

+

Hello {$fullName},

+

We received a request to reset your password. Click the link below to create a new password:

+

Reset Password

+

Or copy and paste this URL into your browser:

+

{$resetUrl}

+

This link will expire in 1 hour.

+

If you did not request a password reset, please ignore this email and your password will remain unchanged.

+ "; + + return self::sendNotificationEmail($email, $subject, $message); + + } catch (\Exception $e) { + $errorMessage = "Failed to send password reset email: " . $e->getMessage(); + + // Log the error using the application's logger + self::getLogger()->error($errorMessage, [ + 'email' => $email, + 'full_name' => $fullName, + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'success' => false, + 'message' => $errorMessage, + 'error' => $e->getMessage() + ]; + } + } + + /** + * Get email subject based on data + */ + public static function getEmailSubject(array $data): string + { + if (isset($data['domain'])) { + $daysLeft = $data['days_left']; + if ($daysLeft <= 0) { + return "🚨 URGENT: Domain {$data['domain']} has EXPIRED"; + } + if ($daysLeft == 1) { + return "⚠️ CRITICAL: Domain {$data['domain']} expires TOMORROW"; + } + return "⚠️ Domain Expiration Alert: {$data['domain']} ({$daysLeft} days)"; + } + + return "Domain Monitor Alert"; + } +} diff --git a/app/Services/Channels/EmailChannel.php b/app/Services/Channels/EmailChannel.php index f784a1d..863dc06 100644 --- a/app/Services/Channels/EmailChannel.php +++ b/app/Services/Channels/EmailChannel.php @@ -5,104 +5,53 @@ namespace App\Services\Channels; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; use App\Models\Setting; +use App\Helpers\EmailHelper; +use App\Services\Logger; class EmailChannel implements NotificationChannelInterface { + private Logger $logger; + + public function __construct() + { + $this->logger = new Logger('email_channel'); + } + public function send(array $config, string $message, array $data = []): bool { - $mail = new PHPMailer(true); - try { - // Get email settings from database - $settingModel = new Setting(); - $emailSettings = $settingModel->getEmailSettings(); - $appSettings = $settingModel->getAppSettings(); - - // Server settings - $mail->isSMTP(); - $mail->Host = $emailSettings['mail_host']; - $mail->SMTPAuth = !empty($emailSettings['mail_username']); - $mail->Username = $emailSettings['mail_username']; - $mail->Password = $emailSettings['mail_password']; - $mail->SMTPSecure = $emailSettings['mail_encryption']; - $mail->Port = $emailSettings['mail_port']; - - // Recipients - $mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']); - $mail->addAddress($config['email']); - - // Content - $mail->isHTML(true); - $mail->Subject = $this->getSubject($data); - $mail->Body = $this->formatHtmlBody($message, $data, $appSettings); - $mail->AltBody = strip_tags($message); - - $mail->send(); + $result = EmailHelper::sendNotificationEmail( + $config['email'], + EmailHelper::getEmailSubject($data), + $message, + $data + ); + + if (!$result['success']) { + $this->logger->error("Email send failed via EmailChannel", [ + 'email' => $config['email'], + 'subject' => EmailHelper::getEmailSubject($data), + 'debug_info' => $result['debug_info'] ?? null, + 'error' => $result['error'] ?? null + ]); + return false; + } + + $this->logger->info("Email sent successfully via EmailChannel", [ + 'email' => $config['email'], + 'subject' => EmailHelper::getEmailSubject($data) + ]); + return true; - } catch (Exception $e) { - error_log("Email send failed: {$mail->ErrorInfo}"); + } catch (\Exception $e) { + $this->logger->error("Email send exception in EmailChannel", [ + 'email' => $config['email'], + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); return false; } } - private function getSubject(array $data): string - { - if (isset($data['domain'])) { - $daysLeft = $data['days_left']; - if ($daysLeft <= 0) { - return "🚨 URGENT: Domain {$data['domain']} has EXPIRED"; - } - if ($daysLeft == 1) { - return "⚠️ CRITICAL: Domain {$data['domain']} expires TOMORROW"; - } - return "⚠️ Domain Expiration Alert: {$data['domain']} ({$daysLeft} days)"; - } - - return "Domain Monitor Alert"; - } - - private function formatHtmlBody(string $message, array $data, array $appSettings): string - { - $messageHtml = nl2br(htmlspecialchars($message)); - $appName = htmlspecialchars($appSettings['app_name']); - $appUrl = htmlspecialchars($appSettings['app_url']); - - // Build domain link if domain ID is available - $domainLink = ''; - if (isset($data['domain_id'])) { - $domainUrl = rtrim($appUrl, '/') . '/domains/' . $data['domain_id']; - $domainLink = "

View Domain Details

"; - } - - return " - - - - - -
-
-

🔔 {$appName} Alert

-
-
-

$messageHtml

- $domainLink -
- -
- - - "; - } } diff --git a/app/Views/settings/index.php b/app/Views/settings/index.php index c94ed8a..1a2a478 100644 --- a/app/Views/settings/index.php +++ b/app/Views/settings/index.php @@ -203,23 +203,18 @@ foreach ($notificationPresets as $key => $preset) {
-
- -
-
-

- - Protocol: This application uses SMTP (Simple Mail Transfer Protocol) for sending emails. -

-
+

+ + Will auto-update based on port selection +

@@ -648,6 +643,48 @@ foreach ($notificationPresets as $key => $preset) {