Completed
Push — master ( c37102...86711a )
by Dominik
18:10
created

AzineTwigSwiftMailer::sendSingleEmail()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 9
crap 1

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Azine\EmailBundle\Services;
4
5
use Azine\EmailBundle\DependencyInjection\AzineEmailExtension;
6
use Azine\EmailBundle\Entity\SentEmail;
7
use Doctrine\Common\Persistence\ManagerRegistry;
8
use FOS\UserBundle\Mailer\TwigSwiftMailer;
9
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
10
use Symfony\Component\HttpFoundation\File\Exception\FileException;
11
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
12
use Symfony\Component\Routing\RequestContext;
13
14
/**
15
 * This Service is used to send html-emails with embedded images.
16
 *
17
 * @author Dominik Businger
18
 */
19
class AzineTwigSwiftMailer extends TwigSwiftMailer implements TemplateTwigSwiftMailerInterface
20
{
21
    /**
22
     * @var Translator
23
     */
24
    protected $translator;
25
26
    /**
27
     * @var TemplateProviderInterface
28
     */
29
    protected $templateProvider;
30
31
    /**
32
     * @var ManagerRegistry
33
     */
34
    protected $managerRegistry;
35
36
    /**
37
     * @var RequestContext
38
     */
39
    protected $routerContext;
40
41
    /**
42
     * @var string email to use for "no-reply"
43
     */
44
    protected $noReplyEmail;
45
46
    /**
47
     * @var string name to use for "no-reply"
48
     */
49
    protected $noReplyName;
50
51
    /**
52
     * The Swift_Mailer to be used for sending emails immediately.
53
     *
54
     * @var \Swift_Mailer
55
     */
56
    private $immediateMailer;
57
58
    /**
59
     * @var EmailOpenTrackingCodeBuilderInterface
60
     */
61
    private $emailOpenTrackingCodeBuilder;
62
63
    /**
64
     * @var AzineEmailTwigExtension
65
     */
66
    private $emailTwigExtension;
67
68
    private $encodedItemIdPattern;
69
    private $templateCache = array();
70
    private $imageCache = array();
71
72
    /**
73
     * @param \Swift_Mailer                         $mailer
74
     * @param UrlGeneratorInterface                 $router
75
     * @param \Twig_Environment                     $twig
76
     * @param Translator                            $translator
77
     * @param TemplateProviderInterface             $templateProvider
78
     * @param ManagerRegistry                       $managerRegistry
79
     * @param EmailOpenTrackingCodeBuilderInterface $emailOpenTrackingCodeBuilder
80
     * @param AzineEmailTwigExtension               $emailTwigExtension
81
     * @param array                                 $parameters
82
     * @param \Swift_Mailer                         $immediateMailer
83
     */
84 7
    public function __construct(\Swift_Mailer $mailer,
85
                                    UrlGeneratorInterface $router,
86
                                    \Twig_Environment $twig,
87
                                    Translator $translator,
88
                                    TemplateProviderInterface $templateProvider,
89
                                    ManagerRegistry $managerRegistry,
90
                                    EmailOpenTrackingCodeBuilderInterface $emailOpenTrackingCodeBuilder,
91
                                    AzineEmailTwigExtension $emailTwigExtension,
92
                                    array $parameters,
93
                                    \Swift_Mailer $immediateMailer = null)
94
    {
95 7
        parent::__construct($mailer, $router, $twig, $parameters);
96 7
        $this->immediateMailer = $immediateMailer;
97 7
        $this->translator = $translator;
98 7
        $this->templateProvider = $templateProvider;
99 7
        $this->managerRegistry = $managerRegistry;
100 7
        $this->noReplyEmail = $parameters[AzineEmailExtension::NO_REPLY][AzineEmailExtension::NO_REPLY_EMAIL_ADDRESS];
101 7
        $this->noReplyName = $parameters[AzineEmailExtension::NO_REPLY][AzineEmailExtension::NO_REPLY_EMAIL_NAME];
102 7
        $this->emailOpenTrackingCodeBuilder = $emailOpenTrackingCodeBuilder;
103 7
        $this->routerContext = $router->getContext();
104 7
        $this->encodedItemIdPattern = '/^cid:.*@/';
105 7
        $this->emailTwigExtension = $emailTwigExtension;
106 7
    }
107
108
    /**
109
     * (non-PHPdoc).
110
     *
111
     * @see Azine\EmailBundle\Services.TemplateTwigSwiftMailerInterface::sendEmail()
112
     *
113
     * @param array        $failedRecipients
114
     * @param string       $subject
115
     * @param string       $from
116
     * @param string       $fromName
117
     * @param array|string $to
118
     * @param string       $toName
119
     * @param array|string $cc
120
     * @param string       $ccName
121
     * @param array|string $bcc
122
     * @param string       $bccName
123
     * @param $replyTo
124
     * @param $replyToName
125
     * @param array $params
126
     * @param $template
127
     * @param array          $attachments
128
     * @param null           $emailLocale
129
     * @param \Swift_Message $message
130
     *
131
     * @return int
132
     */
133 7
    public function sendEmail(&$failedRecipients, $subject, $from, $fromName, $to, $toName, $cc, $ccName, $bcc, $bccName, $replyTo, $replyToName, array $params, $template, $attachments = array(), $emailLocale = null, \Swift_Message &$message = null)
134
    {
135
        // create the message
136 7
        if (null === $message) {
137 7
            $message = \Swift_Message::newInstance();
138
        }
139
140 7
        $message->setSubject($subject);
141
142
        // set the from-Name & -Email to the default ones if not given
143 7
        if (null === $from) {
144 2
            $from = $this->noReplyEmail;
145 2
            if (null === $fromName) {
146 2
                $fromName = $this->noReplyName;
147
            }
148
        }
149
150
        // add the from-email for the footer-text
151 7
        if (!array_key_exists('fromEmail', $params)) {
152 7
            $params['sendMailAccountName'] = $this->noReplyName;
153 7
            $params['sendMailAccountAddress'] = $this->noReplyEmail;
154
        }
155
156
        // get the baseTemplate. => templateId without the ending.
157 7
        $templateBaseId = substr($template, 0, strrpos($template, '.', -6));
158
159
        // check if this email should be stored for web-view
160 7
        if ($this->templateProvider->saveWebViewFor($templateBaseId)) {
161
            // keep a copy of the vars for the web-view
162 4
            $webViewParams = $params;
163
164
            // add the web-view token
165 4
            $params[$this->templateProvider->getWebViewTokenId()] = SentEmail::getNewToken();
166
        } else {
167 3
            $webViewParams = array();
168
        }
169
170
        // recursively add all template-variables for the wrapper-templates and contentItems
171 7
        $params = $this->templateProvider->addTemplateVariablesFor($templateBaseId, $params);
172
173
        // recursively attach all messages in the array
174 7
        $this->embedImages($message, $params);
175
176
        // change the locale for the email-recipients
177 7
        if (null !== $emailLocale && strlen($emailLocale) > 0) {
178 6
            $currentUserLocale = $this->translator->getLocale();
179
180
            // change the router-context locale
181 6
            $this->routerContext->setParameter('_locale', $emailLocale);
182
183
            // change the translator locale
184 6
            $this->translator->setLocale($emailLocale);
185
        } else {
186 1
            $emailLocale = $this->translator->getLocale();
187
        }
188
189
        // recursively add snippets for the wrapper-templates and contentItems
190 7
        $params = $this->templateProvider->addTemplateSnippetsWithImagesFor($templateBaseId, $params, $emailLocale);
191
192
        // add the emailLocale (used for web-view)
193 7
        $params['emailLocale'] = $emailLocale;
194
195
        // render the email parts
196 7
        $twigTemplate = $this->loadTemplate($template);
197 7
        $textBody = $twigTemplate->renderBlock('body_text', $params);
198 7
        $message->addPart($textBody, 'text/plain');
199
200 7
        $htmlBody = $twigTemplate->renderBlock('body_html', $params);
201
202 7
        $campaignParams = $this->templateProvider->getCampaignParamsFor($templateBaseId, $params);
203
204 7
        if (sizeof($campaignParams) > 0) {
205 5
            $htmlBody = $this->emailTwigExtension->addCampaignParamsToAllUrls($htmlBody, $campaignParams);
206
        }
207
208
        // if email-tracking is enabled
209 7
        if ($this->emailOpenTrackingCodeBuilder) {
210
            // add an image at the end of the html tag with the tracking-params to track email-opens
211 7
            $imgTrackingCode = $this->emailOpenTrackingCodeBuilder->getTrackingImgCode($templateBaseId, $campaignParams, $params, $message->getId(), $to, $cc, $bcc);
212 7
            if ($imgTrackingCode && strlen($imgTrackingCode) > 0) {
213 5
                $htmlCloseTagPosition = strpos($htmlBody, '</body>');
214 5
                $htmlBody = substr_replace($htmlBody, $imgTrackingCode, $htmlCloseTagPosition, 0);
215
            }
216
        }
217
218 7
        $message->setBody($htmlBody, 'text/html');
219
220
        // remove unused/unreferenced embeded items from the message
221 7
        $message = $this->removeUnreferecedEmbededItemsFromMessage($message, $params, $htmlBody);
222
223
        // change the locale back to the users locale
224 7
        if (isset($currentUserLocale) && null !== $currentUserLocale) {
225 6
            $this->routerContext->setParameter('_locale', $currentUserLocale);
226 6
            $this->translator->setLocale($currentUserLocale);
227
        }
228
229
        // add attachments
230 7
        foreach ($attachments as $fileName => $file) {
231
            // add attachment from existing file
232 2
            if (is_string($file)) {
233
                // check that the file really exists!
234 2
                if (file_exists($file)) {
235 1
                    $attachment = \Swift_Attachment::fromPath($file);
236 1
                    if (strlen($fileName) >= 5) {
237 1
                        $attachment->setFilename($fileName);
238
                    }
239
                } else {
240 2
                    throw new FileException('File not found: '.$file);
241
                }
242
243
                // add attachment from generated data
244
            } else {
245 1
                $attachment = \Swift_Attachment::newInstance($file, $fileName);
246
            }
247
248 1
            $message->attach($attachment);
249
        }
250
251
        // set the addresses
252 6
        if ($from) {
253 6
            $message->setFrom($from, $fromName);
254
        }
255 6
        if ($replyTo) {
256 2
            $message->setReplyTo($replyTo, $replyToName);
257 4
        } elseif ($from) {
258 4
            $message->setReplyTo($from, $fromName);
259
        }
260 6
        if ($to) {
261 6
            $message->setTo($to, $toName);
262
        }
263 6
        if ($cc) {
264 2
            $message->setCc($cc, $ccName);
265
        }
266 6
        if ($bcc) {
267 2
            $message->setBcc($bcc, $bccName);
268
        }
269
270
        // add custom headers
271 6
        $this->templateProvider->addCustomHeaders($templateBaseId, $message, $params);
272
273
        // send the message
274 6
        $mailer = $this->getMailer($params);
275 6
        $messagesSent = $mailer->send($message, $failedRecipients);
276
277
        // if the message was successfully sent,
278
        // and it should be made available in web-view
279 6
        if ($messagesSent && array_key_exists($this->templateProvider->getWebViewTokenId(), $params)) {
280
            // store the email
281 2
            $sentEmail = new SentEmail();
282 2
            $sentEmail->setToken($params[$this->templateProvider->getWebViewTokenId()]);
283 2
            $sentEmail->setTemplate($templateBaseId);
284 2
            $sentEmail->setSent(new \DateTime());
285
286
            // recursively add all template-variables for the wrapper-templates and contentItems
287 2
            $webViewParams = $this->templateProvider->addTemplateVariablesFor($template, $webViewParams);
288
289
            // replace absolute image-paths with relative ones.
290 2
            $webViewParams = $this->templateProvider->makeImagePathsWebRelative($webViewParams, $emailLocale);
291
292
            // recursively add snippets for the wrapper-templates and contentItems
293 2
            $webViewParams = $this->templateProvider->addTemplateSnippetsWithImagesFor($template, $webViewParams, $emailLocale, true);
294
295 2
            $sentEmail->setVariables($webViewParams);
296
297
            // save only successfull recipients
298 2
            if (!is_array($to)) {
299 2
                $to = array($to);
300
            }
301 2
            $successfulRecipients = array_diff($to, $failedRecipients);
302 2
            $sentEmail->setRecipients($successfulRecipients);
303
304
            // write to db
305 2
            $em = $this->managerRegistry->getManager();
306 2
            $em->persist($sentEmail);
307 2
            $em->flush($sentEmail);
308 2
            $em->clear();
309 2
            gc_collect_cycles();
310
        }
311
312 6
        return $messagesSent;
313
    }
314
315
    /**
316
     * Remove all Embeded Attachments that are not referenced in the html-body from the message
317
     * to avoid using unneccary bandwidth.
318
     *
319
     * @param \Swift_Message $message
320
     * @param array          $params   the parameters used to render the html
321
     * @param string         $htmlBody
322
     *
323
     * @return \Swift_Message
324
     */
325 7
    private function removeUnreferecedEmbededItemsFromMessage(\Swift_Message $message, $params, $htmlBody)
326
    {
327 7
        foreach ($params as $key => $value) {
328
            // remove unreferenced attachments from contentItems too.
329 7
            if ('contentItems' === $key) {
330 2
                foreach ($value as $contentItemParams) {
331 2
                    $message = $this->removeUnreferecedEmbededItemsFromMessage($message, $contentItemParams, $htmlBody);
332
                }
333
            } else {
334
                // check if the embeded items are referenced in the templates
335 7
                $isEmbededItem = is_string($value) && 1 == preg_match($this->encodedItemIdPattern, $value);
336
337 7
                if ($isEmbededItem && false === stripos($htmlBody, $value)) {
338
                    // remove unreferenced items
339 7
                    $children = array();
340
341 7
                    foreach ($message->getChildren() as $attachment) {
342 7
                        if ('cid:'.$attachment->getId() != $value) {
343 7
                            $children[] = $attachment;
344
                        }
345
                    }
346
347 7
                    $message->setChildren($children);
348
                }
349
            }
350
        }
351
352 7
        return $message;
353
    }
354
355
    /**
356
     * Get the template from the cache if it was loaded already.
357
     *
358
     * @param string $template
359
     *
360
     * @return \Twig_Template
361
     */
362 7
    private function loadTemplate($template)
363
    {
364 7
        if (!array_key_exists($template, $this->templateCache)) {
365 7
            $this->templateCache[$template] = $this->twig->loadTemplate($template);
366
        }
367
368 7
        return $this->templateCache[$template];
369
    }
370
371
    /**
372
     * Recursively embed all images in the array into the message.
373
     *
374
     * @param \Swift_Message $message
375
     * @param array          $params
376
     *
377
     * @return array $params
378
     */
379 7
    private function embedImages(&$message, &$params)
380
    {
381
        // loop through the array
382 7
        foreach ($params as $key => $value) {
383
            // if the current value is an array
384 7
            if (is_array($value)) {
385
                // search for more images deeper in the arrays
386 2
                $value = $this->embedImages($message, $value);
387 2
                $params[$key] = $value;
388
389
            // if the current value is an existing file from the image-folder, embed it
390 7
            } elseif (is_string($value)) {
391 7
                if (is_file($value)) {
392
                    // check if the file is from an allowed folder
393 7
                    if (false !== $this->templateProvider->isFileAllowed($value)) {
394 7
                        $encodedImage = $this->cachedEmbedImage($value);
395 7
                        if (null !== $encodedImage) {
396 7
                            $id = $message->embed($encodedImage);
397 7
                            $params[$key] = $id;
398
                        }
399
                    }
400
401
                    // the $filePath isn't a regular file
402
                } else {
403
                    // add a null-value to the cache for this path, so we don't try again.
404 7
                    $this->imageCache[$value] = null;
405
                }
406
407
                //if the current value is a generated image
408 7
            } elseif (is_resource($value) && 0 == stripos(get_resource_type($value), 'gd')) {
409
                // get the image-data as string
410 1
                ob_start();
411 1
                imagepng($value);
412 1
                $imageData = ob_get_clean();
413
414
                // encode the image
415 1
                $encodedImage = \Swift_Image::newInstance($imageData, 'generatedImage'.md5($imageData));
416 1
                $id = $message->embed($encodedImage);
417 7
                $params[$key] = $id;
418
            }
419
            // don't do anything
420
        }
421
422
        // remove duplicate-attachments
423 7
        $message->setChildren(array_unique($message->getChildren()));
424
425 7
        return $params;
426
    }
427
428
    /**
429
     * Get the Swift_Image for the file.
430
     *
431
     * @param string $filePath
432
     *
433
     * @return \Swift_Image|null
434
     */
435 7
    private function cachedEmbedImage($filePath)
436
    {
437 7
        $filePath = realpath($filePath);
438 7
        if (!array_key_exists($filePath, $this->imageCache)) {
439 7
            if (is_file($filePath)) {
440 7
                $image = \Swift_Image::fromPath($filePath);
441 7
                $id = $image->getId();
442
443
                // $id and $value must not be the same => this happens if the file cannot be found/read
444 7
                if ($id == $filePath) {
445
                    // @codeCoverageIgnoreStart
446
                    // add a null-value to the cache for this path, so we don't try again.
447
                    $this->imageCache[$filePath] = null;
448
                } else {
449
                    // @codeCoverageIgnoreEnd
450
                    // add the image to the cache
451 7
                    $this->imageCache[$filePath] = $image;
452
                }
453
            }
454
        }
455
456 7
        return $this->imageCache[$filePath];
457
    }
458
459
    /**
460
     * (non-PHPdoc).
461
     *
462
     * @see Azine\EmailBundle\Services.TemplateTwigSwiftMailerInterface::sendSingleEmail()
463
     *
464
     * @param string         $to
465
     * @param string         $toName
466
     * @param string         $subject
467
     * @param array          $params
468
     * @param string         $template
469
     * @param string         $emailLocale
470
     * @param null           $from
471
     * @param null           $fromName
472
     * @param \Swift_Message $message
473
     *
474
     * @return bool
475
     */
476 4
    public function sendSingleEmail($to, $toName, $subject, array $params, $template, $emailLocale, $from = null, $fromName = null, \Swift_Message &$message = null)
477
    {
478 4
        $failedRecipients = array();
479 4
        $this->sendEmail($failedRecipients, $subject, $from, $fromName, $to, $toName, null, null, null, null, null, null, $params, $template, array(), $emailLocale, $message);
480
481 4
        return 0 == sizeof($failedRecipients);
482
    }
483
484
    /**
485
     * Override the fosuserbundles original sendMessage, to embed template variables etc. into html-emails.
486
     *
487
     * @param string $templateName
488
     * @param array  $context
489
     * @param string $fromEmail
490
     * @param string $toEmail
491
     *
492
     * @return bool true if the mail was sent successfully, else false
493
     */
494 2
    protected function sendMessage($templateName, $context, $fromEmail, $toEmail)
495
    {
496
        // get the subject from the template
497
        // => make sure the subject block exists in your fos-templates (FOSUserBundle:Registration:email.txt.twig & FOSUserBundle:Resetting:email.txt.twig)
498 2
        $twigTemplate = $this->loadTemplate($templateName);
499 2
        $subject = $twigTemplate->renderBlock('subject', $context);
500
501 2
        return $this->sendSingleEmail($toEmail, null, $subject, $context, $templateName, $this->translator->getLocale(), $fromEmail);
502
    }
503
504
    /**
505
     * Return the Swift_Mailer to be used for sending mails immediately (e.g. instead of spooling them) if it is configured.
506
     *
507
     * @param $params
508
     *
509
     * @return \Swift_Mailer
510
     */
511 6
    private function getMailer($params)
512
    {
513
        // if the second mailer for immediate mail-delivery has been configured
514 6
        if (null !== $this->immediateMailer) {
515
            // check if this template has been configured to be sent immediately
516
            if (array_key_exists(AzineTemplateProvider::SEND_IMMEDIATELY_FLAG, $params) && $params[AzineTemplateProvider::SEND_IMMEDIATELY_FLAG]) {
517
                return $this->immediateMailer;
518
            }
519
        }
520
521 6
        return $this->mailer;
522
    }
523
}
524