<?php declare(strict_types=1);
namespace Uandi\UandiEfbDownloadCenter\Controller;
use Shopware\Core\Checkout\Customer\CustomerEntity;
use Shopware\Core\Content\Media\MediaEntity;
use Shopware\Core\Content\Media\MediaType\DocumentType;
use Shopware\Core\Content\Media\Pathname\UrlGeneratorInterface;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Controller\StorefrontController;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Mime\FileinfoMimeTypeGuesser;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route(defaults={"_routeScope"={"storefront"}})
*/
class MediaDownloadController extends StorefrontController
{
private EntityRepositoryInterface $mediaRepository;
private SystemConfigService $systemConfigService;
private UrlGeneratorInterface $urlGenerator;
private KernelInterface $kernel;
public function __construct(
EntityRepositoryInterface $mediaRepository,
SystemConfigService $systemConfigService,
UrlGeneratorInterface $urlGenerator,
KernelInterface $kernel
) {
$this->mediaRepository = $mediaRepository;
$this->systemConfigService = $systemConfigService;
$this->urlGenerator = $urlGenerator;
$this->kernel = $kernel;
}
/**
* Supply a download for the supplied media file id, if the user is authorized.
*
* @Route("/download/media/{mediaId}", name="uandi.downloadcenter.restricted", methods={"GET"})
*
* @param string $mediaId
* @param SalesChannelContext $salesChannelContext
*
* @return BinaryFileResponse
*/
public function downloadProtectedMedia(
string $mediaId,
SalesChannelContext $salesChannelContext
): BinaryFileResponse {
$mediaEntity = $this->getMediaEntity($salesChannelContext, $mediaId);
if (!$mediaEntity->getMediaType() instanceof DocumentType) {
return $this->createMediaDownloadResponse($mediaEntity);
}
$this->checkFolderAccess($mediaEntity, $salesChannelContext->getCustomer());
return $this->createMediaDownloadResponse($mediaEntity);
}
/**
* Shortcut for reading config fields.
*
* @param string $configField
*
* @return array|float|bool|int|string|null
*/
private function getConfig(string $configField): array|float|bool|int|string|null
{
return $this->systemConfigService->get('UandiEfbDownloadCenter.config.' . $configField);
}
/**
* Load the media file for the supplied id. Not found => 404
*
* @param SalesChannelContext $salesChannelContext
* @param string $mediaId
*
* @return MediaEntity|null
*/
private function getMediaEntity(SalesChannelContext $salesChannelContext, string $mediaId): ?MediaEntity
{
$criteria = new Criteria([$mediaId]);
$criteria->addAssociations(['mediaFolder']);
$mediaFile = $this->mediaRepository->search($criteria, $salesChannelContext->getContext())->first();
if ($mediaFile === null) {
throw $this->createNotFoundException();
}
return $mediaFile;
}
/**
* Return pdf stream as a file download response.
*
* @param MediaEntity $pdfEntity
*
* @return BinaryFileResponse
*/
private function createMediaDownloadResponse(MediaEntity $pdfEntity): BinaryFileResponse
{
$filePath = $this->kernel->getProjectDir() . '/public/' . $this->urlGenerator->getRelativeMediaUrl($pdfEntity);
$response = new BinaryFileResponse($filePath);
$mimeTypeGuesser = new FileinfoMimeTypeGuesser();
$response->headers->set('Content-Type', $mimeTypeGuesser->guessMimeType($filePath));
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT);
return $response;
}
/**
* Access check, throws access denied exception when file is inside the protected folder structure but the user is
* not permitted.
*
* @param MediaEntity|null $mediaEntity
* @param CustomerEntity|null $customer
* @return void
*/
private function checkFolderAccess(?MediaEntity $mediaEntity, ?CustomerEntity $customer): void
{
$mediaFolder = $mediaEntity->getMediaFolder();
$parentFolderId = $mediaFolder->getParentId();
// Has no parent folder? Allow access
if ($parentFolderId == null) {
return;
}
// Parent folder is none of the configured roots? Allow access
$priceListRoot = $this->getConfig('rootFolderPriceLists');
$eCatalogRoot = $this->getConfig('rootFolderECatalogs');
if ($parentFolderId !== $priceListRoot && $parentFolderId !== $eCatalogRoot) {
return;
}
// Must be logged in to process further
if ($customer == null) {
throw $this->createAccessDeniedException();
}
// You've come this far and the folder is named after the customer number? Good enough
if ($customer->getCustomerNumber() == $mediaFolder->getName()) {
return;
}
// Still here? Guess we need to check if the folder name is on the customer's permission list for this root
$customFields = $customer->getCustomFields();
$priceListPermissions = [];
$eCatalogPermissions = [];
if (is_array($customFields) && array_key_exists('folderPermissions', $customFields)) {
$permissions = json_decode($customFields['folderPermissions'], true);
if (array_key_exists('priceLists', $permissions)) {
$priceListPermissions = $permissions['priceLists'];
}
if (array_key_exists('eCatalogs', $permissions)) {
$eCatalogPermissions = $permissions['eCatalogs'];
}
}
if ($parentFolderId == $priceListRoot) {
foreach ($priceListPermissions as $priceListPermission) {
if ($priceListPermission == $mediaFolder->getName()) {
return;
}
}
} else {
foreach ($eCatalogPermissions as $eCatalogPermission) {
if ($eCatalogPermission == $mediaFolder->getName()) {
return;
}
}
}
// Nope, obviously not
throw $this->createAccessDeniedException();
}
}