· 7 months ago · Mar 12, 2025, 05:35 PM
1<?php
2
3namespace App\Services;
4
5use PHPMailer\PHPMailer\Exception;
6use PHPMailer\PHPMailer\PHPMailer;
7
8/**
9 * Email Service
10 *
11 * Handles sending emails using PHPMailer
12 */
13class EmailService
14{
15 /**
16 * SMTP Configuration
17 */
18 private $host;
19 private $port;
20 private $username;
21 private $password;
22 private $encryption;
23 private $fromEmail;
24 private $fromName;
25
26 /**
27 * Debug mode
28 * @var bool
29 */
30 private $debug;
31
32 /**
33 * Initialize email service
34 */
35 public function __construct()
36 {
37 // Load configuration
38 $this->host = MAIL_HOST;
39 $this->port = MAIL_PORT;
40 $this->username = MAIL_USERNAME;
41 $this->password = MAIL_PASSWORD;
42 $this->encryption = MAIL_ENCRYPTION;
43 $this->fromEmail = MAIL_FROM_ADDRESS;
44 $this->fromName = MAIL_FROM_NAME;
45
46 // Set debug mode based on config
47 $this->debug = defined('MAIL_DEBUG') ? MAIL_DEBUG : APP_DEBUG;
48 }
49
50 /**
51 * Send an email
52 *
53 * @param string $to
54 * @param string $subject
55 * @param string $body
56 * @param bool $isHtml
57 * @return bool
58 */
59 public function send($to, $subject, $body, $isHtml = true)
60 {
61
62 // Create a new PHPMailer instance
63 $mail = new PHPMailer();
64
65 try {
66 // Server settings
67 $mail->isSMTP();
68 $mail->Host = $this->host;
69 $mail->SMTPAuth = true;
70 $mail->Username = $this->username;
71 $mail->Password = $this->password;
72 $mail->SMTPSecure = $this->encryption;
73 $mail->Port = $this->port;
74
75 $mail->SMTPDebug = $this->debug;
76
77 // Recipients
78 $mail->setFrom($this->fromEmail, $this->fromName);
79 $mail->addAddress($to);
80
81 // Content
82 $mail->isHTML($isHtml);
83 $mail->Subject = $subject;
84 $mail->Body = $body;
85
86 // Send the email
87 $response = $mail->send();
88
89 if ($this->debug) {
90 $this->logEmail($to, $subject, $body, $response);
91 }
92
93 return true;
94 } catch (\Exception $e) {
95 // Log the error
96 error_log('Email sending failed: ' . $mail->ErrorInfo);
97 return false;
98 }
99 }
100
101 /**
102 * Send a password reset email
103 *
104 * @param string $to
105 * @param string $resetLink
106 * @return bool
107 */
108 public function sendPasswordReset($to, $resetLink)
109 {
110 $subject = APP_NAME . ' - Password Reset Request';
111
112 $body = '
113 <html>
114 <head>
115 <title>Password Reset</title>
116 </head>
117 <body>
118 <div style="max-width: 600px; margin: 0 auto; padding: 20px; font-family: Arial, sans-serif;">
119 <h2 style="color: #3498db;">Password Reset Request</h2>
120 <p>You recently requested to reset your password for your ' . APP_NAME . ' account. Click the button below to reset it.</p>
121 <p style="margin: 30px 0;">
122 <a href="' . $resetLink . '" style="background-color: #3498db; color: #ffffff; text-decoration: none; padding: 10px 20px; border-radius: 5px; display: inline-block;">Reset Your Password</a>
123 </p>
124 <p>If you did not request a password reset, please ignore this email or contact support if you have questions.</p>
125 <p>This password reset link is only valid for one hour.</p>
126 <hr style="border: 1px solid #f2f2f2; margin: 20px 0;">
127 <p style="font-size: 12px; color: #777777;">If you\'re having trouble clicking the password reset button, copy and paste the URL below into your web browser:</p>
128 <p style="font-size: 12px; color: #777777;">' . $resetLink . '</p>
129 </div>
130 </body>
131 </html>';
132
133 return $this->send($to, $subject, $body);
134 }
135
136 /**
137 * Log email details instead of sending
138 *
139 * @param string $to
140 * @param string $subject
141 * @param string $body
142 */
143 private function logEmail($to, $subject, $body, $response)
144 {
145 $logDir = BASE_PATH . '/logs';
146
147 // Create logs directory if it doesn't exist
148 if (!is_dir($logDir)) {
149 mkdir($logDir, 0755, true);
150 }
151
152 $logFile = $logDir . '/email_debug.log';
153
154 // Format the log entry
155 $logEntry = "==========================================================\n";
156 $logEntry .= "Date: " . date('Y-m-d H:i:s') . "\n";
157 $logEntry .= "To: $to\n";
158 $logEntry .= "Subject: $subject\n";
159 $logEntry .= "Body:\n$body\n\n";
160 $logEntry .= "Response Server:\n$response\n\n";
161
162 // Write to log file
163 file_put_contents($logFile, $logEntry, FILE_APPEND);
164
165 // Also output to error log for easy debugging
166 error_log("DEBUG MODE - Email would be sent to: $to with subject: $subject");
167 }
168
169 /**
170 * Send an account verification email
171 *
172 * @param string $to
173 * @param string $verificationLink
174 * @return bool
175 */
176 public function sendAccountVerification($to, $verificationLink)
177 {
178 $subject = APP_NAME . ' - Verify Your Email Address';
179
180 $body = '
181 <html>
182 <head>
183 <title>Verify Your Email</title>
184 </head>
185 <body>
186 <div style="max-width: 600px; margin: 0 auto; padding: 20px; font-family: Arial, sans-serif;">
187 <h2 style="color: #3498db;">Verify Your Email Address</h2>
188 <p>Thank you for registering with ' . APP_NAME . '. Please click the button below to verify your email address.</p>
189 <p style="margin: 30px 0;">
190 <a href="' . $verificationLink . '" style="background-color: #3498db; color: #ffffff; text-decoration: none; padding: 10px 20px; border-radius: 5px; display: inline-block;">Verify Email Address</a>
191 </p>
192 <p>If you did not create an account, no further action is required.</p>
193 <p>This verification link is valid for 48 hours.</p>
194 <hr style="border: 1px solid #f2f2f2; margin: 20px 0;">
195 <p style="font-size: 12px; color: #777777;">If you\'re having trouble clicking the verification button, copy and paste the URL below into your web browser:</p>
196 <p style="font-size: 12px; color: #777777;">' . $verificationLink . '</p>
197 </div>
198 </body>
199 </html>';
200
201 return $this->send($to, $subject, $body);
202 }
203
204 /**
205 * Send a notification about suspicious login
206 *
207 * @param string $to User's email
208 * @param array $loginDetails Details about the suspicious login
209 * @return bool
210 */
211 public function sendSuspiciousLoginAlert($to, $loginDetails)
212 {
213 $subject = APP_NAME . ' - Suspicious Login Detected';
214
215 $ipAddress = $loginDetails['ip_address'] ?? 'Unknown';
216 $location = $loginDetails['location'] ?? 'Unknown';
217 $browser = $loginDetails['browser'] ?? 'Unknown';
218 $os = $loginDetails['os'] ?? 'Unknown';
219 $deviceType = $loginDetails['device_type'] ?? 'Unknown';
220 $time = $loginDetails['created_at'] ?? 'Unknown';
221
222 $body = '
223 <html>
224 <head>
225 <title>Suspicious Login Alert</title>
226 </head>
227 <body>
228 <div style="max-width: 600px; margin: 0 auto; padding: 20px; font-family: Arial, sans-serif;">
229 <h2 style="color: #d9534f;">Suspicious Login Detected</h2>
230 <p>We detected a login to your ' . APP_NAME . ' account from a new location or device.</p>
231
232 <div style="background-color: #f8f9fa; border-left: 4px solid #d9534f; padding: 15px; margin: 20px 0;">
233 <h3 style="margin-top: 0;">Login Details:</h3>
234 <p><strong>Time:</strong> ' . $time . '</p>
235 <p><strong>IP Address:</strong> ' . $ipAddress . '</p>
236 <p><strong>Location:</strong> ' . $location . '</p>
237 <p><strong>Browser:</strong> ' . $browser . '</p>
238 <p><strong>Operating System:</strong> ' . $os . '</p>
239 <p><strong>Device:</strong> ' . $deviceType . '</p>
240 </div>
241
242 <p>If this was you, you can ignore this message.</p>
243 <p>If you did not log in at this time, please change your password immediately and enable two-factor authentication for additional security.</p>
244
245 <p style="margin: 30px 0;">
246 <a href="' . APP_URL . '/account/security" style="background-color: #d9534f; color: #ffffff; text-decoration: none; padding: 10px 20px; border-radius: 5px; display: inline-block;">Review Account Security</a>
247 </p>
248
249 <p>If you need assistance, please contact our support team.</p>
250 </div>
251 </body>
252 </html>';
253
254 return $this->send($to, $subject, $body);
255 }
256
257
258 /**
259 * Set debug mode
260 *
261 * @param bool $debug
262 */
263 public function setDebug($debug)
264 {
265 $this->debug = (bool)$debug;
266 }
267
268 /**
269 * Get current debug status
270 *
271 * @return bool
272 */
273 public function isDebug()
274 {
275 return $this->debug;
276 }
277}
278