custom/plugins/CioProductDownloadsTab/src/Subscriber/ProductPageSubscriber.php line 32

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\Framework\DataAbstractionLayer\EntityRepository;
  6. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  8. use Shopware\Core\Framework\Struct\ArrayStruct;
  9. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  10. use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. final class ProductPageSubscriber implements EventSubscriberInterface
  13. {
  14.     private EntityRepository $productDownloadRepository;
  15.     public function __construct(EntityRepository $productDownloadRepository)
  16.     {
  17.         $this->productDownloadRepository = $productDownloadRepository;
  18.     }
  19.     public static function getSubscribedEvents(): array
  20.     {
  21.         return [
  22.             // CIO-AI-Driven: Storefront product detail page event (required for Twig page.extensions.*)
  23.             ProductPageLoadedEvent::class => 'onProductPageLoaded',
  24.         ];
  25.     }
  26.     public function onProductPageLoaded(ProductPageLoadedEvent $event): void
  27.     {
  28.         $page = $event->getPage();
  29.         $product = $page->getProduct();
  30.         $salesChannelContext = $event->getSalesChannelContext();
  31.         $downloads = $this->getProductDownloadsByType($product->getId(), $salesChannelContext, ProductDownloadDefinition::TYPE_DOWNLOAD);
  32.         if ($downloads !== []) {
  33.             // CIO-AI-Driven: keep access consistent across PDP (page.extensions.*) and listing (product.extensions.*)
  34.             $page->addExtension('productDownloads', new ArrayStruct([
  35.                 'downloads' => $downloads,
  36.             ]));
  37.             $product->addExtension('productDownloads', new ArrayStruct([
  38.                 'downloads' => $downloads,
  39.             ]));
  40.         }
  41.         $cadDownloads = $this->getProductDownloadsByType($product->getId(), $salesChannelContext, ProductDownloadDefinition::TYPE_CAD);
  42.         if ($cadDownloads !== []) {
  43.             // CIO-AI-Driven: expose CAD downloads in a dedicated extension so Twig can render a separate tab
  44.             $page->addExtension('productCadDownloads', new ArrayStruct([
  45.                 'downloads' => $cadDownloads,
  46.             ]));
  47.             $product->addExtension('productCadDownloads', new ArrayStruct([
  48.                 'downloads' => $cadDownloads,
  49.             ]));
  50.         }
  51.     }
  52.     /**
  53.      * @return array<int, array{displayName: string, fileName: string, url: string|null}>
  54.      */
  55.     private function getProductDownloadsByType(string $productId, SalesChannelContext $context, string $type): array
  56.     {
  57.         $criteria = new Criteria();
  58.         $criteria->addFilter(new EqualsFilter('productId', $productId));
  59.         // CIO-AI-Driven: discriminator
  60.         $criteria->addFilter(new EqualsFilter('type', $type));
  61.         // CIO-AI-Driven: per-language downloads, use the current storefront request language
  62.         $criteria->addFilter(new EqualsFilter('languageId', $context->getContext()->getLanguageId()));
  63.         // CIO-AI-Driven: Admin stores via media_id now, so load the media association for a public URL
  64.         $criteria->addAssociation('media');
  65.         $result = $this->productDownloadRepository->search($criteria, $context->getContext());
  66.         $downloads = [];
  67.         foreach ($result->getEntities() as $download) {
  68.             $displayName = (string) $download->get('displayName');
  69.             $fileName = (string) $download->get('fileName');
  70.             $media = $download->get('media');
  71.             $url = null;
  72.             if ($media instanceof MediaEntity) {
  73.                 $url = $media->getUrl();
  74.                 $fileName = $fileName !== '' ? $fileName : $this->buildFileNameFromMedia($media);
  75.                 $displayName = $displayName !== '' ? $displayName : $fileName;
  76.             }
  77.             // Backward compatibility (old implementations could have filePath)
  78.             if ($url === null) {
  79.                 $url = $download->get('filePath') ? (string) $download->get('filePath') : null;
  80.             }
  81.             $downloads[] = [
  82.                 'displayName' => $displayName,
  83.                 'fileName' => $fileName,
  84.                 'url' => $url,
  85.             ];
  86.         }
  87.         return $downloads;
  88.     }
  89.     private function buildFileNameFromMedia(MediaEntity $media): string
  90.     {
  91.         $baseName = (string) ($media->getFileName() ?? '');
  92.         $extension = (string) ($media->getFileExtension() ?? '');
  93.         if ($baseName === '') {
  94.             return '';
  95.         }
  96.         return $extension !== '' ? "{$baseName}.{$extension}" : $baseName;
  97.     }
  98. }