Completed
Push — sendEmailChangeConfirmation ( fef717...430b3c )
by Dominik
07:41 queued 05:35
created

AzineTwigSwiftMailer::sendUpdateEmailConfirmation()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 0
cts 12
cp 0
rs 9.2
c 0
b 0
f 0
cc 2
eloc 13
nc 2
nop 4
crap 6
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 $currentHost;
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->currentHost = $this->routerContext->getHost();
106 7
        $this->encodedItemIdPattern = "/^cid:.*@/";
107 7
        $this->emailTwigExtension = $emailTwigExtension;
108 7
    }
109
110
    /**
111
     * (non-PHPdoc)
112
     * @see Azine\EmailBundle\Services.TemplateTwigSwiftMailerInterface::sendEmail()
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
     * @return int
131
     */
132 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)
133
    {
134
        // create the message
135 7
        if ($message == null) {
136 7
            $message = \Swift_Message::newInstance();
137 7
        }
138
139 7
        $message->setSubject($subject);
140
141
        // set the from-Name & -Email to the default ones if not given
142 7
        if ($from == null) {
143 2
            $from = $this->noReplyEmail;
144 2
            if ($fromName == null) {
145 2
                $fromName = $this->noReplyName;
146 2
            }
147 2
        }
148
149
        // add the from-email for the footer-text
150 7
        if (!array_key_exists('fromEmail', $params)) {
151 7
            $params['sendMailAccountName'] = $this->noReplyName;
152 7
            $params['sendMailAccountAddress'] = $this->noReplyEmail;
153 7
        }
154
155
        // get the baseTemplate. => templateId without the ending.
156 7
        $templateBaseId = substr($template, 0, strrpos($template, ".", -6));
157
158
        // check if this email should be stored for web-view
159 7
        if ($this->templateProvider->saveWebViewFor($templateBaseId)) {
160
            // keep a copy of the vars for the web-view
161 4
            $webViewParams = $params;
162
163
            // add the web-view token
164 4
            $params[$this->templateProvider->getWebViewTokenId()] = SentEmail::getNewToken();
165 4
        } else {
166 3
            $webViewParams = array();
167
        }
168
169
        // recursively add all template-variables for the wrapper-templates and contentItems
170 7
        $params = $this->templateProvider->addTemplateVariablesFor($templateBaseId, $params);
171
172
        // recursively attach all messages in the array
173 7
        $this->embedImages($message, $params);
174
175
        // change the locale for the email-recipients
176 7
        if ($emailLocale !== null && strlen($emailLocale) > 0) {
177 6
            $currentUserLocale = $this->translator->getLocale();
178
179
            // change the router-context locale
180 6
            $this->routerContext->setParameter("_locale", $emailLocale);
181
182
            // change the translator locale
183 6
            $this->translator->setLocale($emailLocale);
184 6
        } else {
185 1
            $emailLocale = $this->translator->getLocale();
186
        }
187
188
        // recursively add snippets for the wrapper-templates and contentItems
189 7
        $params = $this->templateProvider->addTemplateSnippetsWithImagesFor($templateBaseId, $params, $emailLocale);
190
191
        // add the emailLocale (used for web-view)
192 7
        $params['emailLocale'] = $emailLocale;
193
194
        // render the email parts
195 7
        $twigTemplate = $this->loadTemplate($template);
196 7
        $textBody = $twigTemplate->renderBlock('body_text', $params);
197 7
        $message->addPart($textBody, 'text/plain');
198
199 7
        $htmlBody = $twigTemplate->renderBlock('body_html', $params);
200
201 7
        $campaignParams = $this->templateProvider->getCampaignParamsFor($templateBaseId, $params);
202
203 7
        if (sizeof($campaignParams) > 0) {
204 5
            $htmlBody = $this->emailTwigExtension->addCampaignParamsToAllUrls($htmlBody, $campaignParams);
205 5
        }
206
207
        // if email-tracking is enabled
208 7
        if($this->emailOpenTrackingCodeBuilder){
209
            // add an image at the end of the html tag with the tracking-params to track email-opens
210 7
            $imgTrackingCode = $this->emailOpenTrackingCodeBuilder->getTrackingImgCode($templateBaseId, $campaignParams, $params, $message->getId(), $to, $cc, $bcc);
211 7
            if($imgTrackingCode && strlen($imgTrackingCode) > 0) {
212 5
                $htmlCloseTagPosition = strpos($htmlBody, "</body>");
213 5
                $htmlBody = substr_replace($htmlBody, $imgTrackingCode, $htmlCloseTagPosition, 0);
214 5
            }
215 7
        }
216
217 7
        $message->setBody($htmlBody, 'text/html');
218
219
        // remove unused/unreferenced embeded items from the message
220 7
        $message = $this->removeUnreferecedEmbededItemsFromMessage($message, $params, $htmlBody);
221
222
        // change the locale back to the users locale
223 7
        if (isset($currentUserLocale) && $currentUserLocale != null) {
224 6
            $this->routerContext->setParameter("_locale", $currentUserLocale);
225 6
            $this->translator->setLocale($currentUserLocale);
226 6
        }
227
228
        // add attachments
229 7
        foreach ($attachments as $fileName => $file) {
230
231
            // add attachment from existing file
232 2
            if (is_string($file)) {
233
234
                // check that the file really exists!
235 2
                if (file_exists($file)) {
236 1
                    $attachment = \Swift_Attachment::fromPath($file);
237 1
                    if (strlen($fileName) >= 5 ) {
238 1
                        $attachment->setFilename($fileName);
239 1
                    }
240 1
                } else {
241 1
                    throw new FileException("File not found: ".$file);
242
                }
243
244
                // add attachment from generated data
245 1
            } else {
246 1
                $attachment = \Swift_Attachment::newInstance($file, $fileName);
247
            }
248
249 1
            $message->attach($attachment);
250 6
        }
251
252
        // set the addresses
253 6
        if ($from) {
254 6
            $message->setFrom($from, $fromName);
255 6
        }
256 6
        if ($replyTo) {
257 2
            $message->setReplyTo($replyTo, $replyToName);
258 6
        } elseif ($from) {
259 4
            $message->setReplyTo($from, $fromName);
260 4
        }
261 6
        if ($to) {
262 6
            $message->setTo($to, $toName);
263 6
        }
264 6
        if ($cc) {
265 2
            $message->setCc($cc, $ccName);
266 2
        }
267 6
        if ($bcc) {
268 2
            $message->setBcc($bcc, $bccName);
269 2
        }
270
271
        // add custom headers
272 6
        $this->templateProvider->addCustomHeaders($templateBaseId, $message, $params);
273
274
        // send the message
275 6
        $mailer = $this->getMailer($params);
276 6
        $messagesSent = $mailer->send($message, $failedRecipients);
277
278
        // if the message was successfully sent,
279
        // and it should be made available in web-view
280 6
        if ($messagesSent && array_key_exists($this->templateProvider->getWebViewTokenId(), $params)) {
281
282
            // store the email
283 2
            $sentEmail = new SentEmail();
284 2
            $sentEmail->setToken($params[$this->templateProvider->getWebViewTokenId()]);
285 2
            $sentEmail->setTemplate($templateBaseId);
286 2
            $sentEmail->setSent(new \DateTime());
287
288
            // recursively add all template-variables for the wrapper-templates and contentItems
289 2
            $webViewParams = $this->templateProvider->addTemplateVariablesFor($template, $webViewParams);
290
291
            // replace absolute image-paths with relative ones.
292 2
            $webViewParams = $this->templateProvider->makeImagePathsWebRelative($webViewParams, $emailLocale);
293
294
            // recursively add snippets for the wrapper-templates and contentItems
295 2
            $webViewParams = $this->templateProvider->addTemplateSnippetsWithImagesFor($template, $webViewParams, $emailLocale, true);
296
297 2
            $sentEmail->setVariables($webViewParams);
298
299
            // save only successfull recipients
300 2
            if (!is_array($to)) {
301 2
                $to = array($to);
302 2
            }
303 2
            $successfulRecipients = array_diff($to, $failedRecipients);
304 2
            $sentEmail->setRecipients($successfulRecipients);
305
306
            // write to db
307 2
            $em = $this->managerRegistry->getManager();
308 2
            $em->persist($sentEmail);
309 2
            $em->flush($sentEmail);
310 2
            $em->clear();
311 2
            gc_collect_cycles();
312 2
        }
313
314 6
        return $messagesSent;
315
    }
316
317
    /**
318
     * Remove all Embeded Attachments that are not referenced in the html-body from the message
319
     * to avoid using unneccary bandwidth.
320
     *
321
     * @param \Swift_Message $message
322
     * @param array $params the parameters used to render the html
323
     * @param string $htmlBody
324
     * @return \Swift_Message
325
     */
326 7
    private function removeUnreferecedEmbededItemsFromMessage(\Swift_Message $message, $params, $htmlBody)
327
    {
328 7
        foreach ($params as $key => $value) {
329
            // remove unreferenced attachments from contentItems too.
330 7
            if ($key === 'contentItems') {
331 2
                foreach ($value as $contentItemParams) {
332 7
                    $message = $this->removeUnreferecedEmbededItemsFromMessage($message, $contentItemParams, $htmlBody);
333 3
                }
334 2
            } else {
335
336
                // check if the embeded items are referenced in the templates
337 7
                $isEmbededItem = is_string($value) && preg_match($this->encodedItemIdPattern, $value) == 1;
338
339 7
                if ($isEmbededItem && stripos($htmlBody, $value) === false) {
340
                    // remove unreferenced items
341 7
                    $children = array();
342
343 7
                    foreach ($message->getChildren() as $attachment) {
344 7
                        if ("cid:".$attachment->getId() != $value) {
345 7
                            $children[] = $attachment;
346 7
                        }
347 7
                    }
348
349 7
                    $message->setChildren($children);
350 7
                }
351
            }
352 7
        }
353
354 7
        return $message;
355
    }
356
357
    /**
358
     * Get the template from the cache if it was loaded already
359
     * @param  string         $template
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 7
        }
367
368 7
        return $this->templateCache[$template];
369
    }
370
371
    /**
372
     * Recursively embed all images in the array into the message
373
     * @param  \Swift_Message $message
374
     * @param  array $params
375
     * @return array $params
376
     */
377 7
    private function embedImages(&$message, &$params)
378
    {
379
        // loop through the array
380 7
        foreach ($params as $key => $value) {
381
382
            // if the current value is an array
383 7
            if (is_array($value)) {
384
                // search for more images deeper in the arrays
385 2
                $value = $this->embedImages($message, $value);
386 2
                $params[$key] = $value;
387
388
            // if the current value is an existing file from the image-folder, embed it
389 7
            } elseif (is_string($value)) {
390 7
                if (is_file($value)) {
391
392
                    // check if the file is from an allowed folder
393 7
                    if ($this->templateProvider->isFileAllowed($value) !== false) {
394 7
                        $encodedImage = $this->cachedEmbedImage($value);
395 7
                        if ($encodedImage != null) {
396 7
                            $id = $message->embed($encodedImage);
397 7
                            $params[$key] = $id;
398 7
                        }
399 7
                    }
400
401
                // the $filePath isn't a regular file
402 7
                } 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) && stripos(get_resource_type($value), "gd") == 0) {
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 1
                $params[$key] = $id;
418 1
            } else {
419
                // don't do anything
420
            }
421 7
        }
422
423
        // remove duplicate-attachments
424 7
        $message->setChildren(array_unique($message->getChildren()));
425
426 7
        return $params;
427
    }
428
429
    /**
430
     * Get the Swift_Image for the file.
431
     * @param  string            $filePath
432
     * @return \Swift_Image|null
433
     */
434 7
    private function cachedEmbedImage($filePath)
435
    {
436 7
        $filePath = realpath($filePath);
437 7
        if (!array_key_exists($filePath, $this->imageCache)) {
438 7
            if (is_file($filePath)) {
439
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
449
                } else {
450
                    // @codeCoverageIgnoreEnd
451
                    // add the image to the cache
452 7
                    $this->imageCache[$filePath] = $image;
453
                }
454
455 7
            }
456
457 7
        }
458
459 7
        return $this->imageCache[$filePath];
460
    }
461
462
    /**
463
     * (non-PHPdoc)
464
     * @see Azine\EmailBundle\Services.TemplateTwigSwiftMailerInterface::sendSingleEmail()
465
     * @param string $to
466
     * @param string $toName
467
     * @param string $subject
468
     * @param array $params
469
     * @param string $template
470
     * @param string $emailLocale
471
     * @param null $from
472
     * @param null $fromName
473
     * @param \Swift_Message $message
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 sizeof($failedRecipients) == 0;
482
    }
483
484
    /**
485
     * Override the fosuserbundles original sendMessage, to embed template variables etc. into html-emails.
486
     * @param  string  $templateName
487
     * @param  array   $context
488
     * @param  string  $fromEmail
489
     * @param  string  $toEmail
490
     * @return boolean true if the mail was sent successfully, else false
491
     */
492 2
    protected function sendMessage($templateName, $context, $fromEmail, $toEmail)
493
    {
494
        // get the subject from the template
495
        // => make sure the subject block exists in your fos-templates (FOSUserBundle:Registration:email.txt.twig & FOSUserBundle:Resetting:email.txt.twig)
496 2
        $twigTemplate = $this->loadTemplate($templateName);
497 2
        $subject = $twigTemplate->renderBlock('subject', $context);
498
499 2
        return $this->sendSingleEmail($toEmail, null, $subject, $context, $templateName, $this->translator->getLocale(), $fromEmail);
500
    }
501
502
    /**
503
     * Return the Swift_Mailer to be used for sending mails immediately (e.g. instead of spooling them) if it is configured
504
     * @param $params
505
     * @return \Swift_Mailer
506
     */
507 6
    private function getMailer($params){
508
        // if the second mailer for immediate mail-delivery has been configured
509 6
        if($this->immediateMailer != null){
510
            // check if this template has been configured to be sent immediately
511
            if(array_key_exists(AzineTemplateProvider::SEND_IMMEDIATELY_FLAG, $params) && $params[AzineTemplateProvider::SEND_IMMEDIATELY_FLAG]) {
512
                return $this->immediateMailer;
513
            }
514
        }
515 6
        return $this->mailer;
516
    }
517
}
518