From 2ce48affb72bc929a9b6a7432f53039ff9a67c99 Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 10:09:55 +0200
Subject: [PATCH 01/13] [BUGFIX] Fixed Type Error when cObj returns null

- Implemented `getContentObjectRenderer()` method to handle cases where `cObj` might be null in some cases where request is not build completely
- Adjusted `applyStdWrapProperties()` to ensure graceful fallback when `cObj` is not available.

This improves error handling and diagnostics, making the extension more reliable and easier to maintain.

Resolves: #37
---
 Classes/Helper/TypoScriptHelper.php                     | 7 ++++++-
 Classes/Traits/GetTypoScriptFrontendControllerTrait.php | 8 +++++++-
 2 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/Classes/Helper/TypoScriptHelper.php b/Classes/Helper/TypoScriptHelper.php
index ff9ca46..2aed77a 100644
--- a/Classes/Helper/TypoScriptHelper.php
+++ b/Classes/Helper/TypoScriptHelper.php
@@ -102,6 +102,11 @@ public function hasReplaceEntry(array $typoScriptConfiguration, $key): bool
 
     public function applyStdWrapProperties(string $content, array $stdWrapConfiguration): string
     {
-        return $this->getContentObjectRenderer()->stdWrap($content, $stdWrapConfiguration);
+        $contentObjectRenderer = $this->getContentObjectRenderer();
+        if ($contentObjectRenderer === null) {
+            return $content;
+        }
+
+        return $contentObjectRenderer->stdWrap($content, $stdWrapConfiguration);
     }
 }
diff --git a/Classes/Traits/GetTypoScriptFrontendControllerTrait.php b/Classes/Traits/GetTypoScriptFrontendControllerTrait.php
index 021f1ce..5ffacec 100644
--- a/Classes/Traits/GetTypoScriptFrontendControllerTrait.php
+++ b/Classes/Traits/GetTypoScriptFrontendControllerTrait.php
@@ -31,6 +31,12 @@ protected function getTypoScriptFrontendController(): TypoScriptFrontendControll
 
     protected function getContentObjectRenderer(): ?ContentObjectRenderer
     {
-        return $this->getTypoScriptFrontendController()->cObj;
+        $tsfe = $this->getTypoScriptFrontendController();
+
+        if (!$tsfe->cObj) {
+            return null;
+        }
+
+        return $tsfe->cObj;
     }
 }

From 7b16b96cad2ce7c685b27201c15929cc5708e485 Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 10:42:34 +0200
Subject: [PATCH 02/13] [TASK] Formatting Lint and CGL mostly commas in arrays

---
 .../workflows/{testscorev12.yml => ci.yml}    |   0
 .github/workflows/testscorev11.yml            |  53 ---
 Build/FunctionalTests.xml                     |  28 --
 Build/FunctionalTestsBootstrap.php            |  17 -
 Build/Scripts/runTests.sh                     | 395 -----------------
 Build/UnitTests.xml                           |  29 --
 Build/UnitTestsBootstrap.php                  |  75 ----
 Build/php-cs-fixer/config.php                 |  94 -----
 Build/testing-docker/docker-compose.yml       | 398 ------------------
 .../ReplaceConfigurationTest.php              |  12 +-
 .../ConfigurationTypeEnumerationTest.php      |   6 +-
 .../ReplaceContentCachedTest.php              |  12 +-
 .../ReplaceContentUnCachedTest.php            |  12 +-
 .../Functional/Helper/ReplacerHelperTest.php  |   2 +-
 .../Helper/TypoScriptHelperTest.php           |  52 +--
 .../TypoScriptFrontendControllerHookTest.php  |   4 +-
 .../ReplaceContentMiddlewareTest.php          |   4 +-
 17 files changed, 52 insertions(+), 1141 deletions(-)
 rename .github/workflows/{testscorev12.yml => ci.yml} (100%)
 delete mode 100644 .github/workflows/testscorev11.yml
 delete mode 100644 Build/FunctionalTests.xml
 delete mode 100644 Build/FunctionalTestsBootstrap.php
 delete mode 100755 Build/Scripts/runTests.sh
 delete mode 100644 Build/UnitTests.xml
 delete mode 100644 Build/UnitTestsBootstrap.php
 delete mode 100644 Build/php-cs-fixer/config.php
 delete mode 100644 Build/testing-docker/docker-compose.yml

diff --git a/.github/workflows/testscorev12.yml b/.github/workflows/ci.yml
similarity index 100%
rename from .github/workflows/testscorev12.yml
rename to .github/workflows/ci.yml
diff --git a/.github/workflows/testscorev11.yml b/.github/workflows/testscorev11.yml
deleted file mode 100644
index 5a5c8ca..0000000
--- a/.github/workflows/testscorev11.yml
+++ /dev/null
@@ -1,53 +0,0 @@
-name: Test replacer for TYPO3 11 LTS
-
-on:
-  pull_request:
-
-jobs:
-  CGL:
-    name: Coding Style Check (TYPO3 Community CGL)
-
-    runs-on: ubuntu-latest
-
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-
-      - name: Composer
-        run: Build/Scripts/runTests.sh -t 11 -p 8.1 -s composerUpdate
-
-      - name: Lint PHP
-        run: Build/Scripts/runTests.sh -t 11 -p 8.1 -s lint
-
-      - name: Validate code against CGL
-        run: Build/Scripts/runTests.sh -t 11 -p 8.1 -s cgl -n
-
-  testsuite:
-    name: PHP Unit and Functional Tests for TYPO3 Version 11 LTS
-
-    needs: CGL
-
-    runs-on: ubuntu-latest
-
-    strategy:
-      matrix:
-        php: [ '7.4', '8.0', '8.1', '8.2' ]
-
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-
-      - name: Composer
-        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -t 11 -s composerUpdate
-
-      - name: Lint PHP
-        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s lint
-
-      - name: Functional tests with mariadb
-        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -d mariadb -s functional
-
-      - name: Functional tests with postgres
-        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -d postgres -s functional
-
-      - name: Functional tests with sqlite
-        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -d sqlite -s functional
diff --git a/Build/FunctionalTests.xml b/Build/FunctionalTests.xml
deleted file mode 100644
index 59a41b6..0000000
--- a/Build/FunctionalTests.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<phpunit
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
-    backupGlobals="true"
-    bootstrap="FunctionalTestsBootstrap.php"
-    cacheResult="false"
-    colors="true"
-    convertErrorsToExceptions="true"
-    convertWarningsToExceptions="true"
-    forceCoversAnnotation="false"
-    stopOnError="false"
-    stopOnFailure="false"
-    stopOnIncomplete="false"
-    stopOnSkipped="false"
-    verbose="false"
-    beStrictAboutTestsThatDoNotTestAnything="false"
-    failOnWarning="true"
->
-    <testsuites>
-        <testsuite name="Functional tests">
-            <directory>../Tests/Functional/</directory>
-        </testsuite>
-    </testsuites>
-    <php>
-        <ini name="display_errors" value="1" />
-        <env name="TYPO3_CONTEXT" value="Testing" />
-    </php>
-</phpunit>
diff --git a/Build/FunctionalTestsBootstrap.php b/Build/FunctionalTestsBootstrap.php
deleted file mode 100644
index 52db699..0000000
--- a/Build/FunctionalTestsBootstrap.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-
-/*
- * This file is part of the package jweiland/replacer.
- *
- * For the full copyright and license information, please read the
- * LICENSE file that was distributed with this source code.
- */
-
-use TYPO3\TestingFramework\Core\Testbase;
-
-call_user_func(function () {
-    $testbase = new Testbase();
-    $testbase->defineOriginalRootPath();
-    $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests');
-    $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient');
-});
diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh
deleted file mode 100755
index 8881707..0000000
--- a/Build/Scripts/runTests.sh
+++ /dev/null
@@ -1,395 +0,0 @@
-#!/usr/bin/env bash
-
-#
-# TYPO3 core test runner based on docker and docker-compose.
-#
-
-# Function to write a .env file in Build/testing-docker
-# This is read by docker-compose and vars defined here are
-# used in Build/testing-docker/docker-compose.yml
-setUpDockerComposeDotEnv() {
-    # Delete possibly existing local .env file if exists
-    [ -e .env ] && rm .env
-    # Set up a new .env file for docker-compose
-    {
-        echo "COMPOSE_PROJECT_NAME=local"
-        # To prevent access rights of files created by the testing, the docker image later
-        # runs with the same user that is currently executing the script. docker-compose can't
-        # use $UID directly itself since it is a shell variable and not an env variable, so
-        # we have to set it explicitly here.
-        echo "HOST_UID=`id -u`"
-        # Your local user
-        echo "ROOT_DIR=${ROOT_DIR}"
-        echo "HOST_USER=${USER}"
-        echo "TEST_FILE=${TEST_FILE}"
-        echo "TYPO3_VERSION=${TYPO3_VERSION}"
-        echo "PHP_XDEBUG_ON=${PHP_XDEBUG_ON}"
-        echo "PHP_XDEBUG_PORT=${PHP_XDEBUG_PORT}"
-        echo "DOCKER_PHP_IMAGE=${DOCKER_PHP_IMAGE}"
-        echo "EXTRA_TEST_OPTIONS=${EXTRA_TEST_OPTIONS}"
-        echo "SCRIPT_VERBOSE=${SCRIPT_VERBOSE}"
-        echo "CGLCHECK_DRY_RUN=${CGLCHECK_DRY_RUN}"
-        echo "DATABASE_DRIVER=${DATABASE_DRIVER}"
-    } > .env
-}
-
-# Options -a and -d depend on each other. The function
-# validates input combinations and sets defaults.
-handleDbmsAndDriverOptions() {
-    case ${DBMS} in
-        mysql|mariadb)
-            [ -z "${DATABASE_DRIVER}" ] && DATABASE_DRIVER="mysqli"
-            if [ "${DATABASE_DRIVER}" != "mysqli" ] && [ "${DATABASE_DRIVER}" != "pdo_mysql" ]; then
-                echo "Invalid option -a ${DATABASE_DRIVER} with -d ${DBMS}" >&2
-                echo >&2
-                echo "call \"./Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
-                exit 1
-            fi
-            ;;
-        postgres|sqlite)
-            if [ -n "${DATABASE_DRIVER}" ]; then
-                echo "Invalid option -a ${DATABASE_DRIVER} with -d ${DBMS}" >&2
-                echo >&2
-                echo "call \"./Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
-                exit 1
-            fi
-            ;;
-    esac
-}
-
-# Load help text into $HELP
-read -r -d '' HELP <<EOF
-jweiland replacer test runner. Execute unit test suite and some other details.
-Also used by github for test execution.
-
-Recommended docker version is >=20.10 for xdebug break pointing to work reliably, and
-a recent docker-compose (tested >=1.21.2) is needed.
-
-Usage: $0 [options] [file]
-
-No arguments: Run all unit tests with PHP 8.1
-
-Options:
-    -s <...>
-        Specifies which test suite to run
-            - acceptance: backend acceptance tests
-            - cgl: cgl test and fix all php files
-            - clean: clean up build and testing related files
-            - composerUpdate: "composer update", handy if host has no PHP
-            - functional: functional tests
-            - lint: PHP linting
-            - phpstan: phpstan analyze
-            - phpstanGenerateBaseline: regenerate phpstan baseline, handy after phpstan updates
-            - unit (default): PHP unit tests
-
-    -a <mysqli|pdo_mysql>
-        Only with -s acceptance,functional
-        Specifies to use another driver, following combinations are available:
-            - mysql
-                - mysqli (default)
-                - pdo_mysql
-            - mariadb
-                - mysqli (default)
-                - pdo_mysql
-
-    -d <sqlite|mariadb|mysql|postgres>
-        Only with -s acceptance,functional
-        Specifies on which DBMS tests are performed
-            - sqlite: (default) use sqlite
-            - mariadb: use mariadb
-            - mysql: use mysql
-            - postgres: use postgres
-
-
-    -p <7.4|8.0|8.1|8.2>
-        Specifies the PHP minor version to be used
-            - 7.4 (default): use PHP 7.4
-            - 8.0: use PHP 8.0
-            - 8.1: use PHP 8.1
-            - 8.2: use PHP 8.2
-
-    -t <11|12>
-        Only with -s composerUpdate
-        Specifies the TYPO3 core major version to be used
-            - 11 (default): use TYPO3 core v11
-            - 12: Use TYPO3 core v12
-
-    -e "<phpunit or codeception options>"
-        Only with -s acceptance|functional|unit
-        Additional options to send to phpunit (unit & functional tests) or codeception (acceptance
-        tests). For phpunit, options starting with "--" must be added after options starting with "-".
-        Example -e "-v --filter canRetrieveValueWithGP" to enable verbose output AND filter tests
-        named "canRetrieveValueWithGP"
-
-    -x
-        Only with -s functional|unit|acceptance
-        Send information to host instance for test or system under test break points. This is especially
-        useful if a local PhpStorm instance is listening on default xdebug port 9003. A different port
-        can be selected with -y
-
-    -n
-        Only with -s cgl
-        Activate dry-run in CGL check that does not actively change files and only prints broken ones.
-
-    -u
-        Update existing typo3/core-testing-*:latest docker images. Maintenance call to docker pull latest
-        versions of the main php images. The images are updated once in a while and only the youngest
-        ones are supported by core testing. Use this if weird test errors occur. Also removes obsolete
-        image versions of typo3/core-testing-*.
-
-    -v
-        Enable verbose script output. Shows variables and docker commands.
-
-    -h
-        Show this help.
-
-Examples:
-    # Run unit tests using PHP 7.4
-    ./Build/Scripts/runTests.sh
-EOF
-
-# Test if docker-compose exists, else exit out with error
-if ! type "docker-compose" > /dev/null; then
-  echo "This script relies on docker and docker-compose. Please install" >&2
-  exit 1
-fi
-
-# Go to the directory this script is located, so everything else is relative
-# to this dir, no matter from where this script is called.
-THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
-cd "$THIS_SCRIPT_DIR" || exit 1
-
-# Go to directory that contains the local docker-compose.yml file
-cd ../testing-docker || exit 1
-
-# Option defaults
-if ! command -v realpath &> /dev/null; then
-  echo "This script works best with realpath installed" >&2
-  ROOT_DIR="${PWD}/../../"
-else
-  ROOT_DIR=`realpath ${PWD}/../../`
-fi
-TEST_SUITE="unit"
-DBMS="sqlite"
-PHP_VERSION="7.4"
-TYPO3_VERSION="11"
-PHP_XDEBUG_ON=0
-PHP_XDEBUG_PORT=9003
-EXTRA_TEST_OPTIONS=""
-SCRIPT_VERBOSE=0
-CGLCHECK_DRY_RUN=""
-DATABASE_DRIVER=""
-
-# Option parsing
-# Reset in case getopts has been used previously in the shell
-OPTIND=1
-# Array for invalid options
-INVALID_OPTIONS=();
-# Simple option parsing based on getopts (! not getopt)
-while getopts ":s:a:d:p:t:e:xnhuv" OPT; do
-    case ${OPT} in
-        s)
-            TEST_SUITE=${OPTARG}
-            ;;
-        a)
-            DATABASE_DRIVER=${OPTARG}
-            ;;
-        d)
-            DBMS=${OPTARG}
-            ;;
-        p)
-            PHP_VERSION=${OPTARG}
-            if ! [[ ${PHP_VERSION} =~ ^(7.4|8.0|8.1|8.2)$ ]]; then
-                INVALID_OPTIONS+=("p ${OPTARG}")
-            fi
-            ;;
-        t)
-            TYPO3_VERSION=${OPTARG}
-            if ! [[ ${TYPO3_VERSION} =~ ^(11|12)$ ]]; then
-                INVALID_OPTIONS+=("p ${OPTARG}")
-            fi
-            ;;
-        e)
-            EXTRA_TEST_OPTIONS=${OPTARG}
-            ;;
-        x)
-            PHP_XDEBUG_ON=1
-            ;;
-        y)
-            PHP_XDEBUG_PORT=${OPTARG}
-            ;;
-        h)
-            echo "${HELP}"
-            exit 0
-            ;;
-        n)
-            CGLCHECK_DRY_RUN="-n"
-            ;;
-        u)
-            TEST_SUITE=update
-            ;;
-        v)
-            SCRIPT_VERBOSE=1
-            ;;
-        \?)
-            INVALID_OPTIONS+=(${OPTARG})
-            ;;
-        :)
-            INVALID_OPTIONS+=(${OPTARG})
-            ;;
-    esac
-done
-
-# Exit on invalid options
-if [ ${#INVALID_OPTIONS[@]} -ne 0 ]; then
-    echo "Invalid option(s):" >&2
-    for I in "${INVALID_OPTIONS[@]}"; do
-        echo "-"${I} >&2
-    done
-    echo >&2
-    echo "${HELP}" >&2
-    exit 1
-fi
-
-# Move "7.4" to "php74", the latter is the docker container name
-DOCKER_PHP_IMAGE=`echo "php${PHP_VERSION}" | sed -e 's/\.//'`
-
-# Set $1 to first mass argument, this is the optional test file or test directory to execute
-shift $((OPTIND - 1))
-TEST_FILE=${1}
-
-if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-    set -x
-fi
-
-# Suite execution
-case ${TEST_SUITE} in
-    acceptance)
-        handleDbmsAndDriverOptions
-        setUpDockerComposeDotEnv
-        case ${DBMS} in
-            sqlite)
-                echo "Using driver: ${DATABASE_DRIVER}"
-                docker-compose run acceptance_cli_sqlite
-                SUITE_EXIT_CODE=$?
-                ;;
-            mysql)
-                echo "Using driver: ${DATABASE_DRIVER}"
-                docker-compose run acceptance_cli_mysql80
-                SUITE_EXIT_CODE=$?
-                ;;
-            mariadb)
-                echo "Using driver: ${DATABASE_DRIVER}"
-                docker-compose run acceptance_cli_mariadb10
-                SUITE_EXIT_CODE=$?
-                ;;
-            postgres)
-                docker-compose run acceptance_cli_postgres10
-                SUITE_EXIT_CODE=$?
-                ;;
-            *)
-                echo "Acceptance tests don't run with DBMS ${DBMS}" >&2
-                echo >&2
-                echo "call \"./Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
-                exit 1
-        esac
-        docker-compose down
-        ;;
-    cgl)
-        # Active dry-run for cgl needs not "-n" but specific options
-        if [[ ! -z ${CGLCHECK_DRY_RUN} ]]; then
-            CGLCHECK_DRY_RUN="--dry-run --diff"
-        fi
-        setUpDockerComposeDotEnv
-        docker-compose run cgl
-        SUITE_EXIT_CODE=$?
-        docker-compose down
-        ;;
-    clean)
-        rm -rf ../../composer.lock ../../.Build/ ../../Tests/Acceptance/Support/_generated/ ../../composer.json.testing
-        ;;
-    composerUpdate)
-        setUpDockerComposeDotEnv
-        cp ../../composer.json ../../composer.json.orig
-        if [ -f "../../composer.json.testing" ]; then
-            cp ../../composer.json ../../composer.json.orig
-        fi
-        docker-compose run composer_update
-        cp ../../composer.json ../../composer.json.testing
-        mv ../../composer.json.orig ../../composer.json
-        SUITE_EXIT_CODE=$?
-        docker-compose down
-        ;;
-    functional)
-        handleDbmsAndDriverOptions
-        setUpDockerComposeDotEnv
-        case ${DBMS} in
-            mariadb)
-                echo "Using driver: ${DATABASE_DRIVER}"
-                docker-compose run functional_mariadb10
-                SUITE_EXIT_CODE=$?
-                ;;
-            mysql)
-                echo "Using driver: ${DATABASE_DRIVER}"
-                docker-compose run functional_mysql80
-                SUITE_EXIT_CODE=$?
-                ;;
-            postgres)
-                docker-compose run functional_postgres10
-                SUITE_EXIT_CODE=$?
-                ;;
-            sqlite)
-                # sqlite has a tmpfs as Web/typo3temp/var/tests/functional-sqlite-dbs/
-                # Since docker is executed as root (yay!), the path to this dir is owned by
-                # root if docker creates it. Thank you, docker. We create the path beforehand
-                # to avoid permission issues.
-                mkdir -p ${ROOT_DIR}/Web/typo3temp/var/tests/functional-sqlite-dbs/
-                docker-compose run functional_sqlite
-                SUITE_EXIT_CODE=$?
-                ;;
-            *)
-                echo "Invalid -d option argument ${DBMS}" >&2
-                echo >&2
-                echo "${HELP}" >&2
-                exit 1
-        esac
-        docker-compose down
-        ;;
-    lint)
-        setUpDockerComposeDotEnv
-        docker-compose run lint
-        SUITE_EXIT_CODE=$?
-        docker-compose down
-        ;;
-    phpstan)
-        setUpDockerComposeDotEnv
-        docker-compose run phpstan
-        SUITE_EXIT_CODE=$?
-        docker-compose down
-        ;;
-    phpstanGenerateBaseline)
-        setUpDockerComposeDotEnv
-        docker-compose run phpstan_generate_baseline
-        SUITE_EXIT_CODE=$?
-        docker-compose down
-        ;;
-    unit)
-        setUpDockerComposeDotEnv
-        docker-compose run unit
-        SUITE_EXIT_CODE=$?
-        docker-compose down
-        ;;
-    update)
-        # pull typo3/core-testing-*:latest versions of those ones that exist locally
-        docker images typo3/core-testing-*:latest --format "{{.Repository}}:latest" | xargs -I {} docker pull {}
-        # remove "dangling" typo3/core-testing-* images (those tagged as <none>)
-        docker images typo3/core-testing-* --filter "dangling=true" --format "{{.ID}}" | xargs -I {} docker rmi {}
-        ;;
-    *)
-        echo "Invalid -s option argument ${TEST_SUITE}" >&2
-        echo >&2
-        echo "${HELP}" >&2
-        exit 1
-esac
-
-exit $SUITE_EXIT_CODE
diff --git a/Build/UnitTests.xml b/Build/UnitTests.xml
deleted file mode 100644
index a90da4c..0000000
--- a/Build/UnitTests.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<phpunit
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
-    backupGlobals="true"
-    cacheResult="false"
-    colors="true"
-    convertErrorsToExceptions="true"
-    convertWarningsToExceptions="true"
-    convertNoticesToExceptions="true"
-    forceCoversAnnotation="false"
-    processIsolation="false"
-    stopOnError="false"
-    stopOnFailure="false"
-    stopOnIncomplete="false"
-    stopOnSkipped="false"
-    verbose="false"
-    beStrictAboutTestsThatDoNotTestAnything="false"
-    failOnWarning="true"
->
-    <testsuites>
-        <testsuite name="Unit tests">
-            <directory>../Tests/Unit/</directory>
-        </testsuite>
-    </testsuites>
-    <php>
-        <ini name="display_errors" value="1" />
-        <env name="TYPO3_CONTEXT" value="Testing" />
-    </php>
-</phpunit>
diff --git a/Build/UnitTestsBootstrap.php b/Build/UnitTestsBootstrap.php
deleted file mode 100644
index bd41c10..0000000
--- a/Build/UnitTestsBootstrap.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-
-/*
- * This file is part of the package jweiland/replacer.
- *
- * For the full copyright and license information, please read the
- * LICENSE file that was distributed with this source code.
- */
-
-use TYPO3\CMS\Core\Cache\Backend\NullBackend;
-use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
-use TYPO3\CMS\Core\Configuration\ConfigurationManager;
-use TYPO3\CMS\Core\Core\Bootstrap;
-use TYPO3\CMS\Core\Core\Environment;
-use TYPO3\CMS\Core\Package\PackageManager;
-use TYPO3\CMS\Core\Package\UnitTestPackageManager;
-use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\TestingFramework\Core\SystemEnvironmentBuilder;
-use TYPO3\TestingFramework\Core\Testbase;
-
-(static function () {
-    $testbase = new Testbase();
-
-    // These if's are for core testing (package typo3/cms) only. cms-composer-installer does
-    // not create the autoload-include.php file that sets these env vars and sets composer
-    // mode to true. testing-framework can not be used without composer anyway, so it is safe
-    // to do this here. This way it does not matter if 'bin/phpunit' or 'vendor/phpunit/phpunit/phpunit'
-    // is called to run the tests since the 'relative to entry script' path calculation within
-    // SystemEnvironmentBuilder is not used. However, the binary must be called from the document
-    // root since getWebRoot() uses 'getcwd()'.
-    if (!getenv('TYPO3_PATH_ROOT')) {
-        putenv('TYPO3_PATH_ROOT=' . rtrim($testbase->getWebRoot(), '/'));
-    }
-    if (!getenv('TYPO3_PATH_WEB')) {
-        putenv('TYPO3_PATH_WEB=' . rtrim($testbase->getWebRoot(), '/'));
-    }
-
-    $testbase->defineSitePath();
-
-    // We can use the "typo3/cms-composer-installers" constant "TYPO3_COMPOSER_MODE" to determine composer mode.
-    // This should be always true except for TYPO3 mono repository.
-    $composerMode = defined('TYPO3_COMPOSER_MODE') && TYPO3_COMPOSER_MODE === true;
-    $requestType = \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_BE | \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_CLI;
-    SystemEnvironmentBuilder::run(0, $requestType, $composerMode);
-
-    $testbase->createDirectory(Environment::getPublicPath() . '/typo3conf/ext');
-    $testbase->createDirectory(Environment::getPublicPath() . '/typo3temp/assets');
-    $testbase->createDirectory(Environment::getPublicPath() . '/typo3temp/var/tests');
-    $testbase->createDirectory(Environment::getPublicPath() . '/typo3temp/var/transient');
-
-    // Retrieve an instance of class loader and inject to core bootstrap
-    $classLoader = require $testbase->getPackagesPath() . '/autoload.php';
-    Bootstrap::initializeClassLoader($classLoader);
-
-    // Initialize default TYPO3_CONF_VARS
-    $configurationManager = new ConfigurationManager();
-    $GLOBALS['TYPO3_CONF_VARS'] = $configurationManager->getDefaultConfiguration();
-
-    $cache = new PhpFrontend(
-        'core',
-        new NullBackend('production', [])
-    );
-    $packageManager = Bootstrap::createPackageManager(
-        UnitTestPackageManager::class,
-        Bootstrap::createPackageCache($cache)
-    );
-
-    GeneralUtility::setSingletonInstance(PackageManager::class, $packageManager);
-    ExtensionManagementUtility::setPackageManager($packageManager);
-
-    $testbase->dumpClassLoadingInformation();
-
-    GeneralUtility::purgeInstances();
-})();
diff --git a/Build/php-cs-fixer/config.php b/Build/php-cs-fixer/config.php
deleted file mode 100644
index 11f4f0a..0000000
--- a/Build/php-cs-fixer/config.php
+++ /dev/null
@@ -1,94 +0,0 @@
-<?php
-
-/*
- * This file is part of the package jweiland/replacer.
- *
- * For the full copyright and license information, please read the
- * LICENSE file that was distributed with this source code.
- */
-
-use PhpCsFixer\Config;
-
-if (PHP_SAPI !== 'cli') {
-    die('This script supports command line usage only. Please check your command.');
-}
-
-$headerComment = <<<COMMENT
-This file is part of the package jweiland/replacer.
-
-For the full copyright and license information, please read the
-LICENSE file that was distributed with this source code.
-COMMENT;
-
-return (new Config())
-    ->setFinder(
-        (new PhpCsFixer\Finder())
-            ->ignoreVCSIgnored(true)
-            ->in([
-                __DIR__ . '/../../Build/',
-                __DIR__ . '/../../Classes/',
-                __DIR__ . '/../../Configuration/',
-                __DIR__ . '/../../Tests/',
-            ])
-    )
-    ->setUsingCache(false)
-    ->setRiskyAllowed(true)
-    ->setRules([
-        '@DoctrineAnnotation' => true,
-        '@PSR2' => true,
-        'header_comment' => [
-            'header' => $headerComment,
-        ],
-        'array_syntax' => ['syntax' => 'short'],
-        'blank_line_after_opening_tag' => true,
-        'cast_spaces' => ['space' => 'none'],
-        'compact_nullable_typehint' => true,
-        'concat_space' => ['spacing' => 'one'],
-        'declare_equal_normalize' => ['space' => 'none'],
-        'declare_parentheses' => true,
-        'dir_constant' => true,
-        'function_to_constant' => ['functions' => ['get_called_class', 'get_class', 'get_class_this', 'php_sapi_name', 'phpversion', 'pi']],
-        'function_typehint_space' => true,
-        'lowercase_cast' => true,
-        'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
-        'modernize_strpos' => false,
-        'modernize_types_casting' => true,
-        'native_function_casing' => true,
-        'new_with_braces' => true,
-        'no_alias_functions' => true,
-        'no_blank_lines_after_phpdoc' => true,
-        'no_empty_phpdoc' => true,
-        'no_empty_statement' => true,
-        'no_extra_blank_lines' => true,
-        'no_leading_import_slash' => true,
-        'no_leading_namespace_whitespace' => true,
-        'no_null_property_initialization' => true,
-        'no_short_bool_cast' => true,
-        'no_singleline_whitespace_before_semicolons' => true,
-        'no_superfluous_elseif' => true,
-        'no_trailing_comma_in_singleline' => true,
-        'no_unneeded_control_parentheses' => true,
-        'no_unused_imports' => true,
-        'no_useless_else' => true,
-        'no_useless_nullsafe_operator' => true,
-        'no_whitespace_in_blank_line' => true,
-        'ordered_imports' => true,
-        'php_unit_construct' => ['assertions' => ['assertEquals', 'assertSame', 'assertNotEquals', 'assertNotSame']],
-        'php_unit_mock_short_will_return' => true,
-        'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
-        'phpdoc_no_access' => true,
-        'phpdoc_no_empty_return' => true,
-        'phpdoc_no_package' => true,
-        'phpdoc_scalar' => true,
-        'phpdoc_trim' => true,
-        'phpdoc_types' => true,
-        'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
-        'return_type_declaration' => ['space_before' => 'none'],
-        'single_quote' => true,
-        'single_space_after_construct' => true,
-        'single_line_comment_style' => ['comment_types' => ['hash']],
-        'single_trait_insert_per_statement' => true,
-        'trailing_comma_in_multiline' => ['elements' => ['arrays']],
-        'whitespace_after_comma_in_array' => ['ensure_single_space' => true],
-        'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false],
-    ]);
diff --git a/Build/testing-docker/docker-compose.yml b/Build/testing-docker/docker-compose.yml
deleted file mode 100644
index 692b5ac..0000000
--- a/Build/testing-docker/docker-compose.yml
+++ /dev/null
@@ -1,398 +0,0 @@
-version: '2.3'
-services:
-  mariadb10:
-    # not using mariadb:10 for the time being, because 10.5.7 (currently latest) is broken
-    image: mariadb:10.5.6
-    environment:
-      MYSQL_ROOT_PASSWORD: funcp
-    tmpfs:
-      - /var/lib/mysql/:rw,noexec,nosuid
-
-  mysql80:
-    image: mysql:8.0
-    environment:
-      MYSQL_ROOT_PASSWORD: funcp
-    tmpfs:
-      - /var/lib/mysql/:rw,noexec,nosuid
-
-  postgres10:
-    image: postgres:10-alpine
-    environment:
-      POSTGRES_PASSWORD: funcp
-      POSTGRES_USER: ${HOST_USER}
-    tmpfs:
-      - /var/lib/postgresql/data:rw,noexec,nosuid
-
-  acceptance_cli_mariadb10:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    links:
-      - mariadb10
-    environment:
-      typo3DatabaseDriver: "${DATABASE_DRIVER}"
-      typo3DatabaseName: func_test
-      typo3DatabaseUsername: root
-      typo3DatabasePassword: funcp
-      typo3DatabaseHost: mariadb10
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    working_dir: ${ROOT_DIR}/
-    extra_hosts:
-      - "host.docker.internal:host-gateway"
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        echo Waiting for database start...;
-        while ! nc -z mariadb10 3306; do
-          sleep 1;
-        done;
-        echo Database is up;
-        php -v | grep '^PHP';
-        mkdir -p .Build/Web/typo3temp/var/tests/
-        COMMAND=\".Build/vendor/codeception/codeception/codecept run Cli -d -c Tests/codeception.yml ${TEST_FILE}\"
-        if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
-          XDEBUG_MODE=\"off\" $${COMMAND};
-        else
-          XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" $${COMMAND};
-        fi
-      "
-
-  acceptance_cli_mysql80:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    links:
-      - mysql80
-    environment:
-      typo3DatabaseDriver: "${DATABASE_DRIVER}"
-      typo3DatabaseName: func_test
-      typo3DatabaseUsername: root
-      typo3DatabasePassword: funcp
-      typo3DatabaseHost: mysql80
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    working_dir: ${ROOT_DIR}
-    extra_hosts:
-      - "host.docker.internal:host-gateway"
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        echo Waiting for database start...;
-        while ! nc -z mysql80 3306; do
-          sleep 1;
-        done;
-        echo Database is up;
-        php -v | grep '^PHP';
-        mkdir -p .Build/Web/typo3temp/var/tests/
-        COMMAND=\".Build/vendor/codeception/codeception/codecept run Cli -d -c Tests/codeception.yml ${TEST_FILE}\"
-        if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
-          XDEBUG_MODE=\"off\" $${COMMAND};
-        else
-          XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" $${COMMAND};
-        fi
-      "
-
-  acceptance_cli_postgres10:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    links:
-      - postgres10
-    environment:
-      typo3DatabaseDriver: pdo_pgsql
-      typo3DatabaseName: bamboo
-      typo3DatabaseUsername: ${HOST_USER}
-      typo3DatabaseHost: postgres10
-      typo3DatabasePassword: funcp
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    working_dir: ${ROOT_DIR}
-    extra_hosts:
-      - "host.docker.internal:host-gateway"
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        echo Waiting for database start...;
-        while ! nc -z postgres10 5432; do
-          sleep 1;
-        done;
-        echo Database is up;
-        php -v | grep '^PHP';
-        mkdir -p .Build/Web/typo3temp/var/tests/
-        COMMAND=\".Build/vendor/codeception/codeception/codecept run Cli -d -c Tests/codeception.yml ${TEST_FILE}\"
-        if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
-          XDEBUG_MODE=\"off\" $${COMMAND};
-        else
-          XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" $${COMMAND};
-        fi
-      "
-
-  acceptance_cli_sqlite:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    environment:
-      typo3DatabaseDriver: pdo_sqlite
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    working_dir: ${ROOT_DIR}
-    extra_hosts:
-      - "host.docker.internal:host-gateway"
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        php -v | grep '^PHP';
-        mkdir -p Web/typo3temp/var/tests/
-        COMMAND=\".Build/vendor/codeception/codeception/codecept run Cli -d -c Tests/codeception.yml ${TEST_FILE}\"
-        if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
-          XDEBUG_MODE=\"off\" $${COMMAND};
-        else
-          XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" $${COMMAND};
-        fi
-      "
-
-  cgl:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    working_dir: ${ROOT_DIR}
-    extra_hosts:
-      - "host.docker.internal:host-gateway"
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        php -v | grep '^PHP';
-        if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
-          php -dxdebug.mode=off .Build/bin/php-cs-fixer fix -v ${CGLCHECK_DRY_RUN} --config=Build/php-cs-fixer/config.php
-        else
-          XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" PHP_CS_FIXER_ALLOW_XDEBUG=1 .Build/bin/php-cs-fixer fix -v ${CGLCHECK_DRY_RUN} --config=Build/php-cs-fixer/config.php
-        fi
-      "
-
-  composer_update:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    working_dir: ${ROOT_DIR}
-    environment:
-      COMPOSER_CACHE_DIR: ".cache/composer"
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        php -v | grep '^PHP';
-        if [ ${TYPO3_VERSION} -eq 11 ]; then
-              composer req --dev --no-update typo3/cms-composer-installers:^3.0 typo3/cms-workspaces:^11.5 typo3/cms-impexp:^11.5
-              composer req typo3/cms-core:^11.5 --no-update
-        fi
-        if [ ${TYPO3_VERSION} -eq 12 ]; then
-            composer req --dev --no-update "typo3/cms-composer-installers:^5.0" typo3/cms-impexp:^12.4 typo3/cms-workspaces:^12.4
-            composer req typo3/cms-core:^12.4 --no-update
-        fi
-        composer update --no-progress --no-interaction;
-      "
-
-  functional_mariadb10:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    links:
-      - mariadb10
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    environment:
-      typo3DatabaseDriver: "${DATABASE_DRIVER}"
-      typo3DatabaseName: func_test
-      typo3DatabaseUsername: root
-      typo3DatabasePassword: funcp
-      typo3DatabaseHost: mariadb10
-    working_dir: ${ROOT_DIR}
-    extra_hosts:
-      - "host.docker.internal:host-gateway"
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        echo Waiting for database start...;
-        while ! nc -z mariadb10 3306; do
-          sleep 1;
-        done;
-        echo Database is up;
-        php -v | grep '^PHP';
-        if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
-          php -dxdebug.mode=off .Build/bin/phpunit -c Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE};
-        else
-          php -dxdebug.mode='debug,develop' XDEBUG_TRIGGER='foo' XDEBUG_CONFIG='client_host=host.docker.internal' .Build/bin/phpunit -c Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE};
-        fi
-      "
-
-  functional_mysql80:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    links:
-      - mysql80
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    environment:
-      typo3DatabaseDriver: "${DATABASE_DRIVER}"
-      typo3DatabaseName: func_test
-      typo3DatabaseUsername: root
-      typo3DatabasePassword: funcp
-      typo3DatabaseHost: mysql80
-    working_dir: ${ROOT_DIR}
-    extra_hosts:
-      - "host.docker.internal:host-gateway"
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        echo Waiting for database start...;
-        while ! nc -z mysql80 3306; do
-          sleep 1;
-        done;
-        echo Database is up;
-        php -v | grep '^PHP';
-        if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
-          XDEBUG_MODE=\"off\" .Build/bin/phpunit -c Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE};
-        else
-          XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" .Build/bin/phpunit -c Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE};
-        fi
-      "
-
-  functional_postgres10:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    links:
-      - postgres10
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    environment:
-      typo3DatabaseDriver: pdo_pgsql
-      typo3DatabaseName: bamboo
-      typo3DatabaseUsername: ${HOST_USER}
-      typo3DatabaseHost: postgres10
-      typo3DatabasePassword: funcp
-    working_dir: ${ROOT_DIR}
-    extra_hosts:
-      - "host.docker.internal:host-gateway"
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        echo Waiting for database start...;
-        while ! nc -z postgres10 5432; do
-          sleep 1;
-        done;
-        echo Database is up;
-        php -v | grep '^PHP';
-        if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
-          XDEBUG_MODE=\"off\" .Build/bin/phpunit -c Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} --exclude-group not-postgres ${TEST_FILE};
-        else
-          XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" .Build/bin/phpunit -c Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} --exclude-group not-postgres ${TEST_FILE};
-        fi
-      "
-
-  functional_sqlite:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    tmpfs:
-      - ${ROOT_DIR}/Web/typo3temp/var/tests/functional-sqlite-dbs/:rw,noexec,nosuid,uid=${HOST_UID}
-    environment:
-      typo3DatabaseDriver: pdo_sqlite
-    working_dir: ${ROOT_DIR}
-    extra_hosts:
-      - "host.docker.internal:host-gateway"
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        php -v | grep '^PHP';
-        if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
-          XDEBUG_MODE=\"off\" .Build/bin/phpunit -c Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} --exclude-group not-sqlite ${TEST_FILE};
-        else
-          XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" .Build/bin/phpunit -c Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} --exclude-group not-sqlite ${TEST_FILE};
-        fi
-      "
-
-  lint:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    working_dir: ${ROOT_DIR}
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        php -v | grep '^PHP';
-        find . -name \\*.php ! -path "./.Build/\\*" -print0 | xargs -0 -n1 -P4 php -dxdebug.mode=off -l >/dev/null
-      "
-
-  phpstan:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    working_dir: ${ROOT_DIR}
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        mkdir -p .Build/.cache
-        php -v | grep '^PHP';
-        php -dxdebug.mode=off .Build/bin/phpstan analyze -c Build/phpstan.neon --no-progress
-      "
-
-  phpstan_generate_baseline:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    working_dir: ${ROOT_DIR}
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        mkdir -p .Build/.cache
-        php -v | grep '^PHP';
-        php -dxdebug.mode=off .Build/bin/phpstan analyze -c Build/phpstan.neon --generate-baseline=Build/phpstan-baseline.neon
-      "
-
-  unit:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    volumes:
-      - ${ROOT_DIR}:${ROOT_DIR}
-    working_dir: ${ROOT_DIR}
-    extra_hosts:
-      - "host.docker.internal:host-gateway"
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        php -v | grep '^PHP';
-        if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
-          XDEBUG_MODE=\"off\" .Build/bin/phpunit -c Build/UnitTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE};
-        else
-          XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" .Build/bin/phpunit -c Build/UnitTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE};
-        fi
-      "
\ No newline at end of file
diff --git a/Tests/Functional/Configuration/ReplaceConfigurationTest.php b/Tests/Functional/Configuration/ReplaceConfigurationTest.php
index 7156e63..59d1e9c 100644
--- a/Tests/Functional/Configuration/ReplaceConfigurationTest.php
+++ b/Tests/Functional/Configuration/ReplaceConfigurationTest.php
@@ -36,7 +36,7 @@ public function getSearchValueInitiallyReturnsEmptyString(): void
     {
         self::assertSame(
             '',
-            $this->subject->getSearchValue()
+            $this->subject->getSearchValue(),
         );
     }
 
@@ -49,7 +49,7 @@ public function setSearchValueWillSetSearchValue(): void
 
         self::assertSame(
             'foo bar',
-            $this->subject->getSearchValue()
+            $this->subject->getSearchValue(),
         );
     }
 
@@ -60,7 +60,7 @@ public function getReplaceValueInitiallyReturnsEmptyString()
     {
         self::assertSame(
             '',
-            $this->subject->getReplaceValue()
+            $this->subject->getReplaceValue(),
         );
     }
 
@@ -73,7 +73,7 @@ public function setReplaceValueSetsReplaceValue()
 
         self::assertSame(
             'foo bar',
-            $this->subject->getReplaceValue()
+            $this->subject->getReplaceValue(),
         );
     }
 
@@ -83,7 +83,7 @@ public function setReplaceValueSetsReplaceValue()
     public function getUseRegExpInitiallyReturnsFalse()
     {
         self::assertFalse(
-            $this->subject->isUseRegExp()
+            $this->subject->isUseRegExp(),
         );
     }
 
@@ -95,7 +95,7 @@ public function setUseRegExpSetsUseRegExp()
         $this->subject->setUseRegExp(true);
 
         self::assertTrue(
-            $this->subject->isUseRegExp()
+            $this->subject->isUseRegExp(),
         );
     }
 }
diff --git a/Tests/Functional/Enumeration/ConfigurationTypeEnumerationTest.php b/Tests/Functional/Enumeration/ConfigurationTypeEnumerationTest.php
index 8bb401d..b1c36fe 100644
--- a/Tests/Functional/Enumeration/ConfigurationTypeEnumerationTest.php
+++ b/Tests/Functional/Enumeration/ConfigurationTypeEnumerationTest.php
@@ -37,7 +37,7 @@ public function configurationTypeAsStringWillReturnDefaultValue(): void
     {
         self::assertSame(
             'search.',
-            (string)$this->subject
+            (string)$this->subject,
         );
     }
 
@@ -50,7 +50,7 @@ public function setValueToSearchWillAppendDot(): void
 
         self::assertSame(
             'search.',
-            (string)$subject
+            (string)$subject,
         );
     }
 
@@ -63,7 +63,7 @@ public function setValueToReplaceWillAppendDot(): void
 
         self::assertSame(
             'replace.',
-            (string)$subject
+            (string)$subject,
         );
     }
 
diff --git a/Tests/Functional/FrontendRequest/ReplaceContentCachedTest.php b/Tests/Functional/FrontendRequest/ReplaceContentCachedTest.php
index f98fa4f..db5be11 100644
--- a/Tests/Functional/FrontendRequest/ReplaceContentCachedTest.php
+++ b/Tests/Functional/FrontendRequest/ReplaceContentCachedTest.php
@@ -33,7 +33,7 @@ protected function setUp(): void
             1,
             [
                 'EXT:replacer/Tests/Functional/Fixtures/basic_template.typoscript',
-            ]
+            ],
         );
     }
 
@@ -43,7 +43,7 @@ protected function setUp(): void
     public function frontendRequestDoNotReplacesContentOnTypo3Error(): void
     {
         $response = self::executeFrontendSubRequest(
-            new InternalRequest('https://website.local/site-not-found')
+            new InternalRequest('https://website.local/site-not-found'),
         );
 
         $body = (string)$response->getBody();
@@ -52,11 +52,11 @@ public function frontendRequestDoNotReplacesContentOnTypo3Error(): void
         // which will result in an uninitialized TSFE or methods on null exception
         self::assertStringNotContainsString(
             'TSFE',
-            $body
+            $body,
         );
         self::assertStringNotContainsString(
             'null',
-            $body
+            $body,
         );
     }
 
@@ -66,14 +66,14 @@ public function frontendRequestDoNotReplacesContentOnTypo3Error(): void
     public function frontendRequestReplacesContent(): void
     {
         $response = self::executeFrontendSubRequest(
-            new InternalRequest('https://website.local/')
+            new InternalRequest('https://website.local/'),
         );
 
         $body = (string)$response->getBody();
 
         self::assertStringContainsString(
             'I like fruits',
-            $body
+            $body,
         );
     }
 }
diff --git a/Tests/Functional/FrontendRequest/ReplaceContentUnCachedTest.php b/Tests/Functional/FrontendRequest/ReplaceContentUnCachedTest.php
index 59453d4..f0d4821 100644
--- a/Tests/Functional/FrontendRequest/ReplaceContentUnCachedTest.php
+++ b/Tests/Functional/FrontendRequest/ReplaceContentUnCachedTest.php
@@ -34,7 +34,7 @@ protected function setUp(): void
             [
                 'EXT:replacer/Tests/Functional/Fixtures/basic_template.typoscript',
                 'EXT:replacer/Tests/Functional/Fixtures/user_int.typoscript',
-            ]
+            ],
         );
     }
 
@@ -44,7 +44,7 @@ protected function setUp(): void
     public function frontendRequestDoNotReplacesContentOnTypo3Error(): void
     {
         $response = self::executeFrontendSubRequest(
-            new InternalRequest('https://website.local/site-not-found')
+            new InternalRequest('https://website.local/site-not-found'),
         );
 
         $body = (string)$response->getBody();
@@ -53,11 +53,11 @@ public function frontendRequestDoNotReplacesContentOnTypo3Error(): void
         // which will result in an uninitialized TSFE or methods on null exception
         self::assertStringNotContainsString(
             'TSFE',
-            $body
+            $body,
         );
         self::assertStringNotContainsString(
             'null',
-            $body
+            $body,
         );
     }
 
@@ -67,14 +67,14 @@ public function frontendRequestDoNotReplacesContentOnTypo3Error(): void
     public function frontendRequestReplacesContent(): void
     {
         $response = self::executeFrontendSubRequest(
-            new InternalRequest('https://website.local/')
+            new InternalRequest('https://website.local/'),
         );
 
         $body = (string)$response->getBody();
 
         self::assertStringContainsString(
             'I like fruits',
-            $body
+            $body,
         );
     }
 }
diff --git a/Tests/Functional/Helper/ReplacerHelperTest.php b/Tests/Functional/Helper/ReplacerHelperTest.php
index 07d640a..0ebedfe 100644
--- a/Tests/Functional/Helper/ReplacerHelperTest.php
+++ b/Tests/Functional/Helper/ReplacerHelperTest.php
@@ -126,7 +126,7 @@ public function replaceContentWithValidSearchAndReplaceValues(
 
         self::assertSame(
             $result,
-            $actualResult
+            $actualResult,
         );
     }
 
diff --git a/Tests/Functional/Helper/TypoScriptHelperTest.php b/Tests/Functional/Helper/TypoScriptHelperTest.php
index 0bc4970..d698121 100644
--- a/Tests/Functional/Helper/TypoScriptHelperTest.php
+++ b/Tests/Functional/Helper/TypoScriptHelperTest.php
@@ -53,7 +53,7 @@ public function hasStdWrapPropertiesWillReturnFalse(array $config, $key, bool $e
     {
         self::assertSame(
             $expected,
-            $this->subject->hasStdWrapProperties($config, $key)
+            $this->subject->hasStdWrapProperties($config, $key),
         );
     }
 
@@ -78,7 +78,7 @@ public function getStdWrapPropertiesWillReturnFalse(array $config, $key, array $
     {
         self::assertSame(
             $expected,
-            $this->subject->getStdWrapProperties($config, $key)
+            $this->subject->getStdWrapProperties($config, $key),
         );
     }
 
@@ -93,8 +93,8 @@ public function findValueOrConfigurationWillReturnEmptyString(): void
                 [
                     20 => 'TEXT',
                 ],
-                10
-            )
+                10,
+            ),
         );
     }
 
@@ -112,8 +112,8 @@ public function findValueOrConfigurationWillReturnBaseNode(): void
                         'value' => 'foo bar',
                     ],
                 ],
-                10
-            )
+                10,
+            ),
         );
     }
 
@@ -133,7 +133,7 @@ public function findValueOrConfigurationWillReturnProperties(): void
                     ],
                 ],
                 '10.',
-            )
+            ),
         );
     }
 
@@ -148,7 +148,7 @@ public function isRegExpEnabledOnBaseNodeWillReturnFalse(): void
                     10 => 'TEXT',
                 ],
                 '10.',
-            )
+            ),
         );
     }
 
@@ -165,8 +165,8 @@ public function isRegExpEnabledWithDisabledRegExWillReturnFalse(): void
                         'enable_regex' => '0',
                     ],
                 ],
-                '10.'
-            )
+                '10.',
+            ),
         );
     }
 
@@ -183,8 +183,8 @@ public function isRegExpEnabledWithoutRegExPropertyWillReturnFalse(): void
                         'wrap' => '<b>|</b>',
                     ],
                 ],
-                '10.'
-            )
+                '10.',
+            ),
         );
     }
 
@@ -201,8 +201,8 @@ public function isRegExpEnabledWithActivatedRegExWillReturnTrue(): void
                         'enable_regex' => '1',
                     ],
                 ],
-                '10.'
-            )
+                '10.',
+            ),
         );
     }
 
@@ -219,8 +219,8 @@ public function hasBaseEntryWillReturnTrue(): void
                         'enable_regex' => '1',
                     ],
                 ],
-                '10.'
-            )
+                '10.',
+            ),
         );
     }
 
@@ -236,8 +236,8 @@ public function hasBaseEntryWillReturnFalse(): void
                         'wrap' => '<b>|</b>',
                     ],
                 ],
-                '10.'
-            )
+                '10.',
+            ),
         );
     }
 
@@ -254,8 +254,8 @@ public function hasReplaceEntryWithBaseNodeWillReturnTrue(): void
                         'wrap' => '<b>|</b>',
                     ],
                 ],
-                '10.'
-            )
+                '10.',
+            ),
         );
     }
 
@@ -271,8 +271,8 @@ public function hasReplaceEntryWithoutBaseNodeWillReturnTrue(): void
                         'wrap' => '<b>|</b>',
                     ],
                 ],
-                '10.'
-            )
+                '10.',
+            ),
         );
     }
 
@@ -289,8 +289,8 @@ public function hasReplaceEntryWillReturnFalse(): void
                         'wrap' => '<b>|</b>',
                     ],
                 ],
-                20
-            )
+                20,
+            ),
         );
     }
 
@@ -317,8 +317,8 @@ public function applyStdWrapProperties(): void
                 'apple',
                 [
                     'wrap' => '<b>|</b>',
-                ]
-            )
+                ],
+            ),
         );
     }
 }
diff --git a/Tests/Functional/Hooks/TypoScriptFrontendControllerHookTest.php b/Tests/Functional/Hooks/TypoScriptFrontendControllerHookTest.php
index 8bd8f7a..ff6dcd8 100644
--- a/Tests/Functional/Hooks/TypoScriptFrontendControllerHookTest.php
+++ b/Tests/Functional/Hooks/TypoScriptFrontendControllerHookTest.php
@@ -33,7 +33,7 @@ protected function setUp(): void
             [
                 'EXT:replacer/Tests/Functional/Fixtures/basic_template.typoscript',
                 'EXT:replacer/Tests/Functional/Fixtures/user_int.typoscript',
-            ]
+            ],
         );
     }
 
@@ -44,7 +44,7 @@ public function frontendRequestUsesReplacerForBasicReplacementOnPageWithoutUserI
     {
         self::assertEquals(
             '<p>I like fruits</p><p>This is MD5 Hash Example: 0800fc577294c34e0b28ad2839435945</p><p>Hello world</p>',
-            $this->getFrontendResponse(1)->getContent()
+            $this->getFrontendResponse(1)->getContent(),
         );
     }
 }
diff --git a/Tests/Functional/Middleware/ReplaceContentMiddlewareTest.php b/Tests/Functional/Middleware/ReplaceContentMiddlewareTest.php
index 5b21e1d..599d994 100644
--- a/Tests/Functional/Middleware/ReplaceContentMiddlewareTest.php
+++ b/Tests/Functional/Middleware/ReplaceContentMiddlewareTest.php
@@ -32,7 +32,7 @@ protected function setUp(): void
             [
                 'EXT:replacer/Tests/Functional/Fixtures/basic_template.typoscript',
                 'EXT:replacer/Tests/Functional/Fixtures/user_int.typoscript',
-            ]
+            ],
         );
     }
 
@@ -43,7 +43,7 @@ public function frontendRequestReplacesContentAsDescribedInTypoScriptOnPageWithU
     {
         self::assertEquals(
             '<p>I like fruits</p><p>This is MD5 Hash Example: 0800fc577294c34e0b28ad2839435945</p><p>Hello world</p>',
-            $this->getFrontendResponse(1)->getContent()
+            $this->getFrontendResponse(1)->getContent(),
         );
     }
 }

From bc61da645b710e3f8aded6aa24a6434264d8c21f Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 10:43:51 +0200
Subject: [PATCH 03/13] [TASK] New TestSuite GitHub Action workflow CI

---
 .github/workflows/ci.yml                   |  65 ++-
 Build/Scripts/runTests.sh                  | 608 +++++++++++++++++++++
 Build/cgl/.php-cs-fixer.dist.php           | 102 ++++
 Build/phpstan/phpstan-baseline.neon        |   7 +
 Build/phpstan/phpstan-typo3-constants.php  |  12 +
 Build/phpstan/phpstan.neon                 |  19 +
 Build/phpunit/FunctionalTests.xml          |  26 +
 Build/phpunit/FunctionalTestsBootstrap.php |  17 +
 Build/phpunit/UnitTests.xml                |  26 +
 Build/phpunit/UnitTestsBootstrap.php       |  97 ++++
 10 files changed, 945 insertions(+), 34 deletions(-)
 create mode 100755 Build/Scripts/runTests.sh
 create mode 100644 Build/cgl/.php-cs-fixer.dist.php
 create mode 100644 Build/phpstan/phpstan-baseline.neon
 create mode 100644 Build/phpstan/phpstan-typo3-constants.php
 create mode 100644 Build/phpstan/phpstan.neon
 create mode 100644 Build/phpunit/FunctionalTests.xml
 create mode 100644 Build/phpunit/FunctionalTestsBootstrap.php
 create mode 100644 Build/phpunit/UnitTests.xml
 create mode 100644 Build/phpunit/UnitTestsBootstrap.php

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ecae596..6efd393 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,53 +1,50 @@
-name: Test replacer for TYPO3 12 LTS
+name: Tests
 
-on:
-  pull_request:
+on: [pull_request]
 
 jobs:
-  CGL:
-    name: Coding Style Check (TYPO3 Community CGL)
+  testing:
+    name: Testing
 
     runs-on: ubuntu-latest
 
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-
-      - name: Composer
-        run: Build/Scripts/runTests.sh -t 12 -p 8.1 -s composerUpdate
+    strategy:
+      fail-fast: true
 
-      - name: Lint PHP
-        run: Build/Scripts/runTests.sh -t 12 -p 8.1 -s lint
+      matrix:
+        php:
+          - '7.4'
+          - '8.0'
+          - '8.1'
+          - '8.2'
 
-      - name: Validate code against CGL
-        run: Build/Scripts/runTests.sh -t 12 -p 8.1 -s cgl -n
+    steps:
+      - name: 'Checkout'
+        uses: actions/checkout@v4
 
-  testsuite:
-    name: PHP Unit and Functional Tests for TYPO3 Version 12 LTS
+      - name: 'Lint PHP'
+        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s lint
 
-    needs: CGL
+      - name: 'Install testing system'
+        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s composerUpdate
 
-    runs-on: ubuntu-latest
+      - name: 'Composer validate'
+        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s composerValidate
 
-    strategy:
-      matrix:
-        php: [ '8.1', '8.2' ]
+      - name: 'Composer normalize'
+        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s composerNormalize -n
 
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
+      - name: 'CGL'
+        run: Build/Scripts/runTests.sh -n -p ${{ matrix.php }} -s cgl
 
-      - name: Composer
-        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -t 12 -s composerUpdate
+      - name: 'Execute unit tests'
+        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s unit
 
-      - name: Lint PHP
-        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s lint
+      - name: 'Execute functional tests'
+        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -d mysql -s functional
 
-      - name: Functional tests with mariadb
+      - name: 'Execute functional tests'
         run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -d mariadb -s functional
 
-      - name: Functional tests with postgres
+      - name: 'Execute functional tests'
         run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -d postgres -s functional
-
-      - name: Functional tests with sqlite
-        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -d sqlite -s functional
diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh
new file mode 100755
index 0000000..21e2373
--- /dev/null
+++ b/Build/Scripts/runTests.sh
@@ -0,0 +1,608 @@
+#!/usr/bin/env bash
+
+#
+# EXT:examples test runner based on docker/podman.
+#
+
+trap 'cleanUp;exit 2' SIGINT
+
+waitFor() {
+    local HOST=${1}
+    local PORT=${2}
+    local TESTCOMMAND="
+        COUNT=0;
+        while ! nc -z ${HOST} ${PORT}; do
+            if [ \"\${COUNT}\" -gt 10 ]; then
+              echo \"Can not connect to ${HOST} port ${PORT}. Aborting.\";
+              exit 1;
+            fi;
+            sleep 1;
+            COUNT=\$((COUNT + 1));
+        done;
+    "
+    ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name wait-for-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${IMAGE_ALPINE} /bin/sh -c "${TESTCOMMAND}"
+    if [[ $? -gt 0 ]]; then
+        kill -SIGINT -$$
+    fi
+}
+
+cleanUp() {
+    ATTACHED_CONTAINERS=$(${CONTAINER_BIN} ps --filter network=${NETWORK} --format='{{.Names}}')
+    for ATTACHED_CONTAINER in ${ATTACHED_CONTAINERS}; do
+        ${CONTAINER_BIN} rm -f ${ATTACHED_CONTAINER} >/dev/null
+    done
+    ${CONTAINER_BIN} network rm ${NETWORK} >/dev/null
+}
+
+cleanCacheFiles() {
+    echo -n "Clean caches ... "
+    rm -rf \
+        .Build/.cache \
+        .php-cs-fixer.cache
+    echo "done"
+}
+
+cleanRenderedDocumentationFiles() {
+    echo -n "Clean rendered documentation files ... "
+    rm -rf \
+        Documentation-GENERATED-temp
+    echo "done"
+}
+
+handleDbmsOptions() {
+    # -a, -d, -i depend on each other. Validate input combinations and set defaults.
+    case ${DBMS} in
+        mariadb)
+            [ -z "${DATABASE_DRIVER}" ] && DATABASE_DRIVER="mysqli"
+            if [ "${DATABASE_DRIVER}" != "mysqli" ] && [ "${DATABASE_DRIVER}" != "pdo_mysql" ]; then
+                echo "Invalid combination -d ${DBMS} -a ${DATABASE_DRIVER}" >&2
+                echo >&2
+                echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
+                exit 1
+            fi
+            [ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10.4"
+            if ! [[ ${DBMS_VERSION} =~ ^(10.4|10.5|10.6|10.7|10.8|10.9|10.10|10.11|11.0|11.1)$ ]]; then
+                echo "Invalid combination -d ${DBMS} -i ${DBMS_VERSION}" >&2
+                echo >&2
+                echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
+                exit 1
+            fi
+            ;;
+        mysql)
+            [ -z "${DATABASE_DRIVER}" ] && DATABASE_DRIVER="mysqli"
+            if [ "${DATABASE_DRIVER}" != "mysqli" ] && [ "${DATABASE_DRIVER}" != "pdo_mysql" ]; then
+                echo "Invalid combination -d ${DBMS} -a ${DATABASE_DRIVER}" >&2
+                echo >&2
+                echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
+                exit 1
+            fi
+            [ -z "${DBMS_VERSION}" ] && DBMS_VERSION="8.0"
+            if ! [[ ${DBMS_VERSION} =~ ^(8.0|8.1|8.2|8.3)$ ]]; then
+                echo "Invalid combination -d ${DBMS} -i ${DBMS_VERSION}" >&2
+                echo >&2
+                echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
+                exit 1
+            fi
+            ;;
+        postgres)
+            if [ -n "${DATABASE_DRIVER}" ]; then
+                echo "Invalid combination -d ${DBMS} -a ${DATABASE_DRIVER}" >&2
+                echo >&2
+                echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
+                exit 1
+            fi
+            [ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10"
+            if ! [[ ${DBMS_VERSION} =~ ^(10|11|12|13|14|15|16)$ ]]; then
+                echo "Invalid combination -d ${DBMS} -i ${DBMS_VERSION}" >&2
+                echo >&2
+                echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
+                exit 1
+            fi
+            ;;
+        sqlite)
+            if [ -n "${DATABASE_DRIVER}" ]; then
+                echo "Invalid combination -d ${DBMS} -a ${DATABASE_DRIVER}" >&2
+                echo >&2
+                echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
+                exit 1
+            fi
+            if [ -n "${DBMS_VERSION}" ]; then
+                echo "Invalid combination -d ${DBMS} -i ${DATABASE_DRIVER}" >&2
+                echo >&2
+                echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
+                exit 1
+            fi
+            ;;
+        *)
+            echo "Invalid option -d ${DBMS}" >&2
+            echo >&2
+            echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
+            exit 1
+            ;;
+    esac
+}
+
+loadHelp() {
+    # Load help text into $HELP
+    read -r -d '' HELP <<EOF
+EXT:examples test runner. Check code styles, lint PHP files and some other details.
+
+Usage: $0 [options] [file]
+
+Options:
+    -s <...>
+        Specifies which test suite to run
+            - cgl: cgl test and fix all php files
+            - clean: Clean temporary files
+            - cleanCache: Clean cache folds for files.
+            - cleanRenderedDocumentation: Clean existing rendered documentation output.
+            - composer: "composer" with all remaining arguments dispatched.
+            - composerNormalize: "composer normalize"
+            - composerUpdate: "composer update", handy if host has no PHP
+            - composerUpdateRector: "composer update", for rector subdirectory
+            - composerValidate: "composer validate"
+            - functional: PHP functional tests
+            - lint: PHP linting
+            - phpstan: PHPStan static analysis
+            - phpstanBaseline: Generate PHPStan baseline
+            - unit: PHP unit tests
+            - rector: Apply Rector rules
+            - renderDocumentation
+            - testRenderDocumentation
+
+    -b <docker|podman>
+        Container environment:
+            - docker
+            - podman
+
+        If not specified, podman will be used if available. Otherwise, docker is used.
+
+    -a <mysqli|pdo_mysql>
+        Only with -s functional|functionalDeprecated
+        Specifies to use another driver, following combinations are available:
+            - mysql
+                - mysqli (default)
+                - pdo_mysql
+            - mariadb
+                - mysqli (default)
+                - pdo_mysql
+
+    -d <sqlite|mariadb|mysql|postgres>
+        Only with -s functional|functionalDeprecated|acceptance|acceptanceComposer|acceptanceInstall
+        Specifies on which DBMS tests are performed
+            - sqlite: (default): use sqlite
+            - mariadb: use mariadb
+            - mysql: use MySQL
+            - postgres: use postgres
+
+    -i version
+        Specify a specific database version
+        With "-d mariadb":
+            - 10.4   short-term, maintained until 2024-06-18 (default)
+            - 10.5   short-term, maintained until 2025-06-24
+            - 10.6   long-term, maintained until 2026-06
+            - 10.7   short-term, no longer maintained
+            - 10.8   short-term, maintained until 2023-05
+            - 10.9   short-term, maintained until 2023-08
+            - 10.10  short-term, maintained until 2023-11
+            - 10.11  long-term, maintained until 2028-02
+            - 11.0   development series
+            - 11.1   short-term development series
+        With "-d mysql":
+            - 8.0   maintained until 2026-04 (default) LTS
+            - 8.1   unmaintained since 2023-10
+            - 8.2   unmaintained since 2024-01
+            - 8.3   maintained until 2024-04
+        With "-d postgres":
+            - 10    unmaintained since 2022-11-10 (default)
+            - 11    unmaintained since 2023-11-09
+            - 12    maintained until 2024-11-14
+            - 13    maintained until 2025-11-13
+            - 14    maintained until 2026-11-12
+            - 15    maintained until 2027-11-11
+            - 16    maintained until 2028-11-09
+
+    -p <7.4|8.0|8.1|8.2|8.3>
+        Specifies the PHP minor version to be used
+            - 7.4: use PHP 7.4
+            - 8.0: use PHP 8.0
+            - 8.1: use PHP 8.1
+            - 8.2: use PHP 8.2
+            - 8.3: use PHP 8.3
+
+    -x
+        Only with -s functional|unit
+        Send information to host instance for test or system under test break points. This is especially
+        useful if a local PhpStorm instance is listening on default xdebug port 9003. A different port
+        can be selected with -y
+
+    -y <port>
+        Send xdebug information to a different port than default 9003 if an IDE like PhpStorm
+        is not listening on default port.
+
+    -n
+        Only with -s cgl, composerNormalize, rector
+        Activate dry-run in CGL check and composer normalize that does not actively change files and only prints broken ones.
+
+    -u
+        Update existing typo3/core-testing-*:latest container images and remove dangling local volumes.
+        New images are published once in a while and only the latest ones are supported by core testing.
+        Use this if weird test errors occur. Also removes obsolete image versions of typo3/core-testing-*.
+
+    -h
+        Show this help.
+
+Examples:
+    # Run unit tests using PHP 8.2
+    ./Build/Scripts/runTests.sh -p 8.2 -s unit
+
+    # Run functional tests using PHP 8.3 and MariaDB 10.6 using pdo_mysql
+    ./Build/Scripts/runTests.sh -p 8.3 -s functional -d mariadb -i 10.6 -a pdo_mysql
+
+    # Run functional tests on postgres with xdebug, php 8.3 and execute a restricted set of tests
+    ./Build/Scripts/runTests.sh -x -p 8.3 -s functional -d postgres -- Tests/Functional/DummyTest.php
+EOF
+}
+
+# Test if docker exists, else exit out with error
+if ! type "docker" >/dev/null 2>&1 && ! type "podman" >/dev/null 2>&1; then
+    echo "This script relies on docker or podman. Please install" >&2
+    exit 1
+fi
+
+# Option defaults
+# @todo Consider to switch from cgl to help as default
+TEST_SUITE="cgl"
+DATABASE_DRIVER=""
+DBMS="sqlite"
+DBMS_VERSION=""
+PHP_VERSION="7.4"
+PHP_XDEBUG_ON=0
+PHP_XDEBUG_PORT=9003
+CGLCHECK_DRY_RUN=0
+CI_PARAMS="${CI_PARAMS:-}"
+DOCS_PARAMS="${DOCS_PARAMS:=--pull always}"
+CONTAINER_BIN=""
+CONTAINER_HOST="host.docker.internal"
+
+# Option parsing updates above default vars
+# Reset in case getopts has been used previously in the shell
+OPTIND=1
+# Array for invalid options
+INVALID_OPTIONS=()
+# Simple option parsing based on getopts (! not getopt)
+while getopts "a:b:d:i:s:p:xy:nhu" OPT; do
+    case ${OPT} in
+        a)
+            DATABASE_DRIVER=${OPTARG}
+            ;;
+        s)
+            TEST_SUITE=${OPTARG}
+            ;;
+        b)
+            if ! [[ ${OPTARG} =~ ^(docker|podman)$ ]]; then
+                INVALID_OPTIONS+=("${OPTARG}")
+            fi
+            CONTAINER_BIN=${OPTARG}
+            ;;
+        d)
+            DBMS=${OPTARG}
+            ;;
+        i)
+            DBMS_VERSION=${OPTARG}
+            ;;
+        p)
+            PHP_VERSION=${OPTARG}
+            if ! [[ ${PHP_VERSION} =~ ^(7.4|8.0|8.1|8.2|8.3)$ ]]; then
+                INVALID_OPTIONS+=("p ${OPTARG}")
+            fi
+            ;;
+        x)
+            PHP_XDEBUG_ON=1
+            ;;
+        y)
+            PHP_XDEBUG_PORT=${OPTARG}
+            ;;
+        n)
+            CGLCHECK_DRY_RUN=1
+            ;;
+        h)
+            loadHelp
+            echo "${HELP}"
+            exit 0
+            ;;
+        u)
+            TEST_SUITE=update
+            ;;
+        \?)
+            INVALID_OPTIONS+=("${OPTARG}")
+            ;;
+        :)
+            INVALID_OPTIONS+=("${OPTARG}")
+            ;;
+    esac
+done
+
+# Exit on invalid options
+if [ ${#INVALID_OPTIONS[@]} -ne 0 ]; then
+    echo "Invalid option(s):" >&2
+    for I in "${INVALID_OPTIONS[@]}"; do
+        echo "-"${I} >&2
+    done
+    echo >&2
+    echo "call \".Build/Scripts/runTests.sh -h\" to display help and valid options"
+    exit 1
+fi
+
+handleDbmsOptions
+
+COMPOSER_ROOT_VERSION="13.0.x-dev"
+HOST_UID=$(id -u)
+USERSET=""
+if [ $(uname) != "Darwin" ]; then
+    USERSET="--user $HOST_UID"
+fi
+
+# Go to the directory this script is located, so everything else is relative
+# to this dir, no matter from where this script is called, then go up two dirs.
+THIS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
+cd "$THIS_SCRIPT_DIR" || exit 1
+cd ../../ || exit 1
+ROOT_DIR="${PWD}"
+
+# Create .cache dir: composer need this.
+mkdir -p .Build/.cache
+mkdir -p .Build/web/typo3temp/var/tests
+
+IMAGE_PREFIX="docker.io/"
+# Non-CI fetches TYPO3 images (php and nodejs) from ghcr.io
+TYPO3_IMAGE_PREFIX="ghcr.io/typo3/"
+CONTAINER_INTERACTIVE="-it --init"
+
+IS_CORE_CI=0
+# ENV var "CI" is set by gitlab-ci. We use it here to distinct 'local' and 'CI' environment.
+if [ "${CI}" == "true" ]; then
+    IS_CORE_CI=1
+    IMAGE_PREFIX=""
+    CONTAINER_INTERACTIVE=""
+fi
+
+# determine default container binary to use: 1. podman 2. docker
+if [[ -z "${CONTAINER_BIN}" ]]; then
+    if type "podman" >/dev/null 2>&1; then
+        CONTAINER_BIN="podman"
+    elif type "docker" >/dev/null 2>&1; then
+        CONTAINER_BIN="docker"
+    fi
+fi
+
+IMAGE_PHP="${TYPO3_IMAGE_PREFIX}core-testing-$(echo "php${PHP_VERSION}" | sed -e 's/\.//'):latest"
+IMAGE_ALPINE="${IMAGE_PREFIX}alpine:3.8"
+IMAGE_MARIADB="docker.io/mariadb:${DBMS_VERSION}"
+IMAGE_MYSQL="docker.io/mysql:${DBMS_VERSION}"
+IMAGE_POSTGRES="docker.io/postgres:${DBMS_VERSION}-alpine"
+IMAGE_DOCS="ghcr.io/typo3-documentation/render-guides:latest"
+
+# Set $1 to first mass argument, this is the optional test file or test directory to execute
+shift $((OPTIND - 1))
+
+SUFFIX=$(echo $RANDOM)
+NETWORK="t3docsexamples-${SUFFIX}"
+${CONTAINER_BIN} network create ${NETWORK} >/dev/null
+
+if [ ${CONTAINER_BIN} = "docker" ]; then
+    # docker needs the add-host for xdebug remote debugging. podman has host.container.internal built in
+    CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} --rm --network ${NETWORK} --add-host "${CONTAINER_HOST}:host-gateway" ${USERSET} -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}"
+    CONTAINER_DOCS_PARAMS="${CONTAINER_INTERACTIVE} ${DOCS_PARAMS} --rm --network ${NETWORK} --add-host "${CONTAINER_HOST}:host-gateway" ${USERSET} -v ${ROOT_DIR}:/project"
+else
+    # podman
+    CONTAINER_HOST="host.containers.internal"
+    CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} ${CI_PARAMS} --rm --network ${NETWORK} -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}"
+    CONTAINER_DOCS_PARAMS="${CONTAINER_INTERACTIVE} ${DOCS_PARAMS} --rm --network ${NETWORK} -v ${ROOT_DIR}:/project"
+fi
+
+if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
+    XDEBUG_MODE="-e XDEBUG_MODE=off"
+    XDEBUG_CONFIG=" "
+else
+    XDEBUG_MODE="-e XDEBUG_MODE=debug -e XDEBUG_TRIGGER=foo"
+    XDEBUG_CONFIG="client_port=${PHP_XDEBUG_PORT} client_host=${CONTAINER_HOST}"
+fi
+
+# Suite execution
+case ${TEST_SUITE} in
+    cgl)
+        if [ "${CGLCHECK_DRY_RUN}" -eq 1 ]; then
+            COMMAND="php -dxdebug.mode=off .Build/bin/php-cs-fixer fix -v --dry-run --diff --config=Build/cgl/.php-cs-fixer.dist.php --using-cache=no ."
+        else
+            COMMAND="php -dxdebug.mode=off .Build/bin/php-cs-fixer fix -v --config=Build/cgl/.php-cs-fixer.dist.php --using-cache=no ."
+        fi
+        ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name cgl-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}"
+        SUITE_EXIT_CODE=$?
+        ;;
+    clean)
+        cleanCacheFiles
+        cleanRenderedDocumentationFiles
+        ;;
+    cleanCache)
+        cleanCacheFiles
+        ;;
+    cleanRenderedDocumentation)
+        cleanRenderedDocumentationFiles
+        ;;
+    composer)
+        COMMAND=(composer "$@")
+        ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}"
+        SUITE_EXIT_CODE=$?
+        ;;
+    composerNormalize)
+        if [ "${CGLCHECK_DRY_RUN}" -eq 1 ]; then
+            COMMAND=(composer normalize -n)
+        else
+            COMMAND=(composer normalize)
+        fi
+        ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}"
+        SUITE_EXIT_CODE=$?
+        ;;
+    composerUpdate)
+        rm -rf .Build/bin/ .Build/typo3 .Build/vendor .Build/Web ./composer.lock
+        cp ${ROOT_DIR}/composer.json ${ROOT_DIR}/composer.json.orig
+        if [ -f "${ROOT_DIR}/composer.json.testing" ]; then
+            cp ${ROOT_DIR}/composer.json ${ROOT_DIR}/composer.json.orig
+        fi
+        COMMAND=(composer require --no-ansi --no-interaction --no-progress)
+        ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}"
+        SUITE_EXIT_CODE=$?
+        cp ${ROOT_DIR}/composer.json ${ROOT_DIR}/composer.json.testing
+        mv ${ROOT_DIR}/composer.json.orig ${ROOT_DIR}/composer.json
+        ;;
+    composerUpdateRector)
+        rm -rf Build/rector/.Build/bin/ Build/rector/.Build/vendor Build/rector/composer.lock
+        cp ${ROOT_DIR}/Build/rector/composer.json ${ROOT_DIR}/Build/rector/composer.json.orig
+        if [ -f "${ROOT_DIR}/Build/rector/composer.json.testing" ]; then
+            cp ${ROOT_DIR}/Build/rector/composer.json ${ROOT_DIR}/Build/rector/composer.json.orig
+        fi
+        COMMAND=(composer require --working-dir=${ROOT_DIR}/Build/rector --no-ansi --no-interaction --no-progress)
+        ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}"
+        SUITE_EXIT_CODE=$?
+        cp ${ROOT_DIR}/Build/rector/composer.json ${ROOT_DIR}/Build/rector/composer.json.testing
+        mv ${ROOT_DIR}/Build/rector/composer.json.orig ${ROOT_DIR}/Build/rector/composer.json
+        ;;
+    composerValidate)
+        COMMAND=(composer validate "$@")
+        ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}"
+        SUITE_EXIT_CODE=$?
+        ;;
+    functional)
+        CONTAINER_PARAMS=""
+        COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml --exclude-group not-${DBMS} ${EXTRA_TEST_OPTIONS} "$@")
+        case ${DBMS} in
+            mariadb)
+                echo "Using driver: ${DATABASE_DRIVER}"
+                ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mariadb-func-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MARIADB} >/dev/null
+                waitFor mariadb-func-${SUFFIX} 3306
+                CONTAINERPARAMS="-e typo3DatabaseDriver=${DATABASE_DRIVER} -e typo3DatabaseName=func_test -e typo3DatabaseUsername=root -e typo3DatabaseHost=mariadb-func-${SUFFIX} -e typo3DatabasePassword=funcp"
+                ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}"
+                SUITE_EXIT_CODE=$?
+                ;;
+            mysql)
+                echo "Using driver: ${DATABASE_DRIVER}"
+                ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mysql-func-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MYSQL} >/dev/null
+                waitFor mysql-func-${SUFFIX} 3306
+                CONTAINERPARAMS="-e typo3DatabaseDriver=${DATABASE_DRIVER} -e typo3DatabaseName=func_test -e typo3DatabaseUsername=root -e typo3DatabaseHost=mysql-func-${SUFFIX} -e typo3DatabasePassword=funcp"
+                ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}"
+                SUITE_EXIT_CODE=$?
+                ;;
+            postgres)
+                ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name postgres-func-${SUFFIX} --network ${NETWORK} -d -e POSTGRES_PASSWORD=funcp -e POSTGRES_USER=funcu --tmpfs /var/lib/postgresql/data:rw,noexec,nosuid ${IMAGE_POSTGRES} >/dev/null
+                waitFor postgres-func-${SUFFIX} 5432
+                CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_pgsql -e typo3DatabaseName=bamboo -e typo3DatabaseUsername=funcu -e typo3DatabaseHost=postgres-func-${SUFFIX} -e typo3DatabasePassword=funcp"
+                ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}"
+                SUITE_EXIT_CODE=$?
+                ;;
+            sqlite)
+                # create sqlite tmpfs mount typo3temp/var/tests/functional-sqlite-dbs/ to avoid permission issues
+                mkdir -p "${ROOT_DIR}/.Build/web/typo3temp/var/tests/functional-sqlite-dbs/"
+                CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_sqlite --tmpfs ${ROOT_DIR}/.Build/web/typo3temp/var/tests/functional-sqlite-dbs/:rw,noexec,nosuid"
+                ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}"
+                SUITE_EXIT_CODE=$?
+                ;;
+        esac
+        ;;
+    lint)
+        COMMAND="find . -name \\*.php ! -path "./.Build/\\*" -print0 | xargs -0 -n1 -P4 php -dxdebug.mode=off -l >/dev/null"
+        ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}"
+        SUITE_EXIT_CODE=$?
+        ;;
+    phpstan)
+        COMMAND="php -dxdebug.mode=off .Build/bin/phpstan --configuration=Build/phpstan/phpstan.neon"
+        ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name phpstan-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}"
+        SUITE_EXIT_CODE=$?
+        ;;
+    phpstanBaseline)
+        COMMAND="php -dxdebug.mode=off .Build/bin/phpstan --configuration=Build/phpstan/phpstan.neon --generate-baseline=Build/phpstan/phpstan-baseline.neon -v"
+        ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name phpstan-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}"
+        SUITE_EXIT_CODE=$?
+        ;;
+    rector)
+        if [ "${CGLCHECK_DRY_RUN}" -eq 1 ]; then
+            COMMAND=(php -dxdebug.mode=off Build/rector/.Build/bin/rector -n --config=Build/rector/rector.php --clear-cache "$@")
+        else
+            COMMAND=(php -dxdebug.mode=off Build/rector/.Build/bin/rector --config=Build/rector/rector.php --clear-cache "$@")
+        fi
+        ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name rector-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}"
+        SUITE_EXIT_CODE=$?
+        ;;
+    renderDocumentation)
+        COMMAND=(--config=Documentation "$@")
+        mkdir -p Documentation-GENERATED-temp
+        ${CONTAINER_BIN} run ${CONTAINER_INTERACTIVE} ${CONTAINER_DOCS_PARAMS} --name render-documentation-${SUFFIX} ${IMAGE_DOCS} "${COMMAND[@]}"
+        SUITE_EXIT_CODE=$?
+        ;;
+    testRenderDocumentation)
+        COMMAND=(--config=Documentation --no-progress --fail-on-log "$@")
+        mkdir -p Documentation-GENERATED-temp
+        ${CONTAINER_BIN} run ${CONTAINER_INTERACTIVE} ${CONTAINER_DOCS_PARAMS} --name render-documentation-test-${SUFFIX} ${IMAGE_DOCS} "${COMMAND[@]}"
+        SUITE_EXIT_CODE=$?
+        ;;
+    unit)
+        COMMAND=(.Build/bin/phpunit -c Build/phpunit/UnitTests.xml ${EXTRA_TEST_OPTIONS} "$@")
+        ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name unit-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${IMAGE_PHP} "${COMMAND[@]}"
+        SUITE_EXIT_CODE=$?
+        ;;
+    update)
+        # pull typo3/core-testing-* versions of those ones that exist locally
+        echo "> pull ${TYPO3_IMAGE_PREFIX}core-testing-* versions of those ones that exist locally"
+        ${CONTAINER_BIN} images "${TYPO3_IMAGE_PREFIX}core-testing-*" --format "{{.Repository}}:{{.Tag}}" | xargs -I {} ${CONTAINER_BIN} pull {}
+        echo ""
+        # remove "dangling" typo3/core-testing-* images (those tagged as <none>)
+        echo "> remove \"dangling\" ${TYPO3_IMAGE_PREFIX}/core-testing-* images (those tagged as <none>)"
+        ${CONTAINER_BIN} images --filter "reference=${TYPO3_IMAGE_PREFIX}/core-testing-*" --filter "dangling=true" --format "{{.ID}}" | xargs -I {} ${CONTAINER_BIN} rmi -f {}
+        echo ""
+        ;;
+    *)
+        loadHelp
+        echo "Invalid -s option argument ${TEST_SUITE}" >&2
+        echo >&2
+        echo "${HELP}" >&2
+        exit 1
+        ;;
+esac
+
+cleanUp
+
+# Print summary
+echo "" >&2
+echo "###########################################################################" >&2
+echo "Result of ${TEST_SUITE}" >&2
+echo "Container runtime: ${CONTAINER_BIN}" >&2
+if [[ ${IS_CORE_CI} -eq 1 ]]; then
+    echo "Environment: CI" >&2
+else
+    echo "Environment: local" >&2
+fi
+echo "PHP: ${PHP_VERSION}" >&2
+echo "TYPO3: ${CORE_VERSION}" >&2
+if [[ ${TEST_SUITE} =~ ^functional$ ]]; then
+    case "${DBMS}" in
+        mariadb|mysql)
+            echo "DBMS: ${DBMS}  version ${DBMS_VERSION}  driver ${DATABASE_DRIVER}" >&2
+            ;;
+        postgres)
+            echo "DBMS: ${DBMS}  version ${DBMS_VERSION}  driver pdo_pgsql" >&2
+            ;;
+        sqlite)
+            echo "DBMS: ${DBMS}  driver pdo_sqlite" >&2
+            ;;
+    esac
+fi
+if [[ ${SUITE_EXIT_CODE} -eq 0 ]]; then
+    echo "SUCCESS" >&2
+else
+    echo "FAILURE" >&2
+fi
+echo "###########################################################################" >&2
+echo "" >&2
+
+# Exit with code of test suite - This script return non-zero if the executed test failed.
+exit $SUITE_EXIT_CODE
diff --git a/Build/cgl/.php-cs-fixer.dist.php b/Build/cgl/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..ee7802f
--- /dev/null
+++ b/Build/cgl/.php-cs-fixer.dist.php
@@ -0,0 +1,102 @@
+<?php
+
+declare(strict_types=1);
+
+use PhpCsFixer\Config;
+use PhpCsFixer\Finder;
+
+/*
+ * This file is part of the package jweiland/replacer.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE file that was distributed with this source code.
+ */
+
+if (PHP_SAPI !== 'cli') {
+    die('This script supports command line usage only. Please check your command.');
+}
+
+$headerComment = <<<COMMENT
+This file is part of the package jweiland/replacer.
+
+For the full copyright and license information, please read the
+LICENSE file that was distributed with this source code.
+COMMENT;
+
+return (new Config())
+    ->setFinder(
+        (new Finder())
+            ->in(__DIR__ . '/../../')
+            ->exclude(__DIR__ . '/../../.Build')
+            ->exclude(__DIR__ . '/../../var')
+    )
+    ->setRiskyAllowed(true)
+    ->setRules([
+        '@DoctrineAnnotation' => true,
+        'header_comment' => [
+            'header' => $headerComment,
+        ],
+        // @todo: Switch to @PER-CS2.0 once php-cs-fixer's todo list is done: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/7247
+        '@PER-CS1.0' => true,
+        'array_indentation' => true,
+        'array_syntax' => ['syntax' => 'short'],
+        'cast_spaces' => ['space' => 'none'],
+        // @todo: Can be dropped once we enable @PER-CS2.0
+        'concat_space' => ['spacing' => 'one'],
+        'declare_equal_normalize' => ['space' => 'none'],
+        'declare_parentheses' => true,
+        'dir_constant' => true,
+        // @todo: Can be dropped once we enable @PER-CS2.0
+        'function_declaration' => [
+            'closure_fn_spacing' => 'none',
+        ],
+        'function_to_constant' => ['functions' => ['get_called_class', 'get_class', 'get_class_this', 'php_sapi_name', 'phpversion', 'pi']],
+        'type_declaration_spaces' => true,
+        'global_namespace_import' => ['import_classes' => false, 'import_constants' => false, 'import_functions' => false],
+        'list_syntax' => ['syntax' => 'short'],
+        // @todo: Can be dropped once we enable @PER-CS2.0
+        'method_argument_space' => true,
+        'modernize_strpos' => true,
+        'modernize_types_casting' => true,
+        'native_function_casing' => true,
+        'no_alias_functions' => true,
+        'no_blank_lines_after_phpdoc' => true,
+        'no_empty_phpdoc' => true,
+        'no_empty_statement' => true,
+        'no_extra_blank_lines' => true,
+        'no_leading_namespace_whitespace' => true,
+        'no_null_property_initialization' => true,
+        'no_short_bool_cast' => true,
+        'no_singleline_whitespace_before_semicolons' => true,
+        'no_superfluous_elseif' => true,
+        'no_trailing_comma_in_singleline' => true,
+        'no_unneeded_control_parentheses' => true,
+        'no_unused_imports' => true,
+        'no_useless_nullsafe_operator' => true,
+        'ordered_imports' => ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha'],
+        'php_unit_construct' => ['assertions' => ['assertEquals', 'assertSame', 'assertNotEquals', 'assertNotSame']],
+        'php_unit_mock_short_will_return' => true,
+        'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
+        'phpdoc_no_access' => true,
+        'phpdoc_no_empty_return' => true,
+        'phpdoc_no_package' => true,
+        'phpdoc_scalar' => true,
+        'phpdoc_trim' => true,
+        'phpdoc_types' => true,
+        'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
+        'return_type_declaration' => ['space_before' => 'none'],
+        'single_quote' => true,
+        'single_space_around_construct' => true,
+        'single_line_comment_style' => ['comment_types' => ['hash']],
+        // @todo: Can be dropped once we enable @PER-CS2.0
+        'single_line_empty_body' => true,
+        'trailing_comma_in_multiline' => ['elements' => ['arguments', 'arrays', 'match', 'parameters']],
+        'whitespace_after_comma_in_array' => ['ensure_single_space' => true],
+        'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false],
+
+        // We need this for documentation!
+        'no_useless_else' => false, // We want to preserve else with comments only
+
+        // Add this rule to convert FQCN to use statements
+        'full_opening_tag' => true,
+    ]);
diff --git a/Build/phpstan/phpstan-baseline.neon b/Build/phpstan/phpstan-baseline.neon
new file mode 100644
index 0000000..dd8ccf1
--- /dev/null
+++ b/Build/phpstan/phpstan-baseline.neon
@@ -0,0 +1,7 @@
+parameters:
+		# Ignore specific errors:
+		ignoreErrors:
+			-
+				message: '#Call to an undefined method .*#'
+			#-
+			#	message: '#^Access to an undefined property TYPO3\\\\CMS\\\\Scheduler\\\\Task\\\\AbstractTask$#'
diff --git a/Build/phpstan/phpstan-typo3-constants.php b/Build/phpstan/phpstan-typo3-constants.php
new file mode 100644
index 0000000..0956596
--- /dev/null
+++ b/Build/phpstan/phpstan-typo3-constants.php
@@ -0,0 +1,12 @@
+<?php
+
+/*
+ * This file is part of the package jweiland/replacer.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE file that was distributed with this source code.
+ */
+
+defined('LF') ?: define('LF', chr(10));
+defined('CR') ?: define('CR', chr(13));
+defined('CRLF') ?: define('CRLF', CR . LF);
diff --git a/Build/phpstan/phpstan.neon b/Build/phpstan/phpstan.neon
new file mode 100644
index 0000000..a43b6a4
--- /dev/null
+++ b/Build/phpstan/phpstan.neon
@@ -0,0 +1,19 @@
+includes:
+	- phpstan-baseline.neon
+parameters:
+	level: 6
+
+	inferPrivatePropertyTypeFromConstructor: true
+	treatPhpDocTypesAsCertain: false
+
+	bootstrapFiles:
+		- phpstan-typo3-constants.php
+
+	paths:
+		- ../../Classes/
+		- ../../Configuration/
+
+	tmpDir: ../../.Build/.cache/phpstan/
+
+	excludePaths:
+		- '../../ext_emconf.php'
diff --git a/Build/phpunit/FunctionalTests.xml b/Build/phpunit/FunctionalTests.xml
new file mode 100644
index 0000000..7c503be
--- /dev/null
+++ b/Build/phpunit/FunctionalTests.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd"
+         backupGlobals="true"
+         beStrictAboutTestsThatDoNotTestAnything="false"
+         bootstrap="FunctionalTestsBootstrap.php"
+         cacheResult="false"
+         colors="true"
+         failOnRisky="true"
+         failOnWarning="true"
+>
+    <testsuites>
+        <testsuite name="Functional tests">
+            <!--
+                This path either needs an adaption in extensions, or an extension's
+                test location path needs to be given to phpunit.
+            -->
+            <directory>../../Tests/Functional/</directory>
+        </testsuite>
+    </testsuites>
+    <php>
+        <ini name="display_errors" value="1"/>
+        <env name="TYPO3_CONTEXT" value="Testing"/>
+    </php>
+</phpunit>
diff --git a/Build/phpunit/FunctionalTestsBootstrap.php b/Build/phpunit/FunctionalTestsBootstrap.php
new file mode 100644
index 0000000..f5ff1c9
--- /dev/null
+++ b/Build/phpunit/FunctionalTestsBootstrap.php
@@ -0,0 +1,17 @@
+<?php
+
+/*
+ * This file is part of the package jweiland/replacer.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE file that was distributed with this source code.
+ */
+
+use TYPO3\TestingFramework\Core\Testbase;
+
+(static function () {
+    $testbase = new Testbase();
+    $testbase->defineOriginalRootPath();
+    $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests');
+    $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient');
+})();
diff --git a/Build/phpunit/UnitTests.xml b/Build/phpunit/UnitTests.xml
new file mode 100644
index 0000000..d1fc4f0
--- /dev/null
+++ b/Build/phpunit/UnitTests.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd"
+         backupGlobals="true"
+         beStrictAboutTestsThatDoNotTestAnything="false"
+         bootstrap="UnitTestsBootstrap.php"
+         cacheResult="false"
+         colors="true"
+         failOnRisky="true"
+         failOnWarning="true"
+>
+    <testsuites>
+        <testsuite name="Unit tests">
+            <!--
+                This path either needs an adaption in extensions, or an extension's
+                test location path needs to be given to phpunit.
+            -->
+            <directory>../../Tests/Unit/</directory>
+        </testsuite>
+    </testsuites>
+    <php>
+        <ini name="display_errors" value="1"/>
+        <env name="TYPO3_CONTEXT" value="Testing"/>
+    </php>
+</phpunit>
diff --git a/Build/phpunit/UnitTestsBootstrap.php b/Build/phpunit/UnitTestsBootstrap.php
new file mode 100644
index 0000000..b4ed0f2
--- /dev/null
+++ b/Build/phpunit/UnitTestsBootstrap.php
@@ -0,0 +1,97 @@
+<?php
+
+/*
+ * This file is part of the package jweiland/replacer.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE file that was distributed with this source code.
+ */
+
+/**
+ * Boilerplate for a unit test phpunit boostrap file.
+ *
+ * This file is loosely maintained within TYPO3 testing-framework, extensions
+ * are encouraged to not use it directly, but to copy it to an own place,
+ * usually in parallel to a UnitTests.xml file.
+ *
+ * This file is defined in UnitTests.xml and called by phpunit
+ * before instantiating the test suites.
+ *
+ * The recommended way to execute the suite is "runTests.sh". See the
+ * according script within TYPO3 core's Build/Scripts directory and
+ * adapt to extensions needs.
+ */
+
+use TYPO3\CMS\Core\Cache\Backend\NullBackend;
+use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
+use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Package\PackageManager;
+use TYPO3\CMS\Core\Package\UnitTestPackageManager;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\SystemEnvironmentBuilder;
+use TYPO3\TestingFramework\Core\Testbase;
+
+(static function () {
+    $testbase = new Testbase();
+
+    // These if's are for core testing (package typo3/cms) only. cms-composer-installer does
+    // not create the autoload-include.php file that sets these env vars and sets composer
+    // mode to true. testing-framework can not be used without composer anyway, so it is safe
+    // to do this here. This way it does not matter if 'bin/phpunit' or 'vendor/phpunit/phpunit/phpunit'
+    // is called to run the tests since the 'relative to entry script' path calculation within
+    // SystemEnvironmentBuilder is not used. However, the binary must be called from the document
+    // root since getWebRoot() uses 'getcwd()'.
+    if (!getenv('TYPO3_PATH_ROOT')) {
+        putenv('TYPO3_PATH_ROOT=' . rtrim($testbase->getWebRoot(), '/'));
+    }
+    if (!getenv('TYPO3_PATH_WEB')) {
+        putenv('TYPO3_PATH_WEB=' . rtrim($testbase->getWebRoot(), '/'));
+    }
+
+    $testbase->defineSitePath();
+
+    // We can use the "typo3/cms-composer-installers" constant "TYPO3_COMPOSER_MODE" to determine composer mode.
+    // This should be always true except for TYPO3 mono repository.
+    $composerMode = defined('TYPO3_COMPOSER_MODE') && TYPO3_COMPOSER_MODE === true;
+
+    // @todo: Remove else branch when dropping support for v12
+    $hasConsolidatedHttpEntryPoint = class_exists(CoreHttpApplication::class);
+    if ($hasConsolidatedHttpEntryPoint) {
+        SystemEnvironmentBuilder::run(0, \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_CLI, $composerMode);
+    } else {
+        $requestType = \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_BE | \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_CLI;
+        SystemEnvironmentBuilder::run(0, $requestType, $composerMode);
+    }
+
+    $testbase->createDirectory(Environment::getPublicPath() . '/typo3conf/ext');
+    $testbase->createDirectory(Environment::getPublicPath() . '/typo3temp/assets');
+    $testbase->createDirectory(Environment::getPublicPath() . '/typo3temp/var/tests');
+    $testbase->createDirectory(Environment::getPublicPath() . '/typo3temp/var/transient');
+
+    // Retrieve an instance of class loader and inject to core bootstrap
+    $classLoader = require $testbase->getPackagesPath() . '/autoload.php';
+    Bootstrap::initializeClassLoader($classLoader);
+
+    // Initialize default TYPO3_CONF_VARS
+    $configurationManager = new ConfigurationManager();
+    $GLOBALS['TYPO3_CONF_VARS'] = $configurationManager->getDefaultConfiguration();
+
+    $cache = new PhpFrontend(
+        'core',
+        new NullBackend('production', []),
+    );
+    $packageManager = Bootstrap::createPackageManager(
+        UnitTestPackageManager::class,
+        Bootstrap::createPackageCache($cache),
+    );
+
+    GeneralUtility::setSingletonInstance(PackageManager::class, $packageManager);
+    ExtensionManagementUtility::setPackageManager($packageManager);
+
+    $testbase->dumpClassLoadingInformation();
+
+    GeneralUtility::purgeInstances();
+})();

From 0ec08f9c9a6395480aa6eddcf967ecfb880d371f Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 10:44:09 +0200
Subject: [PATCH 04/13] [TASK] Updated README badges

---
 README.md | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/README.md b/README.md
index 503cdf7..aa7429f 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,15 @@
 # TYPO3 Extension `replacer`
 
+[![Packagist][packagist-logo-stable]][extension-packagist-url]
+[![Latest Stable Version][extension-build-shield]][extension-ter-url]
+[![License][LICENSE_BADGE]][extension-packagist-url]
+[![Total Downloads][extension-downloads-badge]][extension-packagist-url]
+[![Monthly Downloads][extension-monthly-downloads]][extension-packagist-url]
+[![TYPO3 11.5][TYPO3-shield-11]][TYPO3-11-url]
+[![TYPO3 12.4][TYPO3-shield]][TYPO3-12-url]
+
+![Build Status][extension-ci-shield]
+
 ## What does it do?
 
 Walks through generated HTML Output of TYPO3 and replaces various links with lins from
@@ -20,3 +30,30 @@ composer require jweiland/replacer
 Login into TYPO3 Backend of your project and click on `Extensions` in the left menu.
 Press the `Retrieve/Update` button and search for the extension key `replacer`.
 Import the extension from TER (TYPO3 Extension Repository)
+
+
+<!-- MARKDOWN LINKS & IMAGES -->
+
+[extension-build-shield]: https://poser.pugx.org/jweiland/replacer/v/stable.svg?style=for-the-badge
+
+[extension-ci-shield]: https://github.com/jweiland-net/replacer/actions/workflows/ci.yml/badge.svg
+
+[extension-downloads-badge]: https://poser.pugx.org/jweiland/replacer/d/total.svg?style=for-the-badge
+
+[extension-monthly-downloads]: https://poser.pugx.org/jweiland/replacer/d/monthly?style=for-the-badge
+
+[extension-ter-url]: https://extensions.typo3.org/extension/replacer/
+
+[extension-packagist-url]: https://packagist.org/packages/jweiland/replacer/
+
+[packagist-logo-stable]: https://img.shields.io/badge/--grey.svg?style=for-the-badge&logo=packagist&logoColor=white
+
+[TYPO3-11-url]: https://get.typo3.org/version/11
+
+[TYPO3-12-url]: https://get.typo3.org/version/12
+
+[TYPO3-shield]: https://img.shields.io/badge/TYPO3-12.4-green.svg?style=for-the-badge&logo=typo3
+
+[TYPO3-shield-11]: https://img.shields.io/badge/TYPO3-11.5-green.svg?style=for-the-badge&logo=typo3
+
+[LICENSE_BADGE]: https://img.shields.io/github/license/jweiland-net/replacer?label=license&style=for-the-badge

From 5978f8b954aa8420140f691f0868be05e6ec1ece Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 10:44:59 +0200
Subject: [PATCH 05/13] [TASK] Installed composer normalizer changes

---
 composer.json | 28 ++++++++++++++++++----------
 1 file changed, 18 insertions(+), 10 deletions(-)

diff --git a/composer.json b/composer.json
index 391c24a..ddc00c2 100644
--- a/composer.json
+++ b/composer.json
@@ -1,10 +1,14 @@
 {
 	"name": "jweiland/replacer",
-	"type": "typo3-cms-extension",
 	"description": "Replaces string patterns from the page. You can use it to replace URLs for Content Delivery Network (CDN).",
 	"license": "GPL-2.0-or-later",
-	"keywords": ["typo3", "TYPO3 CMS", "jw", "replacer"],
-	"homepage": "https://jweiland.net",
+	"type": "typo3-cms-extension",
+	"keywords": [
+		"typo3",
+		"TYPO3 CMS",
+		"jw",
+		"replacer"
+	],
 	"authors": [
 		{
 			"name": "Hoja Mustaffa Abdul Latheef",
@@ -17,17 +21,20 @@
 			"role": "Developer"
 		}
 	],
+	"homepage": "https://jweiland.net",
 	"support": {
 		"email": "projects@jweiland.net",
 		"issues": "https://github.com/jweiland-net/replacer/issues",
 		"source": "https://github.com/jweiland-net/replacer"
 	},
 	"require": {
-		"typo3/cms-core": "^11.5.30 || ^12.4.4"
+		"typo3/cms-core": "^11.5.37 || ^12.4.15"
 	},
 	"require-dev": {
+		"ergebnis/composer-normalize": "~2.42.0",
 		"friendsofphp/php-cs-fixer": "^3.14",
 		"phpunit/phpunit": "^9.6",
+		"roave/security-advisories": "dev-latest",
 		"typo3/coding-standards": "^0.6",
 		"typo3/testing-framework": "^7.0.2"
 	},
@@ -50,13 +57,14 @@
 		}
 	},
 	"config": {
-		"sort-packages": true,
-		"vendor-dir": ".Build/vendor",
-		"bin-dir": ".Build/bin",
 		"allow-plugins": {
-			"typo3/cms-composer-installers": true,
-			"typo3/class-alias-loader": true
-		}
+			"ergebnis/composer-normalize": true,
+			"typo3/class-alias-loader": true,
+			"typo3/cms-composer-installers": true
+		},
+		"bin-dir": ".Build/bin",
+		"sort-packages": true,
+		"vendor-dir": ".Build/vendor"
 	},
 	"extra": {
 		"typo3/cms": {

From 4dccb2269d2401f0cfb50eebde8569ca5762eb90 Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 10:45:27 +0200
Subject: [PATCH 06/13] [TASK] Update version changes

---
 ext_emconf.php    | 11 +++++++++--
 ext_localconf.php |  7 +++++++
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/ext_emconf.php b/ext_emconf.php
index 5aabbff..9ed90dd 100644
--- a/ext_emconf.php
+++ b/ext_emconf.php
@@ -1,5 +1,12 @@
 <?php
 
+/*
+ * This file is part of the package jweiland/replacer.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE file that was distributed with this source code.
+ */
+
 $EM_CONF[$_EXTKEY] = [
     'title' => 'Content Replacer',
     'description' => 'Replaces string patterns from the page. You can use it to replace URLs for Content Delivery Network (CDN).',
@@ -8,10 +15,10 @@
     'author_email' => 'projects@jweiland.net',
     'author_company' => 'jweiland.net',
     'state' => 'stable',
-    'version' => '3.0.3',
+    'version' => '3.0.4',
     'constraints' => [
         'depends' => [
-            'typo3' => '11.5.30-12.4.99',
+            'typo3' => '11.5.37-12.4.99',
         ],
         'conflicts' => [],
         'suggests' => [],
diff --git a/ext_localconf.php b/ext_localconf.php
index a7d8c02..8cbfb57 100644
--- a/ext_localconf.php
+++ b/ext_localconf.php
@@ -1,5 +1,12 @@
 <?php
 
+/*
+ * This file is part of the package jweiland/replacer.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE file that was distributed with this source code.
+ */
+
 defined('TYPO3') or die();
 
 use JWeiland\Replacer\Hook\TypoScriptFrontendControllerHook;

From ec7990aed6e12296ae3d68b06f12e6b3443e5cd3 Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 10:46:02 +0200
Subject: [PATCH 07/13] [TASK] Fixed few more lint formating issues

---
 ...CacheableContentGeneratedEventListener.php |  2 +-
 Classes/Helper/ReplacerHelper.php             | 28 +++++++++----------
 .../Hook/TypoScriptFrontendControllerHook.php |  2 +-
 3 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/Classes/EventListener/CacheableContentGeneratedEventListener.php b/Classes/EventListener/CacheableContentGeneratedEventListener.php
index b636190..5abde25 100644
--- a/Classes/EventListener/CacheableContentGeneratedEventListener.php
+++ b/Classes/EventListener/CacheableContentGeneratedEventListener.php
@@ -42,7 +42,7 @@ public function __invoke(AfterCacheableContentIsGeneratedEvent $event): void
         }
 
         $event->getController()->content = $this->replacerHelper->replace(
-            $event->getController()->content
+            $event->getController()->content,
         );
     }
 }
diff --git a/Classes/Helper/ReplacerHelper.php b/Classes/Helper/ReplacerHelper.php
index d0e12fa..ff0f00e 100644
--- a/Classes/Helper/ReplacerHelper.php
+++ b/Classes/Helper/ReplacerHelper.php
@@ -53,7 +53,7 @@ public function replace(string $contentToReplace): string
         $typoScriptFrontendController = $this->getTypoScriptFrontendController();
         $replacerTypoScriptConfiguration = $this->getValueByPath(
             $typoScriptFrontendController->config,
-            'config/tx_replacer.'
+            'config/tx_replacer.',
         );
 
         foreach ($this->getReplaceConfigurationStorage($replacerTypoScriptConfiguration) as $replaceConfiguration) {
@@ -62,14 +62,14 @@ public function replace(string $contentToReplace): string
                 $contentToReplace = (string)preg_replace(
                     $replaceConfiguration->getSearchValue(),
                     $replaceConfiguration->getReplaceValue(),
-                    $contentToReplace
+                    $contentToReplace,
                 );
             } else {
                 // replace using a regular strings as search pattern
                 $contentToReplace = (string)str_replace(
                     $replaceConfiguration->getSearchValue(),
                     $replaceConfiguration->getReplaceValue(),
-                    $contentToReplace
+                    $contentToReplace,
                 );
             }
         }
@@ -86,12 +86,12 @@ protected function getReplaceConfigurationStorage(array $replacerTypoScriptConfi
 
         $searchTypoScriptConfiguration = $this->getConfigurationFor(
             $replacerTypoScriptConfiguration,
-            ConfigurationTypeEnumeration::cast('search')
+            ConfigurationTypeEnumeration::cast('search'),
         );
 
         $replaceTypoScriptConfiguration = $this->getConfigurationFor(
             $replacerTypoScriptConfiguration,
-            ConfigurationTypeEnumeration::cast('replace')
+            ConfigurationTypeEnumeration::cast('replace'),
         );
 
         foreach ($searchTypoScriptConfiguration as $key => $valueOrConfiguration) {
@@ -111,12 +111,12 @@ protected function getReplaceConfigurationStorage(array $replacerTypoScriptConfi
 
             // Add regular expression information
             $replaceConfiguration->setUseRegExp(
-                $this->typoScriptHelper->isRegExpEnabled($searchTypoScriptConfiguration, $key)
+                $this->typoScriptHelper->isRegExpEnabled($searchTypoScriptConfiguration, $key),
             );
 
             // Add search value
             $replaceConfiguration->setSearchValue(
-                $this->getProcessedValue($valueOrConfiguration, $searchTypoScriptConfiguration, $key)
+                $this->getProcessedValue($valueOrConfiguration, $searchTypoScriptConfiguration, $key),
             );
 
             // Add replace value
@@ -124,8 +124,8 @@ protected function getReplaceConfigurationStorage(array $replacerTypoScriptConfi
                 $this->getProcessedValue(
                     $this->typoScriptHelper->findValueOrConfiguration($replaceTypoScriptConfiguration, $key),
                     $replaceTypoScriptConfiguration,
-                    $key
-                )
+                    $key,
+                ),
             );
 
             $replacerConfigurationStorage->attach($replaceConfiguration);
@@ -148,7 +148,7 @@ protected function getProcessedValue($valueOrConfiguration, array $typoScriptCon
             if ($this->typoScriptHelper->hasStdWrapProperties($typoScriptConfiguration, $key)) {
                 $value = $this->typoScriptHelper->applyStdWrapProperties(
                     $valueOrConfiguration,
-                    $this->typoScriptHelper->getStdWrapProperties($typoScriptConfiguration, $key)
+                    $this->typoScriptHelper->getStdWrapProperties($typoScriptConfiguration, $key),
                 );
             } else {
                 $value = $valueOrConfiguration;
@@ -156,7 +156,7 @@ protected function getProcessedValue($valueOrConfiguration, array $typoScriptCon
         } else {
             $value = $this->typoScriptHelper->applyStdWrapProperties(
                 '',
-                $valueOrConfiguration
+                $valueOrConfiguration,
             );
         }
 
@@ -168,13 +168,13 @@ protected function getContentForProcessing(array $processingConfig, string $conf
 
         $contentForProcessing = $this->getValueByPath(
             $processingConfig,
-            'search./' . $configurationSearchPointer
+            'search./' . $configurationSearchPointer,
         );
 
         if (ArrayUtility::isValidPath($processingConfig, 'replace./' . $configurationSearchPointer)) {
             $replaceContentForProcessing = (string)$this->getValueByPath(
                 $processingConfig,
-                'replace./' . $configurationSearchPointer
+                'replace./' . $configurationSearchPointer,
             );
             // if the replace content for processing is `current` then take the value from search array key
             if ($replaceContentForProcessing === 'current') {
@@ -211,7 +211,7 @@ protected function getConfigurationFor(
         try {
             $typoScriptConfiguration = ArrayUtility::getValueByPath(
                 $replacerConfiguration,
-                (string)$configurationType
+                (string)$configurationType,
             );
 
             // We need correct sorting: 10, 10., 20, 30, 30.
diff --git a/Classes/Hook/TypoScriptFrontendControllerHook.php b/Classes/Hook/TypoScriptFrontendControllerHook.php
index d7da351..9564f3d 100644
--- a/Classes/Hook/TypoScriptFrontendControllerHook.php
+++ b/Classes/Hook/TypoScriptFrontendControllerHook.php
@@ -36,7 +36,7 @@ public function contentPostProcAll(array &$params, TypoScriptFrontendController
         }
 
         $typoScriptFrontendController->content = $this->replacerHelper->replace(
-            $typoScriptFrontendController->content
+            $typoScriptFrontendController->content,
         );
     }
 }

From c830ac3d7ebf2727225fbd5097ab7eaae5619a76 Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 10:48:53 +0200
Subject: [PATCH 08/13] [TASK] Added version update and changelog in
 documentation

---
 Documentation/ChangeLog/Index.rst | 8 ++++++++
 Documentation/Settings.cfg        | 2 +-
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/Documentation/ChangeLog/Index.rst b/Documentation/ChangeLog/Index.rst
index c8c6b5d..04517d8 100644
--- a/Documentation/ChangeLog/Index.rst
+++ b/Documentation/ChangeLog/Index.rst
@@ -7,6 +7,14 @@
 ChangeLog
 =========
 
+Version 3.0.4
+=============
+
+*   [TASK] Updated TestSuite for GitHub
+*   [TASK] Formating code lint issues
+*   [BUGFIX] Handled Type Error when cObj is not ready
+
+
 Version 3.0.3
 =============
 
diff --git a/Documentation/Settings.cfg b/Documentation/Settings.cfg
index 4072f37..50c6e39 100644
--- a/Documentation/Settings.cfg
+++ b/Documentation/Settings.cfg
@@ -4,7 +4,7 @@
 [general]
 
 project     = Replacer
-version     = 3.0.3
+version     = 3.0.4
 release     = 3.0
 copyright   = by jweiland.net & Contributors
 

From 1c116fa46f66dcdcd59a52acbb67037f4d7e3ff7 Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 11:21:34 +0200
Subject: [PATCH 09/13] [TASK] Fixed merge conflict rebase

---
 Classes/Middleware/ReplaceContentMiddleware.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Classes/Middleware/ReplaceContentMiddleware.php b/Classes/Middleware/ReplaceContentMiddleware.php
index 841589e..2535987 100644
--- a/Classes/Middleware/ReplaceContentMiddleware.php
+++ b/Classes/Middleware/ReplaceContentMiddleware.php
@@ -39,9 +39,9 @@ public function __construct(ReplacerHelper $replacerHelper)
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
         $response = $handler->handle($request);
-        if (!$this->getTypoScriptFrontendController()->isINTincScript()
-            || $response instanceof NullResponse
+        if ($response instanceof NullResponse
             || $this->getContentObjectRenderer() === null
+            || !$this->getTypoScriptFrontendController()->isINTincScript()
         ) {
             return $response;
         }

From 3816580c15225c7d56e4fadd11babd1dbf0ceeb4 Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 11:04:54 +0200
Subject: [PATCH 10/13] [TASK] Updated GitHub workflow file

---
 .github/workflows/ci.yml | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6efd393..ac6c30e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -37,9 +37,6 @@ jobs:
       - name: 'CGL'
         run: Build/Scripts/runTests.sh -n -p ${{ matrix.php }} -s cgl
 
-      - name: 'Execute unit tests'
-        run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s unit
-
       - name: 'Execute functional tests'
         run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -d mysql -s functional
 

From c46f06d4a952a203ca732b056cd0416fddc4d30f Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 11:07:56 +0200
Subject: [PATCH 11/13] [TASK] Fixes for trailing_comma_in_multiline CGL

---
 Classes/Helper/ReplacerHelper.php              | 2 +-
 Tests/Functional/Helper/ReplacerHelperTest.php | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/Classes/Helper/ReplacerHelper.php b/Classes/Helper/ReplacerHelper.php
index ff0f00e..811587f 100644
--- a/Classes/Helper/ReplacerHelper.php
+++ b/Classes/Helper/ReplacerHelper.php
@@ -206,7 +206,7 @@ protected function processContent(string $content, array $configKey): string
 
     protected function getConfigurationFor(
         array $replacerConfiguration,
-        ConfigurationTypeEnumeration $configurationType
+        ConfigurationTypeEnumeration $configurationType,
     ): array {
         try {
             $typoScriptConfiguration = ArrayUtility::getValueByPath(
diff --git a/Tests/Functional/Helper/ReplacerHelperTest.php b/Tests/Functional/Helper/ReplacerHelperTest.php
index 0ebedfe..ad5558b 100644
--- a/Tests/Functional/Helper/ReplacerHelperTest.php
+++ b/Tests/Functional/Helper/ReplacerHelperTest.php
@@ -109,7 +109,7 @@ public function replaceContentWithValidSearchAndReplaceValues(
         array $search,
         array $replacement,
         string $contentToReplace,
-        string $result
+        string $result,
     ): void {
         $config = [
             'config' => [

From 14a3cc5ab9b6e76f660c79fa903526b03d4ce052 Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 11:15:14 +0200
Subject: [PATCH 12/13] [TASK] Revert trailing_comma_in_multiline fix as
 methods only support from php 8

---
 Classes/Helper/ReplacerHelper.php              | 2 +-
 Tests/Functional/Helper/ReplacerHelperTest.php | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/Classes/Helper/ReplacerHelper.php b/Classes/Helper/ReplacerHelper.php
index 811587f..ff0f00e 100644
--- a/Classes/Helper/ReplacerHelper.php
+++ b/Classes/Helper/ReplacerHelper.php
@@ -206,7 +206,7 @@ protected function processContent(string $content, array $configKey): string
 
     protected function getConfigurationFor(
         array $replacerConfiguration,
-        ConfigurationTypeEnumeration $configurationType,
+        ConfigurationTypeEnumeration $configurationType
     ): array {
         try {
             $typoScriptConfiguration = ArrayUtility::getValueByPath(
diff --git a/Tests/Functional/Helper/ReplacerHelperTest.php b/Tests/Functional/Helper/ReplacerHelperTest.php
index ad5558b..0ebedfe 100644
--- a/Tests/Functional/Helper/ReplacerHelperTest.php
+++ b/Tests/Functional/Helper/ReplacerHelperTest.php
@@ -109,7 +109,7 @@ public function replaceContentWithValidSearchAndReplaceValues(
         array $search,
         array $replacement,
         string $contentToReplace,
-        string $result,
+        string $result
     ): void {
         $config = [
             'config' => [

From 05ca92f6153096833aef9e2b03ff5fda483aca17 Mon Sep 17 00:00:00 2001
From: Hoja Mustaffa Abdul Latheef <hlatheef@jweiland.net>
Date: Mon, 21 Oct 2024 11:16:51 +0200
Subject: [PATCH 13/13] [TASK] Adjust trailing_comma_in_multiline settings

---
 Build/cgl/.php-cs-fixer.dist.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Build/cgl/.php-cs-fixer.dist.php b/Build/cgl/.php-cs-fixer.dist.php
index ee7802f..43584a7 100644
--- a/Build/cgl/.php-cs-fixer.dist.php
+++ b/Build/cgl/.php-cs-fixer.dist.php
@@ -90,7 +90,7 @@
         'single_line_comment_style' => ['comment_types' => ['hash']],
         // @todo: Can be dropped once we enable @PER-CS2.0
         'single_line_empty_body' => true,
-        'trailing_comma_in_multiline' => ['elements' => ['arguments', 'arrays', 'match', 'parameters']],
+        'trailing_comma_in_multiline' => ['elements' => ['arrays']],
         'whitespace_after_comma_in_array' => ['ensure_single_space' => true],
         'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false],