custom/plugins/UandiEfbDownloadCenter/src/Controller/MediaDownloadController.php line 52

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Uandi\UandiEfbDownloadCenter\Controller;
  3. use Shopware\Core\Checkout\Customer\CustomerEntity;
  4. use Shopware\Core\Content\Media\MediaEntity;
  5. use Shopware\Core\Content\Media\MediaType\DocumentType;
  6. use Shopware\Core\Content\Media\Pathname\UrlGeneratorInterface;
  7. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  9. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  10. use Shopware\Core\System\SystemConfig\SystemConfigService;
  11. use Shopware\Storefront\Controller\StorefrontController;
  12. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  13. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  14. use Symfony\Component\HttpKernel\KernelInterface;
  15. use Symfony\Component\Mime\FileinfoMimeTypeGuesser;
  16. use Symfony\Component\Routing\Annotation\Route;
  17. /**
  18.  * @Route(defaults={"_routeScope"={"storefront"}})
  19.  */
  20. class MediaDownloadController extends StorefrontController
  21. {
  22.     private EntityRepositoryInterface $mediaRepository;
  23.     private SystemConfigService $systemConfigService;
  24.     private UrlGeneratorInterface $urlGenerator;
  25.     private KernelInterface $kernel;
  26.     public function __construct(
  27.         EntityRepositoryInterface $mediaRepository,
  28.         SystemConfigService $systemConfigService,
  29.         UrlGeneratorInterface $urlGenerator,
  30.         KernelInterface $kernel
  31.     ) {
  32.         $this->mediaRepository $mediaRepository;
  33.         $this->systemConfigService $systemConfigService;
  34.         $this->urlGenerator $urlGenerator;
  35.         $this->kernel $kernel;
  36.     }
  37.     /**
  38.      * Supply a download for the supplied media file id, if the user is authorized.
  39.      *
  40.      * @Route("/download/media/{mediaId}", name="uandi.downloadcenter.restricted", methods={"GET"})
  41.      *
  42.      * @param string $mediaId
  43.      * @param SalesChannelContext $salesChannelContext
  44.      *
  45.      * @return BinaryFileResponse
  46.      */
  47.     public function downloadProtectedMedia(
  48.         string $mediaId,
  49.         SalesChannelContext $salesChannelContext
  50.     ): BinaryFileResponse {
  51.         $mediaEntity $this->getMediaEntity($salesChannelContext$mediaId);
  52.         if (!$mediaEntity->getMediaType() instanceof DocumentType) {
  53.             return $this->createMediaDownloadResponse($mediaEntity);
  54.         }
  55.         $this->checkFolderAccess($mediaEntity$salesChannelContext->getCustomer());
  56.         return $this->createMediaDownloadResponse($mediaEntity);
  57.     }
  58.     /**
  59.      * Shortcut for reading config fields.
  60.      *
  61.      * @param string $configField
  62.      *
  63.      * @return array|float|bool|int|string|null
  64.      */
  65.     private function getConfig(string $configField): array|float|bool|int|string|null
  66.     {
  67.         return $this->systemConfigService->get('UandiEfbDownloadCenter.config.' $configField);
  68.     }
  69.     /**
  70.      * Load the media file for the supplied id. Not found => 404
  71.      *
  72.      * @param SalesChannelContext $salesChannelContext
  73.      * @param string $mediaId
  74.      *
  75.      * @return MediaEntity|null
  76.      */
  77.     private function getMediaEntity(SalesChannelContext $salesChannelContextstring $mediaId): ?MediaEntity
  78.     {
  79.         $criteria = new Criteria([$mediaId]);
  80.         $criteria->addAssociations(['mediaFolder']);
  81.         $mediaFile $this->mediaRepository->search($criteria$salesChannelContext->getContext())->first();
  82.         if ($mediaFile === null) {
  83.             throw $this->createNotFoundException();
  84.         }
  85.         return $mediaFile;
  86.     }
  87.     /**
  88.      * Return pdf stream as a file download response.
  89.      *
  90.      * @param MediaEntity $pdfEntity
  91.      *
  92.      * @return BinaryFileResponse
  93.      */
  94.     private function createMediaDownloadResponse(MediaEntity $pdfEntity): BinaryFileResponse
  95.     {
  96.         $filePath $this->kernel->getProjectDir() . '/public/' $this->urlGenerator->getRelativeMediaUrl($pdfEntity);
  97.         $response = new BinaryFileResponse($filePath);
  98.         $mimeTypeGuesser = new FileinfoMimeTypeGuesser();
  99.         $response->headers->set('Content-Type'$mimeTypeGuesser->guessMimeType($filePath));
  100.         $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT);
  101.         return $response;
  102.     }
  103.     /**
  104.      * Access check, throws access denied exception when file is inside the protected folder structure but the user is
  105.      * not permitted.
  106.      *
  107.      * @param MediaEntity|null $mediaEntity
  108.      * @param CustomerEntity|null $customer
  109.      * @return void
  110.      */
  111.     private function checkFolderAccess(?MediaEntity $mediaEntity, ?CustomerEntity $customer): void
  112.     {
  113.         $mediaFolder $mediaEntity->getMediaFolder();
  114.         $parentFolderId $mediaFolder->getParentId();
  115.         // Has no parent folder? Allow access
  116.         if ($parentFolderId == null) {
  117.             return;
  118.         }
  119.         // Parent folder is none of the configured roots? Allow access
  120.         $priceListRoot $this->getConfig('rootFolderPriceLists');
  121.         $eCatalogRoot $this->getConfig('rootFolderECatalogs');
  122.         if ($parentFolderId !== $priceListRoot && $parentFolderId !== $eCatalogRoot) {
  123.             return;
  124.         }
  125.         // Must be logged in to process further
  126.         if ($customer == null) {
  127.             throw $this->createAccessDeniedException();
  128.         }
  129.         // You've come this far and the folder is named after the customer number? Good enough
  130.         if ($customer->getCustomerNumber() == $mediaFolder->getName()) {
  131.             return;
  132.         }
  133.         // Still here? Guess we need to check if the folder name is on the customer's permission list for this root
  134.         $customFields $customer->getCustomFields();
  135.         $priceListPermissions = [];
  136.         $eCatalogPermissions = [];
  137.         if (is_array($customFields) && array_key_exists('folderPermissions'$customFields)) {
  138.             $permissions json_decode($customFields['folderPermissions'], true);
  139.             if (array_key_exists('priceLists'$permissions)) {
  140.                 $priceListPermissions $permissions['priceLists'];
  141.             }
  142.             if (array_key_exists('eCatalogs'$permissions)) {
  143.                 $eCatalogPermissions $permissions['eCatalogs'];
  144.             }
  145.         }
  146.         if ($parentFolderId == $priceListRoot) {
  147.             foreach ($priceListPermissions as $priceListPermission) {
  148.                 if ($priceListPermission == $mediaFolder->getName()) {
  149.                     return;
  150.                 }
  151.             }
  152.         } else {
  153.             foreach ($eCatalogPermissions as $eCatalogPermission) {
  154.                 if ($eCatalogPermission == $mediaFolder->getName()) {
  155.                     return;
  156.                 }
  157.             }
  158.         }
  159.         // Nope, obviously not
  160.         throw $this->createAccessDeniedException();
  161.     }
  162. }