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

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\DocumentCollection;
  6. use Shopware\Core\Checkout\Document\DocumentService;
  7. use Shopware\Core\Checkout\Document\Service\DocumentGenerator;
  8. use Shopware\Core\Content\ContactForm\Event\ContactFormEvent;
  9. use Shopware\Core\Content\Flow\Dispatching\DelayableAction;
  10. use Shopware\Core\Content\Flow\Dispatching\StorableFlow;
  11. use Shopware\Core\Content\Flow\Events\FlowSendMailActionEvent;
  12. use Shopware\Core\Content\Mail\Service\AbstractMailService;
  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\Content\Media\MediaCollection;
  19. use Shopware\Core\Content\Media\MediaEntity;
  20. use Shopware\Core\Content\Media\MediaService;
  21. use Shopware\Core\Defaults;
  22. use Shopware\Core\Framework\Adapter\Translation\Translator;
  23. use Shopware\Core\Framework\Context;
  24. use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\FetchModeHelper;
  25. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  26. use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
  27. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  28. use Shopware\Core\Framework\Event\FlowEvent;
  29. use Shopware\Core\Framework\Event\MailAware;
  30. use Shopware\Core\Framework\Event\OrderAware;
  31. use Shopware\Core\Framework\Feature;
  32. use Shopware\Core\Framework\Uuid\Uuid;
  33. use Shopware\Core\Framework\Validation\DataBag\DataBag;
  34. use Shopware\Core\System\Locale\LanguageLocaleCodeProvider;
  35. use Symfony\Contracts\EventDispatcher\Event;
  36. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  37. /**
  38.  * @deprecated tag:v6.5.0 - reason:remove-subscriber - FlowActions won't be executed over the event system anymore,
  39.  * therefore the actions won't implement the EventSubscriberInterface anymore.
  40.  */
  41. class SendMailAction extends FlowAction implements DelayableAction
  42. {
  43.     public const ACTION_NAME MailTemplateActions::MAIL_TEMPLATE_MAIL_SEND_ACTION;
  44.     public const MAIL_CONFIG_EXTENSION 'mail-attachments';
  45.     private const RECIPIENT_CONFIG_ADMIN 'admin';
  46.     private const RECIPIENT_CONFIG_CUSTOM 'custom';
  47.     private const RECIPIENT_CONFIG_CONTACT_FORM_MAIL 'contactFormMail';
  48.     private EntityRepositoryInterface $mailTemplateRepository;
  49.     private MediaService $mediaService;
  50.     private EntityRepositoryInterface $mediaRepository;
  51.     private EntityRepositoryInterface $documentRepository;
  52.     private LoggerInterface $logger;
  53.     private AbstractMailService $emailService;
  54.     private EventDispatcherInterface $eventDispatcher;
  55.     private EntityRepositoryInterface $mailTemplateTypeRepository;
  56.     private Translator $translator;
  57.     private Connection $connection;
  58.     private LanguageLocaleCodeProvider $languageLocaleProvider;
  59.     private bool $updateMailTemplate;
  60.     private DocumentGenerator $documentGenerator;
  61.     private DocumentService $documentService;
  62.     /**
  63.      * @internal
  64.      */
  65.     public function __construct(
  66.         AbstractMailService $emailService,
  67.         EntityRepositoryInterface $mailTemplateRepository,
  68.         MediaService $mediaService,
  69.         EntityRepositoryInterface $mediaRepository,
  70.         EntityRepositoryInterface $documentRepository,
  71.         DocumentService $documentService,
  72.         DocumentGenerator $documentGenerator,
  73.         LoggerInterface $logger,
  74.         EventDispatcherInterface $eventDispatcher,
  75.         EntityRepositoryInterface $mailTemplateTypeRepository,
  76.         Translator $translator,
  77.         Connection $connection,
  78.         LanguageLocaleCodeProvider $languageLocaleProvider,
  79.         bool $updateMailTemplate
  80.     ) {
  81.         $this->mailTemplateRepository $mailTemplateRepository;
  82.         $this->mediaService $mediaService;
  83.         $this->mediaRepository $mediaRepository;
  84.         $this->documentRepository $documentRepository;
  85.         $this->logger $logger;
  86.         $this->emailService $emailService;
  87.         $this->eventDispatcher $eventDispatcher;
  88.         $this->mailTemplateTypeRepository $mailTemplateTypeRepository;
  89.         $this->translator $translator;
  90.         $this->connection $connection;
  91.         $this->languageLocaleProvider $languageLocaleProvider;
  92.         $this->updateMailTemplate $updateMailTemplate;
  93.         $this->documentGenerator $documentGenerator;
  94.         $this->documentService $documentService;
  95.     }
  96.     public static function getName(): string
  97.     {
  98.         return 'action.mail.send';
  99.     }
  100.     /**
  101.      * @deprecated tag:v6.5.0 - reason:remove-subscriber - Will be removed
  102.      */
  103.     public static function getSubscribedEvents(): array
  104.     {
  105.         if (Feature::isActive('v6.5.0.0')) {
  106.             return [];
  107.         }
  108.         return [
  109.             self::getName() => 'handle',
  110.         ];
  111.     }
  112.     /**
  113.      * @return array<string>
  114.      */
  115.     public function requirements(): array
  116.     {
  117.         return [MailAware::class];
  118.     }
  119.     /**
  120.      * @deprecated tag:v6.5.0 Will be removed, implement handleFlow instead
  121.      *
  122.      * @throws MailEventConfigurationException
  123.      * @throws SalesChannelNotFoundException
  124.      * @throws InconsistentCriteriaIdsException
  125.      */
  126.     public function handle(Event $event): void
  127.     {
  128.         Feature::triggerDeprecationOrThrow(
  129.             'v6.5.0.0',
  130.             Feature::deprecatedMethodMessage(__CLASS____METHOD__'v6.5.0.0')
  131.         );
  132.         if (!$event instanceof FlowEvent) {
  133.             return;
  134.         }
  135.         $mailEvent $event->getEvent();
  136.         $extension $event->getContext()->getExtension(self::MAIL_CONFIG_EXTENSION);
  137.         if (!$extension instanceof MailSendSubscriberConfig) {
  138.             $extension = new MailSendSubscriberConfig(false, [], []);
  139.         }
  140.         if ($extension->skip()) {
  141.             return;
  142.         }
  143.         if (!$mailEvent instanceof MailAware) {
  144.             throw new MailEventConfigurationException('Not an instance of MailAware', \get_class($mailEvent));
  145.         }
  146.         $eventConfig $event->getConfig();
  147.         if (empty($eventConfig['recipient'])) {
  148.             throw new MailEventConfigurationException('The recipient value in the flow action configuration is missing.', \get_class($mailEvent));
  149.         }
  150.         if (!isset($eventConfig['mailTemplateId'])) {
  151.             return;
  152.         }
  153.         $mailTemplate $this->getMailTemplate($eventConfig['mailTemplateId'], $event->getContext());
  154.         if ($mailTemplate === null) {
  155.             return;
  156.         }
  157.         $injectedTranslator $this->injectTranslator($mailEvent->getContext(), $mailEvent->getSalesChannelId());
  158.         $data = new DataBag();
  159.         $contactFormData = [];
  160.         if ($mailEvent instanceof ContactFormEvent) {
  161.             $contactFormData $mailEvent->getContactFormData();
  162.         }
  163.         $recipients $this->getRecipients($eventConfig['recipient'], $mailEvent->getMailStruct()->getRecipients(), $contactFormData);
  164.         if (empty($recipients)) {
  165.             return;
  166.         }
  167.         $data->set('recipients'$recipients);
  168.         $data->set('senderName'$mailTemplate->getTranslation('senderName'));
  169.         $data->set('salesChannelId'$mailEvent->getSalesChannelId());
  170.         $data->set('templateId'$mailTemplate->getId());
  171.         $data->set('customFields'$mailTemplate->getCustomFields());
  172.         $data->set('contentHtml'$mailTemplate->getTranslation('contentHtml'));
  173.         $data->set('contentPlain'$mailTemplate->getTranslation('contentPlain'));
  174.         $data->set('subject'$mailTemplate->getTranslation('subject'));
  175.         $data->set('mediaIds', []);
  176.         $attachments array_unique($this->buildAttachments(
  177.             $event->getContext(),
  178.             $mailTemplate,
  179.             $extension,
  180.             $eventConfig,
  181.             $mailEvent instanceof OrderAware $mailEvent->getOrderId() : null
  182.         ), \SORT_REGULAR);
  183.         if (!empty($attachments)) {
  184.             $data->set('binAttachments'$attachments);
  185.         }
  186.         $this->eventDispatcher->dispatch(new FlowSendMailActionEvent($data$mailTemplate$event));
  187.         if ($data->has('templateId')) {
  188.             $this->updateMailTemplateType(
  189.                 $event->getContext(),
  190.                 $event,
  191.                 $this->getTemplateData($mailEvent),
  192.                 $mailTemplate
  193.             );
  194.         }
  195.         $this->send($data$event->getContext(), $this->getTemplateData($mailEvent), $attachments$extension$injectedTranslator);
  196.     }
  197.     /**
  198.      * @throws MailEventConfigurationException
  199.      * @throws SalesChannelNotFoundException
  200.      * @throws InconsistentCriteriaIdsException
  201.      */
  202.     public function handleFlow(StorableFlow $flow): void
  203.     {
  204.         $extension $flow->getContext()->getExtension(self::MAIL_CONFIG_EXTENSION);
  205.         if (!$extension instanceof MailSendSubscriberConfig) {
  206.             $extension = new MailSendSubscriberConfig(false, [], []);
  207.         }
  208.         if ($extension->skip()) {
  209.             return;
  210.         }
  211.         if (!$flow->hasStore(MailAware::MAIL_STRUCT) || !$flow->hasStore(MailAware::SALES_CHANNEL_ID)) {
  212.             throw new MailEventConfigurationException('Not have data from MailAware', \get_class($flow));
  213.         }
  214.         $eventConfig $flow->getConfig();
  215.         if (empty($eventConfig['recipient'])) {
  216.             throw new MailEventConfigurationException('The recipient value in the flow action configuration is missing.', \get_class($flow));
  217.         }
  218.         if (!isset($eventConfig['mailTemplateId'])) {
  219.             return;
  220.         }
  221.         $mailTemplate $this->getMailTemplate($eventConfig['mailTemplateId'], $flow->getContext());
  222.         if ($mailTemplate === null) {
  223.             return;
  224.         }
  225.         $injectedTranslator $this->injectTranslator($flow->getContext(), $flow->getStore(MailAware::SALES_CHANNEL_ID));
  226.         $data = new DataBag();
  227.         $recipients $this->getRecipients(
  228.             $eventConfig['recipient'],
  229.             $flow->getStore(MailAware::MAIL_STRUCT)['recipients'],
  230.             $flow->getStore('contactFormData', []),
  231.         );
  232.         if (empty($recipients)) {
  233.             return;
  234.         }
  235.         $data->set('recipients'$recipients);
  236.         $data->set('senderName'$mailTemplate->getTranslation('senderName'));
  237.         $data->set('salesChannelId'$flow->getStore(MailAware::SALES_CHANNEL_ID));
  238.         $data->set('templateId'$mailTemplate->getId());
  239.         $data->set('customFields'$mailTemplate->getCustomFields());
  240.         $data->set('contentHtml'$mailTemplate->getTranslation('contentHtml'));
  241.         $data->set('contentPlain'$mailTemplate->getTranslation('contentPlain'));
  242.         $data->set('subject'$mailTemplate->getTranslation('subject'));
  243.         $data->set('mediaIds', []);
  244.         $attachments array_unique($this->buildAttachments(
  245.             $flow->getContext(),
  246.             $mailTemplate,
  247.             $extension,
  248.             $eventConfig,
  249.             $flow->getStore(OrderAware::ORDER_ID),
  250.         ), \SORT_REGULAR);
  251.         if (!empty($attachments)) {
  252.             $data->set('binAttachments'$attachments);
  253.         }
  254.         $this->eventDispatcher->dispatch(new FlowSendMailActionEvent($data$mailTemplate$flow));
  255.         if ($data->has('templateId')) {
  256.             $this->updateMailTemplateType(
  257.                 $flow->getContext(),
  258.                 $flow,
  259.                 $flow->data(),
  260.                 $mailTemplate
  261.             );
  262.         }
  263.         $this->send($data$flow->getContext(), $flow->data(), $attachments$extension$injectedTranslator);
  264.     }
  265.     /**
  266.      * @param array<string, mixed> $templateData
  267.      * @param array<mixed, mixed> $attachments
  268.      */
  269.     private function send(DataBag $dataContext $context, array $templateData, array $attachmentsMailSendSubscriberConfig $extensionbool $injectedTranslator): void
  270.     {
  271.         try {
  272.             $this->emailService->send(
  273.                 $data->all(),
  274.                 $context,
  275.                 $templateData
  276.             );
  277.             $documentAttachments array_filter($attachments, function (array $attachment) use ($extension) {
  278.                 return \array_key_exists('id'$attachment) && \in_array($attachment['id'], $extension->getDocumentIds(), true);
  279.             });
  280.             $documentAttachments array_column($documentAttachments'id');
  281.             if (!empty($documentAttachments)) {
  282.                 $this->connection->executeStatement(
  283.                     'UPDATE `document` SET `updated_at` = :now, `sent` = 1 WHERE `id` IN (:ids)',
  284.                     ['ids' => Uuid::fromHexToBytesList($documentAttachments), 'now' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT)],
  285.                     ['ids' => Connection::PARAM_STR_ARRAY]
  286.                 );
  287.             }
  288.         } catch (\Exception $e) {
  289.             $this->logger->error(
  290.                 "Could not send mail:\n"
  291.                 $e->getMessage() . "\n"
  292.                 'Error Code:' $e->getCode() . "\n"
  293.                 "Template data: \n"
  294.                 json_encode($data->all()) . "\n"
  295.             );
  296.         }
  297.         if ($injectedTranslator) {
  298.             $this->translator->resetInjection();
  299.         }
  300.     }
  301.     /**
  302.      * @param FlowEvent|StorableFlow $event
  303.      * @param array<string, mixed> $templateData
  304.      */
  305.     private function updateMailTemplateType(
  306.         Context $context,
  307.         $event,
  308.         array $templateData,
  309.         MailTemplateEntity $mailTemplate
  310.     ): void {
  311.         if (!$mailTemplate->getMailTemplateTypeId()) {
  312.             return;
  313.         }
  314.         if (!$this->updateMailTemplate) {
  315.             return;
  316.         }
  317.         $mailTemplateTypeTranslation $this->connection->fetchOne(
  318.             'SELECT 1 FROM mail_template_type_translation WHERE language_id = :languageId AND mail_template_type_id =:mailTemplateTypeId',
  319.             [
  320.                 'languageId' => Uuid::fromHexToBytes($context->getLanguageId()),
  321.                 'mailTemplateTypeId' => Uuid::fromHexToBytes($mailTemplate->getMailTemplateTypeId()),
  322.             ]
  323.         );
  324.         if (!$mailTemplateTypeTranslation) {
  325.             // Don't throw errors if this fails // Fix with NEXT-15475
  326.             $this->logger->error(
  327.                 "Could not update mail template type, because translation for this language does not exits:\n"
  328.                 'Flow id: ' $event->getFlowState()->flowId "\n"
  329.                 'Sequence id: ' $event->getFlowState()->getSequenceId()
  330.             );
  331.             return;
  332.         }
  333.         $this->mailTemplateTypeRepository->update([[
  334.             'id' => $mailTemplate->getMailTemplateTypeId(),
  335.             'templateData' => $templateData,
  336.         ]], $context);
  337.     }
  338.     private function getMailTemplate(string $idContext $context): ?MailTemplateEntity
  339.     {
  340.         $criteria = new Criteria([$id]);
  341.         $criteria->setTitle('send-mail::load-mail-template');
  342.         $criteria->addAssociation('media.media');
  343.         $criteria->setLimit(1);
  344.         return $this->mailTemplateRepository
  345.             ->search($criteria$context)
  346.             ->first();
  347.     }
  348.     /**
  349.      * @throws MailEventConfigurationException
  350.      *
  351.      * @return array<string, mixed>
  352.      */
  353.     private function getTemplateData(MailAware $event): array
  354.     {
  355.         $data = [];
  356.         foreach (array_keys($event::getAvailableData()->toArray()) as $key) {
  357.             $getter 'get' ucfirst($key);
  358.             if (!method_exists($event$getter)) {
  359.                 throw new MailEventConfigurationException('Data for ' $key ' not available.', \get_class($event));
  360.             }
  361.             $data[$key] = $event->$getter();
  362.         }
  363.         return $data;
  364.     }
  365.     /**
  366.      * @param array<string, mixed> $eventConfig
  367.      *
  368.      * @return array<mixed, mixed>
  369.      */
  370.     private function buildAttachments(
  371.         Context $context,
  372.         MailTemplateEntity $mailTemplate,
  373.         MailSendSubscriberConfig $extensions,
  374.         array $eventConfig,
  375.         ?string $orderId
  376.     ): array {
  377.         $attachments = [];
  378.         if ($mailTemplate->getMedia() !== null) {
  379.             foreach ($mailTemplate->getMedia() as $mailTemplateMedia) {
  380.                 if ($mailTemplateMedia->getMedia() === null) {
  381.                     continue;
  382.                 }
  383.                 if ($mailTemplateMedia->getLanguageId() !== null && $mailTemplateMedia->getLanguageId() !== $context->getLanguageId()) {
  384.                     continue;
  385.                 }
  386.                 $attachments[] = $this->mediaService->getAttachment(
  387.                     $mailTemplateMedia->getMedia(),
  388.                     $context
  389.                 );
  390.             }
  391.         }
  392.         if (!empty($extensions->getMediaIds())) {
  393.             $criteria = new Criteria($extensions->getMediaIds());
  394.             $criteria->setTitle('send-mail::load-media');
  395.             /** @var MediaCollection<MediaEntity> $entities */
  396.             $entities $this->mediaRepository->search($criteria$context);
  397.             foreach ($entities as $media) {
  398.                 $attachments[] = $this->mediaService->getAttachment($media$context);
  399.             }
  400.         }
  401.         $documentIds $extensions->getDocumentIds();
  402.         if (!empty($eventConfig['documentTypeIds']) && \is_array($eventConfig['documentTypeIds']) && $orderId) {
  403.             $latestDocuments $this->getLatestDocumentsOfTypes($orderId$eventConfig['documentTypeIds']);
  404.             $documentIds array_unique(array_merge($documentIds$latestDocuments));
  405.         }
  406.         if (!empty($documentIds)) {
  407.             $extensions->setDocumentIds($documentIds);
  408.             if (Feature::isActive('v6.5.0.0')) {
  409.                 $attachments $this->mappingAttachments($documentIds$attachments$context);
  410.             } else {
  411.                 $attachments $this->buildOrderAttachments($documentIds$attachments$context);
  412.             }
  413.         }
  414.         return $attachments;
  415.     }
  416.     private function injectTranslator(Context $context, ?string $salesChannelId): bool
  417.     {
  418.         if ($salesChannelId === null) {
  419.             return false;
  420.         }
  421.         if ($this->translator->getSnippetSetId() !== null) {
  422.             return false;
  423.         }
  424.         $this->translator->injectSettings(
  425.             $salesChannelId,
  426.             $context->getLanguageId(),
  427.             $this->languageLocaleProvider->getLocaleForLanguageId($context->getLanguageId()),
  428.             $context
  429.         );
  430.         return true;
  431.     }
  432.     /**
  433.      * @param array<string, mixed> $recipients
  434.      * @param array<string, mixed> $mailStructRecipients
  435.      * @param array<int|string, mixed> $contactFormData
  436.      *
  437.      * @return array<int|string, string>
  438.      */
  439.     private function getRecipients(array $recipients, array $mailStructRecipients, array $contactFormData): array
  440.     {
  441.         switch ($recipients['type']) {
  442.             case self::RECIPIENT_CONFIG_CUSTOM:
  443.                 return $recipients['data'];
  444.             case self::RECIPIENT_CONFIG_ADMIN:
  445.                 $admins $this->connection->fetchAllAssociative(
  446.                     'SELECT first_name, last_name, email FROM user WHERE admin = true'
  447.                 );
  448.                 $emails = [];
  449.                 foreach ($admins as $admin) {
  450.                     $emails[$admin['email']] = $admin['first_name'] . ' ' $admin['last_name'];
  451.                 }
  452.                 return $emails;
  453.             case self::RECIPIENT_CONFIG_CONTACT_FORM_MAIL:
  454.                 if (empty($contactFormData)) {
  455.                     return [];
  456.                 }
  457.                 if (!\array_key_exists('email'$contactFormData)) {
  458.                     return [];
  459.                 }
  460.                 return [$contactFormData['email'] => ($contactFormData['firstName'] ?? '') . ' ' . ($contactFormData['lastName'] ?? '')];
  461.             default:
  462.                 return $mailStructRecipients;
  463.         }
  464.     }
  465.     /**
  466.      * @param array<string> $documentIds
  467.      * @param array<mixed, mixed> $attachments
  468.      *
  469.      * @return array<mixed, mixed>
  470.      */
  471.     private function buildOrderAttachments(array $documentIds, array $attachmentsContext $context): array
  472.     {
  473.         $criteria = new Criteria($documentIds);
  474.         $criteria->setTitle('send-mail::load-attachments');
  475.         $criteria->addAssociation('documentMediaFile');
  476.         $criteria->addAssociation('documentType');
  477.         /** @var DocumentCollection $documents */
  478.         $documents $this->documentRepository->search($criteria$context)->getEntities();
  479.         return $this->mappingAttachmentsInfo($documents$attachments$context);
  480.     }
  481.     /**
  482.      * @param array<string> $documentTypeIds
  483.      *
  484.      * @return array<string>
  485.      */
  486.     private function getLatestDocumentsOfTypes(string $orderId, array $documentTypeIds): array
  487.     {
  488.         $documents $this->connection->fetchAllAssociative(
  489.             'SELECT
  490.                 LOWER(hex(`document`.`document_type_id`)) as doc_type,
  491.                 LOWER(hex(`document`.`id`)) as doc_id,
  492.                 `document`.`created_at` as newest_date
  493.             FROM
  494.                 `document`
  495.             WHERE
  496.                 HEX(`document`.`order_id`) = :orderId
  497.                 AND HEX(`document`.`document_type_id`) IN (:documentTypeIds)
  498.             ORDER BY `document`.`created_at` DESC',
  499.             [
  500.                 'orderId' => $orderId,
  501.                 'documentTypeIds' => $documentTypeIds,
  502.             ],
  503.             [
  504.                 'documentTypeIds' => Connection::PARAM_STR_ARRAY,
  505.             ]
  506.         );
  507.         $documentsGroupByType FetchModeHelper::group($documents);
  508.         $documentIds = [];
  509.         foreach ($documentsGroupByType as $document) {
  510.             $documentIds[] = array_shift($document)['doc_id'];
  511.         }
  512.         return $documentIds;
  513.     }
  514.     /**
  515.      * @param array<mixed, mixed> $attachments
  516.      *
  517.      * @return array<mixed, mixed>
  518.      */
  519.     private function mappingAttachmentsInfo(DocumentCollection $documents, array $attachmentsContext $context): array
  520.     {
  521.         foreach ($documents as $document) {
  522.             $documentId $document->getId();
  523.             $document $this->documentService->getDocument($document$context);
  524.             $attachments[] = [
  525.                 'id' => $documentId,
  526.                 'content' => $document->getFileBlob(),
  527.                 'fileName' => $document->getFilename(),
  528.                 'mimeType' => $document->getContentType(),
  529.             ];
  530.         }
  531.         return $attachments;
  532.     }
  533.     /**
  534.      * @param array<string> $documentIds
  535.      * @param array<mixed, mixed> $attachments
  536.      *
  537.      * @return array<mixed, mixed>
  538.      */
  539.     private function mappingAttachments(array $documentIds, array $attachmentsContext $context): array
  540.     {
  541.         foreach ($documentIds as $documentId) {
  542.             $document $this->documentGenerator->readDocument($documentId$context);
  543.             if ($document === null) {
  544.                 continue;
  545.             }
  546.             $attachments[] = [
  547.                 'id' => $documentId,
  548.                 'content' => $document->getContent(),
  549.                 'fileName' => $document->getName(),
  550.                 'mimeType' => $document->getContentType(),
  551.             ];
  552.         }
  553.         return $attachments;
  554.     }
  555. }