Completed
Push — sendEmailChangeConfirmation ( 0a40ce...acd4f6 )
by Dominik
06:26 queued 04:09
created

AzineEmailTemplateController   C

Complexity

Total Complexity 60

Size/Duplication

Total Lines 441
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 73.3%

Importance

Changes 0
Metric Value
wmc 60
lcom 1
cbo 14
dl 0
loc 441
ccs 129
cts 176
cp 0.733
rs 5.6363
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A indexAction() 0 14 1
C webPreViewAction() 0 70 8
B webViewAction() 0 46 4
C userIsAllowedToSeeThisMail() 0 45 8
B reAttachAllEntities() 0 18 6
A serveImageAction() 0 14 2
A getTemplateProviderService() 0 4 1
A renderResponse() 0 4 1
A getSentEmailForToken() 0 6 1
B sendTestEmailAction() 0 57 9
A getSpamIndexReportForSwiftMessage() 0 4 1
C getSpamIndexReport() 0 45 7
C checkSpamScoreOfSentEmailAction() 0 30 11

How to fix   Complexity   

Complex Class

Complex classes like AzineEmailTemplateController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AzineEmailTemplateController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Azine\EmailBundle\Controller;
4
5
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
6
use Symfony\Component\HttpFoundation\JsonResponse;
7
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
8
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
9
use Symfony\Component\HttpFoundation\RedirectResponse;
10
use Azine\EmailBundle\Entity\SentEmail;
11
use Symfony\Component\HttpFoundation\Response;
12
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
13
use Symfony\Component\HttpFoundation\BinaryFileResponse;
14
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
15
use Symfony\Component\HttpFoundation\Request;
16
use Azine\EmailBundle\Services\TemplateProviderInterface;
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
30
class AzineEmailTemplateController extends Controller
31
{
32
33
    /**
34
     * Show a set of options to view html- and text-versions of email in the browser and send them as emails to test-accounts
35
     */
36 1
    public function indexAction(Request $request)
37
    {
38 1
        $customEmail = $request->get('customEmail', '[email protected]');
39 1
        $templates = $this->get('azine_email_web_view_service')->getTemplatesForWebPreView();
40 1
        $emails = $this->get('azine_email_web_view_service')->getTestMailAccounts();
41
42 1
        return $this->get('templating')
43 1
                    ->renderResponse("AzineEmailBundle:Webview:index.html.twig",
44
                    array(	
45 1
                        'customEmail' => $customEmail,
46 1
                        'templates'   => $templates,
47 1
                        'emails'      => $emails,
48 1
                    ));
49
    }
50
51
    /**
52
     * Show a web-preview-version of an email-template, filled with dummy-content
53
     * @param string $format
54
     * @return Response
55
     */
56 1
    public function webPreViewAction(Request $request, $template, $format = null)
57
    {
58
        
59 1
        if ($format !== "txt") {
60 1
            $format = "html";
61 1
        }
62
63 1
        $locale = $request->getLocale();
64
65
        // merge request vars with dummyVars, but make sure request vars remain as they are.
66 1
        $emailVars = array_merge(array(), $request->query->all());
67 1
        $emailVars = $this->get('azine_email_web_view_service')->getDummyVarsFor($template, $locale, $emailVars);
68 1
        $emailVars = array_merge($emailVars, $request->query->all());
69
70
        // add the styles
71 1
        $emailVars = $this->getTemplateProviderService()->addTemplateVariablesFor($template, $emailVars);
72
73
        // add the from-email for the footer-text
74 1
        if (!array_key_exists('fromEmail', $emailVars)) {
75 1
            $noReply = $this->getParameter('azine_email_no_reply');
76 1
            $emailVars['fromEmail'] = $noReply['email'];
77 1
            $emailVars['fromName'] = $noReply['name'];
78 1
        }
79
80
        // set the emailLocale for the templates
81 1
        $emailVars['emailLocale'] = $locale;
82
83
        // replace absolute image-paths with relative ones.
84 1
        $emailVars = $this->getTemplateProviderService()->makeImagePathsWebRelative($emailVars, $locale);
85
86
        // add code-snippets
87 1
        $emailVars = $this->getTemplateProviderService()->addTemplateSnippetsWithImagesFor($template, $emailVars, $locale);
88
89
        // render & return email
90 1
        $response = $this->renderResponse("$template.$format.twig", $emailVars);
91
92
        // add campaign tracking params
93 1
        $campaignParams = $this->getTemplateProviderService()->getCampaignParamsFor($template, $emailVars);
94 1
        $campaignParams['utm_medium'] = 'webPreview';
95 1
        if(sizeof($campaignParams) > 0) {
96 1
            $htmlBody = $response->getContent();
97 1
            $htmlBody = $this->get("azine.email.bundle.twig.filters")->addCampaignParamsToAllUrls($htmlBody, $campaignParams);
98
99 1
            $emailOpenTrackingCodeBuilder = $this->get('azine_email_email_open_tracking_code_builder');
100 1
            if ($emailOpenTrackingCodeBuilder) {
101
                // add an image at the end of the html tag with the tracking-params to track email-opens
102 1
                $imgTrackingCode = $emailOpenTrackingCodeBuilder->getTrackingImgCode($template, $campaignParams, $emailVars, "dummy", "[email protected]", null, null);
103 1
                if ($imgTrackingCode && strlen($imgTrackingCode) > 0) {
104
                    // replace the tracking url, so no request is made to the real tracking system.
105 1
                    $imgTrackingCode = str_replace("://", "://webview-dummy-domain.", $imgTrackingCode);
106 1
                    $htmlCloseTagPosition = strpos($htmlBody, "</html>");
107 1
                    $htmlBody = substr_replace($htmlBody, $imgTrackingCode, $htmlCloseTagPosition, 0);
108 1
                }
109 1
            }
110 1
            $response->setContent($htmlBody);
111 1
        }
112
113
        // if the requested format is txt, remove the html-part
114 1
        if ($format == "txt") {
115
            // set the correct content-type
116 1
            $response->headers->set("Content-Type","text/plain");
117
118
            // cut away the html-part
119 1
            $content = $response->getContent();
120 1
            $textEnd = stripos($content, "<!doctype");
121 1
            $response->setContent(substr($content, 0, $textEnd));
122 1
        }
123
124 1
        return $response;
125
    }
126
127
    /**
128
     * Show a web-version of an email that has been sent to recipients and has been stored in the database.
129
     * @param string $token
130
     */
131 6
    public function webViewAction ($token)
132
    {
133
        // find email recipients, template & params
134 6
        $sentEmail = $this->getSentEmailForToken($token);
135
136
        // check if the sent email is available
137 6
        if ($sentEmail !== null) {
138
139
            // check if the current user is allowed to see the email
140 5
            if ($this->userIsAllowedToSeeThisMail($sentEmail)) {
141
142 3
                $template = $sentEmail->getTemplate();
143 3
                $emailVars = $sentEmail->getVariables();
144
145
                // re-attach all entities to the EntityManager.
146 3
                $this->reAttachAllEntities($emailVars);
147
148
                // remove the web-view-token from the param-array
149 3
                $templateProvider = $this->getTemplateProviderService();
150 3
                unset($emailVars[$templateProvider->getWebViewTokenId()]);
151
152
                // render & return email
153 3
                $response = $this->renderResponse("$template.html.twig", $emailVars);
154
155 3
                $campaignParams = $templateProvider->getCampaignParamsFor($template, $emailVars);
156
157 3
                if (sizeof($campaignParams) > 0) {
158 1
                    $response->setContent($this->get("azine.email.bundle.twig.filters")->addCampaignParamsToAllUrls($response->getContent(), $campaignParams));
159 1
                }
160
161 3
                return $response;
162
163
            // if the user is not allowed to see this mail
164
            } else {
165 2
                $msg = $this->get('translator')->trans('web.pre.view.test.mail.access.denied');
166 2
                throw new AccessDeniedException($msg);
167
            }
168
        }
169
170
        // the parameters-array is null => the email is not available in webView
171 1
        $days = $this->getParameter("azine_email_web_view_retention");
172 1
        $response = $this->renderResponse("AzineEmailBundle:Webview:mail.not.available.html.twig", array('days' => $days));
173 1
        $response->setStatusCode(404);
174
175 1
        return $response;
176
    }
177
178
    /**
179
     * Check if the user is allowed to see the email.
180
     * => the mail is public or the user is among the recipients or the user is an admin.
181
     *
182
     * @param  SentEmail $mail
183
     * @return boolean
184
     */
185 5
    private function userIsAllowedToSeeThisMail(SentEmail $mail)
186
    {
187 5
        $recipients = $mail->getRecipients();
188
189
        // it is a public email
190 5
        if ($recipients === null) {
191 1
            return true;
192
        }
193
194
        // get the current user
195 4
        $currentUser = null;
196 4
        if (!$this->has('security.token_storage')) {
197
            // @codeCoverageIgnoreStart
198
            throw new \LogicException('The SecurityBundle is not registered in your application.');
199
            // @codeCoverageIgnoreEnd
200
201
        } else {
202 4
            $token = $this->get('security.token_storage')->getToken();
203
204
            // check if the token is not null and the user in the token an object
205 4
            if ($token instanceof TokenInterface && is_object($token->getUser())) {
206 3
                $currentUser = $token->getUser();
207 3
            }
208
        }
209
210
        // it is not a public email, and a user is logged in
211 4
        if ($currentUser !== null) {
212
213
            // the user is among the recipients
214 3
            if(array_search($currentUser->getEmail(), $recipients) !== false)
215
216 3
                return true;
217
218
            // the user is admin
219 2
            if($currentUser->hasRole("ROLE_ADMIN"))
220
221 2
                return true;
222 1
        }
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
     * @return null
240
     */
241 3
    private function reAttachAllEntities(array &$vars)
242
    {
243 3
        $em = $this->get('doctrine')->getManager();
244 3
        foreach ($vars as $key => $next) {
245
            if (is_object($next) && method_exists($next, 'getId')) {
246
                $className = get_class($next);
247
                $managedEntity = $em->find($className, $next->getId());
248
                if ($managedEntity) {
249
                    $vars[$key] = $managedEntity;
250
                }
251
                continue;
252
            } elseif (is_array($next)) {
253
                $this->reAttachAllEntities($next);
254
                continue;
255
            }
256 3
        }
257
258 3
    }
259
260
    /**
261
     * Serve the image from the templates-folder
262
     * @param  string                                               $filename
263
     * @param  string                                               $folderKey
264
     * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
265
     */
266 2
    public function serveImageAction($folderKey, $filename)
267
    {
268 2
        $folder = $this->getTemplateProviderService()->getFolderFrom($folderKey);
269 2
        if ($folder !== false) {
270 1
            $fullPath = $folder.$filename;
271 1
            $response = BinaryFileResponse::create($fullPath);
272 1
            $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE);
273 1
            $response->headers->set("Content-Type", "image");
274
275 1
            return $response;
276
        }
277
278 1
        throw new FileNotFoundException($filename);
279
    }
280
281
    /**
282
     * @return TemplateProviderInterface
283
     */
284 6
    protected function getTemplateProviderService()
285
    {
286 6
        return $this->get('azine_email_template_provider');
287
    }
288
289
    /**
290
     * @param  string   $view
291
     * @param  array    $parameters
292
     * @param  Response $response
293
     * @return Response
294
     */
295 5
    protected function renderResponse($view, array $parameters = array(), Response $response = null)
296
    {
297 5
        return $this->get('templating')->renderResponse($view, $parameters, $response);
298
    }
299
300
    /**
301
     * Get the sent email from the database
302
     * @param  string    $token the token identifying the sent email
303
     * @return SentEmail
304
     */
305 6
    protected function getSentEmailForToken($token)
306
    {
307 6
        $sentEmail = $this->get('doctrine')->getRepository('AzineEmailBundle:SentEmail')->findOneByToken($token);
308
309 6
        return $sentEmail;
310
    }
311
312
    /**
313
     * Send a test-mail for the template to the given email-address
314
     * @param  string                                             $template templateId without ending => AzineEmailBundle::baseEmailLayout (without .txt.twig)
315
     * @param  string                                             $email
316
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
317
     */
318
    public function sendTestEmailAction(Request $request, $template, $email)
319
    {
320
        $locale = $request->getLocale();
321
322
        // get the email-vars for email-sending => absolute fs-paths to images
323
        $emailVars = $this->get('azine_email_web_view_service')->getDummyVarsFor($template, $locale);
324
325
        // send the mail
326
        $message = \Swift_Message::newInstance();
327
        $mailer = $this->get("azine_email_template_twig_swift_mailer");
328
        $sent = $mailer->sendSingleEmail($email, "Test Recipient", $emailVars['subject'], $emailVars, $template.".txt.twig", $locale, $emailVars['sendMailAccountAddress'], $emailVars['sendMailAccountName']." (Test)", $message);
329
330
        $spamReport = $this->getSpamIndexReportForSwiftMessage($message);
331
        if (is_array($spamReport)) {
332
            if ($spamReport['curlHttpCode'] == 200 && $spamReport['success']) {
333
                $spamScore = $spamReport['score'];
334
                $spamInfo = "SpamScore: $spamScore! \n".$spamReport['report'];
335
            } else {
336
                //@codeCoverageIgnoreStart
337
                // this only happens if the spam-check-server has a problem / is not responding
338
                $spamScore = 10;
339
                $spamInfo = "Getting the spam-info failed.
340
                             HttpCode: ".$spamReport['curlHttpCode']."
341
                             SpamReportMsg: ".$spamReport['message'];
342
                if(array_key_exists('curlError', $spamReport)) {
343
                    $spamInfo .= "
344
                             cURL-Error: " . $spamReport['curlError'];
345
                }
346
                //@codeCoverageIgnoreEnd
347
            }
348
349
            if ($spamScore <= 2) {
350
                $request->getSession()->getFlashBag()->add('info', $spamInfo);
351
            } elseif ($spamScore > 2 && $spamScore < 5) {
352
                $request->getSession()->getFlashBag()->add('warn', $spamInfo);
353
            } else {
354
                $request->getSession()->getFlashBag()->add('error', $spamInfo);
355
            }
356
357
        }
358
359
        // inform about sent/failed emails
360
        if ($sent) {
361
            $msg = $this->get('translator')->trans('web.pre.view.test.mail.sent.for.%template%.to.%email%', array('%template%' => $template, '%email%' => $email));
362
            $request->getSession()->getFlashBag()->add('info', $msg);
363
364
            //@codeCoverageIgnoreStart
365
        } else {
366
            // this only happens if the mail-server has a problem
367
            $msg = $this->get('translator')->trans('web.pre.view.test.mail.failed.for.%template%.to.%email%', array('%template%' => $template, '%email%' => $email));
368
            $request->getSession()->getFlashBag()->add('warn', $msg);
369
            //@codeCoverageIgnoreStart
370
        }
371
372
        // show the index page again.
373
        return new RedirectResponse($this->get('router')->generate('azine_email_template_index', array('customEmail' => $email)));
374
    }
375
376
    /**
377
     * Make an RESTful call to http://spamcheck.postmarkapp.com/filter to test the emails-spam-index.
378
     * See http://spamcheck.postmarkapp.com/doc
379
     * @return array TestResult array('success', 'message', 'curlHttpCode', 'curlError', ['score', 'report'])
380
     */
381
    public function getSpamIndexReportForSwiftMessage(\Swift_Message $message, $report = 'long')
382
    {
383
        return $this->getSpamIndexReport($message->toString(), $report);
384
    }
385
386
    /**
387
     * @param $msgString
388
     * @param string $report
389
     * @return mixed
390
     */
391 1
    private function getSpamIndexReport($msgString, $report = 'long')
392
    {
393
        // check if cURL is loaded/available
394
        if (!function_exists('curl_init')) {
395
            // @codeCoverageIgnoreStart
396
            return array(   "success" => false,
397
                            "curlHttpCode" => "-",
398
                            "curlError" => "-",
399
                            "message" => "No Spam-Check done. cURL module is not available.",
400
                    );
401
            // @codeCoverageIgnoreEnd
402
        }
403
404 1
        $ch = curl_init("http://spamcheck.postmarkapp.com/filter");
405 1
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
406 1
        curl_setopt($ch, CURLOPT_POST, true);
407 1
        $data = array("email" => $msgString, "options" => $report);
408 1
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
409 1
        curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/json", "Accept: application/json"));
410 1
        curl_setopt($ch, CURLOPT_TIMEOUT, 5); // max wait for 5sec for reply
411
412 1
        $result = json_decode(curl_exec($ch), true);
413 1
        $error = curl_error($ch);
414 1
        $result['curlHttpCode'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
415 1
        curl_close($ch);
416
417 1
        if (strlen($error) > 0) {
418
            $result['curlError'] = $error;
419
        }
420
421 1
        if (!array_key_exists("message", $result)) {
422 1
            $result['message'] = "-";
423 1
        }
424
425 1
        if (!array_key_exists('success', $result)) {
426
            $result['message'] = "Something went wrong! Here's the content of the curl-reply:\n\n".nl2br(print_r($result, true));
427
428 1
        } elseif (!$result['success'] && strpos($msgString, "Content-Transfer-Encoding: base64") !== false) {
429
            $result['message'] = $result['message']."\n\nRemoving the base64-Encoded Mime-Parts might help.";
430
431
        }
432
433 1
        return $result;
434
435
    }
436
437
    /**
438
     * Ajax action to check the spam-score for the pasted email-source
439
     */
440
    public function checkSpamScoreOfSentEmailAction(Request $request)
441
    {
442
        $msgString = $request->get('emailSource');
443
        $spamReport = $this->getSpamIndexReport($msgString);
444
        $spamInfo = "";
445
        if (is_array($spamReport)) {
446
            if (array_key_exists('curlHttpCode', $spamReport) && $spamReport['curlHttpCode'] == 200 && $spamReport['success'] && array_key_exists('score', $spamReport)) {
447
                $spamScore = $spamReport['score'];
448
                $spamInfo = "SpamScore: $spamScore! \n".$spamReport['report'];
449
                //@codeCoverageIgnoreStart
450
                // this only happens if the spam-check-server has a problem / is not responding
451
            } else {
452
                if( array_key_exists('curlHttpCode', $spamReport) && array_key_exists('curlError', $spamReport) && array_key_exists('message', $spamReport)){
453
                    $spamInfo = "Getting the spam-info failed.
454
                    HttpCode: " . $spamReport['curlHttpCode'] . "
455
                    cURL-Error: " . $spamReport['curlError'] . "
456
                    SpamReportMsg: " . $spamReport['message'];
457
458
                } elseif ($spamReport !== null && is_array($spamReport)) {
459
                    $spamInfo = "Getting the spam-info failed. This was returned:
460
---Start----------------------------------------------
461
" . implode(";\n", $spamReport) ."
462
---End------------------------------------------------";
463
                }
464
                //@codeCoverageIgnoreEnd
465
            }
466
        }
467
468
        return new JsonResponse(array('result' => $spamInfo));
469
    }
470
}
471