vendor/shopware/core/Content/Flow/Dispatching/Action/SendMailAction.php line 124

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Flow\Dispatching\Action;
  3. use Doctrine\DBAL\Connection;
  4. use Psr\Log\LoggerInterface;
  5. use Shopware\Core\Content\ContactForm\Event\ContactFormEvent;
  6. use Shopware\Core\Content\Flow\Dispatching\DelayableAction;
  7. use Shopware\Core\Content\Flow\Dispatching\StorableFlow;
  8. use Shopware\Core\Content\Flow\Events\FlowSendMailActionEvent;
  9. use Shopware\Core\Content\Mail\Service\AbstractMailService;
  10. use Shopware\Core\Content\Mail\Service\MailAttachmentsConfig;
  11. use Shopware\Core\Content\MailTemplate\Exception\MailEventConfigurationException;
  12. use Shopware\Core\Content\MailTemplate\Exception\SalesChannelNotFoundException;
  13. use Shopware\Core\Content\MailTemplate\MailTemplateActions;
  14. use Shopware\Core\Content\MailTemplate\MailTemplateEntity;
  15. use Shopware\Core\Content\MailTemplate\Subscriber\MailSendSubscriberConfig;
  16. use Shopware\Core\Framework\Adapter\Translation\Translator;
  17. use Shopware\Core\Framework\Context;
  18. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
  20. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  21. use Shopware\Core\Framework\Event\FlowEvent;
  22. use Shopware\Core\Framework\Event\MailAware;
  23. use Shopware\Core\Framework\Event\OrderAware;
  24. use Shopware\Core\Framework\Feature;
  25. use Shopware\Core\Framework\Log\Package;
  26. use Shopware\Core\Framework\Uuid\Uuid;
  27. use Shopware\Core\Framework\Validation\DataBag\DataBag;
  28. use Shopware\Core\System\Locale\LanguageLocaleCodeProvider;
  29. use Symfony\Contracts\EventDispatcher\Event;
  30. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  31. /**
  32.  * @deprecated tag:v6.5.0 - reason:remove-subscriber - FlowActions won't be executed over the event system anymore,
  33.  * therefore the actions won't implement the EventSubscriberInterface anymore.
  34.  */
  35. #[Package('business-ops')]
  36. class SendMailAction extends FlowAction implements DelayableAction
  37. {
  38.     public const ACTION_NAME MailTemplateActions::MAIL_TEMPLATE_MAIL_SEND_ACTION;
  39.     public const MAIL_CONFIG_EXTENSION 'mail-attachments';
  40.     private const RECIPIENT_CONFIG_ADMIN 'admin';
  41.     private const RECIPIENT_CONFIG_CUSTOM 'custom';
  42.     private const RECIPIENT_CONFIG_CONTACT_FORM_MAIL 'contactFormMail';
  43.     private EntityRepositoryInterface $mailTemplateRepository;
  44.     private LoggerInterface $logger;
  45.     private AbstractMailService $emailService;
  46.     private EventDispatcherInterface $eventDispatcher;
  47.     private EntityRepositoryInterface $mailTemplateTypeRepository;
  48.     private Translator $translator;
  49.     private Connection $connection;
  50.     private LanguageLocaleCodeProvider $languageLocaleProvider;
  51.     private bool $updateMailTemplate;
  52.     /**
  53.      * @internal
  54.      */
  55.     public function __construct(
  56.         AbstractMailService $emailService,
  57.         EntityRepositoryInterface $mailTemplateRepository,
  58.         LoggerInterface $logger,
  59.         EventDispatcherInterface $eventDispatcher,
  60.         EntityRepositoryInterface $mailTemplateTypeRepository,
  61.         Translator $translator,
  62.         Connection $connection,
  63.         LanguageLocaleCodeProvider $languageLocaleProvider,
  64.         bool $updateMailTemplate
  65.     ) {
  66.         $this->mailTemplateRepository $mailTemplateRepository;
  67.         $this->logger $logger;
  68.         $this->emailService $emailService;
  69.         $this->eventDispatcher $eventDispatcher;
  70.         $this->mailTemplateTypeRepository $mailTemplateTypeRepository;
  71.         $this->translator $translator;
  72.         $this->connection $connection;
  73.         $this->languageLocaleProvider $languageLocaleProvider;
  74.         $this->updateMailTemplate $updateMailTemplate;
  75.     }
  76.     public static function getName(): string
  77.     {
  78.         return 'action.mail.send';
  79.     }
  80.     /**
  81.      * @deprecated tag:v6.5.0 - reason:remove-subscriber - Will be removed
  82.      */
  83.     public static function getSubscribedEvents(): array
  84.     {
  85.         if (Feature::isActive('v6.5.0.0')) {
  86.             return [];
  87.         }
  88.         return [
  89.             self::getName() => 'handle',
  90.         ];
  91.     }
  92.     /**
  93.      * @return array<string>
  94.      */
  95.     public function requirements(): array
  96.     {
  97.         return [MailAware::class];
  98.     }
  99.     /**
  100.      * @deprecated tag:v6.5.0 Will be removed, implement handleFlow instead
  101.      *
  102.      * @throws MailEventConfigurationException
  103.      * @throws SalesChannelNotFoundException
  104.      * @throws InconsistentCriteriaIdsException
  105.      */
  106.     public function handle(Event $event): void
  107.     {
  108.         Feature::triggerDeprecationOrThrow(
  109.             'v6.5.0.0',
  110.             Feature::deprecatedMethodMessage(__CLASS____METHOD__'v6.5.0.0')
  111.         );
  112.         if (!$event instanceof FlowEvent) {
  113.             return;
  114.         }
  115.         $mailEvent $event->getEvent();
  116.         $extension $event->getContext()->getExtension(self::MAIL_CONFIG_EXTENSION);
  117.         if (!$extension instanceof MailSendSubscriberConfig) {
  118.             $extension = new MailSendSubscriberConfig(false, [], []);
  119.         }
  120.         if ($extension->skip()) {
  121.             return;
  122.         }
  123.         if (!$mailEvent instanceof MailAware) {
  124.             throw new MailEventConfigurationException('Not an instance of MailAware', \get_class($mailEvent));
  125.         }
  126.         $eventConfig $event->getConfig();
  127.         if (empty($eventConfig['recipient'])) {
  128.             throw new MailEventConfigurationException('The recipient value in the flow action configuration is missing.', \get_class($mailEvent));
  129.         }
  130.         if (!isset($eventConfig['mailTemplateId'])) {
  131.             return;
  132.         }
  133.         $mailTemplate $this->getMailTemplate($eventConfig['mailTemplateId'], $event->getContext());
  134.         if ($mailTemplate === null) {
  135.             return;
  136.         }
  137.         $injectedTranslator $this->injectTranslator($mailEvent->getContext(), $mailEvent->getSalesChannelId());
  138.         $data = new DataBag();
  139.         $contactFormData = [];
  140.         if ($mailEvent instanceof ContactFormEvent) {
  141.             $contactFormData $mailEvent->getContactFormData();
  142.         }
  143.         $recipients $this->getRecipients($eventConfig['recipient'], $mailEvent->getMailStruct()->getRecipients(), $contactFormData);
  144.         if (empty($recipients)) {
  145.             return;
  146.         }
  147.         $data->set('recipients'$recipients);
  148.         $data->set('senderName'$mailTemplate->getTranslation('senderName'));
  149.         $data->set('salesChannelId'$mailEvent->getSalesChannelId());
  150.         $data->set('templateId'$mailTemplate->getId());
  151.         $data->set('customFields'$mailTemplate->getCustomFields());
  152.         $data->set('contentHtml'$mailTemplate->getTranslation('contentHtml'));
  153.         $data->set('contentPlain'$mailTemplate->getTranslation('contentPlain'));
  154.         $data->set('subject'$mailTemplate->getTranslation('subject'));
  155.         $data->set('mediaIds', []);
  156.         $data->set('attachmentsConfig', new MailAttachmentsConfig(
  157.             $event->getContext(),
  158.             $mailTemplate,
  159.             $extension,
  160.             $eventConfig,
  161.             $mailEvent instanceof OrderAware $mailEvent->getOrderId() : null,
  162.         ));
  163.         $this->setReplyTo($data$eventConfig$contactFormData);
  164.         $this->eventDispatcher->dispatch(new FlowSendMailActionEvent($data$mailTemplate$event));
  165.         if ($data->has('templateId')) {
  166.             $this->updateMailTemplateType(
  167.                 $event->getContext(),
  168.                 $event,
  169.                 $this->getTemplateData($mailEvent),
  170.                 $mailTemplate
  171.             );
  172.         }
  173.         $templateData array_merge([
  174.             'eventName' => $mailEvent->getName(),
  175.         ], $this->getTemplateData($mailEvent));
  176.         $this->send($data$event->getContext(), $templateData$extension$injectedTranslator);
  177.     }
  178.     /**
  179.      * @throws MailEventConfigurationException
  180.      * @throws SalesChannelNotFoundException
  181.      * @throws InconsistentCriteriaIdsException
  182.      */
  183.     public function handleFlow(StorableFlow $flow): void
  184.     {
  185.         $extension $flow->getContext()->getExtension(self::MAIL_CONFIG_EXTENSION);
  186.         if (!$extension instanceof MailSendSubscriberConfig) {
  187.             $extension = new MailSendSubscriberConfig(false, [], []);
  188.         }
  189.         if ($extension->skip()) {
  190.             return;
  191.         }
  192.         if (!$flow->hasStore(MailAware::MAIL_STRUCT) || !$flow->hasStore(MailAware::SALES_CHANNEL_ID)) {
  193.             throw new MailEventConfigurationException('Not have data from MailAware', \get_class($flow));
  194.         }
  195.         $eventConfig $flow->getConfig();
  196.         if (empty($eventConfig['recipient'])) {
  197.             throw new MailEventConfigurationException('The recipient value in the flow action configuration is missing.', \get_class($flow));
  198.         }
  199.         if (!isset($eventConfig['mailTemplateId'])) {
  200.             return;
  201.         }
  202.         $mailTemplate $this->getMailTemplate($eventConfig['mailTemplateId'], $flow->getContext());
  203.         if ($mailTemplate === null) {
  204.             return;
  205.         }
  206.         $injectedTranslator $this->injectTranslator($flow->getContext(), $flow->getStore(MailAware::SALES_CHANNEL_ID));
  207.         $data = new DataBag();
  208.         $recipients $this->getRecipients(
  209.             $eventConfig['recipient'],
  210.             $flow->getStore(MailAware::MAIL_STRUCT)['recipients'],
  211.             $flow->getStore('contactFormData', []),
  212.         );
  213.         if (empty($recipients)) {
  214.             return;
  215.         }
  216.         $data->set('recipients'$recipients);
  217.         $data->set('senderName'$mailTemplate->getTranslation('senderName'));
  218.         $data->set('salesChannelId'$flow->getStore(MailAware::SALES_CHANNEL_ID));
  219.         $data->set('templateId'$mailTemplate->getId());
  220.         $data->set('customFields'$mailTemplate->getCustomFields());
  221.         $data->set('contentHtml'$mailTemplate->getTranslation('contentHtml'));
  222.         $data->set('contentPlain'$mailTemplate->getTranslation('contentPlain'));
  223.         $data->set('subject'$mailTemplate->getTranslation('subject'));
  224.         $data->set('mediaIds', []);
  225.         $data->set('attachmentsConfig', new MailAttachmentsConfig(
  226.             $flow->getContext(),
  227.             $mailTemplate,
  228.             $extension,
  229.             $eventConfig,
  230.             $flow->getStore(OrderAware::ORDER_ID),
  231.         ));
  232.         $this->setReplyTo($data$eventConfig$flow->getStore('contactFormData', []));
  233.         $this->eventDispatcher->dispatch(new FlowSendMailActionEvent($data$mailTemplate$flow));
  234.         if ($data->has('templateId')) {
  235.             $this->updateMailTemplateType(
  236.                 $flow->getContext(),
  237.                 $flow,
  238.                 $flow->data(),
  239.                 $mailTemplate
  240.             );
  241.         }
  242.         $templateData array_merge([
  243.             'eventName' => $flow->getName(),
  244.         ], $flow->data());
  245.         $this->send($data$flow->getContext(), $templateData$extension$injectedTranslator);
  246.     }
  247.     /**
  248.      * @param array<string, mixed> $templateData
  249.      */
  250.     private function send(DataBag $dataContext $context, array $templateDataMailSendSubscriberConfig $extensionbool $injectedTranslator): void
  251.     {
  252.         try {
  253.             $this->emailService->send(
  254.                 $data->all(),
  255.                 $context,
  256.                 $templateData
  257.             );
  258.         } catch (\Exception $e) {
  259.             $this->logger->error(
  260.                 "Could not send mail:\n"
  261.                 $e->getMessage() . "\n"
  262.                 'Error Code:' $e->getCode() . "\n"
  263.                 "Template data: \n"
  264.                 json_encode($data->all()) . "\n"
  265.             );
  266.         }
  267.         if ($injectedTranslator) {
  268.             $this->translator->resetInjection();
  269.         }
  270.     }
  271.     /**
  272.      * @param FlowEvent|StorableFlow $event
  273.      * @param array<string, mixed> $templateData
  274.      */
  275.     private function updateMailTemplateType(
  276.         Context $context,
  277.         $event,
  278.         array $templateData,
  279.         MailTemplateEntity $mailTemplate
  280.     ): void {
  281.         if (!$mailTemplate->getMailTemplateTypeId()) {
  282.             return;
  283.         }
  284.         if (!$this->updateMailTemplate) {
  285.             return;
  286.         }
  287.         $mailTemplateTypeTranslation $this->connection->fetchOne(
  288.             'SELECT 1 FROM mail_template_type_translation WHERE language_id = :languageId AND mail_template_type_id =:mailTemplateTypeId',
  289.             [
  290.                 'languageId' => Uuid::fromHexToBytes($context->getLanguageId()),
  291.                 'mailTemplateTypeId' => Uuid::fromHexToBytes($mailTemplate->getMailTemplateTypeId()),
  292.             ]
  293.         );
  294.         if (!$mailTemplateTypeTranslation) {
  295.             // Don't throw errors if this fails // Fix with NEXT-15475
  296.             $this->logger->error(
  297.                 "Could not update mail template type, because translation for this language does not exits:\n"
  298.                 'Flow id: ' $event->getFlowState()->flowId "\n"
  299.                 'Sequence id: ' $event->getFlowState()->getSequenceId()
  300.             );
  301.             return;
  302.         }
  303.         $this->mailTemplateTypeRepository->update([[
  304.             'id' => $mailTemplate->getMailTemplateTypeId(),
  305.             'templateData' => $templateData,
  306.         ]], $context);
  307.     }
  308.     private function getMailTemplate(string $idContext $context): ?MailTemplateEntity
  309.     {
  310.         $criteria = new Criteria([$id]);
  311.         $criteria->setTitle('send-mail::load-mail-template');
  312.         $criteria->addAssociation('media.media');
  313.         $criteria->setLimit(1);
  314.         return $this->mailTemplateRepository
  315.             ->search($criteria$context)
  316.             ->first();
  317.     }
  318.     /**
  319.      * @throws MailEventConfigurationException
  320.      *
  321.      * @return array<string, mixed>
  322.      */
  323.     private function getTemplateData(MailAware $event): array
  324.     {
  325.         $data = [];
  326.         foreach (array_keys($event::getAvailableData()->toArray()) as $key) {
  327.             $getter 'get' ucfirst($key);
  328.             if (!method_exists($event$getter)) {
  329.                 throw new MailEventConfigurationException('Data for ' $key ' not available.', \get_class($event));
  330.             }
  331.             $data[$key] = $event->$getter();
  332.         }
  333.         return $data;
  334.     }
  335.     private function injectTranslator(Context $context, ?string $salesChannelId): bool
  336.     {
  337.         if ($salesChannelId === null) {
  338.             return false;
  339.         }
  340.         if ($this->translator->getSnippetSetId() !== null) {
  341.             return false;
  342.         }
  343.         $this->translator->injectSettings(
  344.             $salesChannelId,
  345.             $context->getLanguageId(),
  346.             $this->languageLocaleProvider->getLocaleForLanguageId($context->getLanguageId()),
  347.             $context
  348.         );
  349.         return true;
  350.     }
  351.     /**
  352.      * @param array<string, mixed> $recipients
  353.      * @param array<string, mixed> $mailStructRecipients
  354.      * @param array<int|string, mixed> $contactFormData
  355.      *
  356.      * @return array<int|string, string>
  357.      */
  358.     private function getRecipients(array $recipients, array $mailStructRecipients, array $contactFormData): array
  359.     {
  360.         switch ($recipients['type']) {
  361.             case self::RECIPIENT_CONFIG_CUSTOM:
  362.                 return $recipients['data'];
  363.             case self::RECIPIENT_CONFIG_ADMIN:
  364.                 $admins $this->connection->fetchAllAssociative(
  365.                     'SELECT first_name, last_name, email FROM user WHERE admin = true'
  366.                 );
  367.                 $emails = [];
  368.                 foreach ($admins as $admin) {
  369.                     $emails[$admin['email']] = $admin['first_name'] . ' ' $admin['last_name'];
  370.                 }
  371.                 return $emails;
  372.             case self::RECIPIENT_CONFIG_CONTACT_FORM_MAIL:
  373.                 if (empty($contactFormData)) {
  374.                     return [];
  375.                 }
  376.                 if (!\array_key_exists('email'$contactFormData)) {
  377.                     return [];
  378.                 }
  379.                 return [$contactFormData['email'] => ($contactFormData['firstName'] ?? '') . ' ' . ($contactFormData['lastName'] ?? '')];
  380.             default:
  381.                 return $mailStructRecipients;
  382.         }
  383.     }
  384.     /**
  385.      * @param array<string, mixed> $eventConfig
  386.      * @param array<int|string, mixed> $contactFormData
  387.      */
  388.     private function setReplyTo(DataBag $data, array $eventConfig, array $contactFormData): void
  389.     {
  390.         if (empty($eventConfig['replyTo']) || !\is_string($eventConfig['replyTo'])) {
  391.             return;
  392.         }
  393.         if ($eventConfig['replyTo'] !== self::RECIPIENT_CONFIG_CONTACT_FORM_MAIL) {
  394.             $data->set('senderMail'$eventConfig['replyTo']);
  395.             return;
  396.         }
  397.         if (empty($contactFormData['email']) || !\is_string($contactFormData['email'])) {
  398.             return;
  399.         }
  400.         $data->set(
  401.             'senderName',
  402.             '{% if contactFormData.firstName is defined %}{{ contactFormData.firstName }}{% endif %} '
  403.             '{% if contactFormData.lastName is defined %}{{ contactFormData.lastName }}{% endif %}'
  404.         );
  405.         $data->set('senderMail'$contactFormData['email']);
  406.     }
  407. }