Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
7 / 7 |
CRAP | |
100.00% |
125 / 125 |
Mailer | |
100.00% |
1 / 1 |
|
100.00% |
7 / 7 |
47 | |
100.00% |
125 / 125 |
__construct | |
100.00% |
1 / 1 |
14 | |
100.00% |
32 / 32 |
|||
sendMail | |
100.00% |
1 / 1 |
3 | |
100.00% |
9 / 9 |
|||
configureSmtp | |
100.00% |
1 / 1 |
5 | |
100.00% |
17 / 17 |
|||
configureRecipients | |
100.00% |
1 / 1 |
6 | |
100.00% |
15 / 15 |
|||
configureBody | |
100.00% |
1 / 1 |
2 | |
100.00% |
5 / 5 |
|||
configureAttachments | |
100.00% |
1 / 1 |
8 | |
100.00% |
25 / 25 |
|||
configureOptions | |
100.00% |
1 / 1 |
9 | |
100.00% |
22 / 22 |
1 | <?php |
2 | |
3 | namespace Miniframe\Mailer\Middleware; |
4 | |
5 | use Miniframe\Mailer\Model\Attachment; |
6 | use Miniframe\Mailer\Model\Recipient; |
7 | use Miniframe\Core\AbstractMiddleware; |
8 | use Miniframe\Core\Config; |
9 | use Miniframe\Core\Request; |
10 | use Miniframe\Core\Response; |
11 | use PHPMailer\PHPMailer\PHPMailer; |
12 | use PHPMailer\PHPMailer\Exception; |
13 | |
14 | class Mailer extends AbstractMiddleware |
15 | { |
16 | /** |
17 | * SMTP config |
18 | * |
19 | * @var array|null |
20 | */ |
21 | protected $smtp = null; |
22 | |
23 | /** |
24 | * Initiates the Mailer middleware |
25 | * |
26 | * @param Request $request Reference to the Request object. |
27 | * @param Config $config Reference to the Config object. |
28 | */ |
29 | public function __construct(Request $request, Config $config) |
30 | { |
31 | parent::__construct($request, $config); |
32 | |
33 | $transport = 'sendmail'; |
34 | if ($config->has('mailer', 'transport')) { |
35 | $transport = $config->get('mailer', 'transport'); |
36 | if (!in_array($transport, ['sendmail', 'smtp'])) { |
37 | throw new \RuntimeException('Invalid mailer transport: ' . $transport); |
38 | } |
39 | } |
40 | |
41 | // Configure SMTP transport |
42 | if ($transport == 'smtp') { |
43 | $this->smtp = [ |
44 | 'hostname' => $config->get('mailer', 'smtp_hostname'), |
45 | 'encryption' => 'PLAIN', |
46 | ]; |
47 | if ($config->has('mailer', 'smtp_authentication')) { |
48 | $this->smtp['authentication'] = $config->get('mailer', 'smtp_authentication'); |
49 | if (!is_bool($this->smtp['authentication'])) { |
50 | throw new \RuntimeException('Mailer SMTP authentication value must be a boolean'); |
51 | } |
52 | } |
53 | if ($this->smtp['authentication']) { |
54 | $this->smtp['username'] = $config->get('mailer', 'smtp_username'); |
55 | $this->smtp['password'] = $config->get('mailer', 'smtp_password'); |
56 | } |
57 | if ($config->has('mailer', 'smtp_encryption')) { |
58 | $this->smtp['encryption'] = strtoupper($config->get('mailer', 'smtp_encryption')); |
59 | if (!in_array($this->smtp['encryption'], ['PLAIN', 'TLS', 'SSL'])) { |
60 | throw new \RuntimeException('Mailer SMTP encryption must be plain, tls or ssl'); |
61 | } |
62 | } |
63 | if ($config->has('mailer', 'smtp_port')) { |
64 | $this->smtp['port'] = $config->get('mailer', 'smtp_port'); |
65 | if (!is_numeric($this->smtp['port'])) { |
66 | throw new \RuntimeException('Mailer SMTP port value must be a numeric value'); |
67 | } |
68 | } elseif ($this->smtp['encryption'] == 'PLAIN') { |
69 | $this->smtp['port'] = 25; |
70 | } elseif ($this->smtp['encryption'] == 'TLS') { |
71 | $this->smtp['port'] = 587; |
72 | } elseif ($this->smtp['encryption'] == 'SSL') { |
73 | $this->smtp['port'] = 465; |
74 | } |
75 | } |
76 | } |
77 | |
78 | /** |
79 | * Sends an email. |
80 | * |
81 | * Valid options are: |
82 | * - from_name |
83 | * - from_email |
84 | * - replyto_name |
85 | * - replyto_email |
86 | * They may also be defined in the ini file as default. |
87 | * |
88 | * @param Recipient|Recipient[] $recipient One or more recipients. |
89 | * @param string $subject Subject of the mail. |
90 | * @param Response $mailBody A response object that returns a mail body. |
91 | * @param boolean $mailBodyIsHtml Set to false if the mail is plain text. |
92 | * @param Attachment|Attachment[]|null $attachment None, one or more attachments. |
93 | * @param array $options Additional options. |
94 | * |
95 | * @return void |
96 | * @throws Exception Can throw PHPMailer exceptions. |
97 | */ |
98 | public function sendMail( |
99 | $recipient, |
100 | string $subject, |
101 | Response $mailBody, |
102 | bool $mailBodyIsHtml = true, |
103 | $attachment = null, |
104 | array $options = array() |
105 | ): void { |
106 | $phpmailer = new PHPMailer(true); |
107 | $this->configureSmtp($phpmailer); |
108 | $this->configureRecipients($phpmailer, is_array($recipient) ? $recipient : [$recipient]); |
109 | $this->configureBody($phpmailer, $mailBody, $mailBodyIsHtml); |
110 | $this->configureAttachments($phpmailer, is_array($attachment) ? $attachment : [$attachment]); |
111 | $this->configureOptions($phpmailer, $options); |
112 | $phpmailer->Subject = $subject; |
113 | |
114 | $phpmailer->send(); |
115 | } |
116 | |
117 | /** |
118 | * Configures the SMTP properties for a PHPMailer instance |
119 | * |
120 | * @param PHPMailer $phpmailer The PHPMailer instance. |
121 | * |
122 | * @return void |
123 | */ |
124 | protected function configureSmtp(PHPMailer $phpmailer): void |
125 | { |
126 | if (!is_array($this->smtp)) { |
127 | return; |
128 | } |
129 | $phpmailer->isSMTP(); |
130 | $phpmailer->Host = $this->smtp['hostname']; |
131 | $phpmailer->SMTPAuth = $this->smtp['authentication']; |
132 | if ($this->smtp['authentication']) { |
133 | $phpmailer->Username = $this->smtp['username']; |
134 | $phpmailer->Password = $this->smtp['password']; |
135 | } |
136 | switch ($this->smtp['encryption']) { |
137 | case 'SSL': |
138 | $phpmailer->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; |
139 | break; |
140 | case 'TLS': |
141 | $phpmailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; |
142 | break; |
143 | } |
144 | $phpmailer->Port = $this->smtp['port']; |
145 | } |
146 | |
147 | /** |
148 | * Adds the recipients to PHPMailer |
149 | * |
150 | * @param PHPMailer $phpmailer The PHPMailer instance. |
151 | * @param Recipient[] $recipients List of recipients. |
152 | * |
153 | * @return void |
154 | */ |
155 | protected function configureRecipients(PHPMailer $phpmailer, array $recipients): void |
156 | { |
157 | foreach ($recipients as $recipient) { |
158 | if (!($recipient instanceof Recipient)) { |
159 | throw new \InvalidArgumentException( |
160 | 'The recipient property must be one or more Recipient models, not ' . gettype($recipient) |
161 | ); |
162 | } |
163 | switch ($recipient->getHeader()) { |
164 | case Recipient::HEADER_TO: |
165 | $phpmailer->addAddress($recipient->getEmail(), $recipient->getName()); |
166 | break; |
167 | case Recipient::HEADER_CC: |
168 | $phpmailer->addCC($recipient->getEmail(), $recipient->getName()); |
169 | break; |
170 | case Recipient::HEADER_BCC: |
171 | $phpmailer->addBCC($recipient->getEmail(), $recipient->getName()); |
172 | break; |
173 | } |
174 | } |
175 | } |
176 | |
177 | /** |
178 | * Configures the mail body |
179 | * |
180 | * @param PHPMailer $phpmailer The PHPMailer instance. |
181 | * @param Response $mailBody A response object that returns a mail body. |
182 | * @param boolean $isHtml Set to false if the mail is plain text. |
183 | * |
184 | * @return void |
185 | */ |
186 | protected function configureBody(PHPMailer $phpmailer, Response $mailBody, bool $isHtml): void |
187 | { |
188 | $phpmailer->isHTML($isHtml); |
189 | if ($isHtml) { |
190 | $phpmailer->msgHTML($mailBody->render()); |
191 | } else { |
192 | $phpmailer->Body = $mailBody->render(); |
193 | } |
194 | } |
195 | |
196 | /** |
197 | * Configures the attachments. |
198 | * |
199 | * @param PHPMailer $phpmailer The PHPMailer instance. |
200 | * @param Attachment[] $attachments List of attachments. |
201 | * |
202 | * @return void |
203 | */ |
204 | protected function configureAttachments(PHPMailer $phpmailer, array $attachments): void |
205 | { |
206 | if ($attachments === [null]) { |
207 | return; |
208 | } |
209 | foreach ($attachments as $attachment) { |
210 | if (!($attachment instanceof Attachment)) { |
211 | throw new \InvalidArgumentException( |
212 | 'The attachment property must be zero, one or more Attachment models, not ' |
213 | . gettype($attachment) |
214 | ); |
215 | } |
216 | switch ($attachment->getDisposition()) { |
217 | case Attachment::DISPOSITION_INLINE: |
218 | $disposition = 'inline'; |
219 | break; |
220 | case Attachment::DISPOSITION_ATTACHMENT: |
221 | default: |
222 | $disposition = 'attachment'; |
223 | break; |
224 | } |
225 | |
226 | if ($attachment->getFullPath()) { |
227 | $phpmailer->addAttachment( |
228 | $attachment->getFullPath(), |
229 | $attachment->getFilename(), |
230 | PHPMailer::ENCODING_BASE64, |
231 | $attachment->getContentType(), |
232 | $disposition |
233 | ); |
234 | } else { |
235 | $phpmailer->addStringAttachment( |
236 | $attachment->getAttachmentData(), |
237 | $attachment->getFilename(), |
238 | PHPMailer::ENCODING_BASE64, |
239 | $attachment->getContentType(), |
240 | $disposition |
241 | ); |
242 | } |
243 | } |
244 | } |
245 | |
246 | /** |
247 | * Parse additional options. |
248 | * |
249 | * @param PHPMailer $phpmailer The PHPMailer instance. |
250 | * @param array $options Additional options. |
251 | * |
252 | * @return void |
253 | */ |
254 | protected function configureOptions(PHPMailer $phpmailer, array $options): void |
255 | { |
256 | // Validate options |
257 | foreach (array_keys($options) as $optionKey) { |
258 | if (!in_array($optionKey, ['from_name', 'from_email', 'replyto_name', 'replyto_email'])) { |
259 | throw new \InvalidArgumentException('Invalid option: ' . $optionKey); |
260 | } |
261 | } |
262 | |
263 | // Default sender (from_email is required, in config or in options) |
264 | $fromEmail = $options['from_email'] ?? $this->config->get('mailer', 'from_email'); |
265 | if (isset($options['from_name'])) { |
266 | $fromName = $options['from_name']; |
267 | } elseif ($this->config->has('mailer', 'from_name')) { |
268 | $fromName = $this->config->get('mailer', 'from_name'); |
269 | } else { |
270 | $fromName = ucfirst(explode('@', $fromEmail, 2)[0]); |
271 | } |
272 | $phpmailer->setFrom($fromEmail, $fromName); |
273 | |
274 | // Reply to address |
275 | if (isset($options['replyto_email'])) { |
276 | $replyToEmail = $options['replyto_email']; |
277 | } elseif ($this->config->has('mailer', 'replyto_email')) { |
278 | $replyToEmail = $this->config->get('mailer', 'replyto_email'); |
279 | } else { |
280 | $replyToEmail = $fromEmail; |
281 | } |
282 | // Reply to name |
283 | if (isset($options['replyto_name'])) { |
284 | $replyToName = $options['replyto_name']; |
285 | } elseif ($this->config->has('mailer', 'replyto_name')) { |
286 | $replyToName = $this->config->get('mailer', 'replyto_name'); |
287 | } else { |
288 | $replyToName = ucfirst(explode('@', $replyToEmail, 2)[0]); |
289 | } |
290 | $phpmailer->addReplyTo($replyToEmail, $replyToName); |
291 | } |
292 | } |