216 lines
9.6 KiB
PHP
216 lines
9.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* This file is part of the TYPO3 CMS project.
|
|
*
|
|
* It is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, either version 2
|
|
* of the License, or any later version.
|
|
*
|
|
* For the full copyright and license information, please read the
|
|
* LICENSE.txt file that was distributed with this source code.
|
|
*
|
|
* The TYPO3 project - inspiring people to share!
|
|
*/
|
|
|
|
namespace TYPO3\CMS\Core\SystemResource;
|
|
|
|
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
|
use TYPO3\CMS\Core\Core\Environment;
|
|
use TYPO3\CMS\Core\Http\Uri;
|
|
use TYPO3\CMS\Core\Package\Resource\Definition\PublicResourceDefinition;
|
|
use TYPO3\CMS\Core\Package\VirtualAppPackage;
|
|
use TYPO3\CMS\Core\Resource\Exception as FalException;
|
|
use TYPO3\CMS\Core\Resource\File;
|
|
use TYPO3\CMS\Core\Resource\Folder;
|
|
use TYPO3\CMS\Core\Resource\ProcessedFile;
|
|
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
|
use TYPO3\CMS\Core\Resource\StorageRepository;
|
|
use TYPO3\CMS\Core\SystemResource\Exception\CanNotResolvePublicResourceException;
|
|
use TYPO3\CMS\Core\SystemResource\Exception\CanNotResolveSystemResourceException;
|
|
use TYPO3\CMS\Core\SystemResource\Exception\CanNotResolveSystemResourceIdentifierException;
|
|
use TYPO3\CMS\Core\SystemResource\Exception\InvalidSystemResourceIdentifierException;
|
|
use TYPO3\CMS\Core\SystemResource\Identifier\FalResourceIdentifier;
|
|
use TYPO3\CMS\Core\SystemResource\Identifier\PackageResourceIdentifier;
|
|
use TYPO3\CMS\Core\SystemResource\Identifier\SystemResourceIdentifierFactory;
|
|
use TYPO3\CMS\Core\SystemResource\Identifier\UriResourceIdentifier;
|
|
use TYPO3\CMS\Core\SystemResource\Type\PackageResource;
|
|
use TYPO3\CMS\Core\SystemResource\Type\PublicPackageFile;
|
|
use TYPO3\CMS\Core\SystemResource\Type\PublicResourceInterface;
|
|
use TYPO3\CMS\Core\SystemResource\Type\StaticResourceInterface;
|
|
use TYPO3\CMS\Core\SystemResource\Type\SystemResourceInterface;
|
|
use TYPO3\CMS\Core\SystemResource\Type\UriResource;
|
|
use TYPO3\CMS\Core\Utility\PathUtility;
|
|
|
|
/**
|
|
* This is the heart of system resource handling and
|
|
* the most important API to be used in userland code
|
|
* and throughout the core.
|
|
*
|
|
* This class should be final, but is not, because it
|
|
* needs to be mocked in tests.
|
|
*/
|
|
#[Autoconfigure(public: true)]
|
|
readonly class SystemResourceFactory
|
|
{
|
|
public function __construct(
|
|
private SystemResourceIdentifierFactory $identifierFactory,
|
|
private ?StorageRepository $storageRepository,
|
|
private ?ResourceFactory $resourceFactory,
|
|
) {}
|
|
|
|
/**
|
|
* Use this method to obtain a resource that is public,
|
|
* which means, that a URI can be generated for it.
|
|
*
|
|
* Always use it when the goal is to generate a URI,
|
|
* as it checks, whether the resolved resource can/ is
|
|
* indeed published or not and throws an exception otherwise.
|
|
*
|
|
* @throws CanNotResolveSystemResourceException
|
|
* @throws CanNotResolvePublicResourceException
|
|
*/
|
|
public function createPublicResource(string $resourceString): PublicResourceInterface
|
|
{
|
|
$resource = $this->createResource($resourceString);
|
|
if (!$resource instanceof PublicResourceInterface || !$resource->isPublished()) {
|
|
throw new CanNotResolvePublicResourceException(sprintf('Resolved resource "%s" is not a public resource. Given resource identifier: "%s"', $resource, $resourceString), 1758098512);
|
|
}
|
|
return $resource;
|
|
}
|
|
|
|
/**
|
|
* Use this method, when generating a URI is not required
|
|
* for the resource and only e.g. file contents like for templates
|
|
* is required.
|
|
*
|
|
* @throws CanNotResolveSystemResourceException
|
|
* @throws InvalidSystemResourceIdentifierException
|
|
*/
|
|
public function createResource(string $resourceString): StaticResourceInterface
|
|
{
|
|
try {
|
|
return $this->createFromIdentifier($resourceString);
|
|
} catch (CanNotResolveSystemResourceIdentifierException $e) {
|
|
if (str_starts_with($resourceString, Environment::getProjectPath())
|
|
|| (PathUtility::isAbsolutePath($resourceString) && file_exists($resourceString))
|
|
) {
|
|
throw new CanNotResolveSystemResourceException('Absolute paths are not allowed as resource identifiers', 1760618195);
|
|
}
|
|
$resourceString = ltrim($resourceString, '/');
|
|
$falResource = $this->createFromLegacyFalPath($resourceString, $e);
|
|
return $falResource ?? $this->createResourceFromRelativePublicPath($resourceString);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws CanNotResolveSystemResourceException
|
|
* @throws InvalidSystemResourceIdentifierException
|
|
*/
|
|
private function createResourceFromRelativePublicPath(string $relativePublicPath): SystemResourceInterface
|
|
{
|
|
$absoluteResourcePath = Environment::getPublicPath() . '/' . $relativePublicPath;
|
|
// The file must actually exist, only then we can be sure our
|
|
// following string manipulation is correct. Otherwise, it could be some unexpected
|
|
// string and the manipulation will lead to unexpected results
|
|
// Strip potentially available query string and fragment from the path before checking, though
|
|
try {
|
|
$strippedAbsolutePath = (new Uri($absoluteResourcePath))->getPath();
|
|
} catch (\InvalidArgumentException) {
|
|
$strippedAbsolutePath = null;
|
|
}
|
|
if (!file_exists($strippedAbsolutePath ?? $absoluteResourcePath)) {
|
|
throw new CanNotResolveSystemResourceException(sprintf('Can not resolve relative public path "%s" to a system resource', $relativePublicPath), 1759740281);
|
|
}
|
|
$packageIdentifier = $this->identifierFactory->createFromPackagePath(
|
|
VirtualAppPackage::APP_PACKAGE_KEY,
|
|
substr($absoluteResourcePath, strlen(Environment::getProjectPath()) + 1),
|
|
$relativePublicPath,
|
|
);
|
|
return $this->createFromPackageIdentifier($packageIdentifier);
|
|
}
|
|
|
|
/**
|
|
* @throws CanNotResolveSystemResourceException
|
|
* @throws InvalidSystemResourceIdentifierException
|
|
* @throws CanNotResolveSystemResourceIdentifierException
|
|
*/
|
|
private function createFromIdentifier(string $potentialIdentifier): StaticResourceInterface
|
|
{
|
|
$identifier = $this->identifierFactory->create($potentialIdentifier);
|
|
return match (get_class($identifier)) {
|
|
UriResourceIdentifier::class => new UriResource($identifier),
|
|
PackageResourceIdentifier::class => $this->createFromPackageIdentifier($identifier),
|
|
FalResourceIdentifier::class => $this->createFromFalIdentifier($identifier),
|
|
default => throw new InvalidSystemResourceIdentifierException(sprintf('Can not resolve "%s" to a system resource. Unknown SystemResourceIdentifier', $potentialIdentifier), 1759393674),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @throws CanNotResolveSystemResourceException
|
|
*/
|
|
private function createFromLegacyFalPath(string $resourceString, \Throwable $e): ?StaticResourceInterface
|
|
{
|
|
if (!str_starts_with($resourceString, $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'])) {
|
|
// For legacy resolving, we do not even try resolving any other path than
|
|
// one starting with configured $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir']
|
|
return null;
|
|
}
|
|
try {
|
|
$potentialFalPath = $resourceString;
|
|
$storageUid = $this->storageRepository?->findBestMatchingStorageByLocalPath($potentialFalPath);
|
|
} catch (\Throwable) {
|
|
$storageUid = 0;
|
|
}
|
|
if ($storageUid <= 0) {
|
|
return null;
|
|
}
|
|
$storage = $this->storageRepository?->findByUid($storageUid);
|
|
if ($storage === null) {
|
|
throw new CanNotResolveSystemResourceException(sprintf('Can not resolve "%s" to a system resource, storage %d does not exist', $resourceString, $storageUid), 1758627596, $e);
|
|
}
|
|
$file = null;
|
|
try {
|
|
$file = $storage->getFile($potentialFalPath);
|
|
} catch (\Throwable) {
|
|
}
|
|
$this->ensureValidFalResource($file, $resourceString);
|
|
return $file;
|
|
}
|
|
|
|
/**
|
|
* @throws CanNotResolveSystemResourceException
|
|
*/
|
|
private function createFromFalIdentifier(FalResourceIdentifier $resourceUri): PublicResourceInterface
|
|
{
|
|
try {
|
|
$file = $this->resourceFactory?->retrieveFileOrFolderObject($resourceUri->getIdentifier());
|
|
$this->ensureValidFalResource($file, (string)$resourceUri);
|
|
return $file;
|
|
} catch (FalException $e) {
|
|
throw new CanNotResolveSystemResourceException(sprintf('Can not resolve "%s" to a system resource', $resourceUri), 1759397430, $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws CanNotResolveSystemResourceException
|
|
*/
|
|
private function ensureValidFalResource(ProcessedFile|File|Folder|null $falResource, string $resourceUri): void
|
|
{
|
|
if (!$falResource instanceof File || $falResource->getStorage()->getUid() === 0) {
|
|
throw new CanNotResolveSystemResourceException(sprintf('Can not resolve file with URI "%s"', $resourceUri), 1758700078);
|
|
}
|
|
}
|
|
|
|
private function createFromPackageIdentifier(PackageResourceIdentifier $packageIdentifier): SystemResourceInterface
|
|
{
|
|
$resourceDefinition = $packageIdentifier->getPackage()->getResources()->definitionForPath($packageIdentifier->getRelativePath());
|
|
if ($resourceDefinition instanceof PublicResourceDefinition) {
|
|
return new PublicPackageFile($packageIdentifier, $resourceDefinition);
|
|
}
|
|
return new PackageResource($packageIdentifier, $resourceDefinition);
|
|
}
|
|
}
|