Commit 7a3202cb authored by Simon Schaufelberger's avatar Simon Schaufelberger Committed by Stefan Bürk
Browse files

[BUGFIX] Make file paths absolute in GIFBUILDER

In CLI context the full path to the temporary generated file by the
GIFBUILDER can't be resolved. Because of this inconsistency, absolute
file paths are used from now on.

Resolves: #95379
Releases: main
Change-Id: If3a8613ed8e8d11a1b8a474fa564f947ef8a5c0c
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/73932


Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Nikita Hovratov's avatarNikita Hovratov <nikita.h@live.de>
Tested-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Reviewed-by: Nikita Hovratov's avatarNikita Hovratov <nikita.h@live.de>
Reviewed-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
parent 1088be60
......@@ -2069,7 +2069,7 @@ class GraphicalFunctions
/**
* Converts $imagefile to another file in temp-dir of type $newExt (extension).
*
* @param string $imagefile The image filepath
* @param string $imagefile The absolute image filepath
* @param string $newExt New extension, eg. "gif", "png", "jpg", "tif". If $newExt is NOT set, the new imagefile will be of the original format. If newExt = 'WEB' then one of the web-formats is applied.
* @param string $w Width. $w / $h is optional. If only one is given the image is scaled proportionally. If an 'm' exists in the $w or $h and if both are present the $w and $h is regarded as the Maximum w/h and the proportions will be kept
* @param string $h Height. See $w
......@@ -2189,8 +2189,8 @@ class GraphicalFunctions
/**
* Gets the input image dimensions.
*
* @param string $imageFile The image filepath
* @return array|null Returns an array where [0]/[1] is w/h, [2] is extension and [3] is the filename.
* @param string $imageFile The absolute image filepath
* @return array|null Returns an array where [0]/[1] is w/h, [2] is extension and [3] is the absolute filepath.
* @see imageMagickConvert()
* @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getImgResource()
*/
......@@ -2243,7 +2243,7 @@ class GraphicalFunctions
/**
* Fetches the cached image dimensions from the cache. Does not check if the image file exists.
*
* @param string $filePath Image file path, relative to public web path
* @param string $filePath The absolute image file path
*
* @return array|bool an array where [0]/[1] is w/h, [2] is extension and [3] is the file name,
* or FALSE for a cache miss
......@@ -2280,19 +2280,19 @@ class GraphicalFunctions
*
* This method does not check if the image file actually exists.
*
* @param string $filePath Image file path, relative to public web path
* @param string $filePath Absolute image file path
*
* @return string the hash key (an SHA1 hash), will not be empty
*/
protected function generateCacheKeyForImageFile($filePath)
{
return sha1($filePath);
return sha1(PathUtility::stripPathSitePrefix($filePath));
}
/**
* Creates the status hash to check whether a file has been changed.
*
* @param string $filePath Image file path, relative to public web path
* @param string $filePath Absolute image file path
*
* @return string the status hash (an SHA1 hash)
*/
......@@ -2681,7 +2681,7 @@ class GraphicalFunctions
* Used in GIFBUILDER
* Uses $this->setup['reduceColors'] for gif/png images and $this->setup['quality'] for jpg images to reduce size/quality if needed.
*
* @param string $file The filename to write to.
* @param string $file The absolute filename to write to
* @return string Returns input filename
* @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::gifBuild()
*/
......@@ -2747,7 +2747,7 @@ class GraphicalFunctions
* Writes the input GDlib image pointer to file
*
* @param resource $destImg The GDlib image resource pointer
* @param string $theImage The filename to write to
* @param string $theImage The absolute file path to write to
* @param int $quality The image quality (for JPEGs)
* @return bool The output of either imageGif, imagePng or imageJpeg based on the filename to write
* @see maskImageOntoImage()
......
......@@ -3819,13 +3819,14 @@ class ContentObjectRenderer implements LoggerAwareInterface
$imageResource = null;
if ($file === 'GIFBUILDER') {
$gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
$theImage = '';
if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
$gifCreator->start($fileArray, $this->data);
$theImage = $gifCreator->gifBuild();
if ($theImage !== '') {
$imageResource = $gifCreator->getImageDimensions(Environment::getPublicPath() . '/' . $theImage);
$imageResource['origFile'] = $theImage;
}
}
$imageResource = $gifCreator->getImageDimensions($theImage);
$imageResource['origFile'] = $theImage;
} else {
if ($file instanceof File) {
$fileObject = $file;
......@@ -3909,7 +3910,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
0 => (int)$processedFileObject->getProperty('width'),
1 => (int)$processedFileObject->getProperty('height'),
2 => $processedFileObject->getExtension(),
3 => $processedFileObject->getPublicUrl(),
3 => Environment::getPublicPath() . '/' . $processedFileObject->getPublicUrl(),
'origFile' => $fileObject->getPublicUrl(),
'origFile_mtime' => $fileObject->getModificationTime(),
// This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder,
......@@ -3929,7 +3930,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
$info = GeneralUtility::makeInstance(GifBuilder::class)->imageMagickConvert($theImage, 'WEB');
$info['origFile'] = $theImage;
// This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder, ln 100ff in order for the setup-array to create a unique filename hash.
$info['origFile_mtime'] = @filemtime($theImage);
$info['origFile_mtime'] = @filemtime(Environment::getPublicPath() . '/' . $theImage);
$imageResource = $info;
} catch (Exception $e) {
// do nothing in case the file path is invalid
......
......@@ -20,6 +20,7 @@ use TYPO3\CMS\Core\Page\AssetCollector;
use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
......@@ -68,10 +69,10 @@ class ImageContentObject extends AbstractContentObject
}
// $info['originalFile'] will be set, when the file is processed by FAL.
// In that case the URL is final and we must not add a prefix
if (!isset($info['originalFile']) && is_file(Environment::getPublicPath() . '/' . $info['3'])) {
$source = $tsfe->absRefPrefix . str_replace('%2F', '/', rawurlencode($info['3']));
if (!isset($info['originalFile']) && is_file($info['3'])) {
$source = $tsfe->absRefPrefix . str_replace('%2F', '/', rawurlencode(PathUtility::stripPathSitePrefix($info['3'])));
} else {
$source = $info[3];
$source = PathUtility::stripPathSitePrefix($info[3]);
}
// Remove file objects for AssetCollector, as it only allows to store scalar values
$infoOriginalFile = $info['originalFile'] ?? null;
......
......@@ -15,6 +15,8 @@
namespace TYPO3\CMS\Frontend\ContentObject;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* Contains IMG_RESOURCE class object.
*/
......@@ -31,7 +33,7 @@ class ImageResourceContentObject extends AbstractContentObject
{
$GLOBALS['TSFE']->lastImgResourceInfo = $this->cObj->getImgResource($conf['file'] ?? '', $conf['file.'] ?? []);
if ($GLOBALS['TSFE']->lastImgResourceInfo) {
$imageResource = $GLOBALS['TSFE']->lastImgResourceInfo[3];
$imageResource = PathUtility::stripPathSitePrefix($GLOBALS['TSFE']->lastImgResourceInfo[3]);
$theValue = isset($conf['stdWrap.']) ? $this->cObj->stdWrap($imageResource, $conf['stdWrap.']) : $imageResource;
} else {
$theValue = '';
......
......@@ -329,27 +329,28 @@ class GifBuilder extends GraphicalFunctions
* Gets filename from fileName() and if file exists in typo3temp/assets/images/ dir it will - of course - not be rendered again.
* Otherwise rendering means calling ->make(), then ->output(), then ->destroy()
*
* @return string The filename for the created GIF/PNG file. The filename will be prefixed "GB_
* @return string The filename for the created GIF/PNG file.
* @see make()
* @see fileName()
*/
public function gifBuild()
{
if ($this->setup) {
// Relative to Environment::getPublicPath()
$gifFileName = $this->fileName('assets/images/');
// File exists
if (!file_exists($gifFileName)) {
// Create temporary directory if not done:
GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/typo3temp/assets/images/');
// Create file:
$this->make();
$this->output($gifFileName);
$this->destroy();
}
return $gifFileName;
if (!$this->setup) {
return '';
}
// Relative to Environment::getPublicPath()
$gifFileName = $this->fileName('assets/images/');
if (!file_exists(Environment::getPublicPath() . '/' . $gifFileName)) {
// Create temporary directory if not done
GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/typo3temp/assets/images/');
// Create file
$this->make();
$this->output(Environment::getPublicPath() . '/' . $gifFileName);
$this->destroy();
}
return '';
return $gifFileName;
}
/**
......
......@@ -21,7 +21,10 @@ use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Resource\StorageRepository;
use TYPO3\CMS\Core\TypoScript\TemplateService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
......@@ -30,6 +33,179 @@ use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
*/
class GifBuilderTest extends FunctionalTestCase
{
private function setupFullTestEnvironment(): void
{
$this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/sys_file_storage.xml');
$this->setUpBackendUserFromFixture(1);
$GLOBALS['TYPO3_REQUEST'] = (new ServerRequest('https://www.example.com/'))
->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE);
$GLOBALS['TSFE'] = $this->createMock(TypoScriptFrontendController::class);
$GLOBALS['TSFE']->tmpl = GeneralUtility::makeInstance(TemplateService::class);
GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/fileadmin');
}
/**
* Sets up Environment to simulate Composer mode and a cli request
*/
private function simulateCliRequestInComposerMode(): void
{
Environment::initialize(
Environment::getContext(),
true,
true,
Environment::getProjectPath(),
Environment::getPublicPath() . '/public',
Environment::getVarPath(),
Environment::getConfigPath(),
Environment::getCurrentScript(),
Environment::isWindows() ? 'WINDOWS' : 'UNIX'
);
}
/**
* @test
*/
public function buildSimpleGifBuilderImageInComposerMode(): void
{
$this->simulateCliRequestInComposerMode();
$conf = [
'XY' => '10,10',
'format' => 'jpg',
];
$gifBuilder = new GifBuilder();
$gifBuilder->start($conf, []);
$gifFileName = $gifBuilder->gifBuild();
self::assertFileDoesNotExist(Environment::getProjectPath() . '/' . $gifFileName);
self::assertFileExists(Environment::getPublicPath() . '/' . $gifFileName);
}
/**
* @test
*/
public function buildImageInCommandLineInterfaceAndComposerMode(): void
{
$this->simulateCliRequestInComposerMode();
$this->setupFullTestEnvironment();
copy(
__DIR__ . '/../Fixtures/Images/kasper-skarhoj1.jpg',
Environment::getPublicPath() . '/fileadmin/kasper-skarhoj-gifbuilder.jpg'
);
$contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$result = $contentObjectRenderer->cObjGetSingle(
'IMAGE',
[
'file' => 'GIFBUILDER',
'file.' => [
'XY' => '[10.w],[10.h]',
'format' => 'jpg',
'10' => 'IMAGE',
'10.' => [
'file' => 'fileadmin/kasper-skarhoj-gifbuilder.jpg',
],
],
]
);
self::assertStringStartsWith('<img src="typo3temp/assets/images/csm_kasper-skarhoj-gifbuilder_', $result);
}
/**
* @test
*/
public function getImageResourceInCommandLineInterfaceAndComposerMode(): void
{
$this->simulateCliRequestInComposerMode();
$this->setupFullTestEnvironment();
copy(
__DIR__ . '/../Fixtures/Images/kasper-skarhoj1.jpg',
Environment::getPublicPath() . '/fileadmin/kasper-skarhoj-gifbuilder-imageresource.jpg'
);
$contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$result = $contentObjectRenderer->cObjGetSingle(
'IMG_RESOURCE',
[
'file' => 'GIFBUILDER',
'file.' => [
'XY' => '[10.w],[10.h]',
'format' => 'jpg',
'10' => 'IMAGE',
'10.' => [
'file' => 'fileadmin/kasper-skarhoj-gifbuilder-imageresource.jpg',
],
],
]
);
self::assertStringStartsWith('typo3temp/assets/images/csm_kasper-skarhoj-gifbuilder-imageresource_', $result);
}
/**
* @test
*/
public function buildImageWithMaskInCommandLineInterfaceAndComposerMode(): void
{
$this->simulateCliRequestInComposerMode();
$this->setupFullTestEnvironment();
copy(
__DIR__ . '/../Fixtures/Images/kasper-skarhoj1.jpg',
Environment::getPublicPath() . '/fileadmin/kasper-skarhoj-gifbuilder.jpg'
);
$contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$result = $contentObjectRenderer->cObjGetSingle(
'IMAGE',
[
'file' => 'GIFBUILDER',
'file.' => [
'XY' => '[10.w],[10.h]',
'format' => 'jpg',
'10' => 'IMAGE',
'10.' => [
'file' => 'fileadmin/kasper-skarhoj-gifbuilder.jpg',
],
'20' => 'IMAGE',
'20.' => [
'offset' => '0,500',
'XY' => '[mask.w],40',
'file' => 'GIFBUILDER',
'file.' => [
'XY' => '400,60',
'backColor' => '#cccccc',
],
'mask' => 'GIFBUILDER',
'mask.' => [
'XY' => '[10.w]+55,60',
'backColor' => '#cccccc',
'10' => 'TEXT',
'10.' => [
'text' => 'Kasper Skårhøj',
'fontColor' => '#111111',
'fontSize' => '20',
'offset' => '20,40',
],
],
],
],
]
);
self::assertStringStartsWith('<img src="typo3temp/', $result);
}
/**
* Check hashes of Images overlayed with other images are idempotent
*
......@@ -37,11 +213,7 @@ class GifBuilderTest extends FunctionalTestCase
*/
public function overlayImagesHasStableHash(): void
{
$this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/sys_file_storage.xml');
$this->setUpBackendUserFromFixture(1);
$GLOBALS['TYPO3_REQUEST'] = (new ServerRequest('https://www.example.com/'))
->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE);
$this->setupFullTestEnvironment();
copy(
__DIR__ . '/../Fixtures/Images/kasper-skarhoj1.jpg',
......@@ -53,14 +225,16 @@ class GifBuilderTest extends FunctionalTestCase
self::assertFalse($file->isMissing());
$fileArray = [
$conf = [
'XY' => '[10.w],[10.h]',
'format' => 'jpg',
'quality' => 88,
'10' => 'IMAGE',
'10.' => [
'file.width' => 300,
'file' => $file,
'file.' => [
'width' => 300,
],
],
'30' => 'IMAGE',
'30.' => [
......@@ -73,13 +247,13 @@ class GifBuilderTest extends FunctionalTestCase
];
$gifBuilder = new GifBuilder();
$gifBuilder->start($fileArray, []);
$gifBuilder->start($conf, []);
$setup1 = $gifBuilder->setup;
$fileName1 = $gifBuilder->gifBuild();
// Recreate a fresh GifBuilder instance, to catch inconsistencies in hashing for different instances
$gifBuilder = new GifBuilder();
$gifBuilder->start($fileArray, []);
$gifBuilder->start($conf, []);
$setup2 = $gifBuilder->setup;
$fileName2 = $gifBuilder->gifBuild();
......
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