Completed
Push — master ( 06cda5...85418c )
by Dominik
03:45
created

AzineTwigSwiftMailer::__construct()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 1

Importance

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