TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
<?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\Package\Cache;
|
||||
|
||||
use Composer\Util\Filesystem;
|
||||
|
||||
/**
|
||||
* TYPO3 Package "cache" for Composer mode.
|
||||
* This class is used in two contexts:
|
||||
* During Composer build time the artifact is stored
|
||||
* and during TYPO3 runtime the artifact is only read.
|
||||
* The context is decided on object construction.
|
||||
*
|
||||
* @internal This class is an implementation detail and does not represent public API
|
||||
*/
|
||||
class ComposerPackageArtifact implements PackageCacheInterface
|
||||
{
|
||||
/**
|
||||
* Location of the file inside the var folder
|
||||
*/
|
||||
private const string ARTIFACTS_FILE = '/PackageArtifact.php';
|
||||
|
||||
/**
|
||||
* Full filesystem path to the file
|
||||
*/
|
||||
private string $packageArtifactsFile;
|
||||
|
||||
/**
|
||||
* The cache entry generated from the artifact
|
||||
*/
|
||||
private PackageCacheEntry $cacheEntry;
|
||||
|
||||
/**
|
||||
* Composer filesystem, provided during Composer build time
|
||||
*/
|
||||
private ?Filesystem $filesystem;
|
||||
|
||||
/**
|
||||
* The cache identifier that is stored alongside the artifact
|
||||
* and used as part of TYPO3 cache identifiers
|
||||
*/
|
||||
private ?string $cacheIdentifier;
|
||||
|
||||
public function __construct(string $packageArtifactsPath, ?Filesystem $filesystem = null, ?string $cacheIdentifier = null)
|
||||
{
|
||||
$this->packageArtifactsFile = $packageArtifactsPath . self::ARTIFACTS_FILE;
|
||||
$this->filesystem = $filesystem;
|
||||
$this->cacheIdentifier = $cacheIdentifier;
|
||||
}
|
||||
|
||||
public function fetch(): PackageCacheEntry
|
||||
{
|
||||
if ($this->isComposerBuildContext()) {
|
||||
throw new \RuntimeException('Can not load package states during generation', 1629820498);
|
||||
}
|
||||
if (isset($this->cacheEntry)) {
|
||||
return $this->cacheEntry;
|
||||
}
|
||||
$packageData = @include $this->packageArtifactsFile;
|
||||
if (!$packageData) {
|
||||
throw new \RuntimeException('Package artifact not found. Run "composer install" to create it.', 1629819799);
|
||||
}
|
||||
|
||||
return $this->cacheEntry = PackageCacheEntry::fromCache($packageData);
|
||||
}
|
||||
|
||||
public function store(PackageCacheEntry $cacheEntry): void
|
||||
{
|
||||
if (!$this->isComposerBuildContext()) {
|
||||
throw new \RuntimeException('Can not modify package states in Composer mode', 1629819858);
|
||||
}
|
||||
$this->filesystem->ensureDirectoryExists(dirname($this->packageArtifactsFile));
|
||||
|
||||
file_put_contents($this->packageArtifactsFile, '<?php' . PHP_EOL . 'return ' . PHP_EOL . $cacheEntry->withIdentifier($this->cacheIdentifier)->serialize() . ';');
|
||||
}
|
||||
|
||||
public function invalidate(): void
|
||||
{
|
||||
throw new \RuntimeException('Can not modify package states in Composer mode', 1629824596);
|
||||
}
|
||||
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
if (!isset($this->cacheEntry)) {
|
||||
$this->fetch();
|
||||
}
|
||||
|
||||
return $this->cacheEntry->getIdentifier();
|
||||
}
|
||||
|
||||
private function isComposerBuildContext(): bool
|
||||
{
|
||||
return isset($this->filesystem, $this->cacheIdentifier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?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\Package\Cache;
|
||||
|
||||
use TYPO3\CMS\Core\Package\Exception\PackageManagerCacheUnavailableException;
|
||||
use TYPO3\CMS\Core\Package\Exception\PackageStatesUnavailableException;
|
||||
use TYPO3\CMS\Core\Package\PackageInterface;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
use TYPO3\CMS\Core\Serializer\DeserializationService;
|
||||
|
||||
/**
|
||||
* A TYPO3 Package cache entry.
|
||||
* Represents a concrete state of TYPO3 packages.
|
||||
* It interfaces between PackageManager and PackageCacheInterface.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @phpstan-import-type PackageKey from PackageManager
|
||||
* @phpstan-import-type PackageName from PackageManager
|
||||
* @phpstan-import-type StatesConfiguration from PackageManager
|
||||
* @phpstan-type CacheArray array{
|
||||
* identifier: string|null,
|
||||
* packageStatesConfiguration: StatesConfiguration,
|
||||
* packageAliasMap: array<PackageName, PackageKey>,
|
||||
* composerNameToPackageKeyMap: array<PackageName, PackageKey>,
|
||||
* packageObjects: string,
|
||||
* packageClasses: list<class-string>,
|
||||
* }
|
||||
*/
|
||||
class PackageCacheEntry
|
||||
{
|
||||
/**
|
||||
* Package configuration. Used by the PackageManager to identify "active" packages.
|
||||
* Every key in this array represents an active extension.
|
||||
*
|
||||
* @var StatesConfiguration
|
||||
*/
|
||||
private array $configuration;
|
||||
|
||||
/**
|
||||
* Alternative names for packages mapping to the package key.
|
||||
* Typically filled from replace section in composer.json
|
||||
*
|
||||
* @var array<PackageName, PackageKey>
|
||||
*/
|
||||
private array $aliasMap;
|
||||
|
||||
/**
|
||||
* Map from composer name of a package (key in this array) to its package key (value)
|
||||
*
|
||||
* @var array<PackageName, PackageKey>
|
||||
*/
|
||||
private array $composerNameMap;
|
||||
|
||||
/**
|
||||
* @var array<PackageKey, PackageInterface>
|
||||
*/
|
||||
private array $packages;
|
||||
|
||||
/**
|
||||
* Identifier for the current state, which can optionally
|
||||
* stored in the cache entry or artifact.
|
||||
* Currently, only used in Composer mode, where the identifier
|
||||
* is comprised from the composer.lock file and stored alongside the artifact.
|
||||
*/
|
||||
private ?string $identifier = null;
|
||||
|
||||
/**
|
||||
* @param StatesConfiguration $configuration
|
||||
* @param array<PackageName, PackageKey> $aliasMap
|
||||
* @param array<PackageName, PackageKey> $composerNameMap
|
||||
* @param array<PackageKey, PackageInterface> $packages
|
||||
*/
|
||||
private function __construct(
|
||||
array $configuration,
|
||||
array $aliasMap,
|
||||
array $composerNameMap,
|
||||
array $packages
|
||||
) {
|
||||
$this->configuration = $configuration;
|
||||
$this->aliasMap = $aliasMap;
|
||||
$this->composerNameMap = $composerNameMap;
|
||||
$this->packages = $packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the configuration has the correct version
|
||||
*
|
||||
* @throws PackageStatesUnavailableException
|
||||
*/
|
||||
public static function ensureValidPackageConfiguration(array $configuration): void
|
||||
{
|
||||
if (($configuration['version'] ?? 0) < 5) {
|
||||
throw new PackageStatesUnavailableException('The PackageStates.php file is either corrupt or unavailable.', 1381507733);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StatesConfiguration $packageStatesConfiguration
|
||||
* @param array<PackageName, PackageKey> $packageAliasMap
|
||||
* @param array<PackageName, PackageKey> $composerNameToPackageKeyMap
|
||||
* @param array<PackageKey, PackageInterface> $packageObjects
|
||||
*/
|
||||
public static function fromPackageData(
|
||||
array $packageStatesConfiguration,
|
||||
array $packageAliasMap,
|
||||
array $composerNameToPackageKeyMap,
|
||||
array $packageObjects
|
||||
): self {
|
||||
self::ensureValidPackageConfiguration($packageStatesConfiguration);
|
||||
|
||||
return new self(
|
||||
$packageStatesConfiguration,
|
||||
$packageAliasMap,
|
||||
$composerNameToPackageKeyMap,
|
||||
$packageObjects
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CacheArray $packageData
|
||||
*/
|
||||
public static function fromCache(array $packageData): self
|
||||
{
|
||||
try {
|
||||
self::ensureValidPackageConfiguration($packageData['packageStatesConfiguration'] ?? []);
|
||||
} catch (PackageStatesUnavailableException $e) {
|
||||
// Invalidate the cache entry
|
||||
throw new PackageManagerCacheUnavailableException('The package state cache could not be loaded.', 1393883341, $e);
|
||||
}
|
||||
// Package objects can now contain classes from userland.
|
||||
// We nevertheless restrict the unserialize call here to classes,
|
||||
// that have been identified during caching.
|
||||
// Yes, this list of classes could be tainted, but then, why not taint,
|
||||
// the complete cache file directly? A tainted list of class names isn't
|
||||
// really less obvious than other PHP code in the cache file.
|
||||
$cacheEntry = new self(
|
||||
$packageData['packageStatesConfiguration'],
|
||||
$packageData['packageAliasMap'],
|
||||
$packageData['composerNameToPackageKeyMap'],
|
||||
unserialize(
|
||||
$packageData['packageObjects'],
|
||||
// data is read from `PackageStates.php` already, if that file is tainted for
|
||||
// deserialization, code execution would have been possible anyway already
|
||||
['allowed_classes' => $packageData['packageClasses']],
|
||||
),
|
||||
);
|
||||
$cacheEntry->identifier = $packageData['identifier'] ?? null;
|
||||
|
||||
return $cacheEntry;
|
||||
}
|
||||
|
||||
public function serialize(): string
|
||||
{
|
||||
$serializedPackages = serialize($this->packages);
|
||||
$deserializationService = new DeserializationService();
|
||||
return var_export(
|
||||
[
|
||||
'identifier' => $this->identifier,
|
||||
'packageStatesConfiguration' => $this->configuration,
|
||||
'packageAliasMap' => $this->aliasMap,
|
||||
'composerNameToPackageKeyMap' => $this->composerNameMap,
|
||||
'packageObjects' => $serializedPackages,
|
||||
'packageClasses' => $deserializationService->parseClassNames($serializedPackages),
|
||||
],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public function getIdentifier(): ?string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
public function withIdentifier(string $identifier): self
|
||||
{
|
||||
$newEntry = clone $this;
|
||||
$newEntry->identifier = $identifier;
|
||||
|
||||
return $newEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StatesConfiguration
|
||||
*/
|
||||
public function getConfiguration(): array
|
||||
{
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<PackageName, PackageKey>
|
||||
*/
|
||||
public function getAliasMap(): array
|
||||
{
|
||||
return $this->aliasMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<PackageName, PackageKey>
|
||||
*/
|
||||
public function getComposerNameMap(): array
|
||||
{
|
||||
return $this->composerNameMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<PackageKey, PackageInterface>
|
||||
*/
|
||||
public function getPackages(): array
|
||||
{
|
||||
return $this->packages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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\Package\Cache;
|
||||
|
||||
use TYPO3\CMS\Core\Package\Exception\PackageManagerCacheUnavailableException;
|
||||
|
||||
/**
|
||||
* Interface for TYPO3 Package cache.
|
||||
*
|
||||
* This is an implementation detail to abstract the way the PackageManager
|
||||
* stores and retrieves package information of installed TYPO3 packages (extensions).
|
||||
*
|
||||
* In non Composer mode, the implementation remains to be around the availability
|
||||
* of a PackageStates.php file, which is used to generate a cache entry of Package objects.
|
||||
* in Composer mode, the package information is put in a persistent artifact file.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface PackageCacheInterface
|
||||
{
|
||||
/**
|
||||
* Fetch the (package states) entry from a persistent or transient location
|
||||
*/
|
||||
public function fetch(): PackageCacheEntry;
|
||||
|
||||
/**
|
||||
* Store the entry
|
||||
*/
|
||||
public function store(PackageCacheEntry $cacheEntry): void;
|
||||
|
||||
/**
|
||||
* Invalidate the current entry (only applicable in non Composer mode)
|
||||
*/
|
||||
public function invalidate(): void;
|
||||
|
||||
/**
|
||||
* Identifier that identifies the current state (typically a hash)
|
||||
* @throws PackageManagerCacheUnavailableException
|
||||
*/
|
||||
public function getIdentifier(): string;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?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\Package\Cache;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Information\Typo3Version;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
|
||||
/**
|
||||
* Represents a cache identifier to be used for caches that depend on
|
||||
* the list of packages bundled with TYPO3.
|
||||
* @internal
|
||||
*/
|
||||
#[AsAlias('package-dependent-cache-identifier', public: true)]
|
||||
class PackageDependentCacheIdentifier
|
||||
{
|
||||
private string $baseIdentifier;
|
||||
private string $prefix = '';
|
||||
private string $additionalIdentifier = '';
|
||||
|
||||
public function __construct(PackageManager $packageManager)
|
||||
{
|
||||
$this->baseIdentifier = (new Typo3Version())->getVersion() . Environment::getProjectPath() . ($packageManager->getCacheIdentifier() ?? '');
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->prefix . hash('xxh3', $this->baseIdentifier . $this->additionalIdentifier);
|
||||
}
|
||||
|
||||
public function withPrefix(string $prefix): self
|
||||
{
|
||||
$newIdentifier = clone $this;
|
||||
$newIdentifier->prefix = $prefix . '_';
|
||||
|
||||
return $newIdentifier;
|
||||
}
|
||||
|
||||
public function withAdditionalHashedIdentifier(string $additionalIdentifier): self
|
||||
{
|
||||
$newIdentifier = clone $this;
|
||||
$newIdentifier->additionalIdentifier = $additionalIdentifier;
|
||||
|
||||
return $newIdentifier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?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\Package\Cache;
|
||||
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Information\Typo3Version;
|
||||
use TYPO3\CMS\Core\Package\Exception\PackageManagerCacheUnavailableException;
|
||||
|
||||
/**
|
||||
* TYPO3 Package cache for package states file.
|
||||
* This replicates previous behaviour around the availability
|
||||
* of a PackageStates.php file and has been extracted from PackageManager
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PackageStatesPackageCache implements PackageCacheInterface
|
||||
{
|
||||
private const string CACHE_IDENTIFIER_PREFIX = 'PackageManager_';
|
||||
private ?string $cacheIdentifier;
|
||||
private string $packageStatesFile;
|
||||
private FrontendInterface $coreCache;
|
||||
|
||||
public function __construct(string $packageStatesFile, FrontendInterface $coreCache)
|
||||
{
|
||||
$this->packageStatesFile = $packageStatesFile;
|
||||
$this->coreCache = $coreCache;
|
||||
}
|
||||
|
||||
public function fetch(): PackageCacheEntry
|
||||
{
|
||||
$packageData = $this->coreCache->require(self::CACHE_IDENTIFIER_PREFIX . $this->getIdentifier());
|
||||
if ($packageData === false || !is_array($packageData)) {
|
||||
throw new PackageManagerCacheUnavailableException('The package state cache could not be loaded.', 1714031925);
|
||||
}
|
||||
|
||||
return PackageCacheEntry::fromCache($packageData ?: []);
|
||||
}
|
||||
|
||||
public function store(PackageCacheEntry $cacheEntry): void
|
||||
{
|
||||
$cacheIdentifier = $this->getIdentifier();
|
||||
$this->coreCache->set(
|
||||
self::CACHE_IDENTIFIER_PREFIX . $cacheIdentifier,
|
||||
'return ' . PHP_EOL . $cacheEntry->withIdentifier($cacheIdentifier)->serialize() . ';'
|
||||
);
|
||||
}
|
||||
|
||||
public function invalidate(): void
|
||||
{
|
||||
if (!isset($this->cacheIdentifier)) {
|
||||
return;
|
||||
}
|
||||
$this->coreCache->remove($this->cacheIdentifier);
|
||||
$this->cacheIdentifier = null;
|
||||
clearstatcache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines mtime and filesize to detect PackageStates.php changes.
|
||||
* mtime alone has 1-second resolution: a write within the same second
|
||||
* produces an identical identifier, causing PackageActivationService
|
||||
* to load a stale DI container missing the just-activated extension.
|
||||
*
|
||||
* @throws PackageManagerCacheUnavailableException
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
if (!isset($this->cacheIdentifier)) {
|
||||
$stat = @stat($this->packageStatesFile);
|
||||
if ($stat === false) {
|
||||
throw new PackageManagerCacheUnavailableException('The package state cache could not be loaded.', 1629817141);
|
||||
}
|
||||
$this->cacheIdentifier = md5((string)(new Typo3Version()) . $this->packageStatesFile . $stat['mtime'] . $stat['size']);
|
||||
}
|
||||
|
||||
return $this->cacheIdentifier;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user