vendor/shopware/core/Content/ProductExport/Service/ProductExportGenerator.php line 234

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\ProductExport\Service;
  3. use Doctrine\DBAL\Connection;
  4. use Monolog\Logger;
  5. use Shopware\Core\Content\Product\ProductDefinition;
  6. use Shopware\Core\Content\Product\ProductEntity;
  7. use Shopware\Core\Content\ProductExport\Event\ProductExportChangeEncodingEvent;
  8. use Shopware\Core\Content\ProductExport\Event\ProductExportLoggingEvent;
  9. use Shopware\Core\Content\ProductExport\Event\ProductExportProductCriteriaEvent;
  10. use Shopware\Core\Content\ProductExport\Event\ProductExportRenderBodyContextEvent;
  11. use Shopware\Core\Content\ProductExport\Exception\EmptyExportException;
  12. use Shopware\Core\Content\ProductExport\Exception\RenderProductException;
  13. use Shopware\Core\Content\ProductExport\ProductExportEntity;
  14. use Shopware\Core\Content\ProductExport\Struct\ExportBehavior;
  15. use Shopware\Core\Content\ProductExport\Struct\ProductExportResult;
  16. use Shopware\Core\Content\ProductStream\Service\ProductStreamBuilderInterface;
  17. use Shopware\Core\Content\Seo\SeoUrlPlaceholderHandlerInterface;
  18. use Shopware\Core\Framework\Adapter\Translation\Translator;
  19. use Shopware\Core\Framework\Adapter\Twig\TwigVariableParser;
  20. use Shopware\Core\Framework\DataAbstractionLayer\Dbal\Common\SalesChannelRepositoryIterator;
  21. use Shopware\Core\Framework\DataAbstractionLayer\Dbal\EntityDefinitionQueryHelper;
  22. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  23. use Shopware\Core\Framework\Log\Package;
  24. use Shopware\Core\Framework\Uuid\Uuid;
  25. use Shopware\Core\System\Locale\LanguageLocaleCodeProvider;
  26. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextPersister;
  27. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService;
  28. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceInterface;
  29. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceParameters;
  30. use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepositoryInterface;
  31. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  32. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  33. #[Package('inventory')]
  34. class ProductExportGenerator implements ProductExportGeneratorInterface
  35. {
  36.     private ProductStreamBuilderInterface $productStreamBuilder;
  37.     private int $readBufferSize;
  38.     private SalesChannelRepositoryInterface $productRepository;
  39.     private ProductExportRendererInterface $productExportRender;
  40.     private EventDispatcherInterface $eventDispatcher;
  41.     private ProductExportValidatorInterface $productExportValidator;
  42.     private SalesChannelContextServiceInterface $salesChannelContextService;
  43.     private Translator $translator;
  44.     private SalesChannelContextPersister $contextPersister;
  45.     private Connection $connection;
  46.     private SeoUrlPlaceholderHandlerInterface $seoUrlPlaceholderHandler;
  47.     private TwigVariableParser $twigVariableParser;
  48.     private ProductDefinition $productDefinition;
  49.     private LanguageLocaleCodeProvider $languageLocaleProvider;
  50.     /**
  51.      * @internal
  52.      */
  53.     public function __construct(
  54.         ProductStreamBuilderInterface $productStreamBuilder,
  55.         SalesChannelRepositoryInterface $productRepository,
  56.         ProductExportRendererInterface $productExportRender,
  57.         EventDispatcherInterface $eventDispatcher,
  58.         ProductExportValidatorInterface $productExportValidator,
  59.         SalesChannelContextServiceInterface $salesChannelContextService,
  60.         Translator $translator,
  61.         SalesChannelContextPersister $contextPersister,
  62.         Connection $connection,
  63.         int $readBufferSize,
  64.         SeoUrlPlaceholderHandlerInterface $seoUrlPlaceholderHandler,
  65.         TwigVariableParser $twigVariableParser,
  66.         ProductDefinition $productDefinition,
  67.         LanguageLocaleCodeProvider $languageLocaleProvider
  68.     ) {
  69.         $this->productStreamBuilder $productStreamBuilder;
  70.         $this->productRepository $productRepository;
  71.         $this->productExportRender $productExportRender;
  72.         $this->eventDispatcher $eventDispatcher;
  73.         $this->productExportValidator $productExportValidator;
  74.         $this->salesChannelContextService $salesChannelContextService;
  75.         $this->translator $translator;
  76.         $this->contextPersister $contextPersister;
  77.         $this->connection $connection;
  78.         $this->readBufferSize $readBufferSize;
  79.         $this->seoUrlPlaceholderHandler $seoUrlPlaceholderHandler;
  80.         $this->twigVariableParser $twigVariableParser;
  81.         $this->productDefinition $productDefinition;
  82.         $this->languageLocaleProvider $languageLocaleProvider;
  83.     }
  84.     public function generate(ProductExportEntity $productExportExportBehavior $exportBehavior): ?ProductExportResult
  85.     {
  86.         $contextToken Uuid::randomHex();
  87.         $this->contextPersister->save(
  88.             $contextToken,
  89.             [
  90.                 SalesChannelContextService::CURRENCY_ID => $productExport->getCurrencyId(),
  91.             ],
  92.             $productExport->getSalesChannelId()
  93.         );
  94.         $context $this->salesChannelContextService->get(
  95.             new SalesChannelContextServiceParameters(
  96.                 $productExport->getStorefrontSalesChannelId(),
  97.                 $contextToken,
  98.                 $productExport->getSalesChannelDomain()->getLanguageId(),
  99.                 $productExport->getCurrencyId()
  100.             )
  101.         );
  102.         $this->translator->injectSettings(
  103.             $productExport->getStorefrontSalesChannelId(),
  104.             $productExport->getSalesChannelDomain()->getLanguageId(),
  105.             $this->languageLocaleProvider->getLocaleForLanguageId($productExport->getSalesChannelDomain()->getLanguageId()),
  106.             $context->getContext()
  107.         );
  108.         $filters $this->productStreamBuilder->buildFilters(
  109.             $productExport->getProductStreamId(),
  110.             $context->getContext()
  111.         );
  112.         $associations $this->getAssociations($productExport$context);
  113.         $criteria = new Criteria();
  114.         $criteria->setTitle('product-export::products');
  115.         $criteria
  116.             ->addFilter(...$filters)
  117.             ->setOffset($exportBehavior->offset())
  118.             ->setLimit($this->readBufferSize);
  119.         foreach ($associations as $association) {
  120.             $criteria->addAssociation($association);
  121.         }
  122.         $this->eventDispatcher->dispatch(
  123.             new ProductExportProductCriteriaEvent($criteria$productExport$exportBehavior$context)
  124.         );
  125.         $iterator = new SalesChannelRepositoryIterator($this->productRepository$context$criteria);
  126.         $total $iterator->getTotal();
  127.         if ($total === 0) {
  128.             $exception = new EmptyExportException($productExport->getId());
  129.             $loggingEvent = new ProductExportLoggingEvent(
  130.                 $context->getContext(),
  131.                 $exception->getMessage(),
  132.                 Logger::WARNING,
  133.                 $exception
  134.             );
  135.             $this->eventDispatcher->dispatch($loggingEvent);
  136.             $this->translator->resetInjection();
  137.             $this->connection->delete('sales_channel_api_context', ['token' => $contextToken]);
  138.             throw $exception;
  139.         }
  140.         $content '';
  141.         if ($exportBehavior->generateHeader()) {
  142.             $content $this->productExportRender->renderHeader($productExport$context);
  143.         }
  144.         $productContext $this->eventDispatcher->dispatch(
  145.             new ProductExportRenderBodyContextEvent(
  146.                 [
  147.                     'productExport' => $productExport,
  148.                     'context' => $context,
  149.                 ]
  150.             )
  151.         );
  152.         $body '';
  153.         while ($productResult $iterator->fetch()) {
  154.             /** @var ProductEntity $product */
  155.             foreach ($productResult->getEntities() as $product) {
  156.                 $data $productContext->getContext();
  157.                 $data['product'] = $product;
  158.                 if ($productExport->isIncludeVariants() && !$product->getParentId() && $product->getChildCount() > 0) {
  159.                     continue; // Skip main product if variants are included
  160.                 }
  161.                 if (!$productExport->isIncludeVariants() && $product->getParentId()) {
  162.                     continue; // Skip variants unless they are included
  163.                 }
  164.                 $body .= $this->productExportRender->renderBody($productExport$context$data);
  165.             }
  166.             if ($exportBehavior->batchMode()) {
  167.                 break;
  168.             }
  169.         }
  170.         $content .= $this->seoUrlPlaceholderHandler->replace($body$productExport->getSalesChannelDomain()->getUrl(), $context);
  171.         if ($exportBehavior->generateFooter()) {
  172.             $content .= $this->productExportRender->renderFooter($productExport$context);
  173.         }
  174.         /** @var ProductExportChangeEncodingEvent $encodingEvent */
  175.         $encodingEvent $this->eventDispatcher->dispatch(
  176.             new ProductExportChangeEncodingEvent($productExport$contentmb_convert_encoding($content$productExport->getEncoding()))
  177.         );
  178.         $this->translator->resetInjection();
  179.         $this->connection->delete('sales_channel_api_context', ['token' => $contextToken]);
  180.         if (empty($content)) {
  181.             return null;
  182.         }
  183.         return new ProductExportResult(
  184.             $encodingEvent->getEncodedContent(),
  185.             $this->productExportValidator->validate($productExport$encodingEvent->getEncodedContent()),
  186.             $iterator->getTotal()
  187.         );
  188.     }
  189.     private function getAssociations(ProductExportEntity $productExportSalesChannelContext $context): array
  190.     {
  191.         try {
  192.             $variables $this->twigVariableParser->parse((string) $productExport->getBodyTemplate());
  193.         } catch (\Exception $e) {
  194.             $e = new RenderProductException($e->getMessage());
  195.             $loggingEvent = new ProductExportLoggingEvent($context->getContext(), $e->getMessage(), Logger::ERROR$e);
  196.             $this->eventDispatcher->dispatch($loggingEvent);
  197.             throw $e;
  198.         }
  199.         $associations = [];
  200.         foreach ($variables as $variable) {
  201.             $associations[] = EntityDefinitionQueryHelper::getAssociationPath($variable$this->productDefinition);
  202.         }
  203.         return array_filter(array_unique($associations));
  204.     }
  205. }