1
|
|
|
<?php |
|
|
|
|
2
|
|
|
/** |
3
|
|
|
* MtMail - e-mail module for Zend Framework 2 |
4
|
|
|
* |
5
|
|
|
* @link http://github.com/mtymek/MtMail |
6
|
|
|
* @copyright Copyright (c) 2013-2014 Mateusz Tymek |
7
|
|
|
* @license BSD 2-Clause |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
namespace MtMail\Service; |
11
|
|
|
|
12
|
|
|
use MtMail\Event\ComposerEvent; |
13
|
|
|
use MtMail\Exception\InvalidArgumentException; |
14
|
|
|
use MtMail\Renderer\RendererInterface; |
15
|
|
|
use MtMail\Template\HtmlTemplateInterface; |
16
|
|
|
use MtMail\Template\TemplateInterface; |
17
|
|
|
use MtMail\Template\TextTemplateInterface; |
18
|
|
|
use Zend\EventManager\EventManager; |
19
|
|
|
use Zend\EventManager\EventManagerAwareInterface; |
20
|
|
|
use Zend\EventManager\EventManagerInterface; |
21
|
|
|
use Zend\Mail\Message; |
22
|
|
|
use Zend\View\Model\ModelInterface; |
23
|
|
|
use Zend\Mime\Message as MimeMessage; |
24
|
|
|
use Zend\Mime\Part as MimePart; |
25
|
|
|
use Zend\View\Model\ViewModel; |
26
|
|
|
|
27
|
|
|
class Composer implements EventManagerAwareInterface |
28
|
|
|
{ |
29
|
|
|
/** |
30
|
|
|
* @var RendererInterface |
31
|
|
|
*/ |
32
|
|
|
protected $renderer; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var EventManagerInterface |
36
|
|
|
*/ |
37
|
|
|
protected $eventManager; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Class constructor |
41
|
|
|
* |
42
|
|
|
* @param RendererInterface $renderer |
43
|
|
|
*/ |
44
|
10 |
|
public function __construct(RendererInterface $renderer) |
45
|
|
|
{ |
46
|
10 |
|
$this->renderer = $renderer; |
47
|
10 |
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Inject an EventManager instance |
51
|
|
|
* |
52
|
|
|
* @param EventManagerInterface $eventManager |
53
|
|
|
* @return self |
54
|
|
|
*/ |
55
|
3 |
|
public function setEventManager(EventManagerInterface $eventManager) |
56
|
|
|
{ |
57
|
3 |
|
$this->eventManager = $eventManager; |
58
|
|
|
|
59
|
3 |
|
return $this; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Retrieve the event manager |
64
|
|
|
* |
65
|
|
|
* Lazy-loads an EventManager instance if none registered. |
66
|
|
|
* |
67
|
|
|
* @return EventManagerInterface |
68
|
|
|
*/ |
69
|
8 |
|
public function getEventManager() |
70
|
|
|
{ |
71
|
8 |
|
if (null === $this->eventManager) { |
72
|
6 |
|
$this->eventManager = new EventManager(); |
73
|
6 |
|
} |
74
|
|
|
|
75
|
8 |
|
return $this->eventManager; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @param \MtMail\Renderer\RendererInterface $renderer |
80
|
|
|
* @return self |
81
|
|
|
*/ |
82
|
1 |
|
public function setRenderer($renderer) |
83
|
|
|
{ |
84
|
1 |
|
$this->renderer = $renderer; |
85
|
|
|
|
86
|
1 |
|
return $this; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* @return \MtMail\Renderer\RendererInterface |
91
|
|
|
*/ |
92
|
3 |
|
public function getRenderer() |
93
|
|
|
{ |
94
|
3 |
|
return $this->renderer; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Create and return event used by compose and send methods |
99
|
|
|
* |
100
|
|
|
* @return ComposerEvent |
101
|
|
|
*/ |
102
|
5 |
|
protected function getEvent() |
103
|
|
|
{ |
104
|
5 |
|
$event = new ComposerEvent(); |
105
|
5 |
|
$event->setTarget($this); |
106
|
|
|
|
107
|
5 |
|
return $event; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Build e-mail message |
112
|
|
|
* |
113
|
|
|
* @param TemplateInterface $template |
114
|
|
|
* @param array $headers |
115
|
|
|
* @param ModelInterface $viewModel |
116
|
|
|
* @throws InvalidArgumentException if template is not string nor TemplateInterface |
117
|
|
|
* @return Message |
118
|
|
|
*/ |
119
|
5 |
|
public function compose(array $headers, TemplateInterface $template, ModelInterface $viewModel = null) |
120
|
|
|
{ |
121
|
5 |
|
if (null == $viewModel) { |
122
|
|
|
$viewModel = new ViewModel(); |
123
|
|
|
} |
124
|
|
|
|
125
|
5 |
|
$event = $this->getEvent(); |
126
|
5 |
|
$event->setTemplate($template); |
127
|
5 |
|
$em = $this->getEventManager(); |
128
|
|
|
|
129
|
|
|
// 1. Trigger pre event |
130
|
5 |
|
$event->setName(ComposerEvent::EVENT_COMPOSE_PRE); |
131
|
5 |
|
$em->triggerEvent($event); |
132
|
|
|
|
133
|
|
|
// 2. inject headers |
134
|
5 |
|
$event->setName(ComposerEvent::EVENT_HEADERS_PRE); |
135
|
5 |
|
$em->triggerEvent($event); |
136
|
5 |
|
foreach ($headers as $name => $value) { |
137
|
1 |
|
switch ($name) { |
138
|
5 |
|
case "to": |
139
|
5 |
|
$value = explode(",", $value); |
140
|
5 |
|
if (!is_array($value)) { |
141
|
|
|
$tmp = $value; |
142
|
|
|
$value = []; |
143
|
5 |
|
$value[] = $tmp; |
144
|
|
|
} |
145
|
|
|
foreach ($value as $item) { |
146
|
5 |
|
$event->getMessage()->addTo($item); |
147
|
2 |
|
} |
148
|
2 |
|
break; |
149
|
2 |
|
case "from": |
150
|
|
|
$value = explode(",", $value); |
151
|
2 |
|
if (!is_array($value)) { |
152
|
2 |
|
$tmp = $value; |
153
|
|
|
$value = []; |
154
|
2 |
|
$value[] = $tmp; |
155
|
2 |
|
} |
156
|
2 |
|
foreach ($value as $item) { |
157
|
2 |
|
$event->getMessage()->addFrom($item); |
158
|
|
|
} |
159
|
2 |
|
break; |
160
|
2 |
|
case "bcc" : |
|
|
|
|
161
|
2 |
|
$value = explode(",", $value); |
162
|
|
|
if (!is_array($value)) { |
163
|
|
|
$tmp = $value; |
164
|
5 |
|
$value = []; |
165
|
3 |
|
$value[] = $tmp; |
166
|
3 |
|
} |
167
|
3 |
|
foreach ($value as $item) { |
168
|
|
|
$event->getMessage()->addBcc($item); |
169
|
3 |
|
} |
170
|
3 |
|
break; |
171
|
|
|
case "cc" : |
|
|
|
|
172
|
3 |
|
$value = explode(",", $value); |
173
|
3 |
|
if (!is_array($value)) { |
174
|
3 |
|
$tmp = $value; |
175
|
3 |
|
$value = []; |
176
|
|
|
$value[] = $tmp; |
177
|
3 |
|
} |
178
|
3 |
|
foreach ($value as $item) { |
179
|
3 |
|
$event->getMessage()->addCc($item); |
180
|
|
|
} |
181
|
|
|
break; |
182
|
5 |
|
default: |
183
|
5 |
|
$event->getMessage()->getHeaders()->addHeaderLine($name, $value); |
184
|
|
|
} |
185
|
|
|
} |
186
|
5 |
|
$event->setName(ComposerEvent::EVENT_HEADERS_POST); |
187
|
|
|
$em->triggerEvent($event); |
188
|
|
|
|
189
|
|
|
// prepare placeholder for message body |
190
|
|
|
$body = new MimeMessage(); |
191
|
5 |
|
|
192
|
5 |
|
// 3. Render plain text template |
193
|
|
|
if ($template instanceof TextTemplateInterface) { |
194
|
5 |
|
$textViewModel = clone $viewModel; |
195
|
|
|
$textViewModel->setTemplate($template->getTextTemplateName()); |
196
|
|
|
$event->setViewModel($textViewModel); |
197
|
|
|
|
198
|
|
|
$event->setName(ComposerEvent::EVENT_TEXT_BODY_PRE); |
199
|
|
|
$em->triggerEvent($event); |
200
|
|
|
|
201
|
|
|
$text = new MimePart($this->renderer->render($event->getViewModel())); |
202
|
|
|
$text->type = 'text/plain'; |
203
|
|
|
$text->charset = $event->getMessage()->getHeaders()->getEncoding(); |
204
|
|
|
$body->addPart($text); |
205
|
|
|
|
206
|
|
|
$event->setName(ComposerEvent::EVENT_TEXT_BODY_POST); |
207
|
|
|
$em->triggerEvent($event); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
// 4. Render HTML template |
211
|
|
|
if ($template instanceof HtmlTemplateInterface) { |
212
|
|
|
$htmlViewModel = clone $viewModel; |
213
|
|
|
$htmlViewModel->setTemplate($template->getHtmlTemplateName()); |
214
|
|
|
$event->setViewModel($htmlViewModel); |
215
|
|
|
|
216
|
|
|
$event->setName(ComposerEvent::EVENT_HTML_BODY_PRE); |
217
|
|
|
$em->triggerEvent($event); |
218
|
|
|
|
219
|
|
|
$html = new MimePart($this->renderer->render($event->getViewModel())); |
220
|
|
|
$html->type = 'text/html'; |
221
|
|
|
$html->charset = $event->getMessage()->getHeaders()->getEncoding(); |
222
|
|
|
$body->addPart($html); |
223
|
|
|
|
224
|
|
|
$event->setName(ComposerEvent::EVENT_HTML_BODY_POST); |
225
|
|
|
$em->triggerEvent($event); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
// 5. inject body into message |
229
|
|
|
$event->setBody($body); |
230
|
|
|
$event->getMessage()->setBody($body); |
231
|
|
|
|
232
|
|
|
// 6. set multipart/alternative when both versions are available |
233
|
|
|
if ($template instanceof TextTemplateInterface && $template instanceof HtmlTemplateInterface) { |
234
|
|
|
$event->getMessage()->getHeaders()->get('content-type')->setType('multipart/alternative') |
235
|
|
|
->addParameter('boundary', $body->getMime()->boundary()); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
$event->setName(ComposerEvent::EVENT_COMPOSE_POST); |
239
|
|
|
$em->triggerEvent($event); |
240
|
|
|
|
241
|
|
|
return $event->getMessage(); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
|
245
|
|
|
public function attachments(Message $message, array $attachments) |
246
|
|
|
{ |
247
|
|
|
if (sizeof($attachments) > 0) { |
248
|
|
|
$type = $message->getHeaders()->get('content-type')->getType(); |
249
|
|
|
if ($type != 'multipart/related') { |
250
|
|
|
$parts = $message->getBody()->getParts(); |
251
|
|
|
$htmlPart = null; |
252
|
|
|
$textPart = null; |
253
|
|
|
|
254
|
|
|
// locate HTML body |
255
|
|
|
foreach ($parts as $part) { |
256
|
|
|
foreach ($part->getHeadersArray() as $header) { |
257
|
|
|
if ($header[0] == 'Content-Type' && strpos($header[1], 'text/html') === 0) { |
258
|
|
|
$htmlPart = $part; |
259
|
|
|
} elseif ($header[0] == 'Content-Type' && strpos($header[1], 'text/plain') === 0) { |
260
|
|
|
$textPart = $part; |
261
|
|
|
} |
262
|
|
|
} |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
if (!empty($textPart) && !empty($htmlPart)) { |
266
|
|
|
$content = new MimeMessage(); |
267
|
|
|
$content->addPart($textPart); |
268
|
|
|
$content->addPart($htmlPart); |
269
|
|
|
$contentPart = new MimePart($content->generateMessage()); |
270
|
|
|
$contentPart->type = "multipart/alternative;\n boundary=\"" . |
271
|
|
|
$content->getMime()->boundary() . '"'; |
272
|
|
|
$message->getBody()->setParts([$contentPart]); |
273
|
|
|
} else { |
274
|
|
|
if (empty($textPart)) { |
275
|
|
|
$message->getBody()->setParts([$htmlPart]); |
276
|
|
|
} else { |
277
|
|
|
$message->getBody()->setParts([$textPart]); |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
|
|
|
|
|
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
foreach ($attachments as $attachment) { |
284
|
|
|
if (is_readable($attachment)) { |
285
|
|
|
$pathParts = pathinfo($attachment); |
286
|
|
|
$at = new MimePart(file_get_contents($attachment)); |
287
|
|
|
$at->type = $this->getType($pathParts['extension']); |
288
|
|
|
$at->filename = $pathParts['filename']; |
289
|
|
|
$at->disposition = Mime::DISPOSITION_ATTACHMENT; |
290
|
|
|
|
291
|
|
|
$message->getBody()->addPart($at); |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
// force multipart/alternative content type |
296
|
|
|
if ($type != 'multipart/related') { |
297
|
|
|
$message->getHeaders()->get('content-type')->setType('multipart/related') |
298
|
|
|
->addParameter('boundary', $event->getBody()->getMime()->boundary()); |
299
|
|
|
} |
300
|
|
|
} |
301
|
|
|
return $message; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
private function getType($ext) { |
305
|
|
|
switch (strtolower($ext)) { |
306
|
|
|
case "pdf": |
307
|
|
|
$type = 'application/pdf'; |
308
|
|
|
break; |
309
|
|
|
case "doc": |
310
|
|
|
$type = "application/msword"; |
311
|
|
|
break; |
312
|
|
|
case "docx": |
313
|
|
|
$type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; |
314
|
|
|
break; |
315
|
|
|
case "odt": |
316
|
|
|
$type = "application/vnd.oasis.opendocument.text"; |
317
|
|
|
break; |
318
|
|
|
case "gzip": |
319
|
|
|
$type = 'application/gzip'; |
320
|
|
|
break; |
321
|
|
|
case "txt": |
322
|
|
|
$type= 'application/text'; |
323
|
|
|
break; |
324
|
|
|
case "zip": |
325
|
|
|
$type = 'application/zip'; |
326
|
|
|
break; |
327
|
|
|
default: |
328
|
|
|
$type = Mime::TYPE_OCTETSTREAM; |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
return $type; |
332
|
|
|
} |
333
|
|
|
} |
|
|
|
|
334
|
|
|
} |
|
|
|
|
335
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.