1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Jacobemerick\Archangel; |
4
|
|
|
|
5
|
|
|
use Psr\Log\LoggerAwareInterface; |
6
|
|
|
use Psr\Log\LoggerInterface; |
7
|
|
|
use Psr\Log\NullLogger; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* This is the main class for Archangel mailer |
11
|
|
|
* For licensing and examples: |
12
|
|
|
* @see https://github.com/jacobemerick/archangel |
13
|
|
|
* |
14
|
|
|
* @author jacobemerick (http://home.jacobemerick.com/) |
15
|
|
|
*/ |
16
|
|
|
class Archangel implements LoggerAwareInterface |
17
|
|
|
{ |
18
|
|
|
|
19
|
|
|
/** @var boolean $isTestMode */ |
20
|
|
|
protected $isTestMode; |
21
|
|
|
|
22
|
|
|
/** @var string $subject */ |
23
|
|
|
protected $subject; |
24
|
|
|
|
25
|
|
|
/** @var array $toAddresses */ |
26
|
|
|
protected $toAddresses = array(); |
27
|
|
|
|
28
|
|
|
/** @var array $headers */ |
29
|
|
|
protected $headers = array(); |
30
|
|
|
|
31
|
|
|
/** @var string $plainMessage */ |
32
|
|
|
protected $plainMessage; |
33
|
|
|
|
34
|
|
|
/** @var string $htmlMessage */ |
35
|
|
|
protected $htmlMessage; |
36
|
|
|
|
37
|
|
|
/** @var array $attachments */ |
38
|
|
|
protected $attachments = array(); |
39
|
|
|
|
40
|
|
|
/** @var string $boundaryMixed */ |
41
|
|
|
protected $boundaryMixed; |
42
|
|
|
|
43
|
|
|
/** @var string $boundaryAlternative */ |
44
|
|
|
protected $boundaryAlternative; |
45
|
|
|
|
46
|
|
|
/** @var LoggerInterface */ |
47
|
|
|
protected $logger; |
48
|
|
|
|
49
|
|
|
/** @var string LINE_BREAK */ |
50
|
|
|
const LINE_BREAK = "\r\n"; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @param string $mailer |
54
|
|
|
* @param boolean $isTestMode |
55
|
|
|
*/ |
56
|
|
|
public function __construct($mailer = null, $isTestMode = false) |
57
|
|
|
{ |
58
|
|
|
if (is_null($mailer)) { |
59
|
|
|
$mailer = sprintf('PHP/%s', phpversion()); |
60
|
|
|
} |
61
|
|
|
$this->headers['X-Mailer'] = $mailer; |
62
|
|
|
$this->isTestMode = $isTestMode; |
63
|
|
|
|
64
|
|
|
$this->logger = new NullLogger(); |
65
|
|
|
$this->boundaryMixed = sprintf('PHP-mixed-%s', uniqid()); |
66
|
|
|
$this->boundaryAlternative = sprintf('PHP-alternative-%s', uniqid()); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @param LoggerInterface $logger |
71
|
|
|
* |
72
|
|
|
* @return object instantiated $this |
73
|
|
|
*/ |
74
|
|
|
public function setLogger(LoggerInterface $logger) |
75
|
|
|
{ |
76
|
|
|
$this->logger = $logger; |
77
|
|
|
|
78
|
|
|
return $this; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Setter method for adding recipients |
83
|
|
|
* |
84
|
|
|
* @param string $address email address for the recipient |
85
|
|
|
* @param string $title name of the recipient (optional) |
86
|
|
|
|
87
|
|
|
* @return object instantiated $this |
88
|
|
|
*/ |
89
|
|
|
public function addTo($address, $title = '') |
90
|
|
|
{ |
91
|
|
|
array_push( |
92
|
|
|
$this->toAddresses, |
93
|
|
|
$this->formatEmailAddress($address, $title) |
94
|
|
|
); |
95
|
|
|
|
96
|
|
|
return $this; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Setter method for adding cc recipients |
101
|
|
|
* |
102
|
|
|
* @param string $address email address for the cc recipient |
103
|
|
|
* @param string $title name of the cc recipient (optional) |
104
|
|
|
* |
105
|
|
|
* @return object instantiated $this |
106
|
|
|
*/ |
107
|
|
View Code Duplication |
public function addCC($address, $title = '') |
|
|
|
|
108
|
|
|
{ |
109
|
|
|
if (!isset($this->headers['CC'])) { |
110
|
|
|
$this->headers['CC'] = array(); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
array_push( |
114
|
|
|
$this->headers['CC'], |
115
|
|
|
$this->formatEmailAddress($address, $title) |
116
|
|
|
); |
117
|
|
|
|
118
|
|
|
return $this; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Setter method for adding bcc recipients |
123
|
|
|
* |
124
|
|
|
* @param string $address email address for the bcc recipient |
125
|
|
|
* @param string $title name of the bcc recipient (optional) |
126
|
|
|
* |
127
|
|
|
* @return object instantiated $this |
128
|
|
|
*/ |
129
|
|
View Code Duplication |
public function addBCC($address, $title = '') |
|
|
|
|
130
|
|
|
{ |
131
|
|
|
if (!isset($this->headers['BCC'])) { |
132
|
|
|
$this->headers['BCC'] = array(); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
array_push( |
136
|
|
|
$this->headers['BCC'], |
137
|
|
|
$this->formatEmailAddress($address, $title) |
138
|
|
|
); |
139
|
|
|
|
140
|
|
|
return $this; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Setter method for setting the single 'from' field |
145
|
|
|
* |
146
|
|
|
* @param string $address email address for the sender |
147
|
|
|
* @param string $title name of the sender (optional) |
148
|
|
|
* |
149
|
|
|
* @return object instantiated $this |
150
|
|
|
*/ |
151
|
|
|
public function setFrom($address, $title = '') |
152
|
|
|
{ |
153
|
|
|
$this->headers['From'] = $this->formatEmailAddress($address, $title); |
154
|
|
|
|
155
|
|
|
return $this; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Setter method for setting the single 'reply-to' field |
160
|
|
|
* |
161
|
|
|
* @param string $address email address for the reply-to |
162
|
|
|
* @param string $title name of the reply-to (optional) |
163
|
|
|
* |
164
|
|
|
* @return object instantiated $this |
165
|
|
|
*/ |
166
|
|
|
public function setReplyTo($address, $title = '') |
167
|
|
|
{ |
168
|
|
|
$this->headers['Reply-To'] = $this->formatEmailAddress($address, $title); |
169
|
|
|
|
170
|
|
|
return $this; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* @param string $address |
175
|
|
|
* @param string $title |
176
|
|
|
* |
177
|
|
|
* @return string |
178
|
|
|
*/ |
179
|
|
|
protected function formatEmailAddress($address, $title) |
180
|
|
|
{ |
181
|
|
|
if (!empty($title)) { |
182
|
|
|
$address = sprintf('"%s" <%s>', $title, $address); |
183
|
|
|
} |
184
|
|
|
return $address; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Setter method for setting a subject |
189
|
|
|
* |
190
|
|
|
* @param string $subject subject for the email |
191
|
|
|
* |
192
|
|
|
* @return object instantiated $this |
193
|
|
|
*/ |
194
|
|
|
public function setSubject($subject) |
195
|
|
|
{ |
196
|
|
|
$this->subject = $subject; |
197
|
|
|
|
198
|
|
|
return $this; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Setter method for the plain text message |
203
|
|
|
* |
204
|
|
|
* @param string $message the plain-text message |
205
|
|
|
* |
206
|
|
|
* @return object instantiated $this |
207
|
|
|
*/ |
208
|
|
|
public function setPlainMessage($message) |
209
|
|
|
{ |
210
|
|
|
$this->plainMessage = $message; |
211
|
|
|
|
212
|
|
|
return $this; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Setter method for the html message |
217
|
|
|
* |
218
|
|
|
* @param string $message the html message |
219
|
|
|
* |
220
|
|
|
* @return object instantiated $this |
221
|
|
|
*/ |
222
|
|
|
public function setHTMLMessage($message) |
223
|
|
|
{ |
224
|
|
|
$this->htmlMessage = $message; |
225
|
|
|
|
226
|
|
|
return $this; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Setter method for adding attachments |
231
|
|
|
* |
232
|
|
|
* @param string $path the full path of the attachment |
233
|
|
|
* @param string $type mime type of the file |
234
|
|
|
* @param string $title the title of the attachment (optional) |
235
|
|
|
* |
236
|
|
|
* @return object instantiated $this |
237
|
|
|
*/ |
238
|
|
|
public function addAttachment($path, $type, $title = '') |
239
|
|
|
{ |
240
|
|
|
array_push($this->attachments, array( |
241
|
|
|
'path' => $path, |
242
|
|
|
'type' => $type, |
243
|
|
|
'title' => $title, |
244
|
|
|
)); |
245
|
|
|
|
246
|
|
|
return $this; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* The executing step, the actual sending of the email |
251
|
|
|
* First checks to make sure the minimum fields are set (returns false if they are not) |
252
|
|
|
* Second it attempts to send the mail with php's mail() (returns false if it fails) |
253
|
|
|
* |
254
|
|
|
* @return boolean whether or not the email was valid & sent |
255
|
|
|
*/ |
256
|
|
|
public function send() |
257
|
|
|
{ |
258
|
|
|
if (!$this->checkRequiredFields()) { |
259
|
|
|
$this->logger->error('Minimum required fields not filled out, cannot send Archangel mail.'); |
260
|
|
|
return false; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
$recipients = $this->buildTo(); |
264
|
|
|
$subject = $this->subject; |
265
|
|
|
$message = (empty($this->attachments)) ? $this->buildMessage() : $this->buildMessageWithAttachments(); |
266
|
|
|
$headers = $this->buildHeaders(); |
267
|
|
|
|
268
|
|
|
$debugMessage = array( |
269
|
|
|
'Triggered send on Archangel mail.', |
270
|
|
|
"Recipients: {$recipients}", |
271
|
|
|
"Subject: {$subject}", |
272
|
|
|
"Message: {$message}", |
273
|
|
|
"Headers: {$headers}", |
274
|
|
|
); |
275
|
|
|
$this->logger->debug(implode(' || ', $debugMessage)); |
276
|
|
|
|
277
|
|
|
if ($this->isTestMode) { |
278
|
|
|
return true; |
279
|
|
|
} |
280
|
|
|
return mail($recipients, $subject, $message, $headers); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Call to check the minimum required fields |
285
|
|
|
* |
286
|
|
|
* @return boolean whether or not the email meets the minimum required fields |
287
|
|
|
*/ |
288
|
|
|
protected function checkRequiredFields() |
289
|
|
|
{ |
290
|
|
|
if (empty($this->toAddresses)) { |
291
|
|
|
return false; |
292
|
|
|
} |
293
|
|
|
if (empty($this->subject)) { |
294
|
|
|
return false; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
if (empty($this->plainMessage) && empty($this->htmlMessage) && empty($this->attachments)) { |
298
|
|
|
return false; |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
return true; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Build the recipients from 'to' |
306
|
|
|
* |
307
|
|
|
* @return string comma-separated lit of recipients |
308
|
|
|
*/ |
309
|
|
|
protected function buildTo() |
310
|
|
|
{ |
311
|
|
|
return implode(', ', $this->toAddresses); |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* Returns a simple email message without attachments |
316
|
|
|
* |
317
|
|
|
* @return string email message |
318
|
|
|
*/ |
319
|
|
|
protected function buildMessage() |
320
|
|
|
{ |
321
|
|
|
if (empty($this->plainMessage) && empty($this->htmlMessage)) { |
322
|
|
|
return ''; |
323
|
|
|
} |
324
|
|
|
if (!empty($this->plainMessage) && empty($this->htmlMessage)) { |
325
|
|
|
return $this->plainMessage; |
326
|
|
|
} |
327
|
|
|
if (empty($this->plainMessage) && !empty($this->htmlMessage)) { |
328
|
|
|
return $this->htmlMessage; |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
$message = array(); |
332
|
|
|
array_push($message, "--{$this->boundaryAlternative}"); |
333
|
|
|
$message = array_merge($message, $this->buildPlainMessageHeader()); |
334
|
|
|
array_push($message, $this->plainMessage); |
335
|
|
|
array_push($message, "--{$this->boundaryAlternative}"); |
336
|
|
|
$message = array_merge($message, $this->buildHtmlMessageHeader()); |
337
|
|
|
array_push($message, $this->htmlMessage); |
338
|
|
|
array_push($message, "--{$this->boundaryAlternative}--"); |
339
|
|
|
|
340
|
|
|
return implode(self::LINE_BREAK, $message); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* Build multi-part message with attachments |
345
|
|
|
* |
346
|
|
|
* @return string email message |
347
|
|
|
*/ |
348
|
|
|
protected function buildMessageWithAttachments() |
349
|
|
|
{ |
350
|
|
|
$message = array(); |
351
|
|
|
|
352
|
|
View Code Duplication |
if (!empty($this->plainMessage) || !empty($this->htmlMessage)) { |
|
|
|
|
353
|
|
|
array_push($message, "--{$this->boundaryMixed}"); |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
if (!empty($this->plainMessage) && !empty($this->htmlMessage)) { |
357
|
|
|
array_push($message, "Content-Type: multipart/alternative; boundary={$this->boundaryAlternative}"); |
358
|
|
|
array_push($message, ''); |
359
|
|
|
array_push($message, "--{$this->boundaryAlternative}"); |
360
|
|
|
$message = array_merge($message, $this->buildPlainMessageHeader()); |
361
|
|
|
array_push($message, $this->plainMessage); |
362
|
|
|
array_push($message, "--{$this->boundaryAlternative}"); |
363
|
|
|
$message = array_merge($message, $this->buildHtmlMessageHeader()); |
364
|
|
|
array_push($message, $this->htmlMessage); |
365
|
|
|
array_push($message, "--{$this->boundaryAlternative}--"); |
366
|
|
|
array_push($message, ''); |
367
|
|
|
} elseif (!empty($this->plainMessage)) { |
368
|
|
|
$message = array_merge($message, $this->buildPlainMessageHeader()); |
369
|
|
|
array_push($message, $this->plainMessage); |
370
|
|
|
} elseif (!empty($this->htmlMessage)) { |
371
|
|
|
$message = array_merge($message, $this->buildHtmlMessageHeader()); |
372
|
|
|
array_push($message, $this->htmlMessage); |
373
|
|
|
} |
374
|
|
|
foreach ($this->attachments as $attachment) { |
375
|
|
|
array_push($message, "--{$this->boundaryMixed}"); |
376
|
|
|
array_push($message, "Content-Type: {$attachment['type']}; name=\"{$attachment['title']}\""); |
377
|
|
|
array_push($message, 'Content-Transfer-Encoding: base64'); |
378
|
|
|
array_push($message, 'Content-Disposition: attachment'); |
379
|
|
|
array_push($message, ''); |
380
|
|
|
array_push($message, $this->buildAttachmentContent($attachment['path'])); |
381
|
|
|
} |
382
|
|
|
array_push($message, "--{$this->boundaryMixed}--"); |
383
|
|
|
|
384
|
|
|
return implode(self::LINE_BREAK, $message); |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
|
388
|
|
|
/** |
389
|
|
|
* Shared holder for the plain message header |
390
|
|
|
* |
391
|
|
|
* @return array |
392
|
|
|
*/ |
393
|
|
|
protected function buildPlainMessageHeader() |
394
|
|
|
{ |
395
|
|
|
return array( |
396
|
|
|
'Content-Type: text/plain; charset="iso-8859"', |
397
|
|
|
'Content-Transfer-Encoding: 7bit', |
398
|
|
|
'', |
399
|
|
|
); |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* Shared holder for the html message header |
404
|
|
|
* |
405
|
|
|
* @return array |
406
|
|
|
*/ |
407
|
|
|
protected function buildHtmlMessageHeader() |
408
|
|
|
{ |
409
|
|
|
return array( |
410
|
|
|
'Content-Type: text/html; charset="iso-8859-1"', |
411
|
|
|
'Content-Transfer-Encoding: 7bit', |
412
|
|
|
'', |
413
|
|
|
); |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
/** |
417
|
|
|
* Builder for the additional headers needed for multipart emails |
418
|
|
|
* |
419
|
|
|
* @return string headers needed for multipart |
420
|
|
|
*/ |
421
|
|
|
protected function buildHeaders() |
422
|
|
|
{ |
423
|
|
|
$headers = array(); |
424
|
|
|
foreach ($this->headers as $key => $value) { |
425
|
|
|
if ($key == 'CC' || $key == 'BCC') { |
426
|
|
|
$value = implode(', ', $value); |
427
|
|
|
} |
428
|
|
|
array_push($headers, sprintf('%s: %s', $key, $value)); |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
if (!empty($this->attachments)) { |
432
|
|
|
array_push( |
433
|
|
|
$headers, |
434
|
|
|
"Content-Type: multipart/mixed; boundary=\"{$this->boundaryMixed}\"" |
435
|
|
|
); |
436
|
|
View Code Duplication |
} elseif (!empty($this->plainMessage) && !empty($this->htmlMessage)) { |
|
|
|
|
437
|
|
|
array_push( |
438
|
|
|
$headers, |
439
|
|
|
"Content-Type: multipart/alternative; boundary=\"{$this->boundaryAlternative}\"" |
440
|
|
|
); |
441
|
|
|
} elseif (!empty($this->htmlMessage)) { |
442
|
|
|
array_push( |
443
|
|
|
$headers, |
444
|
|
|
'Content-type: text/html; charset="iso-8859-1"' |
445
|
|
|
); |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
return implode(self::LINE_BREAK, $headers); |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
/** |
452
|
|
|
* File reader for attachments |
453
|
|
|
* |
454
|
|
|
* @param string $path filepath of the attachment |
455
|
|
|
* |
456
|
|
|
* @return string binary representation of file, base64'd |
457
|
|
|
*/ |
458
|
|
|
protected function buildAttachmentContent($path) |
459
|
|
|
{ |
460
|
|
|
if (!file_exists($path)) { |
461
|
|
|
$this->logger->error("Could not find file {$path} for attaching to Archangel mail."); |
462
|
|
|
return ''; |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
$handle = fopen($path, 'r'); |
466
|
|
|
$contents = fread($handle, filesize($path)); |
467
|
|
|
fclose($handle); |
468
|
|
|
|
469
|
|
|
$contents = base64_encode($contents); |
470
|
|
|
$contents = chunk_split($contents); |
471
|
|
|
return $contents; |
472
|
|
|
} |
473
|
|
|
} |
474
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.