Completed
Pull Request — master (#14)
by
unknown
04:02
created

AzineTwigSwiftMailer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1

Importance

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