platform/src/Core/Content/Flow/Dispatching/Action/SendMailAction.php line 111

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