TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:09 +02:00
commit af8cc155b5
6818 changed files with 642608 additions and 0 deletions
+443
View File
@@ -0,0 +1,443 @@
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Resource;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Collection\AbstractRecordCollection;
use TYPO3\CMS\Core\Collection\CollectionInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Http\ApplicationType;
use TYPO3\CMS\Core\Resource\Collection\FileCollectionRegistry;
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\SystemResource\Exception\SystemResourceException;
use TYPO3\CMS\Core\SystemResource\Publishing\DefaultSystemResourcePublisher;
use TYPO3\CMS\Core\SystemResource\Publishing\UriGenerationOptions;
use TYPO3\CMS\Core\SystemResource\SystemResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* Factory class for FAL objects
*/
readonly class ResourceFactory implements SingletonInterface
{
public function __construct(
protected StorageRepository $storageRepository,
#[Autowire(service: 'cache.runtime')]
protected FrontendInterface $runtimeCache,
private FileIndexRepository $fileIndexRepository,
) {}
/**
* Creates an instance of the collection from given UID. The $recordData can be supplied to increase performance.
*
* @param int $uid The uid of the collection to instantiate.
* @param array $recordData The record row from database.
*
* @throws \InvalidArgumentException
*/
public function getCollectionObject(int $uid, array $recordData = []): CollectionInterface
{
$collectionObject = $this->collectionCacheGet($uid);
if ($collectionObject === null) {
// Get mount data if not already supplied as argument to this function
if (empty($recordData) || $recordData['uid'] !== $uid) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_collection');
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$recordData = $queryBuilder->select('*')
->from('sys_file_collection')
->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
)
)
->executeQuery()
->fetchAssociative();
if (empty($recordData)) {
throw new \InvalidArgumentException('No collection found for given UID: "' . $uid . '"', 1314085992);
}
}
$collectionObject = $this->createCollectionObject($recordData);
$this->collectionCacheSet($uid, $collectionObject);
}
return $collectionObject;
}
/**
* Creates a collection object.
*
* @param array $collectionData The database row of the sys_file_collection record.
* @return CollectionInterface<File>
*/
public function createCollectionObject(array $collectionData): CollectionInterface
{
$registry = GeneralUtility::makeInstance(FileCollectionRegistry::class);
/** @var AbstractRecordCollection $class */
$class = $registry->getFileCollectionClass($collectionData['type']);
return $class::create($collectionData);
}
/**
* Creates an instance of the file given UID. The $fileData can be supplied
* to increase performance.
*
* @param int|string $uid The uid of the file to instantiate. (string is used for the time being as compat-mode)
* @param array $fileData The record row from database.
*/
public function getFileObject(int|string $uid, array $fileData = []): File
{
$uid = (int)$uid;
$fileObject = $this->fileCacheGet($uid);
if ($fileObject === null) {
// Fetches data in case $fileData is empty
if (empty($fileData)) {
$fileData = $this->fileIndexRepository->findOneByUid($uid);
if ($fileData === false) {
throw new FileDoesNotExistException('No file found for given UID: ' . $uid, 1317178604);
}
}
$fileObject = $this->createFileObject($fileData);
$this->fileCacheSet($fileObject);
}
return $fileObject;
}
/**
* Gets a file object from an identifier [storage]:[fileId]
*
* @throws \InvalidArgumentException
*/
public function getFileObjectFromCombinedIdentifier(string $identifier): File|ProcessedFile|null
{
if ($identifier === '') {
throw new \InvalidArgumentException('Invalid file identifier given. It must be not empty.', 1401732564);
}
$parts = GeneralUtility::trimExplode(':', $identifier);
if (count($parts) === 2) {
$storageUid = (int)$parts[0];
$fileIdentifier = $parts[1];
} else {
// We only got a path: Go into backwards compatibility mode and
// use virtual Storage (uid=0)
$storageUid = 0;
$fileIdentifier = $parts[0];
}
return $this->storageRepository->getStorageObject($storageUid, [], $fileIdentifier)
->getFileByIdentifier($fileIdentifier);
}
/**
* Bulk function, can be used for anything to get a file or folder
*
* 1. It's a UID
* 2. It's a combined identifier
* 3. It's just a path/filename (coming from the oldstyle/backwards compatibility)
*
* Files, previously laid on fileadmin/ or something, will be "mapped" to the storage the file is
* in now. Files like typo3temp/ or typo3conf/ will be moved to the first writable storage
* in its processing folder
*
* $input could be
* - "2:myfolder/myfile.jpg" (combined identifier)
* - "23" (file UID)
* - "uploads/myfile.png" (backwards-compatibility, storage "0")
* - "file:23"
*/
public function retrieveFileOrFolderObject(string|int $input): ProcessedFile|File|Folder|null
{
// Remove Environment::getPublicPath() because absolute paths under Windows systems contain ':'
// This is done in all considered sub functions anyway
$input = str_replace(Environment::getPublicPath() . '/', '', (string)$input);
if (str_starts_with($input, 'file:')) {
$input = substr($input, 5);
return $this->retrieveFileOrFolderObject($input);
}
if (MathUtility::canBeInterpretedAsInteger($input)) {
return $this->getFileObject((int)$input);
}
if (strpos($input, ':') > 0) {
[$prefix] = explode(':', $input);
if (MathUtility::canBeInterpretedAsInteger($prefix)) {
// path or folder in a valid storageUID
return $this->getObjectFromCombinedIdentifier($input);
}
if ($prefix === 'EXT') {
try {
// @todo: We make an "URL" relative to public dir because the fallback storage root
// is the public dir and in this case file identifier === url
// this will be resolved once fallback storage is deprecated
// This should be done asap, because other implementations of SystemResourcePublisherInterface
// might not evaluate the uriPrefix options
$potentialPathRelativeToPublicDir = (string)$this->getSystemResourceUri($input, null, new UriGenerationOptions(uriPrefix: '/', cacheBusting: false));
if (!file_exists(Environment::getPublicPath() . $potentialPathRelativeToPublicDir)) {
throw new ResourceDoesNotExistException(sprintf('File "%s" does not exist in fallback compatibility storage.', $input), 1760532790);
}
return $this->getFileObjectFromCombinedIdentifier($potentialPathRelativeToPublicDir);
} catch (SystemResourceException $e) {
throw new ResourceDoesNotExistException(sprintf('Tried to access a private resource file "%s" from fallback compatibility storage. This storage only handles public files.', $input), 1633777536, $e);
}
}
return null;
}
// this is a backwards-compatible way to access "0-storage" files or folders
// @todo: this needs to be removed once we remove support for fallback storage
// eliminate double slashes, /./ and /../
$input = PathUtility::getCanonicalPath(ltrim($input, '/'));
if (@is_file(Environment::getPublicPath() . '/' . $input)) {
// only the local file
return $this->getFileObjectFromCombinedIdentifier($input);
}
if (@is_dir(Environment::getPublicPath() . '/' . ltrim($input, '/'))) {
// only the local path
return $this->getFolderObjectFromCombinedIdentifier(ltrim($input, '/'));
}
return null;
}
private function getSystemResourceUri(string $resourceIdentifier, ?ServerRequestInterface $request = null, ?UriGenerationOptions $options = null): UriInterface
{
$resourceFactory = GeneralUtility::makeInstance(SystemResourceFactory::class);
$resource = $resourceFactory->createPublicResource($resourceIdentifier);
$resourcePublisher = GeneralUtility::makeInstance(DefaultSystemResourcePublisher::class);
return $resourcePublisher->generateUri($resource, $request, $options);
}
/**
* Gets a folder object from an identifier [storage]:[fileId]
*/
public function getFolderObjectFromCombinedIdentifier(string|int $identifier): Folder
{
$parts = GeneralUtility::trimExplode(':', (string)$identifier);
if (count($parts) === 2) {
$storageUid = (int)$parts[0];
$folderIdentifier = $parts[1];
} else {
// We only got a path: Go into backwards compatibility mode and
// use virtual Storage (uid=0)
$storageUid = 0;
// please note that getStorageObject() might modify $folderIdentifier when
// auto-detecting the best-matching storage to use
$folderIdentifier = $parts[0];
// make sure to not use an absolute path, and remove Environment::getPublicPath if it is prepended
if (str_starts_with($folderIdentifier, Environment::getPublicPath() . '/')) {
$folderIdentifier = PathUtility::stripPathSitePrefix($parts[0]);
}
}
return $this->storageRepository->getStorageObject($storageUid, [], $folderIdentifier)->getFolder($folderIdentifier);
}
/**
* Gets a file or folder object.
*
* @throws Exception\ResourceDoesNotExistException
*/
public function getObjectFromCombinedIdentifier(string $identifier): FileInterface|Folder
{
[$storageId, $objectIdentifier] = array_pad(GeneralUtility::trimExplode(':', $identifier), 2, null);
if (!MathUtility::canBeInterpretedAsInteger($storageId) && $objectIdentifier === null) {
$objectIdentifier = $storageId;
$storageId = 0;
}
if (MathUtility::canBeInterpretedAsInteger($storageId)) {
$storage = $this->storageRepository->findByUid($storageId);
if ($storage === null) {
throw new ResourceDoesNotExistException('Storage ' . $storageId . ' does not exist', 1762852423);
}
if ($storage->hasFile($objectIdentifier)) {
return $storage->getFile($objectIdentifier);
}
if ($storage->hasFolder($objectIdentifier)) {
return $storage->getFolder($objectIdentifier);
}
}
throw new ResourceDoesNotExistException('Object with identifier "' . $identifier . '" does not exist in storage', 1329647780);
}
/**
* Creates a file object from an array of file data. Requires a database
* row to be fetched.
*/
public function createFileObject(array $fileData, ?ResourceStorage $storage = null): File
{
if (array_key_exists('storage', $fileData) && MathUtility::canBeInterpretedAsInteger($fileData['storage'])) {
$storageObject = $this->storageRepository->findByUid((int)$fileData['storage']);
} else {
$storageObject = $storage;
}
// Ensure a storage could be fetched to create the file.
if ($storageObject === null) {
throw new \RuntimeException('A file needs to reside in a Storage', 1381570997);
}
$fileData['storage'] = $storageObject->getUid();
return GeneralUtility::makeInstance(File::class, $fileData, $storageObject);
}
/**
* Creates an instance of a FileReference object. The $fileReferenceData can
* be supplied to increase performance.
*
* @param int|string $uid The uid of the file usage (sys_file_reference) to instantiate. string is kept for backwards-compat
* @param array $fileReferenceData The record row from database.
* @param bool $raw Whether to get raw results without performing overlays
* @throws Exception\ResourceDoesNotExistException
*/
public function getFileReferenceObject(int|string $uid, array $fileReferenceData = [], bool $raw = false): FileReference
{
$uid = (int)$uid;
$fileReference = $this->fileReferenceCacheGet($uid);
if ($fileReference === null) {
// Fetches data in case $fileData is empty
if (empty($fileReferenceData)) {
$fileReferenceData = $this->getFileReferenceData($uid, $raw);
if (!is_array($fileReferenceData)) {
throw new ResourceDoesNotExistException(
'No file reference (sys_file_reference) was found for given UID: "' . $uid . '"',
1317178794
);
}
}
$fileReference = $this->createFileReferenceObject($fileReferenceData);
$this->fileReferenceCacheSet($fileReference);
}
return $fileReference;
}
/**
* Creates a file usage object from an array of fileReference data
* from sys_file_reference table.
* Requires a database row to be already fetched and present.
*/
public function createFileReferenceObject(array $fileReferenceData): FileReference
{
return GeneralUtility::makeInstance(FileReference::class, $fileReferenceData);
}
/**
* Gets data for the given uid of the file reference record.
*
* @param int $uid The uid of the file usage (sys_file_reference) to be fetched
* @param bool $raw Whether to get raw results without performing overlays
*/
protected function getFileReferenceData(int $uid, bool $raw = false): array|false|null
{
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
if (!$raw
&& $request instanceof ServerRequestInterface
&& ApplicationType::fromRequest($request)->isBackend()
) {
$fileReferenceData = BackendUtility::getRecordWSOL('sys_file_reference', $uid);
} elseif (!$raw
&& $request instanceof ServerRequestInterface
&& ApplicationType::fromRequest($request)->isFrontend()
) {
$fileReferenceData = GeneralUtility::makeInstance(PageRepository::class)->checkRecord('sys_file_reference', $uid);
} else {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$fileReferenceData = $queryBuilder->select('*')
->from('sys_file_reference')
->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
)
)
->executeQuery()
->fetchAssociative();
}
return $fileReferenceData;
}
protected function collectionCacheIdentifier(int $uid): string
{
return sprintf('resourcefactory-collection-%s', $uid);
}
/**
* @return CollectionInterface<File>|null
*/
protected function collectionCacheGet(int $uid): ?CollectionInterface
{
$entry = $this->runtimeCache->get($this->collectionCacheIdentifier($uid));
if ($entry instanceof CollectionInterface) {
return $entry;
}
return null;
}
/**
* @param CollectionInterface<File> $collection
*/
protected function collectionCacheSet(int $uid, CollectionInterface $collection): void
{
$this->runtimeCache->set($this->collectionCacheIdentifier($uid), $collection);
}
protected function fileCacheIdentifier(int $uid): string
{
return sprintf('resourcestorage-file-%s', $uid);
}
protected function fileCacheGet(int $uid): ?File
{
$entry = $this->runtimeCache->get($this->fileCacheIdentifier($uid));
if ($entry instanceof File) {
return $entry;
}
return null;
}
protected function fileCacheSet(File $file): void
{
$this->runtimeCache->set($this->fileCacheIdentifier($file->getUid()), $file);
}
protected function fileReferenceCacheIdentifier(int $uid): string
{
return sprintf('resourcestorage-filereference-%s', $uid);
}
protected function fileReferenceCacheGet(int $uid): ?FileReference
{
$entry = $this->runtimeCache->get($this->fileReferenceCacheIdentifier($uid));
return ($entry instanceof FileReference) ? $entry : null;
}
protected function fileReferenceCacheSet(FileReference $fileReference): void
{
$this->runtimeCache->set($this->fileReferenceCacheIdentifier($fileReference->getUid()), $fileReference);
}
}