1 | <?php |
||
2 | |||
3 | /* |
||
4 | * @copyright 2014 Mautic Contributors. All rights reserved |
||
5 | * @author Mautic |
||
6 | * |
||
7 | * @link http://mautic.org |
||
8 | * |
||
9 | * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html |
||
10 | */ |
||
11 | |||
12 | namespace Mautic\EmailBundle\Controller; |
||
13 | |||
14 | use Mautic\CoreBundle\Controller\FormController as CommonFormController; |
||
15 | use Mautic\CoreBundle\Helper\EmojiHelper; |
||
16 | use Mautic\CoreBundle\Helper\TrackingPixelHelper; |
||
17 | use Mautic\EmailBundle\EmailEvents; |
||
18 | use Mautic\EmailBundle\Entity\Email; |
||
19 | use Mautic\EmailBundle\Event\EmailSendEvent; |
||
20 | use Mautic\EmailBundle\Event\TransportWebhookEvent; |
||
21 | use Mautic\EmailBundle\Helper\MailHelper; |
||
22 | use Mautic\EmailBundle\Model\EmailModel; |
||
23 | use Mautic\EmailBundle\Swiftmailer\Transport\CallbackTransportInterface; |
||
24 | use Mautic\LeadBundle\Controller\FrequencyRuleTrait; |
||
25 | use Mautic\LeadBundle\Entity\DoNotContact; |
||
26 | use Mautic\PageBundle\Entity\Page; |
||
27 | use Mautic\PageBundle\Event\PageDisplayEvent; |
||
28 | use Mautic\PageBundle\EventListener\BuilderSubscriber; |
||
29 | use Mautic\PageBundle\PageEvents; |
||
30 | use Mautic\QueueBundle\Queue\QueueName; |
||
31 | use Symfony\Component\HttpFoundation\Response; |
||
32 | |||
33 | class PublicController extends CommonFormController |
||
34 | { |
||
35 | use FrequencyRuleTrait; |
||
36 | |||
37 | /** |
||
38 | * @param $idHash |
||
39 | * |
||
40 | * @return Response |
||
41 | */ |
||
42 | public function indexAction($idHash) |
||
43 | { |
||
44 | /** @var \Mautic\EmailBundle\Model\EmailModel $model */ |
||
45 | $model = $this->getModel('email'); |
||
46 | $stat = $model->getEmailStatus($idHash); |
||
47 | |||
48 | if (!empty($stat)) { |
||
49 | if ($this->get('mautic.security')->isAnonymous()) { |
||
50 | $model->hitEmail($stat, $this->request, true); |
||
51 | } |
||
52 | |||
53 | $tokens = $stat->getTokens(); |
||
54 | if (is_array($tokens)) { |
||
55 | // Override tracking_pixel so as to not cause a double hit |
||
56 | $tokens['{tracking_pixel}'] = MailHelper::getBlankPixel(); |
||
57 | } |
||
58 | |||
59 | if ($copy = $stat->getStoredCopy()) { |
||
60 | $subject = $copy->getSubject(); |
||
61 | $content = $copy->getBody(); |
||
62 | |||
63 | // Convert emoji |
||
64 | $content = EmojiHelper::toEmoji($content, 'short'); |
||
65 | $subject = EmojiHelper::toEmoji($subject, 'short'); |
||
66 | |||
67 | // Replace tokens |
||
68 | if (!empty($tokens)) { |
||
69 | $content = str_ireplace(array_keys($tokens), $tokens, $content); |
||
70 | $subject = str_ireplace(array_keys($tokens), $tokens, $subject); |
||
71 | } |
||
72 | } else { |
||
73 | $subject = ''; |
||
74 | $content = ''; |
||
75 | } |
||
76 | |||
77 | $content = $this->get('mautic.helper.template.analytics')->addCode($content); |
||
78 | |||
79 | // Add subject as title |
||
80 | if (!empty($subject)) { |
||
81 | if (false !== strpos($content, '<title></title>')) { |
||
82 | $content = str_replace('<title></title>', "<title>$subject</title>", $content); |
||
83 | } elseif (false === strpos($content, '<title>')) { |
||
84 | $content = str_replace('<head>', "<head>\n<title>$subject</title>", $content); |
||
85 | } |
||
86 | } |
||
87 | |||
88 | return new Response($content); |
||
89 | } |
||
90 | |||
91 | return $this->notFound(); |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * @param $idHash |
||
96 | * |
||
97 | * @return Response |
||
98 | */ |
||
99 | public function trackingImageAction($idHash) |
||
100 | { |
||
101 | $queueService = $this->get('mautic.queue.service'); |
||
102 | if ($queueService->isQueueEnabled()) { |
||
103 | $msg = [ |
||
104 | 'request' => $this->request, |
||
105 | 'idHash' => $idHash, |
||
106 | ]; |
||
107 | $queueService->publishToQueue(QueueName::EMAIL_HIT, $msg); |
||
108 | } else { |
||
109 | /** @var EmailModel $model */ |
||
110 | $model = $this->getModel('email'); |
||
111 | $model->hitEmail($idHash, $this->request); |
||
112 | } |
||
113 | |||
114 | return TrackingPixelHelper::getResponse($this->request); |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * @param $idHash |
||
119 | * |
||
120 | * @return Response |
||
121 | * |
||
122 | * @throws \Exception |
||
123 | * @throws \Mautic\CoreBundle\Exception\FileNotFoundException |
||
124 | */ |
||
125 | public function unsubscribeAction($idHash) |
||
126 | { |
||
127 | // Find the email |
||
128 | /** @var \Mautic\EmailBundle\Model\EmailModel $model */ |
||
129 | $model = $this->getModel('email'); |
||
130 | $translator = $this->get('translator'); |
||
131 | $stat = $model->getEmailStatus($idHash); |
||
132 | $message = ''; |
||
133 | $email = null; |
||
134 | $lead = null; |
||
135 | $template = null; |
||
136 | $session = $this->get('session'); |
||
137 | |||
138 | /** @var \Mautic\LeadBundle\Model\LeadModel $leadModel */ |
||
139 | $leadModel = $this->getModel('lead'); |
||
140 | |||
141 | if (!empty($stat)) { |
||
142 | if ($email = $stat->getEmail()) { |
||
143 | $template = $email->getTemplate(); |
||
144 | if ('mautic_code_mode' === $template) { |
||
145 | // Use system default |
||
146 | $template = null; |
||
147 | } |
||
148 | |||
149 | /** @var \Mautic\FormBundle\Entity\Form $unsubscribeForm */ |
||
150 | $unsubscribeForm = $email->getUnsubscribeForm(); |
||
151 | if (null != $unsubscribeForm && $unsubscribeForm->isPublished()) { |
||
152 | $formTemplate = $unsubscribeForm->getTemplate(); |
||
153 | $formModel = $this->getModel('form'); |
||
154 | $formContent = '<div class="mautic-unsubscribeform">'.$formModel->getContent($unsubscribeForm).'</div>'; |
||
155 | } |
||
156 | } |
||
157 | } |
||
158 | |||
159 | if (empty($template) && empty($formTemplate)) { |
||
160 | $template = $this->coreParametersHelper->get('theme'); |
||
161 | } elseif (!empty($formTemplate)) { |
||
162 | $template = $formTemplate; |
||
163 | } |
||
164 | |||
165 | $theme = $this->factory->getTheme($template); |
||
166 | if ($theme->getTheme() != $template) { |
||
167 | $template = $theme->getTheme(); |
||
168 | } |
||
169 | $contentTemplate = $this->factory->getHelper('theme')->checkForTwigTemplate(':'.$template.':message.html.php'); |
||
170 | if (!empty($stat)) { |
||
171 | $successSessionName = 'mautic.email.prefscenter.success'; |
||
172 | |||
173 | if ($lead = $stat->getLead()) { |
||
174 | // Set the lead as current lead |
||
175 | $this->get('mautic.tracker.contact')->setTrackedContact($lead); |
||
176 | |||
177 | // Set lead lang |
||
178 | if ($lead->getPreferredLocale()) { |
||
179 | $translator->setLocale($lead->getPreferredLocale()); |
||
180 | } |
||
181 | |||
182 | // Add contact ID to the session name in case more contacts |
||
183 | // share the same session/device and the contact is known. |
||
184 | $successSessionName .= ".{$lead->getId()}"; |
||
185 | } |
||
186 | |||
187 | if (!$this->get('mautic.helper.core_parameters')->get('show_contact_preferences')) { |
||
188 | $message = $this->getUnsubscribeMessage($idHash, $model, $stat, $translator); |
||
189 | } elseif ($lead) { |
||
190 | $action = $this->generateUrl('mautic_email_unsubscribe', ['idHash' => $idHash]); |
||
191 | |||
192 | $viewParameters = [ |
||
193 | 'lead' => $lead, |
||
194 | 'idHash' => $idHash, |
||
195 | 'showContactFrequency' => $this->get('mautic.helper.core_parameters')->get('show_contact_frequency'), |
||
196 | 'showContactPauseDates' => $this->get('mautic.helper.core_parameters')->get('show_contact_pause_dates'), |
||
197 | 'showContactPreferredChannels' => $this->get('mautic.helper.core_parameters')->get('show_contact_preferred_channels'), |
||
198 | 'showContactCategories' => $this->get('mautic.helper.core_parameters')->get('show_contact_categories'), |
||
199 | 'showContactSegments' => $this->get('mautic.helper.core_parameters')->get('show_contact_segments'), |
||
200 | ]; |
||
201 | |||
202 | $form = $this->getFrequencyRuleForm($lead, $viewParameters, $data, true, $action, true); |
||
203 | if (true === $form) { |
||
204 | $session->set($successSessionName, 1); |
||
205 | |||
206 | return $this->postActionRedirect( |
||
207 | [ |
||
208 | 'returnUrl' => $this->generateUrl('mautic_email_unsubscribe', ['idHash' => $idHash]), |
||
209 | 'viewParameters' => $viewParameters, |
||
210 | 'contentTemplate' => $contentTemplate, |
||
211 | ] |
||
212 | ); |
||
213 | } |
||
214 | |||
215 | $formView = $form->createView(); |
||
216 | /** @var Page $prefCenter */ |
||
217 | if ($email && ($prefCenter = $email->getPreferenceCenter()) && ($prefCenter->getIsPreferenceCenter())) { |
||
218 | $html = $prefCenter->getCustomHtml(); |
||
219 | // check if tokens are present |
||
220 | $savePrefsPresent = false !== strpos($html, 'data-slot="saveprefsbutton"') || |
||
221 | false !== strpos($html, BuilderSubscriber::saveprefsRegex); |
||
222 | if ($savePrefsPresent) { |
||
223 | // set custom tag to inject end form |
||
224 | // update show pref center slots by looking for their presence in the html |
||
225 | /** @var \Mautic\CoreBundle\Templating\Helper\FormHelper $formHelper */ |
||
226 | $formHelper =$this->get('templating.helper.form'); |
||
227 | $params = array_merge( |
||
228 | $viewParameters, |
||
229 | [ |
||
230 | 'form' => $formView, |
||
231 | 'startform' => $formHelper->start($formView), |
||
232 | 'custom_tag' => '<a name="end-'.$formView->vars['id'].'"></a>', |
||
233 | 'showContactFrequency' => false !== strpos($html, 'data-slot="channelfrequency"') || false !== strpos($html, BuilderSubscriber::channelfrequency), |
||
234 | 'showContactSegments' => false !== strpos($html, 'data-slot="segmentlist"') || false !== strpos($html, BuilderSubscriber::segmentListRegex), |
||
235 | 'showContactCategories' => false !== strpos($html, 'data-slot="categorylist"') || false !== strpos($html, BuilderSubscriber::categoryListRegex), |
||
236 | 'showContactPreferredChannels' => false !== strpos($html, 'data-slot="preferredchannel"') || false !== strpos($html, BuilderSubscriber::preferredchannel), |
||
237 | ] |
||
238 | ); |
||
239 | // Replace tokens in preference center page |
||
240 | $event = new PageDisplayEvent($html, $prefCenter, $params); |
||
241 | $this->get('event_dispatcher') |
||
242 | ->dispatch(PageEvents::PAGE_ON_DISPLAY, $event); |
||
243 | $html = $event->getContent(); |
||
244 | if (!$session->has($successSessionName)) { |
||
245 | $successMessageDataSlots = [ |
||
246 | 'data-slot="successmessage"', |
||
247 | 'class="pref-successmessage"', |
||
248 | ]; |
||
249 | $successMessageDataSlotsHidden = []; |
||
250 | foreach ($successMessageDataSlots as $successMessageDataSlot) { |
||
251 | $successMessageDataSlotsHidden[] = $successMessageDataSlot.' style=display:none'; |
||
252 | } |
||
253 | $html = str_replace( |
||
254 | $successMessageDataSlots, |
||
255 | $successMessageDataSlotsHidden, |
||
256 | $html |
||
257 | ); |
||
258 | } else { |
||
259 | $session->remove($successSessionName); |
||
260 | } |
||
261 | $html = preg_replace( |
||
262 | '/'.BuilderSubscriber::identifierToken.'/', |
||
263 | $lead->getPrimaryIdentifier(), |
||
264 | $html |
||
265 | ); |
||
266 | } else { |
||
267 | unset($html); |
||
268 | } |
||
269 | } |
||
270 | |||
271 | if (empty($html)) { |
||
272 | $html = $this->get('mautic.helper.templating')->getTemplating()->render( |
||
273 | 'MauticEmailBundle:Lead:preference_options.html.php', |
||
274 | array_merge( |
||
275 | $viewParameters, |
||
276 | [ |
||
277 | 'form' => $formView, |
||
278 | 'currentRoute' => $this->generateUrl( |
||
279 | 'mautic_contact_action', |
||
280 | [ |
||
281 | 'objectAction' => 'contactFrequency', |
||
282 | 'objectId' => $lead->getId(), |
||
283 | ] |
||
284 | ), |
||
285 | ] |
||
286 | ) |
||
287 | ); |
||
288 | } |
||
289 | $message = $html; |
||
290 | } |
||
291 | } else { |
||
292 | $message = $translator->trans('mautic.email.stat_record.not_found'); |
||
293 | } |
||
294 | |||
295 | $config = $theme->getConfig(); |
||
296 | |||
297 | $viewParams = [ |
||
298 | 'email' => $email, |
||
299 | 'lead' => $lead, |
||
300 | 'template' => $template, |
||
301 | 'message' => $message, |
||
302 | ]; |
||
303 | |||
304 | if (!empty($formContent)) { |
||
305 | $viewParams['content'] = $formContent; |
||
306 | if (in_array('form', $config['features'])) { |
||
307 | $contentTemplate = $this->factory->getHelper('theme')->checkForTwigTemplate(':'.$template.':form.html.php'); |
||
308 | } else { |
||
309 | $contentTemplate = 'MauticFormBundle::form.html.php'; |
||
310 | } |
||
311 | } |
||
312 | |||
313 | return $this->render($contentTemplate, $viewParams); |
||
314 | } |
||
315 | |||
316 | /** |
||
317 | * @param $idHash |
||
318 | * |
||
319 | * @return Response |
||
320 | * |
||
321 | * @throws \Exception |
||
322 | * @throws \Mautic\CoreBundle\Exception\FileNotFoundException |
||
323 | */ |
||
324 | public function resubscribeAction($idHash) |
||
325 | { |
||
326 | //find the email |
||
327 | $model = $this->getModel('email'); |
||
328 | $stat = $model->getEmailStatus($idHash); |
||
329 | |||
330 | if (!empty($stat)) { |
||
331 | $email = $stat->getEmail(); |
||
332 | $lead = $stat->getLead(); |
||
333 | |||
334 | if ($lead) { |
||
335 | // Set the lead as current lead |
||
336 | /** @var \Mautic\LeadBundle\Model\LeadModel $leadModel */ |
||
337 | $leadModel = $this->getModel('lead'); |
||
338 | $this->get('mautic.tracker.contact')->setTrackedContact($lead); |
||
339 | |||
340 | // Set lead lang |
||
341 | if ($lead->getPreferredLocale()) { |
||
342 | $this->translator->setLocale($lead->getPreferredLocale()); |
||
343 | } |
||
344 | } |
||
345 | |||
346 | $model->removeDoNotContact($stat->getEmailAddress()); |
||
347 | |||
348 | $message = $this->coreParametersHelper->get('resubscribe_message'); |
||
349 | if (!$message) { |
||
350 | $message = $this->translator->trans( |
||
351 | 'mautic.email.resubscribed.success', |
||
352 | [ |
||
353 | '%unsubscribeUrl%' => '|URL|', |
||
354 | '%email%' => '|EMAIL|', |
||
355 | ] |
||
356 | ); |
||
357 | } |
||
358 | $message = str_replace( |
||
359 | [ |
||
360 | '|URL|', |
||
361 | '|EMAIL|', |
||
362 | ], |
||
363 | [ |
||
364 | $this->generateUrl('mautic_email_unsubscribe', ['idHash' => $idHash]), |
||
365 | $stat->getEmailAddress(), |
||
366 | ], |
||
367 | $message |
||
368 | ); |
||
369 | } else { |
||
370 | $email = $lead = false; |
||
371 | $message = $this->translator->trans('mautic.email.stat_record.not_found'); |
||
372 | } |
||
373 | |||
374 | $template = (null !== $email && 'mautic_code_mode' !== $email->getTemplate()) ? $email->getTemplate() : $this->coreParametersHelper->get('theme'); |
||
375 | |||
376 | $theme = $this->factory->getTheme($template); |
||
377 | |||
378 | if ($theme->getTheme() != $template) { |
||
379 | $template = $theme->getTheme(); |
||
380 | } |
||
381 | |||
382 | // Ensure template still exists |
||
383 | $theme = $this->factory->getTheme($template); |
||
384 | if (empty($theme) || $theme->getTheme() !== $template) { |
||
385 | $template = $this->coreParametersHelper->get('theme'); |
||
386 | } |
||
387 | |||
388 | $analytics = $this->factory->getHelper('template.analytics')->getCode(); |
||
389 | |||
390 | if (!empty($analytics)) { |
||
391 | $this->factory->getHelper('template.assets')->addCustomDeclaration($analytics); |
||
392 | } |
||
393 | |||
394 | $logicalName = $this->factory->getHelper('theme')->checkForTwigTemplate(':'.$template.':message.html.php'); |
||
395 | |||
396 | return $this->render( |
||
397 | $logicalName, |
||
398 | [ |
||
399 | 'message' => $message, |
||
400 | 'type' => 'notice', |
||
401 | 'email' => $email, |
||
402 | 'lead' => $lead, |
||
403 | 'template' => $template, |
||
404 | ] |
||
405 | ); |
||
406 | } |
||
407 | |||
408 | /** |
||
409 | * Handles mailer transport webhook post. |
||
410 | * |
||
411 | * @param $transport |
||
412 | * |
||
413 | * @return Response |
||
414 | */ |
||
415 | public function mailerCallbackAction($transport) |
||
416 | { |
||
417 | ignore_user_abort(true); |
||
418 | |||
419 | $realTransport = $this->container->get('swiftmailer.transport.real'); |
||
420 | |||
421 | if (!$realTransport instanceof CallbackTransportInterface || $realTransport->getCallbackPath() !== $transport) { |
||
422 | return $this->notFound(); |
||
423 | } |
||
424 | |||
425 | $event = new TransportWebhookEvent($realTransport, $this->request); |
||
426 | $this->dispatcher->dispatch(EmailEvents::ON_TRANSPORT_WEBHOOK, $event); |
||
427 | |||
428 | return new Response('success'); |
||
429 | } |
||
430 | |||
431 | /** |
||
432 | * Preview email. |
||
433 | * |
||
434 | * @param $objectId |
||
435 | * |
||
436 | * @return \Symfony\Component\HttpFoundation\Response |
||
437 | */ |
||
438 | public function previewAction($objectId) |
||
439 | { |
||
440 | /** @var \Mautic\EmailBundle\Model\EmailModel $model */ |
||
441 | $model = $this->getModel('email'); |
||
442 | $emailEntity = $model->getEntity($objectId); |
||
443 | |||
444 | if (null === $emailEntity) { |
||
445 | return $this->notFound(); |
||
446 | } |
||
447 | |||
448 | if ( |
||
449 | ($this->get('mautic.security')->isAnonymous() && (!$emailEntity->isPublished() || !$emailEntity->isPublicPreview())) |
||
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
450 | || (!$this->get('mautic.security')->isAnonymous() |
||
451 | && !$this->get('mautic.security')->hasEntityAccess( |
||
452 | 'email:emails:viewown', |
||
453 | 'email:emails:viewother', |
||
454 | $emailEntity->getCreatedBy() |
||
455 | )) |
||
456 | ) { |
||
457 | return $this->accessDenied(); |
||
458 | } |
||
459 | |||
460 | //bogus ID |
||
461 | $idHash = 'xxxxxxxxxxxxxx'; |
||
462 | |||
463 | $BCcontent = $emailEntity->getContent(); |
||
464 | $content = $emailEntity->getCustomHtml(); |
||
465 | if (empty($content) && !empty($BCcontent)) { |
||
466 | $template = $emailEntity->getTemplate(); |
||
467 | $slots = $this->factory->getTheme($template)->getSlots('email'); |
||
468 | |||
469 | $assetsHelper = $this->factory->getHelper('template.assets'); |
||
470 | |||
471 | $assetsHelper->addCustomDeclaration('<meta name="robots" content="noindex">'); |
||
472 | |||
473 | $this->processSlots($slots, $emailEntity); |
||
474 | |||
475 | $logicalName = $this->factory->getHelper('theme')->checkForTwigTemplate(':'.$template.':email.html.php'); |
||
476 | |||
477 | $response = $this->render( |
||
478 | $logicalName, |
||
479 | [ |
||
480 | 'inBrowser' => true, |
||
481 | 'slots' => $slots, |
||
482 | 'content' => $emailEntity->getContent(), |
||
483 | 'email' => $emailEntity, |
||
484 | 'lead' => null, |
||
485 | 'template' => $template, |
||
486 | ] |
||
487 | ); |
||
488 | |||
489 | //replace tokens |
||
490 | $content = $response->getContent(); |
||
491 | } |
||
492 | |||
493 | // Convert emojis |
||
494 | $content = EmojiHelper::toEmoji($content, 'short'); |
||
495 | |||
496 | // Override tracking_pixel |
||
497 | $tokens = ['{tracking_pixel}' => '']; |
||
498 | |||
499 | // Prepare a fake lead |
||
500 | /** @var \Mautic\LeadBundle\Model\FieldModel $fieldModel */ |
||
501 | $fieldModel = $this->getModel('lead.field'); |
||
502 | $fields = $fieldModel->getFieldList(false, false); |
||
503 | array_walk( |
||
504 | $fields, |
||
505 | function (&$field) { |
||
506 | $field = "[$field]"; |
||
507 | } |
||
508 | ); |
||
509 | $fields['id'] = 0; |
||
510 | |||
511 | // Generate and replace tokens |
||
512 | $event = new EmailSendEvent( |
||
513 | null, |
||
514 | [ |
||
515 | 'content' => $content, |
||
516 | 'email' => $emailEntity, |
||
517 | 'idHash' => $idHash, |
||
518 | 'tokens' => $tokens, |
||
519 | 'internalSend' => true, |
||
520 | 'lead' => $fields, |
||
521 | ] |
||
522 | ); |
||
523 | $this->dispatcher->dispatch(EmailEvents::EMAIL_ON_DISPLAY, $event); |
||
524 | |||
525 | $content = $event->getContent(true); |
||
526 | |||
527 | if ($this->get('mautic.security')->isAnonymous()) { |
||
528 | $content = $this->get('mautic.helper.template.analytics')->addCode($content); |
||
529 | } |
||
530 | |||
531 | return new Response($content); |
||
532 | } |
||
533 | |||
534 | /** |
||
535 | * @param $slots |
||
536 | * @param Email $entity |
||
537 | */ |
||
538 | public function processSlots($slots, $entity) |
||
539 | { |
||
540 | /** @var \Mautic\CoreBundle\Templating\Helper\SlotsHelper $slotsHelper */ |
||
541 | $slotsHelper = $this->factory->getHelper('template.slots'); |
||
542 | |||
543 | $content = $entity->getContent(); |
||
544 | |||
545 | foreach ($slots as $slot => $slotConfig) { |
||
546 | if (is_numeric($slot)) { |
||
547 | $slot = $slotConfig; |
||
548 | $slotConfig = []; |
||
549 | } |
||
550 | |||
551 | $value = isset($content[$slot]) ? $content[$slot] : ''; |
||
552 | $slotsHelper->set($slot, $value); |
||
553 | } |
||
554 | } |
||
555 | |||
556 | /** |
||
557 | * @param $integration |
||
558 | * |
||
559 | * @throws \Exception |
||
560 | */ |
||
561 | private function doTracking($integration) |
||
562 | { |
||
563 | $logger = $this->get('monolog.logger.mautic'); |
||
564 | |||
565 | // if additional data were sent with the tracking pixel |
||
566 | $query_string = $this->request->server->get('QUERY_STRING'); |
||
567 | if (!$query_string) { |
||
568 | $logger->log('error', $integration.': query string is not available'); |
||
569 | |||
570 | return; |
||
571 | } |
||
572 | |||
573 | if (0 === strpos($query_string, 'r=')) { |
||
574 | $query_string = substr($query_string, strpos($query_string, '?') + 1); |
||
575 | } // remove route variable |
||
576 | |||
577 | parse_str($query_string, $query); |
||
578 | |||
579 | // URL attr 'd' is encoded so let's decode it first. |
||
580 | if (!isset($query['d'], $query['sig'])) { |
||
581 | $logger->log('error', $integration.': query variables are not found'); |
||
582 | |||
583 | return; |
||
584 | } |
||
585 | |||
586 | // get secret from plugin settings |
||
587 | $integrationHelper = $this->get('mautic.helper.integration'); |
||
588 | $myIntegration = $integrationHelper->getIntegrationObject($integration); |
||
589 | |||
590 | if (!$myIntegration) { |
||
591 | $logger->log('error', $integration.': integration not found'); |
||
592 | |||
593 | return; |
||
594 | } |
||
595 | $keys = $myIntegration->getDecryptedApiKeys(); |
||
596 | |||
597 | // generate signature |
||
598 | $salt = $keys['secret']; |
||
599 | if (false === strpos($salt, '$1$')) { |
||
600 | $salt = '$1$'.$salt; |
||
601 | } // add MD5 prefix |
||
602 | $cr = crypt(urlencode($query['d']), $salt); |
||
603 | $mySig = hash('crc32b', $cr); // this hash type is used in c# |
||
604 | |||
605 | // compare signatures |
||
606 | if (hash_equals($mySig, $query['sig'])) { |
||
607 | // decode and parse query variables |
||
608 | $b64 = base64_decode($query['d']); |
||
609 | $gz = gzdecode($b64); |
||
610 | parse_str($gz, $query); |
||
611 | } else { |
||
612 | // signatures don't match: stop |
||
613 | $logger->log('error', $integration.': signatures don\'t match'); |
||
614 | |||
615 | unset($query); |
||
616 | } |
||
617 | |||
618 | if (empty($query) || !isset($query['email'], $query['subject'], $query['body'])) { |
||
619 | $logger->log('error', $integration.': query variables are empty'); |
||
620 | |||
621 | return; |
||
622 | } |
||
623 | |||
624 | if (MAUTIC_ENV === 'dev') { |
||
625 | $logger->log('error', $integration.': '.json_encode($query, JSON_PRETTY_PRINT)); |
||
626 | } |
||
627 | |||
628 | /** @var \Mautic\EmailBundle\Model\EmailModel $model */ |
||
629 | $model = $this->getModel('email'); |
||
630 | |||
631 | // email is a semicolon delimited list of emails |
||
632 | $emails = explode(';', $query['email']); |
||
633 | $repo = $this->getModel('lead')->getRepository(); |
||
634 | |||
635 | foreach ($emails as $email) { |
||
636 | $lead = $repo->getLeadByEmail($email); |
||
637 | if (null === $lead) { |
||
638 | $lead = $this->createLead($email, $repo); |
||
639 | } |
||
640 | |||
641 | if (null === $lead) { |
||
642 | continue; |
||
643 | } // lead was not created |
||
644 | |||
645 | $idHash = hash('crc32', $email.$query['body']); |
||
646 | $idHash = substr($idHash.$idHash, 0, 13); // 13 bytes length |
||
647 | |||
648 | $stat = $model->getEmailStatus($idHash); |
||
649 | |||
650 | // stat doesn't exist, create one |
||
651 | if (null === $stat) { |
||
652 | $lead['email'] = $email; // needed for stat |
||
653 | $stat = $this->addStat($lead, $email, $query, $idHash); |
||
654 | } |
||
655 | |||
656 | $stat->setSource('email.client'); |
||
657 | |||
658 | if ($stat || 'Outlook' !== $integration) { // Outlook requests the tracking gif on send |
||
659 | $model->hitEmail($idHash, $this->request); // add email event |
||
660 | } |
||
661 | } |
||
662 | } |
||
663 | |||
664 | /** |
||
665 | * @param $integration |
||
666 | * |
||
667 | * @return Response |
||
668 | */ |
||
669 | public function pluginTrackingGifAction($integration) |
||
670 | { |
||
671 | $this->doTracking($integration); |
||
672 | |||
673 | return TrackingPixelHelper::getResponse($this->request); // send gif |
||
674 | } |
||
675 | |||
676 | /** |
||
677 | * @param $lead |
||
678 | * @param $email |
||
679 | * @param $query |
||
680 | * @param $idHash |
||
681 | */ |
||
682 | private function addStat($lead, $email, $query, $idHash) |
||
683 | { |
||
684 | if (null !== $lead) { |
||
685 | /** @var \Mautic\EmailBundle\Helper\MailHelper $mailer */ |
||
686 | $mailer = $this->get('mautic.helper.mailer'); |
||
687 | |||
688 | // To lead |
||
689 | $mailer->addTo($email); |
||
690 | |||
691 | // sanitize variables to prevent malicious content |
||
692 | $from = filter_var($query['from'], FILTER_SANITIZE_EMAIL); |
||
693 | $mailer->setFrom($from, ''); |
||
694 | |||
695 | // Set Content |
||
696 | $body = filter_var($query['body'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH); |
||
697 | $mailer->setBody($body); |
||
698 | $mailer->parsePlainText($body); |
||
699 | |||
700 | // Set lead |
||
701 | $mailer->setLead($lead); |
||
702 | $mailer->setIdHash($idHash); |
||
703 | |||
704 | $subject = filter_var($query['subject'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH); |
||
705 | $mailer->setSubject($subject); |
||
706 | |||
707 | return $mailer->createEmailStat(); |
||
708 | } |
||
709 | |||
710 | return null; |
||
711 | } |
||
712 | |||
713 | /** |
||
714 | * @param $email |
||
715 | * @param $repo |
||
716 | * |
||
717 | * @return mixed |
||
718 | */ |
||
719 | private function createLead($email, $repo) |
||
720 | { |
||
721 | $model = $this->getModel('lead.lead'); |
||
722 | $lead = $model->getEntity(); |
||
723 | // set custom field values |
||
724 | $data = ['email' => $email]; |
||
725 | $model->setFieldValues($lead, $data, true); |
||
726 | // create lead |
||
727 | $model->saveEntity($lead); |
||
728 | |||
729 | // return entity |
||
730 | return $repo->getLeadByEmail($email); |
||
731 | } |
||
732 | |||
733 | /** |
||
734 | * @param $idHash |
||
735 | * @param $model |
||
736 | * @param $stat |
||
737 | * @param $translator |
||
738 | * |
||
739 | * @return mixed |
||
740 | */ |
||
741 | public function getUnsubscribeMessage($idHash, $model, $stat, $translator) |
||
742 | { |
||
743 | $model->setDoNotContact($stat, $translator->trans('mautic.email.dnc.unsubscribed'), DoNotContact::UNSUBSCRIBED); |
||
744 | |||
745 | $message = $this->coreParametersHelper->get('unsubscribe_message'); |
||
746 | if (!$message) { |
||
747 | $message = $translator->trans( |
||
748 | 'mautic.email.unsubscribed.success', |
||
749 | [ |
||
750 | '%resubscribeUrl%' => '|URL|', |
||
751 | '%email%' => '|EMAIL|', |
||
752 | ] |
||
753 | ); |
||
754 | } |
||
755 | |||
756 | return str_replace( |
||
757 | [ |
||
758 | '|URL|', |
||
759 | '|EMAIL|', |
||
760 | ], |
||
761 | [ |
||
762 | $this->generateUrl('mautic_email_resubscribe', ['idHash' => $idHash]), |
||
763 | $stat->getEmailAddress(), |
||
764 | ], |
||
765 | $message |
||
766 | ); |
||
767 | } |
||
768 | } |
||
769 |