diff --git a/typo3/sysext/core/Classes/Resource/File.php b/typo3/sysext/core/Classes/Resource/File.php index cd46b6a549677628d4423b1c196cb313d432ea26..eaa31ddb5898a3bdbc08ac56a4a986ea5893172a 100644 --- a/typo3/sysext/core/Classes/Resource/File.php +++ b/typo3/sysext/core/Classes/Resource/File.php @@ -21,23 +21,6 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; */ class File extends AbstractFile { - /** - * @var bool - */ - protected $metaDataLoaded = false; - - /** - * @var array - */ - protected $metaDataProperties = []; - - /** - * Set to TRUE while this file is being indexed - used to prevent some endless loops - * - * @var bool - */ - protected $indexingInProgress = false; - /** * Contains the names of all properties that have been update since the * instantiation of this object @@ -46,6 +29,11 @@ class File extends AbstractFile */ protected $updatedProperties = []; + /** + * @var MetaDataAspect + */ + private $metaDataAspect; + /** * Constructor for a file object. Should normally not be used directly, use * the corresponding factory methods instead. @@ -60,9 +48,9 @@ class File extends AbstractFile $this->name = $fileData['name'] ?? ''; $this->properties = $fileData; $this->storage = $storage; + if (!empty($metaData)) { - $this->metaDataLoaded = true; - $this->metaDataProperties = $metaData; + $this->getMetaData()->add($metaData); } } @@ -80,8 +68,7 @@ class File extends AbstractFile if (parent::hasProperty($key)) { return parent::getProperty($key); } - $metaData = $this->_getMetaData(); - return $metaData[$key] ?? null; + return $this->getMetaData()[$key]; } /** @@ -91,10 +78,10 @@ class File extends AbstractFile * @param string $key * @return bool */ - public function hasProperty($key) + public function hasProperty($key): bool { if (!parent::hasProperty($key)) { - return array_key_exists($key, $this->_getMetaData()); + return isset($this->getMetaData()[$key]); } return true; } @@ -104,9 +91,9 @@ class File extends AbstractFile * * @return array */ - public function getProperties() + public function getProperties(): array { - return array_merge(parent::getProperties(), array_diff_key($this->_getMetaData(), parent::getProperties())); + return array_merge(parent::getProperties(), array_diff_key($this->getMetaData()->get(), parent::getProperties())); } /** @@ -114,13 +101,15 @@ class File extends AbstractFile * * @return array * @internal + * @deprecated */ public function _getMetaData() { - if (!$this->metaDataLoaded) { - $this->loadMetaData(); - } - return $this->metaDataProperties; + trigger_error( + 'The method ' . __CLASS__ . '::' . __METHOD__ . ' has been marked as deprecated and will be removed in TYPO3 v11. Use `->getMetaData()->get()` instead.', + E_USER_DEPRECATED + ); + return $this->getMetaData()->get(); } /****************** @@ -174,19 +163,6 @@ class File extends AbstractFile return true; } - /** - * Loads MetaData from Repository - */ - protected function loadMetaData() - { - if (!$this->indexingInProgress) { - $this->indexingInProgress = true; - $this->metaDataProperties = $this->getMetaDataRepository()->findByFile($this); - $this->metaDataLoaded = true; - $this->indexingInProgress = false; - } - } - /** * Updates the properties of this file, e.g. after re-indexing or moving it. * By default, only properties that exist as a key in the $properties array @@ -232,18 +208,6 @@ class File extends AbstractFile } } - /** - * Updates MetaData properties - * - * @internal Do not use outside the FileAbstraction Layer classes - * - * @param array $properties - */ - public function _updateMetaDataProperties(array $properties) - { - $this->metaDataProperties = array_merge($this->metaDataProperties, $properties); - } - /** * Returns the names of all properties that have been updated in this record * @@ -369,14 +333,6 @@ class File extends AbstractFile return $this->getStorage()->getPublicUrl($this, $relativeToCurrentScript); } - /** - * @return Index\MetaDataRepository - */ - protected function getMetaDataRepository() - { - return GeneralUtility::makeInstance(Index\MetaDataRepository::class); - } - /** * @return Index\FileIndexRepository */ @@ -385,15 +341,6 @@ class File extends AbstractFile return GeneralUtility::makeInstance(Index\FileIndexRepository::class); } - /** - * @param bool $indexingState - * @internal Only for usage in Indexer - */ - public function setIndexingInProgess($indexingState) - { - $this->indexingInProgress = (bool)$indexingState; - } - /** * @param $key * @internal Only for use in Repositories and indexer @@ -403,4 +350,17 @@ class File extends AbstractFile { return parent::getProperty($key); } + + /** + * Loads the metadata of a file in an encapsulated aspect + * + * @return MetaDataAspect + */ + public function getMetaData(): MetaDataAspect + { + if ($this->metaDataAspect === null) { + $this->metaDataAspect = GeneralUtility::makeInstance(MetaDataAspect::class, $this); + } + return $this->metaDataAspect; + } } diff --git a/typo3/sysext/core/Classes/Resource/Index/Indexer.php b/typo3/sysext/core/Classes/Resource/Index/Indexer.php index e8d7f7ff9f39084e9a7b3478aeeabc5f8bb7532d..041a527c5a644e93bb254d4665009ef2feff1af4 100644 --- a/typo3/sysext/core/Classes/Resource/Index/Indexer.php +++ b/typo3/sysext/core/Classes/Resource/Index/Indexer.php @@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Resource\ResourceStorage; +use TYPO3\CMS\Core\Resource\Service\ExtractorService; use TYPO3\CMS\Core\Type\File\ImageInfo; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -43,9 +44,9 @@ class Indexer protected $storage; /** - * @var ExtractorInterface[] + * @var ExtractorService */ - protected $extractionServices; + protected $extractorService; /** * @param ResourceStorage $storage @@ -62,19 +63,25 @@ class Indexer * @return File * @throws \InvalidArgumentException */ - public function createIndexEntry($identifier) + public function createIndexEntry($identifier): File { if (!isset($identifier) || !is_string($identifier) || $identifier === '') { - throw new \InvalidArgumentException('Invalid file identifier given. It must be of type string and not empty. "' . gettype($identifier) . '" given.', 1401732565); + throw new \InvalidArgumentException( + 'Invalid file identifier given. It must be of type string and not empty. "' . gettype($identifier) . '" given.', + 1401732565 + ); } + $fileProperties = $this->gatherFileInformationArray($identifier); $record = $this->getFileIndexRepository()->addRaw($fileProperties); + $fileObject = $this->getResourceFactory()->getFileObject($record['uid'], $record); - $this->extractRequiredMetaData($fileObject); + $metaData = $this->extractRequiredMetaData($fileObject); if ($this->storage->autoExtractMetadataEnabled()) { - $this->extractMetaData($fileObject); + $metaData = array_merge($metaData, $this->getExtractorService()->extractMetaData($fileObject)); } + $fileObject->getMetaData()->add($metaData)->save(); return $fileObject; } @@ -83,13 +90,21 @@ class Indexer * Update index entry * * @param File $fileObject + * @return File */ - public function updateIndexEntry(File $fileObject) + public function updateIndexEntry(File $fileObject): File { $updatedInformation = $this->gatherFileInformationArray($fileObject->getIdentifier()); $fileObject->updateProperties($updatedInformation); + $this->getFileIndexRepository()->update($fileObject); - $this->extractRequiredMetaData($fileObject); + $metaData = $this->extractRequiredMetaData($fileObject); + + if ($this->storage->autoExtractMetadataEnabled()) { + $metaData = array_merge($metaData, $this->getExtractorService()->extractMetaData($fileObject)); + } + $fileObject->getMetaData()->add($metaData)->save(); + return $fileObject; } /** @@ -135,43 +150,15 @@ class Indexer */ public function extractMetaData(File $fileObject) { - $newMetaData = [ - 0 => $fileObject->_getMetaData() - ]; - - // Loop through available extractors and fetch metadata for the given file. - foreach ($this->getExtractionServices() as $service) { - if ($this->isFileTypeSupportedByExtractor($fileObject, $service) && $service->canProcess($fileObject)) { - $newMetaData[$service->getPriority()] = $service->extractMetaData($fileObject, $newMetaData); - } - } + $metaData = array_merge([ + $fileObject->getMetaData()->get() + ], $this->getExtractorService()->extractMetaData($fileObject)); - // Sort metadata by priority so that merging happens in order of precedence. - ksort($newMetaData); + $fileObject->getMetaData()->add($metaData)->save(); - // Merge the collected metadata. - $metaData = []; - foreach ($newMetaData as $data) { - $metaData = array_merge($metaData, $data); - } - $fileObject->_updateMetaDataProperties($metaData); - $this->getMetaDataRepository()->update($fileObject->getUid(), $metaData); $this->getFileIndexRepository()->updateIndexingTime($fileObject->getUid()); } - /** - * Get available extraction services - * - * @return ExtractorInterface[] - */ - protected function getExtractionServices() - { - if ($this->extractionServices === null) { - $this->extractionServices = $this->getExtractorRegistry()->getExtractorsWithDriverSupport($this->storage->getDriverType()); - } - return $this->extractionServices; - } - /** * Since by now all files in filesystem have been looked at it is save to assume, * that files that are in indexed but not touched in this run are missing @@ -280,27 +267,28 @@ class Indexer * This should be called after every "content" update and "record" creation * * @param File $fileObject + * @return array */ - protected function extractRequiredMetaData(File $fileObject) + protected function extractRequiredMetaData(File $fileObject): array { + $metaData = []; + // since the core desperately needs image sizes in metadata table do this manually // prevent doing this for remote storages, remote storages must provide the data with extractors - if ($fileObject->getType() == File::FILETYPE_IMAGE && $this->storage->getDriverType() === 'Local') { + if ($fileObject->getType() === File::FILETYPE_IMAGE && $this->storage->getDriverType() === 'Local') { $rawFileLocation = $fileObject->getForLocalProcessing(false); $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $rawFileLocation); $metaData = [ 'width' => $imageInfo->getWidth(), 'height' => $imageInfo->getHeight(), ]; - $this->getMetaDataRepository()->update($fileObject->getUid(), $metaData); - $fileObject->_updateMetaDataProperties($metaData); } + + return $metaData; } /**************************** - * * UTILITY - * ****************************/ /** @@ -359,7 +347,6 @@ class Indexer * Therefore a mapping must happen. * * @param array $fileInfo - * * @return array */ protected function transformFromDriverFileInfoArrayToFileObjectFormat(array $fileInfo) @@ -416,12 +403,13 @@ class Indexer } /** - * Returns an instance of the FileIndexRepository - * - * @return ExtractorRegistry + * @return ExtractorService */ - protected function getExtractorRegistry() + protected function getExtractorService(): ExtractorService { - return ExtractorRegistry::getInstance(); + if ($this->extractorService === null) { + $this->extractorService = GeneralUtility::makeInstance(ExtractorService::class); + } + return $this->extractorService; } } diff --git a/typo3/sysext/core/Classes/Resource/Index/MetaDataRepository.php b/typo3/sysext/core/Classes/Resource/Index/MetaDataRepository.php index 011a75813020d63e6fa1a310aaa3bcccb48db6ca..ba7853bd5edce5dd8ac447174c1bec5eefa3708b 100644 --- a/typo3/sysext/core/Classes/Resource/Index/MetaDataRepository.php +++ b/typo3/sysext/core/Classes/Resource/Index/MetaDataRepository.php @@ -109,7 +109,7 @@ class MetaDataRepository implements SingletonInterface ->fetch(); if (empty($record)) { - $record = $this->createMetaDataRecord($uid); + return []; } $passedData = new \ArrayObject($record); @@ -135,6 +135,7 @@ class MetaDataRepository implements SingletonInterface 'cruser_id' => isset($GLOBALS['BE_USER']->user['uid']) ? (int)$GLOBALS['BE_USER']->user['uid'] : 0, 'l10n_diffsource' => '' ]; + $additionalFields = array_intersect_key($additionalFields, $this->getTableFields()); $emptyRecord = array_merge($emptyRecord, $additionalFields); $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableName); @@ -162,13 +163,7 @@ class MetaDataRepository implements SingletonInterface */ public function update($fileUid, array $data) { - if (empty($this->tableFields)) { - $this->tableFields = GeneralUtility::makeInstance(ConnectionPool::class) - ->getConnectionForTable($this->tableName) - ->getSchemaManager() - ->listTableColumns($this->tableName); - } - $updateRow = array_intersect_key($data, $this->tableFields); + $updateRow = array_intersect_key($data, $this->getTableFields()); if (array_key_exists('uid', $updateRow)) { unset($updateRow['uid']); } @@ -185,13 +180,13 @@ class MetaDataRepository implements SingletonInterface } } $connection->update( - $this->tableName, - $updateRow, - [ - 'uid' => (int)$row['uid'] - ], - $types - ); + $this->tableName, + $updateRow, + [ + 'uid' => (int)$row['uid'] + ], + $types + ); $this->emitRecordUpdatedSignal(array_merge($row, $updateRow)); } @@ -277,6 +272,23 @@ class MetaDataRepository implements SingletonInterface $this->getSignalSlotDispatcher()->dispatch(self::class, 'recordDeleted', [$fileUid]); } + /** + * Gets the fields that are available in the table + * + * @return array + */ + protected function getTableFields(): array + { + if (empty($this->tableFields)) { + $this->tableFields = GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionForTable($this->tableName) + ->getSchemaManager() + ->listTableColumns($this->tableName); + } + + return $this->tableFields; + } + /** * @return MetaDataRepository */ diff --git a/typo3/sysext/core/Classes/Resource/MetaDataAspect.php b/typo3/sysext/core/Classes/Resource/MetaDataAspect.php new file mode 100644 index 0000000000000000000000000000000000000000..45baeeaf2c14d0ea555d8fac56af5fbbc1936fff --- /dev/null +++ b/typo3/sysext/core/Classes/Resource/MetaDataAspect.php @@ -0,0 +1,220 @@ +<?php +declare(strict_types = 1); + +namespace TYPO3\CMS\Core\Resource; + +/* +* 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! +*/ + +use TYPO3\CMS\Core\Resource\Index\MetaDataRepository; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Aspect that takes care of a file's metadata + */ +class MetaDataAspect implements \ArrayAccess, \Countable, \Iterator +{ + /** + * @var File + */ + private $file; + + /** + * @var array + */ + private $metaData = []; + + /** + * This flag is used to treat a possible recursion between $this->get() and $this->file->getUid() + * + * @var bool + */ + private $loaded = false; + + /** + * @var int + */ + private $indexPosition = 0; + + /** + * Constructor + * + * @param File $file + */ + public function __construct(File $file) + { + $this->file = $file; + } + + /** + * Adds already known metadata to the aspect + * + * @param array $metaData + * @return self + * @internal + */ + public function add(array $metaData): self + { + $this->loaded = true; + $this->metaData = array_merge($this->metaData, $metaData); + + return $this; + } + + /** + * Gets the metadata of a file. If not metadata is loaded yet, the database gets queried + * + * @return array + */ + public function get(): array + { + if (!$this->loaded) { + $this->loaded = true; + $this->metaData = $this->loadFromRepository(); + } + return $this->metaData; + } + + /** + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset): bool + { + return array_key_exists($offset, $this->get()); + } + + /** + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->get()[$offset]; + } + + /** + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value): void + { + $this->loaded = true; + $this->metaData[$offset] = $value; + } + + /** + * @param mixed $offset + */ + public function offsetUnset($offset): void + { + $this->metaData[$offset] = null; + } + + /** + * @return int + */ + public function count(): int + { + return count($this->get()); + } + + /** + * Resets the internal iterator counter + */ + public function rewind(): void + { + $this->indexPosition = 0; + } + + /** + * Gets the current value of iteration + * + * @return mixed + */ + public function current() + { + $key = array_keys($this->metaData)[$this->indexPosition]; + return $this->metaData[$key]; + } + + /** + * Returns the key of the current iteration + * + * @return string + */ + public function key(): string + { + return array_keys($this->metaData)[$this->indexPosition]; + } + + /** + * Increases the index for iteration + */ + public function next(): void + { + ++$this->indexPosition; + } + + /** + * @return bool + */ + public function valid(): bool + { + $key = array_keys($this->metaData)[$this->indexPosition]; + return array_key_exists($key, $this->metaData); + } + + /** + * Creates new or updates existing meta data + * + * @internal + */ + public function save(): void + { + $metaDataInDatabase = $this->loadFromRepository(); + if ($metaDataInDatabase === []) { + $this->metaData = $this->getMetaDataRepository()->createMetaDataRecord($this->file->getUid(), $this->metaData); + } else { + $this->getMetaDataRepository()->update($this->file->getUid(), $this->metaData); + $this->metaData = array_merge($metaDataInDatabase, $this->metaData); + } + } + + /** + * Removes a meta data record + * + * @internal + */ + public function remove(): void + { + $this->getMetaDataRepository()->removeByFileUid($this->file->getUid()); + $this->metaData = []; + } + + /** + * @return MetaDataRepository + */ + protected function getMetaDataRepository(): MetaDataRepository + { + return GeneralUtility::makeInstance(MetaDataRepository::class); + } + + /** + * @return array + */ + protected function loadFromRepository(): array + { + return $this->getMetaDataRepository()->findByFileUid((int)$this->file->getUid()); + } +} diff --git a/typo3/sysext/core/Classes/Resource/Processing/FileDeletionAspect.php b/typo3/sysext/core/Classes/Resource/Processing/FileDeletionAspect.php index 17a947c7a1fceca1e1eede705e5e9c197d6028e4..b3c1b143f23e5f00041876c265833a04a62b38d3 100644 --- a/typo3/sysext/core/Classes/Resource/Processing/FileDeletionAspect.php +++ b/typo3/sysext/core/Classes/Resource/Processing/FileDeletionAspect.php @@ -125,7 +125,7 @@ class FileDeletionAspect protected function cleanupCategoryReferences(File $fileObject) { // Retrieve the file metadata uid which is different from the file uid. - $metadataProperties = $fileObject->_getMetaData(); + $metadataProperties = $fileObject->getMetaData()->get(); $metaDataUid = $metadataProperties['_ORIG_uid'] ?? $metadataProperties['uid']; GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_category_record_mm') diff --git a/typo3/sysext/core/Classes/Resource/ResourceStorage.php b/typo3/sysext/core/Classes/Resource/ResourceStorage.php index abf8e66177ee32ed1645c36b428aa798c417f8ca..1fb9c6ca29ff715c7947e857453412936bbe6534 100644 --- a/typo3/sysext/core/Classes/Resource/ResourceStorage.php +++ b/typo3/sysext/core/Classes/Resource/ResourceStorage.php @@ -1193,9 +1193,6 @@ class ResourceStorage implements ResourceStorageInterface if ($replaceExisting && $file instanceof File) { $this->getIndexer()->updateIndexEntry($file); } - if ($this->autoExtractMetadataEnabled()) { - $this->getIndexer()->extractMetaData($file); - } $this->emitPostFileAddSignal($file, $targetFolder); @@ -1971,9 +1968,6 @@ class ResourceStorage implements ResourceStorageInterface if ($file instanceof File) { $this->getIndexer()->updateIndexEntry($file); } - if ($this->autoExtractMetadataEnabled()) { - $this->getIndexer()->extractMetaData($file); - } $this->emitPostFileReplaceSignal($file, $localFilePath); return $file; diff --git a/typo3/sysext/core/Classes/Resource/Service/ExtractorService.php b/typo3/sysext/core/Classes/Resource/Service/ExtractorService.php new file mode 100644 index 0000000000000000000000000000000000000000..166bc14ab31f6e825e3b2e668b3d6474d6acede3 --- /dev/null +++ b/typo3/sysext/core/Classes/Resource/Service/ExtractorService.php @@ -0,0 +1,96 @@ +<?php +declare(strict_types = 1); + +namespace TYPO3\CMS\Core\Resource\Service; + +/* +* 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! +*/ + +use TYPO3\CMS\Core\Resource\File; +use TYPO3\CMS\Core\Resource\Index\ExtractorInterface; +use TYPO3\CMS\Core\Resource\Index\ExtractorRegistry; + +/** + * Service class to extract metadata + */ +class ExtractorService +{ + /** + * @var ExtractorInterface[][] + */ + private $extractionServices; + + /** + * @param File $fileObject + * @return array + */ + public function extractMetaData(File $fileObject): array + { + $newMetaData = []; + // Loop through available extractors and fetch metadata for the given file. + foreach ($this->getExtractionServices($fileObject->getStorage()->getDriverType()) as $service) { + if ($this->isFileTypeSupportedByExtractor($fileObject, $service) && $service->canProcess($fileObject)) { + $newMetaData[$service->getPriority()] = $service->extractMetaData($fileObject, $newMetaData); + } + } + // Sort metadata by priority so that merging happens in order of precedence. + ksort($newMetaData); + // Merge the collected metadata. + $metaData = [[]]; + foreach ($newMetaData as $data) { + $metaData[] = $data; + } + return array_filter(array_merge(...$metaData)); + } + + /** + * Get available extraction services + * + * @param string $driverType + * @return ExtractorInterface[] + */ + protected function getExtractionServices(string $driverType): array + { + if (empty($this->extractionServices[$driverType])) { + $this->extractionServices[$driverType] = $this->getExtractorRegistry()->getExtractorsWithDriverSupport($driverType); + } + return $this->extractionServices[$driverType]; + } + + /** + * Check whether the extractor service supports this file according to file type restrictions. + * + * @param File $file + * @param ExtractorInterface $extractor + * @return bool + */ + private function isFileTypeSupportedByExtractor(File $file, ExtractorInterface $extractor): bool + { + $isSupported = true; + $fileTypeRestrictions = $extractor->getFileTypeRestrictions(); + if (!empty($fileTypeRestrictions) && !in_array($file->getType(), $fileTypeRestrictions, true)) { + $isSupported = false; + } + return $isSupported; + } + + /** + * Returns an instance of the FileIndexRepository + * + * @return ExtractorRegistry + */ + protected function getExtractorRegistry(): ExtractorRegistry + { + return ExtractorRegistry::getInstance(); + } +} diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85895-DeprecateFile_getMetaData.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85895-DeprecateFile_getMetaData.rst new file mode 100644 index 0000000000000000000000000000000000000000..61c102503effe2b17eb281eb9e6f9bd92d88b633 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85895-DeprecateFile_getMetaData.rst @@ -0,0 +1,32 @@ +.. include:: ../../Includes.txt + +==================================================== +Deprecation: #85895 - Deprecate File::_getMetaData() +==================================================== + +See :issue:`85895` + +Description +=========== + +The internal method :php:`File::_getMetaData()` which is used to fetch meta data of a file has been marked as deprecated. This method has been superseded by the :php:`MetaDataAspect`. + + +Impact +====== + +Using this method will trigger a deprecation entry. + + +Affected Installations +====================== + +Any 3rd party extension calling :php:`_getMetaData()` is affected. + + +Migration +========= + +To fetch the meta data, call :php:`$fileObject->getMetaData()->get()` instead. + +.. index:: FAL, PHP-API, FullyScanned, ext:core diff --git a/typo3/sysext/core/Tests/Unit/Resource/FileTest.php b/typo3/sysext/core/Tests/Unit/Resource/FileTest.php index 32232de639b5a2c5d64d45637307c9b6bc2b74ae..2f31420d289bb3ac862d1e501ef574ad1080d512 100644 --- a/typo3/sysext/core/Tests/Unit/Resource/FileTest.php +++ b/typo3/sysext/core/Tests/Unit/Resource/FileTest.php @@ -17,6 +17,7 @@ namespace TYPO3\CMS\Core\Tests\Unit\Resource; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\Index\MetaDataRepository; +use TYPO3\CMS\Core\Resource\MetaDataAspect; use TYPO3\CMS\Core\Resource\ResourceStorage; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; @@ -250,9 +251,20 @@ class FileTest extends UnitTestCase */ public function hasPropertyReturnsTrueIfMetadataPropertyExists(): void { - $fixture = $this->getAccessibleMock(File::class, ['dummy'], [[], $this->storageMock]); - $fixture->_set('metaDataLoaded', true); - $fixture->_set('metaDataProperties', ['testproperty' => 'testvalue']); + $fixture = $this->getMockBuilder(File::class) + ->setConstructorArgs([[], $this->storageMock]) + ->setMethods(['getMetaData']) + ->getMock(); + + $metaDataAspectMock = $this->getMockBuilder(MetaDataAspect::class) + ->setConstructorArgs([$fixture]) + ->setMethods(['get']) + ->getMock(); + + $metaDataAspectMock->expects($this->any())->method('get')->willReturn(['testproperty' => 'testvalue']); + $fixture->expects($this->any())->method('getMetaData')->willReturn($metaDataAspectMock); + $this->assertTrue($fixture->hasProperty('testproperty')); + $this->assertSame('testvalue', $fixture->getProperty('testproperty')); } } diff --git a/typo3/sysext/core/Tests/Unit/Resource/Index/IndexerTest.php b/typo3/sysext/core/Tests/Unit/Resource/Index/IndexerTest.php index 7af158f532727cb0e77aadc72fdfb8ccfe2dea0f..d16d7b59fb3022518083de81284566c73f598765 100644 --- a/typo3/sysext/core/Tests/Unit/Resource/Index/IndexerTest.php +++ b/typo3/sysext/core/Tests/Unit/Resource/Index/IndexerTest.php @@ -15,8 +15,10 @@ namespace TYPO3\CMS\Core\Tests\Unit\Resource\Index; */ use TYPO3\CMS\Core\Resource\File; -use TYPO3\CMS\Core\Resource\Index\ExtractorInterface; +use TYPO3\CMS\Core\Resource\Index\FileIndexRepository; use TYPO3\CMS\Core\Resource\Index\Indexer; +use TYPO3\CMS\Core\Resource\ResourceStorage; +use TYPO3\CMS\Core\Resource\Service\ExtractorService; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; /** @@ -27,81 +29,30 @@ class IndexerTest extends UnitTestCase /** * @test */ - public function isFileTypeSupportedByExtractorReturnsFalesForFileTypeTextAndExtractorLimitedToFileTypeImage() + public function extractMetaDataCallsSubsequentMethodsWithCorrectArguments(): void { - $mockStorage = $this->createMock(\TYPO3\CMS\Core\Resource\ResourceStorage::class); - $mockFile = $this->createMock(File::class); - $mockFile->expects($this->any())->method('getType')->will($this->returnValue( - File::FILETYPE_TEXT - )); + $mockStorage = $this->createMock(ResourceStorage::class); - $mockExtractor = $this->createMock(ExtractorInterface::class); - $mockExtractor->expects($this->any())->method('getFileTypeRestrictions')->will($this->returnValue( - [File::FILETYPE_IMAGE] - )); + /** @var Indexer|\PHPUnit\Framework\MockObject\MockObject $subject */ + $subject = $this->getMockBuilder(Indexer::class) + ->setConstructorArgs([$mockStorage]) + ->setMethods(['getFileIndexRepository', 'extractRequiredMetaData', 'getExtractorService']) + ->getMock(); - $method = new \ReflectionMethod(Indexer::class, 'isFileTypeSupportedByExtractor'); - $method->setAccessible(true); - $arguments = [ - $mockFile, - $mockExtractor - ]; + $indexFileRepositoryMock = $this->createMock(FileIndexRepository::class); + $subject->expects($this->any())->method('getFileIndexRepository')->willReturn($indexFileRepositoryMock); - $result = $method->invokeArgs(new Indexer($mockStorage), $arguments); - $this->assertFalse($result); - } - - /** - * @test - */ - public function isFileTypeSupportedByExtractorReturnsTrueForFileTypeImageAndExtractorLimitedToFileTypeImage() - { - $mockStorage = $this->createMock(\TYPO3\CMS\Core\Resource\ResourceStorage::class); - $mockFile = $this->createMock(File::class); - $mockFile->expects($this->any())->method('getType')->will($this->returnValue( - File::FILETYPE_IMAGE - )); - - $mockExtractor = $this->createMock(ExtractorInterface::class); - $mockExtractor->expects($this->any())->method('getFileTypeRestrictions')->will($this->returnValue( - [File::FILETYPE_IMAGE] - )); - - $method = new \ReflectionMethod(Indexer::class, 'isFileTypeSupportedByExtractor'); - $method->setAccessible(true); - $arguments = [ - $mockFile, - $mockExtractor - ]; - - $result = $method->invokeArgs(new Indexer($mockStorage), $arguments); - $this->assertTrue($result); - } - - /** - * @test - */ - public function isFileTypeSupportedByExtractorReturnsTrueForFileTypeTextAndExtractorHasNoFileTypeLimitation() - { - $mockStorage = $this->createMock(\TYPO3\CMS\Core\Resource\ResourceStorage::class); - $mockFile = $this->createMock(File::class); - $mockFile->expects($this->any())->method('getType')->will($this->returnValue( - File::FILETYPE_TEXT - )); + $fileMock = $this->createMock(File::class); + $fileMock->expects($this->any())->method('getUid')->willReturn(42); + $fileMock->expects($this->any())->method('getType')->willReturn(File::FILETYPE_TEXT); + $fileMock->expects($this->any())->method('getStorage')->willReturn($mockStorage); - $mockExtractor = $this->createMock(ExtractorInterface::class); - $mockExtractor->expects($this->any())->method('getFileTypeRestrictions')->will($this->returnValue( - [] - )); + $extractorServiceMock = $this->getMockBuilder(ExtractorService::class)->getMock(); + $extractorServiceMock->expects($this->once())->method('extractMetaData')->with($fileMock); + $subject->expects($this->any())->method('getExtractorService')->willReturn($extractorServiceMock); - $method = new \ReflectionMethod(Indexer::class, 'isFileTypeSupportedByExtractor'); - $method->setAccessible(true); - $arguments = [ - $mockFile, - $mockExtractor - ]; + $indexFileRepositoryMock->expects($this->once())->method('updateIndexingTime')->with($fileMock->getUid()); - $result = $method->invokeArgs(new Indexer($mockStorage), $arguments); - $this->assertTrue($result); + $subject->extractMetaData($fileMock); } } diff --git a/typo3/sysext/core/Tests/Unit/Resource/MetaDataAspectTest.php b/typo3/sysext/core/Tests/Unit/Resource/MetaDataAspectTest.php new file mode 100644 index 0000000000000000000000000000000000000000..db1ff3276abb66aeb6f7dbeddd3a8929a162e311 --- /dev/null +++ b/typo3/sysext/core/Tests/Unit/Resource/MetaDataAspectTest.php @@ -0,0 +1,256 @@ +<?php +declare(strict_types = 1); + +namespace TYPO3\CMS\Core\Tests\Unit\Resource; + +/* +* 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! +*/ + +use Prophecy\Argument; +use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Resource\Exception\InvalidUidException; +use TYPO3\CMS\Core\Resource\File; +use TYPO3\CMS\Core\Resource\Index\MetaDataRepository; +use TYPO3\CMS\Core\Resource\MetaDataAspect; +use TYPO3\CMS\Core\Resource\ResourceStorage; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\TestingFramework\Core\Unit\UnitTestCase; + +/** + * Test case + */ +class MetaDataAspectTest extends UnitTestCase +{ + /** + * @var ResourceStorage|\PHPUnit\Framework\MockObject\MockObject + */ + protected $storageMock; + + /** + * Set up + */ + protected function setUp(): void + { + $this->storageMock = $this->createMock(ResourceStorage::class); + $this->storageMock->expects($this->any())->method('getUid')->will($this->returnValue(12)); + } + + /** + * Tear down + */ + protected function tearDown(): void + { + $this->resetSingletonInstances = true; + + GeneralUtility::purgeInstances(); + parent::tearDown(); + } + + /** + * @test + */ + public function knownMetaDataIsAdded(): void + { + $metaData = [ + 'width' => 4711, + 'title' => 'Lorem ipsum meta sit amet', + ]; + $file = new File([], $this->storageMock, $metaData); + + $this->assertSame($metaData, $file->getMetaData()->get()); + } + + /** + * @test + */ + public function manuallyAddedMetaDataIsMerged(): void + { + $metaData = [ + 'width' => 4711, + 'title' => 'Lorem ipsum meta sit amet', + ]; + $file = new File([], $this->storageMock, $metaData); + $file->getMetaData()->add([ + 'height' => 900, + 'description' => 'This file is presented by TYPO3', + ]); + + $expected = [ + 'width' => 4711, + 'title' => 'Lorem ipsum meta sit amet', + 'height' => 900, + 'description' => 'This file is presented by TYPO3', + ]; + + $this->assertSame($expected, $file->getMetaData()->get()); + } + + /** + * @test + */ + public function metaDataGetsRemoved(): void + { + $metaData = ['foo' => 'bar']; + + $file = new File(['uid' => 12], $this->storageMock); + + /** @var MetaDataAspect|\PHPUnit\Framework\MockObject\MockObject $metaDataAspectMock */ + $metaDataAspectMock = $this->getMockBuilder(MetaDataAspect::class) + ->setConstructorArgs([$file]) + ->setMethods(['getMetaDataRepository']) + ->getMock(); + + $metaDataAspectMock->add($metaData); + $metaDataAspectMock->remove(); + + $this->assertEmpty($metaDataAspectMock->get()); + } + + /** + * @test + */ + public function positiveUidOfFileIsExpectedToLoadMetaData(): void + { + $this->expectException(InvalidUidException::class); + $this->expectExceptionCode(1381590731); + + $file = new File(['uid' => -3], $this->storageMock); + $file->getMetaData()->get(); + } + + /** + * @test + */ + public function newMetaDataIsCreated(): void + { + $GLOBALS['EXEC_TIME'] = 1534530781; + $metaData = [ + 'title' => 'Hooray', + // This value is ignored on purpose, we simulate the non-existence of the field "description" + 'description' => 'Yipp yipp yipp', + ]; + + $file = new File(['uid' => 12], $this->storageMock); + + $connectionProphecy = $this->prophesize(Connection::class); + $connectionProphecy->insert(Argument::cetera())->willReturn(1); + $connectionProphecy->lastInsertId(Argument::cetera())->willReturn(5); + $connectionPoolProphecy = $this->prophesize(ConnectionPool::class); + $connectionPoolProphecy->getConnectionForTable(Argument::cetera())->willReturn($connectionProphecy->reveal()); + GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal()); + + $metaDataRepositoryMock = $this->getMockBuilder(MetaDataRepository::class) + ->setMethods(['findByFileUid', 'getTableFields', 'update']) + ->getMock(); + $metaDataRepositoryMock->expects($this->any())->method('findByFileUid')->willReturn([]); + $metaDataRepositoryMock->expects($this->any())->method('getTableFields')->willReturn(['title' => 'sometype']); + $metaDataRepositoryMock->expects($this->never())->method('update'); + GeneralUtility::setSingletonInstance(MetaDataRepository::class, $metaDataRepositoryMock); + + $file->getMetaData()->add($metaData)->save(); + + $expected = [ + 'file' => $file->getUid(), + 'pid' => 0, + 'crdate' => 1534530781, + 'tstamp' => 1534530781, + 'cruser_id' => 0, + 'l10n_diffsource' => '', + 'title' => 'Hooray', + 'uid' => '5', + 'newlyCreated' => true, + ]; + + $this->assertSame($expected, $file->getMetaData()->get()); + } + + /** + * @test + */ + public function existingMetaDataGetsUpdated(): void + { + $metaData = ['foo' => 'bar']; + + $file = new File(['uid' => 12], $this->storageMock); + + $metaDataRepositoryMock = $this->getMockBuilder(MetaDataRepository::class) + ->setMethods(['loadFromRepository', 'createMetaDataRecord', 'update']) + ->getMock(); + + $metaDataRepositoryMock->expects($this->any())->method('createMetaDataRecord')->willReturn($metaData); + GeneralUtility::setSingletonInstance(MetaDataRepository::class, $metaDataRepositoryMock); + + $metaDataAspectMock = $this->getMockBuilder(MetaDataAspect::class) + ->setConstructorArgs([$file]) + ->setMethods(['loadFromRepository']) + ->getMock(); + + $metaDataAspectMock->expects($this->any())->method('loadFromRepository')->will($this->onConsecutiveCalls([], $metaData)); + $metaDataAspectMock->add($metaData)->save(); + $metaDataAspectMock->add(['testproperty' => 'testvalue'])->save(); + + $this->assertSame(['foo' => 'bar', 'testproperty' => 'testvalue'], $metaDataAspectMock->get()); + } + + /** + * @return array + */ + public function propertyDataProvider(): array + { + return [ + [ + [ + 'width' => 4711, + 'title' => 'Lorem ipsum meta sit amet', + ], + [ + 'property' => 'width', + 'expected' => true, + ], + [ + 'property' => 'width', + 'expected' => 4711, + ], + ], + [ + [ + 'foo' => 'bar', + ], + [ + 'property' => 'husel', + 'expected' => false, + ], + [ + 'property' => 'husel', + 'expected' => null, + ], + ], + ]; + } + + /** + * @param $metaData + * @param $has + * @param $get + * @test + * @dataProvider propertyDataProvider + */ + public function propertyIsFetchedProperly($metaData, $has, $get): void + { + $file = new File([], $this->storageMock, $metaData); + + $this->assertSame($has['expected'], isset($file->getMetaData()[$has['property']])); + $this->assertSame($get['expected'], $file->getMetaData()[$get['property']] ?? null); + } +} diff --git a/typo3/sysext/core/Tests/Unit/Resource/Service/ExtractorServiceTest.php b/typo3/sysext/core/Tests/Unit/Resource/Service/ExtractorServiceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a480c6c75fc399bc8091b12251decdbffd50d6c1 --- /dev/null +++ b/typo3/sysext/core/Tests/Unit/Resource/Service/ExtractorServiceTest.php @@ -0,0 +1,165 @@ +<?php +declare(strict_types = 1); + +namespace TYPO3\CMS\Core\Tests\Unit\Resource\Service; + +/* + * 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! + */ + +use TYPO3\CMS\Core\Resource\File; +use TYPO3\CMS\Core\Resource\Index\ExtractorInterface; +use TYPO3\CMS\Core\Resource\Index\ExtractorRegistry; +use TYPO3\CMS\Core\Resource\ResourceStorage; +use TYPO3\CMS\Core\Resource\Service\ExtractorService; + +/** + * Class ExtractorServiceTest + */ +class ExtractorServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase +{ + /** + * @test + */ + public function isFileTypeSupportedByExtractorReturnsFalesForFileTypeTextAndExtractorLimitedToFileTypeImage(): void + { + $fileMock = $this->createMock(File::class); + $fileMock->expects($this->any())->method('getType')->willReturn(File::FILETYPE_TEXT); + + $extractorMock = $this->createMock(ExtractorInterface::class); + $extractorMock->expects($this->any())->method('getFileTypeRestrictions')->willReturn([File::FILETYPE_IMAGE]); + + $extractorService = new ExtractorService(); + $method = new \ReflectionMethod($extractorService, 'isFileTypeSupportedByExtractor'); + $method->setAccessible(true); + $arguments = [ + $fileMock, + $extractorMock + ]; + + $result = $method->invokeArgs($extractorService, $arguments); + $this->assertFalse($result); + } + + /** + * @test + */ + public function isFileTypeSupportedByExtractorReturnsTrueForFileTypeImageAndExtractorLimitedToFileTypeImage(): void + { + $fileMock = $this->createMock(File::class); + $fileMock->expects($this->any())->method('getType')->willReturn(File::FILETYPE_IMAGE); + + $extractorMock = $this->createMock(ExtractorInterface::class); + $extractorMock->expects($this->any())->method('getFileTypeRestrictions')->willReturn([File::FILETYPE_IMAGE]); + + $extractorService = new ExtractorService(); + $method = new \ReflectionMethod($extractorService, 'isFileTypeSupportedByExtractor'); + $method->setAccessible(true); + $arguments = [ + $fileMock, + $extractorMock + ]; + + $result = $method->invokeArgs($extractorService, $arguments); + $this->assertTrue($result); + } + + /** + * @test + */ + public function isFileTypeSupportedByExtractorReturnsTrueForFileTypeTextAndExtractorHasNoFileTypeLimitation(): void + { + $fileMock = $this->createMock(File::class); + $fileMock->expects($this->any())->method('getType')->willReturn(File::FILETYPE_TEXT); + + $extractorMock = $this->createMock(ExtractorInterface::class); + $extractorMock->expects($this->any())->method('getFileTypeRestrictions')->willReturn([]); + + $extractorService = new ExtractorService(); + $method = new \ReflectionMethod($extractorService, 'isFileTypeSupportedByExtractor'); + $method->setAccessible(true); + $arguments = [ + $fileMock, + $extractorMock + ]; + + $result = $method->invokeArgs($extractorService, $arguments); + $this->assertTrue($result); + } + + /** + * @test + */ + public function extractMetaDataComposesDataByAvailableExtractors(): void + { + $storageMock = $this->createMock(ResourceStorage::class); + $storageMock->method('getDriverType')->willReturn('Local'); + + /** @var ExtractorService|\PHPUnit\Framework\MockObject\MockObject $subject */ + $subject = $this->getMockBuilder(ExtractorService::class) + ->setMethods(['getExtractorRegistry']) + ->getMock() + ; + + $fileMock = $this->createMock(File::class); + $fileMock->expects($this->any())->method('getUid')->willReturn(4711); + $fileMock->expects($this->any())->method('getType')->willReturn(File::FILETYPE_IMAGE); + $fileMock->expects($this->any())->method('getStorage')->willReturn($storageMock); + + $extractorClass1 = md5('1'); + $extractorObject1 = $this->getMockBuilder(ExtractorInterface::class) + ->setMockClassName($extractorClass1) + ->getMock(); + + $extractorObject1->expects($this->any())->method('getPriority')->willReturn(10); + $extractorObject1->expects($this->any())->method('getExecutionPriority')->willReturn(10); + $extractorObject1->expects($this->any())->method('canProcess')->willReturn(true); + $extractorObject1->expects($this->any())->method('getFileTypeRestrictions')->willReturn([File::FILETYPE_IMAGE]); + $extractorObject1->expects($this->any())->method('getDriverRestrictions')->willReturn([$storageMock->getDriverType()]); + $extractorObject1->expects($this->any())->method('extractMetaData')->with($fileMock)->willReturn([ + 'width' => 800, + 'height' => 600, + ]); + + $extractorClass2 = md5('2'); + $extractorObject2 = $this->getMockBuilder(ExtractorInterface::class) + ->setMockClassName($extractorClass2) + ->getMock(); + + $extractorObject2->expects($this->any())->method('getPriority')->willReturn(20); + $extractorObject2->expects($this->any())->method('getExecutionPriority')->willReturn(20); + $extractorObject2->expects($this->any())->method('canProcess')->willReturn(true); + $extractorObject2->expects($this->any())->method('getFileTypeRestrictions')->willReturn([File::FILETYPE_IMAGE]); + $extractorObject2->expects($this->any())->method('getDriverRestrictions')->willReturn([$storageMock->getDriverType()]); + $extractorObject2->expects($this->any())->method('extractMetaData')->with($fileMock)->willReturn([ + 'keywords' => 'typo3, cms', + ]); + + /** @var ExtractorRegistry|\PHPUnit\Framework\MockObject\MockObject $extractorRegistryMock */ + $extractorRegistryMock = $this->getMockBuilder(ExtractorRegistry::class) + ->setMethods(['createExtractorInstance']) + ->getMock(); + + $extractorRegistryMock->expects($this->any())->method('createExtractorInstance')->will($this->returnValueMap( + [ + [$extractorClass1, $extractorObject1], + [$extractorClass2, $extractorObject2] + ] + )); + $extractorRegistryMock->registerExtractionService($extractorClass1); + $extractorRegistryMock->registerExtractionService($extractorClass2); + + $subject->expects($this->any())->method('getExtractorRegistry')->willReturn($extractorRegistryMock); + + $this->assertSame(['width' => 800, 'height' => 600, 'keywords' => 'typo3, cms'], $subject->extractMetaData($fileMock)); + } +} diff --git a/typo3/sysext/filelist/Classes/FileList.php b/typo3/sysext/filelist/Classes/FileList.php index 9ebe84708bc9353abe01bd6107de4983bbc6ff5a..073ef464e44ac9edba805a27b717272e3f7b0024 100644 --- a/typo3/sysext/filelist/Classes/FileList.php +++ b/typo3/sysext/filelist/Classes/FileList.php @@ -956,7 +956,7 @@ class FileList { try { if ($fileObject instanceof File && $fileObject->isIndexed() && $fileObject->checkActionPermission('editMeta') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) { - $metaData = $fileObject->_getMetaData(); + $metaData = $fileObject->getMetaData()->get(); $urlParameters = [ 'edit' => [ 'sys_file_metadata' => [ @@ -1050,7 +1050,7 @@ class FileList break; case '_LOCALIZATION_': if (!empty($systemLanguages) && $fileObject->isIndexed() && $fileObject->checkActionPermission('editMeta') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) { - $metaDataRecord = $fileObject->_getMetaData(); + $metaDataRecord = $fileObject->getMetaData()->get(); $translations = $this->getTranslationsForMetaData($metaDataRecord); $languageCode = ''; @@ -1315,7 +1315,7 @@ class FileList // Edit metadata of file if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('editMeta') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) { - $metaData = $fileOrFolderObject->_getMetaData(); + $metaData = $fileOrFolderObject->getMetaData()->get(); $urlParameters = [ 'edit' => [ 'sys_file_metadata' => [ diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php index 610a124922931b4aef2de01e21e449eea8003804..6d3264aec11cc6d2b316f919722eee2f25c876c9 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php @@ -3318,6 +3318,13 @@ return [ 'Breaking-87193-DeprecatedFunctionalityRemoved.rst', ], ], + 'TYPO3\CMS\Core\Resource\File->_getMetaData' => [ + 'numberOfMandatoryArguments' => 0, + 'maximumNumberOfArguments' => 0, + 'restFiles' => [ + 'Deprecation-85895-DeprecateFile_getMetaData.rst', + ], + ], 'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->initFEuser' => [ 'numberOfMandatoryArguments' => 0, 'maximumNumberOfArguments' => 0,