diff --git a/typo3/sysext/core/Classes/Resource/ProcessedFile.php b/typo3/sysext/core/Classes/Resource/ProcessedFile.php index a3dce434486d004c9683396bbf2f3ace862f8c0f..6fee23181cacbbaca586cc6e058726fba4ffe6f6 100644 --- a/typo3/sysext/core/Classes/Resource/ProcessedFile.php +++ b/typo3/sysext/core/Classes/Resource/ProcessedFile.php @@ -16,6 +16,7 @@ namespace TYPO3\CMS\Core\Resource; use TYPO3\CMS\Core\Resource\Processing\TaskTypeRegistry; +use TYPO3\CMS\Core\Resource\Service\ConfigurationService; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; @@ -143,6 +144,7 @@ class ProcessedFile extends AbstractFile protected function reconstituteFromDatabaseRecord(array $databaseRow) { $this->taskType = $this->taskType ?: $databaseRow['task_type']; + // @todo In case the original configuration contained file objects the reconstitution fails. See ConfigurationService->serialize() $this->processingConfiguration = $this->processingConfiguration ?: unserialize($databaseRow['configuration'] ?? ''); $this->originalFileSha1 = $databaseRow['originalfilesha1']; @@ -399,7 +401,7 @@ class ProcessedFile extends AbstractFile $properties['name'] = $this->getName(); } - $properties['configuration'] = serialize($this->processingConfiguration); + $properties['configuration'] = (new ConfigurationService())->serialize($this->processingConfiguration); return array_merge($properties, [ 'storage' => $this->getStorage()->getUid(), diff --git a/typo3/sysext/core/Classes/Resource/ProcessedFileRepository.php b/typo3/sysext/core/Classes/Resource/ProcessedFileRepository.php index 46b39b42b32c98bb3256ad3a0026728c0aeab358..d998a4d2ab3ef9d5962da80f70ade4c12dd112e5 100644 --- a/typo3/sysext/core/Classes/Resource/ProcessedFileRepository.php +++ b/typo3/sysext/core/Classes/Resource/ProcessedFileRepository.php @@ -20,6 +20,7 @@ use Psr\Log\LoggerAwareTrait; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Imaging\ImageManipulation\Area; +use TYPO3\CMS\Core\Resource\Service\ConfigurationService; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -240,7 +241,7 @@ class ProcessedFileRepository extends AbstractRepository implements LoggerAwareI $queryBuilder->expr()->eq('task_type', $queryBuilder->createNamedParameter($taskType)), $queryBuilder->expr()->eq( 'configurationsha1', - $queryBuilder->createNamedParameter(sha1(serialize($configuration))) + $queryBuilder->createNamedParameter(sha1((new ConfigurationService())->serialize($configuration))) ) ) ->executeQuery() diff --git a/typo3/sysext/core/Classes/Resource/Processing/AbstractTask.php b/typo3/sysext/core/Classes/Resource/Processing/AbstractTask.php index 8a66263876d632e5d52992d3e201354b02894c77..8a41c9d3e840529818854f4667b44960a8f0660c 100644 --- a/typo3/sysext/core/Classes/Resource/Processing/AbstractTask.php +++ b/typo3/sysext/core/Classes/Resource/Processing/AbstractTask.php @@ -18,6 +18,7 @@ namespace TYPO3\CMS\Core\Resource\Processing; use TYPO3\CMS\Core\Resource; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\ProcessedFile; +use TYPO3\CMS\Core\Resource\Service\ConfigurationService; /** * Abstract base implementation of a task. @@ -91,7 +92,7 @@ abstract class AbstractTask implements TaskInterface return [ $this->getSourceFile()->getUid(), $this->getType() . '.' . $this->getName() . $this->getSourceFile()->getModificationTime(), - serialize($this->configuration), + (new ConfigurationService())->serialize($this->configuration), ]; } diff --git a/typo3/sysext/core/Classes/Resource/Service/ConfigurationService.php b/typo3/sysext/core/Classes/Resource/Service/ConfigurationService.php new file mode 100644 index 0000000000000000000000000000000000000000..a8f45eabf2cc4c470be91374e3e19a661760a481 --- /dev/null +++ b/typo3/sysext/core/Classes/Resource/Service/ConfigurationService.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Resource\Service; + +use TYPO3\CMS\Core\Resource\FileInterface; + +/** + * Resources can contain configurations: For example to define image dimensions or + * image masking. Resource configurations form part of the object identity and are + * used to create an object signature that itself is used to cache objects but NOT + * to reconstruct them. To prevent objects to be reconstructed they MUST NOT be + * serialized. This is why an object signature is obtained by serializing its array + * rather than serializing it directly. But attention...as well the objects array + * MUST NOT contain resource objects which could be the case when a configuration + * defines image masking. Here this service comes into play: it serializes any + * object configuration, as well those containing resources. + */ +class ConfigurationService +{ + public function serialize(array $configuration): string + { + return serialize($this->makeSerializable($configuration)); + } + + /** + * Recursively substitute file objects with their array representation. + */ + protected function makeSerializable(array $configuration): array + { + return array_map(function ($value) { + if (is_array($value)) { + return $this->makeSerializable($value); + } + if ($value instanceof FileInterface) { + return $value->toArray(); + } + return $value; + }, $configuration); + } +} diff --git a/typo3/sysext/core/Tests/Functional/Resource/Fixtures/ProcessedFileTest.jpg b/typo3/sysext/core/Tests/Functional/Resource/Fixtures/ProcessedFileTest.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/typo3/sysext/core/Tests/Functional/Resource/ProcessedFileTest.php b/typo3/sysext/core/Tests/Functional/Resource/ProcessedFileTest.php new file mode 100644 index 0000000000000000000000000000000000000000..729f46e98367d97fb15049a234dd1a128b8e0bb7 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Resource/ProcessedFileTest.php @@ -0,0 +1,71 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Tests\Functional\Resource; + +use TYPO3\CMS\Core\Core\Bootstrap; +use TYPO3\CMS\Core\Resource\ProcessedFile; +use TYPO3\CMS\Core\Resource\ResourceFactory; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +class ProcessedFileTest extends FunctionalTestCase +{ + private const TEST_IMAGE = 'fileadmin/ProcessedFileTest.jpg'; + + /** + * @var array<string, non-empty-string> + */ + protected array $pathsToProvideInTestInstance = [ + 'typo3/sysext/core/Tests/Functional/Resource/Fixtures/ProcessedFileTest.jpg' => 'fileadmin/ProcessedFileTest.jpg', + ]; + + public function setUp(): void + { + parent::setUp(); + $this->importCSVDataSet(__DIR__ . '/../Fixtures/be_users.csv'); + $this->setUpBackendUser(1); + Bootstrap::initializeLanguageObject(); + } + + /** + * @test + */ + public function processedFileArrayCanBeSerialized(): void + { + $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); + $originalFile = $resourceFactory->retrieveFileOrFolderObject(self::TEST_IMAGE); + $someProcessedFile = new ProcessedFile( + $originalFile, + ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, + [] + ); + $processedFile = new ProcessedFile( + $originalFile, + ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, + [ + 'width' => '2000c', + 'height' => '300c-60', + 'm' => [ + 'bgImg' => $someProcessedFile, + 'mask' => $someProcessedFile, + ], + ], + ); + serialize($processedFile->toArray()); + } +} diff --git a/typo3/sysext/core/Tests/Unit/Resource/Service/ConfigurationServiceTest.php b/typo3/sysext/core/Tests/Unit/Resource/Service/ConfigurationServiceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e25f1bf3372b1e44b1dcdb5092cdbd2874ceaf39 --- /dev/null +++ b/typo3/sysext/core/Tests/Unit/Resource/Service/ConfigurationServiceTest.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Tests\Unit\Resource\Service; + +use TYPO3\CMS\Core\Resource\ProcessedFile; +use TYPO3\CMS\Core\Resource\Service\ConfigurationService; +use TYPO3\TestingFramework\Core\Unit\UnitTestCase; + +class ConfigurationServiceTest extends UnitTestCase +{ + /** + * @test + */ + public function serializeSubstitutesFileObject(): void + { + $fileMock = $this->createMock(ProcessedFile::class); + $fileMock->method('toArray')->willReturn(['id' => '1:test.jpg']); + $configuration = [ + 'width' => '2000c', + 'height' => '300c-60', + 'foo' => $fileMock, + 'maskImages' => [ + 'maskImage' => $fileMock, + 'backgroundImage' => $fileMock, + 'bar' => 'bar1', + ], + ]; + $expected = [ + 'width' => '2000c', + 'height' => '300c-60', + 'foo' => $fileMock->toArray(), + 'maskImages' => [ + 'maskImage' => $fileMock->toArray(), + 'backgroundImage' => $fileMock->toArray(), + 'bar' => 'bar1', + ], + ]; + self::assertSame(serialize($expected), (new ConfigurationService())->serialize($configuration)); + } +}