1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Charcoal\Email; |
4
|
|
|
|
5
|
|
|
use Exception; |
6
|
|
|
use InvalidArgumentException; |
7
|
|
|
|
8
|
|
|
// PSR-3 (logger) dependencies |
9
|
|
|
use Psr\Log\LoggerAwareInterface; |
10
|
|
|
use Psr\Log\LoggerAwareTrait; |
11
|
|
|
|
12
|
|
|
// From 'phpmailer/phpmailer' |
13
|
|
|
use PHPMailer\PHPMailer\PHPMailer; |
14
|
|
|
|
15
|
|
|
// From 'charcoal-config' |
16
|
|
|
use Charcoal\Config\ConfigurableInterface; |
17
|
|
|
use Charcoal\Config\ConfigurableTrait; |
18
|
|
|
|
19
|
|
|
// Module 'charcoal-factory' |
20
|
|
|
use Charcoal\Factory\FactoryInterface; |
21
|
|
|
|
22
|
|
|
// Module 'charcoal-view' |
23
|
|
|
use Charcoal\View\GenericView; |
24
|
|
|
use Charcoal\View\ViewableInterface; |
25
|
|
|
use Charcoal\View\ViewableTrait; |
26
|
|
|
|
27
|
|
|
// Module 'charcoal-queue' |
28
|
|
|
use Charcoal\Queue\QueueableInterface; |
29
|
|
|
use Charcoal\Queue\QueueableTrait; |
30
|
|
|
|
31
|
|
|
|
32
|
|
|
use Charcoal\Email\EmailInterface; |
33
|
|
|
use Charcoal\Email\EmailConfig; |
34
|
|
|
use Charcoal\Email\EmailLog; |
35
|
|
|
use Charcoal\Email\EmailQueueItem; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Default implementation of the `EmailInterface`. |
39
|
|
|
*/ |
40
|
|
|
class Email implements |
41
|
|
|
ConfigurableInterface, |
42
|
|
|
EmailInterface, |
43
|
|
|
LoggerAwareInterface, |
44
|
|
|
QueueableInterface, |
45
|
|
|
ViewableInterface |
46
|
|
|
{ |
47
|
|
|
use ConfigurableTrait; |
48
|
|
|
use LoggerAwareTrait; |
49
|
|
|
use QueueableTrait; |
50
|
|
|
use ViewableTrait; |
51
|
|
|
use EmailAwareTrait; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* The campaign ID. |
55
|
|
|
* |
56
|
|
|
* @var string $campaign |
57
|
|
|
*/ |
58
|
|
|
private $campaign; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* The recipient email address(es). |
62
|
|
|
* |
63
|
|
|
* @var array $to |
64
|
|
|
*/ |
65
|
|
|
private $to = []; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* The CC recipient email address(es). |
69
|
|
|
* |
70
|
|
|
* @var array $cc |
71
|
|
|
*/ |
72
|
|
|
private $cc = []; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* The BCC recipient email address(es). |
76
|
|
|
* |
77
|
|
|
* @var array $bcc |
78
|
|
|
*/ |
79
|
|
|
private $bcc = []; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* The sender's email address. |
83
|
|
|
* |
84
|
|
|
* @var string $from |
85
|
|
|
*/ |
86
|
|
|
private $from; |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* The email address to reply to the message. |
90
|
|
|
* |
91
|
|
|
* @var string $replyTo |
92
|
|
|
*/ |
93
|
|
|
private $replyTo; |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* The email subject. |
97
|
|
|
* |
98
|
|
|
* @var string $subject |
99
|
|
|
*/ |
100
|
|
|
private $subject; |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* The HTML message body. |
104
|
|
|
* |
105
|
|
|
* @var string $msgHtml |
106
|
|
|
*/ |
107
|
|
|
private $msgHtml; |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* The plain-text message body. |
111
|
|
|
* |
112
|
|
|
* @var string $msgTxt |
113
|
|
|
*/ |
114
|
|
|
private $msgTxt; |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @var array $attachments |
118
|
|
|
*/ |
119
|
|
|
private $attachments = []; |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Whether the email should be logged. |
123
|
|
|
* |
124
|
|
|
* @var boolean $log |
125
|
|
|
*/ |
126
|
|
|
private $log; |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Whether the email should be tracked. |
130
|
|
|
* |
131
|
|
|
* @var boolean $track |
132
|
|
|
*/ |
133
|
|
|
private $track; |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* The data to pass onto the view controller. |
137
|
|
|
* |
138
|
|
|
* @var array $templateData |
139
|
|
|
*/ |
140
|
|
|
private $templateData = []; |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* @var PHPMailer $phpMailer PHP Mailer instance. |
144
|
|
|
*/ |
145
|
|
|
private $phpMailer; |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* @var FactoryInterface $templateFactory |
149
|
|
|
*/ |
150
|
|
|
private $templateFactory; |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* @var FactoryInterface $queueItemFactory |
154
|
|
|
*/ |
155
|
|
|
private $queueItemFactory; |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* @var FactoryInterface $logFactory |
159
|
|
|
*/ |
160
|
|
|
private $logFactory; |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Construct a new Email object. |
164
|
|
|
* |
165
|
|
|
* @param array $data Dependencies and settings. |
166
|
|
|
*/ |
167
|
|
|
public function __construct(array $data) |
168
|
|
|
{ |
169
|
|
|
$this->phpMailer = new PHPMailer(true); |
170
|
|
|
$this->setLogger($data['logger']); |
171
|
|
|
$this->setView($data['view']); |
172
|
|
|
$this->setConfig($data['config']); |
173
|
|
|
$this->setTemplateFactory($data['template_factory']); |
174
|
|
|
$this->setQueueItemFactory($data['queue_item_factory']); |
175
|
|
|
$this->setLogFactory($data['log_factory']); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Set the email's data. |
180
|
|
|
* |
181
|
|
|
* @param array $data The data to set. |
182
|
|
|
* @return Email Chainable |
183
|
|
|
*/ |
184
|
|
|
public function setData(array $data) |
185
|
|
|
{ |
186
|
|
|
foreach ($data as $prop => $val) { |
187
|
|
|
$func = [$this, $this->setter($prop)]; |
188
|
|
|
if (is_callable($func)) { |
189
|
|
|
call_user_func($func, $val); |
190
|
|
|
} else { |
191
|
|
|
$this->{$prop} = $val; |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
return $this; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Set the campaign ID. |
200
|
|
|
* |
201
|
|
|
* @param string $campaign The campaign identifier. |
202
|
|
|
* @throws InvalidArgumentException If the campaign is invalid. |
203
|
|
|
* @return self |
204
|
|
|
*/ |
205
|
|
|
public function setCampaign($campaign) |
206
|
|
|
{ |
207
|
|
|
if (!is_string($campaign)) { |
208
|
|
|
throw new InvalidArgumentException( |
209
|
|
|
'Campaign must be a string' |
210
|
|
|
); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
$this->campaign = $campaign; |
214
|
|
|
|
215
|
|
|
return $this; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Get the campaign identifier. |
220
|
|
|
* |
221
|
|
|
* If it has not been explicitely set, it will be auto-generated (with uniqid). |
222
|
|
|
* |
223
|
|
|
* @return string |
224
|
|
|
*/ |
225
|
|
|
public function campaign() |
226
|
|
|
{ |
227
|
|
|
if ($this->campaign === null) { |
228
|
|
|
$this->campaign = $this->generateCampaign(); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
return $this->campaign; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Set the recipient email address(es). |
236
|
|
|
* |
237
|
|
|
* @param string|array $email The recipient email address(es). |
238
|
|
|
* @throws InvalidArgumentException If the email address is invalid. |
239
|
|
|
* @return self |
240
|
|
|
*/ |
241
|
|
View Code Duplication |
public function setTo($email) |
|
|
|
|
242
|
|
|
{ |
243
|
|
|
if (is_string($email)) { |
244
|
|
|
$email = [ $email ]; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
if (!is_array($email)) { |
248
|
|
|
throw new InvalidArgumentException( |
249
|
|
|
'Must be an array of recipients.' |
250
|
|
|
); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
$this->to = []; |
254
|
|
|
|
255
|
|
|
// At this point, `$email` can be an _email array_ or an _array of emails_... |
256
|
|
|
if (isset($email['email'])) { |
257
|
|
|
// Means we're not dealing with multiple emails |
258
|
|
|
$this->addTo($email); |
259
|
|
|
} else { |
260
|
|
|
foreach ($email as $recipient) { |
261
|
|
|
$this->addTo($recipient); |
262
|
|
|
} |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
return $this; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Add a recipient email address. |
270
|
|
|
* |
271
|
|
|
* @param mixed $email The recipient email address to add. |
272
|
|
|
* @throws InvalidArgumentException If the email address is invalid. |
273
|
|
|
* @return self |
274
|
|
|
*/ |
275
|
|
|
public function addTo($email) |
276
|
|
|
{ |
277
|
|
|
$this->to[] = $this->parseEmail($email); |
278
|
|
|
return $this; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* Get the recipient's email addresses. |
283
|
|
|
* |
284
|
|
|
* @return string[] |
285
|
|
|
*/ |
286
|
|
|
public function to() |
287
|
|
|
{ |
288
|
|
|
return $this->to; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* Set the carbon copy (CC) recipient email address(es). |
293
|
|
|
* |
294
|
|
|
* @param string|array $email The CC recipient email address(es). |
295
|
|
|
* @throws InvalidArgumentException If the email address is invalid. |
296
|
|
|
* @return self |
297
|
|
|
*/ |
298
|
|
View Code Duplication |
public function setCc($email) |
|
|
|
|
299
|
|
|
{ |
300
|
|
|
if (is_string($email)) { |
301
|
|
|
$email = [ $email ]; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
if (!is_array($email)) { |
305
|
|
|
throw new InvalidArgumentException( |
306
|
|
|
'Must be an array of CC recipients.' |
307
|
|
|
); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
$this->cc = []; |
311
|
|
|
|
312
|
|
|
// At this point, `$email` can be an _email array_ or an _array of emails_... |
313
|
|
|
if (isset($email['email'])) { |
314
|
|
|
// Means we're not dealing with multiple emails |
315
|
|
|
$this->addCc($email); |
316
|
|
|
} else { |
317
|
|
|
foreach ($email as $recipient) { |
318
|
|
|
$this->addCc($recipient); |
319
|
|
|
} |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
return $this; |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Add a CC recipient email address. |
327
|
|
|
* |
328
|
|
|
* @param mixed $email The CC recipient email address to add. |
329
|
|
|
* @throws InvalidArgumentException If the email address is invalid. |
330
|
|
|
* @return self |
331
|
|
|
*/ |
332
|
|
|
public function addCc($email) |
333
|
|
|
{ |
334
|
|
|
$this->cc[] = $this->parseEmail($email); |
335
|
|
|
return $this; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Get the CC recipient's email address. |
340
|
|
|
* |
341
|
|
|
* @return string[] |
342
|
|
|
*/ |
343
|
|
|
public function cc() |
344
|
|
|
{ |
345
|
|
|
return $this->cc; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* Set the blind carbon copy (BCC) recipient email address(es). |
350
|
|
|
* |
351
|
|
|
* @param string|array $email The BCC recipient email address(es). |
352
|
|
|
* @throws InvalidArgumentException If the email address is invalid. |
353
|
|
|
* @return self |
354
|
|
|
*/ |
355
|
|
View Code Duplication |
public function setBcc($email) |
|
|
|
|
356
|
|
|
{ |
357
|
|
|
if (is_string($email)) { |
358
|
|
|
// Means we have a straight email |
359
|
|
|
$email = [ $email ]; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
if (!is_array($email)) { |
363
|
|
|
throw new InvalidArgumentException( |
364
|
|
|
'Must be an array of BCC recipients.' |
365
|
|
|
); |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
$this->bcc = []; |
369
|
|
|
|
370
|
|
|
// At this point, `$email` can be an _email array_ or an _array of emails_... |
371
|
|
|
if (isset($email['email'])) { |
372
|
|
|
// Means we're not dealing with multiple emails |
373
|
|
|
$this->addBcc($email); |
374
|
|
|
} else { |
375
|
|
|
foreach ($email as $recipient) { |
376
|
|
|
$this->addBcc($recipient); |
377
|
|
|
} |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
return $this; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Add a BCC recipient email address. |
385
|
|
|
* |
386
|
|
|
* @param mixed $email The BCC recipient email address to add. |
387
|
|
|
* @throws InvalidArgumentException If the email address is invalid. |
388
|
|
|
* @return self |
389
|
|
|
*/ |
390
|
|
|
public function addBcc($email) |
391
|
|
|
{ |
392
|
|
|
$this->bcc[] = $this->parseEmail($email); |
393
|
|
|
return $this; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
/** |
397
|
|
|
* Get the BCC recipient's email address. |
398
|
|
|
* |
399
|
|
|
* @return string[] |
400
|
|
|
*/ |
401
|
|
|
public function bcc() |
402
|
|
|
{ |
403
|
|
|
return $this->bcc; |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
/** |
407
|
|
|
* Set the sender's email address. |
408
|
|
|
* |
409
|
|
|
* @param string|array $email An email address. |
410
|
|
|
* @throws InvalidArgumentException If the email is not a string or an array. |
411
|
|
|
* @return self |
412
|
|
|
* @todo Implement optional "Sender" field. |
413
|
|
|
*/ |
414
|
|
|
public function setFrom($email) |
415
|
|
|
{ |
416
|
|
|
$this->from = $this->parseEmail($email); |
417
|
|
|
return $this; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Get the sender's email address. |
422
|
|
|
* |
423
|
|
|
* @return string |
424
|
|
|
*/ |
425
|
|
|
public function from() |
426
|
|
|
{ |
427
|
|
|
if ($this->from === null) { |
428
|
|
|
$this->setFrom($this->config()->defaultFrom()); |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
return $this->from; |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
/** |
435
|
|
|
* Set email address to reply to the message. |
436
|
|
|
* |
437
|
|
|
* @param mixed $email The sender's "Reply-To" email address. |
438
|
|
|
* @throws InvalidArgumentException If the email is not a string or an array. |
439
|
|
|
* @return self |
440
|
|
|
*/ |
441
|
|
|
public function setReplyTo($email) |
442
|
|
|
{ |
443
|
|
|
$this->replyTo = $this->parseEmail($email); |
444
|
|
|
return $this; |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
/** |
448
|
|
|
* Get email address to reply to the message. |
449
|
|
|
* |
450
|
|
|
* @return string |
451
|
|
|
*/ |
452
|
|
|
public function replyTo() |
453
|
|
|
{ |
454
|
|
|
if ($this->replyTo === null) { |
455
|
|
|
$this->replyTo = $this->config()->defaultReplyTo(); |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
return $this->replyTo; |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* Set the email subject. |
463
|
|
|
* |
464
|
|
|
* @param string $subject The email subject. |
465
|
|
|
* @throws InvalidArgumentException If the subject is not a string. |
466
|
|
|
* @return self |
467
|
|
|
*/ |
468
|
|
|
public function setSubject($subject) |
469
|
|
|
{ |
470
|
|
|
if (!is_string($subject)) { |
471
|
|
|
throw new InvalidArgumentException( |
472
|
|
|
'Subject needs to be a string' |
473
|
|
|
); |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
$this->subject = $subject; |
477
|
|
|
|
478
|
|
|
return $this; |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
/** |
482
|
|
|
* Get the email subject. |
483
|
|
|
* |
484
|
|
|
* @return string The emails' subject. |
485
|
|
|
*/ |
486
|
|
|
public function subject() |
487
|
|
|
{ |
488
|
|
|
return $this->subject; |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* Set the email's HTML message body. |
493
|
|
|
* |
494
|
|
|
* @param string $body The HTML message body. |
495
|
|
|
* @throws InvalidArgumentException If the message is not a string. |
496
|
|
|
* @return self |
497
|
|
|
*/ |
498
|
|
|
public function setMsgHtml($body) |
499
|
|
|
{ |
500
|
|
|
if (!is_string($body)) { |
501
|
|
|
throw new InvalidArgumentException( |
502
|
|
|
'HTML message needs to be a string' |
503
|
|
|
); |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
$this->msgHtml = $body; |
507
|
|
|
|
508
|
|
|
return $this; |
509
|
|
|
} |
510
|
|
|
|
511
|
|
|
/** |
512
|
|
|
* Get the email's HTML message body. |
513
|
|
|
* |
514
|
|
|
* If the message is not explitely set, it will be |
515
|
|
|
* auto-generated from a template view. |
516
|
|
|
* |
517
|
|
|
* @return string |
518
|
|
|
*/ |
519
|
|
|
public function msgHtml() |
520
|
|
|
{ |
521
|
|
|
if ($this->msgHtml === null) { |
522
|
|
|
$this->msgHtml = $this->generateMsgHtml(); |
523
|
|
|
} |
524
|
|
|
return $this->msgHtml; |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
/** |
528
|
|
|
* Set the email's plain-text message body. |
529
|
|
|
* |
530
|
|
|
* @param string $body The message's text body. |
531
|
|
|
* @throws InvalidArgumentException If the parameter is invalid. |
532
|
|
|
* @return self |
533
|
|
|
*/ |
534
|
|
|
public function setMsgTxt($body) |
535
|
|
|
{ |
536
|
|
|
if (!is_string($body)) { |
537
|
|
|
throw new InvalidArgumentException( |
538
|
|
|
'Plan-text message needs to be a string' |
539
|
|
|
); |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
$this->msgTxt = $body; |
543
|
|
|
|
544
|
|
|
return $this; |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
/** |
548
|
|
|
* Get the email's plain-text message body. |
549
|
|
|
* |
550
|
|
|
* If the plain-text message is not explitely set, |
551
|
|
|
* it will be auto-generated from the HTML message. |
552
|
|
|
* |
553
|
|
|
* @return string |
554
|
|
|
*/ |
555
|
|
|
public function msgTxt() |
556
|
|
|
{ |
557
|
|
|
if ($this->msgTxt === null) { |
558
|
|
|
$this->msgTxt = $this->stripHtml($this->msgHtml()); |
559
|
|
|
} |
560
|
|
|
|
561
|
|
|
return $this->msgTxt; |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
/** |
565
|
|
|
* Set the email's attachments. |
566
|
|
|
* |
567
|
|
|
* @param array $attachments The file attachments. |
568
|
|
|
* @return self |
569
|
|
|
*/ |
570
|
|
|
public function setAttachments(array $attachments) |
571
|
|
|
{ |
572
|
|
|
foreach ($attachments as $att) { |
573
|
|
|
$this->addAttachment($att); |
574
|
|
|
} |
575
|
|
|
|
576
|
|
|
return $this; |
577
|
|
|
} |
578
|
|
|
|
579
|
|
|
/** |
580
|
|
|
* Add an attachment to the email. |
581
|
|
|
* |
582
|
|
|
* @param mixed $attachment A single file attachment. |
583
|
|
|
* @return self |
584
|
|
|
*/ |
585
|
|
|
public function addAttachment($attachment) |
586
|
|
|
{ |
587
|
|
|
$this->attachments[] = $attachment; |
588
|
|
|
return $this; |
589
|
|
|
} |
590
|
|
|
|
591
|
|
|
/** |
592
|
|
|
* Get the email's attachments. |
593
|
|
|
* |
594
|
|
|
* @return array |
595
|
|
|
*/ |
596
|
|
|
public function attachments() |
597
|
|
|
{ |
598
|
|
|
return $this->attachments; |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
/** |
602
|
|
|
* Enable or disable logging for this particular email. |
603
|
|
|
* |
604
|
|
|
* @param boolean $log The log flag. |
605
|
|
|
* @return self |
606
|
|
|
*/ |
607
|
|
|
public function setLog($log) |
608
|
|
|
{ |
609
|
|
|
$this->log = !!$log; |
610
|
|
|
return $this; |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
/** |
614
|
|
|
* Determine if logging is enabled for this particular email. |
615
|
|
|
* |
616
|
|
|
* @return boolean |
617
|
|
|
*/ |
618
|
|
|
public function log() |
619
|
|
|
{ |
620
|
|
|
if ($this->log === null) { |
621
|
|
|
$this->log = $this->config()->defaultLog(); |
622
|
|
|
} |
623
|
|
|
return $this->log; |
624
|
|
|
} |
625
|
|
|
|
626
|
|
|
/** |
627
|
|
|
* Enable or disable tracking for this particular email. |
628
|
|
|
* |
629
|
|
|
* @param boolean $track The track flag. |
630
|
|
|
* @return self |
631
|
|
|
*/ |
632
|
|
|
public function setTrack($track) |
633
|
|
|
{ |
634
|
|
|
$this->track = !!$track; |
635
|
|
|
return $this; |
636
|
|
|
} |
637
|
|
|
|
638
|
|
|
/** |
639
|
|
|
* Determine if tracking is enabled for this particular email. |
640
|
|
|
* |
641
|
|
|
* @return boolean |
642
|
|
|
*/ |
643
|
|
|
public function track() |
644
|
|
|
{ |
645
|
|
|
if ($this->track === null) { |
646
|
|
|
$this->track = $this->config()->defaultTrack(); |
647
|
|
|
} |
648
|
|
|
return $this->track; |
649
|
|
|
} |
650
|
|
|
|
651
|
|
|
/** |
652
|
|
|
* Send the email to all recipients |
653
|
|
|
* |
654
|
|
|
* @return boolean Success / Failure. |
655
|
|
|
* @todo Implement methods and property for toggling rich-text vs. plain-text |
656
|
|
|
* emails (`$mail->isHTML(true)`). |
657
|
|
|
*/ |
658
|
|
|
public function send() |
659
|
|
|
{ |
660
|
|
|
$this->logger->debug( |
661
|
|
|
'Attempting to send an email', |
662
|
|
|
$this->to() |
663
|
|
|
); |
664
|
|
|
|
665
|
|
|
$mail = $this->phpMailer; |
666
|
|
|
|
667
|
|
|
try { |
668
|
|
|
$this->setSmtpOptions($mail); |
669
|
|
|
|
670
|
|
|
$mail->CharSet = 'UTF-8'; |
671
|
|
|
|
672
|
|
|
// Setting reply-to field, if required. |
673
|
|
|
$replyTo = $this->replyTo(); |
674
|
|
|
if ($replyTo) { |
675
|
|
|
$replyArr = $this->emailToArray($replyTo); |
676
|
|
|
$mail->addReplyTo($replyArr['email'], $replyArr['name']); |
677
|
|
|
} |
678
|
|
|
|
679
|
|
|
// Setting from (sender) field. |
680
|
|
|
$from = $this->from(); |
681
|
|
|
$fromArr = $this->emailToArray($from); |
682
|
|
|
$mail->setFrom($fromArr['email'], $fromArr['name']); |
683
|
|
|
|
684
|
|
|
// Setting to (recipients) field(s). |
685
|
|
|
$to = $this->to(); |
686
|
|
|
foreach ($to as $recipient) { |
687
|
|
|
$toArr = $this->emailToArray($recipient); |
688
|
|
|
$mail->addAddress($toArr['email'], $toArr['name']); |
689
|
|
|
} |
690
|
|
|
|
691
|
|
|
// Setting cc (carbon-copy) field(s). |
692
|
|
|
$cc = $this->cc(); |
693
|
|
|
foreach ($cc as $ccRecipient) { |
694
|
|
|
$ccArr = $this->emailToArray($ccRecipient); |
695
|
|
|
$mail->addCC($ccArr['email'], $ccArr['name']); |
696
|
|
|
} |
697
|
|
|
|
698
|
|
|
// Setting bcc (black-carbon-copy) field(s). |
699
|
|
|
$bcc = $this->bcc(); |
700
|
|
|
foreach ($bcc as $bccRecipient) { |
701
|
|
|
$bccArr = $this->emailToArray($bccRecipient); |
702
|
|
|
$mail->addBCC($bccArr['email'], $bccArr['name']); |
703
|
|
|
} |
704
|
|
|
|
705
|
|
|
// Setting attachment(s), if required. |
706
|
|
|
$attachments = $this->attachments(); |
707
|
|
|
foreach ($attachments as $att) { |
708
|
|
|
$mail->addAttachment($att); |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
$mail->isHTML(true); |
712
|
|
|
|
713
|
|
|
$mail->Subject = $this->subject(); |
714
|
|
|
$mail->Body = $this->msgHtml(); |
715
|
|
|
$mail->AltBody = $this->msgTxt(); |
716
|
|
|
|
717
|
|
|
$ret = $mail->send(); |
718
|
|
|
|
719
|
|
|
$this->logSend($ret, $mail); |
720
|
|
|
|
721
|
|
|
return $ret; |
722
|
|
|
} catch (Exception $e) { |
723
|
|
|
$this->logger->error( |
724
|
|
|
sprintf('Error sending email: %s', $e->getMessage()) |
725
|
|
|
); |
726
|
|
|
} |
727
|
|
|
} |
728
|
|
|
|
729
|
|
|
/** |
730
|
|
|
* Set the SMTP's options for PHPMailer. |
731
|
|
|
* |
732
|
|
|
* @param PHPMailer $mail The PHPMailer to setup. |
733
|
|
|
* @return void |
734
|
|
|
*/ |
735
|
|
|
protected function setSmtpOptions(PHPMailer $mail) |
736
|
|
|
{ |
737
|
|
|
$config = $this->config(); |
738
|
|
|
if (!$config['smtp']) { |
739
|
|
|
return; |
740
|
|
|
} |
741
|
|
|
|
742
|
|
|
$this->logger->debug( |
743
|
|
|
sprintf('Using SMTP "%s" server to send email', $config['smtp_hostname']) |
744
|
|
|
); |
745
|
|
|
|
746
|
|
|
$mail->isSMTP(); |
747
|
|
|
$mail->Host = $config['smtp_hostname']; |
748
|
|
|
$mail->Port = $config['smtp_port']; |
749
|
|
|
$mail->SMTPAuth = $config['smtp_auth']; |
750
|
|
|
$mail->Username = $config['smtp_username']; |
751
|
|
|
$mail->Password = $config['smtp_password']; |
752
|
|
|
$mail->SMTPSecure = $config['smtp_security']; |
753
|
|
|
} |
754
|
|
|
|
755
|
|
|
/** |
756
|
|
|
* Enqueue the email for each recipient. |
757
|
|
|
* |
758
|
|
|
* @param mixed $ts A date/time to initiate the queue processing. |
759
|
|
|
* @return boolean Success / Failure. |
760
|
|
|
*/ |
761
|
|
|
public function queue($ts = null) |
762
|
|
|
{ |
763
|
|
|
$recipients = $this->to(); |
764
|
|
|
$author = $this->from(); |
765
|
|
|
$subject = $this->subject(); |
766
|
|
|
$msgHtml = $this->msgHtml(); |
767
|
|
|
$msgTxt = $this->msgTxt(); |
768
|
|
|
$campaign = $this->campaign(); |
769
|
|
|
$queueId = $this->queueId(); |
770
|
|
|
|
771
|
|
|
foreach ($recipients as $to) { |
772
|
|
|
if (is_string($to) && !empty($to)) { |
773
|
|
|
$queueItem = $this->queueItemFactory()->create(EmailQueueItem::class); |
774
|
|
|
|
775
|
|
|
$queueItem->setTo($to); |
776
|
|
|
$queueItem->setFrom($author); |
777
|
|
|
$queueItem->setSubject($subject); |
778
|
|
|
$queueItem->setMsgHtml($msgHtml); |
779
|
|
|
$queueItem->setMsgTxt($msgTxt); |
780
|
|
|
$queueItem->setCampaign($campaign); |
781
|
|
|
$queueItem->setProcessingDate($ts); |
782
|
|
|
$queueItem->setQueueId($queueId); |
783
|
|
|
|
784
|
|
|
$res = $queueItem->save(); |
|
|
|
|
785
|
|
|
} else { |
786
|
|
|
$this->logger->warning('Could not queue email, null or empty value'); |
787
|
|
|
} |
788
|
|
|
} |
789
|
|
|
|
790
|
|
|
return true; |
|
|
|
|
791
|
|
|
} |
792
|
|
|
|
793
|
|
|
/** |
794
|
|
|
* Log the queue event. |
795
|
|
|
* |
796
|
|
|
* @return void |
797
|
|
|
* @todo Implement log qeueing. |
798
|
|
|
*/ |
799
|
|
|
protected function logQueue() |
800
|
|
|
{ |
801
|
|
|
} |
802
|
|
|
|
803
|
|
|
/** |
804
|
|
|
* Set the template data for the view. |
805
|
|
|
* |
806
|
|
|
* @param array $data The template data. |
807
|
|
|
* @return Email Chainable |
808
|
|
|
*/ |
809
|
|
|
public function setTemplateData(array $data) |
810
|
|
|
{ |
811
|
|
|
$this->templateData = $data; |
812
|
|
|
return $this; |
813
|
|
|
} |
814
|
|
|
|
815
|
|
|
/** |
816
|
|
|
* Get the template data for the view. |
817
|
|
|
* |
818
|
|
|
* @return array |
819
|
|
|
*/ |
820
|
|
|
public function templateData() |
821
|
|
|
{ |
822
|
|
|
return $this->templateData; |
823
|
|
|
} |
824
|
|
|
|
825
|
|
|
/** |
826
|
|
|
* Get the custom view controller for rendering |
827
|
|
|
* the email's HTML message. |
828
|
|
|
* |
829
|
|
|
* Unlike typical `ViewableInterface` objects, the view controller is not |
830
|
|
|
* the email itself but an external "email" template. |
831
|
|
|
* |
832
|
|
|
* @see ViewableInterface::viewController() |
833
|
|
|
* @return \Charcoal\App\Template\TemplateInterface|array |
834
|
|
|
*/ |
835
|
|
|
public function viewController() |
836
|
|
|
{ |
837
|
|
|
$templateIdent = $this->templateIdent(); |
838
|
|
|
|
839
|
|
|
if (!$templateIdent) { |
840
|
|
|
return []; |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
$templateFactory = clone($this->templateFactory()); |
844
|
|
|
$templateFactory->setDefaultClass(GenericEmailTemplate::class); |
|
|
|
|
845
|
|
|
$template = $templateFactory->create($templateIdent); |
846
|
|
|
|
847
|
|
|
$template->setData($this->templateData()); |
848
|
|
|
|
849
|
|
|
return $template; |
850
|
|
|
} |
851
|
|
|
|
852
|
|
|
/** |
853
|
|
|
* @param FactoryInterface $factory The factory to use to create email template objects. |
854
|
|
|
* @return Email Chainable |
855
|
|
|
*/ |
856
|
|
|
protected function setTemplateFactory(FactoryInterface $factory) |
857
|
|
|
{ |
858
|
|
|
$this->templateFactory = $factory; |
859
|
|
|
return $this; |
860
|
|
|
} |
861
|
|
|
|
862
|
|
|
/** |
863
|
|
|
* @return FactoryInterface |
864
|
|
|
*/ |
865
|
|
|
protected function templateFactory() |
866
|
|
|
{ |
867
|
|
|
return $this->templateFactory; |
868
|
|
|
} |
869
|
|
|
|
870
|
|
|
/** |
871
|
|
|
* @param FactoryInterface $factory The factory to use to create email queue item objects. |
872
|
|
|
* @return Email Chainable |
873
|
|
|
*/ |
874
|
|
|
protected function setQueueItemFactory(FactoryInterface $factory) |
875
|
|
|
{ |
876
|
|
|
$this->queueItemFactory = $factory; |
877
|
|
|
return $this; |
878
|
|
|
} |
879
|
|
|
|
880
|
|
|
/** |
881
|
|
|
* @return FactoryInterface |
882
|
|
|
*/ |
883
|
|
|
protected function queueItemFactory() |
884
|
|
|
{ |
885
|
|
|
return $this->queueItemFactory; |
886
|
|
|
} |
887
|
|
|
|
888
|
|
|
/** |
889
|
|
|
* @param FactoryInterface $factory The factory to use to create log objects. |
890
|
|
|
* @return Email Chainable |
891
|
|
|
*/ |
892
|
|
|
protected function setLogFactory(FactoryInterface $factory) |
893
|
|
|
{ |
894
|
|
|
$this->logFactory = $factory; |
895
|
|
|
return $this; |
896
|
|
|
} |
897
|
|
|
|
898
|
|
|
/** |
899
|
|
|
* @return FactoryInterface |
900
|
|
|
*/ |
901
|
|
|
protected function logFactory() |
902
|
|
|
{ |
903
|
|
|
return $this->logFactory; |
904
|
|
|
} |
905
|
|
|
|
906
|
|
|
/** |
907
|
|
|
* Get the email's HTML message from the template, if applicable. |
908
|
|
|
* |
909
|
|
|
* @see ViewableInterface::renderTemplate() |
910
|
|
|
* @return string |
911
|
|
|
*/ |
912
|
|
|
protected function generateMsgHtml() |
913
|
|
|
{ |
914
|
|
|
$templateIdent = $this->templateIdent(); |
915
|
|
|
|
916
|
|
|
if (!$templateIdent) { |
917
|
|
|
$message = ''; |
918
|
|
|
} else { |
919
|
|
|
$message = $this->renderTemplate($templateIdent); |
920
|
|
|
} |
921
|
|
|
|
922
|
|
|
return $message; |
923
|
|
|
} |
924
|
|
|
|
925
|
|
|
/** |
926
|
|
|
* Generates a unique identifier ideal for a campaign ID. |
927
|
|
|
* |
928
|
|
|
* @return string |
929
|
|
|
*/ |
930
|
|
|
protected function generateCampaign() |
931
|
|
|
{ |
932
|
|
|
return uniqid(); |
933
|
|
|
} |
934
|
|
|
|
935
|
|
|
/** |
936
|
|
|
* Allow an object to define how the key getter are called. |
937
|
|
|
* |
938
|
|
|
* @param string $key The key to get the getter from. |
939
|
|
|
* @return string The getter method name, for a given key. |
940
|
|
|
*/ |
941
|
|
|
protected function getter($key) |
942
|
|
|
{ |
943
|
|
|
$getter = $key; |
944
|
|
|
return $this->camelize($getter); |
945
|
|
|
} |
946
|
|
|
|
947
|
|
|
/** |
948
|
|
|
* Allow an object to define how the key setter are called. |
949
|
|
|
* |
950
|
|
|
* @param string $key The key to get the setter from. |
951
|
|
|
* @return string The setter method name, for a given key. |
952
|
|
|
*/ |
953
|
|
|
protected function setter($key) |
954
|
|
|
{ |
955
|
|
|
$setter = 'set_'.$key; |
956
|
|
|
return $this->camelize($setter); |
957
|
|
|
} |
958
|
|
|
|
959
|
|
|
/** |
960
|
|
|
* Convert an HTML string to plain-text. |
961
|
|
|
* |
962
|
|
|
* @param string $html The HTML string to convert. |
963
|
|
|
* @return string The resulting plain-text string. |
964
|
|
|
*/ |
965
|
|
|
protected function stripHtml($html) |
966
|
|
|
{ |
967
|
|
|
$str = html_entity_decode($html); |
968
|
|
|
|
969
|
|
|
// Strip HTML (Replace br with newline, remove "invisible" tags and strip other tags) |
970
|
|
|
$str = preg_replace('#<br[^>]*?>#siu', "\n", $str); |
971
|
|
|
$str = preg_replace( |
972
|
|
|
[ |
973
|
|
|
'#<applet[^>]*?.*?</applet>#siu', |
974
|
|
|
'#<embed[^>]*?.*?</embed>#siu', |
975
|
|
|
'#<head[^>]*?>.*?</head>#siu', |
976
|
|
|
'#<noframes[^>]*?.*?</noframes>#siu', |
977
|
|
|
'#<noscript[^>]*?.*?</noscript>#siu', |
978
|
|
|
'#<noembed[^>]*?.*?</noembed>#siu', |
979
|
|
|
'#<object[^>]*?.*?</object>#siu', |
980
|
|
|
'#<script[^>]*?.*?</script>#siu', |
981
|
|
|
'#<style[^>]*?>.*?</style>#siu' |
982
|
|
|
], |
983
|
|
|
'', |
984
|
|
|
$str |
985
|
|
|
); |
986
|
|
|
$str = strip_tags($str); |
987
|
|
|
|
988
|
|
|
// Trim whitespace |
989
|
|
|
$str = str_replace("\t", '', $str); |
990
|
|
|
$str = preg_replace('#\n\r|\r\n#', "\n", $str); |
991
|
|
|
$str = preg_replace('#\n{3,}#', "\n\n", $str); |
992
|
|
|
$str = preg_replace('/ {2,}/', ' ', $str); |
993
|
|
|
$str = implode("\n", array_map('trim', explode("\n", $str))); |
994
|
|
|
$str = trim($str)."\n"; |
995
|
|
|
return $str; |
996
|
|
|
} |
997
|
|
|
|
998
|
|
|
/** |
999
|
|
|
* Log the send event for each recipient. |
1000
|
|
|
* |
1001
|
|
|
* @param boolean $result Success or failure. |
1002
|
|
|
* @param mixed $mailer The raw mailer. |
1003
|
|
|
* @return void |
1004
|
|
|
*/ |
1005
|
|
|
protected function logSend($result, $mailer) |
1006
|
|
|
{ |
1007
|
|
|
if ($this->log() === false) { |
1008
|
|
|
return; |
1009
|
|
|
} |
1010
|
|
|
|
1011
|
|
|
if (!$result) { |
1012
|
|
|
$this->logger->error('Email could not be sent.'); |
1013
|
|
|
} else { |
1014
|
|
|
$this->logger->debug( |
1015
|
|
|
sprintf('Email "%s" sent successfully.', $this->subject()), |
1016
|
|
|
$this->to() |
1017
|
|
|
); |
1018
|
|
|
} |
1019
|
|
|
|
1020
|
|
|
$recipients = array_merge( |
1021
|
|
|
$this->to(), |
1022
|
|
|
$this->cc(), |
1023
|
|
|
$this->bcc() |
1024
|
|
|
); |
1025
|
|
|
|
1026
|
|
|
foreach ($recipients as $to) { |
1027
|
|
|
$log = $this->logFactory()->create('charcoal/email/email-log'); |
1028
|
|
|
|
1029
|
|
|
$log->setQueueId($this->queueId()); |
1030
|
|
|
$log->setMessageId($mailer->getLastMessageId()); |
1031
|
|
|
$log->setCampaign($this->campaign()); |
1032
|
|
|
$log->setSendTs('now'); |
1033
|
|
|
$log->setFrom($mailer->From); |
1034
|
|
|
$log->setTo($to); |
1035
|
|
|
$log->setSubject($this->subject()); |
1036
|
|
|
|
1037
|
|
|
if (!empty($mailer->getSMTPInstance()->getError()['smtp_code'])) { |
1038
|
|
|
$log->setErrorCode($mailer->getSMTPInstance()->getError()['smtp_code']); |
1039
|
|
|
} |
1040
|
|
|
|
1041
|
|
|
$log->save(); |
1042
|
|
|
} |
1043
|
|
|
} |
1044
|
|
|
|
1045
|
|
|
/** |
1046
|
|
|
* Transform a snake_case string to camelCase. |
1047
|
|
|
* |
1048
|
|
|
* @param string $str The snake_case string to camelize. |
1049
|
|
|
* @return string The camelCase string. |
1050
|
|
|
*/ |
1051
|
|
|
private function camelize($str) |
1052
|
|
|
{ |
1053
|
|
|
return lcfirst(implode('', array_map('ucfirst', explode('_', $str)))); |
1054
|
|
|
} |
1055
|
|
|
|
1056
|
|
|
/** |
1057
|
|
|
* Temporary hack to fulfills the Configurable Interface. |
1058
|
|
|
* |
1059
|
|
|
* @return EmailConfig |
1060
|
|
|
*/ |
1061
|
|
|
public function createConfig() |
1062
|
|
|
{ |
1063
|
|
|
// This should really be avoided. |
1064
|
|
|
$this->logger->warning('AbstractEmail::createConfig() was called, but should not.'); |
1065
|
|
|
return new \Charcoal\Email\EmailConfig(); |
1066
|
|
|
} |
1067
|
|
|
} |
1068
|
|
|
|
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.