Commit 72a4825c authored by Stefan Froemken's avatar Stefan Froemken Committed by Susanne Moog
Browse files

[FEATURE] Improve FileDumpController

- Add possibility to use records of sys_file_reference
- Add possibility to resize images
- Add possibility to apply cropVariants

Resolves: #90068
Releases: master
Change-Id: Ib80021dc25b42e7021cf5429b2df8029aac1fd8c
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62834


Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Frank Nägler's avatarFrank Nägler <frank.naegler@typo3.org>
Tested-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Reviewed-by: default avatarChristian Eßl <indy.essl@gmail.com>
Reviewed-by: Markus Klein's avatarMarkus Klein <markus.klein@typo3.org>
Reviewed-by: Daniel Goerz's avatarDaniel Goerz <daniel.goerz@posteo.de>
Reviewed-by: Frank Nägler's avatarFrank Nägler <frank.naegler@typo3.org>
Reviewed-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
parent 5aea1b2e
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Core\Controller;
/*
......@@ -19,7 +18,12 @@ namespace TYPO3\CMS\Core\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Http\Response;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Resource\Hook\FileDumpEIDHookInterface;
use TYPO3\CMS\Core\Resource\ProcessedFile;
use TYPO3\CMS\Core\Resource\ProcessedFileRepository;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
......@@ -29,78 +33,168 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
*/
class FileDumpController
{
/**
* @var ResourceFactory
*/
protected $resourceFactory;
public function __construct(ResourceFactory $resourceFactory)
{
$this->resourceFactory = $resourceFactory;
}
/**
* Main method to dump a file
*
* @param ServerRequestInterface $request
* @return ResponseInterface
*
* @throws \InvalidArgumentException
* @throws \RuntimeException
* @throws \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException
* @throws FileDoesNotExistException
* @throws \UnexpectedValueException
*/
public function dumpAction(ServerRequestInterface $request): ResponseInterface
{
$parameters = $this->buildParametersFromRequest($request);
if (!$this->isTokenValid($parameters, $request)) {
return (new Response)->withStatus(403);
}
$file = $this->createFileObjectByParameters($parameters);
if ($file === null) {
return (new Response)->withStatus(404);
}
// Hook: allow some other process to do some security/access checks. Hook should return 403 response if access is rejected, void otherwise
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['FileDumpEID.php']['checkFileAccess'] ?? [] as $className) {
$hookObject = GeneralUtility::makeInstance($className);
if (!$hookObject instanceof FileDumpEIDHookInterface) {
throw new \UnexpectedValueException($className . ' must implement interface ' . FileDumpEIDHookInterface::class, 1394442417);
}
$response = $hookObject->checkFileAccess($file);
if ($response instanceof ResponseInterface) {
return $response;
}
}
// Apply cropping, if possible
if (!$file instanceof ProcessedFile) {
$cropVariant = $parameters['cv'] ?: 'default';
$cropString = $file instanceof FileReference ? $file->getProperty('crop') : '';
$cropArea = CropVariantCollection::create((string)$cropString)->getCropArea($cropVariant);
$processingInstructions = [
'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($file),
];
// Apply width/height, if given
if (!empty($parameters['s'])) {
$size = GeneralUtility::trimExplode(':', $parameters['s']);
$processingInstructions = array_merge(
$processingInstructions,
[
'width' => $size[0] ?? null,
'height' => $size[1] ?? null,
'minWidth' => $size[2] ? (int)$size[2] : null,
'minHeight' => $size[3] ? (int)$size[3] : null,
'maxWidth' => $size[4] ? (int)$size[4] : null,
'maxHeight' => $size[5] ? (int)$size[5] : null
]
);
}
if (is_callable([$file, 'getOriginalFile'])) {
// Get the original file from the file reference
$file = $file->getOriginalFile();
}
$file = $file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingInstructions);
}
return $file->getStorage()->streamFile($file);
}
protected function buildParametersFromRequest(ServerRequestInterface $request): array
{
$parameters = ['eID' => 'dumpFile'];
$t = $this->getGetOrPost($request, 't');
$queryParams = $request->getQueryParams();
// Identifier of what to process. f, r or p
// Only needed while hash_equals
$t = (string)($queryParams['t'] ?? '');
if ($t) {
$parameters['t'] = $t;
}
$f = $this->getGetOrPost($request, 'f');
// sys_file
$f = (string)($queryParams['f'] ?? '');
if ($f) {
$parameters['f'] = $f;
$parameters['f'] = (int)$f;
}
// sys_file_reference
$r = (string)($queryParams['r'] ?? '');
if ($r) {
$parameters['r'] = (int)$r;
}
$p = $this->getGetOrPost($request, 'p');
// Processed file
$p = (string)($queryParams['p'] ?? '');
if ($p) {
$parameters['p'] = $p;
$parameters['p'] = (int)$p;
}
// File's width and height in this order: w:h:minW:minH:maxW:maxH
$s = (string)($queryParams['s'] ?? '');
if ($s) {
$parameters['s'] = $s;
}
// File's crop variant
$v = (string)($queryParams['cv'] ?? '');
if ($v) {
$parameters['cv'] = (string)$v;
}
return $parameters;
}
protected function isTokenValid(array $parameters, ServerRequestInterface $request): bool
{
return hash_equals(
GeneralUtility::hmac(implode('|', $parameters), 'resourceStorageDumpFile'),
$request->getQueryParams()['token'] ?? ''
);
}
if (hash_equals(GeneralUtility::hmac(implode('|', $parameters), 'resourceStorageDumpFile'), $this->getGetOrPost($request, 'token'))) {
if (isset($parameters['f'])) {
try {
$file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObject($parameters['f']);
if ($file->isDeleted() || $file->isMissing()) {
$file = null;
}
} catch (\Exception $e) {
/**
* @param array $parameters
* @return File|FileReference|ProcessedFile|null
*/
protected function createFileObjectByParameters(array $parameters)
{
$file = null;
if (isset($parameters['f'])) {
try {
$file = $this->resourceFactory->getFileObject($parameters['f']);
if ($file->isDeleted() || $file->isMissing()) {
$file = null;
}
} else {
$file = GeneralUtility::makeInstance(ProcessedFileRepository::class)->findByUid($parameters['p']);
if (!$file || $file->isDeleted()) {
} catch (\Exception $e) {
$file = null;
}
} elseif (isset($parameters['r'])) {
try {
$file = $this->resourceFactory->getFileReferenceObject($parameters['r']);
if ($file->isMissing()) {
$file = null;
}
} catch (\Exception $e) {
$file = null;
}
if ($file === null) {
return (new Response)->withStatus(404);
}
// Hook: allow some other process to do some security/access checks. Hook should return 403 response if access is rejected, void otherwise
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['FileDumpEID.php']['checkFileAccess'] ?? [] as $className) {
$hookObject = GeneralUtility::makeInstance($className);
if (!$hookObject instanceof FileDumpEIDHookInterface) {
throw new \UnexpectedValueException($className . ' must implement interface ' . FileDumpEIDHookInterface::class, 1394442417);
}
$response = $hookObject->checkFileAccess($file);
if ($response instanceof ResponseInterface) {
return $response;
} elseif (isset($parameters['p'])) {
try {
$processedFileRepository = GeneralUtility::makeInstance(ProcessedFileRepository::class);
/** @var ProcessedFile|null $file */
$file = $processedFileRepository->findByUid($parameters['p']);
if (!$file || $file->isDeleted()) {
$file = null;
}
} catch (\Exception $e) {
$file = null;
}
return $file->getStorage()->streamFile($file);
}
return (new Response)->withStatus(403);
}
/**
* @param ServerRequestInterface $request
* @param string $parameter
* @return string
*/
protected function getGetOrPost(ServerRequestInterface $request, string $parameter): string
{
return (string)($request->getParsedBody()[$parameter] ?? $request->getQueryParams()[$parameter] ?? '');
return $file;
}
}
......@@ -85,6 +85,9 @@ services:
TYPO3\CMS\Core\Mail\Mailer:
public: true
TYPO3\CMS\Core\Controller\FileDumpController:
public: true
TYPO3\CMS\Core\Core\ClassLoadingInformation:
public: false
tags:
......
.. include:: ../../Includes.txt
=====================================================================
Feature: #90068 - Implement better FileDumpController
=====================================================================
See :issue:`90068`
Description
===========
FileDumpController can now process UIDs of sys_file_reference records and
can adopt image sizes to records of sys_file.
Following URI-Parameters are now possible:
+ `t` (*Type*): Can be one of `f` (sys_file), `r` (sys_file_reference) or `p` (sys_file_processedfile)
+ `f` (*File*): Use it for an UID of table sys_file
+ `r` (*Reference*): Use it for an UID of table sys_file_reference
+ `p` (*Processed*): Use it for an UID of table sys_file_processedfile
+ `s` (*Size*): Use it for an UID of table sys_file_processedfile
+ `cv` (*CropVariant*): In case of sys_file_reference, you can assign it a cropping variant
You have to choose one of these parameters: `f`, `r` and `p`. It is not possible
to use them multiple times in one request.
The Parameter `s` has following syntax: width:height:minW:minH:maxW:maxH. You
can leave this Parameter empty to load file in original size. Parameter `width`
and `height` can consist of trailing `c` or `m` identicator like known from TS.
See following example how to create an URI using the FileDumpController for
a sys_file record with a fixed image size:
.. code-block:: php
$queryParameterArray = ['eID' => 'dumpFile', 't' => 'f'];
$queryParameterArray['f'] = $resourceObject->getUid();
$queryParameterArray['s'] = '320c:280c';
$queryParameterArray['token'] = GeneralUtility::hmac(implode('|', $queryParameterArray), 'resourceStorageDumpFile');
$publicUrl = GeneralUtility::locationHeaderUrl(PathUtility::getAbsoluteWebPath(Environment::getPublicPath() . '/index.php'));
$publicUrl .= '?' . http_build_query($queryParameterArray, '', '&', PHP_QUERY_RFC3986);
In this example crop variant `default` and an image size of 320:280 will be
applied to a sys_file_reference record:
.. code-block:: php
$queryParameterArray = ['eID' => 'dumpFile', 't' => 'r'];
$queryParameterArray['f'] = $resourceObject->getUid();
$queryParameterArray['s'] = '320c:280c:320:280:320:280';
$queryParameterArray['cv'] = 'default';
$queryParameterArray['token'] = GeneralUtility::hmac(implode('|', $queryParameterArray), 'resourceStorageDumpFile');
$publicUrl = GeneralUtility::locationHeaderUrl(PathUtility::getAbsoluteWebPath(Environment::getPublicPath() . '/index.php'));
$publicUrl .= '?' . http_build_query($queryParameterArray, '', '&', PHP_QUERY_RFC3986);
This example shows the usage how to create an URI to load an image of
sys_file_processfiles:
.. code-block:: php
$queryParameterArray = ['eID' => 'dumpFile', 't' => 'p'];
$queryParameterArray['p'] = $resourceObject->getUid();
$queryParameterArray['token'] = GeneralUtility::hmac(implode('|', $queryParameterArray), 'resourceStorageDumpFile');
$publicUrl = GeneralUtility::locationHeaderUrl(PathUtility::getAbsoluteWebPath(Environment::getPublicPath() . '/index.php'));
$publicUrl .= '?' . http_build_query($queryParameterArray, '', '&', PHP_QUERY_RFC3986);
There are some restriction while using the new URI-Parameters:
+ You can't assign any size parameter to processed files, as they are already resized.
+ You can't apply CropVariants to sys_file and sys_file_processedfile records.
Impact
======
No impact, as this class was extended only. It's full backwards compatible
.. index:: FAL, ext:core
......@@ -176,7 +176,10 @@ class ImageService implements \TYPO3\CMS\Core\SingletonInterface
*/
protected function setCompatibilityValues(ProcessedFile $processedImage): void
{
if ($this->environmentService->isEnvironmentInFrontendMode()) {
if (
$this->environmentService->isEnvironmentInFrontendMode()
&& is_object($GLOBALS['TSFE'])
) {
$GLOBALS['TSFE']->lastImageInfo = $this->getCompatibilityImageResourceValues($processedImage);
$GLOBALS['TSFE']->imagesOnPage[] = $processedImage->getPublicUrl();
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment