1
|
|
|
<?php |
2
|
|
|
namespace AcMailer\Service; |
3
|
|
|
|
4
|
|
|
use AcMailer\Event\MailEvent; |
5
|
|
|
use AcMailer\Event\MailListenerInterface; |
6
|
|
|
use AcMailer\Event\MailListenerAwareInterface; |
7
|
|
|
use AcMailer\Exception\MailException; |
8
|
|
|
use AcMailer\View\DefaultLayout; |
9
|
|
|
use AcMailer\View\DefaultLayoutInterface; |
10
|
|
|
use Zend\EventManager\EventManager; |
11
|
|
|
use Zend\EventManager\EventManagerAwareInterface; |
12
|
|
|
use Zend\EventManager\EventManagerInterface; |
13
|
|
|
use Zend\Mail\Transport\TransportInterface; |
14
|
|
|
use Zend\Mail\Message; |
15
|
|
|
use Zend\Mime; |
16
|
|
|
use Zend\Mail\Exception\ExceptionInterface as ZendMailException; |
17
|
|
|
use AcMailer\Result\ResultInterface; |
18
|
|
|
use AcMailer\Result\MailResult; |
19
|
|
|
use Zend\View\Model\ViewModel; |
20
|
|
|
use Zend\View\Renderer\RendererInterface; |
21
|
|
|
use AcMailer\Exception\InvalidArgumentException; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Wraps Zend\Mail functionality |
25
|
|
|
* @author Alejandro Celaya Alastrué |
26
|
|
|
* @link http://www.alejandrocelaya.com |
27
|
|
|
*/ |
28
|
|
|
class MailService implements MailServiceInterface, EventManagerAwareInterface, MailListenerAwareInterface |
29
|
|
|
{ |
30
|
|
|
/** |
31
|
|
|
* @var \Zend\Mail\Message |
32
|
|
|
*/ |
33
|
|
|
private $message; |
34
|
|
|
/** |
35
|
|
|
* @var \Zend\Mail\Transport\TransportInterface |
36
|
|
|
*/ |
37
|
|
|
private $transport; |
38
|
|
|
/** |
39
|
|
|
* @var RendererInterface |
40
|
|
|
*/ |
41
|
|
|
private $renderer; |
42
|
|
|
/** |
43
|
|
|
* @var EventManagerInterface |
44
|
|
|
*/ |
45
|
|
|
private $events; |
46
|
|
|
/** |
47
|
|
|
* @var array |
48
|
|
|
*/ |
49
|
|
|
private $attachments = []; |
50
|
|
|
/** |
51
|
|
|
* @var DefaultLayoutInterface |
52
|
|
|
*/ |
53
|
|
|
private $defaultLayout; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Creates a new MailService |
57
|
|
|
* @param Message $message |
58
|
|
|
* @param TransportInterface $transport |
59
|
|
|
* @param RendererInterface $renderer Renderer used to render templates, typically a PhpRenderer |
60
|
|
|
*/ |
61
|
33 |
|
public function __construct(Message $message, TransportInterface $transport, RendererInterface $renderer) |
62
|
|
|
{ |
63
|
33 |
|
$this->message = $message; |
64
|
33 |
|
$this->transport = $transport; |
65
|
33 |
|
$this->renderer = $renderer; |
66
|
33 |
|
$this->setDefaultLayout(); |
67
|
33 |
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* Returns this service's message |
71
|
|
|
* @return \Zend\Mail\Message |
72
|
|
|
* @see \AcMailer\Service\MailServiceInterface::getMessage() |
73
|
|
|
*/ |
74
|
13 |
|
public function getMessage() |
75
|
|
|
{ |
76
|
13 |
|
return $this->message; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Sends the mail |
81
|
|
|
* @return ResultInterface |
82
|
|
|
* @throws MailException |
83
|
|
|
*/ |
84
|
9 |
|
public function send() |
85
|
|
|
{ |
86
|
9 |
|
$result = new MailResult(); |
87
|
|
|
try { |
88
|
|
|
// Trigger pre send event |
89
|
9 |
|
$this->getEventManager()->trigger($this->createMailEvent()); |
90
|
|
|
|
91
|
|
|
// Attach files before sending the email |
92
|
9 |
|
$this->attachFiles(); |
93
|
|
|
|
94
|
|
|
// Try to send the message |
95
|
9 |
|
$this->transport->send($this->message); |
96
|
|
|
|
97
|
|
|
// Trigger post send event |
98
|
5 |
|
$this->getEventManager()->trigger($this->createMailEvent(MailEvent::EVENT_MAIL_POST_SEND, $result)); |
99
|
9 |
|
} catch (\Exception $e) { |
100
|
4 |
|
$result = $this->createMailResultFromException($e); |
101
|
|
|
// Trigger send error event |
102
|
4 |
|
$this->getEventManager()->trigger($this->createMailEvent(MailEvent::EVENT_MAIL_SEND_ERROR, $result)); |
103
|
|
|
|
104
|
|
|
// If the exception produced is not a Zend\Mail exception, rethrow it as a MailException |
105
|
4 |
|
if (! $e instanceof ZendMailException) { |
106
|
1 |
|
throw new MailException('An non Zend\Mail exception occurred', $e->getCode(), $e); |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
110
|
8 |
|
return $result; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Creates a new MailEvent object |
115
|
|
|
* @param ResultInterface $result |
116
|
|
|
* @param string $name |
117
|
|
|
* @return MailEvent |
118
|
|
|
*/ |
119
|
9 |
|
protected function createMailEvent($name = MailEvent::EVENT_MAIL_PRE_SEND, ResultInterface $result = null) |
120
|
|
|
{ |
121
|
9 |
|
$event = new MailEvent($this, $name); |
122
|
9 |
|
if (isset($result)) { |
123
|
9 |
|
$event->setResult($result); |
124
|
9 |
|
} |
125
|
9 |
|
return $event; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Creates a error MailResult from an exception |
130
|
|
|
* @param \Exception $e |
131
|
|
|
* @return MailResult |
132
|
|
|
*/ |
133
|
4 |
|
protected function createMailResultFromException(\Exception $e) |
134
|
|
|
{ |
135
|
4 |
|
return new MailResult(false, $e->getMessage(), $e); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Sets the message body |
140
|
|
|
* @param \Zend\Mime\Part|\Zend\Mime\Message|string $body Email body |
141
|
|
|
* @param string $charset |
142
|
|
|
* @return $this Returns this MailService for chaining purposes |
143
|
|
|
* @throws InvalidArgumentException |
144
|
|
|
* @see \AcMailer\Service\MailServiceInterface::setBody() |
145
|
|
|
*/ |
146
|
21 |
|
public function setBody($body, $charset = null) |
147
|
|
|
{ |
148
|
21 |
|
if (is_string($body)) { |
149
|
|
|
// Create a Mime\Part and wrap it into a Mime\Message |
150
|
17 |
|
$mimePart = new Mime\Part($body); |
151
|
17 |
|
$mimePart->type = $body != strip_tags($body) ? Mime\Mime::TYPE_HTML : Mime\Mime::TYPE_TEXT; |
152
|
17 |
|
$mimePart->charset = $charset ?: self::DEFAULT_CHARSET; |
153
|
17 |
|
$body = new Mime\Message(); |
154
|
17 |
|
$body->setParts([$mimePart]); |
155
|
21 |
|
} elseif ($body instanceof Mime\Part) { |
156
|
|
|
// Overwrite the charset if the Part object if provided |
157
|
2 |
|
if (isset($charset)) { |
158
|
1 |
|
$body->charset = $charset; |
159
|
1 |
|
} |
160
|
|
|
// The body is a Mime\Part. Wrap it into a Mime\Message |
161
|
2 |
|
$mimeMessage = new Mime\Message(); |
162
|
2 |
|
$mimeMessage->setParts([$body]); |
163
|
2 |
|
$body = $mimeMessage; |
164
|
2 |
|
} |
165
|
|
|
|
166
|
|
|
// If the body is not a string or a Mime\Message at this point, it is not a valid argument |
167
|
21 |
|
if (! is_string($body) && ! $body instanceof Mime\Message) { |
168
|
1 |
|
throw new InvalidArgumentException(sprintf( |
169
|
1 |
|
'Provided body is not valid. It should be one of "%s". %s provided', |
170
|
1 |
|
implode('", "', ['string', 'Zend\Mime\Part', 'Zend\Mime\Message']), |
171
|
1 |
|
is_object($body) ? get_class($body) : gettype($body) |
172
|
1 |
|
)); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
// The headers Content-type and Content-transfer-encoding are duplicated every time the body is set. |
176
|
|
|
// Removing them before setting the body prevents this error |
177
|
20 |
|
$this->message->getHeaders()->removeHeader('contenttype'); |
178
|
20 |
|
$this->message->getHeaders()->removeHeader('contenttransferencoding'); |
179
|
20 |
|
$this->message->setBody($body); |
180
|
20 |
|
return $this; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Sets the body of this message from a template |
185
|
|
|
* @param string|\Zend\View\Model\ViewModel $template |
186
|
|
|
* @param array $params |
187
|
|
|
* @see \AcMailer\Service\MailServiceInterface::setTemplate() |
188
|
|
|
*/ |
189
|
5 |
|
public function setTemplate($template, array $params = []) |
190
|
|
|
{ |
191
|
5 |
|
if ($template instanceof ViewModel) { |
192
|
2 |
|
$view = $template; |
193
|
2 |
|
} else { |
194
|
3 |
|
$view = new ViewModel(); |
195
|
3 |
|
$view->setTemplate($template) |
196
|
3 |
|
->setVariables($params); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
// Check if a common layout has to be used |
200
|
5 |
|
if ($this->defaultLayout->hasModel()) { |
201
|
2 |
|
$layoutModel = $this->defaultLayout->getModel(); |
202
|
2 |
|
$layoutModel->addChild($view, $this->defaultLayout->getTemplateCaptureTo()); |
203
|
2 |
|
$view = $layoutModel; |
204
|
2 |
|
} |
205
|
|
|
// Render the template and all of its children |
206
|
5 |
|
$this->renderChildren($view); |
207
|
|
|
|
208
|
5 |
|
$charset = isset($params['charset']) ? $params['charset'] : null; |
209
|
5 |
|
$this->setBody($this->renderer->render($view), $charset); |
210
|
4 |
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Sets the default layout to be used with all the templates set when calling setTemplate. |
214
|
|
|
* |
215
|
|
|
* @param DefaultLayoutInterface $layout |
216
|
|
|
* @return mixed |
217
|
|
|
*/ |
218
|
33 |
|
public function setDefaultLayout(DefaultLayoutInterface $layout = null) |
219
|
|
|
{ |
220
|
33 |
|
$this->defaultLayout = isset($layout) ? $layout : new DefaultLayout(); |
221
|
33 |
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Renders template childrens. |
225
|
|
|
* Inspired on Zend\View\View implementation to recursively render child models |
226
|
|
|
* @param ViewModel $model |
227
|
|
|
* @see Zend\View\View::renderChildren |
228
|
|
|
*/ |
229
|
5 |
|
protected function renderChildren(ViewModel $model) |
230
|
|
|
{ |
231
|
5 |
|
if (! $model->hasChildren()) { |
232
|
5 |
|
return; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/* @var ViewModel $child */ |
236
|
3 |
|
foreach ($model as $child) { |
237
|
3 |
|
$capture = $child->captureTo(); |
238
|
3 |
|
if (! empty($capture)) { |
239
|
|
|
// Recursively render children |
240
|
3 |
|
$this->renderChildren($child); |
241
|
3 |
|
$result = $this->renderer->render($child); |
242
|
|
|
|
243
|
3 |
|
if ($child->isAppend()) { |
244
|
|
|
$oldResult = $model->{$capture}; |
245
|
|
|
$model->setVariable($capture, $oldResult . $result); |
246
|
|
|
} else { |
247
|
3 |
|
$model->setVariable($capture, $result); |
248
|
|
|
} |
249
|
3 |
|
} |
250
|
3 |
|
} |
251
|
3 |
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Attaches files to the message if any |
255
|
|
|
*/ |
256
|
9 |
|
protected function attachFiles() |
257
|
|
|
{ |
258
|
9 |
|
if (count($this->attachments) === 0) { |
259
|
7 |
|
return; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
// Get old message parts |
263
|
2 |
|
$mimeMessage = $this->message->getBody(); |
264
|
2 |
|
if (is_string($mimeMessage)) { |
265
|
1 |
|
$originalBodyPart = new Mime\Part($mimeMessage); |
266
|
1 |
|
$originalBodyPart->type = $mimeMessage != strip_tags($mimeMessage) |
267
|
1 |
|
? Mime\Mime::TYPE_HTML |
268
|
1 |
|
: Mime\Mime::TYPE_TEXT; |
269
|
|
|
|
270
|
|
|
// A Mime\Part body will be wraped into a Mime\Message, ensuring we handle a Mime\Message after this point |
271
|
1 |
|
$this->setBody($originalBodyPart); |
272
|
1 |
|
$mimeMessage = $this->message->getBody(); |
273
|
1 |
|
} |
274
|
2 |
|
$oldParts = $mimeMessage->getParts(); |
275
|
|
|
|
276
|
|
|
// Generate a new Mime\Part for each attachment |
277
|
2 |
|
$attachmentParts = []; |
278
|
2 |
|
$info = new \finfo(FILEINFO_MIME_TYPE); |
279
|
2 |
|
foreach ($this->attachments as $key => $attachment) { |
280
|
2 |
|
if (! is_file($attachment)) { |
281
|
1 |
|
continue; // If checked file is not valid, continue to the next |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
// If the key is a string, use it as the attachment name |
285
|
2 |
|
$basename = is_string($key) ? $key : basename($attachment); |
286
|
|
|
|
287
|
2 |
|
$part = new Mime\Part(fopen($attachment, 'r')); |
288
|
2 |
|
$part->id = $basename; |
289
|
2 |
|
$part->filename = $basename; |
290
|
2 |
|
$part->type = $info->file($attachment); |
291
|
2 |
|
$part->encoding = Mime\Mime::ENCODING_BASE64; |
292
|
2 |
|
$part->disposition = Mime\Mime::DISPOSITION_ATTACHMENT; |
293
|
2 |
|
$attachmentParts[] = $part; |
294
|
2 |
|
} |
295
|
|
|
|
296
|
2 |
|
$body = new Mime\Message(); |
297
|
2 |
|
$body->setParts(array_merge($oldParts, $attachmentParts)); |
298
|
2 |
|
$this->message->setBody($body); |
299
|
2 |
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* Sets the message subject |
303
|
|
|
* @param string $subject The subject of the message |
304
|
|
|
* @return $this Returns this MailService for chaining purposes |
305
|
|
|
* @deprecated Use $mailService->getMessage()->setSubject() instead |
306
|
|
|
*/ |
307
|
|
|
public function setSubject($subject) |
308
|
|
|
{ |
309
|
|
|
$this->message->setSubject($subject); |
310
|
|
|
return $this; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* @param string $path |
315
|
|
|
* @param string|null $filename |
316
|
|
|
* @return $this |
317
|
|
|
*/ |
318
|
2 |
View Code Duplication |
public function addAttachment($path, $filename = null) |
|
|
|
|
319
|
|
|
{ |
320
|
2 |
|
if (isset($filename)) { |
321
|
1 |
|
$this->attachments[$filename] = $path; |
322
|
1 |
|
} else { |
323
|
2 |
|
$this->attachments[] = $path; |
324
|
|
|
} |
325
|
2 |
|
return $this; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* @param array $paths |
330
|
|
|
* @return $this |
331
|
|
|
*/ |
332
|
12 |
|
public function addAttachments(array $paths) |
333
|
|
|
{ |
334
|
12 |
|
return $this->setAttachments(array_merge($this->attachments, $paths)); |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* @param array $paths |
339
|
|
|
* @return $this |
340
|
|
|
*/ |
341
|
14 |
|
public function setAttachments(array $paths) |
342
|
|
|
{ |
343
|
14 |
|
$this->attachments = $paths; |
344
|
14 |
|
return $this; |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* Returns the list of attachments |
349
|
|
|
* @return array |
350
|
|
|
*/ |
351
|
2 |
|
public function getAttachments() |
352
|
|
|
{ |
353
|
2 |
|
return $this->attachments; |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* Inject an EventManager instance |
358
|
|
|
* @param EventManagerInterface $events |
359
|
|
|
* @return $this|void |
360
|
|
|
*/ |
361
|
10 |
|
public function setEventManager(EventManagerInterface $events) |
362
|
|
|
{ |
363
|
10 |
|
$events->setIdentifiers([ |
364
|
10 |
|
__CLASS__, |
365
|
10 |
|
get_called_class(), |
366
|
10 |
|
]); |
367
|
10 |
|
$this->events = $events; |
368
|
10 |
|
return $this; |
369
|
|
|
} |
370
|
|
|
/** |
371
|
|
|
* Retrieve the event manager |
372
|
|
|
* Lazy-loads an EventManager instance if none registered. |
373
|
|
|
* @return EventManagerInterface |
374
|
|
|
*/ |
375
|
10 |
|
public function getEventManager() |
376
|
|
|
{ |
377
|
10 |
|
if (! isset($this->events)) { |
378
|
10 |
|
$this->setEventManager(new EventManager()); |
379
|
10 |
|
} |
380
|
|
|
|
381
|
10 |
|
return $this->events; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* Attaches a new MailListenerInterface |
386
|
|
|
* @param MailListenerInterface $mailListener |
387
|
|
|
* @param int $priority |
388
|
|
|
* @return mixed|void |
389
|
|
|
*/ |
390
|
4 |
|
public function attachMailListener(MailListenerInterface $mailListener, $priority = 1) |
391
|
|
|
{ |
392
|
4 |
|
$this->getEventManager()->attach($mailListener, $priority); |
|
|
|
|
393
|
4 |
|
return $this; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
/** |
397
|
|
|
* Detaches provided MailListener |
398
|
|
|
* @param MailListenerInterface $mailListener |
399
|
|
|
* @return $this |
400
|
|
|
*/ |
401
|
1 |
|
public function detachMailListener(MailListenerInterface $mailListener) |
402
|
|
|
{ |
403
|
1 |
|
$mailListener->detach($this->getEventManager()); |
|
|
|
|
404
|
1 |
|
return $this; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* @param TransportInterface $transport |
409
|
|
|
* @return $this |
410
|
|
|
*/ |
411
|
1 |
|
public function setTransport(TransportInterface $transport) |
412
|
|
|
{ |
413
|
1 |
|
$this->transport = $transport; |
414
|
1 |
|
return $this; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Returns the transport object that will be used to send the wrapped message |
419
|
|
|
* @return TransportInterface |
420
|
|
|
*/ |
421
|
5 |
|
public function getTransport() |
422
|
|
|
{ |
423
|
5 |
|
return $this->transport; |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
/** |
427
|
|
|
* @param RendererInterface $renderer |
428
|
|
|
* |
429
|
|
|
* @return $this |
430
|
|
|
*/ |
431
|
1 |
|
public function setRenderer(RendererInterface $renderer) |
432
|
|
|
{ |
433
|
1 |
|
$this->renderer = $renderer; |
434
|
1 |
|
return $this; |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
/** |
438
|
|
|
* Returns the renderer object that will be used to render templates |
439
|
|
|
* @return RendererInterface |
440
|
|
|
*/ |
441
|
4 |
|
public function getRenderer() |
442
|
|
|
{ |
443
|
4 |
|
return $this->renderer; |
444
|
|
|
} |
445
|
|
|
} |
446
|
|
|
|
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.