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
@@ -0,0 +1,23 @@
<?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\Exception;
/**
* Thrown when a URI to a public resource can not be created
*/
final class CanNotGenerateUriException extends SystemResourceException {}
@@ -0,0 +1,23 @@
<?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\Exception;
/**
* Thrown when a resolved resource is NOT public, but MUST be public (e.g. for URI generation)
*/
final class CanNotResolvePublicResourceException extends SystemResourceException {}
@@ -0,0 +1,23 @@
<?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\Exception;
/**
* Thrown when all attempts to resolve a string to a unique system resource fail
*/
class CanNotResolveSystemResourceException extends SystemResourceException {}
@@ -0,0 +1,23 @@
<?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\Exception;
/**
* Thrown when no specific identifier type can be detected
*/
final class CanNotResolveSystemResourceIdentifierException extends SystemResourceException {}
@@ -0,0 +1,23 @@
<?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\Exception;
/**
* Thrown when an identifier type is detected, but the identifier contains errors
*/
final class InvalidSystemResourceIdentifierException extends SystemResourceException {}
@@ -0,0 +1,23 @@
<?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\Exception;
/**
* Thrown when a resource is requested that has not been defined beforehand
*/
final class SystemResourceDefinitionNotFoundException extends SystemResourceException {}
@@ -0,0 +1,24 @@
<?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\Exception;
/**
* This exception is thrown when a referenced system resource (aka file)
* does not exist, but information about this file is requested (like contents)
*/
final class SystemResourceDoesNotExistException extends SystemResourceException {}
@@ -0,0 +1,27 @@
<?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\Exception;
use TYPO3\CMS\Core\Resource\Exception;
/**
* This is the base exception. Catch this one to
* gracefully proceed when errors happen in resource
* creation or resource URI generation
*/
class SystemResourceException extends Exception {}
@@ -0,0 +1,66 @@
<?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\Http;
use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Core\Http\ApplicationType;
use TYPO3\CMS\Core\Http\Uri;
use TYPO3\CMS\Core\Resource\File;
/**
* This is subject to change during v14 development. Do not use.
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
class CacheBustingUri extends Uri
{
public static function fromFileSystemPath(string $absolutePathToPotentialFile, UriInterface $baseUri, ?ApplicationType $applicationType = null): UriInterface
{
try {
// The absolute path might be appended with a query string and/ or fragment
// therefore we remove it here before we check for file existence
$absolutePath = (new Uri($absolutePathToPotentialFile))->getPath();
if (!is_file($absolutePath)) {
return $baseUri;
}
} catch (\Throwable) {
// See https://review.typo3.org/75477 for the reason of this try/catch
// likely we can remove this in the future, but it is kept for now to ensure BC
return $baseUri;
}
$configAccessor = $applicationType?->isFrontend() ? 'FE' : 'BE';
$rewriteFileName = (bool)($GLOBALS['TYPO3_CONF_VARS'][$configAccessor]['versionNumberInFilename'] ?? false);
$fileModificationTime = filemtime($absolutePath);
if ($rewriteFileName) {
$nameParts = explode('.', $baseUri->getPath());
$fileExtension = array_pop($nameParts);
array_push($nameParts, $fileModificationTime, $fileExtension);
$uri = $baseUri->withPath(implode('.', $nameParts));
} else {
$query = $baseUri->getQuery();
$uri = $baseUri->withQuery($query . ($query !== '' ? '&' : '') . $fileModificationTime);
}
return $uri;
}
public static function fromFile(File $file, UriInterface $baseUri): UriInterface
{
$query = $baseUri->getQuery();
return $baseUri->withQuery($query . ($query !== '' ? '&' : '') . $file->getSha1());
}
}
@@ -0,0 +1,53 @@
<?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\Identifier;
use TYPO3\CMS\Core\SystemResource\Exception\InvalidSystemResourceIdentifierException;
use TYPO3\CMS\Core\Utility\MathUtility;
/**
* This is subject to change during v14 development. Do not use.
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
final class FalResourceIdentifier extends SystemResourceIdentifier
{
public const string TYPE = 'FAL';
public function __construct(private readonly string $storageId, private readonly string $falIdentifier, string $givenIdentifier)
{
parent::__construct($givenIdentifier);
if (!MathUtility::canBeInterpretedAsInteger($storageId)) {
throw new InvalidSystemResourceIdentifierException(sprintf('Given identifier "%s" is invalid. Storage id must be integer.', $givenIdentifier), 1760433315);
}
}
public function getIdentifier(): string
{
return sprintf('%d:%s', $this->storageId, $this->falIdentifier);
}
public function __toString()
{
return sprintf(
'%s:%d:%s',
self::TYPE,
$this->storageId,
$this->falIdentifier,
);
}
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\SystemResource\Identifier;
use TYPO3\CMS\Core\Package\PackageInterface;
use TYPO3\CMS\Core\SystemResource\Exception\InvalidSystemResourceIdentifierException;
/**
* This is subject to change during v14 development. Do not use.
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
final class PackageResourceIdentifier extends SystemResourceIdentifier
{
public const string LEGACY_TYPE = 'EXT';
public const string TYPE = 'PKG';
public function __construct(
private readonly PackageInterface $package,
private readonly string $relativePath,
string $givenIdentifier,
) {
parent::__construct($givenIdentifier);
}
public function getPackage(): PackageInterface
{
return $this->package;
}
public function getRelativePath(): string
{
return $this->relativePath;
}
/**
* @throws InvalidSystemResourceIdentifierException
*/
public function withRelativePath(string $newPath): self
{
return new self(
$this->package,
$newPath,
$this->givenIdentifier,
);
}
public function __toString()
{
return sprintf(
'%s:%s:%s',
self::TYPE,
$this->package->getValueFromComposerManifest('name') ?? $this->package->getPackageKey(),
$this->relativePath,
);
}
}
@@ -0,0 +1,37 @@
<?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\Identifier;
use TYPO3\CMS\Core\SystemResource\Exception\InvalidSystemResourceIdentifierException;
/**
* This is subject to change during v14 development. Do not use.
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
abstract class SystemResourceIdentifier implements \Stringable
{
/**
* @throws InvalidSystemResourceIdentifierException
*/
public function __construct(public readonly string $givenIdentifier) {}
public function __toString()
{
return $this->givenIdentifier;
}
}
@@ -0,0 +1,150 @@
<?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\Identifier;
use TYPO3\CMS\Core\Package\Exception\UnknownPackageException;
use TYPO3\CMS\Core\Package\Exception\UnknownPackagePathException;
use TYPO3\CMS\Core\Package\PackageInterface;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\SystemResource\Exception\CanNotResolveSystemResourceIdentifierException;
use TYPO3\CMS\Core\SystemResource\Exception\InvalidSystemResourceIdentifierException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* This is subject to change during v14 development. Do not use.
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace,
* with four exceptions due to deprecated legacy code, see Uri\ResourceViewHelper, PageRendererBackendSetupTrait,
* AbstractItemProvider and ExtensionManagementUtility::resolvePackagePath()
*/
final readonly class SystemResourceIdentifierFactory
{
public function __construct(private PackageManager $packageManager) {}
/**
* @throws InvalidSystemResourceIdentifierException
* @throws CanNotResolveSystemResourceIdentifierException
*/
public function create(string $resourceIdentifier): SystemResourceIdentifier
{
$givenIdentifier = $resourceIdentifier;
if (PathUtility::hasProtocolAndScheme($resourceIdentifier)) {
$resourceIdentifier = sprintf('%s:%s', UriResourceIdentifier::TYPE, $resourceIdentifier);
}
[$identifierType] = explode(':', $resourceIdentifier, 2);
return match ($identifierType) {
PackageResourceIdentifier::LEGACY_TYPE => $this->createPackageResourceIdentifier($this->convertExtensionPathToPackageResourceIdentifier($resourceIdentifier), $resourceIdentifier),
PackageResourceIdentifier::TYPE => $this->createPackageResourceIdentifier($resourceIdentifier),
FalResourceIdentifier::TYPE => $this->createFalResourceIdentifier($resourceIdentifier),
UriResourceIdentifier::TYPE => $this->createUriResourceIdentifier($givenIdentifier),
default => throw new CanNotResolveSystemResourceIdentifierException(sprintf('Can not resolve system resource identifier "%s".', $resourceIdentifier), 1758700314),
};
}
/**
* @throws InvalidSystemResourceIdentifierException
*/
public function createFromPackagePath(string $packageKey, string $relativePath, string $givenIdentifier): PackageResourceIdentifier
{
return new PackageResourceIdentifier(
$this->getPackageAndValidatePath($packageKey, $relativePath, $givenIdentifier),
$relativePath,
$givenIdentifier
);
}
/**
* @throws InvalidSystemResourceIdentifierException
*/
private function createPackageResourceIdentifier(string $resourceIdentifier, ?string $originalIdentifier = null): PackageResourceIdentifier
{
[,$packageKey, $relativePath] = $this->parseIdentifier($resourceIdentifier);
return $this->createFromPackagePath($packageKey, $relativePath, $originalIdentifier ?? $resourceIdentifier);
}
/**
* @throws InvalidSystemResourceIdentifierException
*/
private function createFalResourceIdentifier(string $resourceIdentifier): FalResourceIdentifier
{
[,$storageId, $falIdentifier] = $this->parseIdentifier($resourceIdentifier);
return new FalResourceIdentifier($storageId, $falIdentifier, $resourceIdentifier);
}
/**
* @throws InvalidSystemResourceIdentifierException
*/
private function createUriResourceIdentifier(string $resourceIdentifier): UriResourceIdentifier
{
try {
return new UriResourceIdentifier($resourceIdentifier);
} catch (\Throwable $e) {
throw new InvalidSystemResourceIdentifierException(sprintf('Can not resolve system resource identifier "%s". Invalid URI.', $resourceIdentifier), 1761732010, $e);
}
}
/**
* @return array{string, string, string}
* @throws InvalidSystemResourceIdentifierException
*/
private function parseIdentifier(string $resourceIdentifier): array
{
$identifierParts = explode(':', $resourceIdentifier);
if (count($identifierParts) !== 3) {
throw new InvalidSystemResourceIdentifierException(sprintf('Given system resource identifier "%s" is invalid. An identifier consists of three parts, separated by a colon (":").', $resourceIdentifier), 1760386146);
}
return $identifierParts;
}
/**
* @throws InvalidSystemResourceIdentifierException
*/
private function convertExtensionPathToPackageResourceIdentifier(string $extensionPath): string
{
try {
$packageKey = $this->packageManager->extractPackageKeyFromPackagePath($extensionPath);
} catch (UnknownPackageException|UnknownPackagePathException $e) {
throw new InvalidSystemResourceIdentifierException(sprintf('Can not create system resource identifier from "%s".', $extensionPath), 1758884297, $e);
}
return sprintf(
'%s:%s:%s',
PackageResourceIdentifier::TYPE,
$packageKey,
substr($extensionPath, strlen($packageKey) + 5),
);
}
/**
* @throws InvalidSystemResourceIdentifierException
*/
private function getPackageAndValidatePath(string $packageKey, string $relativePath, string $givenIdentifier): PackageInterface
{
if ($relativePath === ''
|| str_starts_with($relativePath, '/')
|| !GeneralUtility::validPathStr($relativePath)
) {
throw new InvalidSystemResourceIdentifierException(sprintf('Relative package path "%s" must not be empty, must not start with a slash ("/") and must not contain invalid characters (e.g. ../ back path). (Given identifier "%s")', $relativePath, $givenIdentifier), 1763381514);
}
try {
$package = $this->packageManager->getPackage($packageKey);
} catch (UnknownPackageException $e) {
throw new InvalidSystemResourceIdentifierException(sprintf('Package with key "%s" does not exist. (Given identifier "%s")', $packageKey, $givenIdentifier), 1763381504, $e);
}
return $package;
}
}
@@ -0,0 +1,58 @@
<?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\Identifier;
use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Core\Http\Uri;
/**
* This is subject to change during v14 development. Do not use.
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
final class UriResourceIdentifier extends SystemResourceIdentifier
{
public const string TYPE = 'URI';
private readonly UriInterface $uri;
public function __construct(string $givenIdentifier)
{
parent::__construct($givenIdentifier);
if (str_starts_with($givenIdentifier, self::TYPE)) {
$uri = substr($givenIdentifier, strlen(self::TYPE) + 1);
}
$this->uri = new Uri($uri ?? $givenIdentifier);
}
public function getUri(): UriInterface
{
return $this->uri;
}
public function __toString()
{
if ($this->isRelative()) {
return $this->givenIdentifier;
}
return (string)$this->uri;
}
private function isRelative(): bool
{
return $this->uri->getAuthority() === '';
}
}
@@ -0,0 +1,172 @@
<?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\Publishing;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Http\NormalizedParams;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Core\Package\Exception\PackageAssetsPublishingFailedException;
use TYPO3\CMS\Core\Package\PackageInterface;
use TYPO3\CMS\Core\SystemResource\Exception\CanNotGenerateUriException;
use TYPO3\CMS\Core\SystemResource\Publishing\FileSystem\FileSystemPublisherInterface;
use TYPO3\CMS\Core\SystemResource\Type\PublicResourceInterface;
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* This implementation publishes (when implemented) public assets from extension
* packages to the public _assets directory using a hash as directory name.
* Subsequently, it can also generate URIs to public resource objects within that _assets folder
*
* @internal Never use or reference it directly, use SystemResourcePublisherInterface to inject it (or a proper replacement).
*/
#[Autoconfigure(public: true), AsAlias(SystemResourcePublisherInterface::class, public: true)]
final readonly class DefaultSystemResourcePublisher implements SystemResourcePublisherInterface
{
private const string PUBLISHING_DIRECTORY = '_assets/';
private const string PUBLISHING_DIRECTORY_INSTALL = '_assets_install/';
/**
* @var FileSystemPublisherInterface[]
*/
private array $fileSystemPublishers;
private string $publishingDirectory;
public function __construct(
array $fileSystemPublishers = [],
bool $failsafe = false,
) {
$this->fileSystemPublishers = $fileSystemPublishers;
$this->publishingDirectory = $failsafe ? self::PUBLISHING_DIRECTORY_INSTALL : self::PUBLISHING_DIRECTORY;
}
public function publishResources(
PackageInterface $package,
): FlashMessageQueue {
$queue = new FlashMessageQueue('asset:publish');
$resourceDefinitions = $package
->getResources()
->getPublicResourceDefinitions();
foreach ($resourceDefinitions as $definition) {
$publishingContext = new ResourcePublishingContext(
package: $package,
definition: $definition,
);
if ($publishingContext->isSourcePublic) {
continue;
}
$publicResourcesPath = Environment::getPublicPath() . '/' . $this->publishingDirectory . $publishingContext->prefix;
GeneralUtility::mkdir_deep(dirname($publicResourcesPath));
try {
foreach ($this->fileSystemPublishers as $publisher) {
if (!$publisher->canPublish($publishingContext->filesystemPath, $publicResourcesPath)) {
continue;
}
if (is_file($publishingContext->filesystemPath)) {
$publisher->publishFile($publishingContext->filesystemPath, $publicResourcesPath);
} else {
if (!is_dir($publishingContext->filesystemPath)) {
$queue->addMessage(new FlashMessage(
sprintf(
'Did not publish public resource for extension "%s".'
. chr(10)
. 'The source file/directory "%s" does not exist.',
$package->getPackageKey(),
substr($publishingContext->filesystemPath, strlen(Environment::getProjectPath())),
),
$package->getPackageKey(),
ContextualFeedbackSeverity::INFO,
));
break;
}
$publisher->publishFolder($publishingContext->filesystemPath, $publicResourcesPath);
}
break;
}
} catch (PackageAssetsPublishingFailedException $e) {
$queue->addMessage(new FlashMessage(
sprintf(
'Could not publish public resources for extension "%s" by using the "%s" strategy.'
. chr(10)
. 'Check whether the target directory "%s" already exists'
. chr(10)
. 'and TYPO3 has permissions to write inside the "_assets" directory.',
$package->getPackageKey(),
$e->publishingStrategy,
'.' . substr($publicResourcesPath, strlen(Environment::getProjectPath())),
),
$package->getPackageKey(),
ContextualFeedbackSeverity::ERROR,
));
}
}
return $queue;
}
/**
* @throws CanNotGenerateUriException
*/
public function generateUri(PublicResourceInterface $publicResource, ?ServerRequestInterface $request, ?UriGenerationOptions $options = null): UriInterface
{
if (!$publicResource->isPublished()) {
throw new CanNotGenerateUriException(sprintf('Can not generate Uri for an unpublished resource %s', $publicResource), 1761211273);
}
$request ??= $GLOBALS['TYPO3_REQUEST'] ?? null;
$options ??= new UriGenerationOptions();
return $publicResource->getPublicUri(
new DefaultSystemResourceUriGenerator(
$this->publishingDirectory,
$this->extractPublicPrefixFromRequest($request, $options->uriPrefix),
$request,
$options,
)
);
}
private function extractPublicPrefixFromRequest(?ServerRequestInterface $request, ?string $publicPrefix): string
{
if ($publicPrefix !== null) {
return $publicPrefix;
}
if ($request === null) {
return '/';
}
$normalizedParams = $request->getAttribute('normalizedParams');
return $this->getFrontendUrlPrefix($request->getAttribute('frontend.typoscript')?->getConfigArray(), $normalizedParams)
?? $normalizedParams->getSitePath();
}
private function getFrontendUrlPrefix(?array $typoScriptConfigArray, NormalizedParams $normalizedParams): ?string
{
if ($typoScriptConfigArray === null) {
return null;
}
if ($typoScriptConfigArray['forceAbsoluteUrls'] ?? false) {
return $normalizedParams->getSiteUrl();
}
$absRefPrefix = trim($typoScriptConfigArray['absRefPrefix'] ?? '');
return $absRefPrefix === 'auto' ? $normalizedParams->getSitePath() : $absRefPrefix;
}
}
@@ -0,0 +1,104 @@
<?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\Publishing;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Http\ApplicationType;
use TYPO3\CMS\Core\Http\Uri;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\SystemResource\Exception\CanNotGenerateUriException;
use TYPO3\CMS\Core\SystemResource\Http\CacheBustingUri;
/**
* This is tightly coupled to DefaultSystemResourcePublisher and acts
* as a helper to actually generate the URI for a public resource.
* This helper and its interface only exists to not expose the absolute
* path from the system resource objects directly.
*
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
readonly class DefaultSystemResourceUriGenerator implements SystemResourceUriGeneratorInterface
{
public function __construct(
private string $publishingDirectory,
private string $prefix,
private ?ServerRequestInterface $request,
private UriGenerationOptions $options,
) {}
public function generateForPackageResource(
ResourceUriBuildingContext $context,
): UriInterface {
$uri = $this->makeAbsolute(new Uri($this->calculateUriPath($context)));
if (!$this->options->cacheBusting) {
return $uri;
}
return CacheBustingUri::fromFileSystemPath(
$context->absoluteResourcePath,
$uri,
$this->request ? ApplicationType::fromRequest($this->request) : null
);
}
public function generateForFile(File $file): UriInterface
{
$publicUrl = $file->getPublicUrl();
if ($publicUrl === null) {
throw new CanNotGenerateUriException(sprintf('Can not create a public Uri for a file %s', $file), 1758619473);
}
if (Environment::isCli()) {
// On CLI FAL public URLs are always relative to public directory,
// so we apply the prefix here, which is likely a "/" only,
// unless calling code properly faked a request.
$publicUrl = $this->prefix . $publicUrl;
}
$uri = $this->makeAbsolute(new Uri($publicUrl));
if (!$this->options->cacheBusting) {
return $uri;
}
return CacheBustingUri::fromFile(
$file,
$uri,
);
}
private function makeAbsolute(UriInterface $uri): UriInterface
{
if ($this->request === null || !$this->options->absoluteUri) {
return $uri;
}
if ($uri->getHost() !== '') {
return $uri;
}
$siteUri = new Uri($this->request->getAttribute('normalizedParams')->getSiteUrl());
return $uri->withScheme($siteUri->getScheme())
->withUserInfo($siteUri->getUserInfo())
->withHost($siteUri->getHost())
->withPort($siteUri->getPort());
}
private function calculateUriPath(ResourceUriBuildingContext $context): string
{
if ($context->isSourcePublic) {
return $this->prefix . substr($context->absoluteResourcePath, strlen(Environment::getPublicPath()) + 1);
}
return $this->prefix . $this->publishingDirectory . $context->uriPath;
}
}
@@ -0,0 +1,38 @@
<?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\Publishing\FileSystem;
use TYPO3\CMS\Core\Package\Exception\PackageAssetsPublishingFailedException;
/**
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
interface FileSystemPublisherInterface
{
public function canPublish(string $source, string $target): bool;
/**
* @throws PackageAssetsPublishingFailedException
*/
public function publishFolder(string $source, string $target): void;
/**
* @throws PackageAssetsPublishingFailedException
*/
public function publishFile(string $source, string $target): void;
}
@@ -0,0 +1,80 @@
<?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\Publishing\FileSystem;
use Symfony\Component\Filesystem\Exception\IOException;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Package\Exception\PackageAssetsPublishingFailedException;
use TYPO3\CMS\Core\Utility\File\FileSystem;
/**
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
final readonly class JunctionPublisher implements FileSystemPublisherInterface
{
private PublishingConfiguration $config;
public function __construct(private FileSystem $fileSystem)
{
$this->config = new PublishingConfiguration();
}
public function canPublish(string $source, string $target): bool
{
return Environment::isWindows()
&& $this->config->isLinkPublishingEnabled();
}
/**
* @throws PackageAssetsPublishingFailedException
*/
public function publishFolder(string $source, string $target): void
{
$this->ensureJunctionExists($source, $target);
}
/**
* @throws \LogicException
*/
public function publishFile(string $source, string $target): void
{
throw new \LogicException(self::class . ' can not be used to publish single files', 1772535297);
}
/**
* @throws PackageAssetsPublishingFailedException
*/
private function ensureJunctionExists(string $target, string $junction): void
{
$e = null;
if (!$this->fileSystem->isJunction($junction)) {
try {
$this->fileSystem->junction($target, $junction);
} catch (IOException $e) {
}
}
if ($e !== null || realpath($target) !== realpath($junction)) {
throw new PackageAssetsPublishingFailedException(
'junction',
1717488535,
$e,
);
}
}
}
@@ -0,0 +1,74 @@
<?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\Publishing\FileSystem;
use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
use TYPO3\CMS\Core\Package\Exception\PackageAssetsPublishingFailedException;
/**
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
final readonly class MirrorPublisher implements FileSystemPublisherInterface
{
private PublishingConfiguration $config;
public function __construct()
{
$this->config = new PublishingConfiguration();
}
public function canPublish(string $source, string $target): bool
{
return $this->config->isMirrorPublishingEnabled();
}
public function publishFolder(string $source, string $target): void
{
if (realpath($source) === realpath($target)) {
throw new PackageAssetsPublishingFailedException(
'mirror',
1773140314,
);
}
$symfonyFilesystem = new SymfonyFilesystem();
$symfonyFilesystem->mirror(
$source,
$target,
null,
[
'delete' => true,
'override' => true,
],
);
}
public function publishFile(string $source, string $target): void
{
if (!is_file($source)) {
throw new \LogicException('Can not publish file, because source is not a file', 1772538042);
}
if (realpath($source) === realpath($target)) {
throw new PackageAssetsPublishingFailedException(
'mirror',
1773140294,
);
}
$symfonyFilesystem = new SymfonyFilesystem();
$symfonyFilesystem->copy($source, $target, true);
}
}
@@ -0,0 +1,60 @@
<?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\Publishing\FileSystem;
use TYPO3\CMS\Core\Core\Environment;
/**
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
final readonly class PublishingConfiguration
{
private string $publishingType;
public function __construct(?string $publishingType = null)
{
$publishingType = $publishingType ?? $GLOBALS['TYPO3_CONF_VARS']['SYS']['SystemResources']['filesystemPublishingType'] ?? 'auto';
if ($publishingType === 'auto') {
$publishingType = Environment::getContext()->isDevelopment() ? 'link' : 'mirror';
}
$this->publishingType = $publishingType;
}
public function isLinkPublishingEnabled(): bool
{
return $this->publishingType === 'link';
}
public function isMirrorPublishingEnabled(): bool
{
return $this->publishingType === 'mirror';
}
public function hasCustomPublishingType(): bool
{
return !$this->isMirrorPublishingEnabled() && !$this->isLinkPublishingEnabled();
}
public function getCustomPublishingType(): string
{
if (!$this->hasCustomPublishingType()) {
throw new \LogicException('There is no custom publishing type enabled', 1773138713);
}
return $this->publishingType;
}
}
@@ -0,0 +1,82 @@
<?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\Publishing\FileSystem;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Package\Exception\PackageAssetsPublishingFailedException;
use TYPO3\CMS\Core\Utility\File\FileSystem;
/**
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
final readonly class SymlinkPublisher implements FileSystemPublisherInterface
{
private PublishingConfiguration $config;
public function __construct(private FileSystem $fileSystem)
{
$this->config = new PublishingConfiguration();
}
public function canPublish(string $source, string $target): bool
{
return !Environment::isWindows()
&& $this->config->isLinkPublishingEnabled();
}
public function publishFolder(string $source, string $target): void
{
$this->ensureSymlinkExists($source, $target, 'dir');
}
public function publishFile(string $source, string $target): void
{
$this->ensureSymlinkExists($source, $target, 'file');
}
/**
* @throws PackageAssetsPublishingFailedException
*/
private function ensureSymlinkExists(string $target, string $link, string $type): void
{
$success = true;
if (!$this->isSymlinked($link, $type)) {
$success = $this->fileSystem->relativeSymlink($target, $link);
}
$this->ensureIsValid($target, $link, $success);
}
private function isSymlinked(string $link, string $type): bool
{
return match ($type) {
'file' => $this->fileSystem->isSymlinkedFile($link),
'dir' => $this->fileSystem->isSymlinkedDirectory($link),
default => throw new \UnexpectedValueException(sprintf('Type can only be "file" or "dir", "%s" given.', $type), 1774611766),
};
}
private function ensureIsValid(string $target, string $link, bool $success): void
{
if (!$success || realpath($target) !== realpath($link)) {
throw new PackageAssetsPublishingFailedException(
'symlink',
1717488536,
);
}
}
}
@@ -0,0 +1,48 @@
<?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\Publishing;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Package\PackageInterface;
use TYPO3\CMS\Core\Package\Resource\Definition\DynamicPublicPrefixInterface;
use TYPO3\CMS\Core\Package\Resource\Definition\PublicResourceDefinition;
/**
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
final readonly class ResourcePublishingContext
{
public string $prefix;
public bool $isSourcePublic;
public string $filesystemPath;
/**
* Variable names are explicitly public API
* for named variable access
*/
public function __construct(
private PackageInterface $package,
private PublicResourceDefinition $definition,
) {
$this->prefix = $definition->getPublicPrefix() instanceof DynamicPublicPrefixInterface
? $definition->getPublicPrefix()->calculatePrefix($package, $definition)
: $definition->getPublicPrefix();
$this->isSourcePublic = str_starts_with($this->package->getPackagePath() . $this->definition->getRelativePath(), Environment::getPublicPath());
$this->filesystemPath = $package->getPackagePath() . $definition->getRelativePath();
}
}
@@ -0,0 +1,50 @@
<?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\Publishing;
use TYPO3\CMS\Core\Package\PackageInterface;
use TYPO3\CMS\Core\Package\Resource\Definition\PublicResourceDefinition;
use TYPO3\CMS\Core\SystemResource\Type\PublicPackageFile;
/**
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
final readonly class ResourceUriBuildingContext
{
public string $absoluteResourcePath;
public string $uriPath;
public bool $isSourcePublic;
/**
* Variable names are explicitly public API
* for named variable access
*/
public function __construct(
public PublicPackageFile $resource,
public PackageInterface $package,
public PublicResourceDefinition $definition,
) {
$this->absoluteResourcePath = $this->package->getPackagePath() . $this->resource->getRelativePath();
$publishingContext = new ResourcePublishingContext(
package: $package,
definition: $definition
);
$this->isSourcePublic = $publishingContext->isSourcePublic;
$this->uriPath = $publishingContext->prefix . substr($this->resource->getRelativePath(), strlen($this->definition->getRelativePath()));
}
}
@@ -0,0 +1,37 @@
<?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\Publishing;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Core\Package\PackageInterface;
use TYPO3\CMS\Core\SystemResource\Type\PublicResourceInterface;
/**
* Implementations of this interface can publish public extension resources (once implemented)
* and therefore also generate URIs to those published resources.
* E.g. publish resources directly to a CDN and then generate CDS URIs
* to those resources.
*/
interface SystemResourcePublisherInterface
{
public function publishResources(PackageInterface $package): FlashMessageQueue;
public function generateUri(PublicResourceInterface $publicResource, ?ServerRequestInterface $request, ?UriGenerationOptions $options = null): UriInterface;
}
@@ -0,0 +1,36 @@
<?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\Publishing;
use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Core\Resource\File;
/**
* This is an implementation detail to allow not exposing the absolute file path
* to extension resources directly, but only to the resource publisher
* when generating URLs to the _assets directory.
*
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
interface SystemResourceUriGeneratorInterface
{
public function generateForPackageResource(
ResourceUriBuildingContext $context,
): UriInterface;
public function generateForFile(File $file): UriInterface;
}
@@ -0,0 +1,44 @@
<?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\Publishing;
/**
* Options for system resource URI generation.
* These might change, by adding more options,
* which means the variable names MUST be kept
* (or properly deprecated) as they are public API.
* Also, this object MUST be crated using named arguments.
*/
final readonly class UriGenerationOptions
{
/**
* Variable names are explicitly public API
* for named variable access
*
* Some or all of these options might to be applicable
* to specific implementations SystemResourcePublisherInterface,
* which means, that if other resource publishing strategies
* are configured, that changing these options might not
* influence the resulting URI
*/
public function __construct(
public ?string $uriPrefix = null,
public bool $absoluteUri = false,
public bool $cacheBusting = true,
) {}
}
@@ -0,0 +1,215 @@
<?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);
}
}
@@ -0,0 +1,113 @@
<?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\Type;
use TYPO3\CMS\Core\Package\Resource\Definition\ResourceDefinitionInterface;
use TYPO3\CMS\Core\SystemResource\Exception\SystemResourceDoesNotExistException;
use TYPO3\CMS\Core\SystemResource\Identifier\PackageResourceIdentifier;
use TYPO3\CMS\Core\Type\File\FileInfo;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
class PackageResource implements SystemResourceInterface
{
private ?FileInfo $fileInfo = null;
public function __construct(
protected readonly PackageResourceIdentifier $identifier,
protected readonly ResourceDefinitionInterface $resourceDefinition,
) {}
public function getName(): string
{
return PathUtility::pathinfo($this->identifier->getRelativePath())['basename'];
}
public function getNameWithoutExtension(): string
{
return PathUtility::pathinfo($this->identifier->getRelativePath())['filename'];
}
public function getExtension(): string
{
return PathUtility::pathinfo($this->identifier->getRelativePath())['extension'];
}
/**
* @throws SystemResourceDoesNotExistException
*/
public function getContents(): string
{
$fileInfo = $this->getValidatedFileInfo();
$content = file_get_contents($fileInfo->getPathname());
if ($content === false) {
throw new SystemResourceDoesNotExistException(sprintf('Can not get contents from referenced system resource "%s" (resolved as "%s")', $this->identifier->givenIdentifier, $this), 1758714587);
}
return $content;
}
/**
* @throws SystemResourceDoesNotExistException
*/
public function getMimeType(): string
{
$fileInfo = $this->getValidatedFileInfo();
$mimeType = $fileInfo->getMimeType();
if ($mimeType === false) {
throw new SystemResourceDoesNotExistException(sprintf('Can not get mime type from referenced system resource "%s" (resolved as "%s")', $this->identifier->givenIdentifier, $this), 1758786841);
}
return $mimeType;
}
/**
* @throws SystemResourceDoesNotExistException
*/
public function getHash(): string
{
$fileInfo = $this->getValidatedFileInfo();
return md5_file($fileInfo->getPathname());
}
/**
* @throws SystemResourceDoesNotExistException
*/
private function getValidatedFileInfo(): FileInfo
{
if ($this->fileInfo !== null) {
return $this->fileInfo;
}
$fileInfo = GeneralUtility::makeInstance(FileInfo::class, $this->identifier->getPackage()->getPackagePath() . $this->identifier->getRelativePath());
if (!$fileInfo->isFile()) {
throw new SystemResourceDoesNotExistException(sprintf('Referenced system resource "%s" (resolved as "%s") does not exist, or is not a file', $this->identifier->givenIdentifier, $this), 1758785343);
}
return $this->fileInfo = $fileInfo;
}
public function getResourceIdentifier(): string
{
return (string)$this->identifier;
}
public function __toString(): string
{
return $this->getResourceIdentifier();
}
}
@@ -0,0 +1,79 @@
<?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\Type;
use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Core\Package\Resource\Definition\PublicResourceDefinition;
use TYPO3\CMS\Core\SystemResource\Identifier\PackageResourceIdentifier;
use TYPO3\CMS\Core\SystemResource\Publishing\ResourceUriBuildingContext;
use TYPO3\CMS\Core\SystemResource\Publishing\SystemResourceUriGeneratorInterface;
/**
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
final class PublicPackageFile extends PackageResource implements PublicResourceInterface
{
public function __construct(
PackageResourceIdentifier $identifier,
PublicResourceDefinition $resourceDefinition,
) {
parent::__construct($identifier, $resourceDefinition);
}
public function getPublicUri(SystemResourceUriGeneratorInterface $uriGenerator): UriInterface
{
// This is fine, as the type is enforced in constructor
assert($this->resourceDefinition instanceof PublicResourceDefinition);
return $uriGenerator->generateForPackageResource(
new ResourceUriBuildingContext(
resource: $this,
package: $this->identifier->getPackage(),
definition: $this->resourceDefinition,
),
);
}
public function getRelativePath(): string
{
return $this->identifier->getRelativePath();
}
public function isPublished(): bool
{
return $this->identifier->getPackage()->getResources()->isPublicPath($this->identifier->getRelativePath());
}
/**
* @internal This API is only meant for very limited use cases,
* e.g. for building URIs for Vite dev server, where the dev server actually
* publishes (exposes) all (private) source files, so that they can be processed on the fly
* Do *not* use within TYPO3 core or other third party extensions
*/
public static function fromPackageResource(PackageResource $packageResource): self
{
if ($packageResource instanceof PublicResourceInterface) {
throw new \LogicException('It is pointless to create a public resource from an already public resource', 1761217630);
}
return new self(
$packageResource->identifier,
new PublicResourceDefinition(
$packageResource->resourceDefinition->getRelativePath()
)
);
}
}
@@ -0,0 +1,40 @@
<?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\Type;
use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Core\SystemResource\Publishing\SystemResourceUriGeneratorInterface;
/**
* This interface is public API and can be referenced in third party code
* or throughout the TYPO3 core.
* Implementations of this interface are internal though
* and must only happen in TYPO3\CMS\Core\SystemResource namespace
*/
interface PublicResourceInterface extends StaticResourceInterface
{
/**
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
public function getPublicUri(SystemResourceUriGeneratorInterface $uriGenerator): UriInterface;
/**
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
public function isPublished(): bool;
}
@@ -0,0 +1,35 @@
<?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\Type;
/**
* All static resources in TYPO3 have one thing in common
* and that is, that they can be identified with a unique string.
* This means they can be constructed from a string by SystemResourceFactory,
* and they can be cast to a unique string representation.
*
* Currently absolute and relative URLs (UriResource), files within an extension directory (PackageResource),
* and file abstraction layer (FAL) files serve as static resources
* to be referenced e.g. as public URL (see PublicResourceInterface)
*
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
interface StaticResourceInterface extends \Stringable
{
public function getResourceIdentifier(): string;
}
@@ -0,0 +1,39 @@
<?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\Type;
use TYPO3\CMS\Core\SystemResource\Exception\SystemResourceDoesNotExistException;
/**
* This interface is public API and can be referenced in third party code
* or throughout the TYPO3 core.
* Implementations of this interface are internal though
* and must only happen in TYPO3\CMS\Core\SystemResource namespace
*/
interface SystemResourceInterface extends StaticResourceInterface
{
/**
* @throws SystemResourceDoesNotExistException
*/
public function getContents(): string;
public function getName(): string;
public function getNameWithoutExtension(): string;
public function getExtension(): string;
public function getMimeType(): string;
public function getHash(): string;
}
@@ -0,0 +1,50 @@
<?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\Type;
use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Core\SystemResource\Identifier\UriResourceIdentifier;
use TYPO3\CMS\Core\SystemResource\Publishing\SystemResourceUriGeneratorInterface;
/**
* @internal Only to be used in TYPO3\CMS\Core\SystemResource namespace
*/
final readonly class UriResource implements StaticResourceInterface, PublicResourceInterface
{
public function __construct(private UriResourceIdentifier $identifier) {}
public function getPublicUri(SystemResourceUriGeneratorInterface $uriGenerator): UriInterface
{
return $this->identifier->getUri();
}
public function isPublished(): bool
{
return true;
}
public function __toString(): string
{
return $this->getResourceIdentifier();
}
public function getResourceIdentifier(): string
{
return (string)$this->identifier;
}
}