From 70026a62a7189ca55c24daa77e395da95e03648a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BCrk?= Date: Wed, 8 Apr 2026 10:22:45 +0200 Subject: [PATCH 1/4] [TASK] Add ability for composer patches for development From time to time it could be use-full to have the ability to apply composer patches on packages, for example TYPO3 to have fixes in place before they are released. > [!NOTE] > Having this available in general does not hurt and has > no impact and can also be kept if no patches are needed > to be applied. This change makes this possible with following tasks: * 'vaimo/composer-patches' is added as development dependency to have it available, providing a lot of composer commands around applying and managing patches. * `Build/Scripts/download-patch-from-gerrit.phpsh` to have a simple tool to download gerrit changes and makes it available in `runTests.sh. The script splits them into the different patch files for each sysext containted in the change along with adding basic annotations like package name and link information. Changes on tests are automatically stripped because they are not included in composer dist archives and paths are also aligned to reduce required manual modifications. * Configure `patches/` as folder to search for patch files to avoid the need to manage patch files in the composer.json file and reduce changes on it. Used command(s): ```bash composer config \ "allow-plugins"."vaimo/composer-patches" true \ && composer config \ "extra"."patches-search" "patches/" \ && composer require --dev \ 'vaimo/composer-patches':'^6.0.1' ``` --- .../Scripts/download-patch-from-gerrit.phpsh | 142 ++++++++++++++++++ Build/Scripts/runTests.sh | 6 + composer.json | 9 +- 3 files changed, 154 insertions(+), 3 deletions(-) create mode 100755 Build/Scripts/download-patch-from-gerrit.phpsh diff --git a/Build/Scripts/download-patch-from-gerrit.phpsh b/Build/Scripts/download-patch-from-gerrit.phpsh new file mode 100755 index 00000000..acd34e25 --- /dev/null +++ b/Build/Scripts/download-patch-from-gerrit.phpsh @@ -0,0 +1,142 @@ +#!/usr/bin/php +`, `@label ` and `@link ` +// to the patch file as meta data. + +$gerritId = null; +$showUsage = count($argv) < 2; +$patchesDirectory = __DIR__ . '/../../patches/'; + +if (!$showUsage) { + $gerritId = (int)$argv[1]; +} + +if ($showUsage || empty($gerritId)) { + $usage = [ + 'NAME', + "\tbin/" . basename($argv[0]) . ' -- downloads a patch from Gerrit', + '', + 'SYNOPSIS', + "\tbin/" . basename($argv[0] . ' '), + '', + 'DESCRIPTION', + "\tThis will download the latest patchset from", + "\thttps://review.typo3.org/c/Packages/TYPO3.CMS/+/id then", + "\tsplit it for the various typo3/cms-* packages and update", + "\tyour composer.json", + ]; + echo implode("\n", $usage) . "\n\n"; + exit(1); +} + +$change = file_get_contents('https://review.typo3.org/changes/' . $gerritId); +// Fix garbage at the beginning +if (strpos($change, ")]}'") === 0) { + $change = json_decode(trim(substr($change, 4)), true); +} +if (empty($change['subject'])) { + echo "Change $gerritId was not found.\n"; + exit(2); +} + +$subject = $change['subject']; +$normalizedSubject = preg_replace('/-+/', '-', str_replace( + [ + '`', + ' ', + '[!!!]', + '[wip]', + '[', + ']', + ':', + '$', + '/', + '\\', + ], + [ + '', + '-', + 'breaking-', + '', + '', + '-', + '_', + '', + '', + '', + ], + mb_strtolower($subject) +)); +echo "Subject is '$subject'\n"; + +$patch = base64_decode(file_get_contents('https://review.typo3.org/changes/' . $gerritId . '/revisions/current/patch')); +$patchMessage = null; +$patches = []; +do { + $nextPatchPos = strpos($patch, 'diff --git', 1); + if ($nextPatchPos === false) { + $buffer = $patch; + $patch = ''; + } else { + $buffer = substr($patch, 0, $nextPatchPos); + $patch = substr($patch, $nextPatchPos); + } + if ($patchMessage === null) { + $patchMessage = $buffer; + continue; + } + // Ignore any file not part of a system extension, for example monorepository build system files. + if (!preg_match('#^diff --git a/typo3/sysext/([^/]+)/([^ ]+)#', $buffer, $matches)) { + continue; + } + $sysExtRelativeFilename = $matches[2]; + if (str_starts_with($sysExtRelativeFilename, 'Tests/')) { + // Skip any test related files, which are not included in distribution + // archives of composer packages and are not patchable. + continue; + } + $sysext = $matches[1]; + $package = match(true) { + str_starts_with($sysext, 'theme-') => 'typo3/' . str_replace('_', '-', $sysext), + default => 'typo3/cms-' . str_replace('_', '-', $sysext), + }; + $patchPrologue = [ + '@package ' . $package, + '@label ' . $subject, + '@link ' . 'https://review.typo3.org/c/Packages/TYPO3.CMS/+/' . $gerritId, + '', + ]; + if (!isset($patches[$package])) { + $patches[$package] = [ + implode(PHP_EOL, $patchPrologue) . PHP_EOL . $patchMessage, + ]; + } + + // Fix the patch + $prefix = 'typo3/sysext/' . $matches[1]; + $file = $matches[2]; + $buffer = str_replace(' a/' . $prefix . '/' . $file, ' a/' . $file, $buffer); + $buffer = str_replace(' b/' . $prefix . '/' . $file, ' b/' . $file, $buffer); + $patches[$package][] = $buffer; +} while (!empty($patch)); + +$composerChanges = []; +foreach ($patches as $package => $chunks) { + if (!is_dir($patchesDirectory)) { + @mkdir($patchesDirectory, 0775, true); + } + $content = implode('', $chunks); + $patchFileName = str_replace('/', '-', $package) . '_' . $gerritId . '_' . $normalizedSubject . '.patch'; + file_put_contents($patchesDirectory . $patchFileName, $content); + echo "Created patch '" . $patchesDirectory . $patchFileName . "'\n"; + + $composerChanges[$package] = [ + $subject => 'patches/' . $patchFileName, + ]; +} + +echo "\n\nRun \"./composer install\" to install and apply missing patches.\n\n"; diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh index 6191c016..27e895f7 100755 --- a/Build/Scripts/runTests.sh +++ b/Build/Scripts/runTests.sh @@ -131,6 +131,7 @@ Options: - composer: "composer" command dispatcher, to execute various composer commands - composerInstall: "composer install", handy if host has no PHP, uses composer cache of users home - composerValidate: "composer validate" + - downloadGerritPatch: Download TYPO3 Gerrit change and transform it to composer patch files in "patches/" - functional: functional tests - lint: PHP linting - phpstan: phpstan tests @@ -628,6 +629,11 @@ case ${TEST_SUITE} in cp composer.json composer.json.testing mv composer.json.orig composer.json ;; + downloadGerritPatch) + COMMAND=(php -dxdebug.mode=off Build/Scripts/download-patch-from-gerrit.phpsh "$@") + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name phpstan-${SUFFIX} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; functional) COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml --exclude-group not-${DBMS} "$@") case ${DBMS} in diff --git a/composer.json b/composer.json index 55ef6636..f8afda2a 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ "bin-dir": ".Build/bin", "allow-plugins": { "typo3/class-alias-loader": true, - "typo3/cms-composer-installers": true + "typo3/cms-composer-installers": true, + "vaimo/composer-patches": true } }, "require-dev": { @@ -41,7 +42,8 @@ "codeception/module-asserts": "^3.0", "codeception/module-webdriver": "^4.0", "codeception/module-db": "^3.1", - "phpunit/phpunit": "^11.3" + "phpunit/phpunit": "^11.3", + "vaimo/composer-patches": "^6.0.1" }, "replace": { "typo3-ter/container": "self.version" @@ -52,6 +54,7 @@ "web-dir": ".Build/Web", "app-dir": ".Build", "extension-key": "container" - } + }, + "patches-search": "patches/" } } From addccda1dcdccc1d12eb8fea8020b625eb907d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BCrk?= Date: Wed, 8 Apr 2026 11:25:59 +0200 Subject: [PATCH 2/4] [TASK] Allow mitigation of `ext_emconf.php` deprecation With [1] included in TYPO3 v14.2 `ext_emconf.php` has been deprecated and emits a deprecation error in case following criteria's are not included within the root `composer.json` file for classic-mode installations: * "version" must be set * and "extra"."typo3/cms"."Package"."providesPackages" must at least exists. Having a "version" in the "composer.json" file is not best-practice in the composer world in case that the package is published on `packagist.org` or similar composer package registries [2]: ``` Note: Packagist uses VCS repositories, so the statement above is very much true for Packagist as well. Specifying the version yourself will most likely end up creating problems at some point due to human error. ``` This has been discussed and relaxed with [3] introducing `"extra"."typo3/cms"."version"` as mitigation strategy for extension development. To get functional tests green for TYPO3 14.2 the related change is now added as composer patch along with required pre-changes, restricted to `~14.2.0` only. The can be removed as soon as 14.3 (LTS) is released and testing against 14.2 is not required anymore. * Download gerrit changes using `downloadGerritPatch`. * Restrict to `~14.2.0` manually editing the patch files. * Use `@after` to ensure correct order of related changes and avoid apply issues. > [!NOTE] > Required changes to `composer.json` using the relaxed > variant will be done with follup change to have them > separated from introducing these TYPO3 composer patches > allow simply reverting this change at a later point. Used command(s): ```bash Build/Scripts/runTests.sh -s downloadGerritPatch 93530 \ && Build/Scripts/runTests.sh -s downloadGerritPatch 93484 \ && Build/Scripts/runTests.sh -s downloadGerritPatch 93536 ``` [1] https://review.typo3.org/c/Packages/TYPO3.CMS/+/91908 [2] https://getcomposer.org/doc/04-schema.md#version [3] https://review.typo3.org/c/Packages/TYPO3.CMS/+/93536 --- ...rom-package-description-if-extracted.patch | 43 +++ ...rom-package-description-if-extracted.patch | 53 +++ ...-property_-stdclass__version-warning.patch | 61 ++++ ...version-declaration-in-extra.version.patch | 339 ++++++++++++++++++ 4 files changed, 496 insertions(+) create mode 100644 patches/typo3-cms-backend_93484_bugfix-strip-title-from-package-description-if-extracted.patch create mode 100644 patches/typo3-cms-core_93484_bugfix-strip-title-from-package-description-if-extracted.patch create mode 100644 patches/typo3-cms-core_93530_bugfix-avoid-undefined-property_-stdclass__version-warning.patch create mode 100644 patches/typo3-cms-core_93536_task-allow-extension-version-declaration-in-extra.version.patch diff --git a/patches/typo3-cms-backend_93484_bugfix-strip-title-from-package-description-if-extracted.patch b/patches/typo3-cms-backend_93484_bugfix-strip-title-from-package-description-if-extracted.patch new file mode 100644 index 00000000..1d385ba3 --- /dev/null +++ b/patches/typo3-cms-backend_93484_bugfix-strip-title-from-package-description-if-extracted.patch @@ -0,0 +1,43 @@ +@package typo3/cms-backend +@label [BUGFIX] Strip title from package description if extracted +@link https://review.typo3.org/c/Packages/TYPO3.CMS/+/93484 +@version ~14.2.0 + +From 17f8256acba6eb1c5b4b3e2732b63dc891279ad0 Mon Sep 17 00:00:00 2001 +From: Benjamin Kott +Date: Tue, 31 Mar 2026 11:24:01 +0200 +Subject: [PATCH] [BUGFIX] Strip title from package description if extracted + +When a composer description contains " - ", the part before +the separator is used as the package title. However, the +description was not updated and still contained the full +string including the title prefix, leading to duplicate +information when both title and description are displayed. + +The description is now set to only the part after the +separator when a title is extracted. + +Resolves: #109435 +Releases: main +Change-Id: I2a7a57ca18b750fa26fc967fe3cc5f6df07f3728 +Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/93484 +Reviewed-by: Benjamin Franzke +Reviewed-by: Benni Mack +Tested-by: Benjamin Franzke +Tested-by: Benni Mack +Tested-by: core-ci +--- + +diff --git a/Classes/Controller/AboutController.php b/Classes/Controller/AboutController.php +index 7ffdf16..c8ca11c 100644 +--- a/Classes/Controller/AboutController.php ++++ b/Classes/Controller/AboutController.php +@@ -81,7 +81,7 @@ + } + $extensions[] = [ + 'key' => $package->getPackageKey(), +- 'title' => $package->getPackageMetaData()->getDescription(), ++ 'title' => $package->getPackageMetaData()->getTitle(), + 'authors' => $package->getValueFromComposerManifest('authors'), + ]; + } diff --git a/patches/typo3-cms-core_93484_bugfix-strip-title-from-package-description-if-extracted.patch b/patches/typo3-cms-core_93484_bugfix-strip-title-from-package-description-if-extracted.patch new file mode 100644 index 00000000..88d0cb88 --- /dev/null +++ b/patches/typo3-cms-core_93484_bugfix-strip-title-from-package-description-if-extracted.patch @@ -0,0 +1,53 @@ +@package typo3/cms-core +@label [BUGFIX] Strip title from package description if extracted +@link https://review.typo3.org/c/Packages/TYPO3.CMS/+/93484 +@version ~14.2.0 +@after typo3-cms-core_93530_bugfix-avoid-undefined-property_-stdclass__version-warning.patch + +From 17f8256acba6eb1c5b4b3e2732b63dc891279ad0 Mon Sep 17 00:00:00 2001 +From: Benjamin Kott +Date: Tue, 31 Mar 2026 11:24:01 +0200 +Subject: [PATCH] [BUGFIX] Strip title from package description if extracted + +When a composer description contains " - ", the part before +the separator is used as the package title. However, the +description was not updated and still contained the full +string including the title prefix, leading to duplicate +information when both title and description are displayed. + +The description is now set to only the part after the +separator when a title is extracted. + +Resolves: #109435 +Releases: main +Change-Id: I2a7a57ca18b750fa26fc967fe3cc5f6df07f3728 +Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/93484 +Reviewed-by: Benjamin Franzke +Reviewed-by: Benni Mack +Tested-by: Benjamin Franzke +Tested-by: Benni Mack +Tested-by: core-ci +--- + +diff --git a/Classes/Package/Package.php b/Classes/Package/Package.php +index b163b85..383c126 100644 +--- a/Classes/Package/Package.php ++++ b/Classes/Package/Package.php +@@ -143,13 +143,15 @@ + { + $this->packageMetaData = new MetaData($this->getPackageKey()); + $description = (string)$this->getValueFromComposerManifest('description'); +- $this->packageMetaData->setDescription($description); + $descriptionParts = explode(' - ', $description, 2); +- $title = $description; + if (count($descriptionParts) === 2) { + $title = $descriptionParts[0]; ++ $description = $descriptionParts[1]; ++ } else { ++ $title = $description; + } + $this->packageMetaData->setTitle($title); ++ $this->packageMetaData->setDescription($description); + $this->packageMetaData->setPackageType((string)$this->getValueFromComposerManifest('type')); + $isFrameworkPackage = $this->packageMetaData->isFrameworkType(); + $version = (string)($this->getValueFromComposerManifest('version') ?? '1.0.0'); diff --git a/patches/typo3-cms-core_93530_bugfix-avoid-undefined-property_-stdclass__version-warning.patch b/patches/typo3-cms-core_93530_bugfix-avoid-undefined-property_-stdclass__version-warning.patch new file mode 100644 index 00000000..2896bafa --- /dev/null +++ b/patches/typo3-cms-core_93530_bugfix-avoid-undefined-property_-stdclass__version-warning.patch @@ -0,0 +1,61 @@ +@package typo3/cms-core +@label [BUGFIX] Avoid `Undefined property: stdClass::$version` warning +@link https://review.typo3.org/c/Packages/TYPO3.CMS/+/93530 +@version ~14.2.0 + +From 0672a9436796d0b8cc047fe29e611a654dbea6e7 Mon Sep 17 00:00:00 2001 +From: Stefan Bürk +Date: Thu, 02 Apr 2026 16:34:35 +0200 +Subject: [PATCH] [BUGFIX] Avoid `Undefined property: stdClass::$version` warning + +With [1] resolving #108345 #96388 extension need to have +`version` set in the extension `composer.json` for classic +mode installation. + +In case the optional `version` key is not set, which is +recommended to avoid by composer, this leads to a native +php warning: + + Undefined property: stdClass::$version in + vendor/typo3/cms-core/Classes/Package/PackageManager.php:939 + +This has been detected trying to make a extension TYPO3 v14 ready +with v13/v14 dual support and keeping the `ext_emconf.php`, which +emits the warning during functional test execution and should at +least respect that it is optional and possible not available. + +Does not fix the fact that the deprecation is still triggered, +which is the topic of another change to discuss and resolve. + +Let's make at least the warning to vanish. + +[1] https://review.typo3.org/c/Packages/TYPO3.CMS/+/91908 + +Resolves: #109473 +Related: #108345 +Related: #96388 +Releases: main +Change-Id: Iac794747b3afe837a7b89e369233fd9f891aa714 +Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/93530 +Tested-by: Oliver Klee +Tested-by: Garvin Hicking +Reviewed-by: Garvin Hicking +Tested-by: Stefan Bürk +Reviewed-by: Stefan Bürk +Tested-by: core-ci +Reviewed-by: Oliver Klee +--- + +diff --git a/Classes/Package/PackageManager.php b/Classes/Package/PackageManager.php +index 3d9496a..dd9b427 100644 +--- a/Classes/Package/PackageManager.php ++++ b/Classes/Package/PackageManager.php +@@ -936,7 +936,7 @@ + private function isComposerOnlyCapable(\stdClass $manifest): bool + { + return isset($manifest->extra->{'typo3/cms'}->Package->providesPackages) +- && $manifest->version !== null; ++ && ($manifest->version ?? null) !== null; + } + + /** diff --git a/patches/typo3-cms-core_93536_task-allow-extension-version-declaration-in-extra.version.patch b/patches/typo3-cms-core_93536_task-allow-extension-version-declaration-in-extra.version.patch new file mode 100644 index 00000000..a9ac8fda --- /dev/null +++ b/patches/typo3-cms-core_93536_task-allow-extension-version-declaration-in-extra.version.patch @@ -0,0 +1,339 @@ +@package typo3/cms-core +@label [TASK] Allow extension version declaration in extra.version +@link https://review.typo3.org/c/Packages/TYPO3.CMS/+/93536 +@version ~14.2.0 +@after typo3-cms-core_93484_bugfix-strip-title-from-package-description-if-extracted.patch + +From ccce176b8173fbb59d79312a0723b599e8838682 Mon Sep 17 00:00:00 2001 +From: Helmut Hummel +Date: Fri, 03 Apr 2026 12:08:32 +0200 +Subject: [PATCH] [TASK] Allow extension version declaration in extra.version + +Using the top level "version" field for a TYPO3 extension version +for classic mode has the disadvantage, that Composer pulls in +this version also for branches (aka. dev versions). +This would then either mean for extension authors that they need +to update this field constantly for branches and/ or releases, +which would have a bigger impact on behaviour in Composer managed +TYPO3 systems and extension authors than initially intended. + +Therefore it is now possible to declare the version in extra section. + +This field must still match the composer version that is tagged. + +Releases: main +Resolves: #109482 +Change-Id: I96cbdb175dc46ff5b3b1f863663412395fd4469c +Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/93536 +Reviewed-by: Garvin Hicking +Reviewed-by: Georg Ringer +Tested-by: Garvin Hicking +Reviewed-by: Stefan Bürk +Tested-by: Georg Ringer +Tested-by: core-ci +Tested-by: Stefan Bürk +--- + +diff --git a/Classes/Package/Package.php b/Classes/Package/Package.php +index 383c126..85739b7 100644 +--- a/Classes/Package/Package.php ++++ b/Classes/Package/Package.php +@@ -150,16 +150,17 @@ + } else { + $title = $description; + } ++ $manifest = $this->getValueFromComposerManifest(); + $this->packageMetaData->setTitle($title); + $this->packageMetaData->setDescription($description); +- $this->packageMetaData->setPackageType((string)$this->getValueFromComposerManifest('type')); ++ $this->packageMetaData->setPackageType($manifest->type ?? ''); + $isFrameworkPackage = $this->packageMetaData->isFrameworkType(); +- $version = (string)($this->getValueFromComposerManifest('version') ?? '1.0.0'); ++ $version = $manifest->extra->{'typo3/cms'}->{'version'} ?? $manifest->version ?? '1.0.0+no-version-set'; + if ($isFrameworkPackage) { + $version = str_replace('-dev', '', (new Typo3Version())->getVersion()); + } + $this->packageMetaData->setVersion($version); +- $requirements = $this->getValueFromComposerManifest('require'); ++ $requirements = $manifest->require ?? null; + if ($requirements !== null) { + foreach ($requirements as $packageName => $versionConstraints) { + if ($this->ignoreDependencyInPackageConstraint($packageName, $packageManager, $isBuildingPackageArtifact)) { +@@ -174,7 +175,7 @@ + ); + } + } +- $suggestions = $this->getValueFromComposerManifest('suggest'); ++ $suggestions = $manifest->suggest ?? null; + if ($suggestions !== null) { + foreach ($suggestions as $packageName => $description) { + if ($this->ignoreDependencyInPackageConstraint($packageName, $packageManager, $isBuildingPackageArtifact)) { +@@ -184,7 +185,7 @@ + $this->packageMetaData->addConstraint($constraint); + } + } +- $conflicts = $this->getValueFromComposerManifest('conflict'); ++ $conflicts = $manifest->conflict ?? null; + if ($conflicts !== null) { + foreach ($conflicts as $packageName => $versionConstraints) { + if ($this->ignoreDependencyInPackageConstraint($packageName, $packageManager, $isBuildingPackageArtifact)) { +diff --git a/Classes/Package/PackageManager.php b/Classes/Package/PackageManager.php +index dd9b427..cc419ff 100644 +--- a/Classes/Package/PackageManager.php ++++ b/Classes/Package/PackageManager.php +@@ -936,7 +936,10 @@ + private function isComposerOnlyCapable(\stdClass $manifest): bool + { + return isset($manifest->extra->{'typo3/cms'}->Package->providesPackages) +- && ($manifest->version ?? null) !== null; ++ && ( ++ ($manifest->version ?? null) !== null ++ || isset($manifest->extra->{'typo3/cms'}->version) ++ ); + } + + /** +diff --git a/Documentation/Changelog/14.2/Deprecation-108345-Deprecation-of-ext-emconf-php.rst b/Documentation/Changelog/14.2/Deprecation-108345-Deprecation-of-ext-emconf-php.rst +index cea425f..7ca1e9b 100644 +--- a/Documentation/Changelog/14.2/Deprecation-108345-Deprecation-of-ext-emconf-php.rst ++++ b/Documentation/Changelog/14.2/Deprecation-108345-Deprecation-of-ext-emconf-php.rst +@@ -26,7 +26,6 @@ + "name": "vendor/example", + "type": "typo3-cms-extension", + "description": "example", +- "version": "1.0.0", + "license": "GPL-2.0-or-later", + "require": { + "typo3/cms-core": "^14.2", +@@ -36,6 +35,7 @@ + "extra": { + "typo3/cms": { + "extension-key": "example_extension", ++ "version": "1.0.0", + "Package": { + "providesPackages": { + "symfony/dotenv": "" +@@ -52,7 +52,6 @@ + "name": "vendor/example2", + "type": "typo3-cms-extension", + "description": "example", +- "version": "1.0.0", + "license": "GPL-2.0-or-later", + "require": { + "typo3/cms-core": "^14.2" +@@ -60,6 +59,7 @@ + "extra": { + "typo3/cms": { + "extension-key": "example2_extension", ++ "version": "1.0.0", + "Package": { + "providesPackages": {} + } +@@ -68,10 +68,20 @@ + } + + For compatibility with TYPO3 classic mode, third-party extensions +-must set the exact extension version in the top-level `"version"` field +-of `composer.json`. This version should match the version previously ++must set the exact extension version in `extra.typo3/cms.version` ++or in the top level `version` field of `composer.json`. ++This version must match the version previously + defined in `ext_emconf.php` and the released Git tag. + ++Fixture extensions used in tests can set any version number, for example `1.0.0`, ++but a version number must still be provided to avoid deprecation messages. ++ ++During testing, the version number is not evaluated. ++ ++TYPO3 Core extensions may omit the version number ++in `composer.json`, because their version can and will be derived from ++php`\TYPO3\CMS\Core\Information\Typo3Version`. ++ + If an extension depends on regular Composer packages, these packages + must be declared in + `extra.typo3/cms.Package.providesPackages`. +@@ -81,6 +91,13 @@ + to avoid deprecation messages and to declare future compatibility + with TYPO3 classic mode. + ++If strict `composer.json` validation is required and the extension is published ++to Packagist as well, where setting the top level `version` field is not recommended, ++it is recommended to set the version via `extra.typo3/cms.version`. ++ ++If the `version` field is set anyway, it is recommended to omit `extra.typo3/cms.version` ++to avoid redundant data points. ++ + Impact + ====== + +@@ -88,7 +105,8 @@ + + TYPO3 classic installations will trigger a deprecation message + for extensions that still ship `ext_emconf.php` but do not yet define +-both the `"version"` field and `providesPackages` in `composer.json`. ++both `extra.typo3/cms.version` (or `"version"` field ) *and* `providesPackages` ++in `composer.json`. + + Affected installations + ====================== +@@ -109,6 +127,6 @@ + For the time being, `ext_emconf.php` may still need to be kept for + third-party tooling such as TYPO3 TER or Tailor. However, once the + required metadata is correctly defined in `composer.json`, +-TYPO3 will no longer need to evaluate `ext_emconf.php`. ++TYPO3 will no longer evaluate `ext_emconf.php`. + + .. index:: ext:core, NotScanned +diff --git a/Documentation/Changelog/14.2/Feature-108345-No-ext-em-conf-in-classic-mode.rst b/Documentation/Changelog/14.2/Feature-108345-No-ext-em-conf-in-classic-mode.rst +index 9a05b80..ef8c299 100644 +--- a/Documentation/Changelog/14.2/Feature-108345-No-ext-em-conf-in-classic-mode.rst ++++ b/Documentation/Changelog/14.2/Feature-108345-No-ext-em-conf-in-classic-mode.rst +@@ -18,7 +18,7 @@ + + This is now resolved by allowing an extension's `composer.json` + to contain information that previously had to be defined in +-`ext_emconf.php`. ++`ext_emconf.php`: + + 1. Extension title + 2. Extension version +@@ -30,16 +30,15 @@ + See :ref:`feature-108653-1767199420` for how the extension title can also be set + in `composer.json`. + +-The version number can be set in the regular `"version"` field +-in `composer.json`. +- + Extension version + ----------------- + ++The version number can be set in `extra.typo3/cms.version` or alternatively ++in the regular `"version"` field in `composer.json`. ++ + For third-party extensions to be compatible with TYPO3 classic mode, +-this field must now be set to the exact version previously defined in `ext_emconf.php` +-and must match the version in the Git tag +-(for example, when publishing to Packagist). ++this version must now be set to the exact version previously defined in `ext_emconf.php` ++and must match the version in the Git tag (for example, when publishing to Packagist). + + Fixture extensions used in tests can set any version number, for example `1.0.0`, + but a version number must still be provided to avoid deprecation messages. +@@ -57,7 +56,7 @@ + a version range. + + `composer.json` also contains a field for specifying dependencies +-by using a Composer package name with a version range. ++using a Composer package name with a version range. + However, there is no direct way to distinguish whether such a package name + refers to another TYPO3 extension or to a regular Composer package + that should be installed from Packagist. +@@ -113,7 +112,6 @@ + "name": "vendor/example", + "type": "typo3-cms-extension", + "description": "example", +- "version": "1.0.0", + "license": "GPL-2.0-or-later", + "require": { + "typo3/cms-core": "^14.2", +@@ -123,6 +121,7 @@ + "extra": { + "typo3/cms": { + "extension-key": "example_extension", ++ "version": "1.0.0", + "Package": { + "providesPackages": { + "symfony/dotenv": "" +@@ -151,7 +150,6 @@ + "name": "vendor/example", + "type": "typo3-cms-extension", + "description": "example", +- "version": "1.0.0", + "license": "GPL-2.0-or-later", + "require": { + "typo3/cms-core": "^14.2", +@@ -160,6 +158,7 @@ + "extra": { + "typo3/cms": { + "extension-key": "example_extension", ++ "version": "1.0.0", + "Package": { + "providesPackages": {} + } +@@ -167,13 +166,41 @@ + } + } + ++If the `version` field is set anyway, the version is populated from there ++and `extra.typo3/cms.version` can be omitted: ++ ++.. code-block:: json ++ ++ { ++ "name": "vendor/example", ++ "version": "1.0.0", ++ "type": "typo3-cms-extension", ++ "description": "example", ++ "license": "GPL-2.0-or-later", ++ "require": { ++ "typo3/cms-core": "^14.2", ++ "vendor/other-example": "*", ++ "symfony/dotenv": "^8.0" ++ }, ++ "extra": { ++ "typo3/cms": { ++ "extension-key": "example_extension", ++ "Package": { ++ "providesPackages": { ++ "symfony/dotenv": "" ++ } ++ } ++ } ++ } ++ } ++ + Be aware that keeping `ext_emconf.php`, while no longer directly required + by TYPO3, may still be necessary for some tools, + such as Tailor or TYPO3 TER. Therefore, for the time being, it is recommended + to keep the file and ensure that its information stays in sync + with `composer.json` as outlined above. + +-However, TYPO3 will **not** evaluate `ext_emconf.php` any more if the `version` field ++However, TYPO3 will **not** evaluate `ext_emconf.php` anymore if the `version` field + and `providesPackages` are correctly defined in `composer.json`. + + Impact +@@ -181,7 +208,7 @@ + + Extensions can now omit `ext_emconf.php` in TYPO3 classic mode. + A deprecation message is shown during cache warm-up when `ext_emconf.php` +-is present and `composer.json` is not yet future-proof, ++is present and `composer.json` is not yet future-proof + because it does not contain the `version` and `providesPackages` definitions. + + .. index:: ext:core +diff --git a/Tests/Unit/Utility/Fixtures/ext_emconf.php b/Tests/Unit/Utility/Fixtures/ext_emconf.php +deleted file mode 100644 +index 8288584..0000000 +--- a/Tests/Unit/Utility/Fixtures/ext_emconf.php ++++ /dev/null +@@ -1,19 +0,0 @@ +- '', +- 'description' => 'This is a fixture extension configuration file used for unit tests.', +- 'category' => '', +- 'state' => 'stable', +- 'author' => '', +- 'author_email' => '', +- 'author_company' => '', +- 'version' => '14.3.0', +- 'constraints' => [ +- 'depends' => [], +- 'conflicts' => [], +- 'suggests' => [], +- ], +-]; From 76852f59e45abdb190a2116280f878d6675fd952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BCrk?= Date: Wed, 8 Apr 2026 12:51:52 +0200 Subject: [PATCH 3/4] [TASK] Mitigate `ext_emconf.php` deprecation This change sets following missing `composer` values to mitigate the `ext_emconf.php` deprecation message in functional tests (hard failing) and also in real classic mode instances: * Set current development version `3.2.4-dev` as `"extra"."typo3/cms"."version"`. [1] * Add `"extra"."typo3/cms"."Package"."providesPackages"` [1] * `ext_emconf.php` version is set to next patchlevel version. [1] * Publish GitHub action workflow is modified to ensure that tag version is set in `composer.json`, which is a must requirement. [1] * Combine `title` and `description` with ` - ` separator and set it as `composer.json` description. [2] > [!NOTE] > Version to set has been derieved by looking up the > latest tagged/released version and raised on patch > level. > [!IMPORTANT] > This requires adoption for a release process to set the > version as pre-change (1.), doing the release (2.) and > setting next development version (3.) again, which can't > be updated with this change as there is no documentation > for it included in the repository. Used command(s): ```bash composer config "extra"."typo3/cms"."version" "3.2.4-dev" \ && composer config \ "extra"."typo3/cms"."Package" '{}' \ && composer config \ --json "extra"."typo3/cms"."Package" '{"providesPackages": {}}' \ && composer config "description" \ "Container Content Elements - Create Custom Container Content Elements for TYPO3" ``` [1] https://review.typo3.org/c/Packages/TYPO3.CMS/+/93536 [2] https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/14.0/Breaking-108304-PopulateExtensionTitleFromComposerJson.html --- .github/workflows/ci.yml | 6 +++--- .github/workflows/publish.yml | 8 +++++++- .../config.yaml | 14 ++++++++++++++ composer.json | 11 ++++++----- ext_emconf.php | 2 +- 5 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 Build/sites/autogenerated-2-c81e728d9d4c2f636f067f89cc14862c/config.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4895e54..b39965ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: TYPO3: [ '13', '14', '14-dev' ] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install testing system run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -t ${{ matrix.TYPO3 }} -s composerInstall @@ -52,14 +52,14 @@ jobs: - name: Acceptance Tests run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -t ${{ matrix.TYPO3 }} -s acceptance -- --fail-fast - name: Archive acceptance tests results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: always() with: name: acceptance-test-reports-${{ matrix.php }}-${{ matrix.TYPO3 }} path: .Build/Web/typo3temp/var/tests/_output - name: Archive composer.lock - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: always() with: name: composer.lock-${{ matrix.php }}-${{ matrix.TYPO3 }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e0afab8f..dd1ef96c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Check tag run: | @@ -39,6 +39,12 @@ jobs: with: php-version: 8.3 extensions: intl, mbstring, json, zip, curl + tools: composer:v2 + + - name: Set composer.json version and extra.typo3/cms.version + run: | + composer config "extra"."typo3/cms"."version" "${{ steps.get-version.outputs.version }}" \ + && composer validate --strict --no-check-lock --no-check-version - name: Install tailor run: composer global require typo3/tailor --prefer-dist --no-progress --no-suggest diff --git a/Build/sites/autogenerated-2-c81e728d9d4c2f636f067f89cc14862c/config.yaml b/Build/sites/autogenerated-2-c81e728d9d4c2f636f067f89cc14862c/config.yaml new file mode 100644 index 00000000..74296369 --- /dev/null +++ b/Build/sites/autogenerated-2-c81e728d9d4c2f636f067f89cc14862c/config.yaml @@ -0,0 +1,14 @@ +base: autogenerated-2 +dependencies: { } +errorHandling: { } +languages: + - + title: English + enabled: true + languageId: 0 + base: / + locale: en_US.UTF-8 + navigationTitle: English + flag: us +rootPageId: 2 +routes: { } diff --git a/composer.json b/composer.json index f8afda2a..dbb8837d 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "b13/container", - "description": "Create Custom Container Content Elements for TYPO3", + "description": "Container Content Elements - Create Custom Container Content Elements for TYPO3", "type": "typo3-cms-extension", "homepage": "https://b13.com", "license": ["GPL-2.0-or-later"], @@ -45,15 +45,16 @@ "phpunit/phpunit": "^11.3", "vaimo/composer-patches": "^6.0.1" }, - "replace": { - "typo3-ter/container": "self.version" - }, "extra": { "typo3/cms": { "cms-package-dir": "{$vendor-dir}/typo3/cms", "web-dir": ".Build/Web", "app-dir": ".Build", - "extension-key": "container" + "extension-key": "container", + "version": "3.2.4-dev", + "Package": { + "providesPackages": [] + } }, "patches-search": "patches/" } diff --git a/ext_emconf.php b/ext_emconf.php index 5ca7385f..8890f07f 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -11,7 +11,7 @@ 'uploadfolder' => false, 'createDirs' => '', 'clearCacheOnLoad' => true, - 'version' => '3.2.2', + 'version' => '3.2.4', 'constraints' => [ 'depends' => ['typo3' => '13.4.26-14.99.99'], 'conflicts' => [], From d796feae1fda72c80bbfe6949f2c3ee44a0bccff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BCrk?= Date: Wed, 8 Apr 2026 13:22:38 +0200 Subject: [PATCH 4/4] [TASK] Update TYPO3 version constraints This change updates the used TYPO3 v14 constraints to support and test against: * TYPO3 v13.4 -> latest released patchlevel version for minor version (feature freeze) * TYPO3 v14.2 -> latest released patchlevel version for minor version (feature freeze) * TYPO3 v14.3 -> current development (main branch) and automatically switching to LTS patchlevel version once 14.3.0 LTS release has been released. Handling in `Build/Scripts/runTests.sh` is aligned to test 14.2 and 14.3-dev and drop testing against and support for 14.1. Used command(s): ```bash composer config sort-packages true \ && composer require --dev --no-update \ "typo3/cms-install":"^13.4 || ^14.2 || 14.3.*@dev" \ "typo3/cms-fluid-styled-content":"^13.4 || ^14.2 || 14.3.*@dev" \ "typo3/cms-info":"^13.4 || ^14.2 || 14.3.*@dev" \ "typo3/cms-workspaces":"^13.4 || ^14.2 || 14.3.*@dev" \ "typo3/testing-framework":"^9.5" ``` --- .github/workflows/ci.yml | 1 + Build/Scripts/runTests.sh | 12 ++++++------ composer.json | 21 +++++++++++---------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b39965ab..da4565d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ jobs: name: all tests runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: php: [ '8.2', '8.3', '8.4', '8.5'] TYPO3: [ '13', '14', '14-dev' ] diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh index 27e895f7..6fb77c08 100755 --- a/Build/Scripts/runTests.sh +++ b/Build/Scripts/runTests.sh @@ -284,7 +284,7 @@ CORE_ROOT="${PWD}" # Option defaults TEST_SUITE="help" -COMPOSER_ROOT_VERSION="2.3.7-dev" +COMPOSER_ROOT_VERSION="3.2.4-dev" DBMS="mariadb" DBMS_VERSION="" PHP_VERSION="8.2" @@ -598,9 +598,9 @@ case ${TEST_SUITE} in php -v | grep '^PHP'; if [ "${TYPO3}" == "14-dev" ]; then - composer require typo3/cms-core:14.2.x-dev --dev -W --no-progress --no-interaction + composer require typo3/cms-core:14.3.x-dev --dev -W --no-progress --no-interaction elif [ ${TYPO3} -eq 14 ]; then - composer require typo3/cms-core:^14.1 --dev -W --no-progress --no-interaction + composer require typo3/cms-core:^14.2 --dev -W --no-progress --no-interaction else composer require typo3/cms-core:^13.4 ichhabrecht/content-defender --dev -W --no-progress --no-interaction fi @@ -617,11 +617,11 @@ case ${TEST_SUITE} in ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-validate-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c " php -v | grep '^PHP'; if [ "${TYPO3}" == "14-dev" ]; then - composer require typo3/cms-core:14.2.x-dev --dev -W --no-progress --no-interaction + composer require 'typo3/cms-core':'14.3.*@dev' --dev -W --no-progress --no-interaction elif [ ${TYPO3} -eq 14 ]; then - composer require typo3/cms-core:^14.1 --dev -W --no-progress --no-interaction + composer require 'typo3/cms-core':'^14.1' --dev -W --no-progress --no-interaction else - composer require typo3/cms-core:^13.4 ichhabrecht/content-defender --dev -W --no-progress --no-interaction + composer require 'typo3/cms-core':'^13.4' 'ichhabrecht/content-defender' --dev -W --no-progress --no-interaction fi composer validate " diff --git a/composer.json b/composer.json index dbb8837d..cd3db31d 100644 --- a/composer.json +++ b/composer.json @@ -26,23 +26,24 @@ "typo3/class-alias-loader": true, "typo3/cms-composer-installers": true, "vaimo/composer-patches": true - } + }, + "sort-packages": true }, "require-dev": { "b13/container-example": "dev-task/v4", - "typo3/cms-install": "^13.4 || ^14.1 || 14.2.x-dev", - "typo3/cms-fluid-styled-content": "^13.4 || ^14.1 || 14.2.x-dev", - "typo3/cms-info": "^13.4 || ^14.1 || 14.2.x-dev", - "typo3/cms-workspaces": "^13.4 || ^14.1 || 14.2.x-dev", - "typo3/testing-framework": "^9.1", - "phpstan/phpstan": "^1.10", - "typo3/coding-standards": "^0.5.5", - "friendsofphp/php-cs-fixer": "^3.51", "codeception/codeception": "^5.1", "codeception/module-asserts": "^3.0", - "codeception/module-webdriver": "^4.0", "codeception/module-db": "^3.1", + "codeception/module-webdriver": "^4.0", + "friendsofphp/php-cs-fixer": "^3.51", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^11.3", + "typo3/cms-fluid-styled-content": "^13.4 || ^14.2 || 14.3.*@dev", + "typo3/cms-info": "^13.4 || ^14.2 || 14.3.*@dev", + "typo3/cms-install": "^13.4 || ^14.2 || 14.3.*@dev", + "typo3/cms-workspaces": "^13.4 || ^14.2 || 14.3.*@dev", + "typo3/coding-standards": "^0.5.5", + "typo3/testing-framework": "^9.5", "vaimo/composer-patches": "^6.0.1" }, "extra": {