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

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\Checkout\Document\DocumentService;
  6. use Shopware\Core\Content\ContactForm\Event\ContactFormEvent;
  7. use Shopware\Core\Content\Flow\Events\FlowSendMailActionEvent;
  8. use Shopware\Core\Content\Mail\Service\AbstractMailService;
  9. use Shopware\Core\Content\MailTemplate\Exception\MailEventConfigurationException;
  10. use Shopware\Core\Content\MailTemplate\Exception\SalesChannelNotFoundException;
  11. use Shopware\Core\Content\MailTemplate\MailTemplateActions;
  12. use Shopware\Core\Content\MailTemplate\MailTemplateEntity;
  13. use Shopware\Core\Content\MailTemplate\Subscriber\MailSendSubscriberConfig;
  14. use Shopware\Core\Content\Media\MediaService;
  15. use Shopware\Core\Framework\Adapter\Translation\Translator;
  16. use Shopware\Core\Framework\Context;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\FetchModeHelper;
  18. use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
  19. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  20. use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
  21. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  22. use Shopware\Core\Framework\Event\FlowEvent;
  23. use Shopware\Core\Framework\Event\MailAware;
  24. use Shopware\Core\Framework\Event\OrderAware;
  25. use Shopware\Core\Framework\Uuid\Uuid;
  26. use Shopware\Core\Framework\Validation\DataBag\DataBag;
  27. use Shopware\Core\System\Locale\LanguageLocaleCodeProvider;
  28. use Symfony\Contracts\EventDispatcher\Event;
  29. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  30. class SendMailAction extends FlowAction
  31. {
  32.     public const ACTION_NAME = MailTemplateActions::MAIL_TEMPLATE_MAIL_SEND_ACTION;
  33.     public const MAIL_CONFIG_EXTENSION = 'mail-attachments';
  34.     private const RECIPIENT_CONFIG_ADMIN = 'admin';
  35.     private const RECIPIENT_CONFIG_CUSTOM = 'custom';
  36.     private const RECIPIENT_CONFIG_CONTACT_FORM_MAIL = 'contactFormMail';
  37.     private EntityRepositoryInterface $mailTemplateRepository;
  38.     private MediaService $mediaService;
  39.     private EntityRepositoryInterface $mediaRepository;
  40.     private DocumentService $documentService;
  41.     private EntityRepositoryInterface $documentRepository;
  42.     private LoggerInterface $logger;
  43.     private AbstractMailService $emailService;
  44.     private EventDispatcherInterface $eventDispatcher;
  45.     private EntityRepositoryInterface $mailTemplateTypeRepository;
  46.     private Translator $translator;
  47.     private Connection $connection;
  48.     private LanguageLocaleCodeProvider $languageLocaleProvider;
  49.     private bool $updateMailTemplate;
  50.     public function __construct(
  51.         AbstractMailService $emailService,
  52.         EntityRepositoryInterface $mailTemplateRepository,
  53.         MediaService $mediaService,
  54.         EntityRepositoryInterface $mediaRepository,
  55.         EntityRepositoryInterface $documentRepository,
  56.         DocumentService $documentService,
  57.         LoggerInterface $logger,
  58.         EventDispatcherInterface $eventDispatcher,
  59.         EntityRepositoryInterface $mailTemplateTypeRepository,
  60.         Translator $translator,
  61.         Connection $connection,
  62.         LanguageLocaleCodeProvider $languageLocaleProvider,
  63.         bool $updateMailTemplate
  64.     ) {
  65.         $this->mailTemplateRepository = $mailTemplateRepository;
  66.         $this->mediaService = $mediaService;
  67.         $this->mediaRepository = $mediaRepository;
  68.         $this->documentRepository = $documentRepository;
  69.         $this->documentService = $documentService;
  70.         $this->logger = $logger;
  71.         $this->emailService = $emailService;
  72.         $this->eventDispatcher = $eventDispatcher;
  73.         $this->mailTemplateTypeRepository = $mailTemplateTypeRepository;
  74.         $this->translator = $translator;
  75.         $this->connection = $connection;
  76.         $this->languageLocaleProvider = $languageLocaleProvider;
  77.         $this->updateMailTemplate = $updateMailTemplate;
  78.     }
  79.     public static function getName(): string
  80.     {
  81.         return 'action.mail.send';
  82.     }
  83.     public static function getSubscribedEvents(): array
  84.     {
  85.         return [
  86.             self::getName() => 'handle',
  87.         ];
  88.     }
  89.     public function requirements(): array
  90.     {
  91.         return [MailAware::class];
  92.     }
  93.     /**
  94.      * @throws MailEventConfigurationException
  95.      * @throws SalesChannelNotFoundException
  96.      * @throws InconsistentCriteriaIdsException
  97.      */
  98.     public function handle(Event $event): void
  99.     {
  100.         if (!$event instanceof FlowEvent) {
  101.             return;
  102.         }
  103.         $mailEvent = $event->getEvent();
  104.         $extension = $event->getContext()->getExtension(self::MAIL_CONFIG_EXTENSION);
  105.         if (!$extension instanceof MailSendSubscriberConfig) {
  106.             $extension = new MailSendSubscriberConfig(false, [], []);
  107.         }
  108.         if ($extension->skip()) {
  109.             return;
  110.         }
  111.         if (!$mailEvent instanceof MailAware) {
  112.             throw new MailEventConfigurationException('Not an instance of MailAware', \get_class($mailEvent));
  113.         }
  114.         $eventConfig = $event->getConfig();
  115.         if (empty($eventConfig['recipient'])) {
  116.             throw new MailEventConfigurationException('The recipient value in the flow action configuration is missing.', \get_class($mailEvent));
  117.         }
  118.         if (!isset($eventConfig['mailTemplateId'])) {
  119.             return;
  120.         }
  121.         $mailTemplate = $this->getMailTemplate($eventConfig['mailTemplateId'], $event->getContext());
  122.         if ($mailTemplate === null) {
  123.             return;
  124.         }
  125.         $injectedTranslator = $this->injectTranslator($mailEvent);
  126.         $data = new DataBag();
  127.         $recipients = $this->getRecipients($eventConfig['recipient'], $mailEvent);
  128.         if (empty($recipients)) {
  129.             return;
  130.         }
  131.         $data->set('recipients', $recipients);
  132.         $data->set('senderName', $mailTemplate->getTranslation('senderName'));
  133.         $data->set('salesChannelId', $mailEvent->getSalesChannelId());
  134.         $data->set('templateId', $mailTemplate->getId());
  135.         $data->set('customFields', $mailTemplate->getCustomFields());
  136.         $data->set('contentHtml', $mailTemplate->getTranslation('contentHtml'));
  137.         $data->set('contentPlain', $mailTemplate->getTranslation('contentPlain'));
  138.         $data->set('subject', $mailTemplate->getTranslation('subject'));
  139.         $data->set('mediaIds', []);
  140.         $attachments = array_unique($this->buildAttachments($mailEvent, $mailTemplate, $extension, $eventConfig), \SORT_REGULAR);
  141.         if (!empty($attachments)) {
  142.             $data->set('binAttachments', $attachments);
  143.         }
  144.         $this->eventDispatcher->dispatch(new FlowSendMailActionEvent($data, $mailTemplate, $event));
  145.         if ($data->has('templateId')) {
  146.             $this->updateMailTemplateType($event, $mailEvent, $mailTemplate);
  147.         }
  148.         try {
  149.             $this->emailService->send(
  150.                 $data->all(),
  151.                 $event->getContext(),
  152.                 $this->getTemplateData($mailEvent)
  153.             );
  154.             $writes = array_map(static function ($id) {
  155.                 return ['id' => $id, 'sent' => true];
  156.             }, array_column($attachments, 'id'));
  157.             if (!empty($writes)) {
  158.                 $this->documentRepository->update($writes, $event->getContext());
  159.             }
  160.         } catch (\Exception $e) {
  161.             $this->logger->error(
  162.                 "Could not send mail:\n"
  163.                 . $e->getMessage() . "\n"
  164.                 . 'Error Code:' . $e->getCode() . "\n"
  165.                 . "Template data: \n"
  166.                 . json_encode($data->all()) . "\n"
  167.             );
  168.         }
  169.         if ($injectedTranslator) {
  170.             $this->translator->resetInjection();
  171.         }
  172.     }
  173.     private function updateMailTemplateType(FlowEvent $event, MailAware $mailAware, MailTemplateEntity $mailTemplate): void
  174.     {
  175.         if (!$mailTemplate->getMailTemplateTypeId()) {
  176.             return;
  177.         }
  178.         if (!$this->updateMailTemplate) {
  179.             return;
  180.         }
  181.         $mailTemplateTypeTranslation = $this->connection->fetchOne(
  182.             'SELECT 1 FROM mail_template_type_translation WHERE language_id = :languageId AND mail_template_type_id =:mailTemplateTypeId',
  183.             [
  184.                 'languageId' => Uuid::fromHexToBytes($event->getContext()->getLanguageId()),
  185.                 'mailTemplateTypeId' => Uuid::fromHexToBytes($mailTemplate->getMailTemplateTypeId()),
  186.             ]
  187.         );
  188.         if (!$mailTemplateTypeTranslation) {
  189.             // Don't throw errors if this fails // Fix with NEXT-15475
  190.             $this->logger->error(
  191.                 "Could not update mail template type, because translation for this language does not exits:\n"
  192.                 . 'Flow id: ' . $event->getFlowState()->flowId . "\n"
  193.                 . 'Sequence id: ' . $event->getFlowState()->sequenceId
  194.             );
  195.             return;
  196.         }
  197.         $this->mailTemplateTypeRepository->update([[
  198.             'id' => $mailTemplate->getMailTemplateTypeId(),
  199.             'templateData' => $this->getTemplateData($mailAware),
  200.         ]], $mailAware->getContext());
  201.     }
  202.     private function getMailTemplate(string $id, Context $context): ?MailTemplateEntity
  203.     {
  204.         $criteria = new Criteria([$id]);
  205.         $criteria->setTitle('send-mail::load-mail-template');
  206.         $criteria->addAssociation('media.media');
  207.         $criteria->setLimit(1);
  208.         return $this->mailTemplateRepository
  209.             ->search($criteria, $context)
  210.             ->first();
  211.     }
  212.     /**
  213.      * @throws MailEventConfigurationException
  214.      */
  215.     private function getTemplateData(MailAware $event): array
  216.     {
  217.         $data = [];
  218.         foreach (array_keys($event::getAvailableData()->toArray()) as $key) {
  219.             $getter = 'get' . ucfirst($key);
  220.             if (!method_exists($event, $getter)) {
  221.                 throw new MailEventConfigurationException('Data for ' . $key . ' not available.', \get_class($event));
  222.             }
  223.             $data[$key] = $event->$getter();
  224.         }
  225.         return $data;
  226.     }
  227.     private function buildAttachments(MailAware $mailEvent, MailTemplateEntity $mailTemplate, MailSendSubscriberConfig $extensions, array $eventConfig): array
  228.     {
  229.         $attachments = [];
  230.         if ($mailTemplate->getMedia() !== null) {
  231.             foreach ($mailTemplate->getMedia() as $mailTemplateMedia) {
  232.                 if ($mailTemplateMedia->getMedia() === null) {
  233.                     continue;
  234.                 }
  235.                 if ($mailTemplateMedia->getLanguageId() !== null && $mailTemplateMedia->getLanguageId() !== $mailEvent->getContext()->getLanguageId()) {
  236.                     continue;
  237.                 }
  238.                 $attachments[] = $this->mediaService->getAttachment(
  239.                     $mailTemplateMedia->getMedia(),
  240.                     $mailEvent->getContext()
  241.                 );
  242.             }
  243.         }
  244.         if (!empty($extensions->getMediaIds())) {
  245.             $criteria = new Criteria($extensions->getMediaIds());
  246.             $criteria->setTitle('send-mail::load-media');
  247.             $entities = $this->mediaRepository->search($criteria, $mailEvent->getContext());
  248.             foreach ($entities as $media) {
  249.                 $attachments[] = $this->mediaService->getAttachment($media, $mailEvent->getContext());
  250.             }
  251.         }
  252.         if (!empty($extensions->getDocumentIds())) {
  253.             $attachments = $this->buildOrderAttachments($extensions->getDocumentIds(), $attachments, $mailEvent->getContext());
  254.         }
  255.         if (empty($eventConfig['documentTypeIds']) || !\is_array($eventConfig['documentTypeIds']) || !$mailEvent instanceof OrderAware) {
  256.             return $attachments;
  257.         }
  258.         return $this->buildFlowSettingAttachments($mailEvent->getOrderId(), $eventConfig['documentTypeIds'], $attachments, $mailEvent->getContext());
  259.     }
  260.     private function injectTranslator(MailAware $event): bool
  261.     {
  262.         if ($event->getSalesChannelId() === null) {
  263.             return false;
  264.         }
  265.         if ($this->translator->getSnippetSetId() !== null) {
  266.             return false;
  267.         }
  268.         $this->translator->injectSettings(
  269.             $event->getSalesChannelId(),
  270.             $event->getContext()->getLanguageId(),
  271.             $this->languageLocaleProvider->getLocaleForLanguageId($event->getContext()->getLanguageId()),
  272.             $event->getContext()
  273.         );
  274.         return true;
  275.     }
  276.     private function getRecipients(array $recipients, MailAware $mailEvent): array
  277.     {
  278.         switch ($recipients['type']) {
  279.             case self::RECIPIENT_CONFIG_CUSTOM:
  280.                 return $recipients['data'];
  281.             case self::RECIPIENT_CONFIG_ADMIN:
  282.                 $admins = $this->connection->fetchAllAssociative(
  283.                     'SELECT first_name, last_name, email FROM user WHERE admin = true'
  284.                 );
  285.                 $emails = [];
  286.                 foreach ($admins as $admin) {
  287.                     $emails[$admin['email']] = $admin['first_name'] . ' ' . $admin['last_name'];
  288.                 }
  289.                 return $emails;
  290.             case self::RECIPIENT_CONFIG_CONTACT_FORM_MAIL:
  291.                 if (!$mailEvent instanceof ContactFormEvent) {
  292.                     return [];
  293.                 }
  294.                 $data = $mailEvent->getContactFormData();
  295.                 if (!\array_key_exists('email', $data)) {
  296.                     return [];
  297.                 }
  298.                 return [$data['email'] => ($data['firstName'] ?? '') . ' ' . ($data['lastName'] ?? '')];
  299.             default:
  300.                 return $mailEvent->getMailStruct()->getRecipients();
  301.         }
  302.     }
  303.     private function buildOrderAttachments(array $documentIds, array $attachments, Context $context): array
  304.     {
  305.         $criteria = new Criteria($documentIds);
  306.         $criteria->setTitle('send-mail::load-attachments');
  307.         $criteria->addAssociation('documentMediaFile');
  308.         $criteria->addAssociation('documentType');
  309.         $entities = $this->documentRepository->search($criteria, $context);
  310.         return $this->mappingAttachmentsInfo($entities, $attachments, $context);
  311.     }
  312.     private function buildFlowSettingAttachments(string $orderId, array $documentTypeIds, array $attachments, Context $context): array
  313.     {
  314.         $documents = $this->connection->fetchAllAssociative(
  315.             'SELECT
  316.                 LOWER(hex(`document`.`document_type_id`)) as doc_type,
  317.                 LOWER(hex(`document`.`id`)) as doc_id,
  318.                 `document`.`created_at` as newest_date
  319.             FROM
  320.                 `document`
  321.             WHERE
  322.                 HEX(`document`.`order_id`) = :orderId
  323.                 AND HEX(`document`.`document_type_id`) IN (:documentTypeIds)
  324.             ORDER BY `document`.`created_at` DESC',
  325.             [
  326.                 'orderId' => $orderId,
  327.                 'documentTypeIds' => $documentTypeIds,
  328.             ],
  329.             [
  330.                 'documentTypeIds' => Connection::PARAM_STR_ARRAY,
  331.             ]
  332.         );
  333.         $documentsGroupByType = FetchModeHelper::group($documents);
  334.         foreach ($documentsGroupByType as $document) {
  335.             $documentIds[] = array_shift($document)['doc_id'];
  336.         }
  337.         if (empty($documentIds)) {
  338.             return $attachments;
  339.         }
  340.         $criteria = new Criteria($documentIds);
  341.         $criteria->setTitle('send-mail::load-flow-documents');
  342.         $criteria->addAssociations(['documentMediaFile', 'documentType']);
  343.         $entities = $this->documentRepository->search($criteria, $context);
  344.         return $this->mappingAttachmentsInfo($entities, $attachments, $context);
  345.     }
  346.     private function mappingAttachmentsInfo(EntityCollection $entities, array $attachments, Context $context): array
  347.     {
  348.         foreach ($entities as $document) {
  349.             $documentId = $document->getId();
  350.             $document = $this->documentService->getDocument($document, $context);
  351.             $attachments[] = [
  352.                 'id' => $documentId,
  353.                 'content' => $document->getFileBlob(),
  354.                 'fileName' => $document->getFilename(),
  355.                 'mimeType' => $document->getContentType(),
  356.             ];
  357.         }
  358.         return $attachments;
  359.     }
  360. }