vendor/shopware/core/Content/Media/Subscriber/MediaDeletionSubscriber.php line 111

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Media\Subscriber;
  3. use Doctrine\DBAL\Connection;
  4. use League\Flysystem\AdapterInterface;
  5. use Shopware\Core\Content\Media\Aggregate\MediaFolder\MediaFolderDefinition;
  6. use Shopware\Core\Content\Media\Aggregate\MediaThumbnail\MediaThumbnailCollection;
  7. use Shopware\Core\Content\Media\Aggregate\MediaThumbnail\MediaThumbnailDefinition;
  8. use Shopware\Core\Content\Media\Event\MediaThumbnailDeletedEvent;
  9. use Shopware\Core\Content\Media\MediaDefinition;
  10. use Shopware\Core\Content\Media\MediaEntity;
  11. use Shopware\Core\Content\Media\Message\DeleteFileHandler;
  12. use Shopware\Core\Content\Media\Message\DeleteFileMessage;
  13. use Shopware\Core\Content\Media\Pathname\UrlGeneratorInterface;
  14. use Shopware\Core\Framework\Context;
  15. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Event\BeforeDeleteEvent;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntitySearchedEvent;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  20. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  21. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  22. use Shopware\Core\Framework\Log\Package;
  23. use Shopware\Core\Framework\Uuid\Uuid;
  24. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  25. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  26. use Symfony\Component\Messenger\MessageBusInterface;
  27. /**
  28.  * @deprecated tag:v6.5.0 - reason:becomes-internal - EventSubscribers will become internal in v6.5.0
  29.  */
  30. #[Package('content')]
  31. class MediaDeletionSubscriber implements EventSubscriberInterface
  32. {
  33.     public const SYNCHRONE_FILE_DELETE 'synchrone-file-delete';
  34.     private Connection $connection;
  35.     private UrlGeneratorInterface $urlGenerator;
  36.     private EventDispatcherInterface $dispatcher;
  37.     private EntityRepositoryInterface $thumbnailRepository;
  38.     private MessageBusInterface $messageBus;
  39.     private DeleteFileHandler $deleteFileHandler;
  40.     private EntityRepositoryInterface $mediaRepository;
  41.     /**
  42.      * @internal
  43.      */
  44.     public function __construct(
  45.         UrlGeneratorInterface $urlGenerator,
  46.         EventDispatcherInterface $dispatcher,
  47.         EntityRepositoryInterface $thumbnailRepository,
  48.         MessageBusInterface $messageBus,
  49.         DeleteFileHandler $deleteFileHandler,
  50.         Connection $connection,
  51.         EntityRepositoryInterface $mediaRepository
  52.     ) {
  53.         $this->urlGenerator $urlGenerator;
  54.         $this->dispatcher $dispatcher;
  55.         $this->thumbnailRepository $thumbnailRepository;
  56.         $this->messageBus $messageBus;
  57.         $this->deleteFileHandler $deleteFileHandler;
  58.         $this->connection $connection;
  59.         $this->mediaRepository $mediaRepository;
  60.     }
  61.     public static function getSubscribedEvents(): array
  62.     {
  63.         return [
  64.             BeforeDeleteEvent::class => 'beforeDelete',
  65.             EntitySearchedEvent::class => 'securePrivateFolders',
  66.         ];
  67.     }
  68.     public function securePrivateFolders(EntitySearchedEvent $event): void
  69.     {
  70.         if ($event->getContext()->getScope() === Context::SYSTEM_SCOPE) {
  71.             return;
  72.         }
  73.         if ($event->getDefinition()->getEntityName() === MediaFolderDefinition::ENTITY_NAME) {
  74.             $event->getCriteria()->addFilter(
  75.                 new MultiFilter('OR', [
  76.                     new EqualsFilter('media_folder.configuration.private'false),
  77.                     new EqualsFilter('media_folder.configuration.private'null),
  78.                 ])
  79.             );
  80.             return;
  81.         }
  82.         if ($event->getDefinition()->getEntityName() === MediaDefinition::ENTITY_NAME) {
  83.             $event->getCriteria()->addFilter(
  84.                 new MultiFilter('OR', [
  85.                     new EqualsFilter('private'false),
  86.                     new MultiFilter('AND', [
  87.                         new EqualsFilter('private'true),
  88.                         new EqualsFilter('mediaFolder.defaultFolder.entity''product_download'),
  89.                     ]),
  90.                 ])
  91.             );
  92.         }
  93.     }
  94.     public function beforeDelete(BeforeDeleteEvent $event): void
  95.     {
  96.         $affected $event->getIds(MediaThumbnailDefinition::ENTITY_NAME);
  97.         if (!empty($affected)) {
  98.             $this->handleThumbnailDeletion($event$affected$event->getContext());
  99.         }
  100.         $affected $event->getIds(MediaFolderDefinition::ENTITY_NAME);
  101.         if (!empty($affected)) {
  102.             $this->handleFolderDeletion($affected$event->getContext());
  103.         }
  104.         $affected $event->getIds(MediaDefinition::ENTITY_NAME);
  105.         if (!empty($affected)) {
  106.             $this->handleMediaDeletion($affected$event->getContext());
  107.         }
  108.     }
  109.     /**
  110.      * @param list<string> $affected
  111.      */
  112.     private function handleMediaDeletion(array $affectedContext $context): void
  113.     {
  114.         $media $context->scope(Context::SYSTEM_SCOPE, function (Context $context) use ($affected) {
  115.             return $this->mediaRepository->search(new Criteria($affected), $context);
  116.         });
  117.         $privatePaths = [];
  118.         $publicPaths = [];
  119.         $thumbnails = [];
  120.         /** @var MediaEntity $mediaEntity */
  121.         foreach ($media as $mediaEntity) {
  122.             if (!$mediaEntity->hasFile()) {
  123.                 continue;
  124.             }
  125.             if ($mediaEntity->isPrivate()) {
  126.                 $privatePaths[] = $this->urlGenerator->getRelativeMediaUrl($mediaEntity);
  127.             } else {
  128.                 $publicPaths[] = $this->urlGenerator->getRelativeMediaUrl($mediaEntity);
  129.             }
  130.             if (!$mediaEntity->getThumbnails()) {
  131.                 continue;
  132.             }
  133.             foreach ($mediaEntity->getThumbnails()->getIds() as $id) {
  134.                 $thumbnails[] = ['id' => $id];
  135.             }
  136.         }
  137.         $this->performFileDelete($context$publicPathsAdapterInterface::VISIBILITY_PUBLIC);
  138.         $this->performFileDelete($context$privatePathsAdapterInterface::VISIBILITY_PRIVATE);
  139.         $this->thumbnailRepository->delete($thumbnails$context);
  140.     }
  141.     /**
  142.      * @param list<string> $affected
  143.      */
  144.     private function handleFolderDeletion(array $affectedContext $context): void
  145.     {
  146.         $ids $this->fetchChildrenIds($affected);
  147.         if (empty($ids)) {
  148.             return;
  149.         }
  150.         $media $this->connection->fetchAllAssociative(
  151.             'SELECT LOWER(HEX(id)) as id FROM media WHERE media_folder_id IN (:ids)',
  152.             ['ids' => Uuid::fromHexToBytesList($ids)],
  153.             ['ids' => Connection::PARAM_STR_ARRAY]
  154.         );
  155.         if (empty($media)) {
  156.             return;
  157.         }
  158.         $this->mediaRepository->delete($media$context);
  159.     }
  160.     /**
  161.      * @param list<string> $ids
  162.      *
  163.      * @return list<string>
  164.      */
  165.     private function fetchChildrenIds(array $ids): array
  166.     {
  167.         $children $this->connection->fetchFirstColumn(
  168.             'SELECT LOWER(HEX(id)) FROM media_folder WHERE parent_id IN (:ids)',
  169.             ['ids' => Uuid::fromHexToBytesList($ids)],
  170.             ['ids' => Connection::PARAM_STR_ARRAY]
  171.         );
  172.         if (empty($children)) {
  173.             return \array_merge($ids$children);
  174.         }
  175.         $nested $this->fetchChildrenIds($children);
  176.         $children \array_merge($children$nested);
  177.         return \array_merge($ids$children$nested);
  178.     }
  179.     /**
  180.      * @param list<string> $affected
  181.      */
  182.     private function handleThumbnailDeletion(BeforeDeleteEvent $event, array $affectedContext $context): void
  183.     {
  184.         $privatePaths = [];
  185.         $publicPaths = [];
  186.         $thumbnails $this->getThumbnails($affected$context);
  187.         foreach ($thumbnails as $thumbnail) {
  188.             if ($thumbnail->getMedia() === null) {
  189.                 continue;
  190.             }
  191.             if ($thumbnail->getMedia()->isPrivate()) {
  192.                 $privatePaths[] = $this->urlGenerator->getRelativeThumbnailUrl($thumbnail->getMedia(), $thumbnail);
  193.             } else {
  194.                 $publicPaths[] = $this->urlGenerator->getRelativeThumbnailUrl($thumbnail->getMedia(), $thumbnail);
  195.             }
  196.         }
  197.         $this->performFileDelete($context$privatePathsAdapterInterface::VISIBILITY_PRIVATE);
  198.         $this->performFileDelete($context$publicPathsAdapterInterface::VISIBILITY_PUBLIC);
  199.         $event->addSuccess(function () use ($thumbnails$context): void {
  200.             $this->dispatcher->dispatch(new MediaThumbnailDeletedEvent($thumbnails$context), MediaThumbnailDeletedEvent::EVENT_NAME);
  201.         });
  202.     }
  203.     /**
  204.      * @param list<string> $ids
  205.      */
  206.     private function getThumbnails(array $idsContext $context): MediaThumbnailCollection
  207.     {
  208.         $criteria = new Criteria();
  209.         $criteria->addAssociation('media');
  210.         $criteria->addFilter(new EqualsAnyFilter('media_thumbnail.id'$ids));
  211.         $thumbnailsSearch $this->thumbnailRepository->search($criteria$context);
  212.         /** @var MediaThumbnailCollection $thumbnails */
  213.         $thumbnails $thumbnailsSearch->getEntities();
  214.         return $thumbnails;
  215.     }
  216.     /**
  217.      * @param list<string> $paths
  218.      */
  219.     private function performFileDelete(Context $context, array $pathsstring $visibility): void
  220.     {
  221.         if (\count($paths) <= 0) {
  222.             return;
  223.         }
  224.         if ($context->hasState(self::SYNCHRONE_FILE_DELETE)) {
  225.             $this->deleteFileHandler->handle(new DeleteFileMessage($paths$visibility));
  226.             return;
  227.         }
  228.         $this->messageBus->dispatch(new DeleteFileMessage($paths$visibility));
  229.     }
  230. }