Passed
Push — 6.4 ( 2d6f80...5cefcc )
by Christian
15:13 queued 16s
created

SendMailAction   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 445
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 216
dl 0
loc 445
rs 6.4799
c 0
b 0
f 0
wmc 54

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 3 1
A __construct() 0 20 1
A requirements() 0 3 1
A getSubscribedEvents() 0 8 2
C handle() 0 90 12
A getMailTemplate() 0 10 1
A getTemplateData() 0 13 3
A send() 0 20 3
A updateMailTemplateType() 0 37 4
B getRecipients() 0 27 7
A injectTranslator() 0 18 3
B handleFlow() 0 77 10
A setReplyTo() 0 22 6

How to fix   Complexity   

Complex Class

Complex classes like SendMailAction 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.

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 SendMailAction, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Content\Flow\Dispatching\Action;
4
5
use Doctrine\DBAL\Connection;
6
use Psr\Log\LoggerInterface;
7
use Shopware\Core\Content\ContactForm\Event\ContactFormEvent;
8
use Shopware\Core\Content\Flow\Dispatching\DelayableAction;
9
use Shopware\Core\Content\Flow\Dispatching\StorableFlow;
10
use Shopware\Core\Content\Flow\Events\FlowSendMailActionEvent;
11
use Shopware\Core\Content\Mail\Service\AbstractMailService;
12
use Shopware\Core\Content\Mail\Service\MailAttachmentsConfig;
13
use Shopware\Core\Content\MailTemplate\Exception\MailEventConfigurationException;
14
use Shopware\Core\Content\MailTemplate\Exception\SalesChannelNotFoundException;
15
use Shopware\Core\Content\MailTemplate\MailTemplateActions;
16
use Shopware\Core\Content\MailTemplate\MailTemplateEntity;
17
use Shopware\Core\Content\MailTemplate\Subscriber\MailSendSubscriberConfig;
18
use Shopware\Core\Framework\Adapter\Translation\Translator;
19
use Shopware\Core\Framework\Context;
20
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
21
use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
22
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
23
use Shopware\Core\Framework\Event\FlowEvent;
24
use Shopware\Core\Framework\Event\MailAware;
25
use Shopware\Core\Framework\Event\OrderAware;
26
use Shopware\Core\Framework\Feature;
27
use Shopware\Core\Framework\Uuid\Uuid;
28
use Shopware\Core\Framework\Validation\DataBag\DataBag;
29
use Shopware\Core\System\Locale\LanguageLocaleCodeProvider;
30
use Symfony\Contracts\EventDispatcher\Event;
31
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
32
33
/**
34
 * @package business-ops
35
 *
36
 * @deprecated tag:v6.5.0 - reason:remove-subscriber - FlowActions won't be executed over the event system anymore,
37
 * therefore the actions won't implement the EventSubscriberInterface anymore.
38
 */
39
class SendMailAction extends FlowAction implements DelayableAction
0 ignored issues
show
Deprecated Code introduced by
The class Shopware\Core\Content\Fl...ching\Action\FlowAction has been deprecated: tag:v6.5.0 - reason:remove-subscriber - FlowActions won't be executed over the event system anymore, therefore the actions won't implement the EventSubscriberInterface anymore. ( Ignorable by Annotation )

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

39
class SendMailAction extends /** @scrutinizer ignore-deprecated */ FlowAction implements DelayableAction
Loading history...
40
{
41
    public const ACTION_NAME = MailTemplateActions::MAIL_TEMPLATE_MAIL_SEND_ACTION;
42
    public const MAIL_CONFIG_EXTENSION = 'mail-attachments';
43
    private const RECIPIENT_CONFIG_ADMIN = 'admin';
44
    private const RECIPIENT_CONFIG_CUSTOM = 'custom';
45
    private const RECIPIENT_CONFIG_CONTACT_FORM_MAIL = 'contactFormMail';
46
47
    private EntityRepositoryInterface $mailTemplateRepository;
48
49
    private LoggerInterface $logger;
50
51
    private AbstractMailService $emailService;
52
53
    private EventDispatcherInterface $eventDispatcher;
54
55
    private EntityRepositoryInterface $mailTemplateTypeRepository;
56
57
    private Translator $translator;
58
59
    private Connection $connection;
60
61
    private LanguageLocaleCodeProvider $languageLocaleProvider;
62
63
    private bool $updateMailTemplate;
64
65
    /**
66
     * @internal
67
     */
68
    public function __construct(
69
        AbstractMailService $emailService,
70
        EntityRepositoryInterface $mailTemplateRepository,
71
        LoggerInterface $logger,
72
        EventDispatcherInterface $eventDispatcher,
73
        EntityRepositoryInterface $mailTemplateTypeRepository,
74
        Translator $translator,
75
        Connection $connection,
76
        LanguageLocaleCodeProvider $languageLocaleProvider,
77
        bool $updateMailTemplate
78
    ) {
79
        $this->mailTemplateRepository = $mailTemplateRepository;
80
        $this->logger = $logger;
81
        $this->emailService = $emailService;
82
        $this->eventDispatcher = $eventDispatcher;
83
        $this->mailTemplateTypeRepository = $mailTemplateTypeRepository;
84
        $this->translator = $translator;
85
        $this->connection = $connection;
86
        $this->languageLocaleProvider = $languageLocaleProvider;
87
        $this->updateMailTemplate = $updateMailTemplate;
88
    }
89
90
    public static function getName(): string
91
    {
92
        return 'action.mail.send';
93
    }
94
95
    /**
96
     * @deprecated tag:v6.5.0 - reason:remove-subscriber - Will be removed
97
     */
98
    public static function getSubscribedEvents(): array
99
    {
100
        if (Feature::isActive('v6.5.0.0')) {
101
            return [];
102
        }
103
104
        return [
105
            self::getName() => 'handle',
106
        ];
107
    }
108
109
    /**
110
     * @return array<string>
111
     */
112
    public function requirements(): array
113
    {
114
        return [MailAware::class];
115
    }
116
117
    /**
118
     * @deprecated tag:v6.5.0 Will be removed, implement handleFlow instead
119
     *
120
     * @throws MailEventConfigurationException
121
     * @throws SalesChannelNotFoundException
122
     * @throws InconsistentCriteriaIdsException
123
     */
124
    public function handle(Event $event): void
125
    {
126
        Feature::triggerDeprecationOrThrow(
127
            'v6.5.0.0',
128
            Feature::deprecatedMethodMessage(__CLASS__, __METHOD__, 'v6.5.0.0')
129
        );
130
131
        if (!$event instanceof FlowEvent) {
132
            return;
133
        }
134
135
        $mailEvent = $event->getEvent();
136
137
        $extension = $event->getContext()->getExtension(self::MAIL_CONFIG_EXTENSION);
138
        if (!$extension instanceof MailSendSubscriberConfig) {
139
            $extension = new MailSendSubscriberConfig(false, [], []);
140
        }
141
142
        if ($extension->skip()) {
143
            return;
144
        }
145
146
        if (!$mailEvent instanceof MailAware) {
147
            throw new MailEventConfigurationException('Not an instance of MailAware', \get_class($mailEvent));
148
        }
149
150
        $eventConfig = $event->getConfig();
151
152
        if (empty($eventConfig['recipient'])) {
153
            throw new MailEventConfigurationException('The recipient value in the flow action configuration is missing.', \get_class($mailEvent));
154
        }
155
156
        if (!isset($eventConfig['mailTemplateId'])) {
157
            return;
158
        }
159
160
        $mailTemplate = $this->getMailTemplate($eventConfig['mailTemplateId'], $event->getContext());
161
162
        if ($mailTemplate === null) {
163
            return;
164
        }
165
166
        $injectedTranslator = $this->injectTranslator($mailEvent->getContext(), $mailEvent->getSalesChannelId());
167
168
        $data = new DataBag();
169
170
        $contactFormData = [];
171
        if ($mailEvent instanceof ContactFormEvent) {
172
            $contactFormData = $mailEvent->getContactFormData();
173
        }
174
175
        $recipients = $this->getRecipients($eventConfig['recipient'], $mailEvent->getMailStruct()->getRecipients(), $contactFormData);
176
177
        if (empty($recipients)) {
178
            return;
179
        }
180
181
        $data->set('recipients', $recipients);
182
        $data->set('senderName', $mailTemplate->getTranslation('senderName'));
183
        $data->set('salesChannelId', $mailEvent->getSalesChannelId());
184
185
        $data->set('templateId', $mailTemplate->getId());
186
        $data->set('customFields', $mailTemplate->getCustomFields());
187
        $data->set('contentHtml', $mailTemplate->getTranslation('contentHtml'));
188
        $data->set('contentPlain', $mailTemplate->getTranslation('contentPlain'));
189
        $data->set('subject', $mailTemplate->getTranslation('subject'));
190
        $data->set('mediaIds', []);
191
192
        $data->set('attachmentsConfig', new MailAttachmentsConfig(
193
            $event->getContext(),
194
            $mailTemplate,
195
            $extension,
196
            $eventConfig,
197
            $mailEvent instanceof OrderAware ? $mailEvent->getOrderId() : null,
198
        ));
199
200
        $this->setReplyTo($data, $eventConfig, $contactFormData);
201
202
        $this->eventDispatcher->dispatch(new FlowSendMailActionEvent($data, $mailTemplate, $event));
203
204
        if ($data->has('templateId')) {
205
            $this->updateMailTemplateType(
206
                $event->getContext(),
207
                $event,
208
                $this->getTemplateData($mailEvent),
209
                $mailTemplate
210
            );
211
        }
212
213
        $this->send($data, $event->getContext(), $this->getTemplateData($mailEvent), $extension, $injectedTranslator);
214
    }
215
216
    /**
217
     * @throws MailEventConfigurationException
218
     * @throws SalesChannelNotFoundException
219
     * @throws InconsistentCriteriaIdsException
220
     */
221
    public function handleFlow(StorableFlow $flow): void
222
    {
223
        $extension = $flow->getContext()->getExtension(self::MAIL_CONFIG_EXTENSION);
224
        if (!$extension instanceof MailSendSubscriberConfig) {
225
            $extension = new MailSendSubscriberConfig(false, [], []);
226
        }
227
228
        if ($extension->skip()) {
229
            return;
230
        }
231
232
        if (!$flow->hasStore(MailAware::MAIL_STRUCT) || !$flow->hasStore(MailAware::SALES_CHANNEL_ID)) {
233
            throw new MailEventConfigurationException('Not have data from MailAware', \get_class($flow));
234
        }
235
236
        $eventConfig = $flow->getConfig();
237
        if (empty($eventConfig['recipient'])) {
238
            throw new MailEventConfigurationException('The recipient value in the flow action configuration is missing.', \get_class($flow));
239
        }
240
241
        if (!isset($eventConfig['mailTemplateId'])) {
242
            return;
243
        }
244
245
        $mailTemplate = $this->getMailTemplate($eventConfig['mailTemplateId'], $flow->getContext());
246
247
        if ($mailTemplate === null) {
248
            return;
249
        }
250
251
        $injectedTranslator = $this->injectTranslator($flow->getContext(), $flow->getStore(MailAware::SALES_CHANNEL_ID));
252
253
        $data = new DataBag();
254
255
        $recipients = $this->getRecipients(
256
            $eventConfig['recipient'],
257
            $flow->getStore(MailAware::MAIL_STRUCT)['recipients'],
258
            $flow->getStore('contactFormData', []),
259
        );
260
261
        if (empty($recipients)) {
262
            return;
263
        }
264
265
        $data->set('recipients', $recipients);
266
        $data->set('senderName', $mailTemplate->getTranslation('senderName'));
267
        $data->set('salesChannelId', $flow->getStore(MailAware::SALES_CHANNEL_ID));
268
269
        $data->set('templateId', $mailTemplate->getId());
270
        $data->set('customFields', $mailTemplate->getCustomFields());
271
        $data->set('contentHtml', $mailTemplate->getTranslation('contentHtml'));
272
        $data->set('contentPlain', $mailTemplate->getTranslation('contentPlain'));
273
        $data->set('subject', $mailTemplate->getTranslation('subject'));
274
        $data->set('mediaIds', []);
275
276
        $data->set('attachmentsConfig', new MailAttachmentsConfig(
277
            $flow->getContext(),
278
            $mailTemplate,
279
            $extension,
280
            $eventConfig,
281
            $flow->getStore(OrderAware::ORDER_ID),
282
        ));
283
284
        $this->setReplyTo($data, $eventConfig, $flow->getStore('contactFormData', []));
285
286
        $this->eventDispatcher->dispatch(new FlowSendMailActionEvent($data, $mailTemplate, $flow));
287
288
        if ($data->has('templateId')) {
289
            $this->updateMailTemplateType(
290
                $flow->getContext(),
291
                $flow,
292
                $flow->data(),
293
                $mailTemplate
294
            );
295
        }
296
297
        $this->send($data, $flow->getContext(), $flow->data(), $extension, $injectedTranslator);
298
    }
299
300
    /**
301
     * @param array<string, mixed> $templateData
302
     */
303
    private function send(DataBag $data, Context $context, array $templateData, MailSendSubscriberConfig $extension, bool $injectedTranslator): void
304
    {
305
        try {
306
            $this->emailService->send(
307
                $data->all(),
308
                $context,
309
                $templateData
310
            );
311
        } catch (\Exception $e) {
312
            $this->logger->error(
313
                "Could not send mail:\n"
314
                . $e->getMessage() . "\n"
315
                . 'Error Code:' . $e->getCode() . "\n"
316
                . "Template data: \n"
317
                . json_encode($data->all()) . "\n"
318
            );
319
        }
320
321
        if ($injectedTranslator) {
322
            $this->translator->resetInjection();
323
        }
324
    }
325
326
    /**
327
     * @param FlowEvent|StorableFlow $event
328
     * @param array<string, mixed> $templateData
329
     */
330
    private function updateMailTemplateType(
331
        Context $context,
332
        $event,
333
        array $templateData,
334
        MailTemplateEntity $mailTemplate
335
    ): void {
336
        if (!$mailTemplate->getMailTemplateTypeId()) {
337
            return;
338
        }
339
340
        if (!$this->updateMailTemplate) {
341
            return;
342
        }
343
344
        $mailTemplateTypeTranslation = $this->connection->fetchOne(
345
            'SELECT 1 FROM mail_template_type_translation WHERE language_id = :languageId AND mail_template_type_id =:mailTemplateTypeId',
346
            [
347
                'languageId' => Uuid::fromHexToBytes($context->getLanguageId()),
348
                'mailTemplateTypeId' => Uuid::fromHexToBytes($mailTemplate->getMailTemplateTypeId()),
349
            ]
350
        );
351
352
        if (!$mailTemplateTypeTranslation) {
353
            // Don't throw errors if this fails // Fix with NEXT-15475
354
            $this->logger->error(
355
                "Could not update mail template type, because translation for this language does not exits:\n"
356
                . 'Flow id: ' . $event->getFlowState()->flowId . "\n"
357
                . 'Sequence id: ' . $event->getFlowState()->getSequenceId()
358
            );
359
360
            return;
361
        }
362
363
        $this->mailTemplateTypeRepository->update([[
364
            'id' => $mailTemplate->getMailTemplateTypeId(),
365
            'templateData' => $templateData,
366
        ]], $context);
367
    }
368
369
    private function getMailTemplate(string $id, Context $context): ?MailTemplateEntity
370
    {
371
        $criteria = new Criteria([$id]);
372
        $criteria->setTitle('send-mail::load-mail-template');
373
        $criteria->addAssociation('media.media');
374
        $criteria->setLimit(1);
375
376
        return $this->mailTemplateRepository
377
            ->search($criteria, $context)
378
            ->first();
379
    }
380
381
    /**
382
     * @throws MailEventConfigurationException
383
     *
384
     * @return array<string, mixed>
385
     */
386
    private function getTemplateData(MailAware $event): array
387
    {
388
        $data = [];
389
390
        foreach (array_keys($event::getAvailableData()->toArray()) as $key) {
391
            $getter = 'get' . ucfirst($key);
392
            if (!method_exists($event, $getter)) {
393
                throw new MailEventConfigurationException('Data for ' . $key . ' not available.', \get_class($event));
394
            }
395
            $data[$key] = $event->$getter();
396
        }
397
398
        return $data;
399
    }
400
401
    private function injectTranslator(Context $context, ?string $salesChannelId): bool
402
    {
403
        if ($salesChannelId === null) {
404
            return false;
405
        }
406
407
        if ($this->translator->getSnippetSetId() !== null) {
408
            return false;
409
        }
410
411
        $this->translator->injectSettings(
412
            $salesChannelId,
413
            $context->getLanguageId(),
414
            $this->languageLocaleProvider->getLocaleForLanguageId($context->getLanguageId()),
415
            $context
416
        );
417
418
        return true;
419
    }
420
421
    /**
422
     * @param array<string, mixed> $recipients
423
     * @param array<string, mixed> $mailStructRecipients
424
     * @param array<int|string, mixed> $contactFormData
425
     *
426
     * @return array<int|string, string>
427
     */
428
    private function getRecipients(array $recipients, array $mailStructRecipients, array $contactFormData): array
429
    {
430
        switch ($recipients['type']) {
431
            case self::RECIPIENT_CONFIG_CUSTOM:
432
                return $recipients['data'];
433
            case self::RECIPIENT_CONFIG_ADMIN:
434
                $admins = $this->connection->fetchAllAssociative(
435
                    'SELECT first_name, last_name, email FROM user WHERE admin = true'
436
                );
437
                $emails = [];
438
                foreach ($admins as $admin) {
439
                    $emails[$admin['email']] = $admin['first_name'] . ' ' . $admin['last_name'];
440
                }
441
442
                return $emails;
443
            case self::RECIPIENT_CONFIG_CONTACT_FORM_MAIL:
444
                if (empty($contactFormData)) {
445
                    return [];
446
                }
447
448
                if (!\array_key_exists('email', $contactFormData)) {
449
                    return [];
450
                }
451
452
                return [$contactFormData['email'] => ($contactFormData['firstName'] ?? '') . ' ' . ($contactFormData['lastName'] ?? '')];
453
            default:
454
                return $mailStructRecipients;
455
        }
456
    }
457
458
    /**
459
     * @param array<string, mixed> $eventConfig
460
     * @param array<int|string, mixed> $contactFormData
461
     */
462
    private function setReplyTo(DataBag $data, array $eventConfig, array $contactFormData): void
463
    {
464
        if (empty($eventConfig['replyTo']) || !\is_string($eventConfig['replyTo'])) {
465
            return;
466
        }
467
468
        if ($eventConfig['replyTo'] !== self::RECIPIENT_CONFIG_CONTACT_FORM_MAIL) {
469
            $data->set('senderMail', $eventConfig['replyTo']);
470
471
            return;
472
        }
473
474
        if (empty($contactFormData['email']) || !\is_string($contactFormData['email'])) {
475
            return;
476
        }
477
478
        $data->set(
479
            'senderName',
480
            '{% if contactFormData.firstName is defined %}{{ contactFormData.firstName }}{% endif %} '
481
            . '{% if contactFormData.lastName is defined %}{{ contactFormData.lastName }}{% endif %}'
482
        );
483
        $data->set('senderMail', $contactFormData['email']);
484
    }
485
}
486