vendor/shopware/core/Content/ImportExport/DataAbstractionLayer/Serializer/Entity/MediaSerializer.php line 153

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\ImportExport\DataAbstractionLayer\Serializer\Entity;
  3. use Shopware\Core\Content\ImportExport\Exception\InvalidMediaUrlException;
  4. use Shopware\Core\Content\ImportExport\Exception\MediaDownloadException;
  5. use Shopware\Core\Content\ImportExport\Struct\Config;
  6. use Shopware\Core\Content\Media\Aggregate\MediaFolder\MediaFolderEntity;
  7. use Shopware\Core\Content\Media\File\FileSaver;
  8. use Shopware\Core\Content\Media\File\MediaFile;
  9. use Shopware\Core\Content\Media\MediaEvents;
  10. use Shopware\Core\Content\Media\MediaService;
  11. use Shopware\Core\Framework\Context;
  12. use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
  13. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  17. use Shopware\Core\Framework\Log\Package;
  18. use Shopware\Core\Framework\Struct\Struct;
  19. use Shopware\Core\Framework\Uuid\Uuid;
  20. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  21. use Symfony\Component\HttpFoundation\Request;
  22. use Symfony\Contracts\Service\ResetInterface;
  23. /**
  24.  * @deprecated tag:v6.5.0 - reason:becomes-internal - EventSubscribers will become internal in v6.5.0
  25.  */
  26. #[Package('core')]
  27. class MediaSerializer extends EntitySerializer implements EventSubscriberInterfaceResetInterface
  28. {
  29.     private FileSaver $fileSaver;
  30.     private MediaService $mediaService;
  31.     private EntityRepositoryInterface $mediaFolderRepository;
  32.     private EntityRepositoryInterface $mediaRepository;
  33.     /**
  34.      * @var array<string, array{media: MediaFile, destination: string}>
  35.      */
  36.     private array $cacheMediaFiles = [];
  37.     /**
  38.      * @internal
  39.      */
  40.     public function __construct(
  41.         MediaService $mediaService,
  42.         FileSaver $fileSaver,
  43.         EntityRepositoryInterface $mediaFolderRepository,
  44.         EntityRepositoryInterface $mediaRepository
  45.     ) {
  46.         $this->mediaService $mediaService;
  47.         $this->fileSaver $fileSaver;
  48.         $this->mediaFolderRepository $mediaFolderRepository;
  49.         $this->mediaRepository $mediaRepository;
  50.     }
  51.     /**
  52.      * @param array<mixed>|Struct|null $entity
  53.      *
  54.      * @return \Generator
  55.      */
  56.     public function serialize(Config $configEntityDefinition $definition$entity): iterable
  57.     {
  58.         yield from parent::serialize($config$definition$entity);
  59.     }
  60.     /**
  61.      * @param array<mixed>|\Traversable<mixed> $entity
  62.      *
  63.      * @return array<mixed>|\Traversable<mixed>
  64.      */
  65.     public function deserialize(Config $configEntityDefinition $definition$entity)
  66.     {
  67.         $entity \is_array($entity) ? $entity iterator_to_array($entity);
  68.         $deserialized parent::deserialize($config$definition$entity);
  69.         $deserialized \is_array($deserialized) ? $deserialized iterator_to_array($deserialized);
  70.         $url $entity['url'] ?? null;
  71.         if (empty($url)) {
  72.             return $deserialized;
  73.         }
  74.         if (!filter_var($url\FILTER_VALIDATE_URL)) {
  75.             $deserialized['_error'] = new InvalidMediaUrlException($url);
  76.             return $deserialized;
  77.         }
  78.         $media null;
  79.         if (isset($deserialized['id'])) {
  80.             $media $this->mediaRepository->search(new Criteria([$deserialized['id']]), Context::createDefaultContext())->first();
  81.         }
  82.         $isNew $media === null;
  83.         if ($isNew || $media->getUrl() !== $url) {
  84.             $entityName $config->get('sourceEntity') ?? $definition->getEntityName();
  85.             $deserialized['mediaFolderId'] = $deserialized['mediaFolderId']
  86.                 ?? $this->getMediaFolderId($deserialized['id'] ?? null$entityName);
  87.             $deserialized['id'] = $deserialized['id'] ?? Uuid::randomHex();
  88.             $parsed parse_url($url);
  89.             if (!$parsed) {
  90.                 throw new \RuntimeException('Error parsing media URL: ' $url);
  91.             }
  92.             $pathInfo pathinfo($parsed['path'] ?? '');
  93.             $media $this->fetchFileFromURL((string) $url$pathInfo['extension'] ?? '');
  94.             if ($media === null) {
  95.                 $deserialized['_error'] = new MediaDownloadException($url);
  96.                 return $deserialized;
  97.             }
  98.             if ($isNew && $media->getHash()) {
  99.                 $deserialized $this->fetchExistingMediaByHash($deserialized$media->getHash());
  100.             }
  101.             $this->cacheMediaFiles[(string) $deserialized['id']] = [
  102.                 'media' => $media,
  103.                 'destination' => $pathInfo['filename'],
  104.             ];
  105.         }
  106.         return $deserialized;
  107.     }
  108.     public function supports(string $entity): bool
  109.     {
  110.         return $entity === 'media';
  111.     }
  112.     /**
  113.      * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
  114.      */
  115.     public static function getSubscribedEvents()
  116.     {
  117.         return [
  118.             MediaEvents::MEDIA_WRITTEN_EVENT => 'persistMedia',
  119.         ];
  120.     }
  121.     /**
  122.      * @internal
  123.      */
  124.     public function persistMedia(EntityWrittenEvent $event): void
  125.     {
  126.         if (empty($this->cacheMediaFiles)) {
  127.             return;
  128.         }
  129.         $mediaFiles $this->cacheMediaFiles;
  130.         // prevent recursion
  131.         $this->cacheMediaFiles = [];
  132.         foreach ($event->getIds() as $id) {
  133.             if (!isset($mediaFiles[$id])) {
  134.                 continue;
  135.             }
  136.             $mediaFile $mediaFiles[$id];
  137.             $this->fileSaver->persistFileToMedia(
  138.                 $mediaFile['media'],
  139.                 $mediaFile['destination'],
  140.                 $id,
  141.                 $event->getContext()
  142.             );
  143.         }
  144.     }
  145.     public function reset(): void
  146.     {
  147.         $this->cacheMediaFiles = [];
  148.     }
  149.     private function getMediaFolderId(?string $idstring $entity): string
  150.     {
  151.         if ($id !== null) {
  152.             /** @var MediaFolderEntity|null $folder */
  153.             $folder $this->mediaFolderRepository->search(new Criteria([$id]), Context::createDefaultContext())->first();
  154.             if ($folder !== null) {
  155.                 return $folder->getId();
  156.             }
  157.         }
  158.         $criteria = new Criteria();
  159.         $criteria->addFilter(new EqualsFilter('media_folder.defaultFolder.entity'$entity));
  160.         $criteria->addAssociation('defaultFolder');
  161.         /** @var MediaFolderEntity|null $default */
  162.         $default $this->mediaFolderRepository->search($criteriaContext::createDefaultContext())->first();
  163.         if ($default !== null) {
  164.             return $default->getId();
  165.         }
  166.         $criteria = new Criteria();
  167.         $criteria->addFilter(new EqualsFilter('media_folder.defaultFolder.entity''import_export_profile'));
  168.         $criteria->addAssociation('defaultFolder');
  169.         /** @var MediaFolderEntity|null $fallback */
  170.         $fallback $this->mediaFolderRepository->search($criteriaContext::createDefaultContext())->first();
  171.         if ($fallback === null) {
  172.             throw new \RuntimeException('Failed to find default media folder for import_export_profile');
  173.         }
  174.         return $fallback->getId();
  175.     }
  176.     private function fetchFileFromURL(string $urlstring $extension): ?MediaFile
  177.     {
  178.         $request = new Request();
  179.         $request->query->set('url'$url);
  180.         $request->query->set('extension'$extension);
  181.         $request->request->set('url'$url);
  182.         $request->request->set('extension'$extension);
  183.         $request->headers->set('content-type''application/json');
  184.         try {
  185.             $file $this->mediaService->fetchFile($request);
  186.             if ($file !== null && $file->getFileSize() > 0) {
  187.                 return $file;
  188.             }
  189.         } catch (\Throwable $throwable) {
  190.         }
  191.         return null;
  192.     }
  193.     /**
  194.      * @param array<string, mixed> $deserialized
  195.      *
  196.      * @return array<string, mixed>
  197.      */
  198.     private function fetchExistingMediaByHash(array $deserializedstring $hash): array
  199.     {
  200.         $criteria = new Criteria();
  201.         $criteria->addFilter(new EqualsFilter('metaData.hash'$hash));
  202.         $media $this->mediaRepository->search($criteriaContext::createDefaultContext())->first();
  203.         if ($media) {
  204.             $deserialized['id'] = $media->getId();
  205.         }
  206.         return $deserialized;
  207.     }
  208. }