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

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