From 1eb9162d935b2398ad2649fcecaa69c415b0d371 Mon Sep 17 00:00:00 2001 From: Andreas Fernandez <a.fernandez@scripting-base.de> Date: Wed, 15 Aug 2018 09:36:35 +0200 Subject: [PATCH] [TASK] Refactor metadata handling in FAL Meta data of files handled by FAL is fetched, created and updated in various places, which makes it hard to maintain the current code base. Albeit the method `_getMetaData()` is marked as internal, it has been marked as deprecated as well, because the method is widely used in the TYPO3 extension universe. For this reason, a MetaDataAspect is introduced that takes care of meta data handling on a low-level basis. In the same run, FAL's `Indexer` is now responsible for creating or updating such meta data records, the `ResourceStorage` now only tells whether auto-extraction is enabled. The meta data extraction, based on registered extractors implementing the `ExtractorInterface` interface, has been moved into a separate service class. Resolves: #85895 Releases: master Change-Id: Icb929a6226777dcea3868ee5c083cf13ff5a71f6 Reviewed-on: https://review.typo3.org/57908 Tested-by: TYPO3com <noreply@typo3.com> Reviewed-by: Susanne Moog <susanne.moog@typo3.org> Tested-by: Susanne Moog <susanne.moog@typo3.org> Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de> Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de> --- typo3/sysext/core/Classes/Resource/File.php | 102 +++---- .../core/Classes/Resource/Index/Indexer.php | 92 +++---- .../Resource/Index/MetaDataRepository.php | 42 ++- .../core/Classes/Resource/MetaDataAspect.php | 220 +++++++++++++++ .../Processing/FileDeletionAspect.php | 2 +- .../core/Classes/Resource/ResourceStorage.php | 6 - .../Resource/Service/ExtractorService.php | 96 +++++++ ...cation-85895-DeprecateFile_getMetaData.rst | 32 +++ .../core/Tests/Unit/Resource/FileTest.php | 18 +- .../Tests/Unit/Resource/Index/IndexerTest.php | 91 ++----- .../Unit/Resource/MetaDataAspectTest.php | 256 ++++++++++++++++++ .../Resource/Service/ExtractorServiceTest.php | 165 +++++++++++ typo3/sysext/filelist/Classes/FileList.php | 6 +- .../Php/MethodCallMatcher.php | 7 + 14 files changed, 914 insertions(+), 221 deletions(-) create mode 100644 typo3/sysext/core/Classes/Resource/MetaDataAspect.php create mode 100644 typo3/sysext/core/Classes/Resource/Service/ExtractorService.php create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Deprecation-85895-DeprecateFile_getMetaData.rst create mode 100644 typo3/sysext/core/Tests/Unit/Resource/MetaDataAspectTest.php create mode 100644 typo3/sysext/core/Tests/Unit/Resource/Service/ExtractorServiceTest.php diff --git a/typo3/sysext/core/Classes/Resource/File.php b/typo3/sysext/core/Classes/Resource/File.php index cd46b6a54967..eaa31ddb5898 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 e8d7f7ff9f39..041a527c5a64 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 011a75813020..ba7853bd5edc 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 000000000000..45baeeaf2c14 --- /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 17a947c7a1fc..b3c1b143f23e 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 abf8e66177ee..1fb9c6ca29ff 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 000000000000..166bc14ab31f --- /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 000000000000..61c102503eff --- /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 32232de639b5..2f31420d289b 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 7af158f53272..d16d7b59fb30 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 000000000000..db1ff3276abb --- /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 000000000000..a480c6c75fc3 --- /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 9ebe84708bc9..073ef464e44a 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 610a12492293..6d3264aec11c 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, -- GitLab