Issues (114)

Controller/AzineEmailTemplateController.php (14 issues)

1
<?php
2
3
namespace Azine\EmailBundle\Controller;
4
5
use Azine\EmailBundle\Entity\SentEmail;
6
use Azine\EmailBundle\Services\TemplateProviderInterface;
7
use Doctrine\ORM\EntityManager;
8
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
9
use Symfony\Component\HttpFoundation\BinaryFileResponse;
10
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
11
use Symfony\Component\HttpFoundation\JsonResponse;
12
use Symfony\Component\HttpFoundation\RedirectResponse;
13
use Symfony\Component\HttpFoundation\Request;
14
use Symfony\Component\HttpFoundation\Response;
15
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
16
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
17
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
18
19
/**
20
 * This controller provides the following actions:.
21
 *
22
 * 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) .
23
 * webPreView: shows the selected html- or txt-email-template filled with the dummy-data you defined (in the WebViewServiceInterface::getDummyVarsFor() function).
24
 * webView: shows an email that has been sent (and stored as SentEmail-entity in the database)
25
 * sendTestMail: sends an email filled with the dummy-data you defined to the selected email-address.
26
 * serveImage: serve an image from the template-image-folder
27
 *
28
 * @author dominik
29
 */
30
class AzineEmailTemplateController extends Controller
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Bundle\Framework...e\Controller\Controller has been deprecated: since Symfony 4.2, use "Symfony\Bundle\FrameworkBundle\Controller\AbstractController" instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

30
class AzineEmailTemplateController extends /** @scrutinizer ignore-deprecated */ Controller
Loading history...
31
{
32
    /**
33
     * Show a set of options to view html- and text-versions of email in the browser and send them as emails to test-accounts.
34 1
     */
35
    public function indexAction(Request $request)
36 1
    {
37 1
        $customEmail = $request->get('customEmail', '[email protected]');
38 1
        $templates = $this->get('azine_email_web_view_service')->getTemplatesForWebPreView();
39
        $emails = $this->get('azine_email_web_view_service')->getTestMailAccounts();
40 1
41 1
        return $this->get('templating')
42
                    ->renderResponse('AzineEmailBundle:Webview:index.html.twig',
43 1
                    array(
44 1
                        'customEmail' => $customEmail,
45 1
                        'templates' => $templates,
46
                        'emails' => $emails,
47
                    ));
48
    }
49
50
    /**
51
     * Show a web-preview-version of an email-template, filled with dummy-content.
52
     *
53
     * @param string $format
54
     *
55
     * @return Response
56 1
     */
57
    public function webPreViewAction(Request $request, $template, $format = null)
58 1
    {
59 1
        if ('txt' !== $format) {
60
            $format = 'html';
61
        }
62 1
63
        $template = urldecode($template);
64 1
65
        $locale = $request->getLocale();
66
67 1
        // merge request vars with dummyVars, but make sure request vars remain as they are.
68 1
        $emailVars = array_merge(array(), $request->query->all());
69 1
        $emailVars = $this->get('azine_email_web_view_service')->getDummyVarsFor($template, $locale, $emailVars);
70
        $emailVars = array_merge($emailVars, $request->query->all());
71
72 1
        // add the styles
73
        $emailVars = $this->getTemplateProviderService()->addTemplateVariablesFor($template, $emailVars);
74
75 1
        // add the from-email for the footer-text
76 1
        if (!array_key_exists('fromEmail', $emailVars)) {
77 1
            $noReply = $this->getParameter('azine_email_no_reply');
78 1
            $emailVars['fromEmail'] = $noReply['email'];
79
            $emailVars['fromName'] = $noReply['name'];
80
        }
81
82 1
        // set the emailLocale for the templates
83
        $emailVars['emailLocale'] = $locale;
84
85 1
        // replace absolute image-paths with relative ones.
86
        $emailVars = $this->getTemplateProviderService()->makeImagePathsWebRelative($emailVars, $locale);
87
88 1
        // add code-snippets
89
        $emailVars = $this->getTemplateProviderService()->addTemplateSnippetsWithImagesFor($template, $emailVars, $locale);
90
91 1
        // render & return email
92
        $response = $this->renderResponse("$template.$format.twig", $emailVars);
93
94 1
        // add campaign tracking params
95 1
        $campaignParams = $this->getTemplateProviderService()->getCampaignParamsFor($template, $emailVars);
96 1
        $campaignParams['utm_medium'] = 'webPreview';
97 1
        if (sizeof($campaignParams) > 0) {
98 1
            $htmlBody = $response->getContent();
99
            $htmlBody = $this->get('azine.email.bundle.twig.filters')->addCampaignParamsToAllUrls($htmlBody, $campaignParams);
100 1
101 1
            $emailOpenTrackingCodeBuilder = $this->get('azine_email_email_open_tracking_code_builder');
102
            if ($emailOpenTrackingCodeBuilder) {
0 ignored issues
show
$emailOpenTrackingCodeBuilder is of type object, thus it always evaluated to true.
Loading history...
103 1
                // add an image at the end of the html tag with the tracking-params to track email-opens
104 1
                $imgTrackingCode = $emailOpenTrackingCodeBuilder->getTrackingImgCode($template, $campaignParams, $emailVars, 'dummy', '[email protected]', null, null);
105
                if ($imgTrackingCode && strlen($imgTrackingCode) > 0) {
106 1
                    // replace the tracking url, so no request is made to the real tracking system.
107 1
                    $imgTrackingCode = str_replace('://', '://webview-dummy-domain.', $imgTrackingCode);
108 1
                    $htmlCloseTagPosition = strpos($htmlBody, '</html>');
109
                    $htmlBody = substr_replace($htmlBody, $imgTrackingCode, $htmlCloseTagPosition, 0);
110
                }
111 1
            }
112
            $response->setContent($htmlBody);
113
        }
114
115 1
        // if the requested format is txt, remove the html-part
116
        if ('txt' == $format) {
117 1
            // set the correct content-type
118
            $response->headers->set('Content-Type', 'text/plain');
119
120 1
            // cut away the html-part
121 1
            $content = $response->getContent();
122 1
            $textEnd = stripos($content, '<!doctype');
123
            $response->setContent(substr($content, 0, $textEnd));
124
        }
125 1
126
        return $response;
127
    }
128
129
    /**
130
     * Show a web-version of an email that has been sent to recipients and has been stored in the database.
131
     *
132
     * @param Request $request
133
     * @param string  $token
134
     *
135
     * @return Response
136 6
     */
137
    public function webViewAction(Request $request, $token)
0 ignored issues
show
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

137
    public function webViewAction(/** @scrutinizer ignore-unused */ Request $request, $token)

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

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

273
    public function serveImageAction(/** @scrutinizer ignore-unused */ Request $request, $folderKey, $filename)

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

Loading history...
274 1
    {
275 1
        $folder = $this->getTemplateProviderService()->getFolderFrom($folderKey);
276 1
        if (false !== $folder) {
277
            $fullPath = $folder.urldecode($filename);
0 ignored issues
show
Are you sure $folder of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

277
            $fullPath = /** @scrutinizer ignore-type */ $folder.urldecode($filename);
Loading history...
278 1
            $response = BinaryFileResponse::create($fullPath);
279
            $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE);
280
            $response->headers->set('Content-Type', 'image');
281 1
282
            return $response;
283
        }
284
285
        throw new FileNotFoundException($filename);
286
    }
287 6
288
    /**
289 6
     * @return TemplateProviderInterface
290
     */
291
    protected function getTemplateProviderService()
292
    {
293
        return $this->get('azine_email_template_provider');
294
    }
295
296
    /**
297
     * @param string   $view
298
     * @param array    $parameters
299 5
     * @param Response $response
300
     *
301 5
     * @return Response
302
     */
303
    protected function renderResponse($view, array $parameters = array(), Response $response = null)
304
    {
305
        return $this->get('templating')->renderResponse($view, $parameters, $response);
306
    }
307
308
    /**
309
     * Get the sent email from the database.
310
     *
311 6
     * @param string $token the token identifying the sent email
312
     *
313 6
     * @return SentEmail
314
     */
315 6
    protected function getSentEmailForToken($token)
316
    {
317
        $sentEmail = $this->get('doctrine')->getRepository('AzineEmailBundle:SentEmail')->findOneByToken($token);
318
319
        return $sentEmail;
320
    }
321
322
    /**
323
     * Send a test-mail for the template to the given email-address.
324
     *
325
     * @param Request $request
326
     * @param string  $template templateId without ending => AzineEmailBundle::baseEmailLayout (without .txt.twig)
327
     * @param string  $email
328
     *
329
     * @return RedirectResponse
330
     */
331
    public function sendTestEmailAction(Request $request, $template, $email)
332
    {
333
        $locale = $request->getLocale();
334
335
        $template = urldecode($template);
336
337
        // get the email-vars for email-sending => absolute fs-paths to images
338
        $emailVars = $this->get('azine_email_web_view_service')->getDummyVarsFor($template, $locale);
339
340
        // send the mail
341
        $message = new \Swift_Message();
342
        $mailer = $this->get('azine_email_template_twig_swift_mailer');
343
        $sent = $mailer->sendSingleEmail($email, 'Test Recipient', $emailVars['subject'], $emailVars, $template.'.txt.twig', $locale, $emailVars['sendMailAccountAddress'], $emailVars['sendMailAccountName'].' (Test)', $message);
344
345
        $flashBag = $request->getSession()->getFlashBag();
346
347
        $spamReport = $this->getSpamIndexReportForSwiftMessage($message);
348
        if (is_array($spamReport)) {
0 ignored issues
show
The condition is_array($spamReport) is always true.
Loading history...
349
            if (200 == $spamReport['curlHttpCode'] && $spamReport['success']) {
350
                $spamScore = $spamReport['score'];
351
                $spamInfo = "SpamScore: $spamScore! \n".$spamReport['report'];
352
            } else {
353
                //@codeCoverageIgnoreStart
354
                // this only happens if the spam-check-server has a problem / is not responding
355
                $spamScore = 10;
356
                $spamInfo = 'Getting the spam-info failed.
357
                             HttpCode: '.$spamReport['curlHttpCode'].'
358
                             SpamReportMsg: '.$spamReport['message'];
359
                if (array_key_exists('curlError', $spamReport)) {
360
                    $spamInfo .= '
361
                             cURL-Error: '.$spamReport['curlError'];
362
                }
363
                //@codeCoverageIgnoreEnd
364
            }
365
366
            if ($spamScore <= 2) {
367
                $flashBag->add('info', $spamInfo);
368
            } elseif ($spamScore > 2 && $spamScore < 5) {
369
                $flashBag->add('warn', $spamInfo);
370
            } else {
371
                $flashBag->add('error', $spamInfo);
372
            }
373
        }
374
375
        // inform about sent/failed emails
376
        if ($sent) {
377
            $msg = $this->get('translator')->trans('web.pre.view.test.mail.sent.for.%template%.to.%email%', array('%template%' => $template, '%email%' => $email));
378
            $flashBag->add('info', $msg);
379
380
        //@codeCoverageIgnoreStart
381
        } else {
382
            // this only happens if the mail-server has a problem
383
            $msg = $this->get('translator')->trans('web.pre.view.test.mail.failed.for.%template%.to.%email%', array('%template%' => $template, '%email%' => $email));
384
            $flashBag->add('warn', $msg);
385
            //@codeCoverageIgnoreStart
386
        }
387
388
        // show the index page again.
389
        return new RedirectResponse($this->get('router')->generate('azine_email_template_index', array('customEmail' => $email)));
390
    }
391
392
    /**
393
     * Make an RESTful call to http://spamcheck.postmarkapp.com/filter to test the emails-spam-index.
394
     * See http://spamcheck.postmarkapp.com/doc.
395
     *
396
     * @return array TestResult array('success', 'message', 'curlHttpCode', 'curlError', ['score', 'report'])
397
     */
398
    public function getSpamIndexReportForSwiftMessage(\Swift_Message $message, $report = 'long')
399
    {
400
        return $this->getSpamIndexReport($message->toString(), $report);
401
    }
402
403
    /**
404
     * @param $msgString
405 1
     * @param string $report
406
     *
407
     * @return mixed
408
     */
409
    private function getSpamIndexReport($msgString, $report = 'long')
410
    {
411
        // check if cURL is loaded/available
412
        if (!function_exists('curl_init')) {
413
            // @codeCoverageIgnoreStart
414
            return array('success' => false,
415
                            'curlHttpCode' => '-',
416
                            'curlError' => '-',
417
                            'message' => 'No Spam-Check done. cURL module is not available.',
418 1
                    );
419 1
            // @codeCoverageIgnoreEnd
420 1
        }
421 1
422 1
        $ch = curl_init('http://spamcheck.postmarkapp.com/filter');
423 1
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
0 ignored issues
show
It seems like $ch can also be of type false; however, parameter $ch of curl_setopt() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

423
        curl_setopt(/** @scrutinizer ignore-type */ $ch, CURLOPT_RETURNTRANSFER, true);
Loading history...
424 1
        curl_setopt($ch, CURLOPT_POST, true);
425
        $data = array('email' => $msgString, 'options' => $report);
426 1
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
427 1
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Accept: application/json'));
428 1
        curl_setopt($ch, CURLOPT_TIMEOUT, 5); // max wait for 5sec for reply
429 1
430
        $result = json_decode(curl_exec($ch), true);
0 ignored issues
show
It seems like $ch can also be of type false; however, parameter $ch of curl_exec() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

430
        $result = json_decode(curl_exec(/** @scrutinizer ignore-type */ $ch), true);
Loading history...
431 1
        $error = curl_error($ch);
0 ignored issues
show
It seems like $ch can also be of type false; however, parameter $ch of curl_error() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

431
        $error = curl_error(/** @scrutinizer ignore-type */ $ch);
Loading history...
432
        $result['curlHttpCode'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
0 ignored issues
show
It seems like $ch can also be of type false; however, parameter $ch of curl_getinfo() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

432
        $result['curlHttpCode'] = curl_getinfo(/** @scrutinizer ignore-type */ $ch, CURLINFO_HTTP_CODE);
Loading history...
433
        curl_close($ch);
0 ignored issues
show
It seems like $ch can also be of type false; however, parameter $ch of curl_close() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

433
        curl_close(/** @scrutinizer ignore-type */ $ch);
Loading history...
434
435 1
        if (strlen($error) > 0) {
436 1
            $result['curlError'] = $error;
437
        }
438
439 1
        if (!array_key_exists('message', $result)) {
440
            $result['message'] = '-';
441 1
        }
442
443
        if (!array_key_exists('success', $result)) {
444
            $result['message'] = "Something went wrong! Here's the content of the curl-reply:\n\n".nl2br(print_r($result, true));
445 1
        } elseif (!$result['success'] && false !== strpos($msgString, 'Content-Transfer-Encoding: base64')) {
446
            $result['message'] = $result['message']."\n\nRemoving the base64-Encoded Mime-Parts might help.";
447
        }
448
449
        return $result;
450
    }
451
452
    /**
453
     * Ajax action to check the spam-score for the pasted email-source.
454
     */
455
    public function checkSpamScoreOfSentEmailAction(Request $request)
456
    {
457
        $msgString = $request->get('emailSource');
458
        $spamReport = $this->getSpamIndexReport($msgString);
459
        $spamInfo = '';
460
        if (is_array($spamReport)) {
461
            if (array_key_exists('curlHttpCode', $spamReport) && 200 == $spamReport['curlHttpCode'] && $spamReport['success'] && array_key_exists('score', $spamReport)) {
462
                $spamScore = $spamReport['score'];
463
                $spamInfo = "SpamScore: $spamScore! \n".$spamReport['report'];
0 ignored issues
show
Are you sure $spamReport['report'] of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

463
                $spamInfo = "SpamScore: $spamScore! \n"./** @scrutinizer ignore-type */ $spamReport['report'];
Loading history...
464
            //@codeCoverageIgnoreStart
465
                // this only happens if the spam-check-server has a problem / is not responding
466
            } else {
467
                if (array_key_exists('curlHttpCode', $spamReport) && array_key_exists('curlError', $spamReport) && array_key_exists('message', $spamReport)) {
468
                    $spamInfo = 'Getting the spam-info failed.
469
                    HttpCode: '.$spamReport['curlHttpCode'].'
470
                    cURL-Error: '.$spamReport['curlError'].'
471
                    SpamReportMsg: '.$spamReport['message'];
472
                } elseif (null !== $spamReport && is_array($spamReport)) {
0 ignored issues
show
The condition is_array($spamReport) is always true.
Loading history...
473
                    $spamInfo = 'Getting the spam-info failed. This was returned:
474
---Start----------------------------------------------
475
'.implode(";\n", $spamReport).'
476
---End------------------------------------------------';
477
                }
478
                //@codeCoverageIgnoreEnd
479
            }
480
        }
481
482
        return new JsonResponse(array('result' => $spamInfo));
483
    }
484
}
485