custom/plugins/CioProductDownloadsTab/src/Subscriber/ProductListingSubscriber.php line 35

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace CioProductDownloadsTab\Subscriber;
  3. use CioProductDownloadsTab\Entity\ProductDownloadDefinition;
  4. use Shopware\Core\Content\Media\MediaEntity;
  5. use Shopware\Core\Content\Product\Events\ProductListingResultEvent;
  6. use Shopware\Core\Content\Product\Events\ProductSearchResultEvent;
  7. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  11. use Shopware\Core\Framework\Struct\ArrayStruct;
  12. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  13. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  14. final class ProductListingSubscriber implements EventSubscriberInterface
  15. {
  16.     private EntityRepository $productDownloadRepository;
  17.     public function __construct(EntityRepository $productDownloadRepository)
  18.     {
  19.         $this->productDownloadRepository = $productDownloadRepository;
  20.     }
  21.     public static function getSubscribedEvents(): array
  22.     {
  23.         return [
  24.             // CIO-AI-Driven: make downloads available in listing/search templates via product.extensions.productDownloads
  25.             ProductListingResultEvent::class => 'onProductListingResult',
  26.             ProductSearchResultEvent::class => 'onProductListingResult',
  27.         ];
  28.     }
  29.     public function onProductListingResult(ProductListingResultEvent $event): void
  30.     {
  31.         $products = $event->getResult()->getEntities();
  32.         if ($products->count() <= 0) {
  33.             return;
  34.         }
  35.         $productIds = [];
  36.         foreach ($products as $product) {
  37.             $productIds[] = $product->getId();
  38.         }
  39.         $downloadsByProductId = $this->getDownloadsByProductIdAndType($productIds, $event->getSalesChannelContext(), ProductDownloadDefinition::TYPE_DOWNLOAD);
  40.         foreach ($products as $product) {
  41.             $downloads = $downloadsByProductId[$product->getId()] ?? [];
  42.             if ($downloads !== []) {
  43.                 $product->addExtension('productDownloads', new ArrayStruct([
  44.                     'downloads' => $downloads,
  45.                 ]));
  46.             }
  47.         }
  48.         $cadDownloadsByProductId = $this->getDownloadsByProductIdAndType($productIds, $event->getSalesChannelContext(), ProductDownloadDefinition::TYPE_CAD);
  49.         foreach ($products as $product) {
  50.             $downloads = $cadDownloadsByProductId[$product->getId()] ?? [];
  51.             if ($downloads !== []) {
  52.                 $product->addExtension('productCadDownloads', new ArrayStruct([
  53.                     'downloads' => $downloads,
  54.                 ]));
  55.             }
  56.         }
  57.     }
  58.     /**
  59.      * @param array<int, string> $productIds
  60.      *
  61.      * @return array<string, array<int, array{displayName: string, fileName: string, url: string|null}>>
  62.      */
  63.     private function getDownloadsByProductIdAndType(array $productIds, SalesChannelContext $context, string $type): array
  64.     {
  65.         if ($productIds === []) {
  66.             return [];
  67.         }
  68.         $criteria = new Criteria();
  69.         $criteria->addFilter(new EqualsAnyFilter('productId', $productIds));
  70.         $criteria->addFilter(new EqualsFilter('languageId', $context->getContext()->getLanguageId()));
  71.         // CIO-AI-Driven: discriminator
  72.         $criteria->addFilter(new EqualsFilter('type', $type));
  73.         $criteria->addAssociation('media');
  74.         $result = $this->productDownloadRepository->search($criteria, $context->getContext());
  75.         if ($result->getTotal() <= 0) {
  76.             return [];
  77.         }
  78.         $downloadsByProductId = [];
  79.         foreach ($result->getEntities() as $download) {
  80.             $productId = (string) $download->get('productId');
  81.             if ($productId === '') {
  82.                 continue;
  83.             }
  84.             $displayName = (string) $download->get('displayName');
  85.             $fileName = (string) $download->get('fileName');
  86.             $media = $download->get('media');
  87.             $url = null;
  88.             if ($media instanceof MediaEntity) {
  89.                 $url = $media->getUrl();
  90.                 $fileName = $fileName !== '' ? $fileName : $this->buildFileNameFromMedia($media);
  91.                 $displayName = $displayName !== '' ? $displayName : $fileName;
  92.             }
  93.             // Backward compatibility (old implementations could have filePath)
  94.             if ($url === null) {
  95.                 $url = $download->get('filePath') ? (string) $download->get('filePath') : null;
  96.             }
  97.             $downloadsByProductId[$productId] ??= [];
  98.             $downloadsByProductId[$productId][] = [
  99.                 'displayName' => $displayName,
  100.                 'fileName' => $fileName,
  101.                 'url' => $url,
  102.             ];
  103.         }
  104.         return $downloadsByProductId;
  105.     }
  106.     private function buildFileNameFromMedia(MediaEntity $media): string
  107.     {
  108.         $baseName = (string) ($media->getFileName() ?? '');
  109.         $extension = (string) ($media->getFileExtension() ?? '');
  110.         if ($baseName === '') {
  111.             return '';
  112.         }
  113.         return $extension !== '' ? "{$baseName}.{$extension}" : $baseName;
  114.     }
  115. }