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