From 43f9a2e7b95d8a96ce53c86625d2ea8139b784fe Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 20 Apr 2026 14:28:15 +0200 Subject: [PATCH 01/11] feat: update version constraints for TYPO3 v13/v14 support Drop TYPO3 v12 support. Target v13.4 LTS and v14.x. Bump extension version to 6.0.0. --- composer.json | 6 +++--- ext_emconf.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 4c80f0f..2fbd6d2 100644 --- a/composer.json +++ b/composer.json @@ -30,9 +30,9 @@ } ], "require": { - "typo3/cms-core": "^12.4 || ^13.4", - "typo3/cms-extbase": "^12.4 || ^13.4", - "typo3/cms-form": "^12.4 || ^13.4" + "typo3/cms-core": "^13.4 || ^14.0", + "typo3/cms-extbase": "^13.4 || ^14.0", + "typo3/cms-form": "^13.4 || ^14.0" }, "autoload": { "psr-4": { diff --git a/ext_emconf.php b/ext_emconf.php index 252f5c5..39fecf6 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -7,10 +7,10 @@ 'state' => 'stable', 'author' => 'Ralf Zimmermann, Elias Häußler, Christian Seyfferth', 'author_email' => 'r.zimmermann@dreistrom.land, elias@haeussler.dev, c.seyfferth@dreistrom.land', - 'version' => '5.0.0', + 'version' => '6.0.0', 'constraints' => [ 'depends' => [ - 'typo3' => '12.4.0-13.4.99', + 'typo3' => '13.4.0-14.99.99', ], 'conflicts' => [], 'suggests' => [], From f628f41317cfd020b174ea7fc5196cb0688b395a Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 20 Apr 2026 14:29:15 +0200 Subject: [PATCH 02/11] refactor: remove unnecessary call_user_func wrapper in ext_localconf --- ext_localconf.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ext_localconf.php b/ext_localconf.php index 1198e1e..2bc0d8c 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -4,9 +4,7 @@ defined('TYPO3') or die(); -call_user_func(function () { - Extension::addTypoScriptSetup(); - Extension::registerHooks(); +Extension::addTypoScriptSetup(); +Extension::registerHooks(); - $GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['repeatableFormElements.copyVariants'] ??= true; -}); +$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['repeatableFormElements.copyVariants'] ??= true; From da0b074049c906253919d7b954286cb3551cfc0f Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 20 Apr 2026 14:34:44 +0200 Subject: [PATCH 03/11] feat: replace afterBuildingFinished hook dispatch with PSR-14 event TYPO3 v14 removes the SC_OPTIONS afterBuildingFinished hook (Breaking #98239). Introduce AfterBuildingFinishedEvent as PSR-14 replacement. Legacy hook dispatch is kept for v13 backward compatibility. --- Classes/Event/AfterBuildingFinishedEvent.php | 21 +++++++++++ Classes/Hooks/FormHooks.php | 7 ++++ Classes/Service/CopyService.php | 39 ++++++++++++-------- 3 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 Classes/Event/AfterBuildingFinishedEvent.php diff --git a/Classes/Event/AfterBuildingFinishedEvent.php b/Classes/Event/AfterBuildingFinishedEvent.php new file mode 100644 index 0000000..62db7fe --- /dev/null +++ b/Classes/Event/AfterBuildingFinishedEvent.php @@ -0,0 +1,21 @@ +addValidator($validator); } + // Legacy hook (v13 compat, no-op in v14) foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterBuildingFinished'] ?? [] as $className) { $hookObj = GeneralUtility::makeInstance($className); if (method_exists($hookObj, 'afterBuildingFinished')) { $hookObj->afterBuildingFinished($renderable); } } + // PSR-14 event (v13 + v14) + GeneralUtility::makeInstance(EventDispatcherInterface::class)->dispatch( + new AfterBuildingFinishedEvent($renderable) + ); } if ($renderable instanceof CompositeRenderableInterface) { diff --git a/Classes/Service/CopyService.php b/Classes/Service/CopyService.php index af9c325..f592337 100644 --- a/Classes/Service/CopyService.php +++ b/Classes/Service/CopyService.php @@ -12,6 +12,7 @@ */ use Psr\EventDispatcher\EventDispatcherInterface; +use TRITUM\RepeatableFormElements\Event\AfterBuildingFinishedEvent; use TRITUM\RepeatableFormElements\Event\CopyVariantEvent; use TRITUM\RepeatableFormElements\FormElements\RepeatableContainerInterface; use TYPO3\CMS\Core\Configuration\Features; @@ -23,6 +24,7 @@ use TYPO3\CMS\Form\Domain\Model\FormDefinition; use TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface; use TYPO3\CMS\Form\Domain\Model\Renderable\CompositeRenderableInterface; +use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface; use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableVariant; use TYPO3\CMS\Form\Domain\Runtime\FormRuntime; use TYPO3\CMS\Form\Domain\Runtime\FormState; @@ -36,7 +38,8 @@ class CopyService protected FormDefinition $formDefinition; protected array $repeatableContainersByOriginalIdentifier = []; protected array $typeDefinitions = []; - protected mixed $features; + protected Features $features; + protected EventDispatcherInterface $eventDispatcher; /** * @param FormRuntime $formRuntime @@ -48,6 +51,7 @@ public function __construct(FormRuntime $formRuntime) $this->formDefinition = $formRuntime->getFormDefinition(); $this->typeDefinitions = $this->formDefinition->getTypeDefinitions(); $this->features = GeneralUtility::makeInstance(Features::class); + $this->eventDispatcher = GeneralUtility::makeInstance(EventDispatcherInterface::class); } /** @@ -185,12 +189,7 @@ protected function copyRepeatableContainer( $parentRenderableForNewContainer->addElement($newContainer); $parentRenderableForNewContainer->moveElementAfter($newContainer, $moveAfterContainer); - foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterBuildingFinished'] ?? [] as $className) { - $hookObj = GeneralUtility::makeInstance($className); - if (method_exists($hookObj, 'afterBuildingFinished')) { - $hookObj->afterBuildingFinished($newContainer); - } - } + $this->dispatchAfterBuildingFinished($newContainer); foreach ($copyFromContainer->getElements() as $originalFormElement) { $this->createNestedElements($originalFormElement, $newContainer, $copyFromContainer->getIdentifier(), $newIdentifier); @@ -252,12 +251,7 @@ protected function createNestedElements( $this->copyProcessingRule($originalFormElement->getIdentifier(), $newIdentifier); $this->copyVariants($originalFormElement, $newFormElement, $newIdentifier); - foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterBuildingFinished'] ?? [] as $className) { - $hookObj = GeneralUtility::makeInstance($className); - if (method_exists($hookObj, 'afterBuildingFinished')) { - $hookObj->afterBuildingFinished($newFormElement); - } - } + $this->dispatchAfterBuildingFinished($newFormElement); if ($originalFormElement instanceof CompositeRenderableInterface) { foreach ($originalFormElement->getElements() as $originalChildFormElement) { @@ -390,9 +384,8 @@ protected function copyVariants( $options['condition'] = $propCondition->getValue($originalVariant); $options['identifier'] = $originalIdentifier; - $eventDispatcher = GeneralUtility::makeInstance(EventDispatcherInterface::class); /** @var CopyVariantEvent $event */ - $event = $eventDispatcher->dispatch( + $event = $this->eventDispatcher->dispatch( new CopyVariantEvent($options, $originalFormElement, $newFormElement, $newIdentifier), ); @@ -407,4 +400,20 @@ protected function copyVariants( } } + /** + * Dispatch the afterBuildingFinished event/hook for a renderable. + * In v13: dispatches both the legacy SC_OPTIONS hook and the PSR-14 event. + * In v14+: the SC_OPTIONS hook no longer exists, only the PSR-14 event fires. + */ + protected function dispatchAfterBuildingFinished(RenderableInterface $renderable): void + { + foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterBuildingFinished'] ?? [] as $className) { + $hookObj = GeneralUtility::makeInstance($className); + if (method_exists($hookObj, 'afterBuildingFinished')) { + $hookObj->afterBuildingFinished($renderable); + } + } + + $this->eventDispatcher->dispatch(new AfterBuildingFinishedEvent($renderable)); + } } From 3acd0aba6350372a2582970903c112cdd43a5af9 Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 20 Apr 2026 14:35:02 +0200 Subject: [PATCH 04/11] docs: annotate form hooks with v14 migration notes --- Classes/Configuration/Extension.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Classes/Configuration/Extension.php b/Classes/Configuration/Extension.php index abc3edb..9a89806 100644 --- a/Classes/Configuration/Extension.php +++ b/Classes/Configuration/Extension.php @@ -56,6 +56,9 @@ public static function addTypoScriptSetup(): void public static function registerHooks(): void { + // These hooks are still supported in TYPO3 v13 and v14. + // When TYPO3 introduces PSR-14 replacements, migrate to event listeners + // registered in Configuration/Services.yaml. $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterInitializeCurrentPage'][1511196413] = FormHooks::class; $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeRendering'][1511196413] = FormHooks::class; } From bb8c38b3564182acf84cfca19c3482e2a30d34dd Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 20 Apr 2026 14:35:28 +0200 Subject: [PATCH 05/11] refactor: improve type safety and add final keyword - Make FormHooks final - Type CopyService::features property as Features - Use explicit nullable types for PHP 8.4 compat --- Classes/Hooks/FormHooks.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Classes/Hooks/FormHooks.php b/Classes/Hooks/FormHooks.php index edcc802..a74a8fb 100644 --- a/Classes/Hooks/FormHooks.php +++ b/Classes/Hooks/FormHooks.php @@ -24,7 +24,7 @@ use TYPO3\CMS\Form\Domain\Model\Renderable\RootRenderableInterface; use TYPO3\CMS\Form\Domain\Runtime\FormRuntime; -class FormHooks +final class FormHooks { /** * @param FormRuntime $formRuntime @@ -37,8 +37,8 @@ class FormHooks */ public function afterInitializeCurrentPage( FormRuntime $formRuntime, - CompositeRenderableInterface $currentPage = null, - CompositeRenderableInterface $lastPage = null, + ?CompositeRenderableInterface $currentPage = null, + ?CompositeRenderableInterface $lastPage = null, array $rawRequestArguments = [], ): ?CompositeRenderableInterface { foreach ($formRuntime->getPages() as $page) { @@ -158,8 +158,8 @@ protected function setRootRepeatableContainerIdentifiers( */ protected function userWentBackToPreviousStep( FormRuntime $formRuntime, - CompositeRenderableInterface $currentPage = null, - CompositeRenderableInterface $lastPage = null, + ?CompositeRenderableInterface $currentPage = null, + ?CompositeRenderableInterface $lastPage = null, ): bool { return $currentPage !== null && $lastPage !== null From dc85bb6d050bf1a2d6db7bf7e161a66ea9296d1c Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 20 Apr 2026 14:35:46 +0200 Subject: [PATCH 06/11] refactor: update Services.yaml with proper exclusions --- Configuration/Services.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 5abf579..6474f00 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -6,6 +6,9 @@ services: TRITUM\RepeatableFormElements\: resource: '../Classes/*' + exclude: + - '../Classes/Domain/Model/*' + - '../Classes/Event/*' TRITUM\RepeatableFormElements\EventListener\AdaptVariantConditionEventListener: tags: From 13d38a904448eda2caba16e3b7612c013ca5abac Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 20 Apr 2026 16:14:16 +0200 Subject: [PATCH 07/11] fix: remove deprecated formEditorPartials for v14 compatibility TYPO3 v14.2 deprecated Fluid-based stage partials in favor of web components (Deprecation #109306). Removing formEditorPartials lets both v13 and v14 use their default composite element rendering, fixing the visual nesting of child elements in the backend form editor. --- Configuration/Yaml/FormSetupBackend.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Configuration/Yaml/FormSetupBackend.yaml b/Configuration/Yaml/FormSetupBackend.yaml index a227030..c49aa80 100644 --- a/Configuration/Yaml/FormSetupBackend.yaml +++ b/Configuration/Yaml/FormSetupBackend.yaml @@ -58,5 +58,5 @@ prototypes: dynamicJavaScriptModules: additionalViewModelModules: 1595333290: '@tritum/repeatable-form-elements/backend/form-editor/view-model.js' - formEditorPartials: - FormElement-RepeatableContainer: 'Stage/Fieldset' + # formEditorPartials removed: v14 uses web components for stage rendering (Deprecation #109306) + # Omitting formEditorPartials lets both v13 and v14 use their default composite element rendering. From f89551bf018edbab553ff509114b66614203a33a Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 20 Apr 2026 16:20:19 +0200 Subject: [PATCH 08/11] feat: add PSR-14 event listeners for v14 form hook replacements TYPO3 v14 removed the afterInitializeCurrentPage and beforeRendering SC_OPTIONS hooks. Add PSR-14 event listeners for: - AfterCurrentPageIsResolvedEvent (replaces afterInitializeCurrentPage) - BeforeRenderableIsRenderedEvent (replaces beforeRendering) The listeners delegate to existing FormHooks methods, keeping the v13 hook registration intact for backward compatibility. --- .../AfterCurrentPageIsResolvedListener.php | 29 +++++++++++++++++++ .../BeforeRenderableIsRenderedListener.php | 24 +++++++++++++++ Configuration/Services.yaml | 10 +++++++ 3 files changed, 63 insertions(+) create mode 100644 Classes/EventListener/AfterCurrentPageIsResolvedListener.php create mode 100644 Classes/EventListener/BeforeRenderableIsRenderedListener.php diff --git a/Classes/EventListener/AfterCurrentPageIsResolvedListener.php b/Classes/EventListener/AfterCurrentPageIsResolvedListener.php new file mode 100644 index 0000000..90ce413 --- /dev/null +++ b/Classes/EventListener/AfterCurrentPageIsResolvedListener.php @@ -0,0 +1,29 @@ +currentPage = $this->formHooks->afterInitializeCurrentPage( + $event->formRuntime, + $event->currentPage, + $event->lastDisplayedPage, + $event->request->getArguments(), + ); + } +} diff --git a/Classes/EventListener/BeforeRenderableIsRenderedListener.php b/Classes/EventListener/BeforeRenderableIsRenderedListener.php new file mode 100644 index 0000000..dc301b5 --- /dev/null +++ b/Classes/EventListener/BeforeRenderableIsRenderedListener.php @@ -0,0 +1,24 @@ +formHooks->beforeRendering($event->formRuntime, $event->renderable); + } +} diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 6474f00..113ff79 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -14,3 +14,13 @@ services: tags: - name: event.listener identifier: 'repeatableFormElements/copyVariants/adaptCondition' + + TRITUM\RepeatableFormElements\EventListener\AfterCurrentPageIsResolvedListener: + tags: + - name: event.listener + identifier: 'repeatableFormElements/afterCurrentPageIsResolved' + + TRITUM\RepeatableFormElements\EventListener\BeforeRenderableIsRenderedListener: + tags: + - name: event.listener + identifier: 'repeatableFormElements/beforeRenderableIsRendered' From 7da685303d11921c5aee924add5e8bf5d4f59541 Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 20 Apr 2026 16:27:15 +0200 Subject: [PATCH 09/11] chore: add DDEV multi-version test environment for v13/v14 --- .ddev/.setup/scripts/utils.sh | 457 ++++++++++++++++++ .ddev/.setup/templates/index.php | 102 ++++ .../manifest.yaml | 26 + .ddev/apache/10.conf | 43 ++ .ddev/apache/20.conf | 51 ++ .ddev/commands/web/.install-11 | 19 + .ddev/commands/web/.install-12 | 18 + .ddev/commands/web/.install-13 | 44 ++ .ddev/commands/web/.install-14 | 51 ++ .ddev/commands/web/11 | 24 + .ddev/commands/web/12 | 23 + .ddev/commands/web/13 | 23 + .ddev/commands/web/14 | 23 + .ddev/commands/web/all | 28 ++ .ddev/commands/web/install | 27 ++ .ddev/config.typo3-setup.yaml | 11 + .ddev/config.yaml | 293 +++++++++++ .ddev/docker-compose.typo3-setup.yaml | 33 ++ .gitignore | 1 + Tests/Acceptance/Fixtures/data.sql | 19 + .../repeatable-nested.form.yaml | 82 ++++ .../repeatable-simple.form.yaml | 58 +++ .../sitepackage/Configuration/Services.yaml | 6 + .../Sets/Sitepackage/config.yaml | 6 + .../Sets/Sitepackage/setup.typoscript | 12 + .../Fixtures/packages/sitepackage/README.md | 6 + .../Resources/Private/Layouts/Default.html | 6 + .../Private/Templates/Page/Default.html | 6 + .../packages/sitepackage/composer.json | 23 + 29 files changed, 1521 insertions(+) create mode 100644 .ddev/.setup/scripts/utils.sh create mode 100644 .ddev/.setup/templates/index.php create mode 100644 .ddev/addon-metadata/typo3-multi-version-extension/manifest.yaml create mode 100644 .ddev/apache/10.conf create mode 100644 .ddev/apache/20.conf create mode 100755 .ddev/commands/web/.install-11 create mode 100755 .ddev/commands/web/.install-12 create mode 100755 .ddev/commands/web/.install-13 create mode 100755 .ddev/commands/web/.install-14 create mode 100755 .ddev/commands/web/11 create mode 100755 .ddev/commands/web/12 create mode 100755 .ddev/commands/web/13 create mode 100755 .ddev/commands/web/14 create mode 100755 .ddev/commands/web/all create mode 100755 .ddev/commands/web/install create mode 100644 .ddev/config.typo3-setup.yaml create mode 100644 .ddev/config.yaml create mode 100644 .ddev/docker-compose.typo3-setup.yaml create mode 100644 Tests/Acceptance/Fixtures/data.sql create mode 100644 Tests/Acceptance/Fixtures/form_definitions/repeatable-nested.form.yaml create mode 100644 Tests/Acceptance/Fixtures/form_definitions/repeatable-simple.form.yaml create mode 100644 Tests/Acceptance/Fixtures/packages/sitepackage/Configuration/Services.yaml create mode 100644 Tests/Acceptance/Fixtures/packages/sitepackage/Configuration/Sets/Sitepackage/config.yaml create mode 100644 Tests/Acceptance/Fixtures/packages/sitepackage/Configuration/Sets/Sitepackage/setup.typoscript create mode 100644 Tests/Acceptance/Fixtures/packages/sitepackage/README.md create mode 100644 Tests/Acceptance/Fixtures/packages/sitepackage/Resources/Private/Layouts/Default.html create mode 100644 Tests/Acceptance/Fixtures/packages/sitepackage/Resources/Private/Templates/Page/Default.html create mode 100644 Tests/Acceptance/Fixtures/packages/sitepackage/composer.json diff --git a/.ddev/.setup/scripts/utils.sh b/.ddev/.setup/scripts/utils.sh new file mode 100644 index 0000000..6fa7564 --- /dev/null +++ b/.ddev/.setup/scripts/utils.sh @@ -0,0 +1,457 @@ +#!/bin/bash + +# Global variable to store spinner PID +SPINNER_PID=0 + +# Function to display a spinner at the current cursor position (simple colored style) +function _spinner() { + local chars="|/-\\" + local delay=0.1 + local i=0 + + while true; do + printf "\b\e[36m%c\e[0m" "${chars:$i:1}" + ((i++)) + if [ $i -eq ${#chars} ]; then + i=0 + fi + sleep $delay + done +} + +function _progress() { + printf "%s... " "$1" + # Check if we're in CI environment or non-interactive terminal + if [[ "$VERBOSE" -eq 0 ]] && [[ -z "$CI" ]] && [[ -z "$GITHUB_ACTIONS" ]] && [[ -z "$GITLAB_CI" ]] && [[ -z "$JENKINS_URL" ]] && [[ -t 1 ]]; then + # Print initial space for spinner + printf " " + # Start spinner in background at current position + _spinner & + SPINNER_PID=$! + # Save current stdout/stderr + exec 3>&1 4>&2 + exec >/dev/null 2>&1 + else + printf "\n" + fi +} + +function _done() { + # Stop spinner if it was started + if [ $SPINNER_PID -ne 0 ]; then + kill $SPINNER_PID 2>/dev/null + wait $SPINNER_PID 2>/dev/null + SPINNER_PID=0 + # Restore stdout/stderr + exec 1>&3 2>&4 + # Clear any remaining color codes and replace with checkmark + printf "\b\e[0m\e[32m✔\e[39m\n" + else + # No spinner was running, just print checkmark + printf "\e[32m✔\e[39m\n" + fi +} + + +# Function to get the lowest supported TYPO3 version from the environment variable TYPO3_VERSIONS +# It reads the TYPO3_VERSIONS environment variable, splits it into an array, and sorts the versions. +# If no versions are found, it prints an error message and exits with status 1. +# Otherwise, it prints the lowest version. +function get_lowest_supported_typo3_versions() { + local TYPO3_VERSIONS_ARRAY=() + IFS=' ' read -r -a TYPO3_VERSIONS_ARRAY <<< "$TYPO3_VERSIONS" + if [ ${#TYPO3_VERSIONS_ARRAY[@]} -eq 0 ]; then + message red "Error! No supported TYPO3 versions found in environment variables." + exit 1 + fi + printf "%s\n" "${TYPO3_VERSIONS_ARRAY[@]}" | sort -V | head -n 1 +} + +# Function to get the supported TYPO3 versions from the environment variable TYPO3_VERSIONS. +# It checks if the TYPO3_VERSIONS environment variable is set and not empty. +# If the variable is unset or empty, it prints an error message and returns 1. +# Otherwise, it splits the TYPO3_VERSIONS variable into an array and prints the supported versions. +function get_supported_typo3_versions() { + if [ -z "${TYPO3_VERSIONS+x}" ]; then + message red "TYPO3_VERSIONS is unset. Please set it before running this function." + return 1 + else + local TYPO3_VERSIONS_ARRAY=() + IFS=' ' read -r -a TYPO3_VERSIONS_ARRAY <<< "$TYPO3_VERSIONS" + if [ ${#TYPO3_VERSIONS_ARRAY[@]} -eq 0 ]; then + message red "Error! No supported TYPO3 versions found in environment variables." + return 1 + fi + printf "%s\n" "${TYPO3_VERSIONS_ARRAY[@]}" + fi +} + +# Function to check if a given TYPO3 version is supported. +# It takes one argument, the TYPO3 version to check. +# The function reads the supported TYPO3 versions from the environment variable TYPO3_VERSIONS. +# If the provided version is not in the list of supported versions, it prints an error message and exits with status 1. +# If the provided version is supported, it returns 0. +# +# Arguments: +# $1 - The TYPO3 version to check. +# +# Returns: +# 0 if the provided TYPO3 version is supported. +# 1 if the provided TYPO3 version is not supported or if no version is provided. +function check_typo3_version() { + local TYPO3=$1 + local SUPPORTED_TYPO3_VERSIONS=() + local found=0 + + if [ -z "$TYPO3" ]; then + message red "No TYPO3 version provided. Please set one of the supported TYPO3 versions as argument: $(get_supported_typo3_versions_comma_separated)" + exit 1 + fi + + while IFS= read -r line; do + SUPPORTED_TYPO3_VERSIONS+=("$line") + done < <(get_supported_typo3_versions) + + for version in "${SUPPORTED_TYPO3_VERSIONS[@]}"; do + if [[ "$version" == "$TYPO3" ]]; then + found=1 + break + fi + done + + if [[ $found -eq 0 ]]; then + message red "TYPO3 version '$TYPO3' is not supported." + exit 1 + fi + + return 0 +} + +# Function to perform pre-setup tasks for TYPO3 installation. +# It exports the provided TYPO3 version to the VERSION environment variable, +# displays an introductory message for the TYPO3 version, and starts the installation process. +# +# Arguments: +# $1 - The TYPO3 version to set up. +# +# Usage: +# pre_setup +function pre_setup() { + export VERSION=$1 + message magenta "Install TYPO3 $VERSION" + _progress " ├─ Prepare environment" + export BASE_PATH="/var/www/html/.Build/$VERSION" + intro_typo3 + message blue "Pre Setup for TYPO3 $VERSION" + _done + install_start + install_composer_packages +} + +# Function to perform post-setup tasks for TYPO3 installation. +# It changes the current directory to the base path, sets the TYPO3 installation database name, +# and calls the appropriate post-setup function based on the TYPO3 version. +# After that, it imports data and updates TYPO3. +function post_setup() { + prepare_acceptance_testing + + cd $BASE_PATH + TYPO3_INSTALL_DB_DBNAME=$DATABASE + + _progress " ├─ Setup TYPO3" + if [ "$VERSION" == "11" ]; then + post_setup_11 + elif [ "$VERSION" == "12" ]; then + post_setup_12 + elif [ "$VERSION" == "13" ]; then + post_setup_13 + elif [ "$VERSION" == "14" ]; then + post_setup_14 + fi + _done + + _progress " ├─ Import data" + import_xml_data + import_sql_data + _done + _progress " ├─ Update TYPO3" + update_typo3 + _done + printf " └─ \033[33mTYPO3 $VERSION setup completed!\033[0m Open in your browser: https://$VERSION.${EXTENSION_NAME}.ddev.site\n" +} + +# Function to display an introductory message for the TYPO3 version. +# It prints a formatted message with the TYPO3 version in magenta color. +function intro_typo3() { + message magenta "-------------------------------------------------" + message magenta "|\t\t\t\t\t\t|" + message magenta "| \t\t TYPO3 $VERSION \t\t|" + message magenta "|\t\t\t\t\t\t|" + message magenta "-------------------------------------------------" +} + +# Function to start the installation process for TYPO3. +# It removes any existing files in the test directory for the specified version, +# sets up the environment, creates symlinks for the main and additional extensions, +# and sets up Composer for the TYPO3 installation. +function install_start() { + rm -rf /var/www/html/.Build/$VERSION/* + _progress " ├─ Setup environment" + setup_environment + _done + _progress " ├─ Create symlinks" + create_symlinks_main_extension + create_symlinks_additional_extensions + _done + _progress " ├─ Setup composer" + setup_composer + _done +} + +# Function to set up the environment for TYPO3 installation. +# It sets the base path for the TYPO3 installation, removes any existing files in the base path, +# creates necessary directories, sets permissions, and exports environment variables. +# Additionally, it drops the existing database for the TYPO3 version. +function setup_environment() { + rm -rf "$BASE_PATH" + mkdir -p "$BASE_PATH/packages/$EXTENSION_KEY" + chmod 775 -R $BASE_PATH + export DATABASE="database_$VERSION" + if [ "$VERSION" == "11" ]; then + export TYPO3_BIN="$BASE_PATH/vendor/bin/typo3cms" + else + export TYPO3_BIN="$BASE_PATH/vendor/bin/typo3" + fi + mysql -uroot -proot -e "DROP DATABASE IF EXISTS $DATABASE" +} + +# Function to create symlinks for the main extension. +# It iterates over the items in the current directory, excluding certain directories and files, +# and creates symbolic links for the remaining items in the specified base path. +function create_symlinks_main_extension() { + local exclusions=(".*" "Documentation" "Documentation-GENERATED-temp" "var") + for item in ./*; do + local base_name=$(basename "$item") + for exclusion in "${exclusions[@]}"; do + if [[ $base_name == "$exclusion" ]]; then + continue 2 + fi + done + ln -sr "$item" "$BASE_PATH/packages/$EXTENSION_KEY/$base_name" + done +} + +# Function to create symlinks for additional extensions. +# It iterates over the directories in the specified path and creates symbolic links +# for each directory in the base path. +function create_symlinks_additional_extensions() { + for dir in Tests/Acceptance/Fixtures/packages/*/; do + ln -sr "$dir" "$BASE_PATH/packages/$(basename "$dir")" + done +} + +# Function to set up Composer for TYPO3 installation. +# It initializes a new Composer project in the specified base path, +# configures the TYPO3 web directory, sets up the repository for packages, +# and allows necessary Composer plugins. +function setup_composer() { + composer init --name="xima/typo3-$VERSION" --description="TYPO3 $VERSION" --no-interaction --working-dir "$BASE_PATH" + composer config extra.typo3/cms.web-dir public --working-dir "$BASE_PATH" + composer config repositories.packages path 'packages/*' --working-dir "$BASE_PATH" + composer config --no-interaction allow-plugins.typo3/cms-composer-installers true --working-dir "$BASE_PATH" + composer config --no-interaction allow-plugins.typo3/class-alias-loader true --working-dir "$BASE_PATH" +} + +# Function to set up TYPO3 configuration. +# It changes the current directory to the base path, sets the TYPO3 installation database name, +# and configures various TYPO3 settings such as debug mode, error display, trusted hosts pattern, +# mail transport, and graphics processor. +function setup_typo3() { + cd $BASE_PATH + export TYPO3_INSTALL_DB_DBNAME=$DATABASE + $TYPO3_BIN configuration:set 'BE/debug' 1 + $TYPO3_BIN configuration:set 'FE/debug' 1 + $TYPO3_BIN configuration:set 'SYS/devIPmask' '*' + $TYPO3_BIN configuration:set 'SYS/displayErrors' 1 + $TYPO3_BIN configuration:set 'SYS/trustedHostsPattern' '.*.*' + $TYPO3_BIN configuration:set 'MAIL/transport' 'smtp' + $TYPO3_BIN configuration:set 'MAIL/transport_smtp_server' 'localhost:1025' + $TYPO3_BIN configuration:set 'GFX/processor' 'ImageMagick' + $TYPO3_BIN configuration:set 'GFX/processor_path' '/usr/bin/' +} + +# Function to update TYPO3. +# It updates the TYPO3 database schema and flushes the cache. +function update_typo3() { + $TYPO3_BIN database:updateschema + $TYPO3_BIN cache:flush +} + +# Function to install required Composer packages for TYPO3. +function install_composer_packages() { + _progress " ├─ Install composer packages" + composer req typo3/cms-base-distribution:"^$VERSION" \ + $PACKAGE_NAME:'*@dev' \ + test/sitepackage:'*@dev' \ + helhum/typo3-console:'*' \ + --no-progress -n -d $BASE_PATH + _done +} + +# Function to install Codeception and related packages using Composer. +# It checks if the codeception.yml file exists in the extension's package directory. +# If the file exists, it installs the necessary Codeception packages, creates symlinks for +# the codeception.yml file and acceptance tests directory, and builds the Codeception configuration. +function prepare_acceptance_testing() { + if [ ! -f "$BASE_PATH/packages/$EXTENSION_KEY/codeception.yml" ]; then + return + fi + _progress " ├─ Prepare acceptance testing" + composer config --no-interaction allow-plugins.codeception/c3 true --working-dir "$BASE_PATH" + composer req --dev codeception/codeception:'*' \ + codeception/module-asserts:'*' \ + codeception/module-cli:'*' \ + codeception/module-db:'*' \ + codeception/module-phpbrowser:'*' \ + codeception/module-webdriver:'*' \ + eliashaeussler/typo3-codeception-helper:'*' \ + typo3/testing-framework:'*' \ + --no-progress -n -d $BASE_PATH + + ln -sr "$BASE_PATH/packages/$EXTENSION_KEY/codeception.yml" "$BASE_PATH/codeception.yml" + mkdir -p "$BASE_PATH/Tests" + ln -sr "$BASE_PATH/packages/$EXTENSION_KEY/Tests/Acceptance" "$BASE_PATH/Tests/Acceptance" + + $BASE_PATH/vendor/bin/codecept build -c $BASE_PATH/codeception.yml + _done +} + +# Function to import XML data into TYPO3. +# It sets the public directory and export directory paths, checks for all .xml files in the FIXTURE_DIR, +# and imports each XML file using TYPO3's import/export tool. +function import_xml_data() { + PUBLIC_DIR="/var/www/html/.Build/${VERSION}/public" + EXPORT_DIR="${PUBLIC_DIR}/fileadmin/user_upload/_temp_/importexport" + FIXTURE_DIR="/var/www/html/Tests/Acceptance/Fixtures" + + mkdir -p $EXPORT_DIR + + for XML_FILE in "$FIXTURE_DIR"/*.xml; do + if [ -f "$XML_FILE" ]; then + FILENAME=$(basename "$XML_FILE") + message yellow "Importing XML file $FILENAME..." + cp "$XML_FILE" "$EXPORT_DIR/" + $TYPO3_BIN impexp:import -vvv --force-uid "$EXPORT_DIR/$FILENAME" + fi + done + + # Check if no XML files were found + if ! ls "$FIXTURE_DIR"/*.xml >/dev/null 2>&1; then + message yellow "No XML files found in $FIXTURE_DIR. Skipping XML import." + fi +} + +# Function to import SQL data into TYPO3. +# It checks if the SQL data files exists, and if it does, it imports the SQL +# data into the TYPO3 database using the MySQL command. +function import_sql_data() { + FIXTURE_DIR="/var/www/html/Tests/Acceptance/Fixtures" + + for DATA_FILE in "$FIXTURE_DIR"/*.sql; do + if [ -f "$DATA_FILE" ]; then + message yellow "Importing $DATA_FILE..." + mysql -h db -u root -p"root" $DATABASE < "$DATA_FILE" + else + message yellow "No SQL files found in $FIXTURE_DIR. Import will be skipped." + fi + done +} + +# Function to perform post-setup tasks for TYPO3 version 11. +# It sets up TYPO3 by running the installation setup, configuring TYPO3 settings, +# and modifying configuration files to enable deprecations and adjust base paths. +function post_setup_11 { + $TYPO3_BIN install:setup -n --database-name $DATABASE + setup_typo3 + $TYPO3_BIN configuration:set 'GFX/processor_path_lzw' '/usr/bin/' + + sed -i "/'deprecations'/,/^[[:space:]]*'disabled' => true,/s/'disabled' => true,/'disabled' => false,/" /var/www/html/.Build/$VERSION/public/typo3conf/LocalConfiguration.php + + sed -i -e "s/base: ht\//base: \//g" /var/www/html/.Build/$VERSION/config/sites/main/config.yaml + sed -i -e 's/base: \/en\//base: \//g' /var/www/html/.Build/$VERSION/config/sites/main/config.yaml +} + +# Function to perform post-setup tasks for TYPO3 version 12. +# It sets up TYPO3 by running the installation setup, configuring TYPO3 settings, +# and modifying configuration files to enable deprecations and adjust base paths. +function post_setup_12 { + $TYPO3_BIN install:setup -n --database-name $DATABASE + setup_typo3 + + sed -i "/'deprecations'/,/^[[:space:]]*'disabled' => true,/s/'disabled' => true,/'disabled' => false,/" /var/www/html/.Build/$VERSION/config/system/settings.php + + sed -i -e "s/base: ht\//base: \//g" /var/www/html/.Build/$VERSION/config/sites/main/config.yaml + sed -i -e 's/base: \/en\//base: \//g' /var/www/html/.Build/$VERSION/config/sites/main/config.yaml +} + +# Function to perform post-setup tasks for TYPO3 version 13. +# It creates the TYPO3 database, sets up TYPO3 by running the installation setup, +# configures TYPO3 settings, and modifies configuration files to enable deprecations. +function post_setup_13 { + mysql -h db -u root -p"root" -e "CREATE DATABASE $DATABASE;" + $TYPO3_BIN setup -n --dbname=$DATABASE --password=$TYPO3_DB_PASSWORD --create-site="https://${VERSION}.${EXTENSION_NAME}.ddev.site" --admin-user-password=$TYPO3_SETUP_ADMIN_PASSWORD + setup_typo3 + + sed -i "/'deprecations'/,/^[[:space:]]*'disabled' => true,/s/'disabled' => true,/'disabled' => false,/" /var/www/html/.Build/$VERSION/config/system/settings.php +} + +# Function to perform post-setup tasks for TYPO3 version 14. +# It creates the TYPO3 database, sets up TYPO3 by running the installation setup, +# configures TYPO3 settings, and modifies configuration files to enable deprecations. +function post_setup_14 { + mysql -h db -u root -p"root" -e "CREATE DATABASE $DATABASE;" + $TYPO3_BIN setup -n --dbname=$DATABASE --password=$TYPO3_DB_PASSWORD --create-site="https://${VERSION}.${EXTENSION_NAME}.ddev.site" --admin-user-password=$TYPO3_SETUP_ADMIN_PASSWORD + setup_typo3 + + sed -i "/'deprecations'/,/^[[:space:]]*'disabled' => true,/s/'disabled' => true,/'disabled' => false,/" /var/www/html/.Build/$VERSION/config/system/settings.php +} + +# Function to display a colored message. +# It takes two arguments: the color and the message to display. +# The function supports the following colors: red, green, yellow, blue, magenta, cyan. +# If an unsupported color is provided, the message is displayed without color. +# +# Usage: +# message +# +# Arguments: +# color - The color to use for the message (red, green, yellow, blue, magenta, cyan). +# message - The message to display. +message() { + local color=$1 + local message=$2 + + case $color in + red) + echo -e "\033[31m$message\033[0m" + ;; + green) + echo -e "\033[32m$message\033[0m" + ;; + yellow) + echo -e "\033[33m$message\033[0m" + ;; + blue) + echo -e "\033[34m$message\033[0m" + ;; + magenta) + echo -e "\033[35m$message\033[0m" + ;; + cyan) + echo -e "\033[36m$message\033[0m" + ;; + *) + echo -e "$message" + ;; + esac +} diff --git a/.ddev/.setup/templates/index.php b/.ddev/.setup/templates/index.php new file mode 100644 index 0000000..db5d9cc --- /dev/null +++ b/.ddev/.setup/templates/index.php @@ -0,0 +1,102 @@ + + + + + + <?php echo $extensionKey; ?> + + + + +
+

+ +

+
+
+
+
+

Run ddev install all to install all TYPO3 instances below:

+ {$version}
Frontend
Backend
"; + } else { + echo "
Version {$version} is not installed. Run ddev install {$version} to install.
"; + } + } +?> +

Additional information

+

DDEV commands

+ getPathname(); + $fileName = $fileInfo->getFilename(); + + if ($fileName[0] === '.' || $fileInfo->isDir()) { + continue; + } + + $fileContent = file($filePath); + if (str_starts_with($fileContent[0], '#!/bin/bash')) { + $description = ''; + $usage = ''; + $example = ''; + + foreach ($fileContent as $line) { + if (str_starts_with($line, '## Description:')) { + $description = trim(str_replace('## Description:', '', $line)); + } elseif (str_starts_with($line, '## Usage:')) { + $usage = trim(str_replace('## Usage:', '', $line)); + } elseif (str_starts_with($line, '## Example:')) { + $example = trim(str_replace('## Example:', '', $line)); + } + } + + echo "
ddev $usage
$description
Example: $example
"; + } + } +} +?> +
+

TYPO3 Backend Credentials

+
    +
  • Username:
  • +
  • Password:
  • +
+
+
+ + + diff --git a/.ddev/addon-metadata/typo3-multi-version-extension/manifest.yaml b/.ddev/addon-metadata/typo3-multi-version-extension/manifest.yaml new file mode 100644 index 0000000..4f4eb06 --- /dev/null +++ b/.ddev/addon-metadata/typo3-multi-version-extension/manifest.yaml @@ -0,0 +1,26 @@ +name: typo3-multi-version-extension +repository: konradmichalik/ddev-typo3-multi-version-extension +version: 0.3.0 +install_date: "2026-04-20T14:42:24+02:00" +project_files: + - .setup/scripts/utils.sh + - .setup/templates/index.php + - .setup/Tests/Acceptance/Fixtures/ + - apache/10.conf + - apache/20.conf + - commands/host/launch + - commands/web/.install-11 + - commands/web/.install-12 + - commands/web/.install-13 + - commands/web/.install-14 + - commands/web/11 + - commands/web/12 + - commands/web/13 + - commands/web/14 + - commands/web/all + - commands/web/install + - docker-compose.typo3-setup.yaml +global_files: [] +removal_actions: + - rm ${DDEV_APPROOT}/.ddev/config.typo3-setup.yaml + - rm -f ${DDEV_APPROOT}/.ddev/.setup/ diff --git a/.ddev/apache/10.conf b/.ddev/apache/10.conf new file mode 100644 index 0000000..2105ee0 --- /dev/null +++ b/.ddev/apache/10.conf @@ -0,0 +1,43 @@ +#ddev-generated +# If you want to take over this file and customize it, remove the line above +# and ddev will respect it and won't overwrite the file. +# See https://ddev.readthedocs.io/en/stable/users/extend/customization-extendibility/#custom-apache-configuration + + ServerName repeatable-form-elements.ddev.site + DocumentRoot /var/www/html/.Build + + AllowOverride All + Allow from All + + + RewriteEngine On + RewriteCond %{HTTP:X-Forwarded-Proto} =https + RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d + RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last] + SetEnvIf X-Forwarded-Proto "https" HTTPS=on + ErrorLog /dev/stdout + CustomLog ${APACHE_LOG_DIR}/access.log combined + Alias "/phpstatus" "/var/www/phpstatus.php" + + + + ServerName repeatable-form-elements.ddev.site + DocumentRoot /var/www/html/.Build + + AllowOverride All + Allow from All + + + RewriteEngine On + RewriteCond %{HTTP:X-Forwarded-Proto} =https + RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d + RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last] + SetEnvIf X-Forwarded-Proto "https" HTTPS=on + ErrorLog /dev/stdout + CustomLog ${APACHE_LOG_DIR}/access.log combined + Alias "/phpstatus" "/var/www/phpstatus.php" + + SSLEngine on + SSLCertificateFile /etc/ssl/certs/master.crt + SSLCertificateKeyFile /etc/ssl/certs/master.key + diff --git a/.ddev/apache/20.conf b/.ddev/apache/20.conf new file mode 100644 index 0000000..af82af4 --- /dev/null +++ b/.ddev/apache/20.conf @@ -0,0 +1,51 @@ +#ddev-generated +# If you want to take over this file and customize it, remove the line above +# and ddev will respect it and won't overwrite the file. +# See https://ddev.readthedocs.io/en/stable/users/extend/customization-extendibility/#custom-apache-configuration + + ServerName sub.repeatable-form-elements.ddev.site + ServerAlias *.repeatable-form-elements.ddev.site + DocumentRoot /var/www/html/.Build + + AllowOverride All + Allow from All + + + RewriteEngine On + RewriteCond %{HTTP_HOST} ^([a-z0-9-]+)\.repeatable-form-elements\.ddev\.site$ + RewriteRule ^(.*)$ /var/www/html/.Build/%1/public/$1 [L] + + RewriteCond %{HTTP:X-Forwarded-Proto} =https + RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d + RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last] + SetEnvIf X-Forwarded-Proto "https" HTTPS=on + ErrorLog /dev/stdout + CustomLog ${APACHE_LOG_DIR}/access.log combined + Alias "/phpstatus" "/var/www/phpstatus.php" + + + + ServerName sub.repeatable-form-elements.ddev.site + ServerAlias *.repeatable-form-elements.ddev.site + DocumentRoot /var/www/html/.Build + + AllowOverride All + Allow from All + + + RewriteEngine On + RewriteCond %{HTTP_HOST} ^([a-z0-9-]+)\.repeatable-form-elements\.ddev\.site$ + RewriteRule ^(.*)$ /var/www/html/.Build/%1/public/$1 [L] + + RewriteCond %{HTTP:X-Forwarded-Proto} =https + RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d + RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last] + SetEnvIf X-Forwarded-Proto "https" HTTPS=on + ErrorLog /dev/stdout + CustomLog ${APACHE_LOG_DIR}/access.log combined + Alias "/phpstatus" "/var/www/phpstatus.php" + + SSLEngine on + SSLCertificateFile /etc/ssl/certs/master.crt + SSLCertificateKeyFile /etc/ssl/certs/master.key + diff --git a/.ddev/commands/web/.install-11 b/.ddev/commands/web/.install-11 new file mode 100755 index 0000000..2280e97 --- /dev/null +++ b/.ddev/commands/web/.install-11 @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +## #ddev-generated +. .ddev/.setup/scripts/utils.sh + +# Pre-setup function for TYPO3 version 11. +# This function is part of the installation script and is responsible for performing +# initial setup tasks before the main installation process begins. +pre_setup 11 + +#_progress " ├─ Install additional composer packages" +# composer req x/y:'^1.0' \ +# --no-progress -n -d $BASE_PATH +#_done + +# Function to perform post-setup tasks. +# This function is called after the main installation process to finalize the setup. +# It typically includes tasks such as configuring settings, modifying files, and other necessary adjustments. +post_setup \ No newline at end of file diff --git a/.ddev/commands/web/.install-12 b/.ddev/commands/web/.install-12 new file mode 100755 index 0000000..a7df200 --- /dev/null +++ b/.ddev/commands/web/.install-12 @@ -0,0 +1,18 @@ +#!/bin/bash + +. .ddev/.setup/scripts/utils.sh + +# Pre-setup function for TYPO3 version 12. +# This function is part of the installation script and is responsible for performing +# initial setup tasks before the main installation process begins. +pre_setup 12 + +#_progress " ├─ Install additional composer packages" +# composer req x/y:'^1.0' \ +# --no-progress -n -d $BASE_PATH +#_done + +# Function to perform post-setup tasks. +# This function is called after the main installation process to finalize the setup. +# It typically includes tasks such as configuring settings, modifying files, and other necessary adjustments. +post_setup diff --git a/.ddev/commands/web/.install-13 b/.ddev/commands/web/.install-13 new file mode 100755 index 0000000..b32a167 --- /dev/null +++ b/.ddev/commands/web/.install-13 @@ -0,0 +1,44 @@ +#!/bin/bash + +. .ddev/.setup/scripts/utils.sh + +# Pre-setup function for TYPO3 version 13. +pre_setup 13 + +# Copy form definitions to fileadmin (before post_setup so they exist when TYPO3 boots) +_progress " ├─ Copy form definitions" + FORM_DIR="$BASE_PATH/public/fileadmin/form_definitions" + mkdir -p "$FORM_DIR" + cp /var/www/html/Tests/Acceptance/Fixtures/form_definitions/*.form.yaml "$FORM_DIR/" 2>/dev/null || true +_done + +post_setup + +# v13 needs sys_template with explicit TypoScript imports + PAGE definition +_progress " ├─ Configure sys_template" + cd $BASE_PATH + php -r ' + $pdo = new PDO("mysql:host=db;dbname='$DATABASE'", "root", "root"); + $config = "@import \"EXT:fluid_styled_content/Configuration/TypoScript/setup.typoscript\"\n@import \"EXT:form/Configuration/TypoScript/setup.typoscript\"\n@import \"EXT:repeatable_form_elements/Configuration/TypoScript/setup.typoscript\"\n\npage = PAGE\npage.typeNum = 0\npage.includeCSSLibs.bootstrap = https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css\npage.includeCSSLibs.bootstrap.external = 1\npage.10 = FLUIDTEMPLATE\npage.10.templateName = Default\npage.10.templateRootPaths.10 = EXT:sitepackage/Resources/Private/Templates/Page/\npage.10.layoutRootPaths.10 = EXT:sitepackage/Resources/Private/Layouts/\n"; + $pdo->prepare("UPDATE sys_template SET config = ? WHERE pid = 1")->execute([$config]); + echo "sys_template updated\n"; + ' +_done + +# Add site set dependency AFTER post_setup (site config is created during post_setup) +_progress " ├─ Configure site set" + SITE_CONFIG="$BASE_PATH/config/sites/main/config.yaml" + if [ -f "$SITE_CONFIG" ]; then + cd $BASE_PATH + php -r ' + $f = "config/sites/main/config.yaml"; + $c = file_get_contents($f); + $c = preg_replace("/^dependencies:\n( - .*\n)*/m", "dependencies:\n - test/sitepackage\n", $c); + file_put_contents($f, $c); + ' + fi +_done + +_progress " ├─ Flush cache" + $TYPO3_BIN cache:flush +_done diff --git a/.ddev/commands/web/.install-14 b/.ddev/commands/web/.install-14 new file mode 100755 index 0000000..2a99957 --- /dev/null +++ b/.ddev/commands/web/.install-14 @@ -0,0 +1,51 @@ +#!/bin/bash + +. .ddev/.setup/scripts/utils.sh + +# Pre-setup function for TYPO3 version 14. +pre_setup 14 + +# Copy form definitions to fileadmin (before post_setup so they exist when TYPO3 boots) +_progress " ├─ Copy form definitions" + FORM_DIR="$BASE_PATH/public/fileadmin/form_definitions" + mkdir -p "$FORM_DIR" + cp /var/www/html/Tests/Acceptance/Fixtures/form_definitions/*.form.yaml "$FORM_DIR/" 2>/dev/null || true +_done + +post_setup + +# v14: Remove sys_template to avoid double rendering (site sets provide all TypoScript) +_progress " ├─ Remove sys_template" + cd $BASE_PATH + php -r ' + $pdo = new PDO("mysql:host=db;dbname='$DATABASE'", "root", "root"); + $pdo->exec("DELETE FROM sys_template"); + echo "sys_template removed\n"; + ' +_done + +# v14: Disable contentRenderingTemplates to prevent double content rendering +_progress " ├─ Disable contentRenderingTemplates" + echo ' $BASE_PATH/config/system/additional.php +_done + +# Add test/sitepackage to existing site dependencies +_progress " ├─ Configure site set" + SITE_CONFIG="$BASE_PATH/config/sites/main/config.yaml" + if [ -f "$SITE_CONFIG" ]; then + cd $BASE_PATH + php -r ' + $f = "config/sites/main/config.yaml"; + $c = file_get_contents($f); + if (strpos($c, "test/sitepackage") === false) { + $c = preg_replace("/^(dependencies:\n(?: - .*\n)*)/m", "$1 - test/sitepackage\n", $c); + file_put_contents($f, $c); + } + ' + fi +_done + +_progress " ├─ Flush cache" + rm -rf $BASE_PATH/var/cache/* + $TYPO3_BIN cache:flush +_done diff --git a/.ddev/commands/web/11 b/.ddev/commands/web/11 new file mode 100755 index 0000000..c96ff77 --- /dev/null +++ b/.ddev/commands/web/11 @@ -0,0 +1,24 @@ +#!/bin/bash + +## #ddev-generated +## Description: Exec command for TYPO3 instance 11. +## Usage: 11 +## Example: "ddev 11 composer du -o" or "ddev 11 typo3 cache:flush" + +. .ddev/.setup/scripts/utils.sh + +command=$@ +version=11 + +if [[ $command == typo3* ]]; then + command="/usr/bin/php vendor/bin/typo3cms${command:5}" +fi + +TYPO3_PATH=".Build/${version}" +if [ -d "$TYPO3_PATH" ]; then + message magenta "[TYPO3 v${version}] ${command}" + cd $TYPO3_PATH + $command +else + message red "TYPO3 binary not found for version ${version}" +fi diff --git a/.ddev/commands/web/12 b/.ddev/commands/web/12 new file mode 100755 index 0000000..040e65c --- /dev/null +++ b/.ddev/commands/web/12 @@ -0,0 +1,23 @@ +#!/bin/bash + +## Description: Exec command for TYPO3 instance 12. +## Usage: 12 +## Example: "ddev 12 composer du -o" or "ddev 12 typo3 cache:flush" + +. .ddev/.setup/scripts/utils.sh + +command=$@ +version=12 + +if [[ $command == typo3* ]]; then + command="/usr/bin/php vendor/bin/${command}" +fi + +TYPO3_PATH=".Build/${version}" +if [ -d "$TYPO3_PATH" ]; then + message magenta "[TYPO3 v${version}] ${command}" + cd $TYPO3_PATH + $command +else + message red "TYPO3 binary not found for version ${version}" +fi diff --git a/.ddev/commands/web/13 b/.ddev/commands/web/13 new file mode 100755 index 0000000..d70fae6 --- /dev/null +++ b/.ddev/commands/web/13 @@ -0,0 +1,23 @@ +#!/bin/bash + +## Description: Exec command for TYPO3 instance 13. +## Usage: 13 +## Example: "ddev 13 composer du -o" or "ddev 13 typo3 cache:flush" + +. .ddev/.setup/scripts/utils.sh + +command=$@ +version=13 + +if [[ $command == typo3* ]]; then + command="/usr/bin/php vendor/bin/${command}" +fi + +TYPO3_PATH=".Build/${version}" +if [ -d "$TYPO3_PATH" ]; then + message magenta "[TYPO3 v${version}] ${command}" + cd $TYPO3_PATH + $command +else + message red "TYPO3 binary not found for version ${version}" +fi diff --git a/.ddev/commands/web/14 b/.ddev/commands/web/14 new file mode 100755 index 0000000..2eb257d --- /dev/null +++ b/.ddev/commands/web/14 @@ -0,0 +1,23 @@ +#!/bin/bash + +## Description: Exec command for TYPO3 instance 14. +## Usage: 14 +## Example: "ddev 14 composer du -o" or "ddev 14 typo3 cache:flush" + +. .ddev/.setup/scripts/utils.sh + +command=$@ +version=14 + +if [[ $command == typo3* ]]; then + command="/usr/bin/php vendor/bin/${command}" +fi + +TYPO3_PATH=".Build/${version}" +if [ -d "$TYPO3_PATH" ]; then + message magenta "[TYPO3 v${version}] ${command}" + cd $TYPO3_PATH + $command +else + message red "TYPO3 binary not found for version ${version}" +fi diff --git a/.ddev/commands/web/all b/.ddev/commands/web/all new file mode 100755 index 0000000..e0bd4cc --- /dev/null +++ b/.ddev/commands/web/all @@ -0,0 +1,28 @@ +#!/bin/bash + +## Description: Exec command for all TYPO3 instances. +## Usage: all +## Example: "ddev all composer du -o" or "ddev all typo3 cache:flush" + +. .ddev/.setup/scripts/utils.sh + +command=$@ +mapfile -t versions < <(get_supported_typo3_versions) + +for version in "${versions[@]}"; do + TYPO3_PATH="/var/www/html/.Build/${version}" + if [ -d "$TYPO3_PATH" ]; then + if [[ $command == typo3* ]]; then + if [[ $version == 11 ]]; then + tempCommand="/usr/bin/php vendor/bin/typo3cms${command:5}" + else + tempCommand="/usr/bin/php vendor/bin/${command}" + fi + fi + cd $TYPO3_PATH + message magenta "[TYPO3 v${version}] ${tempCommand}" + $tempCommand + else + message red "TYPO3 instance not found for version ${version}" + fi +done \ No newline at end of file diff --git a/.ddev/commands/web/install b/.ddev/commands/web/install new file mode 100755 index 0000000..5af6da2 --- /dev/null +++ b/.ddev/commands/web/install @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +## #ddev-generated +## Description: Install TYPO3 instances. +## Usage: install +## Example: "ddev install" or "ddev install 12" or "ddev install all" +## MutagenSync: true + +TYPO3=${1} + +. .ddev/.setup/scripts/utils.sh + +if [ "$TYPO3" == "all" ]; then + mapfile -t versions < <(get_supported_typo3_versions) + for version in "${versions[@]}"; do + .ddev/commands/web/.install-$version + done +else + if [ -z "$TYPO3" ]; then + TYPO3=$(get_lowest_supported_typo3_versions) + else + if ! check_typo3_version "$TYPO3"; then + exit 1 + fi + fi + ".ddev/commands/web/.install-$TYPO3" +fi diff --git a/.ddev/config.typo3-setup.yaml b/.ddev/config.typo3-setup.yaml new file mode 100644 index 0000000..17149d5 --- /dev/null +++ b/.ddev/config.typo3-setup.yaml @@ -0,0 +1,11 @@ +type: php +docroot: .Build +webserver_type: apache-fpm +additional_hostnames: + - 11.repeatable-form-elements + - 12.repeatable-form-elements + - 13.repeatable-form-elements + - 14.repeatable-form-elements +hooks: + post-start: + - exec: mkdir -p /var/www/html/.Build/ && cp /var/www/html/.ddev/.setup/templates/* /var/www/html/.Build/ diff --git a/.ddev/config.yaml b/.ddev/config.yaml new file mode 100644 index 0000000..b436348 --- /dev/null +++ b/.ddev/config.yaml @@ -0,0 +1,293 @@ +name: repeatable-form-elements +type: typo3 +docroot: "" +php_version: "8.3" +webserver_type: apache-fpm +xdebug_enabled: false +additional_hostnames: [] +additional_fqdns: [] +database: + type: mariadb + version: "11.4" +use_dns_when_possible: true +composer_version: "2" +web_environment: [] +corepack_enable: false + +# Key features of DDEV's config.yaml: + +# name: # Name of the project, automatically provides +# http://projectname.ddev.site and https://projectname.ddev.site +# If the name is omitted, the project will take the name of the enclosing directory, +# which is useful if you want to have a copy of the project side by side with this one. + +# type: # backdrop, cakephp, codeigniter, craftcms, drupal, drupal6, drupal7, drupal8, drupal9, drupal10, drupal11, drupal12, generic, laravel, magento, magento2, php, shopware6, silverstripe, symfony, typo3, wordpress +# See https://docs.ddev.com/en/stable/users/quickstart/ for more +# information on the different project types + +# docroot: # Relative path to the directory containing index.php. + +# php_version: "8.4" # PHP version to use, "5.6" through "8.5" + +# You can explicitly specify the webimage but this +# is not recommended, as the images are often closely tied to DDEV's' behavior, +# so this can break upgrades. + +# webimage: +# It’s unusual to change this option, and we don’t recommend it without Docker experience and a good reason. +# Typically, this means additions to the existing web image using a .ddev/web-build/Dockerfile.* + +# database: +# type: # mysql, mariadb, postgres +# version: # database version, like "10.11" or "8.0" +# MariaDB versions can be 5.5-10.8, 10.11, 11.4, 11.8 +# MySQL versions can be 5.5-8.0, 8.4 +# PostgreSQL versions can be 9-18 + +# router_http_port: # Port to be used for http (defaults to global configuration, usually 80) +# router_https_port: # Port for https (defaults to global configuration, usually 443) + +# xdebug_enabled: false # Set to true to enable Xdebug and "ddev start" or "ddev restart" +# Note that for most people the commands +# "ddev xdebug" to enable Xdebug and "ddev xdebug off" to disable it work better, +# as leaving Xdebug enabled all the time is a big performance hit. + +# xhgui_http_port: "8143" +# xhgui_https_port: "8142" +# The XHGui ports can be changed from the default 8143 and 8142 +# Very rarely used + +# host_xhgui_port: "8142" +# Can be used to change the host binding port of the XHGui +# application. Rarely used; only when port conflict and +# bind_all_ports is used (normally with router disabled) + +# xhprof_mode: [prepend|xhgui|global] +# Default is "xhgui" + +# webserver_type: nginx-fpm, apache-fpm, generic + +# timezone: Europe/Berlin +# If timezone is unset, DDEV will attempt to derive it from the host system timezone +# using the $TZ environment variable or the /etc/localtime symlink. +# This is the timezone used in the containers and by PHP; +# it can be set to any valid timezone, +# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +# For example Europe/Dublin or MST7MDT + +# composer_root: +# Relative path to the Composer root directory from the project root. This is +# the directory which contains the composer.json and where all Composer related +# commands are executed. + +# composer_version: "2" +# You can set it to "" or "2" (default) for Composer v2 +# to use the latest major version available at the time your container is built. +# It is also possible to use each other Composer version channel. This includes: +# - 2.2 (latest Composer LTS version) +# - stable +# - preview +# - snapshot +# Alternatively, an explicit Composer version may be specified, for example "2.2.18". +# To reinstall Composer after the image was built, run "ddev utility rebuild". + +# nodejs_version: "24" +# change from the default system Node.js version to any other version. +# See https://docs.ddev.com/en/stable/users/configuration/config/#nodejs_version for more information +# and https://www.npmjs.com/package/n#specifying-nodejs-versions for the full documentation. + +# corepack_enable: false +# Change to 'true' to 'corepack enable' and gain access to latest versions of yarn/pnpm + +# additional_hostnames: +# - somename +# - someothername +# would provide http and https URLs for "somename.ddev.site" +# and "someothername.ddev.site". + +# additional_fqdns: +# - example.com +# - sub1.example.com +# would provide http and https URLs for "example.com" and "sub1.example.com" +# Please take care with this because it can cause great confusion. + +# upload_dirs: "custom/upload/dir" +# +# upload_dirs: +# - custom/upload/dir +# - ../private +# +# would set the destination paths for ddev import-files to /custom/upload/dir +# When Mutagen is enabled this path is bind-mounted so that all the files +# in the upload_dirs don't have to be synced into Mutagen. + +# disable_upload_dirs_warning: false +# If true, turns off the normal warning that says +# "You have Mutagen enabled and your 'php' project type doesn't have upload_dirs set" + +# ddev_version_constraint: "" +# Example: +# ddev_version_constraint: ">= 1.24.8" +# This will enforce that the running ddev version is within this constraint. +# See https://github.com/Masterminds/semver#checking-version-constraints for +# supported constraint formats + +# working_dir: +# web: /var/www/html +# db: /home +# would set the default working directory for the web and db services. +# These values specify the destination directory for ddev ssh and the +# directory in which commands passed into ddev exec are run. + +# omit_containers: [db, ddev-ssh-agent] +# Currently only these containers are supported. Some containers can also be +# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit +# the "db" container, several standard features of DDEV that access the +# database container will be unusable. In the global configuration it is also +# possible to omit ddev-router, but not here. + +# performance_mode: "global" +# DDEV offers performance optimization strategies to improve the filesystem +# performance depending on your host system. Should be configured globally. +# +# If set, will override the global config. Possible values are: +# - "global": uses the value from the global config. +# - "none": disables performance optimization for this project. +# - "mutagen": enables Mutagen for this project. +# +# See https://docs.ddev.com/en/stable/users/install/performance/#mutagen + +# fail_on_hook_fail: False +# Decide whether 'ddev start' should be interrupted by a failing hook + +# host_https_port: "59002" +# The host port binding for https can be explicitly specified. It is +# dynamic unless otherwise specified. +# This is not used by most people, most people use the *router* instead +# of the localhost port. + +# host_webserver_port: "59001" +# The host port binding for the ddev-webserver can be explicitly specified. It is +# dynamic unless otherwise specified. +# This is not used by most people, most people use the *router* instead +# of the localhost port. + +# host_db_port: "59002" +# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic +# unless explicitly specified. + +# mailpit_http_port: "8025" +# mailpit_https_port: "8026" +# The Mailpit ports can be changed from the default 8025 and 8026 + +# host_mailpit_port: "8025" +# The mailpit port is not normally bound on the host at all, instead being routed +# through ddev-router, but it can be bound directly to localhost if specified here. + +# webimage_extra_packages: ['php${DDEV_PHP_VERSION}-tidy', 'php${DDEV_PHP_VERSION}-yac'] +# Extra Debian packages that are needed in the webimage can be added here + +# dbimage_extra_packages: [netcat, telnet, sudo] +# Extra Debian packages that are needed in the dbimage can be added here + +# use_dns_when_possible: true +# If the host has internet access and the domain configured can +# successfully be looked up, DNS will be used for hostname resolution +# instead of editing /etc/hosts +# Defaults to true + +# project_tld: ddev.site +# The top-level domain used for project URLs +# The default "ddev.site" allows DNS lookup via a wildcard + +# share_default_provider: ngrok +# The default share provider to use for "ddev share" +# Defaults to global configuration, usually "ngrok" +# Can be "ngrok" or "cloudflared" or the name of a custom provider from .ddev/share-providers/ + +# share_provider_args: --basic-auth username:pass1234 +# Provide extra flags to the share provider script +# See https://docs.ddev.com/en/stable/users/configuration/config/#share_provider_args + +# disable_settings_management: false +# If true, DDEV will not create CMS-specific settings files like +# Drupal's settings.php/settings.ddev.php or TYPO3's additional.php +# In this case the user must provide all such settings. + +# You can inject environment variables into the web container with: +# web_environment: +# - SOMEENV=somevalue +# - SOMEOTHERENV=someothervalue + +# no_project_mount: false +# (Experimental) If true, DDEV will not mount the project into the web container; +# the user is responsible for mounting it manually or via a script. +# This is to enable experimentation with alternate file mounting strategies. +# For advanced users only! + +# bind_all_interfaces: false +# If true, host ports will be bound on all network interfaces, +# not the localhost interface only. This means that ports +# will be available on the local network if the host firewall +# allows it. + +# default_container_timeout: 120 +# The default time that DDEV waits for all containers to become ready can be increased from +# the default 120. This helps in importing huge databases, for example. + +#web_extra_exposed_ports: +#- name: nodejs +# container_port: 3000 +# http_port: 2999 +# https_port: 3000 +#- name: something +# container_port: 4000 +# https_port: 4000 +# http_port: 3999 +# Allows a set of extra ports to be exposed via ddev-router +# Fill in all three fields even if you don’t intend to use the https_port! +# If you don’t add https_port, then it defaults to 0 and ddev-router will fail to start. +# +# The port behavior on the ddev-webserver must be arranged separately, for example +# using web_extra_daemons. +# For example, with a web app on port 3000 inside the container, this config would +# expose that web app on https://.ddev.site:9999 and http://.ddev.site:9998 +# web_extra_exposed_ports: +# - name: myapp +# container_port: 3000 +# http_port: 9998 +# https_port: 9999 + +#web_extra_daemons: +#- name: "http-1" +# command: "/var/www/html/node_modules/.bin/http-server -p 3000" +# directory: /var/www/html +#- name: "http-2" +# command: "/var/www/html/node_modules/.bin/http-server /var/www/html/sub -p 3000" +# directory: /var/www/html + +# override_config: false +# By default, config.*.yaml files are *merged* into the configuration +# But this means that some things can't be overridden +# For example, if you have 'use_dns_when_possible: true'' you can't override it with a merge +# and you can't erase existing hooks or all environment variables. +# However, with "override_config: true" in a particular config.*.yaml file, +# 'use_dns_when_possible: false' can override the existing values, and +# hooks: +# post-start: [] +# or +# web_environment: [] +# or +# additional_hostnames: [] +# can have their intended affect. 'override_config' affects only behavior of the +# config.*.yaml file it exists in. + +# Many DDEV commands can be extended to run tasks before or after the +# DDEV command is executed, for example "post-start", "post-import-db", +# "pre-composer", "post-composer" +# See https://docs.ddev.com/en/stable/users/extend/custom-commands/ for more +# information on the commands that can be extended and the tasks you can define +# for them. Example: +#hooks: +# post-start: +# - exec: composer install -d /var/www/html diff --git a/.ddev/docker-compose.typo3-setup.yaml b/.ddev/docker-compose.typo3-setup.yaml new file mode 100644 index 0000000..3329007 --- /dev/null +++ b/.ddev/docker-compose.typo3-setup.yaml @@ -0,0 +1,33 @@ +## #ddev-generated +services: + web: + environment: + - EXTENSION_KEY=repeatable_form_elements + - EXTENSION_NAME=repeatable-form-elements + - PACKAGE_NAME=tritum/repeatable-form-elements + - TYPO3_VERSIONS=13 14 + - SYMLINK_EXCLUSIONS=.* Documentation Documentation-GENERATED-temp var vendor public + + # TYPO3 v11 and v12 config + - TYPO3_INSTALL_DB_DRIVER=mysqli + - TYPO3_INSTALL_DB_USER=root + - TYPO3_INSTALL_DB_PASSWORD=root + - TYPO3_INSTALL_DB_HOST=db + - TYPO3_INSTALL_DB_UNIX_SOCKET= + - TYPO3_INSTALL_DB_USE_EXISTING=0 + - TYPO3_INSTALL_ADMIN_USER=admin + - TYPO3_INSTALL_ADMIN_PASSWORD=Password1! + - TYPO3_INSTALL_SITE_NAME=EXT:repeatable-form-elements Dev Environment + - TYPO3_INSTALL_SITE_SETUP_TYPE=site + - TYPO3_INSTALL_WEB_SERVER_CONFIG=apache + + # TYPO3 v13 and v14 config + - TYPO3_DB_DRIVER=mysqli + - TYPO3_DB_USERNAME=root + - TYPO3_DB_PASSWORD=root + - TYPO3_DB_HOST=db + - TYPO3_SETUP_ADMIN_EMAIL=admin@example.com + - TYPO3_SETUP_ADMIN_USERNAME=admin + - TYPO3_SETUP_ADMIN_PASSWORD=Password1! + - TYPO3_PROJECT_NAME=EXT:repeatable-form-elements Dev Environment + - TYPO3_SERVER_TYPE=apache diff --git a/.gitignore b/.gitignore index fff40c4..bd5576a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /public /vendor /composer.lock +.Build/ diff --git a/Tests/Acceptance/Fixtures/data.sql b/Tests/Acceptance/Fixtures/data.sql new file mode 100644 index 0000000..3c36784 --- /dev/null +++ b/Tests/Acceptance/Fixtures/data.sql @@ -0,0 +1,19 @@ +-- Pages +INSERT INTO `pages` (`uid`, `pid`, `title`, `slug`, `doktype`, `is_siteroot`, `sorting`, `hidden`) +VALUES + (2, 1, 'Simple Repeatable Form', '/simple-form', 1, 0, 256, 0), + (3, 1, 'Nested Multi-Step Form', '/nested-form', 1, 0, 512, 0) +ON DUPLICATE KEY UPDATE `title`=VALUES(`title`), `slug`=VALUES(`slug`); + +-- Remove default content from root page (v14 setup creates a welcome element) +DELETE FROM `tt_content` WHERE `pid` = 1; + +-- Content elements: Form plugins (use high UIDs to avoid conflicts) +INSERT INTO `tt_content` (`uid`, `pid`, `CType`, `header`, `sorting`, `colPos`, `pi_flexform`) +VALUES + (100, 2, 'form_formframework', 'Simple Repeatable Form', 256, 0, '\n\n \n \n \n \n 1:/form_definitions/repeatable-simple.form.yaml\n \n \n \n \n'), + (101, 3, 'form_formframework', 'Nested Multi-Step Form', 256, 0, '\n\n \n \n \n \n 1:/form_definitions/repeatable-nested.form.yaml\n \n \n \n \n') +ON DUPLICATE KEY UPDATE `pid`=VALUES(`pid`), `CType`=VALUES(`CType`), `header`=VALUES(`header`), `pi_flexform`=VALUES(`pi_flexform`); + +-- Update root page title +UPDATE `pages` SET `title` = 'Repeatable Form Elements Test' WHERE `uid` = 1; diff --git a/Tests/Acceptance/Fixtures/form_definitions/repeatable-nested.form.yaml b/Tests/Acceptance/Fixtures/form_definitions/repeatable-nested.form.yaml new file mode 100644 index 0000000..a717950 --- /dev/null +++ b/Tests/Acceptance/Fixtures/form_definitions/repeatable-nested.form.yaml @@ -0,0 +1,82 @@ +renderingOptions: + submitButtonLabel: Absenden +type: Form +identifier: repeatable-nested +label: 'Repeatable Container - Nested & Multi-Step' +prototypeName: standard +finishers: + - + identifier: Confirmation + options: + message: 'Vielen Dank! Alle Daten wurden korrekt uebermittelt.' +renderables: + - + type: Page + identifier: page-1 + label: 'Schritt 1: Teilnehmer' + renderingOptions: + previousButtonLabel: Zurueck + nextButtonLabel: Weiter + renderables: + - + type: Text + identifier: event-name + label: Veranstaltung + validators: + - + identifier: NotEmpty + - + type: RepeatableContainer + identifier: participants + label: Teilnehmer + properties: + minimumCopies: 1 + maximumCopies: 10 + showRemoveButton: true + copyButtonLabel: 'Teilnehmer hinzufuegen' + removeButtonLabel: 'Teilnehmer entfernen' + renderables: + - + type: Text + identifier: name + label: Name + validators: + - + identifier: NotEmpty + - + type: Email + identifier: email + label: E-Mail + - + type: SingleSelect + identifier: ticket-type + label: Ticket-Typ + properties: + options: + '': 'Bitte waehlen' + standard: Standard + vip: VIP + student: Student + - + type: Checkbox + identifier: catering + label: 'Catering gewuenscht' + - + type: Page + identifier: page-2 + label: 'Schritt 2: Zusatzinformationen' + renderingOptions: + previousButtonLabel: Zurueck + nextButtonLabel: Weiter + renderables: + - + type: Textarea + identifier: notes + label: Anmerkungen + properties: + fluidAdditionalAttributes: + rows: '4' + - + type: SummaryPage + identifier: summary + label: Zusammenfassung diff --git a/Tests/Acceptance/Fixtures/form_definitions/repeatable-simple.form.yaml b/Tests/Acceptance/Fixtures/form_definitions/repeatable-simple.form.yaml new file mode 100644 index 0000000..ad48200 --- /dev/null +++ b/Tests/Acceptance/Fixtures/form_definitions/repeatable-simple.form.yaml @@ -0,0 +1,58 @@ +renderingOptions: + submitButtonLabel: Absenden +type: Form +identifier: repeatable-simple +label: 'Repeatable Container - Simple Test' +prototypeName: standard +finishers: + - + identifier: Confirmation + options: + message: 'Vielen Dank! Das Formular wurde erfolgreich abgesendet.' +renderables: + - + type: Page + identifier: page-1 + label: 'Kontaktdaten' + renderables: + - + type: Text + identifier: company + label: Firma + properties: + fluidAdditionalAttributes: + placeholder: 'Firmenname' + - + type: RepeatableContainer + identifier: persons + label: Personen + properties: + minimumCopies: 0 + maximumCopies: 5 + showRemoveButton: true + copyButtonLabel: 'Weitere Person hinzufuegen' + removeButtonLabel: 'Person entfernen' + renderables: + - + type: Text + identifier: firstname + label: Vorname + validators: + - + identifier: NotEmpty + - + type: Text + identifier: lastname + label: Nachname + validators: + - + identifier: NotEmpty + - + type: Email + identifier: email + label: E-Mail + validators: + - + identifier: NotEmpty + - + identifier: EmailAddress diff --git a/Tests/Acceptance/Fixtures/packages/sitepackage/Configuration/Services.yaml b/Tests/Acceptance/Fixtures/packages/sitepackage/Configuration/Services.yaml new file mode 100644 index 0000000..f1576f3 --- /dev/null +++ b/Tests/Acceptance/Fixtures/packages/sitepackage/Configuration/Services.yaml @@ -0,0 +1,6 @@ +#ddev-generated +services: + _defaults: + autowire: true + autoconfigure: true + public: false \ No newline at end of file diff --git a/Tests/Acceptance/Fixtures/packages/sitepackage/Configuration/Sets/Sitepackage/config.yaml b/Tests/Acceptance/Fixtures/packages/sitepackage/Configuration/Sets/Sitepackage/config.yaml new file mode 100644 index 0000000..929fa7c --- /dev/null +++ b/Tests/Acceptance/Fixtures/packages/sitepackage/Configuration/Sets/Sitepackage/config.yaml @@ -0,0 +1,6 @@ +name: test/sitepackage +label: Test Sitepackage +dependencies: + - typo3/fluid-styled-content + - typo3/form + - tritum/repeatable-form-elements diff --git a/Tests/Acceptance/Fixtures/packages/sitepackage/Configuration/Sets/Sitepackage/setup.typoscript b/Tests/Acceptance/Fixtures/packages/sitepackage/Configuration/Sets/Sitepackage/setup.typoscript new file mode 100644 index 0000000..b7ffe16 --- /dev/null +++ b/Tests/Acceptance/Fixtures/packages/sitepackage/Configuration/Sets/Sitepackage/setup.typoscript @@ -0,0 +1,12 @@ +page = PAGE +page { + typeNum = 0 + includeCSSLibs.bootstrap = https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css + includeCSSLibs.bootstrap.external = 1 + 10 = FLUIDTEMPLATE + 10 { + templateName = Default + templateRootPaths.10 = EXT:sitepackage/Resources/Private/Templates/Page/ + layoutRootPaths.10 = EXT:sitepackage/Resources/Private/Layouts/ + } +} diff --git a/Tests/Acceptance/Fixtures/packages/sitepackage/README.md b/Tests/Acceptance/Fixtures/packages/sitepackage/README.md new file mode 100644 index 0000000..304250a --- /dev/null +++ b/Tests/Acceptance/Fixtures/packages/sitepackage/README.md @@ -0,0 +1,6 @@ +#ddev-generated +# Sitepackage + +This is a demo sitepackage for testing purposes. + +Adjust them to your needs to test features of the main extension. \ No newline at end of file diff --git a/Tests/Acceptance/Fixtures/packages/sitepackage/Resources/Private/Layouts/Default.html b/Tests/Acceptance/Fixtures/packages/sitepackage/Resources/Private/Layouts/Default.html new file mode 100644 index 0000000..ebcb47d --- /dev/null +++ b/Tests/Acceptance/Fixtures/packages/sitepackage/Resources/Private/Layouts/Default.html @@ -0,0 +1,6 @@ + +
+

{data.title}

+ +
+ diff --git a/Tests/Acceptance/Fixtures/packages/sitepackage/Resources/Private/Templates/Page/Default.html b/Tests/Acceptance/Fixtures/packages/sitepackage/Resources/Private/Templates/Page/Default.html new file mode 100644 index 0000000..81fd559 --- /dev/null +++ b/Tests/Acceptance/Fixtures/packages/sitepackage/Resources/Private/Templates/Page/Default.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/Tests/Acceptance/Fixtures/packages/sitepackage/composer.json b/Tests/Acceptance/Fixtures/packages/sitepackage/composer.json new file mode 100644 index 0000000..93c8786 --- /dev/null +++ b/Tests/Acceptance/Fixtures/packages/sitepackage/composer.json @@ -0,0 +1,23 @@ +{ + "name": "test/sitepackage", + "description": "Sitepackage for repeatable_form_elements testing", + "license": "GPL-2.0-or-later", + "type": "typo3-cms-extension", + "require": { + "typo3/cms-core": "^13.4 || ^14.0", + "typo3/cms-fluid-styled-content": "^13.4 || ^14.0", + "typo3/cms-form": "^13.4 || ^14.0", + "tritum/repeatable-form-elements": "*" + }, + "extra": { + "typo3/cms": { + "extension-key": "sitepackage" + } + }, + "version": "1.0.0", + "autoload": { + "psr-4": { + "Test\\Sitepackage\\": "Classes/" + } + } +} From 7c51d63b265482e11abcf9176b06475dd32a26df Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 20 Apr 2026 16:43:44 +0200 Subject: [PATCH 10/11] docs: add CHANGELOG with unreleased v14 changes --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a37db0d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +## Unreleased + +### Breaking + +- Dropped TYPO3 v12 support. Minimum version is now TYPO3 v13.4 LTS. + +### Added + +- TYPO3 v14 compatibility +- PSR-14 `AfterBuildingFinishedEvent` replaces the removed `afterBuildingFinished` SC_OPTIONS hook (Breaking #98239) +- PSR-14 `AfterCurrentPageIsResolvedListener` replaces the removed `afterInitializeCurrentPage` SC_OPTIONS hook +- PSR-14 `BeforeRenderableIsRenderedListener` replaces the removed `beforeRendering` SC_OPTIONS hook +- DDEV multi-version test environment with pre-loaded form fixtures for v13 and v14 + +### Changed + +- Removed `call_user_func` wrapper in `ext_localconf.php` +- Removed deprecated `formEditorPartials` configuration for backend form editor (Deprecation #109306). Both v13 and v14 now use their default composite element rendering. +- Typed `CopyService::$features` property as `Features` instead of `mixed` +- Made `FormHooks` class `final` +- Used explicit nullable types for PHP 8.4 compatibility +- Updated `Services.yaml` to exclude Event classes from DI auto-registration + +### Migration + +Legacy SC_OPTIONS hook registrations are kept for v13 backward compatibility. The PSR-14 event listeners handle v14 automatically. No manual migration steps required. From 2baef4a5375c2f7453a6cc21f2154a5aa21a4ef7 Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Tue, 21 Apr 2026 12:55:48 +0200 Subject: [PATCH 11/11] chore: register plugin.tx_form YAML globally and update hook comments --- Classes/Configuration/Extension.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Classes/Configuration/Extension.php b/Classes/Configuration/Extension.php index 9a89806..931c8ac 100644 --- a/Classes/Configuration/Extension.php +++ b/Classes/Configuration/Extension.php @@ -41,8 +41,14 @@ final class Extension public static function addTypoScriptSetup(): void { - // @todo: maybe move this to 'EXT:repeatable_form_elements/ext_typoscript_setup.typoscript' ExtensionManagementUtility::addTypoScriptSetup(trim(' + plugin.tx_form { + settings { + yamlConfigurations { + 1511193633 = EXT:repeatable_form_elements/Configuration/Yaml/FormSetup.yaml + } + } + } module.tx_form { settings { yamlConfigurations { @@ -56,9 +62,9 @@ public static function addTypoScriptSetup(): void public static function registerHooks(): void { - // These hooks are still supported in TYPO3 v13 and v14. - // When TYPO3 introduces PSR-14 replacements, migrate to event listeners - // registered in Configuration/Services.yaml. + // v13: these SC_OPTIONS hooks are the active mechanism. + // v14: these hooks were removed (Breaking #107566, #107569). + // The PSR-14 event listeners in Configuration/Services.yaml take over. $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterInitializeCurrentPage'][1511196413] = FormHooks::class; $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeRendering'][1511196413] = FormHooks::class; }