Completed
Pull Request — master (#43)
by Eugene
15:58
created

reAttachAllEntities()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 21.1875

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 3
cts 12
cp 0.25
rs 8.9777
c 0
b 0
f 0
cc 6
nc 5
nop 1
crap 21.1875
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 with message: since Symfony 4.2, use {@see AbstractController} instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

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) {
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
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...
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) {
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);
0 ignored issues
show
Bug introduced by
It seems like $managedEntity defined by $em->find($className, $next->getId()) on line 250 can also be of type null; however, Doctrine\ORM\EntityManager::refresh() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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
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...
274 1
    {
275 1
        $folder = $this->getTemplateProviderService()->getFolderFrom($folderKey);
276 1
        if (false !== $folder) {
277
            $fullPath = $folder.urldecode($filename);
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 = \Swift_Message::newInstance();
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)) {
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);
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);
431 1
        $error = curl_error($ch);
432
        $result['curlHttpCode'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
433
        curl_close($ch);
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'];
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)) {
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