Skip to content

Commit e80a330

Browse files
bmacklolli42
authored andcommitted
[!!!][TASK] Require composer.json in classic mode
This change scans all extension paths for extensions that include a composer.json, and not for a ext_emconf.php, making ext_emconf.php obsolete, and requiring composer.json as well for both install types. TER extensions require a valid composer.json since 2021 already. Resolves: #108310 Releases: main Change-Id: I942fde4089ea515a941d98d050fa4cc61fab9ad1 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/91856 Tested-by: Georg Ringer <georg.ringer@gmail.com> Reviewed-by: Garvin Hicking <garvin@hick.ing> Tested-by: Helmut Hummel <typo3@helhum.io> Tested-by: Garvin Hicking <garvin@hick.ing> Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch> Reviewed-by: Georg Ringer <georg.ringer@gmail.com> Reviewed-by: Helmut Hummel <typo3@helhum.io> Reviewed-by: Markus Klein <markus.klein@typo3.org> Tested-by: Markus Klein <markus.klein@typo3.org> Tested-by: Christian Kuhn <lolli@schwarzbu.ch> Tested-by: core-ci <typo3@b13.com>
1 parent a535d2b commit e80a330

File tree

4 files changed

+82
-25
lines changed

4 files changed

+82
-25
lines changed

Build/phpstan/phpstan-baseline.neon

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -924,12 +924,6 @@ parameters:
924924
count: 1
925925
path: ../../typo3/sysext/core/Classes/Package/PackageActivationService.php
926926

927-
-
928-
message: '#^Call to function is_object\(\) with object will always evaluate to true\.$#'
929-
identifier: function.alreadyNarrowedType
930-
count: 1
931-
path: ../../typo3/sysext/core/Classes/Package/PackageManager.php
932-
933927
-
934928
message: '#^Offset string does not exist on null\.$#'
935929
identifier: offsetAccess.notFound

typo3/sysext/core/Classes/Package/PackageManager.php

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -319,33 +319,44 @@ public function packagesMayHaveChanged(PackagesMayHaveChangedEvent $event): void
319319
}
320320

321321
/**
322-
* Fetches all directories from sysext/global/local locations and checks if the extension contains an ext_emconf.php
322+
* Fetches all directories from sysext/local locations and checks if the extension contains an composer.json
323+
* with a type "typo3-cms-framework", or "typo3-cms-extension".
323324
*
324325
* @return array
325326
*/
326327
protected function scanPackagePathsForExtensions()
327328
{
328329
$collectedExtensionPaths = [];
329330
foreach ($this->getPackageBasePaths() as $packageBasePath) {
330-
// Only add the extension if we have an EMCONF and the extension is not yet registered.
331+
// Only add the extension if we have a composer.json and the extension is not yet registered.
331332
// This is crucial in order to allow overriding of system extension by local extensions
332333
// and strongly depends on the order of paths defined in $this->packagesBasePaths.
333334
$finder = new Finder();
334335
$finder
335-
->name('ext_emconf.php')
336+
->name('composer.json')
336337
->followLinks()
337338
->depth(0)
338339
->ignoreUnreadableDirs()
339340
->in($packageBasePath);
340341

341342
/** @var SplFileInfo $fileInfo */
342343
foreach ($finder as $fileInfo) {
344+
try {
345+
$composerContents = json_decode($fileInfo->getContents(), false, 512, JSON_THROW_ON_ERROR);
346+
} catch (\JsonException) {
347+
continue;
348+
}
349+
$packageType = $composerContents->type ?? '';
350+
// Only allow typo3-cms package types
351+
if (!str_starts_with($packageType, 'typo3-cms-')) {
352+
continue;
353+
}
343354
$path = PathUtility::dirname($fileInfo->getPathname());
344-
$extensionName = PathUtility::basename($path);
345355
// Fix Windows backslashes
346356
$currentPath = GeneralUtility::fixWindowsFilePath($path) . '/';
347-
if (!isset($collectedExtensionPaths[$extensionName])) {
348-
$collectedExtensionPaths[$extensionName] = $currentPath;
357+
$packageKey = $this->getPackageKeyFromManifest($composerContents, $currentPath);
358+
if (!isset($collectedExtensionPaths[$packageKey])) {
359+
$collectedExtensionPaths[$packageKey] = $currentPath;
349360
}
350361
}
351362
}
@@ -997,23 +1008,16 @@ protected function getDependencyArrayForPackage($packageKey, array &$dependentPa
9971008
*
9981009
* Else the composer name will be used with the slash replaced by a dot
9991010
*
1000-
* @param object $manifest
1001-
* @param string $packagePath
10021011
* @throws Exception\InvalidPackageManifestException
1003-
* @return string
10041012
*/
1005-
protected function getPackageKeyFromManifest($manifest, $packagePath)
1013+
protected function getPackageKeyFromManifest(\stdClass $manifest, string $packagePath): string
10061014
{
1007-
if (!is_object($manifest)) {
1008-
throw new InvalidPackageManifestException('Invalid composer manifest in package path: ' . $packagePath, 1348146451);
1009-
}
10101015
if (!empty($manifest->extra->{'typo3/cms'}->{'extension-key'})) {
10111016
return $manifest->extra->{'typo3/cms'}->{'extension-key'};
10121017
}
10131018
if (empty($manifest->name) || (isset($manifest->type) && str_starts_with($manifest->type, 'typo3-cms-'))) {
1014-
return PathUtility::basename($packagePath);
1019+
throw new InvalidPackageManifestException('Invalid composer manifest (no extension key set) in package path: ' . $packagePath, 1348146451);
10151020
}
1016-
10171021
return $manifest->name;
10181022
}
10191023

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
.. include:: /Includes.rst.txt
2+
3+
.. _breaking-108310-1732545123:
4+
5+
=========================================================
6+
Breaking: #108310 - Require composer.json in classic mode
7+
=========================================================
8+
9+
See :issue:`108310`
10+
11+
Description
12+
===========
13+
14+
Extension detection in classic mode now requires a valid :file:`composer.json`
15+
file instead of :file:`ext_emconf.php`. The :file:`composer.json` file must
16+
include :json:`"type": "typo3-cms-*"` and the extension key in
17+
:json:`extra.typo3/cms.extension-key`.
18+
19+
Impact
20+
======
21+
22+
Extensions without a valid :file:`composer.json` are no longer detected
23+
and loaded in classic mode installations.
24+
25+
Affected installations
26+
======================
27+
28+
All classic mode installations must verify that every extension contains
29+
a :file:`composer.json` with:
30+
31+
* :json:`"type"` starting with :json:`"typo3-cms-"`
32+
* :json:`"extra.typo3/cms.extension-key"` containing the extension key
33+
34+
Composer-based installations are not affected.
35+
36+
Migration
37+
=========
38+
39+
Extension authors must ensure their extensions include a valid
40+
:file:`composer.json`. TER extensions have required this since 2021.
41+
42+
Example :file:`composer.json`:
43+
44+
.. code-block:: json
45+
46+
{
47+
"name": "vendor/extension-name",
48+
"type": "typo3-cms-extension",
49+
"extra": {
50+
"typo3/cms": {
51+
"extension-key": "extension_name"
52+
}
53+
}
54+
}
55+
56+
.. index:: PHP-API, NotScanned, ext:core

typo3/sysext/core/Tests/Unit/Package/PackageManagerTest.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ protected function createPackage(string $packageKey): Package
8787
mkdir($packagePath, 0770, true);
8888
}
8989
file_put_contents($packagePath . 'ext_emconf.php', '<?php' . LF . '$EM_CONF[$_EXTKEY] = [];');
90-
file_put_contents($packagePath . 'composer.json', '{}');
90+
file_put_contents(
91+
$packagePath . 'composer.json',
92+
json_encode(['extra' => ['typo3/cms' => ['extension-key' => $packageKey]]], JSON_THROW_ON_ERROR)
93+
);
9194
$package = new Package($this->packageManager, $packageKey, $packagePath);
9295
$this->packageManager->registerPackage($package);
9396
$this->packageManager->activatePackage($packageKey);
@@ -127,7 +130,7 @@ public function scanAvailablePackagesTraversesThePackagesDirectoryAndRegistersPa
127130
$packagePath = $this->testRoot . 'Packages/Application/' . $packageKey . '/';
128131

129132
mkdir($packagePath, 0770, true);
130-
file_put_contents($packagePath . 'composer.json', '{"name": "' . $packageKey . '", "type": "typo3-test"}');
133+
file_put_contents($packagePath . 'composer.json', '{"name": "' . $packageKey . '", "type": "typo3-cms-test", "extra": {"typo3/cms": {"extension-key": "' . $packageKey . '"}}}');
131134
}
132135

133136
$packageManager = $this->getAccessibleMock(PackageManager::class, ['sortAndSavePackageStates'], [new DependencyOrderingService()]);
@@ -158,7 +161,7 @@ public function scanAvailablePackagesKeepsExistingPackageConfiguration(): void
158161
$packagePaths[] = $packagePath;
159162

160163
mkdir($packagePath, 0770, true);
161-
file_put_contents($packagePath . 'composer.json', '{"name": "' . $packageKey . '", "type": "typo3-cms-test"}');
164+
file_put_contents($packagePath . 'composer.json', '{"name": "' . $packageKey . '", "type": "typo3-cms-test", "extra": {"typo3/cms": {"extension-key": "' . $packageKey . '"}}}');
162165
file_put_contents($packagePath . 'ext_emconf.php', '<?php' . LF . '$EM_CONF[$_EXTKEY] = [];');
163166
}
164167

@@ -225,7 +228,7 @@ public function packageStatesConfigurationContainsRelativePaths(): void
225228
$packagePath = $this->testRoot . 'Packages/Application/' . $packageKey . '/';
226229

227230
mkdir($packagePath, 0770, true);
228-
file_put_contents($packagePath . 'composer.json', '{"name": "' . $packageKey . '", "type": "typo3-cms-test"}');
231+
file_put_contents($packagePath . 'composer.json', '{"name": "' . $packageKey . '", "type": "typo3-cms-test", "extra": {"typo3/cms": {"extension-key": "' . $packageKey . '"}}}');
229232
file_put_contents($packagePath . 'ext_emconf.php', '<?php' . LF . '$EM_CONF[$_EXTKEY] = [];');
230233
$packagePaths[] = $packagePath;
231234
}

0 commit comments

Comments
 (0)