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

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