Issues (3627)

EmailBundle/EventListener/BuilderSubscriber.php (1 issue)

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\EventListener;
13
14
use Doctrine\ORM\EntityManager;
15
use Mautic\CoreBundle\Form\Type\SlotButtonType;
16
use Mautic\CoreBundle\Form\Type\SlotCodeModeType;
17
use Mautic\CoreBundle\Form\Type\SlotDynamicContentType;
18
use Mautic\CoreBundle\Form\Type\SlotImageCaptionType;
19
use Mautic\CoreBundle\Form\Type\SlotImageCardType;
20
use Mautic\CoreBundle\Form\Type\SlotSeparatorType;
21
use Mautic\CoreBundle\Form\Type\SlotSocialFollowType;
22
use Mautic\CoreBundle\Form\Type\SlotTextType;
23
use Mautic\CoreBundle\Helper\CoreParametersHelper;
24
use Mautic\CoreBundle\Helper\EmojiHelper;
25
use Mautic\EmailBundle\EmailEvents;
26
use Mautic\EmailBundle\Entity\Email;
27
use Mautic\EmailBundle\Event\EmailBuilderEvent;
28
use Mautic\EmailBundle\Event\EmailSendEvent;
29
use Mautic\EmailBundle\Model\EmailModel;
30
use Mautic\PageBundle\Entity\Redirect;
31
use Mautic\PageBundle\Entity\Trackable;
32
use Mautic\PageBundle\Model\RedirectModel;
33
use Mautic\PageBundle\Model\TrackableModel;
34
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
35
use Symfony\Component\Translation\TranslatorInterface;
36
37
class BuilderSubscriber implements EventSubscriberInterface
38
{
39
    /**
40
     * @var CoreParametersHelper
41
     */
42
    private $coreParametersHelper;
43
44
    /**
45
     * @var EmailModel
46
     */
47
    private $emailModel;
48
49
    /**
50
     * @var TrackableModel
51
     */
52
    private $pageTrackableModel;
53
54
    /**
55
     * @var RedirectModel
56
     */
57
    private $pageRedirectModel;
58
59
    /**
60
     * @var TranslatorInterface
61
     */
62
    private $translator;
63
64
    /**
65
     * @var EntityManager
66
     */
67
    private $entityManager;
68
69
    public function __construct(
70
        CoreParametersHelper $coreParametersHelper,
71
        EmailModel $emailModel,
72
        TrackableModel $trackableModel,
73
        RedirectModel $redirectModel,
74
        TranslatorInterface $translator,
75
        EntityManager $entityManager
76
    ) {
77
        $this->coreParametersHelper = $coreParametersHelper;
78
        $this->emailModel           = $emailModel;
79
        $this->pageTrackableModel   = $trackableModel;
80
        $this->pageRedirectModel    = $redirectModel;
81
        $this->translator           = $translator;
82
        $this->entityManager        = $entityManager;
83
    }
84
85
    /**
86
     * @return array
87
     */
88
    public static function getSubscribedEvents()
89
    {
90
        return [
91
            EmailEvents::EMAIL_ON_BUILD => ['onEmailBuild', 0],
92
            EmailEvents::EMAIL_ON_SEND  => [
93
                ['onEmailGenerate', 0],
94
                // Ensure this is done last in order to catch all tokenized URLs
95
                ['convertUrlsToTokens', -9999],
96
            ],
97
            EmailEvents::EMAIL_ON_DISPLAY => [
98
                ['onEmailGenerate', 0],
99
                // Ensure this is done last in order to catch all tokenized URLs
100
                ['convertUrlsToTokens', -9999],
101
            ],
102
        ];
103
    }
104
105
    public function onEmailBuild(EmailBuilderEvent $event)
106
    {
107
        if ($event->abTestWinnerCriteriaRequested()) {
108
            //add AB Test Winner Criteria
109
            $openRate = [
110
                'group'    => 'mautic.email.stats',
111
                'label'    => 'mautic.email.abtest.criteria.open',
112
                'event'    => EmailEvents::ON_DETERMINE_OPEN_RATE_WINNER,
113
            ];
114
            $event->addAbTestWinnerCriteria('email.openrate', $openRate);
115
116
            $clickThrough = [
117
                'group'    => 'mautic.email.stats',
118
                'label'    => 'mautic.email.abtest.criteria.clickthrough',
119
                'event'    => EmailEvents::ON_DETERMINE_CLICKTHROUGH_RATE_WINNER,
120
            ];
121
            $event->addAbTestWinnerCriteria('email.clickthrough', $clickThrough);
122
        }
123
124
        $tokens = [
125
            '{unsubscribe_text}' => $this->translator->trans('mautic.email.token.unsubscribe_text'),
126
            '{webview_text}'     => $this->translator->trans('mautic.email.token.webview_text'),
127
            '{signature}'        => $this->translator->trans('mautic.email.token.signature'),
128
            '{subject}'          => $this->translator->trans('mautic.email.subject'),
129
        ];
130
131
        if ($event->tokensRequested(array_keys($tokens))) {
132
            $event->addTokens(
133
                $event->filterTokens($tokens)
134
            );
135
        }
136
137
        // these should not allow visual tokens
138
        $tokens = [
139
            '{unsubscribe_url}' => $this->translator->trans('mautic.email.token.unsubscribe_url'),
140
            '{webview_url}'     => $this->translator->trans('mautic.email.token.webview_url'),
141
        ];
142
        if ($event->tokensRequested(array_keys($tokens))) {
143
            $event->addTokens(
144
                $event->filterTokens($tokens)
145
            );
146
        }
147
148
        if ($event->slotTypesRequested()) {
149
            $event->addSlotType(
150
                'text',
151
                $this->translator->trans('mautic.core.slot.label.text'),
152
                'font',
153
                'MauticCoreBundle:Slots:text.html.php',
154
                SlotTextType::class,
155
                1000
156
            );
157
            $event->addSlotType(
158
                'image',
159
                $this->translator->trans('mautic.core.slot.label.image'),
160
                'image',
161
                'MauticCoreBundle:Slots:image.html.php',
162
                SlotImageCardType::class,
163
                900
164
            );
165
            $event->addSlotType(
166
                'imagecard',
167
                $this->translator->trans('mautic.core.slot.label.imagecard'),
168
                'id-card-o',
169
                'MauticCoreBundle:Slots:imagecard.html.php',
170
                SlotImageCardType::class,
171
                870
172
            );
173
            $event->addSlotType(
174
                'imagecaption',
175
                $this->translator->trans('mautic.core.slot.label.imagecaption'),
176
                'image',
177
                'MauticCoreBundle:Slots:imagecaption.html.php',
178
                SlotImageCaptionType::class,
179
                850
180
            );
181
            $event->addSlotType(
182
                'button',
183
                $this->translator->trans('mautic.core.slot.label.button'),
184
                'external-link',
185
                'MauticCoreBundle:Slots:button.html.php',
186
                SlotButtonType::class,
187
                800
188
            );
189
            $event->addSlotType(
190
                'socialfollow',
191
                $this->translator->trans('mautic.core.slot.label.socialfollow'),
192
                'twitter',
193
                'MauticCoreBundle:Slots:socialfollow.html.php',
194
                SlotSocialFollowType::class,
195
                600
196
            );
197
            $event->addSlotType(
198
                'codemode',
199
                $this->translator->trans('mautic.core.slot.label.codemode'),
200
                'code',
201
                'MauticCoreBundle:Slots:codemode.html.php',
202
                SlotCodeModeType::class,
203
                500
204
            );
205
            $event->addSlotType(
206
                'separator',
207
                $this->translator->trans('mautic.core.slot.label.separator'),
208
                'minus',
209
                'MauticCoreBundle:Slots:separator.html.php',
210
                SlotSeparatorType::class,
211
                400
212
            );
213
214
            $event->addSlotType(
215
                'dynamicContent',
216
                $this->translator->trans('mautic.core.slot.label.dynamiccontent'),
217
                'tag',
218
                'MauticCoreBundle:Slots:dynamiccontent.html.php',
219
                SlotDynamicContentType::class,
220
                300
221
            );
222
        }
223
224
        if ($event->sectionsRequested()) {
225
            $event->addSection(
226
                'one-column',
227
                $this->translator->trans('mautic.core.slot.label.onecolumn'),
228
                'file-text-o',
229
                'MauticCoreBundle:Sections:one-column.html.php',
230
                null,
231
                1000
232
            );
233
            $event->addSection(
234
                'two-column',
235
                $this->translator->trans('mautic.core.slot.label.twocolumns'),
236
                'columns',
237
                'MauticCoreBundle:Sections:two-column.html.php',
238
                null,
239
                900
240
            );
241
            $event->addSection(
242
                'three-column',
243
                $this->translator->trans('mautic.core.slot.label.threecolumns'),
244
                'th',
245
                'MauticCoreBundle:Sections:three-column.html.php',
246
                null,
247
                800
248
            );
249
        }
250
    }
251
252
    public function onEmailGenerate(EmailSendEvent $event)
253
    {
254
        $idHash = $event->getIdHash();
255
        $lead   = $event->getLead();
256
        $email  = $event->getEmail();
257
258
        if (null == $idHash) {
259
            // Generate a bogus idHash to prevent errors for routes that may include it
260
            $idHash = uniqid();
261
        }
262
263
        $unsubscribeText = $this->coreParametersHelper->get('unsubscribe_text');
264
        if (!$unsubscribeText) {
265
            $unsubscribeText = $this->translator->trans('mautic.email.unsubscribe.text', ['%link%' => '|URL|']);
266
        }
267
        $unsubscribeText = str_replace('|URL|', $this->emailModel->buildUrl('mautic_email_unsubscribe', ['idHash' => $idHash]), $unsubscribeText);
268
        $event->addToken('{unsubscribe_text}', EmojiHelper::toHtml($unsubscribeText));
269
270
        $event->addToken('{unsubscribe_url}', $this->emailModel->buildUrl('mautic_email_unsubscribe', ['idHash' => $idHash]));
271
272
        $webviewText = $this->coreParametersHelper->get('webview_text');
273
        if (!$webviewText) {
274
            $webviewText = $this->translator->trans('mautic.email.webview.text', ['%link%' => '|URL|']);
275
        }
276
        $webviewText = str_replace('|URL|', $this->emailModel->buildUrl('mautic_email_webview', ['idHash' => $idHash]), $webviewText);
277
        $event->addToken('{webview_text}', EmojiHelper::toHtml($webviewText));
278
279
        // Show public email preview if the lead is not known to prevent 404
280
        if (empty($lead['id']) && $email) {
281
            $event->addToken('{webview_url}', $this->emailModel->buildUrl('mautic_email_preview', ['objectId' => $email->getId()]));
282
        } else {
283
            $event->addToken('{webview_url}', $this->emailModel->buildUrl('mautic_email_webview', ['idHash' => $idHash]));
284
        }
285
286
        $signatureText = $this->coreParametersHelper->get('default_signature_text');
287
        $fromName      = $this->coreParametersHelper->get('mailer_from_name');
288
        $signatureText = str_replace('|FROM_NAME|', $fromName, nl2br($signatureText));
289
        $event->addToken('{signature}', EmojiHelper::toHtml($signatureText));
290
291
        $event->addToken('{subject}', EmojiHelper::toHtml($event->getSubject()));
292
    }
293
294
    /**
295
     * @return array
296
     */
297
    public function convertUrlsToTokens(EmailSendEvent $event)
298
    {
299
        if ($event->isInternalSend() || $this->coreParametersHelper->get('disable_trackable_urls')) {
300
            // Don't convert urls
301
            return;
302
        }
303
304
        $email   = $event->getEmail();
305
        $emailId = ($email) ? $email->getId() : null;
306
        if (!$email instanceof Email) {
0 ignored issues
show
$email is always a sub-type of Mautic\EmailBundle\Entity\Email.
Loading history...
307
            $email = $this->emailModel->getEntity($emailId);
308
        }
309
310
        $utmTags      = $email->getUtmTags();
311
        $clickthrough = $event->generateClickthrough();
312
        $trackables   = $this->parseContentForUrls($event, $emailId);
313
314
        /**
315
         * @var string
316
         * @var Trackable $trackable
317
         */
318
        foreach ($trackables as $token => $trackable) {
319
            $url = ($trackable instanceof Trackable)
320
                ?
321
                $this->pageTrackableModel->generateTrackableUrl($trackable, $clickthrough, false, $utmTags)
322
                :
323
                $this->pageRedirectModel->generateRedirectUrl($trackable, $clickthrough, false, $utmTags);
324
325
            $event->addToken($token, $url);
326
        }
327
    }
328
329
    /**
330
     * Parses content for URLs and tokens.
331
     *
332
     * @param $emailId
333
     *
334
     * @return mixed
335
     */
336
    private function parseContentForUrls(EmailSendEvent $event, $emailId)
337
    {
338
        static $convertedContent = [];
339
340
        // Prevent parsing the exact same content over and over
341
        if (!isset($convertedContent[$event->getContentHash()])) {
342
            $html = $event->getContent();
343
            $text = $event->getPlainText();
344
345
            $contentTokens = $event->getTokens();
346
347
            [$content, $trackables] = $this->pageTrackableModel->parseContentForTrackables(
348
                [$html, $text],
349
                $contentTokens,
350
                ($emailId) ? 'email' : null,
351
                $emailId
352
            );
353
354
            [$html, $text] = $content;
355
            unset($content);
356
357
            if ($html) {
358
                $event->setContent($html);
359
            }
360
            if ($text) {
361
                $event->setPlainText($text);
362
            }
363
364
            $convertedContent[$event->getContentHash()] = $trackables;
365
366
            // Don't need to preserve Trackable or Redirect entities in memory
367
            $this->entityManager->clear(Redirect::class);
368
            $this->entityManager->clear(Trackable::class);
369
370
            unset($html, $text, $trackables);
371
        }
372
373
        return $convertedContent[$event->getContentHash()];
374
    }
375
}
376