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

AzineEmailTemplateController::renderResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 3
crap 1
1
<?php
2
3
namespace Azine\EmailBundle\Controller;
4
5
use Azine\EmailBundle\Entity\SentEmail;
6
use Azine\EmailBundle\Services\TemplateProviderInterface;
7
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
8
use Symfony\Component\HttpFoundation\BinaryFileResponse;
9
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
10
use Symfony\Component\HttpFoundation\JsonResponse;
11
use Symfony\Component\HttpFoundation\RedirectResponse;
12
use Symfony\Component\HttpFoundation\Request;
13
use Symfony\Component\HttpFoundation\Response;
14
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
15
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
16
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
17
18
/**
19
 * This controller provides the following actions:.
20
 *
21
 * index: view a list of all your templates with the option to send a test mail with "dummy"-data to an email-address of your choice (see WebViewServiceInterface::getTemplatesForWebPreView() & WebViewServiceInterface::getTestMailAccounts) .
22
 * webPreView: shows the selected html- or txt-email-template filled with the dummy-data you defined (in the WebViewServiceInterface::getDummyVarsFor() function).
23
 * webView: shows an email that has been sent (and stored as SentEmail-entity in the database)
24
 * sendTestMail: sends an email filled with the dummy-data you defined to the selected email-address.
25
 * serveImage: serve an image from the template-image-folder
26
 *
27
 * @author dominik
28
 */
29
class AzineEmailTemplateController extends Controller
30
{
31
    /**
32
     * Show a set of options to view html- and text-versions of email in the browser and send them as emails to test-accounts.
33
     */
34 1
    public function indexAction(Request $request)
35
    {
36 1
        $customEmail = $request->get('customEmail', '[email protected]');
37 1
        $templates = $this->get('azine_email_web_view_service')->getTemplatesForWebPreView();
38 1
        $emails = $this->get('azine_email_web_view_service')->getTestMailAccounts();
39
40 1
        return $this->get('templating')
41 1
                    ->renderResponse('AzineEmailBundle:Webview:index.html.twig',
42
                    array(
43 1
                        'customEmail' => $customEmail,
44 1
                        'templates' => $templates,
45 1
                        'emails' => $emails,
46
                    ));
47
    }
48
49
    /**
50
     * Show a web-preview-version of an email-template, filled with dummy-content.
51
     *
52
     * @param string $format
53
     *
54
     * @return Response
55
     */
56 1
    public function webPreViewAction(Request $request, $template, $format = null)
57
    {
58 1
        if ('txt' !== $format) {
59 1
            $format = 'html';
60
        }
61
62 1
        $template = urldecode($template);
63
64 1
        $locale = $request->getLocale();
65
66
        // merge request vars with dummyVars, but make sure request vars remain as they are.
67 1
        $emailVars = array_merge(array(), $request->query->all());
68 1
        $emailVars = $this->get('azine_email_web_view_service')->getDummyVarsFor($template, $locale, $emailVars);
69 1
        $emailVars = array_merge($emailVars, $request->query->all());
70
71
        // add the styles
72 1
        $emailVars = $this->getTemplateProviderService()->addTemplateVariablesFor($template, $emailVars);
73
74
        // add the from-email for the footer-text
75 1
        if (!array_key_exists('fromEmail', $emailVars)) {
76 1
            $noReply = $this->getParameter('azine_email_no_reply');
77 1
            $emailVars['fromEmail'] = $noReply['email'];
78 1
            $emailVars['fromName'] = $noReply['name'];
79
        }
80
81
        // set the emailLocale for the templates
82 1
        $emailVars['emailLocale'] = $locale;
83
84
        // replace absolute image-paths with relative ones.
85 1
        $emailVars = $this->getTemplateProviderService()->makeImagePathsWebRelative($emailVars, $locale);
86
87
        // add code-snippets
88 1
        $emailVars = $this->getTemplateProviderService()->addTemplateSnippetsWithImagesFor($template, $emailVars, $locale);
89
90
        // render & return email
91 1
        $response = $this->renderResponse("$template.$format.twig", $emailVars);
92
93
        // add campaign tracking params
94 1
        $campaignParams = $this->getTemplateProviderService()->getCampaignParamsFor($template, $emailVars);
95 1
        $campaignParams['utm_medium'] = 'webPreview';
96 1
        if (sizeof($campaignParams) > 0) {
97 1
            $htmlBody = $response->getContent();
98 1
            $htmlBody = $this->get('azine.email.bundle.twig.filters')->addCampaignParamsToAllUrls($htmlBody, $campaignParams);
99
100 1
            $emailOpenTrackingCodeBuilder = $this->get('azine_email_email_open_tracking_code_builder');
101 1
            if ($emailOpenTrackingCodeBuilder) {
102
                // add an image at the end of the html tag with the tracking-params to track email-opens
103 1
                $imgTrackingCode = $emailOpenTrackingCodeBuilder->getTrackingImgCode($template, $campaignParams, $emailVars, 'dummy', '[email protected]', null, null);
104 1
                if ($imgTrackingCode && strlen($imgTrackingCode) > 0) {
105
                    // replace the tracking url, so no request is made to the real tracking system.
106 1
                    $imgTrackingCode = str_replace('://', '://webview-dummy-domain.', $imgTrackingCode);
107 1
                    $htmlCloseTagPosition = strpos($htmlBody, '</html>');
108 1
                    $htmlBody = substr_replace($htmlBody, $imgTrackingCode, $htmlCloseTagPosition, 0);
109
                }
110
            }
111 1
            $response->setContent($htmlBody);
112
        }
113
114
        // if the requested format is txt, remove the html-part
115 1
        if ('txt' == $format) {
116
            // set the correct content-type
117 1
            $response->headers->set('Content-Type', 'text/plain');
118
119
            // cut away the html-part
120 1
            $content = $response->getContent();
121 1
            $textEnd = stripos($content, '<!doctype');
122 1
            $response->setContent(substr($content, 0, $textEnd));
123
        }
124
125 1
        return $response;
126
    }
127
128
    /**
129
     * Show a web-version of an email that has been sent to recipients and has been stored in the database.
130
     *
131
     * @param Request $request
132
     * @param string  $token
133
     *
134
     * @return Response
135
     */
136 6
    public function webViewAction(Request $request, $token)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
137
    {
138
        // find email recipients, template & params
139 6
        $sentEmail = $this->getSentEmailForToken($token);
140
141
        // check if the sent email is available
142 6
        if (null !== $sentEmail) {
143
            // check if the current user is allowed to see the email
144 5
            if ($this->userIsAllowedToSeeThisMail($sentEmail)) {
145 3
                $template = $sentEmail->getTemplate();
146 3
                $emailVars = $sentEmail->getVariables();
147
148
                // re-attach all entities to the EntityManager.
149 3
                $this->reAttachAllEntities($emailVars);
150
151
                // remove the web-view-token from the param-array
152 3
                $templateProvider = $this->getTemplateProviderService();
153 3
                unset($emailVars[$templateProvider->getWebViewTokenId()]);
154
155
                // render & return email
156 3
                $response = $this->renderResponse("$template.html.twig", $emailVars);
157
158 3
                $campaignParams = $templateProvider->getCampaignParamsFor($template, $emailVars);
159
160 3
                if (null != $campaignParams && sizeof($campaignParams) > 0) {
161 1
                    $response->setContent($this->get('azine.email.bundle.twig.filters')->addCampaignParamsToAllUrls($response->getContent(), $campaignParams));
162
                }
163
164 3
                return $response;
165
166
                // if the user is not allowed to see this mail
167
            }
168 2
            $msg = $this->get('translator')->trans('web.pre.view.test.mail.access.denied');
169 2
            throw new AccessDeniedException($msg);
170
        }
171
172
        // the parameters-array is null => the email is not available in webView
173 1
        $days = $this->getParameter('azine_email_web_view_retention');
174 1
        $response = $this->renderResponse('AzineEmailBundle:Webview:mail.not.available.html.twig', array('days' => $days));
175 1
        $response->setStatusCode(404);
176
177 1
        return $response;
178
    }
179
180
    /**
181
     * Check if the user is allowed to see the email.
182
     * => the mail is public or the user is among the recipients or the user is an admin.
183
     *
184
     * @param SentEmail $mail
185
     *
186
     * @return bool
187
     */
188 5
    private function userIsAllowedToSeeThisMail(SentEmail $mail)
189
    {
190 5
        $recipients = $mail->getRecipients();
191
192
        // it is a public email
193 5
        if (null === $recipients) {
194 1
            return true;
195
        }
196
197
        // get the current user
198 4
        $currentUser = null;
199 4
        if (!$this->has('security.token_storage')) {
200
            // @codeCoverageIgnoreStart
201
            throw new \LogicException('The SecurityBundle is not registered in your application.');
202
            // @codeCoverageIgnoreEnd
203
        }
204 4
        $token = $this->get('security.token_storage')->getToken();
205
206
        // check if the token is not null and the user in the token an object
207 4
        if ($token instanceof TokenInterface && is_object($token->getUser())) {
208 3
            $currentUser = $token->getUser();
209
        }
210
211
        // it is not a public email, and a user is logged in
212 4
        if (null !== $currentUser) {
213
            // the user is among the recipients
214 3
            if (false !== array_search($currentUser->getEmail(), $recipients)) {
215 1
                return true;
216
            }
217
218
            // the user is admin
219 2
            if ($currentUser->hasRole('ROLE_ADMIN')) {
220 1
                return true;
221
            }
222
        }
223
224
        // not public email, but
225
        // 		- there is no user, or
226
        //		- the user is not among the recipients and
227
        //		- the user not an admin-user either
228 2
        return false;
229
    }
230
231
    /**
232
     * Replace all unmanaged Objects in the array (recursively)
233
     * by managed Entities fetched via Doctrine EntityManager.
234
     *
235
     *  It is assumed that managed objects can be identified
236
     *  by their id and implement the function getId() to get that id.
237
     *
238
     * @param array $vars passed by reference & manipulated but not returned
239
     *
240
     * @return null
241
     */
242 3
    private function reAttachAllEntities(array &$vars)
243
    {
244 3
        $em = $this->get('doctrine')->getManager();
245 3
        foreach ($vars as $key => $next) {
246
            if (is_object($next) && method_exists($next, 'getId')) {
247
                $className = get_class($next);
248
                $managedEntity = $em->find($className, $next->getId());
249
                if ($managedEntity) {
250
                    $vars[$key] = $managedEntity;
251
                }
252
                continue;
253
            } elseif (is_array($next)) {
254
                $this->reAttachAllEntities($next);
255
                continue;
256
            }
257
        }
258 3
    }
259
260
    /**
261
     * Serve the image from the templates-folder.
262
     *
263
     * @param Request $request
264
     * @param string  $folderKey
265
     * @param string  $filename
266
     *
267
     * @return BinaryFileResponse
268
     */
269 2
    public function serveImageAction(Request $request, $folderKey, $filename)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
270
    {
271 2
        $folder = $this->getTemplateProviderService()->getFolderFrom($folderKey);
272 2
        if (false !== $folder) {
273 1
            $fullPath = $folder.$filename;
274 1
            $response = BinaryFileResponse::create($fullPath);
275 1
            $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE);
276 1
            $response->headers->set('Content-Type', 'image');
277
278 1
            return $response;
279
        }
280
281 1
        throw new FileNotFoundException($filename);
282
    }
283
284
    /**
285
     * @return TemplateProviderInterface
286
     */
287 6
    protected function getTemplateProviderService()
288
    {
289 6
        return $this->get('azine_email_template_provider');
290
    }
291
292
    /**
293
     * @param string   $view
294
     * @param array    $parameters
295
     * @param Response $response
296
     *
297
     * @return Response
298
     */
299 5
    protected function renderResponse($view, array $parameters = array(), Response $response = null)
300
    {
301 5
        return $this->get('templating')->renderResponse($view, $parameters, $response);
302
    }
303
304
    /**
305
     * Get the sent email from the database.
306
     *
307
     * @param string $token the token identifying the sent email
308
     *
309
     * @return SentEmail
310
     */
311 6
    protected function getSentEmailForToken($token)
312
    {
313 6
        $sentEmail = $this->get('doctrine')->getRepository('AzineEmailBundle:SentEmail')->findOneByToken($token);
314
315 6
        return $sentEmail;
316
    }
317
318
    /**
319
     * Send a test-mail for the template to the given email-address.
320
     *
321
     * @param Request $request
322
     * @param string  $template templateId without ending => AzineEmailBundle::baseEmailLayout (without .txt.twig)
323
     * @param string  $email
324
     *
325
     * @return RedirectResponse
326
     */
327
    public function sendTestEmailAction(Request $request, $template, $email)
328
    {
329
        $locale = $request->getLocale();
330
331
        $template = urldecode($template);
332
333
        // get the email-vars for email-sending => absolute fs-paths to images
334
        $emailVars = $this->get('azine_email_web_view_service')->getDummyVarsFor($template, $locale);
335
336
        // send the mail
337
        $message = \Swift_Message::newInstance();
338
        $mailer = $this->get('azine_email_template_twig_swift_mailer');
339
        $sent = $mailer->sendSingleEmail($email, 'Test Recipient', $emailVars['subject'], $emailVars, $template.'.txt.twig', $locale, $emailVars['sendMailAccountAddress'], $emailVars['sendMailAccountName'].' (Test)', $message);
340
341
        $flashBag = $request->getSession()->getFlashBag();
342
343
        $spamReport = $this->getSpamIndexReportForSwiftMessage($message);
344
        if (is_array($spamReport)) {
345
            if (200 == $spamReport['curlHttpCode'] && $spamReport['success']) {
346
                $spamScore = $spamReport['score'];
347
                $spamInfo = "SpamScore: $spamScore! \n".$spamReport['report'];
348
            } else {
349
                //@codeCoverageIgnoreStart
350
                // this only happens if the spam-check-server has a problem / is not responding
351
                $spamScore = 10;
352
                $spamInfo = 'Getting the spam-info failed.
353
                             HttpCode: '.$spamReport['curlHttpCode'].'
354
                             SpamReportMsg: '.$spamReport['message'];
355
                if (array_key_exists('curlError', $spamReport)) {
356
                    $spamInfo .= '
357
                             cURL-Error: '.$spamReport['curlError'];
358
                }
359
                //@codeCoverageIgnoreEnd
360
            }
361
362
            if ($spamScore <= 2) {
363
                $flashBag->add('info', $spamInfo);
364
            } elseif ($spamScore > 2 && $spamScore < 5) {
365
                $flashBag->add('warn', $spamInfo);
366
            } else {
367
                $flashBag->add('error', $spamInfo);
368
            }
369
        }
370
371
        // inform about sent/failed emails
372
        if ($sent) {
373
            $msg = $this->get('translator')->trans('web.pre.view.test.mail.sent.for.%template%.to.%email%', array('%template%' => $template, '%email%' => $email));
374
            $flashBag->add('info', $msg);
375
376
        //@codeCoverageIgnoreStart
377
        } else {
378
            // this only happens if the mail-server has a problem
379
            $msg = $this->get('translator')->trans('web.pre.view.test.mail.failed.for.%template%.to.%email%', array('%template%' => $template, '%email%' => $email));
380
            $flashBag->add('warn', $msg);
381
            //@codeCoverageIgnoreStart
382
        }
383
384
        // show the index page again.
385
        return new RedirectResponse($this->get('router')->generate('azine_email_template_index', array('customEmail' => $email)));
386
    }
387
388
    /**
389
     * Make an RESTful call to http://spamcheck.postmarkapp.com/filter to test the emails-spam-index.
390
     * See http://spamcheck.postmarkapp.com/doc.
391
     *
392
     * @return array TestResult array('success', 'message', 'curlHttpCode', 'curlError', ['score', 'report'])
393
     */
394
    public function getSpamIndexReportForSwiftMessage(\Swift_Message $message, $report = 'long')
395
    {
396
        return $this->getSpamIndexReport($message->toString(), $report);
397
    }
398
399
    /**
400
     * @param $msgString
401
     * @param string $report
402
     *
403
     * @return mixed
404
     */
405 1
    private function getSpamIndexReport($msgString, $report = 'long')
406
    {
407
        // check if cURL is loaded/available
408
        if (!function_exists('curl_init')) {
409
            // @codeCoverageIgnoreStart
410
            return array('success' => false,
411
                            'curlHttpCode' => '-',
412
                            'curlError' => '-',
413
                            'message' => 'No Spam-Check done. cURL module is not available.',
414
                    );
415
            // @codeCoverageIgnoreEnd
416
        }
417
418 1
        $ch = curl_init('http://spamcheck.postmarkapp.com/filter');
419 1
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
420 1
        curl_setopt($ch, CURLOPT_POST, true);
421 1
        $data = array('email' => $msgString, 'options' => $report);
422 1
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
423 1
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Accept: application/json'));
424 1
        curl_setopt($ch, CURLOPT_TIMEOUT, 5); // max wait for 5sec for reply
425
426 1
        $result = json_decode(curl_exec($ch), true);
427 1
        $error = curl_error($ch);
428 1
        $result['curlHttpCode'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
429 1
        curl_close($ch);
430
431 1
        if (strlen($error) > 0) {
432
            $result['curlError'] = $error;
433
        }
434
435 1
        if (!array_key_exists('message', $result)) {
436 1
            $result['message'] = '-';
437
        }
438
439 1
        if (!array_key_exists('success', $result)) {
440
            $result['message'] = "Something went wrong! Here's the content of the curl-reply:\n\n".nl2br(print_r($result, true));
441 1
        } elseif (!$result['success'] && false !== strpos($msgString, 'Content-Transfer-Encoding: base64')) {
442
            $result['message'] = $result['message']."\n\nRemoving the base64-Encoded Mime-Parts might help.";
443
        }
444
445 1
        return $result;
446
    }
447
448
    /**
449
     * Ajax action to check the spam-score for the pasted email-source.
450
     */
451
    public function checkSpamScoreOfSentEmailAction(Request $request)
452
    {
453
        $msgString = $request->get('emailSource');
454
        $spamReport = $this->getSpamIndexReport($msgString);
455
        $spamInfo = '';
456
        if (is_array($spamReport)) {
457
            if (array_key_exists('curlHttpCode', $spamReport) && 200 == $spamReport['curlHttpCode'] && $spamReport['success'] && array_key_exists('score', $spamReport)) {
458
                $spamScore = $spamReport['score'];
459
                $spamInfo = "SpamScore: $spamScore! \n".$spamReport['report'];
460
            //@codeCoverageIgnoreStart
461
                // this only happens if the spam-check-server has a problem / is not responding
462
            } else {
463
                if (array_key_exists('curlHttpCode', $spamReport) && array_key_exists('curlError', $spamReport) && array_key_exists('message', $spamReport)) {
464
                    $spamInfo = 'Getting the spam-info failed.
465
                    HttpCode: '.$spamReport['curlHttpCode'].'
466
                    cURL-Error: '.$spamReport['curlError'].'
467
                    SpamReportMsg: '.$spamReport['message'];
468
                } elseif (null !== $spamReport && is_array($spamReport)) {
469
                    $spamInfo = 'Getting the spam-info failed. This was returned:
470
---Start----------------------------------------------
471
'.implode(";\n", $spamReport).'
472
---End------------------------------------------------';
473
                }
474
                //@codeCoverageIgnoreEnd
475
            }
476
        }
477
478
        return new JsonResponse(array('result' => $spamInfo));
479
    }
480
}
481