diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..290f6c5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +/.github export-ignore +/Build export-ignore +/Tests export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.gitlab-ci.yml export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4ebdfe --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.cache/ +.idea/ +Build/ +bin/ +Documentation-GENERATED-temp/ +typo3temp/ +var/ +public/ +vendor/ +composer.lock +.php-cs-fixer.cache diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml old mode 100755 new mode 100644 diff --git a/Build/Scripts/additionalTests.sh b/Build/Scripts/additionalTests.sh new file mode 100755 index 0000000..03de71a --- /dev/null +++ b/Build/Scripts/additionalTests.sh @@ -0,0 +1,412 @@ +#!/usr/bin/env bash + +# +# TYPO3 core test runner based on docker or 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} kill ${ATTACHED_CONTAINER} >/dev/null + done + if [ ${CONTAINER_BIN} = "docker" ]; then + ${CONTAINER_BIN} network rm ${NETWORK} >/dev/null + else + ${CONTAINER_BIN} network rm -f ${NETWORK} >/dev/null + fi +} + +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 +} + +cleanBuildFiles() { + echo -n "Clean builds ... " + rm -rf \ + .cache \ + Build/JavaScript \ + Build/node_modules \ + Documentation-GENERATED-temp + echo "done" +} + +cleanTestFiles() { + # test related + echo -n "Clean test related files ... " + rm -rf \ + bin/ \ + Build/phpunit \ + public/ \ + typo3temp/ \ + vendor/ \ + var/ \ + composer.lock + git checkout composer.json + echo "done" +} + +getPhpImageVersion() { + case ${1} in + 8.1) + echo -n "2.12" + ;; + 8.2) + echo -n "1.12" + ;; + 8.3) + echo -n "1.13" + ;; + esac +} + +loadHelp() { + # Load help text into $HELP + read -r -d '' HELP < + Specifies the test suite to run + - buildDocumentation: test build the documentation + - clean: clean up build, cache and testing related files and folders + - composerInstallPackage: install a package with composer + - lintXliff: test XLIFF language files + + -b + Container environment: + - podman (default) + - docker + + -p <8.1|8.2|8.3> + Specifies the PHP minor version to be used + - 8.1: use PHP 8.1 + - 8.2 (default): use PHP 8.2 + - 8.3: use PHP 8.3 + + -q + package to be installed by composer + + -r + parameters used with composer commands + + -h + Show this help. + + -v + Enable verbose script output. Shows variables and docker commands. + +Examples: + # Run install a package with composer + ./Build/Scripts/additionalTests.sh -p 8.2 -s composerInstallPackage "typo3/cms-core:13.0" + + # Test build the documentation + ./Build/Scripts/additionalTests.sh -s buildDocumentation + + # Test XLIFF language files + ./Build/Scripts/additionalTests.sh -s lintXliff +EOF +} + +# Test if docker exists, else exit out with error +if ! type "docker" >/dev/null; then + echo "This script relies on docker. Please install" >&2 + exit 1 +fi + +# Option defaults +TEST_SUITE="unit" +DBMS="sqlite" +DBMS_VERSION="" +PHP_VERSION="8.1" +PHP_XDEBUG_ON=0 +PHP_XDEBUG_PORT=9003 +ACCEPTANCE_HEADLESS=1 +EXTRA_TEST_OPTIONS="" +PHPUNIT_RANDOM="" +CGLCHECK_DRY_RUN="" +DATABASE_DRIVER="" +CHUNKS=0 +THISCHUNK=0 +CONTAINER_BIN="docker" + +SCRIPT_VERBOSE=0 +COMPOSER_PACKAGE="" +COMPOSER_PARAMETER="" + +# 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 ":s:p:q:r:hv" OPT; do + case ${OPT} in + s) + TEST_SUITE=${OPTARG} + ;; + p) + PHP_VERSION=${OPTARG} + if ! [[ ${PHP_VERSION} =~ ^(8.1|8.2|8.3)$ ]]; then + INVALID_OPTIONS+=("${OPTARG}") + fi + ;; + q) + COMPOSER_PACKAGE=${OPTARG} + ;; + r) + COMPOSER_PARAMETER=${OPTARG} + ;; + h) + loadHelp + echo "${HELP}" + exit 0 + ;; + 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 "Use \"./Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 +fi + +handleDbmsOptions + +COMPOSER_ROOT_VERSION="7.0.1" +HOST_UID=$(id -u) +HOST_PID=$(id -g) +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 +CORE_ROOT="${PWD}" + +# Create .cache dir: composer and various npm jobs need this. +mkdir -p .cache +mkdir -p typo3temp/var/tests + +PHPSTAN_CONFIG_FILE="phpstan.local.neon" +IMAGE_PREFIX="docker.io/" +# Non-CI fetches TYPO3 images (php and nodejs) from ghcr.io +TYPO3_IMAGE_PREFIX="ghcr.io/" +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 + PHPSTAN_CONFIG_FILE="phpstan.ci.neon" + # In CI, we need to pull images from docker.io for the registry proxy to kick in. + TYPO3_IMAGE_PREFIX="docker.io/" + IMAGE_PREFIX="" + CONTAINER_INTERACTIVE="" +fi + + +IMAGE_APACHE="${TYPO3_IMAGE_PREFIX}typo3/core-testing-apache24:latest" +IMAGE_PHP="${TYPO3_IMAGE_PREFIX}typo3/core-testing-$(echo "php${PHP_VERSION}" | sed -e 's/\.//'):latest" +IMAGE_NODEJS="${TYPO3_IMAGE_PREFIX}typo3/core-testing-nodejs18:latest" +IMAGE_NODEJS_CHROME="${TYPO3_IMAGE_PREFIX}typo3/core-testing-nodejs18-chrome:latest" +IMAGE_ALPINE="${IMAGE_PREFIX}alpine:3.8" +IMAGE_SELENIUM="${IMAGE_PREFIX}selenium/standalone-chrome:4.11.0-20230801" +IMAGE_REDIS="${IMAGE_PREFIX}redis:4-alpine" +IMAGE_MEMCACHED="${IMAGE_PREFIX}memcached:1.5-alpine" +IMAGE_MARIADB="${IMAGE_PREFIX}mariadb:${DBMS_VERSION}" +IMAGE_MYSQL="${IMAGE_PREFIX}mysql:${DBMS_VERSION}" +IMAGE_POSTGRES="${IMAGE_PREFIX}postgres:${DBMS_VERSION}-alpine" +IMAGE_DOCUMENTATION="ghcr.io/t3docs/render-documentation:v3.0.dev30" +IMAGE_XLIFF="container.registry.gitlab.typo3.org/qa/example-extension:typo3-ci-xliff-lint" + +# Detect arm64 to use seleniarm image. +ARCH=$(uname -m) +if [ ${ARCH} = "arm64" ]; then + IMAGE_SELENIUM="${IMAGE_PREFIX}seleniarm/standalone-chromium:4.1.2-20220227" + echo "Architecture" ${ARCH} "requires" ${IMAGE_SELENIUM} "to run acceptance tests." +fi + +# Set $1 to first mass argument, this is the optional test file or test directory to execute +shift $((OPTIND - 1)) +TEST_FILE=${1} + +SUFFIX=$(echo $RANDOM) +NETWORK="typo3-core-${SUFFIX}" +${CONTAINER_BIN} network create ${NETWORK} >/dev/null + +CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} --rm --network $NETWORK --add-host "host.docker.internal:host-gateway" $USERSET -v ${CORE_ROOT}:${CORE_ROOT}" + +if [ ${PHP_XDEBUG_ON} -eq 0 ]; then + XDEBUG_MODE="-e XDEBUG_MODE=off" + XDEBUG_CONFIG=" " + PHP_FPM_OPTIONS="-d xdebug.mode=off" +else + XDEBUG_MODE="-e XDEBUG_MODE=debug -e XDEBUG_TRIGGER=foo" + XDEBUG_CONFIG="client_port=${PHP_XDEBUG_PORT} client_host=host.docker.internal" + PHP_FPM_OPTIONS="-d xdebug.mode=debug -d xdebug.start_with_request=yes -d xdebug.client_host=host.docker.internal -d xdebug.client_port=${PHP_XDEBUG_PORT} -d memory_limit=256M" +fi +# if host uid is root, like for example on ci we need to set additional php-fpm command line options +if [ "${HOST_UID}" = 0 ]; then + PHP_FPM_OPTIONS+=" --allow-to-run-as-root" +fi + +# Suite execution +case ${TEST_SUITE} in + buildDocumentation) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} -v ${CORE_ROOT}:/project ghcr.io/typo3-documentation/render-guides:latest render Documentation + SUITE_EXIT_CODE=$? + ;; + clean) + cleanBuildFiles + cleanTestFiles + ;; + composerInstallPackage) + COMMAND="[ ${SCRIPT_VERBOSE} -eq 1 ] && set -x; composer require -W -n ${COMPOSER_PARAMETER} ${COMPOSER_PACKAGE};" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-require-package-${SUFFIX} -w ${CORE_ROOT} -e COMPOSER_CACHE_DIR=${CORE_ROOT}/Build/.cache/composer ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + lintXliff) + COMMAND="[ ${SCRIPT_VERBOSE} -eq 1 ] && set -x; xmllint --schema /xliff-core-1.2-strict.xsd --noout *.xlf;" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name lint-xliff-${SUFFIX} -w ${CORE_ROOT}/Resources/Private/Language ${IMAGE_XLIFF} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; +esac + +cleanUp + +# Print summary +echo "" >&2 +echo "###########################################################################" >&2 +echo "Result of ${TEST_SUITE}" >&2 +if [[ ${IS_CORE_CI} -eq 1 ]]; then + echo "Environment: CI" >&2 +else + echo "Environment: local" >&2 +fi +echo "PHP: ${PHP_VERSION}" >&2 +if [[ "${COMPOSER_PACKAGE}" != "" ]]; then + echo "Package: ${COMPOSER_PACKAGE}" >&2 +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/Scripts/runTests.sh b/Build/Scripts/runTests.sh new file mode 100755 index 0000000..b929768 --- /dev/null +++ b/Build/Scripts/runTests.sh @@ -0,0 +1,1147 @@ +#!/usr/bin/env bash + +# +# TYPO3 core test runner based on docker or 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} kill ${ATTACHED_CONTAINER} >/dev/null + done + if [ ${CONTAINER_BIN} = "docker" ]; then + ${CONTAINER_BIN} network rm ${NETWORK} >/dev/null + else + ${CONTAINER_BIN} network rm -f ${NETWORK} >/dev/null + fi +} + +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 +} + +cleanBuildFiles() { + echo -n "Clean builds ... " + rm -rf \ + Build/JavaScript \ + Build/node_modules + echo "done" +} + +cleanCacheFiles() { + echo -n "Clean caches ... " + rm -rf \ + .cache \ + Build/.cache \ + Build/composer/.cache/ \ + .php-cs-fixer.cache + echo "done" +} + +cleanTestFiles() { + # composer distribution test + echo -n "Clean composer distribution test ... " + rm -rf \ + Build/composer/composer.json \ + Build/composer/composer.lock \ + Build/composer/public/index.php \ + Build/composer/public/typo3 \ + Build/composer/public/typo3conf/ext \ + Build/composer/var/ \ + Build/composer/vendor/ + echo "done" + + # test related + echo -n "Clean test related files ... " + rm -rf \ + Build/phpunit/FunctionalTests-Job-*.xml \ + typo3/sysext/core/Tests/AcceptanceTests-Job-* \ + typo3/sysext/core/Tests/Acceptance/Support/_generated \ + typo3temp/var/tests/ + echo "done" +} + +cleanRenderedDocumentationFiles() { + echo -n "Clean rendered documentation files ... " + rm -rf \ + ../../../typo3/sysext/*/Documentation-GENERATED-temp + echo "done" +} + +getPhpImageVersion() { + case ${1} in + 8.1) + echo -n "2.12" + ;; + 8.2) + echo -n "1.12" + ;; + 8.3) + echo -n "1.13" + ;; + esac +} + +loadHelp() { + # Load help text into $HELP + read -r -d '' HELP < + Specifies the test suite to run + - acceptance: main application acceptance tests + - acceptanceComposer: main application acceptance tests + - acceptanceInstall: installation acceptance tests, only with -d mariadb|postgres|sqlite + - buildCss: execute scss to css builder + - buildJavascript: execute typescript to javascript builder + - cgl: test and fix all core php files + - cglGit: test and fix latest committed patch for CGL compliance + - cglHeader: test and fix file header for all core php files + - cglHeaderGit: test and fix latest committed patch for CGL file header compliance + - checkAnnotations: check php code for allowed annotations + - checkBom: check UTF-8 files do not contain BOM + - checkComposer: check composer.json files for version integrity + - checkExceptionCodes: test core for duplicate exception codes + - checkExtensionScannerRst: test all .rst files referenced by extension scanner exist + - checkFilePathLength: test core file paths do not exceed maximum length + - checkGitSubmodule: test core git has no sub modules defined + - checkGruntClean: Verify "grunt build" is clean. Warning: Executes git commands! Usually used in CI only. + - checkIsoDatabase: Verify "updateIsoDatabase.php" does not change anything. + - checkNamespaceIntegrity: Verify namespace integrity in class and test code files are in good shape. + - checkPermissions: test some core files for correct executable bits + - checkRst: test .rst files for integrity + - checkTestClassFinal: check test case classes are final + - checkTestMethodsPrefix: check tests methods do not start with "test" + - clean: clean up build, cache and testing related files and folders + - cleanBuild: clean up build related files and folders + - cleanCache: clean up cache related files and folders + - cleanRenderedDocumentation: clean up rendered documentation files and folders (Documentation-GENERATED-temp) + - cleanTests: clean up test related files and folders + - composer: "composer" command dispatcher, to execute various composer commands + - composerInstall: "composer install" + - composerInstallMax: "composer update", with no platform.php config. + - composerInstallMin: "composer update --prefer-lowest", with platform.php set to PHP version x.x.0. + - composerTestDistribution: "composer update" in Build/composer to verify core dependencies + - composerValidate: "composer validate" + - functional: PHP functional tests + - functionalDeprecated: deprecated PHP functional tests + - lintPhp: PHP linting + - lintScss: SCSS linting + - lintTypescript: TS linting + - lintHtml: HTML linting + - listExceptionCodes: list core exception codes in JSON format + - npm: "npm" command dispatcher, to execute various npm commands directly + - phpstan: phpstan tests + - phpstanGenerateBaseline: regenerate phpstan baseline, handy after phpstan updates + - unit (default): PHP unit tests + - unitDeprecated: deprecated PHP unit tests + - unitJavascript: JavaScript unit tests + - unitRandom: PHP unit tests in random order, add -o to use specific seed + + -b + Container environment: + - podman (default) + - docker + + -a + 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 + 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 + + -c + Only with -s functional|acceptance + Hack functional or acceptance tests into #numberOfChunks pieces and run tests of #chunk. + Example -c 3/13 + + -p <8.1|8.2|8.3> + Specifies the PHP minor version to be used + - 8.1: use PHP 8.1 + - 8.2 (default): use PHP 8.2 + - 8.3: use PHP 8.3 + + -t sets|systemplate + Only with -s acceptance|acceptanceComposer + Specifies which frontend rendering mechanism should be used + - sets: (default): use site sets + - systemplate: use sys_template records + + -e "" (DEPRECATED). + Only with -s functional|functionalDeprecated|unit|unitDeprecated|unitRandom|acceptance + 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 "-d memory_limit=-1 --filter filterByValueRecursiveCorrectlyFiltersArray" to enable verbose output AND filter tests + named "canRetrieveValueWithGP" + + DEPRECATED - pass arguments after the `--` separator directly. For example, instead of + Build/Scripts/runTests.sh -s unit -e "--filter filterByValueRecursiveCorrectlyFiltersArray" + use + Build/Scripts/runTests.sh -s unit -- --filter filterByValueRecursiveCorrectlyFiltersArray + + -g + Only with -s acceptance|acceptanceComposer|acceptanceInstall + Activate selenium grid as local port to watch browser clicking around. Can be surfed using + http://localhost:7900/. A browser tab is opened automatically if xdg-open is installed. + + -x + Only with -s functional|functionalDeprecated|unit|unitDeprecated|unitRandom|acceptance|acceptanceComposer|acceptanceInstall + 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 + Send xdebug information to a different port than default 9003 if an IDE like PhpStorm + is not listening on default port. + + -o + Only with -s unitRandom + Set specific random seed to replay a random run in this order again. The phpunit randomizer + outputs the used seed at the end (in gitlab core testing logs, too). Use that number to + replay the unit tests in that order. + + -n + Only with -s cgl|cglGit|cglHeader|cglHeaderGit + Activate dry-run in CGL check that does not actively change files and only prints broken ones. + + -u + Update existing typo3/core-testing-* container images and remove obsolete dangling image versions. + Use this if weird test errors occur. + + -h + Show this help. + +Examples: + # Run all core unit tests using PHP 8.2 + ./Build/Scripts/runTests.sh + ./Build/Scripts/runTests.sh -s unit + + # Run all core units tests and enable xdebug (have a PhpStorm listening on port 9003!) + ./Build/Scripts/runTests.sh -x + + # Run unit tests in phpunit with xdebug on PHP 8.3 and filter for test filterByValueRecursiveCorrectlyFiltersArray + ./Build/Scripts/runTests.sh -x -p 8.3 -- --filter filterByValueRecursiveCorrectlyFiltersArray + + # Run functional tests in phpunit with a filtered test method name in a specified file + # example will currently execute two tests, both of which start with the search term + ./Build/Scripts/runTests.sh -s functional -- \ + --filter datetimeInstanceCanBePersistedToDatabaseIfTypeIsExplicitlySpecified \ + typo3/sysext/core/Tests/Functional/Database/ConnectionTest.php + + # 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 typo3/sysext/core/Tests/Functional/Authentication + + # Run functional tests on postgres 11 + ./Build/Scripts/runTests.sh -s functional -d postgres -i 11 + + # Run restricted set of application acceptance tests + ./Build/Scripts/runTests.sh -s acceptance typo3/sysext/core/Tests/Acceptance/Application/Login/BackendLoginCest.php:loginButtonMouseOver + + # Run installer tests of a new instance on sqlite + ./Build/Scripts/runTests.sh -s acceptanceInstall -d sqlite + + # Run composer require to require a dependency + ./Build/Scripts/runTests.sh -s composer -- require --dev typo3/testing-framework:dev-main + + # Some composer command examples + ./Build/Scripts/runTests.sh -s composer -- dumpautoload + ./Build/Scripts/runTests.sh -s composer -- info | grep "symfony" + + # Some npm command examples + ./Build/Scripts/runTests.sh -s npm -- audit + ./Build/Scripts/runTests.sh -s npm -- ci + ./Build/Scripts/runTests.sh -s npm -- run build + ./Build/Scripts/runTests.sh -s npm -- run watch:build + ./Build/Scripts/runTests.sh -s npm -- install --save bootstrap@^5.3.2 +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 + +# 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 +CORE_ROOT="${PWD}" + +# Default variables +TEST_SUITE="unit" +DBMS="sqlite" +DBMS_VERSION="" +PHP_VERSION="8.2" +PHP_XDEBUG_ON=0 +PHP_XDEBUG_PORT=9003 +ACCEPTANCE_HEADLESS=1 +ACCEPTANCE_TOPIC="sets" +EXTRA_TEST_OPTIONS="" +PHPUNIT_RANDOM="" +CGLCHECK_DRY_RUN="" +DATABASE_DRIVER="" +CHUNKS=0 +THISCHUNK=0 +CONTAINER_BIN="" +COMPOSER_ROOT_VERSION="13.2.x-dev" +PHPSTAN_CONFIG_FILE="phpstan.local.neon" +CONTAINER_INTERACTIVE="-it --init" +HOST_UID=$(id -u) +HOST_PID=$(id -g) +USERSET="" +SUFFIX=$(echo $RANDOM) +NETWORK="typo3-core-${SUFFIX}" +CI_PARAMS="${CI_PARAMS:-}" +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:s:c:d:i:t:p:e:xy:o:nhug" OPT; do + case ${OPT} in + s) + TEST_SUITE=${OPTARG} + ;; + b) + if ! [[ ${OPTARG} =~ ^(docker|podman)$ ]]; then + INVALID_OPTIONS+=("${OPTARG}") + fi + CONTAINER_BIN=${OPTARG} + ;; + a) + DATABASE_DRIVER=${OPTARG} + ;; + c) + if ! [[ ${OPTARG} =~ ^([0-9]+\/[0-9]+)$ ]]; then + INVALID_OPTIONS+=("${OPTARG}") + else + # Split "2/13" - run chunk 2 of 13 chunks + THISCHUNK=$(echo "${OPTARG}" | cut -d '/' -f1) + CHUNKS=$(echo "${OPTARG}" | cut -d '/' -f2) + fi + ;; + d) + DBMS=${OPTARG} + ;; + i) + DBMS_VERSION=${OPTARG} + ;; + p) + PHP_VERSION=${OPTARG} + if ! [[ ${PHP_VERSION} =~ ^(8.1|8.2|8.3)$ ]]; then + INVALID_OPTIONS+=("${OPTARG}") + fi + ;; + e) + EXTRA_TEST_OPTIONS=${OPTARG} + ;; + g) + ACCEPTANCE_HEADLESS=0 + ;; + t) + ACCEPTANCE_TOPIC=${OPTARG} + ;; + x) + PHP_XDEBUG_ON=1 + ;; + y) + PHP_XDEBUG_PORT=${OPTARG} + ;; + o) + PHPUNIT_RANDOM="--random-order-seed=${OPTARG}" + ;; + n) + CGLCHECK_DRY_RUN="-n" + ;; + 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 "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 +fi + +handleDbmsOptions + +# ENV var "CI" is set by gitlab-ci. Use it to force some CI details. +if [ "${CI}" == "true" ]; then + PHPSTAN_CONFIG_FILE="phpstan.ci.neon" + 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 + +if [ $(uname) != "Darwin" ] && [ ${CONTAINER_BIN} = "docker" ]; then + # Run docker jobs as current user to prevent permission issues. Not needed with podman. + USERSET="--user $HOST_UID" +fi + +if ! type ${CONTAINER_BIN} >/dev/null 2>&1; then + echo "Selected container environment \"${CONTAINER_BIN}\" not found. Please install or use -b option to select one." >&2 + exit 1 +fi + +IMAGE_APACHE="ghcr.io/typo3/core-testing-apache24:1.3" +IMAGE_PHP="ghcr.io/typo3/core-testing-$(echo "php${PHP_VERSION}" | sed -e 's/\.//'):$(getPhpImageVersion $PHP_VERSION)" + +IMAGE_NODEJS="ghcr.io/typo3/core-testing-nodejs22:1.0" +IMAGE_NODEJS_CHROME="ghcr.io/typo3/core-testing-nodejs22-chrome:1.0" +IMAGE_ALPINE="docker.io/alpine:3.8" +IMAGE_SELENIUM="docker.io/selenium/standalone-chrome:4.11.0-20230801" +IMAGE_REDIS="docker.io/redis:4-alpine" +IMAGE_MEMCACHED="docker.io/memcached:1.5-alpine" +IMAGE_MARIADB="docker.io/mariadb:${DBMS_VERSION}" +IMAGE_MYSQL="docker.io/mysql:${DBMS_VERSION}" +IMAGE_POSTGRES="docker.io/postgres:${DBMS_VERSION}-alpine" + +# Detect arm64 to use seleniarm image. +ARCH=$(uname -m) +if [ ${ARCH} = "arm64" ]; then + IMAGE_SELENIUM="docker.io/seleniarm/standalone-chromium:4.10.0-20230615" +fi + +# Remove handled options and leaving the rest in the line, so it can be passed raw to commands +shift $((OPTIND - 1)) + +# Create .cache dir: composer and various npm jobs need this. +mkdir -p .cache +mkdir -p typo3temp/var/tests + +${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 ${CORE_ROOT}:${CORE_ROOT} -w ${CORE_ROOT}" +else + # podman + CONTAINER_HOST="host.containers.internal" + CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} ${CI_PARAMS} --rm --network ${NETWORK} -v ${CORE_ROOT}:${CORE_ROOT} -w ${CORE_ROOT}" +fi + +if [ ${PHP_XDEBUG_ON} -eq 0 ]; then + XDEBUG_MODE="-e XDEBUG_MODE=off" + XDEBUG_CONFIG=" " + PHP_FPM_OPTIONS="-d xdebug.mode=off" +else + XDEBUG_MODE="-e XDEBUG_MODE=debug -e XDEBUG_TRIGGER=foo" + XDEBUG_CONFIG="client_port=${PHP_XDEBUG_PORT} client_host=${CONTAINER_HOST}" + PHP_FPM_OPTIONS="-d xdebug.mode=debug -d xdebug.start_with_request=yes -d xdebug.client_host=${CONTAINER_HOST} -d xdebug.client_port=${PHP_XDEBUG_PORT} -d memory_limit=256M" +fi + +# Suite execution +case ${TEST_SUITE} in + acceptance) + CODECEPION_ENV="--env ci,classic,${ACCEPTANCE_TOPIC}" + if [ "${ACCEPTANCE_HEADLESS}" -eq 1 ]; then + CODECEPION_ENV="--env ci,classic,headless,${ACCEPTANCE_TOPIC}" + fi + if [ "${CHUNKS}" -gt 0 ]; then + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name ac-splitter-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/splitAcceptanceTests.php -v ${CHUNKS} + COMMAND=(bin/codecept run Application -d -g AcceptanceTests-Job-${THISCHUNK} -c typo3/sysext/core/Tests/codeception.yml ${EXTRA_TEST_OPTIONS} ${CODECEPION_ENV} "$@" --html reports.html) + else + COMMAND=(bin/codecept run Application -d -c typo3/sysext/core/Tests/codeception.yml ${EXTRA_TEST_OPTIONS} ${CODECEPION_ENV} "$@" --html reports.html) + fi + SELENIUM_GRID="" + if [ "${ACCEPTANCE_HEADLESS}" -eq 0 ]; then + SELENIUM_GRID="-p 7900:7900 -e SE_VNC_NO_PASSWORD=1 -e VNC_NO_PASSWORD=1" + fi + rm -rf "${CORE_ROOT}/typo3temp/var/tests/acceptance" "${CORE_ROOT}/typo3temp/var/tests/AcceptanceReports" + mkdir -p "${CORE_ROOT}/typo3temp/var/tests/acceptance" + APACHE_OPTIONS="-e APACHE_RUN_USER=#${HOST_UID} -e APACHE_RUN_SERVERNAME=web -e APACHE_RUN_GROUP=#${HOST_PID} -e APACHE_RUN_DOCROOT=${CORE_ROOT}/typo3temp/var/tests/acceptance -e PHPFPM_HOST=phpfpm -e PHPFPM_PORT=9000" + ${CONTAINER_BIN} run --rm ${CI_PARAMS} -d ${SELENIUM_GRID} --name ac-chrome-${SUFFIX} --network ${NETWORK} --network-alias chrome --tmpfs /dev/shm:rw,nosuid,nodev,noexec ${IMAGE_SELENIUM} >/dev/null + if [ ${CONTAINER_BIN} = "docker" ]; then + ${CONTAINER_BIN} run --rm -d --name ac-phpfpm-${SUFFIX} --network ${NETWORK} --network-alias phpfpm --add-host "${CONTAINER_HOST}:host-gateway" ${USERSET} -e PHPFPM_USER=${HOST_UID} -e PHPFPM_GROUP=${HOST_PID} -v ${CORE_ROOT}:${CORE_ROOT} ${IMAGE_PHP} php-fpm ${PHP_FPM_OPTIONS} >/dev/null + ${CONTAINER_BIN} run --rm -d --name ac-web-${SUFFIX} --network ${NETWORK} --network-alias web --add-host "${CONTAINER_HOST}:host-gateway" -v ${CORE_ROOT}:${CORE_ROOT} ${APACHE_OPTIONS} ${IMAGE_APACHE} >/dev/null + else + ${CONTAINER_BIN} run --rm ${CI_PARAMS} -d --name ac-phpfpm-${SUFFIX} --network ${NETWORK} --network-alias phpfpm ${USERSET} -e PHPFPM_USER=0 -e PHPFPM_GROUP=0 -v ${CORE_ROOT}:${CORE_ROOT} ${IMAGE_PHP} php-fpm -R ${PHP_FPM_OPTIONS} >/dev/null + ${CONTAINER_BIN} run --rm ${CI_PARAMS} -d --name ac-web-${SUFFIX} --network ${NETWORK} --network-alias web -v ${CORE_ROOT}:${CORE_ROOT} ${APACHE_OPTIONS} ${IMAGE_APACHE} >/dev/null + fi + waitFor chrome 4444 + if [ "${ACCEPTANCE_HEADLESS}" -eq 0 ]; then + waitFor chrome 7900 + fi + waitFor web 80 + if [ "${ACCEPTANCE_HEADLESS}" -eq 0 ] && type "xdg-open" >/dev/null; then + xdg-open http://localhost:7900/?autoconnect=1 >/dev/null + elif [ "${ACCEPTANCE_HEADLESS}" -eq 0 ] && type "open" >/dev/null; then + open http://localhost:7900/?autoconnect=1 >/dev/null + fi + case ${DBMS} in + mariadb) + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mariadb-ac-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MARIADB} >/dev/null + waitFor mariadb-ac-${SUFFIX} 3306 + CONTAINERPARAMS="-e typo3DatabaseName=func_test -e typo3DatabaseUsername=root -e typo3DatabasePassword=funcp -e typo3DatabaseHost=mariadb-ac-${SUFFIX}" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name ac-mariadb ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + mysql) + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mysql-ac-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MYSQL} >/dev/null + waitFor mysql-ac-${SUFFIX} 3306 + CONTAINERPARAMS="-e typo3DatabaseName=func_test -e typo3DatabaseUsername=root -e typo3DatabasePassword=funcp -e typo3DatabaseHost=mysql-ac-${SUFFIX}" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name ac-mysql ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + postgres) + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name postgres-ac-${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-ac-${SUFFIX} 5432 + CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_pgsql -e typo3DatabaseName=func_test -e typo3DatabaseUsername=funcu -e typo3DatabasePassword=funcp -e typo3DatabaseHost=postgres-ac-${SUFFIX}" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name ac-postgres ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + sqlite) + rm -rf "${CORE_ROOT}/typo3temp/var/tests/acceptance-sqlite-dbs/" + mkdir -p "${CORE_ROOT}/typo3temp/var/tests/acceptance-sqlite-dbs/" + CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_sqlite" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name ac-sqlite ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + esac + ;; + acceptanceComposer) + rm -rf "${CORE_ROOT}/typo3temp/var/tests/acceptance-composer" "${CORE_ROOT}/typo3temp/var/tests/AcceptanceReports" + + PREPAREPARAMS="" + TESTPARAMS="" + case ${DBMS} in + mariadb) + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mariadb-ac-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=acp -e MYSQL_DATABASE=ac_test --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MARIADB} >/dev/null + waitFor mariadb-ac-${SUFFIX} 3306 + PREPAREPARAMS="-e TYPO3_DB_DRIVER=mysqli -e TYPO3_DB_DBNAME=ac_test -e TYPO3_DB_USERNAME=root -e TYPO3_DB_PASSWORD=acp -e TYPO3_DB_HOST=mariadb-ac-${SUFFIX} -e TYPO3_DB_PORT=3306" + TESTPARAMS="-e typo3DatabaseName=ac_test -e typo3DatabaseUsername=root -e typo3DatabasePassword=funcp -e typo3DatabaseHost=mariadb-ac-${SUFFIX}" + ;; + mysql) + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mysql-ac-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=acp -e MYSQL_DATABASE=ac_test --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MYSQL} >/dev/null + waitFor mysql-ac-${SUFFIX} 3306 + PREPAREPARAMS="-e TYPO3_DB_DRIVER=mysqli -e TYPO3_DB_DBNAME=ac_test -e TYPO3_DB_USERNAME=root -e TYPO3_DB_PASSWORD=acp -e TYPO3_DB_HOST=mysql-ac-${SUFFIX} -e TYPO3_DB_PORT=3306" + TESTPARAMS="-e typo3DatabaseName=ac_test -e typo3DatabaseUsername=root -e typo3DatabasePassword=funcp -e typo3DatabaseHost=mysql-ac-${SUFFIX}" + ;; + postgres) + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name postgres-ac-${SUFFIX} --network ${NETWORK} -d -e POSTGRES_DB=ac_test -e POSTGRES_PASSWORD=acp -e POSTGRES_USER=ac_test --tmpfs /var/lib/postgresql/data:rw,noexec,nosuid ${IMAGE_POSTGRES} >/dev/null + waitFor postgres-ac-${SUFFIX} 5432 + PREPAREPARAMS="-e TYPO3_DB_DRIVER=postgres -e TYPO3_DB_DBNAME=ac_test -e TYPO3_DB_USERNAME=ac_test -e TYPO3_DB_PASSWORD=acp -e TYPO3_DB_HOST=postgres-ac-${SUFFIX} -e TYPO3_DB_PORT=5432" + TESTPARAMS="-e typo3DatabaseDriver=pdo_pgsql -e typo3DatabaseName=ac_test -e typo3DatabaseUsername=ac_test -e typo3DatabasePassword=acp -e typo3DatabaseHost=postgres-ac-${SUFFIX}" + ;; + sqlite) + PREPAREPARAMS="-e TYPO3_DB_DRIVER=sqlite" + TESTPARAMS="-e typo3DatabaseDriver=pdo_sqlite" + ;; + esac + + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name acceptance-prepare ${XDEBUG_MODE} -e COMPOSER_CACHE_DIR=${CORE_ROOT}/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${PREPAREPARAMS} ${IMAGE_PHP} "${CORE_ROOT}/Build/Scripts/setupAcceptanceComposer.sh" "typo3temp/var/tests/acceptance-composer" sqlite "" "${ACCEPTANCE_TOPIC}" + SUITE_EXIT_CODE=$? + if [[ ${SUITE_EXIT_CODE} -eq 0 ]]; then + CODECEPION_ENV="--env ci,composer,${ACCEPTANCE_TOPIC}" + if [ "${ACCEPTANCE_HEADLESS}" -eq 1 ]; then + CODECEPION_ENV="--env ci,composer,headless,${ACCEPTANCE_TOPIC}" + fi + if [ "${CHUNKS}" -gt 0 ]; then + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name ac-splitter-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/splitAcceptanceTests.php -v ${CHUNKS} + COMMAND=(bin/codecept run Application -d -g AcceptanceTests-Job-${THISCHUNK} -c typo3/sysext/core/Tests/codeception.yml ${EXTRA_TEST_OPTIONS} ${CODECEPION_ENV} "$@" --html reports.html) + else + COMMAND=(bin/codecept run Application -d -c typo3/sysext/core/Tests/codeception.yml ${EXTRA_TEST_OPTIONS} ${CODECEPION_ENV} "$@" --html reports.html) + fi + SELENIUM_GRID="" + if [ "${ACCEPTANCE_HEADLESS}" -eq 0 ]; then + SELENIUM_GRID="-p 7900:7900 -e SE_VNC_NO_PASSWORD=1 -e VNC_NO_PASSWORD=1" + fi + APACHE_OPTIONS="-e APACHE_RUN_USER=#${HOST_UID} -e APACHE_RUN_SERVERNAME=web -e APACHE_RUN_GROUP=#${HOST_PID} -e APACHE_RUN_DOCROOT=${CORE_ROOT}/typo3temp/var/tests/acceptance-composer/public -e PHPFPM_HOST=phpfpm -e PHPFPM_PORT=9000" + ${CONTAINER_BIN} run --rm ${CI_PARAMS} -d ${SELENIUM_GRID} --name ac-chrome-${SUFFIX} --network ${NETWORK} --network-alias chrome --tmpfs /dev/shm:rw,nosuid,nodev,noexec ${IMAGE_SELENIUM} >/dev/null + if [ ${CONTAINER_BIN} = "docker" ]; then + ${CONTAINER_BIN} run --rm -d --name ac-phpfpm-${SUFFIX} --network ${NETWORK} --network-alias phpfpm --add-host "${CONTAINER_HOST}:host-gateway" ${USERSET} -e PHPFPM_USER=${HOST_UID} -e PHPFPM_GROUP=${HOST_PID} -v ${CORE_ROOT}:${CORE_ROOT} ${IMAGE_PHP} php-fpm ${PHP_FPM_OPTIONS} >/dev/null + ${CONTAINER_BIN} run --rm -d --name ac-web-${SUFFIX} --network ${NETWORK} --network-alias web --add-host "${CONTAINER_HOST}:host-gateway" -v ${CORE_ROOT}:${CORE_ROOT} ${APACHE_OPTIONS} ${IMAGE_APACHE} >/dev/null + else + ${CONTAINER_BIN} run --rm ${CI_PARAMS} -d --name ac-phpfpm-${SUFFIX} --network ${NETWORK} --network-alias phpfpm ${USERSET} -e PHPFPM_USER=0 -e PHPFPM_GROUP=0 -v ${CORE_ROOT}:${CORE_ROOT} ${IMAGE_PHP} php-fpm -R ${PHP_FPM_OPTIONS} >/dev/null + ${CONTAINER_BIN} run --rm ${CI_PARAMS} -d --name ac-web-${SUFFIX} --network ${NETWORK} --network-alias web -v ${CORE_ROOT}:${CORE_ROOT} ${APACHE_OPTIONS} ${IMAGE_APACHE} >/dev/null + fi + waitFor chrome 4444 + if [ "${ACCEPTANCE_HEADLESS}" -eq 0 ]; then + waitFor chrome 7900 + fi + waitFor web 80 + if [ "${ACCEPTANCE_HEADLESS}" -eq 0 ] && type "xdg-open" >/dev/null; then + xdg-open http://localhost:7900/?autoconnect=1 >/dev/null + elif [ "${ACCEPTANCE_HEADLESS}" -eq 0 ] && type "open" >/dev/null; then + open http://localhost:7900/?autoconnect=1 >/dev/null + fi + + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name ac-${DBMS}-composer ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${TESTPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + fi + ;; + acceptanceInstall) + SELENIUM_GRID="" + if [ "${ACCEPTANCE_HEADLESS}" -eq 0 ]; then + SELENIUM_GRID="-p 7900:7900 -e SE_VNC_NO_PASSWORD=1 -e VNC_NO_PASSWORD=1" + fi + rm -rf "${CORE_ROOT}/typo3temp/var/tests/acceptance" "${CORE_ROOT}/typo3temp/var/tests/AcceptanceReports" + mkdir -p "${CORE_ROOT}/typo3temp/var/tests/acceptance" + APACHE_OPTIONS="-e APACHE_RUN_USER=#${HOST_UID} -e APACHE_RUN_SERVERNAME=web -e APACHE_RUN_GROUP=#${HOST_PID} -e APACHE_RUN_DOCROOT=${CORE_ROOT}/typo3temp/var/tests/acceptance -e PHPFPM_HOST=phpfpm -e PHPFPM_PORT=9000" + ${CONTAINER_BIN} run --rm ${CI_PARAMS} -d ${SELENIUM_GRID} --name ac-install-chrome-${SUFFIX} --network ${NETWORK} --network-alias chrome --tmpfs /dev/shm:rw,nosuid,nodev,noexec ${IMAGE_SELENIUM} >/dev/null + if [ ${CONTAINER_BIN} = "docker" ]; then + ${CONTAINER_BIN} run --rm -d --name ac-install-phpfpm-${SUFFIX} --network ${NETWORK} --network-alias phpfpm --add-host "${CONTAINER_HOST}:host-gateway" ${USERSET} -e PHPFPM_USER=${HOST_UID} -e PHPFPM_GROUP=${HOST_PID} -v ${CORE_ROOT}:${CORE_ROOT} ${IMAGE_PHP} php-fpm ${PHP_FPM_OPTIONS} >/dev/null + ${CONTAINER_BIN} run --rm -d --name ac-install-web-${SUFFIX} --network ${NETWORK} --network-alias web --add-host "${CONTAINER_HOST}:host-gateway" -v ${CORE_ROOT}:${CORE_ROOT} ${APACHE_OPTIONS} ${IMAGE_APACHE} >/dev/null + else + ${CONTAINER_BIN} run --rm ${CI_PARAMS} -d --name ac-install-phpfpm-${SUFFIX} --network ${NETWORK} --network-alias phpfpm ${USERSET} -e PHPFPM_USER=0 -e PHPFPM_GROUP=0 -v ${CORE_ROOT}:${CORE_ROOT} ${IMAGE_PHP} php-fpm -R ${PHP_FPM_OPTIONS} >/dev/null + ${CONTAINER_BIN} run --rm ${CI_PARAMS} -d --name ac-install-web-${SUFFIX} --network ${NETWORK} --network-alias web -v ${CORE_ROOT}:${CORE_ROOT} ${APACHE_OPTIONS} ${IMAGE_APACHE} >/dev/null + fi + waitFor chrome 4444 + if [ "${ACCEPTANCE_HEADLESS}" -eq 0 ]; then + waitFor chrome 7900 + fi + waitFor web 80 + if [ "${ACCEPTANCE_HEADLESS}" -eq 0 ] && type "xdg-open" >/dev/null; then + xdg-open http://localhost:7900/?autoconnect=1 >/dev/null + elif [ "${ACCEPTANCE_HEADLESS}" -eq 0 ] && type "open" >/dev/null; then + open http://localhost:7900/?autoconnect=1 >/dev/null + fi + case ${DBMS} in + mariadb) + CODECEPION_ENV="--env ci,mysql" + if [ "${ACCEPTANCE_HEADLESS}" -eq 1 ]; then + CODECEPION_ENV="--env ci,mysql,headless" + fi + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mariadb-ac-install-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MARIADB} >/dev/null + waitFor mariadb-ac-install-${SUFFIX} 3306 + CONTAINERPARAMS="-e typo3InstallMysqlDatabaseName=func_test -e typo3InstallMysqlDatabaseUsername=root -e typo3InstallMysqlDatabasePassword=funcp -e typo3InstallMysqlDatabaseHost=mariadb-ac-install-${SUFFIX}" + COMMAND="bin/codecept run Install -d -c typo3/sysext/core/Tests/codeception.yml ${EXTRA_TEST_OPTIONS} ${CODECEPION_ENV} --html reports.html" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name ac-install-mariadb ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} ${COMMAND} + SUITE_EXIT_CODE=$? + ;; + mysql) + CODECEPION_ENV="--env ci,mysql" + if [ "${ACCEPTANCE_HEADLESS}" -eq 1 ]; then + CODECEPION_ENV="--env ci,mysql,headless" + fi + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mysql-ac-install-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MYSQL} >/dev/null + waitFor mysql-ac-install-${SUFFIX} 3306 + CONTAINERPARAMS="-e typo3InstallMysqlDatabaseName=func_test -e typo3InstallMysqlDatabaseUsername=root -e typo3InstallMysqlDatabasePassword=funcp -e typo3InstallMysqlDatabaseHost=mysql-ac-install-${SUFFIX}" + COMMAND="bin/codecept run Install -d -c typo3/sysext/core/Tests/codeception.yml ${EXTRA_TEST_OPTIONS} ${CODECEPION_ENV} --html reports.html" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name ac-install-mysql ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} ${COMMAND} + SUITE_EXIT_CODE=$? + ;; + postgres) + CODECEPION_ENV="--env ci,postgresql" + if [ "${ACCEPTANCE_HEADLESS}" -eq 1 ]; then + CODECEPION_ENV="--env ci,postgresql,headless" + fi + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name postgres-ac-install-${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-ac-install-${SUFFIX} 5432 + CONTAINERPARAMS="-e typo3InstallPostgresqlDatabasePort=5432 -e typo3InstallPostgresqlDatabaseName=${USER} -e typo3InstallPostgresqlDatabaseHost=postgres-ac-install-${SUFFIX} -e typo3InstallPostgresqlDatabaseUsername=funcu -e typo3InstallPostgresqlDatabasePassword=funcp" + COMMAND="bin/codecept run Install -d -c typo3/sysext/core/Tests/codeception.yml ${EXTRA_TEST_OPTIONS} ${CODECEPION_ENV} --html reports.html" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name ac-install-postgres ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} ${COMMAND} + SUITE_EXIT_CODE=$? + ;; + sqlite) + rm -rf "${CORE_ROOT}/typo3temp/var/tests/acceptance-sqlite-dbs/" + mkdir -p "${CORE_ROOT}/typo3temp/var/tests/acceptance-sqlite-dbs/" + CODECEPION_ENV="--env ci,sqlite" + if [ "${ACCEPTANCE_HEADLESS}" -eq 1 ]; then + CODECEPION_ENV="--env ci,sqlite,headless" + fi + CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_sqlite" + COMMAND="bin/codecept run Install -d -c typo3/sysext/core/Tests/codeception.yml ${EXTRA_TEST_OPTIONS} ${CODECEPION_ENV} --html reports.html" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name ac-install-sqlite ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} ${COMMAND} + SUITE_EXIT_CODE=$? + ;; + esac + ;; + buildCss) + COMMAND="cd Build; npm ci || exit 1; node_modules/grunt/bin/grunt css" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name build-css-${SUFFIX} -e HOME=${CORE_ROOT}/.cache ${IMAGE_NODEJS} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + buildJavascript) + COMMAND="cd Build/; npm ci || exit 1; node_modules/grunt/bin/grunt scripts" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name build-js-${SUFFIX} -e HOME=${CORE_ROOT}/.cache ${IMAGE_NODEJS} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + cgl) + # Active dry-run for cgl needs not "-n" but specific options + if [ -n "${CGLCHECK_DRY_RUN}" ]; then + CGLCHECK_DRY_RUN="--dry-run --diff" + fi + COMMAND="php -dxdebug.mode=off bin/php-cs-fixer fix -v ${CGLCHECK_DRY_RUN} --path-mode intersection --config=Build/php-cs-fixer/config.php ." + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name cgl-${SUFFIX} ${IMAGE_PHP} ${COMMAND} + SUITE_EXIT_CODE=$? + ;; + cglGit) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name cgl-git-${SUFFIX} ${IMAGE_PHP} Build/Scripts/cglFixMyCommit.sh ${CGLCHECK_DRY_RUN} + SUITE_EXIT_CODE=$? + ;; + cglHeader) + # Active dry-run for cgl needs not "-n" but specific options + if [ -n "${CGLCHECK_DRY_RUN}" ]; then + CGLCHECK_DRY_RUN="--dry-run --diff" + fi + COMMAND="php -dxdebug.mode=off bin/php-cs-fixer fix -v ${CGLCHECK_DRY_RUN} --path-mode intersection --config=Build/php-cs-fixer/header-comment.php ." + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name cgl-header-${SUFFIX} ${IMAGE_PHP} ${COMMAND} + SUITE_EXIT_CODE=$? + ;; + cglHeaderGit) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name cgl-header-git-${SUFFIX} ${IMAGE_PHP} Build/Scripts/cglFixMyCommitFileHeader.sh ${CGLCHECK_DRY_RUN} + SUITE_EXIT_CODE=$? + ;; + checkAnnotations) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-annotations-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/annotationChecker.php + SUITE_EXIT_CODE=$? + ;; + checkTestClassFinal) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-test-classes-final-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/testClassFinalChecker.php + SUITE_EXIT_CODE=$? + ;; + checkTestMethodsPrefix) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-test-methods-prefix-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/testMethodPrefixChecker.php + SUITE_EXIT_CODE=$? + ;; + checkBom) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-utf8bom-${SUFFIX} ${IMAGE_PHP} Build/Scripts/checkUtf8Bom.sh + SUITE_EXIT_CODE=$? + ;; + checkComposer) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-composer-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/checkIntegrityComposer.php + SUITE_EXIT_CODE=$? + ;; + checkExceptionCodes) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-exception-codes-${SUFFIX} ${IMAGE_PHP} Build/Scripts/duplicateExceptionCodeCheck.sh + SUITE_EXIT_CODE=$? + ;; + checkExtensionScannerRst) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-extensionscanner-rst-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/extensionScannerRstFileReferences.php + SUITE_EXIT_CODE=$? + ;; + checkFilePathLength) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-file-path-length-${SUFFIX} ${IMAGE_PHP} Build/Scripts/maxFilePathLength.sh + SUITE_EXIT_CODE=$? + ;; + checkGitSubmodule) + COMMAND="if [ \$(git submodule status 2>&1 | wc -l) -ne 0 ]; then echo \"Found a submodule definition in repository\"; exit 1; fi" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-git-submodule-${SUFFIX} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + checkGruntClean) + COMMAND="find 'typo3/sysext' -name '*.js' -not -path '*/Fixtures/*' -exec rm '{}' + && cd Build; npm ci || exit 1; node_modules/grunt/bin/grunt build; cd ..; git add *; git status; git status | grep -q \"nothing to commit, working tree clean\"" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-grunt-clean-${SUFFIX} -e HOME=${CORE_ROOT}/.cache ${IMAGE_NODEJS} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + checkIsoDatabase) + COMMAND="git checkout -- composer.json; git checkout -- composer.lock; php -dxdebug.mode=off Build/Scripts/updateIsoDatabase.php; git add *; git status; git status | grep -q \"nothing to commit, working tree clean\"" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-iso-database-${SUFFIX} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + checkNamespaceIntegrity) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-namespaces-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/checkNamespaceIntegrity.php + SUITE_EXIT_CODE=$? + ;; + checkPermissions) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-permissions-${SUFFIX} ${IMAGE_PHP} Build/Scripts/checkFilePermissions.sh + SUITE_EXIT_CODE=$? + ;; + checkRst) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-rst-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/validateRstFiles.php + SUITE_EXIT_CODE=$? + ;; + clean) + cleanBuildFiles + cleanCacheFiles + cleanRenderedDocumentationFiles + cleanTestFiles + ;; + cleanBuild) + cleanBuildFiles + ;; + cleanCache) + cleanCacheFiles + ;; + cleanRenderedDocumentation) + cleanRenderedDocumentationFiles + ;; + cleanTests) + cleanTestFiles + ;; + composer) + COMMAND=(composer "$@") + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + composerInstall) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} composer install --no-progress --no-interaction + SUITE_EXIT_CODE=$? + ;; + composerInstallMax) + COMMAND="composer config --unset platform.php; composer update --no-progress --no-interaction; composer dumpautoload" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-max-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + composerInstallMin) + COMMAND="composer config platform.php ${PHP_VERSION}.0; composer update --prefer-lowest --no-progress --no-interaction; composer dumpautoload" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-min-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + composerTestDistribution) + COMMAND="cd Build/composer; rm -rf composer.json composer.lock public/index.php public/typo3 public/typo3conf/ext var/ vendor/; cp composer.dist.json composer.json; composer update --no-progress --no-interaction" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-test-distribution-${SUFFIX} -e COMPOSER_CACHE_DIR=${CORE_ROOT}/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + composerValidate) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-validate-${SUFFIX} ${IMAGE_PHP} composer validate + SUITE_EXIT_CODE=$? + ;; + functional) + if [ "${CHUNKS}" -gt 0 ]; then + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name func-splitter-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/splitFunctionalTests.php -v ${CHUNKS} + COMMAND=(bin/phpunit -c Build/phpunit/FunctionalTests-Job-${THISCHUNK}.xml --exclude-group not-${DBMS} ${EXTRA_TEST_OPTIONS} "$@") + else + COMMAND=(bin/phpunit -c Build/phpunit/FunctionalTests.xml --exclude-group not-${DBMS} ${EXTRA_TEST_OPTIONS} "$@") + fi + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name redis-func-${SUFFIX} --network ${NETWORK} -d ${IMAGE_REDIS} >/dev/null + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name memcached-func-${SUFFIX} --network ${NETWORK} -d ${IMAGE_MEMCACHED} >/dev/null + waitFor redis-func-${SUFFIX} 6379 + waitFor memcached-func-${SUFFIX} 11211 + CONTAINER_COMMON_PARAMS="${CONTAINER_COMMON_PARAMS} -e typo3TestingRedisHost=redis-func-${SUFFIX} -e typo3TestingMemcachedHost=memcached-func-${SUFFIX}" + 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 "${CORE_ROOT}/typo3temp/var/tests/functional-sqlite-dbs/" + CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_sqlite --tmpfs ${CORE_ROOT}/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 + ;; + functionalDeprecated) + COMMAND=(bin/phpunit -c Build/phpunit/FunctionalTestsDeprecated.xml --exclude-group not-${DBMS} ${EXTRA_TEST_OPTIONS} "$@") + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name redis-func-dep-${SUFFIX} --network ${NETWORK} -d ${IMAGE_REDIS} >/dev/null + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name memcached-func-dep-${SUFFIX} --network ${NETWORK} -d ${IMAGE_MEMCACHED} >/dev/null + waitFor redis-func-dep-${SUFFIX} 6379 + waitFor memcached-func-dep-${SUFFIX} 11211 + CONTAINER_COMMON_PARAMS="${CONTAINER_COMMON_PARAMS} -e typo3TestingRedisHost=redis-func-dep-${SUFFIX} -e typo3TestingMemcachedHost=memcached-func-dep-${SUFFIX}" + case ${DBMS} in + mariadb) + echo "Using driver: ${DATABASE_DRIVER}" + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mariadb-func-dep-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MARIADB} >/dev/null + waitFor mariadb-func-dep-${SUFFIX} 3306 + CONTAINERPARAMS="-e typo3DatabaseDriver=${DATABASE_DRIVER} -e typo3DatabaseName=func_test -e typo3DatabaseUsername=root -e typo3DatabaseHost=mariadb-func-dep-${SUFFIX} -e typo3DatabasePassword=funcp" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-deprecated-${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-dep-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MYSQL} >/dev/null + waitFor mysql-func-dep-${SUFFIX} 3306 + CONTAINERPARAMS="-e typo3DatabaseDriver=${DATABASE_DRIVER} -e typo3DatabaseName=func_test -e typo3DatabaseUsername=root -e typo3DatabaseHost=mysql-func-dep-${SUFFIX} -e typo3DatabasePassword=funcp" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-deprecated-${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-dep-${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-dep-${SUFFIX} 5432 + CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_pgsql -e typo3DatabaseName=bamboo -e typo3DatabaseUsername=funcu -e typo3DatabaseHost=postgres-func-dep-${SUFFIX} -e typo3DatabasePassword=funcp" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-deprecated-${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 "${CORE_ROOT}/typo3temp/var/tests/functional-sqlite-dbs/" + CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_sqlite --tmpfs ${CORE_ROOT}/typo3temp/var/tests/functional-sqlite-dbs/:rw,noexec,nosuid" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-deprecated-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + esac + ;; + lintPhp) + COMMAND="php -v | grep '^PHP'; find typo3/ -name \\*.php -print0 | xargs -0 -n1 -P4 php -dxdebug.mode=off -l >/dev/null" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name lint-php-${SUFFIX} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + lintScss) + COMMAND="cd Build; npm ci || exit 1; node_modules/grunt/bin/grunt stylelint" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name lint-css-${SUFFIX} -e HOME=${CORE_ROOT}/.cache ${IMAGE_NODEJS} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + lintTypescript) + COMMAND="cd Build; npm ci || exit 1; node_modules/grunt/bin/grunt eslint" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name lint-typescript-${SUFFIX} -e HOME=${CORE_ROOT}/.cache ${IMAGE_NODEJS} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + lintHtml) + COMMAND="cd Build; npm ci || exit 1; node_modules/grunt/bin/grunt exec:lintspaces" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name lint-html-${SUFFIX} -e HOME=${CORE_ROOT}/.cache ${IMAGE_NODEJS} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + listExceptionCodes) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name list-exception-codes-${SUFFIX} ${IMAGE_PHP} Build/Scripts/duplicateExceptionCodeCheck.sh -p + SUITE_EXIT_CODE=$? + ;; + npm) + COMMAND=(npm "$@") + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} -w ${CORE_ROOT}/Build -e HOME=${CORE_ROOT}/.cache --name npm-${SUFFIX} ${IMAGE_NODEJS} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + phpstan) + COMMAND=(php -dxdebug.mode=off bin/phpstan analyse -c Build/phpstan/${PHPSTAN_CONFIG_FILE} --no-progress --no-interaction --memory-limit 4G "$@") + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name phpstan-${SUFFIX} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + phpstanGenerateBaseline) + COMMAND="php -dxdebug.mode=off bin/phpstan analyse -c Build/phpstan/${PHPSTAN_CONFIG_FILE} --no-progress --no-interaction --memory-limit 4G --generate-baseline=Build/phpstan/phpstan-baseline.neon" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name phpstan-baseline-${SUFFIX} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + unit) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name unit-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${IMAGE_PHP} bin/phpunit -c Build/phpunit/UnitTests.xml ${EXTRA_TEST_OPTIONS} "$@" + SUITE_EXIT_CODE=$? + ;; + unitDeprecated) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name unit-deprecated-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${IMAGE_PHP} bin/phpunit -c Build/phpunit/UnitTestsDeprecated.xml ${EXTRA_TEST_OPTIONS} "$@" + SUITE_EXIT_CODE=$? + ;; + unitJavascript) + COMMAND="cd Build; npm ci || exit 1; CHROME_SANDBOX=false BROWSERS=chrome npm run test" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name unit-javascript-${SUFFIX} -e HOME=${CORE_ROOT}/.cache ${IMAGE_NODEJS_CHROME} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + unitRandom) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name unit-random-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${IMAGE_PHP} bin/phpunit -c Build/phpunit/UnitTests.xml --order-by=random ${EXTRA_TEST_OPTIONS} ${PHPUNIT_RANDOM} "$@" + SUITE_EXIT_CODE=$? + ;; + update) + # pull typo3/core-testing-* versions of those ones that exist locally + echo "> pull ghcr.io/typo3/core-testing-* versions of those ones that exist locally" + ${CONTAINER_BIN} images "ghcr.io/typo3/core-testing-*" --format "{{.Repository}}:{{.Tag}}" | xargs -I {} ${CONTAINER_BIN} pull {} + echo "" + # remove "dangling" typo3/core-testing-* images (those tagged as ) + echo "> remove \"dangling\" ghcr.io/typo3/core-testing-* images (those tagged as )" + ${CONTAINER_BIN} images --filter "reference=ghcr.io/typo3/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 +echo "PHP: ${PHP_VERSION}" >&2 +if [[ ${TEST_SUITE} =~ ^(functional|functionalDeprecated|acceptance|acceptanceComposer|acceptanceInstall)$ ]]; then + case "${DBMS}" in + mariadb|mysql|postgres) + echo "DBMS: ${DBMS} version ${DBMS_VERSION} driver ${DATABASE_DRIVER}" >&2 + ;; + sqlite) + echo "DBMS: ${DBMS}" >&2 + ;; + esac +fi +if [[ -n ${EXTRA_TEST_OPTIONS} ]]; then + echo " Note: Using -e is deprecated. Simply add the options at the end of the command." + echo " Instead of: Build/Scripts/runTests.sh -s ${TEST_SUITE} -e '${EXTRA_TEST_OPTIONS}' $@" + echo " use: Build/Scripts/runTests.sh -s ${TEST_SUITE} -- ${EXTRA_TEST_OPTIONS} $@" +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/Scripts/test.sh b/Build/Scripts/test.sh new file mode 100755 index 0000000..c1fa0cb --- /dev/null +++ b/Build/Scripts/test.sh @@ -0,0 +1,143 @@ +#!/bin/bash + +export NC='\e[0m' +export RED='\e[0;31m' +export GREEN='\e[0;32m' + +THIS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd "$THIS_SCRIPT_DIR" || exit 1 + +################################################# +# Run resource tests. +# Arguments: +# none +################################################# +checkResources () { + echo "#################################################################" >&2 + echo "Checking documentation, TypeScript and Scss files" >&2 + echo "#################################################################" >&2 + + ./additionalTests.sh -s lintXliff + EXIT_CODE_XLIFF=$? + + ./additionalTests.sh -s buildDocumentation + EXIT_CODE_DOCUMENTATION=$? + + echo "#################################################################" >&2 + echo "Checked documentation, TypeScript and Scss files" >&2 + if [[ ${EXIT_CODE_SCSS} -eq 0 ]] && \ + [[ ${EXIT_CODE_TYPESCRIPT} -eq 0 ]] && \ + [[ ${EXIT_CODE_XLIFF} -eq 0 ]] && \ + [[ ${EXIT_CODE_DOCUMENTATION} -eq 0 ]] + then + echo -e "${GREEN}Resources valid${NC}" >&2 + else + echo -e "${RED}Resources invalid${NC}" >&2 + fi + echo "#################################################################" >&2 + echo "" >&2 + + ./additionalTests.sh -s clean +} + +################################################# +# Run test matrix. +# Arguments: +# php version +# typo3 version +# testing framework version +# test path +# prefer lowest +################################################# +runFunctionalTests () { + local PHP_VERSION="${1}" + local TYPO3_VERSION=${2} + local TESTING_FRAMEWORK=${3} + local TEST_PATH=${4} + local PREFER_LOWEST=${5} + + echo "###########################################################################" >&2 + echo " Run unit and/or functional tests with" >&2 + echo " - TYPO3 ${TYPO3_VERSION}" >&2 + echo " - PHP ${PHP_VERSION}">&2 + echo " - Testing framework ${TESTING_FRAMEWORK}">&2 + echo " - Test path ${TEST_PATH}">&2 + echo " - Additional ${PREFER_LOWEST}">&2 + echo "###########################################################################" >&2 + + ./runTests.sh -s cleanTests + + ./additionalTests.sh \ + -p ${PHP_VERSION} \ + -s lintPhp || exit 1 ; \ + EXIT_CODE_LINT=$? + + ./additionalTests.sh \ + -p ${PHP_VERSION} \ + -s composerInstallPackage \ + -q "typo3/cms-core:${TYPO3_VERSION}" \ + -r " ${PREFER_LOWEST}" || exit 1 ; \ + EXIT_CODE_CORE=$? + + ./additionalTests.sh \ + -p ${PHP_VERSION} \ + -s composerInstallPackage \ + -q "typo3/testing-framework:${TESTING_FRAMEWORK}" \ + -r " --dev ${PREFER_LOWEST}" || exit 1 ; \ + EXIT_CODE_FRAMEWORK=$? + + ./runTests.sh \ + -p ${PHP_VERSION} \ + -s composerValidate || exit 1 ; \ + EXIT_CODE_VALIDATE=$? + + echo "###########################################################################" >&2 + echo " Finished unit and/or functional tests with" >&2 + echo " - TYPO3 ${TYPO3_VERSION}" >&2 + echo " - PHP ${PHP_VERSION}">&2 + echo " - Testing framework ${TESTING_FRAMEWORK}">&2 + echo " - Test path ${TEST_PATH}">&2 + echo " - Additional ${PREFER_LOWEST}">&2 + if [[ ${EXIT_CODE_LINT} -eq 0 ]] && \ + [[ ${EXIT_CODE_INSTALL} -eq 0 ]] && \ + [[ ${EXIT_CODE_CORE} -eq 0 ]] && \ + [[ ${EXIT_CODE_FRAMEWORK} -eq 0 ]] && \ + [[ ${EXIT_CODE_VALIDATE} -eq 0 ]] && \ + [[ ${EXIT_CODE_FUNCTIONAL} -eq 0 ]] + then + echo -e "${GREEN}SUCCESS${NC}" >&2 + else + echo -e "${RED}FAILURE${NC}" >&2 + exit 1 + fi + echo "#################################################################" >&2 + echo "" >&2 +} + +################################################# +# Removes all files created by tests. +# Arguments: +# none +################################################# +cleanup () { + ./runTests.sh -s clean + ./additionalTests.sh -s clean + git checkout ../../composer.json +} + +DEBUG_TESTS=false +if [[ $DEBUG_TESTS != true ]]; then + checkResources + + runFunctionalTests "8.2" "^13.0" "dev-main" "Tests/Functional" || exit 1 + runFunctionalTests "8.2" "^13.0" "dev-main" "Tests/Functional" "--prefer-lowest" || exit 1 + runFunctionalTests "8.3" "^13.0" "dev-main" "Tests/Functional" || exit 1 + runFunctionalTests "8.3" "^13.0" "dev-main" "Tests/Functional" "--prefer-lowest" || exit 1 + cleanup +else + cleanup + runFunctionalTests "8.2" "^13.0" "dev-main" "Tests/Functional" || exit 1 + cleanup + # ./runTests.sh -x -p 8.2 -d sqlite -s functional -e "--group selected" Tests/Functional + # ./runTests.sh -x -p 8.2 -d sqlite -s functional Tests/Functional +fi diff --git a/Build/php-cs-fixer/config.php b/Build/php-cs-fixer/config.php new file mode 100644 index 0000000..5e32557 --- /dev/null +++ b/Build/php-cs-fixer/config.php @@ -0,0 +1,116 @@ + Build/Scripts/additionalTests.sh -p 8.3 -s composerInstallPackage -q "typo3/cms-core:[dev-main,13...]" + * > Build/Scripts/runTests.sh -s cgl + * + * Fix your current patch: + * > Build/Scripts/runTests.sh -s cglGit + */ +if (PHP_SAPI !== 'cli') { + die('This script supports command line usage only. Please check your command.'); +} + +// Return a Code Sniffing configuration using +// all sniffers needed for PER +// and additionally: +// - Remove leading slashes in use clauses. +// - PHP single-line arrays should not have trailing comma. +// - Single-line whitespace before closing semicolon are prohibited. +// - Remove unused use statements in the PHP source code +// - Ensure Concatenation to have at least one whitespace around +// - Remove trailing whitespace at the end of blank lines. +return (new \PhpCsFixer\Config()) + ->setParallelConfig(\PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()) + ->setFinder( + PhpCsFixer\Finder::create() + ->ignoreVCSIgnored(true) + ->in(realpath(__DIR__ . '/../../')) + ->exclude('bin') + ->exclude('public') + ->exclude('typo3temp') + ->exclude('vendor') + ) + ->setRiskyAllowed(true) + ->setRules([ + '@DoctrineAnnotation' => true, + // @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_else' => 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' => ['arrays']], + 'whitespace_after_comma_in_array' => ['ensure_single_space' => true], + 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], + ]); diff --git a/Build/php-cs-fixer/header-comment.php b/Build/php-cs-fixer/header-comment.php new file mode 100644 index 0000000..284ba9b --- /dev/null +++ b/Build/php-cs-fixer/header-comment.php @@ -0,0 +1,75 @@ + Build/Scripts/additionalTests.sh -p 8.3 -s composerInstallPackage -q "typo3/cms-core:[dev-main,13...]" + * > Build/Scripts/runTests.sh -s cglHeader + * + * Fix your current patch: + * > Build/Scripts/runTests.sh -s cglHeaderGit + */ +if (PHP_SAPI !== 'cli') { + die('This script supports command line usage only. Please check your command.'); +} + +$finder = PhpCsFixer\Finder::create() + ->name('*.php') + ->in(__DIR__ . '/../../') + ->exclude('Acceptance/Support/_generated') // EXT:core + ->exclude('Build') + // Configuration files do not need header comments + ->exclude('Configuration') + ->notName('*locallang*.php') + ->notName('ext_localconf.php') + ->notName('ext_tables.php') + ->notName('ext_emconf.php') + // ClassAliasMap files do not need header comments + ->notName('ClassAliasMap.php') + // CodeSnippets and Examples in Documentation do not need header comments + ->exclude('Documentation') + // Third-party inclusion files should not have a changed comment + ->notName('Rfc822AddressesParser.php') + ->notName('ClassMapGenerator.php') +; + +$headerComment = <<setParallelConfig(\PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()) + ->setRiskyAllowed(false) + ->setRules([ + 'no_extra_blank_lines' => true, + 'header_comment' => [ + 'header' => $headerComment, + 'comment_type' => 'comment', + 'separate' => 'both', + 'location' => 'after_declare_strict', + ], + ]) + ->setFinder($finder); diff --git a/Classes/Command/ContentElementCommand.php b/Classes/Command/ContentElementCommand.php old mode 100755 new mode 100644 index 687e76d..544fcb2 --- a/Classes/Command/ContentElementCommand.php +++ b/Classes/Command/ContentElementCommand.php @@ -1,6 +1,6 @@ setAliases(['kc-sitepackage']) diff --git a/Classes/Configuration/AdditionalConfiguration.php b/Classes/Configuration/AdditionalConfiguration.php old mode 100755 new mode 100644 index 298a17a..7998e86 --- a/Classes/Configuration/AdditionalConfiguration.php +++ b/Classes/Configuration/AdditionalConfiguration.php @@ -2,44 +2,22 @@ declare(strict_types=1); -namespace Evoweb\EwBase\Configuration; - /* - * This file is part of TYPO3 CMS-based extension "container" by b13. + * This file is part of TYPO3 CMS-based extension "ew-base" by evoWeb. * * It is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, either version 2 * of the License, or any later version. */ -use Mfc\OAuth2\ResourceServer\GitLab; -use Mfc\OAuth2\ResourceServer\Registry; +namespace Evoweb\EwBase\Configuration; + use TYPO3\CMS\Core\Core\Environment; -use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; -use TYPO3\CMS\Core\Utility\GeneralUtility; class AdditionalConfiguration { protected string $extensionKey = 'ew_base'; - protected array $oauthOptions = [ - 'enabled' => true, // Enable/Disable the provider - 'arguments' => [ - 'appId' => '', - 'appSecret' => '', - 'projectName' => '', - 'gitlabServer' => 'https://github.com', - // User level at which the user will be given admin permissions - 'gitlabAdminUserLevel' => 30, - // Groups to assign to the User (comma separated list possible) - 'gitlabDefaultGroups' => 1, - // UserConfig db and/or file mount from groups - 'gitlabUserOption' => 3, - // Blocks users with flag external from access the backend - 'blockExternalUser' => false, - ], - ]; - protected array $developConfig = [ 'BE' => [ 'debug' => true, @@ -70,12 +48,11 @@ class AdditionalConfiguration /** * This is needed to override all mail settings instead of merging them. * - * @var array|string[] + * @var array */ protected array $mailConfig = [ 'transport' => 'smtp', - 'transport_smtp_server' => '127.0.0.1:1025', - 'defaultMailFromAddress' => 'test@dev.arpa', + 'transport_smtp_server' => 'mailpit:1025' ]; public function initialize(array $configuration = []): void @@ -83,7 +60,6 @@ class AdditionalConfiguration $this->addContextToSitename(); $this->addContextConfiguration($configuration); if (Environment::getContext() == 'Development') { - $this->addBaseUrl(); $this->addDebugConfiguration(); } $this->addFurtherConfigurationFiles(); @@ -94,28 +70,6 @@ class AdditionalConfiguration $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] .= ' - ' . Environment::getContext(); } - /** - * Set the baseurl on local environments automatically - */ - protected function addBaseUrl(): void - { - if (Environment::isCli()) { - return; - } - $remoteHost = GeneralUtility::getIndpEnv('HTTP_HOST'); - ExtensionManagementUtility::addTypoScript( - $this->extensionKey, - 'constants', - ' - // condition should trigger different cache hashes - [request.getNormalizedParams().getHttpHost() == \'' . $remoteHost . '\'] - config.baseURL = ' . $remoteHost . ' - [end] - ', - 'defaultContentRendering' - ); - } - protected function addDebugConfiguration(): void { $GLOBALS['TYPO3_CONF_VARS'] = $this->arrayMergeRecursive( @@ -169,19 +123,6 @@ class AdditionalConfiguration } } - public function addGitlabLogin(array $options = []): void - { - if (!empty($options)) { - $this->oauthOptions['arguments']['gitlabAdminUserLevel'] = GitLab::USER_LEVEL_DEVELOPER; - Registry::addServer( - 'gitlab', - 'Login mit GitLab', - GitLab::class, - $this->arrayMergeRecursive($this->oauthOptions, $options) - ); - } - } - protected function arrayMergeRecursive(array $array1, array $array2): array { $merged = $array1; diff --git a/Classes/DataProcessing/ContainerProcessor.php b/Classes/DataProcessing/ContainerProcessor.php deleted file mode 100755 index 6d48d2a..0000000 --- a/Classes/DataProcessing/ContainerProcessor.php +++ /dev/null @@ -1,58 +0,0 @@ -getChildrenByColPos($colPos); - - if (!$processorConfiguration['doNotProcessChildren']) { - $contentRecordRenderer = new RecordsContentObject(); - $conf = [ - 'tables' => 'tt_content' - ]; - foreach ($children as &$child) { - if ($child['l18n_parent'] > 0) { - $conf['source'] = $child['l18n_parent']; - } else { - $conf['source'] = $child['uid']; - } - if ($child['t3ver_oid'] > 0) { - $conf['source'] = $child['t3ver_oid']; - } - $child['renderedContent'] = $cObj->render($contentRecordRenderer, $conf); - /** @var ContentObjectRenderer $recordContentObjectRenderer */ - $recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class); - $recordContentObjectRenderer->start($child, 'tt_content'); - $child = $this->contentDataProcessor->process($recordContentObjectRenderer, $processorConfiguration, $child); - } - } - - $processedData[$as] = $children; - return $processedData; - } -} diff --git a/Classes/DataProcessing/DatabaseQueryProcessor.php b/Classes/DataProcessing/DatabaseQueryProcessor.php new file mode 100644 index 0000000..7f6d5b9 --- /dev/null +++ b/Classes/DataProcessing/DatabaseQueryProcessor.php @@ -0,0 +1,616 @@ +checkIf($processorConfiguration['if.'])) { + return $processedData; + } + + // the table to query, if none given, exit + $tableName = $cObj->stdWrapValue('table', $processorConfiguration); + if (empty($tableName)) { + return $processedData; + } + if (isset($processorConfiguration['table.'])) { + unset($processorConfiguration['table.']); + } + if (isset($processorConfiguration['table'])) { + unset($processorConfiguration['table']); + } + + $this->request = $cObj->getRequest(); + $this->tableName = $tableName; + $this->cObj = clone $cObj; + // @extensionScannerIgnoreLine + $this->cObj->start($cObj->data, $tableName); + $this->cObj->setRequest($this->request); + + // The variable to be used within the result + $targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'records'); + + // Execute a SQL statement to fetch the records + $records = $this->getRecords($tableName, $processorConfiguration); + $request = $cObj->getRequest(); + $processedRecordVariables = []; + foreach ($records as $key => $record) { + /** @var ContentObjectRenderer $recordContentObjectRenderer */ + $recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class); + $recordContentObjectRenderer->setRequest($request); + $recordContentObjectRenderer->start($record, $tableName); + $processedRecordVariables[$key] = ['data' => $record]; + $processedRecordVariables[$key] = $this->contentDataProcessor->process( + $recordContentObjectRenderer, + $processorConfiguration, + $processedRecordVariables[$key] + ); + } + + $processedData[$targetVariableName] = $processedRecordVariables; + + return $processedData; + } + + protected function getRecords(string $tableName, array $queryConfiguration): array + { + $records = []; + + $statement = $this->exec_getQuery($tableName, $queryConfiguration); + + $pageRepository = $this->getPageRepository(); + while ($row = $statement->fetchAssociative()) { + // Versioning preview: + $pageRepository->versionOL($tableName, $row, true); + + // Language overlay: + if (is_array($row)) { + $row = $pageRepository->getLanguageOverlay($tableName, $row); + } + + // Might be unset in the language overlay + if (is_array($row)) { + $records[] = $row; + } + } + + return $records; + } + + protected function exec_getQuery(string $table, array $conf): Result + { + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table); + $statement = $this->getQuery($connection, $table, $conf); + + return $connection->executeQuery($statement); + } + + public function getQuery(Connection $connection, $table, $conf): string + { + // Resolve stdWrap in these properties first + $properties = [ + 'pidInList', + 'uidInList', + 'languageField', + 'selectFields', + 'max', + 'begin', + 'groupBy', + 'orderBy', + 'join', + 'leftjoin', + 'rightjoin', + 'recursive', + 'where', + ]; + foreach ($properties as $property) { + $conf[$property] = trim( + isset($conf[$property . '.']) + ? (string)$this->cObj->stdWrap($conf[$property] ?? '', $conf[$property . '.'] ?? []) + : (string)($conf[$property] ?? '') + ); + if ($conf[$property] === '') { + unset($conf[$property]); + } elseif (in_array($property, ['languageField', 'selectFields', 'join', 'leftjoin', 'rightjoin', 'where'], true)) { + $conf[$property] = QueryHelper::quoteDatabaseIdentifiers($connection, $conf[$property]); + } + if (isset($conf[$property . '.'])) { + // stdWrapping already done, so remove the sub-array + unset($conf[$property . '.']); + } + } + // Handle PDO-style named parameter markers first + $queryMarkers = $this->cObj->getQueryMarkers($connection, $conf); + // Replace the markers in the non-stdWrap properties + foreach ($queryMarkers as $marker => $markerValue) { + $properties = [ + 'uidInList', + 'selectFields', + 'where', + 'max', + 'begin', + 'groupBy', + 'orderBy', + 'join', + 'leftjoin', + 'rightjoin', + ]; + foreach ($properties as $property) { + if ($conf[$property] ?? false) { + $conf[$property] = str_replace('###' . $marker . '###', $markerValue, $conf[$property]); + } + } + } + + // Construct WHERE clause: + // Handle recursive function for the pidInList + if (isset($conf['recursive'])) { + $conf['recursive'] = (int)$conf['recursive']; + if ($conf['recursive'] > 0) { + $pidList = GeneralUtility::trimExplode(',', $conf['pidInList'], true); + array_walk($pidList, function (&$storagePid) { + if ($storagePid === 'this') { + $storagePid = $this->getRequest()->getAttribute('frontend.page.information')->getId(); + } + }); + $pageRepository = $this->getPageRepository(); + $expandedPidList = $pageRepository->getPageIdsRecursive($pidList, $conf['recursive']); + $conf['pidInList'] = implode(',', $expandedPidList); + } + } + if ((string)($conf['pidInList'] ?? '') === '') { + $conf['pidInList'] = 'this'; + } + + $queryParts = $this->getQueryConstraints($connection, $table, $conf); + + $queryBuilder = $connection->createQueryBuilder(); + // @todo Check against getQueryConstraints, can probably use FrontendRestrictions + // @todo here and remove enableFields there. + $queryBuilder->getRestrictions()->removeAll(); + $queryBuilder->select('*')->from($table); + + if ($queryParts['where'] ?? false) { + $queryBuilder->where($queryParts['where']); + } + + if (($queryParts['groupBy'] ?? false) && is_array($queryParts['groupBy'])) { + $queryBuilder->groupBy(...$queryParts['groupBy']); + } + + if (is_array($queryParts['orderBy'] ?? false)) { + foreach ($queryParts['orderBy'] as $orderBy) { + $queryBuilder->addOrderBy(...$orderBy); + } + } + + // Fields: + if ($conf['selectFields'] ?? false) { + $queryBuilder->selectLiteral($this->sanitizeSelectPart($connection, $conf['selectFields'], $table)); + } + + // Setting LIMIT: + if (($conf['max'] ?? false) || ($conf['begin'] ?? false)) { + // Finding the total number of records, if used: + if (str_contains(strtolower(($conf['begin'] ?? '') . ($conf['max'] ?? '')), 'total')) { + $countQueryBuilder = $connection->createQueryBuilder(); + $countQueryBuilder->getRestrictions()->removeAll(); + $countQueryBuilder->count('*') + ->from($table) + ->where($queryParts['where']); + + if ($queryParts['groupBy']) { + $countQueryBuilder->groupBy(...$queryParts['groupBy']); + } + + try { + $count = $countQueryBuilder->executeQuery()->fetchOne(); + if (isset($conf['max'])) { + $conf['max'] = str_ireplace('total', $count, (string)$conf['max']); + } + if (isset($conf['begin'])) { + $conf['begin'] = str_ireplace('total', $count, (string)$conf['begin']); + } + } catch (DBALException $e) { + $this->getTimeTracker()->setTSlogMessage($e->getPrevious()->getMessage()); + return ''; + } + } + + if (isset($conf['begin']) && $conf['begin'] > 0) { + $conf['begin'] = MathUtility::forceIntegerInRange((int)ceil($this->cObj->calc($conf['begin'])), 0); + $queryBuilder->setFirstResult($conf['begin']); + } + if (isset($conf['max'])) { + $conf['max'] = MathUtility::forceIntegerInRange((int)ceil($this->cObj->calc($conf['max'])), 0); + $queryBuilder->setMaxResults($conf['max'] ?: 100000); + } + } + + // Setting up tablejoins: + if ($conf['join'] ?? false) { + $joinParts = QueryHelper::parseJoin($conf['join']); + $queryBuilder->join( + $table, + $joinParts['tableName'], + $joinParts['tableAlias'], + $joinParts['joinCondition'] + ); + } elseif ($conf['leftjoin'] ?? false) { + $joinParts = QueryHelper::parseJoin($conf['leftjoin']); + $queryBuilder->leftJoin( + $table, + $joinParts['tableName'], + $joinParts['tableAlias'], + $joinParts['joinCondition'] + ); + } elseif ($conf['rightjoin'] ?? false) { + $joinParts = QueryHelper::parseJoin($conf['rightjoin']); + $queryBuilder->rightJoin( + $table, + $joinParts['tableName'], + $joinParts['tableAlias'], + $joinParts['joinCondition'] + ); + } + + // Convert the QueryBuilder object into a SQL statement. + $query = $queryBuilder->getSQL(); + + // Replace the markers in the queryParts to handle stdWrap enabled properties + foreach ($queryMarkers as $marker => $markerValue) { + // @todo Ugly hack that needs to be cleaned up, with the current architecture + // @todo for exec_Query / getQuery it's the best we can do. + $query = str_replace('###' . $marker . '###', (string)$markerValue, $query); + } + + return $query; + } + + /** + * Helper function for getQuery(), creating the WHERE clause of the SELECT query + * + * @param Connection $connection + * @param string $table The table name + * @param array $conf The TypoScript configuration properties + * @return array Associative array containing the prepared data for WHERE, ORDER BY and GROUP BY fragments + * @throws AspectNotFoundException + * @throws ContentRenderingException + * @see getQuery() + */ + protected function getQueryConstraints(Connection $connection, string $table, array $conf): array + { + $queryBuilder = $connection->createQueryBuilder(); + $expressionBuilder = $queryBuilder->expr(); + $request = $this->getRequest(); + $contentPid = $request->getAttribute('frontend.page.information')->getContentFromPid(); + $constraints = []; + $pid_uid_flag = 0; + $enableFieldsIgnore = []; + $queryParts = [ + 'where' => null, + 'groupBy' => null, + 'orderBy' => null, + ]; + + $isInWorkspace = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('workspace', 'isOffline'); + $considerMovePointers = ( + $isInWorkspace && $table !== 'pages' + && !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) + ); + + if (trim($conf['uidInList'] ?? '')) { + $listArr = GeneralUtility::intExplode(',', str_replace('this', (string)$contentPid, $conf['uidInList'])); + + // If moved records shall be considered, select via t3ver_oid + if ($considerMovePointers) { + $constraints[] = (string)$expressionBuilder->or( + $expressionBuilder->in($table . '.uid', $listArr), + $expressionBuilder->and( + $expressionBuilder->eq( + $table . '.t3ver_state', + VersionState::MOVE_POINTER->value + ), + $expressionBuilder->in($table . '.t3ver_oid', $listArr) + ) + ); + } else { + $constraints[] = (string)$expressionBuilder->in($table . '.uid', $listArr); + } + $pid_uid_flag++; + } + + // Static_* tables are allowed to be fetched from root page + if (str_starts_with($table, 'static_')) { + $pid_uid_flag++; + } + + if (trim($conf['pidInList'])) { + $listArr = GeneralUtility::intExplode(',', str_replace('this', (string)$contentPid, $conf['pidInList'])); + // Removes all pages which are not visible for the user! + $listArr = $this->cObj->checkPidArray($listArr); + if (GeneralUtility::inList($conf['pidInList'], 'root')) { + $listArr[] = 0; + } + if (GeneralUtility::inList($conf['pidInList'], '-1')) { + $listArr[] = -1; + $enableFieldsIgnore['pid'] = true; + } + if (!empty($listArr)) { + $constraints[] = $expressionBuilder->in($table . '.pid', array_map('intval', $listArr)); + $pid_uid_flag++; + } else { + // If not uid and not pid then uid is set to 0 - which results in nothing!! + $pid_uid_flag = 0; + } + } + + // If not uid and not pid then uid is set to 0 - which results in nothing!! + if (!$pid_uid_flag && trim($conf['pidInList'] ?? '') !== 'ignore') { + $constraints[] = $expressionBuilder->eq($table . '.uid', 0); + } + + $where = trim((string)$this->cObj->stdWrapValue('where', $conf)); + if ($where) { + $constraints[] = QueryHelper::stripLogicalOperatorPrefix($where); + } + + // Check if the default language should be fetched (= doing overlays), or if only the records of a language should be fetched + // but only do this for TCA tables that have languages enabled + $languageConstraint = $this->getLanguageRestriction($expressionBuilder, $table, $conf, GeneralUtility::makeInstance(Context::class)); + if ($languageConstraint !== null) { + $constraints[] = $languageConstraint; + } + + // default constraints from TCA + $pageRepository = $this->getPageRepository(); + $constraints = array_merge( + $constraints, + array_values($pageRepository->getDefaultConstraints($table, $enableFieldsIgnore)) + ); + + // MAKE WHERE: + if ($constraints !== []) { + $queryParts['where'] = $expressionBuilder->and(...$constraints); + } + // GROUP BY + $groupBy = trim((string)$this->cObj->stdWrapValue('groupBy', $conf)); + if ($groupBy) { + $queryParts['groupBy'] = QueryHelper::parseGroupBy($groupBy); + } + + // ORDER BY + $orderByString = trim((string)$this->cObj->stdWrapValue('orderBy', $conf)); + if ($orderByString) { + $queryParts['orderBy'] = QueryHelper::parseOrderBy($orderByString); + } + + // Return result: + return $queryParts; + } + + /** + * Adds parts to the WHERE clause that are related to language. + * This only works on TCA tables which have the [ctrl][languageField] field set or if they + * have select.languageField = my_language_field set explicitly. + * + * It is also possible to disable the language restriction for a query by using select.languageField = 0, + * if select.languageField is not explicitly set, the TCA default values are taken. + * + * If the table is "localizeable" (= any of the criteria above is met), then the DB query is restricted: + * + * If the current language aspect has overlays enabled, then the only records with language "0" or "-1" are + * fetched (the overlays are taken care of later-on). + * if the current language has overlays but also records without localization-parent (free mode) available, + * then these are fetched as well. This can explicitly set via select.includeRecordsWithoutDefaultTranslation = 1 + * which overrules the overlayType within the language aspect. + * + * If the language aspect has NO overlays enabled, it behaves as in "free mode" (= only fetch the records + * for the current language. + * + * @throws AspectNotFoundException + */ + protected function getLanguageRestriction( + ExpressionBuilder $expressionBuilder, + string $table, + array $conf, + Context $context + ): string|CompositeExpression|null { + $languageField = ''; + $localizationParentField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null; + // Check if the table is translatable, and set the language field by default from the TCA information + if (!empty($conf['languageField']) || !isset($conf['languageField'])) { + if (isset($conf['languageField']) && !empty($GLOBALS['TCA'][$table]['columns'][$conf['languageField']])) { + $languageField = $conf['languageField']; + } elseif (!empty($GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty($localizationParentField)) { + $languageField = $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['languageField']; + } + } + + // No language restriction enabled explicitly or available via TCA + if (empty($languageField)) { + return null; + } + + /** @var LanguageAspect $languageAspect */ + $languageAspect = $context->getAspect('language'); + if ($languageAspect->doOverlays() && !empty($localizationParentField)) { + // Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will + // OVERLAY the records with localized versions! + $languageQuery = $expressionBuilder->in($languageField, [0, -1]); + // Use this option to include records that don't have a default language counterpart ("free mode") + // (originalpointerfield is 0 and the language field contains the requested language) + if (isset($conf['includeRecordsWithoutDefaultTranslation']) || !empty($conf['includeRecordsWithoutDefaultTranslation.'])) { + $includeRecordsWithoutDefaultTranslation = isset($conf['includeRecordsWithoutDefaultTranslation.']) + ? $this->cObj->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.']) + : $conf['includeRecordsWithoutDefaultTranslation']; + $includeRecordsWithoutDefaultTranslation = trim((string)$includeRecordsWithoutDefaultTranslation); + $includeRecordsWithoutDefaultTranslation = $includeRecordsWithoutDefaultTranslation !== '' && $includeRecordsWithoutDefaultTranslation !== '0'; + } else { + // Option was not explicitly set, check what's in for the language overlay type. + $includeRecordsWithoutDefaultTranslation = $languageAspect->getOverlayType() === $languageAspect::OVERLAYS_ON_WITH_FLOATING; + } + if ($includeRecordsWithoutDefaultTranslation) { + $languageQuery = $expressionBuilder->or( + $languageQuery, + $expressionBuilder->and( + $expressionBuilder->eq($table . '.' . $localizationParentField, 0), + $expressionBuilder->eq($languageField, $languageAspect->getContentId()) + ) + ); + } + return $languageQuery; + } + // No overlays = only fetch records given for the requested language and "all languages" + return (string)$expressionBuilder->in($languageField, [$languageAspect->getContentId(), -1]); + } + + /** + * Helper function for getQuery, sanitizing the select part + * + * This functions checks if the necessary fields are part of the select + * and adds them if necessary. + * + * @return string Sanitized select part + * @internal + * @see getQuery + */ + protected function sanitizeSelectPart(Connection $connection, string $selectPart, string $table): string + { + // Pattern matching parts + $matchStart = '/(^\\s*|,\\s*|' . $table . '\\.)'; + $matchEnd = '(\\s*,|\\s*$)/'; + $necessaryFields = ['uid', 'pid']; + $wsFields = ['t3ver_state']; + $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? false; + if (isset($GLOBALS['TCA'][$table]) && !preg_match($matchStart . '\\*' . $matchEnd, $selectPart) && !preg_match('/(count|max|min|avg|sum)\\([^\\)]+\\)|distinct/i', $selectPart)) { + foreach ($necessaryFields as $field) { + $match = $matchStart . $field . $matchEnd; + if (!preg_match($match, $selectPart)) { + $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field); + } + } + if (is_string($languageField)) { + $match = $matchStart . $languageField . $matchEnd; + if (!preg_match($match, $selectPart)) { + $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $languageField) . ' AS ' . $connection->quoteIdentifier($languageField); + } + } + if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] ?? false) { + foreach ($wsFields as $field) { + $match = $matchStart . $field . $matchEnd; + if (!preg_match($match, $selectPart)) { + $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field); + } + } + } + } + return $selectPart; + } + + protected function getPageRepository(): PageRepository + { + return GeneralUtility::makeInstance(PageRepository::class); + } + + protected function getTimeTracker(): TimeTracker + { + return GeneralUtility::makeInstance(TimeTracker::class); + } + + public function getRequest(): ServerRequestInterface + { + if ($this->request instanceof ServerRequestInterface) { + return $this->request; + } + throw new ContentRenderingException( + 'PSR-7 request is missing in ContentObjectRenderer. Inject with start(), setRequest() or provide via $GLOBALS[\'TYPO3_REQUEST\'].', + 1607172972 + ); + } +} diff --git a/Classes/EventListener/CssMerger.php b/Classes/EventListener/CssMerger.php old mode 100755 new mode 100644 index dfd8de0..ab6003b --- a/Classes/EventListener/CssMerger.php +++ b/Classes/EventListener/CssMerger.php @@ -1,17 +1,33 @@ extensionConfiguration->get('ew_base', 'inlineCssStyles'); - if ($inlineCssStyles) { + /** @var Site $site */ + $site = $this->getRequest()->getAttribute('site'); + if ( + $site instanceof Site + && ($siteSettings = $site->getSettings()) + && !$siteSettings->isEmpty() + && $siteSettings->get('ew-base.inlineCssStyles') + ) { $assetCollector->addInlineStyleSheet('ew_base', $styles, [], ['priority' => true]); } else { $temporaryFile = GeneralUtility::writeStyleSheetContentToTemporaryFile($styles); - $assetCollector->addStyleSheet('combined_styles', $temporaryFile); + // @extensionScannerIgnoreLine + $assetCollector->addStyleSheet( + 'ew_base_combined_styles', + $temporaryFile, + ['nounce' => $this->requestId->nonce] + ); } } } @@ -96,4 +123,9 @@ class CssMerger $content ); } + + protected function getRequest(): ServerRequestInterface + { + return $GLOBALS['TYPO3_REQUEST']; + } } diff --git a/Classes/EventListener/IsContentUsedOnPageLayout.php b/Classes/EventListener/IsContentUsedOnPageLayout.php deleted file mode 100755 index daafc1e..0000000 --- a/Classes/EventListener/IsContentUsedOnPageLayout.php +++ /dev/null @@ -1,25 +0,0 @@ -setUsed($event->isRecordUsed() || $this->findCTypeBegin($event->getRecord()['CType'])); - } - - public function findCTypeBegin($cType): bool - { - $found = false; - foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['ContentUsedStrings'] ?? [] as $search) { - if (strpos($cType, $search) !== false) { - $found = true; - break; - } - } - return $found; - } -} diff --git a/Classes/EventListener/JsMerger.php b/Classes/EventListener/JsMerger.php old mode 100755 new mode 100644 index 52d7381..8fd6092 --- a/Classes/EventListener/JsMerger.php +++ b/Classes/EventListener/JsMerger.php @@ -1,14 +1,32 @@ isInline() && $event->isPriority()) { @@ -22,7 +40,11 @@ class JsMerger $scripts = trim(implode(LF, $scripts)); if (!empty($scripts)) { $temporaryFile = GeneralUtility::writeJavaScriptContentToTemporaryFile($scripts); - $assetCollector->addJavaScript('combined_scripts', $temporaryFile); + $assetCollector->addJavaScript( + 'ew_base_combined_scripts', + $temporaryFile, + ['nounce' => $this->requestId->nonce] + ); } } } @@ -90,7 +112,7 @@ class JsMerger 'url("' . $relativeFilePath . '/..', 'url(\'' . $relativeFilePath . '/..', ], - $content + $content, ); } } diff --git a/Classes/EventListener/ReleaseToolbarItem.php b/Classes/EventListener/ReleaseToolbarItem.php new file mode 100644 index 0000000..e316acc --- /dev/null +++ b/Classes/EventListener/ReleaseToolbarItem.php @@ -0,0 +1,58 @@ +getToolbarItem(); + + $release = $this->getRelease(); + if ($release !== []) { + $label = $this->getLanguageService()->sL( + 'LLL:EXT:ew_base/Resources/Private/Language/locallang_core.xlf:rm.release_label-' + . ($release['isTag'] ? 'tag' : 'branch') + ); + $systemInformationToolbarItem->addSystemInformation( + $label, + $release['release'], + 'information-git', + InformationStatus::OK + ); + } + } + + protected function getRelease(): array + { + $release = GeneralUtility::getUrl(Environment::getProjectPath() . '/release'); + return $release ? [ + 'release' => trim($release), + 'isTag' => str_contains($release, '.'), + ] : []; + } + + private function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } +} diff --git a/Classes/Form/Element/PickColorFromImage.php b/Classes/Form/Element/PickColorFromImage.php old mode 100755 new mode 100644 index afe6a18..38eb3f8 --- a/Classes/Form/Element/PickColorFromImage.php +++ b/Classes/Form/Element/PickColorFromImage.php @@ -2,8 +2,6 @@ declare(strict_types=1); -namespace Evoweb\EwBase\Form\Element; - /* * This file is developed by evoWeb. * @@ -15,11 +13,14 @@ namespace Evoweb\EwBase\Form\Element; * LICENSE.txt file that was distributed with this source code. */ +namespace Evoweb\EwBase\Form\Element; + use TYPO3\CMS\Backend\Form\Element\AbstractFormElement; -use TYPO3\CMS\Backend\Form\NodeFactory; +use TYPO3\CMS\Backend\View\BackendViewFactory; use TYPO3\CMS\Core\Imaging\ImageManipulation\Area; use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection; use TYPO3\CMS\Core\Imaging\ImageManipulation\InvalidConfigurationException; +use TYPO3\CMS\Core\Imaging\ImageManipulation\Ratio; use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction; use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException; use TYPO3\CMS\Core\Resource\File; @@ -27,15 +28,11 @@ use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Core\Utility\StringUtility; -use TYPO3Fluid\Fluid\ViEvoweb\TemplateView; -use TYPO3Fluid\Fluid\ViEvoweb\ViewInterface; class PickColorFromImage extends AbstractFormElement { /** * Default element configuration - * - * @var array */ protected static array $defaultConfig = [ 'imageField' => 'image', @@ -110,22 +107,10 @@ class PickColorFromImage extends AbstractFormElement ], ]; - protected ViewInterface $templateView; - - public function __construct(NodeFactory $nodeFactory, array $data) - { - parent::__construct($nodeFactory, $data); - // Would be great, if we could inject the view here, but since the constructor is in the interface, we can't - // @todo: It's unfortunate we're using Typo3Fluid TemplateView directly here. We can't - // inject BackendViewFactory here since __construct() is polluted by NodeInterface. - // Remove __construct() from NodeInterface to have DI, then use BackendViewFactory here. - $view = GeneralUtility::makeInstance(TemplateView::class); - $templatePaths = $view->getRenderingContext()->getTemplatePaths(); - $templatePaths - ->setTemplateRootPaths([GeneralUtility::getFileAbsFileName('EXT:ew_base/Resources/Private/Templates')]); - $templatePaths - ->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:ew_base/Resources/Private/Partials')]); - $this->templateView = $view; + public function __construct( + private readonly BackendViewFactory $backendViewFactory, + private readonly ResourceFactory $resourceFactory, + ) { } public function render(): array @@ -133,40 +118,22 @@ class PickColorFromImage extends AbstractFormElement $resultArray = $this->initializeResultArray(); $parameterArray = $this->data['parameterArray']; $config = $this->populateConfiguration($parameterArray['fieldConf']['config']); - $fieldId = StringUtility::getUniqueId('formengine-color-'); - $file = $this->getFileObject($this->data['databaseRow'], $config['imageField']); + $file = $this->getFile($this->data['databaseRow'], $config['imageField']); if (!$file) { - $languageService = $this->getLanguageService(); - $label = $languageService->sL( - 'LLL:EXT:ew_base/Resources/Private/Language/locallang_db.xlf:imageField.empty' - ); - $fieldLabel = $this->data['processedTca']['columns'][$config['imageField']]['label']; - $resultArray['html'] = ' -
-
-
- - ' . sprintf($label, '' . $fieldLabel . '') . ' - -
-
-
- '; // Early return in case we do not find a file return $resultArray; } + $config = $this->processConfiguration($config, $parameterArray['itemFormElValue'], $file); + $fieldInformationResult = $this->renderFieldInformation(); - $fieldInformationHtml = $fieldInformationResult['html']; $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false); $fieldControlResult = $this->renderFieldControl(); - $fieldControlHtml = $fieldControlResult['html']; $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false); $fieldWizardResult = $this->renderFieldWizard(); - $fieldWizardHtml = $fieldWizardResult['html']; $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false); $width = $this->formMaxWidth( @@ -178,9 +145,9 @@ class PickColorFromImage extends AbstractFormElement ); $arguments = [ - 'fieldInformation' => $fieldInformationHtml, - 'fieldControl' => $fieldControlHtml, - 'fieldWizard' => $fieldWizardHtml, + 'fieldInformation' => $fieldInformationResult['html'], + 'fieldControl' => $fieldControlResult['html'], + 'fieldWizard' => $fieldWizardResult['html'], 'isAllowedFileExtension' => in_array( strtolower($file->getExtension()), GeneralUtility::trimExplode(',', strtolower($config['allowedExtensions'])), @@ -199,6 +166,8 @@ class PickColorFromImage extends AbstractFormElement ]; if ($arguments['isAllowedFileExtension']) { + $fieldId = StringUtility::getUniqueId('formengine-color-'); + $resultArray['stylesheetFiles'][] = 'EXT:ew_base/Resources/Public/JavaScript/form-engine/element/pick-color-from-image.css'; $resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::create( @@ -215,13 +184,14 @@ class PickColorFromImage extends AbstractFormElement } } - $this->templateView->assignMultiple($arguments); - $resultArray['html'] = $this->templateView->render('Form/ImageManipulationElement'); + $view = $this->backendViewFactory->create($this->data['request'], ['evoweb/ew-base']); + $view->assignMultiple($arguments); + $resultArray['html'] = $view->render('Form/ImageManipulationElement'); return $resultArray; } - protected function getFileObject(array $row, string $fieldName): ?File + protected function getFile(array $row, string $fieldName): ?File { $file = null; $fileUid = !empty($row[$fieldName]) ? $row[$fieldName] : null; @@ -230,21 +200,26 @@ class PickColorFromImage extends AbstractFormElement } if (MathUtility::canBeInterpretedAsInteger($fileUid)) { try { - $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); - $fileReference = $resourceFactory->getFileReferenceObject($fileUid); - $file = $fileReference->getOriginalFile(); + $file = $this->resourceFactory->getFileObject($fileUid); } catch (FileDoesNotExistException | \InvalidArgumentException) { } } return $file; } + /** + * @throws InvalidConfigurationException + */ protected function populateConfiguration(array $baseConfiguration): array { $defaultConfig = self::$defaultConfig; + + // If ratios are set do not add default options + if (isset($baseConfiguration['cropVariants'])) { + unset($defaultConfig['cropVariants']); + } + $config = array_replace_recursive($defaultConfig, $baseConfiguration); - $imageConfig = $this->data['processedTca']['columns'][$config['imageField']]; - $config['cropVariants'] = $imageConfig['config']['cropVariants'] ?? $defaultConfig['cropVariants']; if (!is_array($config['cropVariants'])) { throw new InvalidConfigurationException('Crop variants configuration must be an array', 1485377267); @@ -253,12 +228,17 @@ class PickColorFromImage extends AbstractFormElement $cropVariants = []; foreach ($config['cropVariants'] as $id => $cropVariant) { // Filter allowed aspect ratios - $cropVariant['allowedAspectRatios'] = array_filter( - $cropVariant['allowedAspectRatios'] ?? [], - static function ($aspectRatio) { - return !(bool)($aspectRatio['disabled'] ?? false); - } - ); + $cropVariant['allowedAspectRatios'] = array_filter($cropVariant['allowedAspectRatios'] ?? [], static function (array $aspectRatio): bool { + return !(bool)($aspectRatio['disabled'] ?? false); + }); + + // Aspect ratios may not contain a "." character, see Ratio::__construct() + // To match them again properly, same replacement is required here. + $preparedAllowedAspectRatios = []; + foreach ($cropVariant['allowedAspectRatios'] as $aspectRatio => $aspectRatioDefinition) { + $preparedAllowedAspectRatios[Ratio::prepareAspectRatioId($aspectRatio)] = $aspectRatioDefinition; + } + $cropVariant['allowedAspectRatios'] = $preparedAllowedAspectRatios; // Ignore disabled crop variants if (!empty($cropVariant['disabled'])) { @@ -266,10 +246,7 @@ class PickColorFromImage extends AbstractFormElement } if (empty($cropVariant['allowedAspectRatios'])) { - throw new InvalidConfigurationException( - 'Crop variants configuration ' . $id . ' contains no allowed aspect ratios', - 1620147893 - ); + throw new InvalidConfigurationException('Crop variants configuration ' . $id . ' contains no allowed aspect ratios', 1620147893); } // Enforce a crop area (default is full image) @@ -282,8 +259,8 @@ class PickColorFromImage extends AbstractFormElement $config['cropVariants'] = $cropVariants; + // By default we allow all image extensions that can be handled by the GFX functionality $config['allowedExtensions'] ??= $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']; - return $config; } @@ -295,10 +272,7 @@ class PickColorFromImage extends AbstractFormElement $elementValue = (string)$cropVariantCollection; } $config['cropVariants'] = $cropVariantCollection->asArray(); - $config['allowedExtensions'] = implode( - ', ', - GeneralUtility::trimExplode(',', $config['allowedExtensions'], true) - ); + $config['allowedExtensions'] = implode(', ', GeneralUtility::trimExplode(',', $config['allowedExtensions'], true)); return $config; } } diff --git a/Classes/Form/FormDataProvider/UsercentricsDatabaseEditRow.php b/Classes/Form/FormDataProvider/UsercentricsDatabaseEditRow.php deleted file mode 100755 index 1d7e571..0000000 --- a/Classes/Form/FormDataProvider/UsercentricsDatabaseEditRow.php +++ /dev/null @@ -1,56 +0,0 @@ -siteConfiguration); - if (in_array($tableName, ['site_usercentrics'], true)) { - $rootPageId = (int)($result['inlineTopMostParentUid'] ?? $result['inlineParentUid']); - try { - $rowData = $this->getRawConfigurationForSiteWithRootPageId($siteFinder, $rootPageId); - $parentFieldName = $result['inlineParentFieldName']; - if (!isset($rowData[$parentFieldName])) { - throw new \RuntimeException('Field "' . $parentFieldName . '" not found', 1520886092); - } - $rowData = $rowData[$parentFieldName][$result['vanillaUid']]; - $result['databaseRow']['uid'] = $result['vanillaUid']; - } catch (SiteNotFoundException $e) { - $rowData = []; - } - } else { - return $result; - } - - foreach ($rowData as $fieldName => $value) { - // Flat values only - databaseRow has no "tree" - if (!is_array($value)) { - $result['databaseRow'][$fieldName] = $value; - } - } - // All "records" are always on pid 0 - $result['databaseRow']['pid'] = 0; - return $result; - } -} diff --git a/Classes/Form/FormDataProvider/UsercentricsTcaInline.php b/Classes/Form/FormDataProvider/UsercentricsTcaInline.php deleted file mode 100755 index 1d8e59e..0000000 --- a/Classes/Form/FormDataProvider/UsercentricsTcaInline.php +++ /dev/null @@ -1,55 +0,0 @@ -addInlineFirstPid($result); - foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) { - if (!$this->isInlineField($fieldConfig)) { - continue; - } - $childTableName = $fieldConfig['config']['foreign_table'] ?? ''; - if (!in_array($childTableName, ['site_usercentrics'], true)) { - continue; - } - $result['processedTca']['columns'][$fieldName]['children'] = []; - $result = $this->resolveSiteRelatedChildren($result, $fieldName); - if (!empty($result['processedTca']['columns'][$fieldName]['config']['selectorOrUniqueConfiguration'])) { - throw new \RuntimeException('selectorOrUniqueConfiguration not implemented in sites module', 1624313533); - } - } - - return $result; - } -} diff --git a/Classes/Hooks/PageLayoutView.php b/Classes/Hooks/PageLayoutView.php deleted file mode 100755 index 608ccf1..0000000 --- a/Classes/Hooks/PageLayoutView.php +++ /dev/null @@ -1,34 +0,0 @@ -findCTypeBegin($params['record']['CType']); - } - - public function findCTypeBegin($cType): bool - { - $found = false; - foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['ContentUsedStrings'] ?? [] as $search) { - if (str_contains($cType, $search)) { - $found = true; - break; - } - } - return $found; - } -} diff --git a/Classes/Hooks/UsercentricsPostRenderHook.php b/Classes/Hooks/UsercentricsPostRenderHook.php deleted file mode 100755 index adf37f0..0000000 --- a/Classes/Hooks/UsercentricsPostRenderHook.php +++ /dev/null @@ -1,102 +0,0 @@ - '', - 'version' => 'loader', - 'useBlocker' => false, - ]; - - public function executePostRenderHook(array $params): void - { - $pageUid = $this->getRequest()->getAttribute('frontend.controller')->page['uid'] ?? 0; - if ($pageUid < 1) { - return; - } - - $siteArguments = $this->getCurrentSiteArguments($pageUid); - $id = $siteArguments['id'] ?? ''; - if ($id !== '') { - $extensionConfig = $this->getExtensionConfiguration(); - - $preload = []; - $usercentrics = []; - - // CMP 1, 2, ... - $version = $siteArguments['version'] ?? ''; - if ($config = $extensionConfig[$version] ?? false) { - $preload[] = $config['preload'] ?? ''; - $usercentrics[] = $config['template'] ?? ''; - } - - // Blocker - if ( - ($siteArguments['useBlocker'] ?? false) - && $config = $extensionConfig['block'] ?? false - ) { - $preload[] = $config['preload'] ?? ''; - $usercentrics[] = $config['template'] ?? ''; - } - - $params['titleTag'] .= str_replace('###ID###', $id, implode(LF, array_merge($preload, $usercentrics))); - } - } - - protected function getCurrentSiteArguments(int $pageUid): array - { - $siteArguments = $this->siteArguments; - - try { - /** @var SiteFinder $siteFinder */ - $siteFinder = GeneralUtility::makeInstance(SiteFinder::class); - $site = $siteFinder->getSiteByPageId($pageUid); - foreach ($site->getAttribute('usercentrics') as $usercentrics) { - if (str_contains($usercentrics['applicationContext'] ?? '', (string)Environment::getContext())) { - $siteArguments = $usercentrics; - break; - } - } - } catch (Exception) { - } - - return is_array($siteArguments) ? $siteArguments : []; - } - - protected function getExtensionConfiguration(): array - { - /** @var ExtensionConfiguration $extensionConfiguration */ - $extensionConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class); - try { - $configuration = $extensionConfiguration->get('ew_base', 'userCentrics'); - } catch (Exception) { - $configuration = []; - } - return is_array($configuration) ? $configuration : []; - } - - protected function getRequest(): ServerRequestInterface - { - return $GLOBALS['TYPO3_REQUEST']; - } -} diff --git a/Classes/Services/QueryBuilderHelper.php b/Classes/Services/QueryBuilderHelper.php new file mode 100644 index 0000000..aa385fa --- /dev/null +++ b/Classes/Services/QueryBuilderHelper.php @@ -0,0 +1,312 @@ +expandListParameters( + $queryBuilder->getSQL(), + $queryBuilder->getParameters(), + $queryBuilder->getParameterTypes(), + ); + + array_walk($parameters, function ($value, $key) use (&$sql, $types) { + if ($types[$key] == ParameterType::STRING) { + $value = '\'' . $value . '\''; + } elseif ($types[$key] == ParameterType::INTEGER) { + $value = '' . $value; + } elseif ($types[$key] == ArrayParameterType::INTEGER) { + $value = implode(', ', $value); + } elseif ($types[$key] == ArrayParameterType::STRING) { + $value = $value ? '"' . implode('", "', $value) . '"' : '""'; + } + if (is_int($key)) { + $sql = substr_replace($sql, (string)$value, strpos($sql, '?'), 1); + } else { + $sql = str_replace(':' . $key, $value, $sql); + } + }); + + return $sql; + } + + /** + * Gets an array of the placeholders in a sql statements as keys and their positions in the query string. + * + * For a statement with positional parameters, returns a zero-indexed list of placeholder position. + * For a statement with named parameters, returns a map of placeholder positions to their parameter names. + */ + public function getPlaceholderPositions(string $statement, bool $isPositional = true): array + { + return $isPositional + ? $this->getPositionalPlaceholderPositions($statement) + : $this->getNamedPlaceholderPositions($statement); + } + + /** + * Returns a zero-indexed list of placeholder position. + * + * @return list + */ + private function getPositionalPlaceholderPositions(string $statement): array + { + return $this->collectPlaceholders( + $statement, + '?', + '\?', + function (string $_, int $placeholderPosition, int $fragmentPosition, array &$carry): void { + $carry[] = $placeholderPosition + $fragmentPosition; + } + ); + } + + /** + * Returns a map of placeholder positions to their parameter names. + * + * @return array + */ + private function getNamedPlaceholderPositions(string $statement): array + { + return $this->collectPlaceholders( + $statement, + ':', + '(?getUnquotedStatementFragments($statement) as $fragment) { + preg_match_all('/' . $token . '/', $fragment[0], $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $placeholder) { + $collector($placeholder[0], $placeholder[1], $fragment[1], $carry); + } + } + + return $carry; + } + + /** + * For a positional query this method can rewrite the sql statement with regard to array parameters. + * + * @throws \Exception + */ + public function expandListParameters(string $query, array $params, array $types): array + { + $isPositional = is_int(key($params)); + $bindIndex = -1; + $arrayPositions = []; + + if ($isPositional) { + // make sure that $types has the same keys as $params + // to allow omitting parameters with unspecified types + $types += array_fill_keys(array_keys($params), null); + + ksort($params); + ksort($types); + } + + foreach ($types as $name => $type) { + ++$bindIndex; + + if ($type !== ArrayParameterType::INTEGER && $type !== ArrayParameterType::STRING) { + continue; + } + + if ($isPositional) { + $name = $bindIndex; + } + + $arrayPositions[$name] = false; + } + + // parameter are positional and no array parameter given + if ($isPositional && !$arrayPositions) { + return [$query, $params, $types]; + } + + return $isPositional + ? $this->preparePositionalParameters($query, $params, $types, $arrayPositions) + : $this->convertNamedParamsToPositionalParams($query, $params, $types, $arrayPositions); + } + + private function preparePositionalParameters( + string $query, + array $params, + array $types, + array $arrayPositions + ): array { + $paramOffset = 0; + $queryOffset = 0; + $params = array_values($params); + $types = array_values($types); + + $paramPos = $this->getPositionalPlaceholderPositions($query); + + foreach ($paramPos as $needle => $needlePos) { + if (!isset($arrayPositions[$needle])) { + continue; + } + + $needle += $paramOffset; + $needlePos += $queryOffset; + $count = count($params[$needle]); + + $params = array_merge( + array_slice($params, 0, $needle), + $params[$needle], + array_slice($params, $needle + 1) + ); + + $types = array_merge( + array_slice($types, 0, $needle), + $count ? + // array needles are at {@link \Doctrine\DBAL\ArrayParameterType} constants + array_fill(0, $count, $types[$needle]) : + [], + array_slice($types, $needle + 1) + ); + + $expandStr = $count ? implode(', ', array_fill(0, $count, '?')) : 'NULL'; + $query = substr($query, 0, $needlePos) . $expandStr . substr($query, $needlePos + 1); + + $paramOffset += $count - 1; // Grows larger by number of parameters minus the replaced needle. + $queryOffset += strlen($expandStr) - 1; + } + + return [$query, $params, $types]; + } + + private function convertNamedParamsToPositionalParams( + string $query, + array $params, + array $types, + array $arrayPositions + ): array { + $queryOffset = 0; + $typesOrd = []; + $paramsOrd = []; + + $paramPos = $this->getNamedPlaceholderPositions($query); + + foreach ($paramPos as $pos => $paramName) { + $paramLen = strlen($paramName) + 1; + $value = $this->extractParam($paramName, $params, true); + + if (!isset($arrayPositions[$paramName]) && !isset($arrayPositions[':' . $paramName])) { + $pos += $queryOffset; + $queryOffset -= $paramLen - 1; + $paramsOrd[] = $value; + $typesOrd[] = $this->extractParam($paramName, $types, false, ParameterType::STRING); + $query = substr($query, 0, $pos) . '?' . substr($query, $pos + $paramLen); + + continue; + } + + $count = count($value); + $expandStr = $count > 0 ? implode(', ', array_fill(0, $count, '?')) : 'NULL'; + + foreach ($value as $val) { + $paramsOrd[] = $val; + $type = $this->extractParam($paramName, $types, false); + $typesOrd[] = match ($type) { + ArrayParameterType::INTEGER => ParameterType::INTEGER, + ArrayParameterType::STRING => ParameterType::STRING, + default => $type + }; + } + + $pos += $queryOffset; + $queryOffset += strlen($expandStr) - $paramLen; + $query = substr($query, 0, $pos) . $expandStr . substr($query, $pos + $paramLen); + } + + return [$query, $paramsOrd, $typesOrd]; + } + + /** + * Slice the SQL statement around pairs of quotes and + * return string fragments of outside SQL of quoted literals. + * Each fragment is captured as a 2-element array: + * + * 0 => matched fragment string, + * 1 => offset of fragment in $statement + */ + private function getUnquotedStatementFragments(string $statement): array + { + $literal = "(?:'(?:\\\\)+'|'(?:[^'\\\\]|\\\\'?|'')*')" + . '|' . '(?:"(?:\\\\)+"|"(?:[^"\\\\]|\\\\"?)*")' + . '|' . '(?:`(?:\\\\)+`|`(?:[^`\\\\]|\\\\`?)*`)' + . '|' . '(?request = $request; - } - - public function checkAccess(): bool - { - $result = false; - $backendUser = $this->getBackendUser(); - if ($backendUser->isAdmin()) { - $result = true; - } - if ($backendUser->getTSConfig()['options.']['showSystemInformation'] ?? false) { - $result = true; - } - $release = $this->getRelease(); - if (empty($release)) { - $result = false; - } - return $result; - } - - public function getItem(): string - { - $view = $this->backendViewFactory->create($this->request, ['typo3/cms-backend', 'evoweb/ew-base']); - return $view->render('ToolbarItems/ShowReleaseToolbarItem'); - } - - public function hasDropDown(): bool - { - return true; - } - - public function getDropDown(): string - { - $view = $this->backendViewFactory->create($this->request, ['typo3/cms-backend', 'evoweb/ew-base']); - $view->assignMultiple([ - 'release' => $this->getRelease(), - ]); - return $view->render('ToolbarItems/ShowReleaseDropDown'); - } - - public function getAdditionalAttributes(): array - { - return []; - } - - public function getIndex(): int - { - return 20; - } - - protected function getRelease(): array - { - $release = GeneralUtility::getUrl(Environment::getProjectPath() . '/release'); - return $release ? [ - 'release' => trim($release), - 'isTag' => str_contains($release, '.'), - ] : []; - } - - protected function getBackendUser(): BackendUserAuthentication - { - return $GLOBALS['BE_USER']; - } -} diff --git a/Classes/Updates/GridelementsToContainerMigration.php b/Classes/Updates/GridelementsToContainerMigration.php deleted file mode 100755 index fe6e1b0..0000000 --- a/Classes/Updates/GridelementsToContainerMigration.php +++ /dev/null @@ -1,101 +0,0 @@ -logger = $logManager->getLogger(self::class); - } - - public function getIdentifier(): string - { - return 'gridelementsToContainer'; - } - - public function getTitle(): string - { - return 'Migrate gridelements to container'; - } - - public function getDescription(): string - { - return 'Migrates content elements that are type gridelements to corresponding container elements'; - } - - public function getPrerequisites(): array - { - return [ - DatabaseUpdatedPrerequisite::class - ]; - } - - public function updateNecessary(): bool - { - return $this->hasRecordsToUpdate(); - } - - public function executeUpdate(): bool - { - $updateSuccessful = true; - try { - $this->service->migrate(); - } catch (\Exception $exception) { - $this->logger->log(LogLevel::ERROR, $exception->getMessage()); - $updateSuccessful = false; - } - - return $updateSuccessful; - } - - protected function hasRecordsToUpdate(): bool - { - return (bool)$this->getPreparedQueryBuilder()->count('uid')->executeQuery()->fetchOne(); - } - - protected function getPreparedQueryBuilder(): QueryBuilder - { - $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable(self::TABLE_NAME); - $queryBuilder->getRestrictions() - ->removeByType(HiddenRestriction::class) - ->removeByType(StartTimeRestriction::class) - ->removeByType(EndTimeRestriction::class); - $queryBuilder - ->from(self::TABLE_NAME) - ->where( - $queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('gridelements_pi1')) - ); - return $queryBuilder; - } - - protected function getConnectionPool(): ConnectionPool - { - return GeneralUtility::makeInstance(ConnectionPool::class); - } -} diff --git a/Classes/Updates/GridelementsToContainerService.php b/Classes/Updates/GridelementsToContainerService.php deleted file mode 100755 index 4b0cdac..0000000 --- a/Classes/Updates/GridelementsToContainerService.php +++ /dev/null @@ -1,268 +0,0 @@ - [ - 'CType' => 'three-col-columns', - ], - '140' => [ - 'CType' => [ - 'search' => 'pi_flexform/type', - 'matches' => [ - 0 => 'two-col-columns-11', - 1 => 'two-col-columns-12', - 2 => 'two-col-columns-21', - ], - ], - 'map' => [ - 'pi_flexform/row_class' => 'frame_class', - ] - ], -]; -*/ - -class GridelementsToContainerService -{ - private const TABLE_NAME = 'tt_content'; - - protected array $resolveContainer = []; - - protected int $colPosOffset = 0; - - protected array $configuration = []; - - public function __construct( - protected ConnectionPool $connectionPool, - protected DataHandler $dataHandler, - protected FlexFormService $flexFormService, - ) { - $config =& $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']; - $this->resolveContainer = $config['migrationResolveContainer'] ?? []; - $this->colPosOffset = $config['migrationColPosOffset'] ?? 0; - $this->configuration = $config['migrationMapping'] ?? []; - } - - public function migrate(): void - { - $this->initializeDataHandler(); - - // Move children out of container and remove container - foreach ($this->resolveContainer as $containerId) { - $this->resolveContainers((string)$containerId); - } - - $this->migrateConfiguredContainer(); - } - - protected function initializeDataHandler(): void - { - $backendUser = GeneralUtility::makeInstance(BackendUserAuthentication::class); - $backendUser->user = [ - 'uid' => 0, - 'admin' => 1, - ]; - $this->dataHandler->start([], [], $backendUser); - } - - - protected function resolveContainers(string $layout): void - { - $containers = $this->getGridElementsByLayout($layout); - foreach ($containers as $container) { - $this->processContainerResolve($container); - } - } - - protected function processContainerResolve(array $container): void - { - $children = $this->getGridContainerChildren($container['uid']); - // move first child after container - $moveAfterThis = $container; - foreach ($children as $child) { - [$moveAfterThis, $container] = $this->processContainerResolveChild($child, $moveAfterThis, $container); - } - - $this->deleteElement($container['uid']); - } - - protected function processContainerResolveChild(array $child, array $moveAfterThis, array $container): array - { - $this->updateElement( - $child['uid'], - [ - 'tx_gridelements_container' => 0, - 'colPos' => $container['colPos'], - 'header' => $child['header'] ?: $container['header'] - ] - ); - $this->moveElementAfterElement($child['uid'], $moveAfterThis['uid']); - // use this child to move the next child after - $moveAfterThis = $child; - // empty container header so only the first child gets the header - $container['header'] = ''; - - return [$moveAfterThis, $container]; - } - - protected function deleteElement(int $uid): void - { - $this->connectionPool - ->getConnectionForTable(self::TABLE_NAME) - ->update(self::TABLE_NAME, ['delete' => 1], ['uid' => $uid]); - } - - protected function moveElementAfterElement(int $elementToMove, int $elementToMoveAfter): void - { - $this->dataHandler->moveRecord(self::TABLE_NAME, $elementToMove, $elementToMoveAfter * -1); - } - - - protected function migrateConfiguredContainer(): void - { - array_walk($this->configuration, function($config, $key) { - $containers = $this->getGridElementsByLayout((string)$key); - foreach ($containers as $container) { - $container['pi_flexform'] = $this->flexFormService->convertFlexFormContentToArray( - $container['pi_flexform'] - ); - $this->processContainerMigration($container, $config); - } - }); - } - - protected function processContainerMigration(array $container, array $config): void - { - $children = $this->getGridContainerChildren($container['uid']); - foreach ($children as $child) { - $this->processContainerMigrationChild($child, $container); - } - - $data = [ - 'CType' => $this->getCType($container, $config), - 'tx_gridelements_backend_layout' => '' - ]; - - if (isset($config['map']) && is_array($config['map']) && !empty($container['pi_flexform'])) { - $data = $this->addMappedValues($data, $container, $config['map']); - } - - $this->updateElement($container['uid'], $data); - } - - protected function processContainerMigrationChild(array $child, array $container): void - { - $this->updateElement( - $child['uid'], - [ - 'hidden' => $child['hidden'] ?: $container['hidden'], - 'colPos' => $child['tx_gridelements_columns'] + $this->colPosOffset, - 'tx_container_parent' => $child['tx_gridelements_container'], - 'tx_gridelements_columns' => 0, - 'tx_gridelements_container' => 0, - ] - ); - } - - protected function getCType(array $container, array $config): string - { - if (is_array($config['CType'])) { - $value = ArrayUtility::getValueByPath($container, $config['CType']['search']); - $result = $config['CType']['matches'][$value] ?? null; - } else { - $result = $config['CType'] ?? null; - } - - if (empty($result)) { - throw new \Exception('CType must always be set'); - } - - return $result; - } - - protected function addMappedValues(array $data, array $container, array $config): array - { - foreach ($config as $from => $to) { - try { - $value = ArrayUtility::getValueByPath($container, $from); - if (empty($container[$to]) && !empty($value)) { - $data[$to] = $value; - } - } catch (\throwable) {} - } - - return $data; - } - - - protected function updateElement(int $uid, array $changes): void - { - $this->connectionPool - ->getConnectionForTable(self::TABLE_NAME) - ->update(self::TABLE_NAME, $changes, ['uid' => $uid]); - } - - protected function getGridContainerChildren(int $containerUid): array - { - $queryBuilder = $this->getQueryBuilderForTable(); - - return $queryBuilder - ->select('*') - ->where( - $queryBuilder->expr()->eq( - 'tx_gridelements_container', - $queryBuilder->createNamedParameter($containerUid, \PDO::PARAM_INT) - ) - ) - ->orderBy('sorting') - ->executeQuery() - ->fetchAllAssociative(); - } - - protected function getGridElementsByLayout(string $layout): array - { - $queryBuilder = $this->getQueryBuilderForTable(); - $expr = $queryBuilder->expr(); - - return $queryBuilder - ->select('*') - ->where( - $expr->eq('CType', $queryBuilder->createNamedParameter('gridelements_pi1')), - $expr->eq('tx_gridelements_backend_layout', $queryBuilder->createNamedParameter($layout)) - ) - ->orderBy('sorting') - ->executeQuery() - ->fetchAllAssociative(); - } - - protected function getQueryBuilderForTable(string $table = 'tt_content'): QueryBuilder - { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable($table); - - $queryBuilder->getRestrictions() - ->removeByType(HiddenRestriction::class) - ->removeByType(StartTimeRestriction::class) - ->removeByType(EndTimeRestriction::class); - - $queryBuilder->from($table); - - return $queryBuilder; - } -} diff --git a/Classes/Updates/ParentChildToContainerMigration.php b/Classes/Updates/ParentChildToContainerMigration.php deleted file mode 100755 index 7fe8ea2..0000000 --- a/Classes/Updates/ParentChildToContainerMigration.php +++ /dev/null @@ -1,111 +0,0 @@ -logger = $logManager->getLogger(self::class); - } - - public function getIdentifier(): string - { - return 'parentChildToContainer'; - } - - public function getTitle(): string - { - return 'Migrate parent/child to container'; - } - - public function getDescription(): string - { - return 'Migrates content elements that are type parent/child to corresponding container elements'; - } - - public function getPrerequisites(): array - { - return [ - DatabaseUpdatedPrerequisite::class - ]; - } - - public function updateNecessary(): bool - { - return $this->hasRecordsToUpdate(); - } - - public function executeUpdate(): bool - { - $updateSuccessful = true; - try { - $this->service->migrate(); - } catch (\Exception $exception) { - $this->logger->log(LogLevel::ERROR, $exception->getMessage()); - $updateSuccessful = false; - } - - return $updateSuccessful; - } - - protected function hasRecordsToUpdate(): bool - { - $queryBuilder = $this->getPreparedQueryBuilder(); - $tableColumns = $queryBuilder - ->getConnection() - ->createSchemaManager() - ->listTableColumns(self::TABLE_NAME); - return isset($tableColumns['parent']) - && $queryBuilder - ->count('uid') - ->executeQuery() - ->fetchOne() > 0; - } - - protected function getPreparedQueryBuilder(): QueryBuilder - { - $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable(self::TABLE_NAME); - $queryBuilder->getRestrictions() - ->removeByType(HiddenRestriction::class) - ->removeByType(StartTimeRestriction::class) - ->removeByType(EndTimeRestriction::class); - $queryBuilder - ->from(self::TABLE_NAME) - ->where( - // check if there are still records that have parent instead of tx_container_parent - $queryBuilder->expr()->gt('parent', 0) - ); - return $queryBuilder; - } - - protected function getConnectionPool(): ConnectionPool - { - return GeneralUtility::makeInstance(ConnectionPool::class); - } -} diff --git a/Classes/Updates/ParentChildToContainerService.php b/Classes/Updates/ParentChildToContainerService.php deleted file mode 100755 index 737ed75..0000000 --- a/Classes/Updates/ParentChildToContainerService.php +++ /dev/null @@ -1,168 +0,0 @@ - [ 'CType' => 'container-downloads' ], -]; -*/ - -class ParentChildToContainerService -{ - private const TABLE_NAME = 'tt_content'; - - protected int $colPosOffset = 0; - - protected array $configuration = []; - - public function __construct( - protected ConnectionPool $connectionPool, - protected DataHandler $dataHandler, - protected FlexFormService $flexFormService, - ) { - $config =& $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']; - $this->colPosOffset = $config['childParentColPosOffset'] ?? 0; - $this->configuration = $config['childParentMigrationMapping'] ?? []; - } - - public function migrate(): void - { - $this->initializeDataHandler(); - $this->migrateConfiguredContainer(); - } - - protected function initializeDataHandler(): void - { - $backendUser = GeneralUtility::makeInstance(BackendUserAuthentication::class); - $backendUser->user = [ - 'uid' => 0, - 'admin' => 1, - ]; - $this->dataHandler->start([], [], $backendUser); - } - - - protected function migrateConfiguredContainer(): void - { - array_walk($this->configuration, function($config, $key) { - $parents = $this->getParentsByCType((string)$key); - foreach ($parents as $parent) { - $this->processParentMigration($parent, $config); - } - }); - } - - protected function processParentMigration(array $parent, array $config): void - { - $children = $this->getChildren($parent['uid']); - foreach ($children as $child) { - $this->processChildMigration($child, $parent); - } - - $this->updateElement( - $parent['uid'], - [ - 'CType' => $this->getCType($parent, $config), - 'children' => 0, - ] - ); - } - - protected function processChildMigration(array $child, array $parent): void - { - $this->updateElement( - $child['uid'], - [ - 'tx_container_parent' => $parent['uid'], - 'colPos' => $child['colPos'] + $this->colPosOffset, - 'parent' => 0, - ] - ); - } - - protected function getCType(array $container, array $config): string - { - if (is_array($config['CType'])) { - $value = ArrayUtility::getValueByPath($container, $config['CType']['search']); - $result = $config['CType']['matches'][$value] ?? null; - } else { - $result = $config['CType'] ?? null; - } - - if (empty($result)) { - throw new \Exception('CType must always be set'); - } - - return $result; - } - - - protected function updateElement(int $uid, array $changes): void - { - $this->connectionPool - ->getConnectionForTable(self::TABLE_NAME) - ->update(self::TABLE_NAME, $changes, ['uid' => $uid]); - } - - protected function getChildren(int $parentUid): array - { - $queryBuilder = $this->getQueryBuilderForTable(); - - return $queryBuilder - ->select('*') - ->where( - $queryBuilder->expr()->eq( - 'parent', - $queryBuilder->createNamedParameter($parentUid, \PDO::PARAM_INT) - ) - ) - ->orderBy('sorting') - ->executeQuery() - ->fetchAllAssociative(); - } - - protected function getParentsByCType(string $cType): array - { - $queryBuilder = $this->getQueryBuilderForTable(); - $expr = $queryBuilder->expr(); - - return $queryBuilder - ->select('*') - ->where( - $expr->eq('CType', $queryBuilder->createNamedParameter($cType)) - ) - ->orderBy('sorting') - ->executeQuery() - ->fetchAllAssociative(); - } - - protected function getQueryBuilderForTable(string $table = 'tt_content'): QueryBuilder - { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable($table); - - $queryBuilder->getRestrictions() - ->removeByType(HiddenRestriction::class) - ->removeByType(StartTimeRestriction::class) - ->removeByType(EndTimeRestriction::class); - - $queryBuilder->from($table); - - return $queryBuilder; - } -} diff --git a/Classes/User/AssetPath.php b/Classes/User/AssetPath.php old mode 100755 new mode 100644 index 9659830..8232df3 --- a/Classes/User/AssetPath.php +++ b/Classes/User/AssetPath.php @@ -2,6 +2,17 @@ declare(strict_types=1); +/* + * This file is developed by evoWeb. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + */ + namespace Evoweb\EwBase\User; use TYPO3\CMS\Core\Utility\GeneralUtility; diff --git a/Classes/ViewHelpers/Array/AddViewHelper.php b/Classes/ViewHelpers/Array/AddViewHelper.php old mode 100755 new mode 100644 index 1d0b5c9..06357e8 --- a/Classes/ViewHelpers/Array/AddViewHelper.php +++ b/Classes/ViewHelpers/Array/AddViewHelper.php @@ -1,6 +1,6 @@ registerArgument('array', 'array', 'Array to add value to'); $this->registerArgument('key', 'string', 'Key to add value by', true); $this->registerArgument('value', 'mixed', 'Value to add'); } - /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * - * @return array - */ - public static function renderStatic( - array $arguments, - \Closure $renderChildrenClosure, - RenderingContextInterface $renderingContext - ) { - $array = $arguments['array'] ?: []; - $key = $arguments['key']; - $value = !is_null($arguments['value']) ? $arguments['value'] : $renderChildrenClosure(); + public function render(): array + { + $array = $this->arguments['array'] ?: []; + $key = $this->arguments['key']; + $value = !is_null($this->arguments['value']) ? $this->arguments['value'] : $this->renderChildren(); return array_merge($array, [$key => $value]); } diff --git a/Classes/ViewHelpers/Array/ExplodeViewHelper.php b/Classes/ViewHelpers/Array/ExplodeViewHelper.php new file mode 100755 index 0000000..0d04347 --- /dev/null +++ b/Classes/ViewHelpers/Array/ExplodeViewHelper.php @@ -0,0 +1,84 @@ + ewb:iterator.explode(glue: 'constant:LF')} + * + * + * {as} + * + */ +class ExplodeViewHelper extends AbstractViewHelper +{ + protected string $method = 'explode'; + + public function initializeArguments(): void + { + $this->registerArgument( + 'as', + 'string', + 'Template variable name to assign; if not specified the ViewHelper returns the variable instead.', + ); + $this->registerArgument('content', 'string', 'String to be exploded by glue'); + $this->registerArgument( + 'glue', + 'string', + 'String used as glue in the string to be exploded. Use glue value of "constant:NAMEOFCONSTANT" ' + . '(fx "constant:LF" for linefeed as glue)', + false, + ',' + ); + } + + public function render(): mixed + { + $content = $this->arguments['content'] ?? $this->renderChildren(); + $glue = $this->resolveGlue($this->arguments); + $content = call_user_func_array($this->method, [$glue, $content]); + + $as = $this->arguments['as']; + if ($as !== null) { + $templateVariableContainer = $this->renderingContext->getVariableProvider(); + $templateVariableContainer->add($as, $content); + $output = $this->renderChildren(); + $templateVariableContainer->remove($as); + } else { + $output = $content; + } + return $output; + } + + protected function resolveGlue(array $arguments): string + { + $glue = $arguments['glue']; + if (str_contains($glue, ':') && strlen($glue) > 1) { + // glue contains a special type identifier, resolve the actual glue + [$type, $value] = explode(':', $glue); + $glue = match ($type) { + 'constant' => constant($value), + default => $value, + }; + } + return $glue; + } +} diff --git a/Classes/ViewHelpers/Be/ThumbnailViewHelper.php b/Classes/ViewHelpers/Be/ThumbnailViewHelper.php old mode 100755 new mode 100644 index f229195..0fa73a2 --- a/Classes/ViewHelpers/Be/ThumbnailViewHelper.php +++ b/Classes/ViewHelpers/Be/ThumbnailViewHelper.php @@ -19,21 +19,17 @@ use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Imaging\IconFactory; +use TYPO3\CMS\Core\Imaging\IconSize; use TYPO3\CMS\Core\Localization\LanguageService; +use TYPO3\CMS\Core\Resource\ProcessedFile; use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; -use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; class ThumbnailViewHelper extends AbstractViewHelper { - use CompileWithRenderStatic; - - /** - * Initializes the arguments - */ - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('row', 'array', 'content data', true); @@ -44,34 +40,47 @@ class ThumbnailViewHelper extends AbstractViewHelper /** * Render a constant * - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * * @return string Value of constant */ - public static function renderStatic( - array $arguments, - \Closure $renderChildrenClosure, - RenderingContextInterface $renderingContext - ) { - $row = $arguments['row']; - $tableName = $arguments['tableName']; - $fieldName = $arguments['fieldName']; + public function render(): string + { + $table = $this->arguments['tableName']; + $field = $this->arguments['fieldName']; + $row = $this->arguments['row']; - return self::linkEditContent( - BackendUtility::thumbCode(row: $row, table: $tableName, field: $fieldName, linkInfoPopup: false), - $row + $fileReferences = BackendUtility::resolveFileReferences($table, $field, $row); + $fileObject = is_array($fileReferences) ? $fileReferences[0]->getOriginalFile() : null; + + if ($fileObject && $fileObject->isMissing()) { + /** @var IconFactory $iconFactory */ + $iconFactory = GeneralUtility::makeInstance(IconFactory::class); + $label = $this->getLanguageService()->sL( + 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing' + ); + return $iconFactory + ->getIcon('mimetypes-other-other', IconSize::MEDIUM, 'overlay-missing') + ->setTitle($label . ' ' . $fileObject->getName()) + ->render(); + } + + $previewFile = $fileObject->process( + ProcessedFile::CONTEXT_IMAGEPREVIEW, + [ + 'width' => 64, + 'height' => 64, + ] ); + + return $this->linkEditContent('', $row ); } - protected static function linkEditContent(string $linkText, $row): string + protected function linkEditContent(string $linkText, $row): string { if (empty($linkText)) { return $linkText; } - $backendUser = self::getBackendUser(); + $backendUser = $this->getBackendUser(); if ( $backendUser->check('tables_modify', 'tt_content') && $backendUser->recordEditAccessInternals('tt_content', $row) @@ -87,14 +96,15 @@ class ThumbnailViewHelper extends AbstractViewHelper $row['uid'] => 'edit', ], ], - 'returnUrl' => self::getRequest()->getAttribute('normalizedParams')->getRequestUri() + // @extensionScannerIgnoreLine + 'returnUrl' => $this->getRequest()->getAttribute('normalizedParams')->getRequestUri() . '#element-tt_content-' . $row['uid'], ]; /** @var UriBuilder $uriBuilder */ $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters); return '' . $linkText . ''; @@ -102,17 +112,17 @@ class ThumbnailViewHelper extends AbstractViewHelper return $linkText; } - protected static function getBackendUser(): BackendUserAuthentication + protected function getBackendUser(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } - protected static function getLanguageService(): LanguageService + protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } - protected static function getRequest(): ServerRequestInterface + protected function getRequest(): ServerRequestInterface { return $GLOBALS['TYPO3_REQUEST']; } diff --git a/Classes/ViewHelpers/Condition/InArrayViewHelper.php b/Classes/ViewHelpers/Condition/InArrayViewHelper.php old mode 100755 new mode 100644 index bc713f5..36b47a2 --- a/Classes/ViewHelpers/Condition/InArrayViewHelper.php +++ b/Classes/ViewHelpers/Condition/InArrayViewHelper.php @@ -1,14 +1,21 @@ registerArgument('haystack', 'array', 'haystack', true); $this->registerArgument('needle', 'mixed', 'needle', true); } - /** - * @param array $arguments - * @return bool - */ - protected static function evaluateCondition($arguments = null) + public static function verdict(array $arguments, RenderingContextInterface $renderingContext): bool { $array = $arguments['haystack']->toArray(); return in_array($arguments['needle'], $array); diff --git a/Classes/ViewHelpers/Condition/StringContainsViewHelper.php b/Classes/ViewHelpers/Condition/StringContainsViewHelper.php old mode 100755 new mode 100644 index efb6b6f..7b28cc6 --- a/Classes/ViewHelpers/Condition/StringContainsViewHelper.php +++ b/Classes/ViewHelpers/Condition/StringContainsViewHelper.php @@ -1,14 +1,21 @@ registerArgument('haystack', 'string', 'haystack', true); $this->registerArgument('needle', 'string', 'need', true); } - /** - * @param array $arguments - * @return bool - */ - protected static function evaluateCondition($arguments = null) + public static function verdict(array $arguments, RenderingContextInterface $renderingContext): bool { - return false !== strpos($arguments['haystack'], $arguments['needle']); + return str_contains($arguments['haystack'], $arguments['needle']); } } diff --git a/Classes/ViewHelpers/Context/DevelopmentViewHelper.php b/Classes/ViewHelpers/Context/DevelopmentViewHelper.php old mode 100755 new mode 100644 index 0875df1..fe4a5b2 --- a/Classes/ViewHelpers/Context/DevelopmentViewHelper.php +++ b/Classes/ViewHelpers/Context/DevelopmentViewHelper.php @@ -1,24 +1,27 @@ isDevelopment(); } diff --git a/Classes/ViewHelpers/Context/ProductionViewHelper.php b/Classes/ViewHelpers/Context/ProductionViewHelper.php old mode 100755 new mode 100644 index f5356bb..e57fc31 --- a/Classes/ViewHelpers/Context/ProductionViewHelper.php +++ b/Classes/ViewHelpers/Context/ProductionViewHelper.php @@ -1,24 +1,27 @@ isProduction(); } diff --git a/Classes/ViewHelpers/Context/StagingViewHelper.php b/Classes/ViewHelpers/Context/StagingViewHelper.php old mode 100755 new mode 100644 index a76003e..bbbae08 --- a/Classes/ViewHelpers/Context/StagingViewHelper.php +++ b/Classes/ViewHelpers/Context/StagingViewHelper.php @@ -1,24 +1,27 @@ registerArgument('data', 'array', 'The data array of content element', true); } - /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * - * @return array - */ - public static function renderStatic( - array $arguments, - \Closure $renderChildrenClosure, - RenderingContextInterface $renderingContext - ) { - $templateVariableContainer = $renderingContext->getVariableProvider(); + public function render(): string + { + $templateVariableContainer = $this->renderingContext->getVariableProvider(); /** @var FlexFormTools $flexFormTools */ $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class); - $data = $arguments['data']; + $data = $this->arguments['data']; if (is_array($data['pi_flexform'])) { $data['pi_flexform'] = $flexFormTools->flexArray2Xml($data['pi_flexform']); } $templateVariableContainer->add('data', $data); - $output = $renderChildrenClosure(); + $output = $this->renderChildren(); $templateVariableContainer->remove('data'); return $output; diff --git a/Classes/ViewHelpers/FlexFormViewHelper.php b/Classes/ViewHelpers/FlexFormViewHelper.php old mode 100755 new mode 100644 index cd7fc27..ea793b0 --- a/Classes/ViewHelpers/FlexFormViewHelper.php +++ b/Classes/ViewHelpers/FlexFormViewHelper.php @@ -1,6 +1,6 @@ registerArgument('data', 'array', 'Array to get flex form data from', true); @@ -47,33 +45,23 @@ class FlexFormViewHelper extends AbstractViewHelper $this->registerArgument('as', 'string', 'Name of the variable to assign', false, 'flexFormData'); } - /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * - * @return array - */ - public static function renderStatic( - array $arguments, - \Closure $renderChildrenClosure, - RenderingContextInterface $renderingContext - ) { + public function render(): string + { $data = []; - if (is_array($arguments['data'])) { - if (isset($arguments['data'][$arguments['fieldName']])) { - $data = is_array($arguments['data'][$arguments['fieldName']]) ? - $arguments['data'][$arguments['fieldName']] : - GeneralUtility::xml2array($arguments['data'][$arguments['fieldName']]); + if (is_array($this->arguments['data'])) { + if (isset($this->arguments['data'][$this->arguments['fieldName']])) { + $data = is_array($this->arguments['data'][$this->arguments['fieldName']]) ? + $this->arguments['data'][$this->arguments['fieldName']] : + GeneralUtility::xml2array($this->arguments['data'][$this->arguments['fieldName']]); $data = $data['data'] ?? $data; } } - $templateVariableContainer = $renderingContext->getVariableProvider(); - $templateVariableContainer->add($arguments['as'], $data); - $content = $renderChildrenClosure(); - $templateVariableContainer->remove($arguments['as']); + $templateVariableContainer = $this->renderingContext->getVariableProvider(); + $templateVariableContainer->add($this->arguments['as'], $data); + $content = $this->renderChildren(); + $templateVariableContainer->remove($this->arguments['as']); return $content; } diff --git a/Classes/ViewHelpers/HashViewHelper.php b/Classes/ViewHelpers/HashViewHelper.php deleted file mode 100755 index 0b61138..0000000 --- a/Classes/ViewHelpers/HashViewHelper.php +++ /dev/null @@ -1,60 +0,0 @@ -registerArgument('action', 'string', 'Target action'); - $this->registerArgument('arguments', 'array', 'Arguments for the controller action, associative array'); - } - - /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * @return string - */ - public static function renderStatic( - array $arguments, - \Closure $renderChildrenClosure, - RenderingContextInterface $renderingContext - ) { - $result = ''; - if ( - $arguments['action'] !== null - && $arguments['arguments'] !== null - && isset($arguments['arguments']['user']) - ) { - $result = \TYPO3\CMS\Core\Utility\GeneralUtility::hmac( - $arguments['action'] . '::' . $arguments['arguments']['user'] - ); - } - - return $result; - } -} diff --git a/Classes/ViewHelpers/Iterator/AddViewHelper.php b/Classes/ViewHelpers/Iterator/AddViewHelper.php deleted file mode 100755 index bb24422..0000000 --- a/Classes/ViewHelpers/Iterator/AddViewHelper.php +++ /dev/null @@ -1,56 +0,0 @@ -registerArgument('array', 'array', 'Array to add value to'); - $this->registerArgument('key', 'string', 'Key to add value by', true); - $this->registerArgument('value', 'mixed', 'Value to add'); - } - - /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * - * @return array - */ - public static function renderStatic( - array $arguments, - \Closure $renderChildrenClosure, - RenderingContextInterface $renderingContext - ) { - $array = $arguments['array'] ?: []; - $key = $arguments['key']; - $value = $arguments['value'] ?: $renderChildrenClosure(); - - return array_merge($array, [$key => $value]); - } -} diff --git a/Classes/ViewHelpers/Iterator/ExplodeViewHelper.php b/Classes/ViewHelpers/Iterator/ExplodeViewHelper.php deleted file mode 100755 index e50139f..0000000 --- a/Classes/ViewHelpers/Iterator/ExplodeViewHelper.php +++ /dev/null @@ -1,93 +0,0 @@ - ewb:iterator.explode(glue: 'constant:LF')} - * - * - * {as} - * - */ -class ExplodeViewHelper extends AbstractViewHelper -{ - use CompileWithRenderStatic; - - protected static string $method = 'explode'; - - public function initializeArguments() - { - $this->registerArgument( - 'as', - 'string', - 'Template variable name to assign; if not specified the ViewHelper returns the variable instead.' - ); - $this->registerArgument('content', 'string', 'String to be exploded by glue'); - $this->registerArgument( - 'glue', - 'string', - 'String used as glue in the string to be exploded. Use glue value of "constant:NAMEOFCONSTANT" ' . - '(fx "constant:LF" for linefeed as glue)', - false, - ',' - ); - } - - /** - * Render method - * - * @return string|array - */ - public static function renderStatic( - array $arguments, - \Closure $renderChildrenClosure, - RenderingContextInterface $renderingContext - ) { - $content = $arguments['content'] ?? $renderChildrenClosure(); - $glue = static::resolveGlue($arguments); - $content = call_user_func_array(static::$method, [$glue, $content]); - - $as = $arguments['as']; - if (true === empty($as)) { - $output = $content; - } else { - $templateVariableContainer = $renderingContext->getVariableProvider(); - $templateVariableContainer->add($as, $content); - $output = $renderChildrenClosure(); - $templateVariableContainer->remove($as); - } - return $output; - } - - protected static function resolveGlue(array $arguments): string - { - $glue = $arguments['glue']; - if (false !== strpos($glue, ':') && 1 < strlen($glue)) { - // glue contains a special type identifier, resolve the actual glue - list ($type, $value) = explode(':', $glue); - switch ($type) { - case 'constant': - $glue = constant($value); - break; - default: - $glue = $value; - } - } - return $glue; - } -} diff --git a/Classes/ViewHelpers/PublicPathViewHelper.php b/Classes/ViewHelpers/PublicPathViewHelper.php old mode 100755 new mode 100644 index 769f001..f9c1329 --- a/Classes/ViewHelpers/PublicPathViewHelper.php +++ b/Classes/ViewHelpers/PublicPathViewHelper.php @@ -2,8 +2,6 @@ declare(strict_types=1); -namespace Evoweb\EwBase\ViewHelpers; - /* * This file is developed by evoWeb. * @@ -15,10 +13,11 @@ namespace Evoweb\EwBase\ViewHelpers; * LICENSE.txt file that was distributed with this source code. */ +namespace Evoweb\EwBase\ViewHelpers; + +use TYPO3\CMS\Core\Resource\Exception\InvalidFileException; use TYPO3\CMS\Core\Utility\PathUtility; -use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; -use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; /** * = Examples = @@ -34,34 +33,23 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; */ class PublicPathViewHelper extends AbstractViewHelper { - use CompileWithRenderStatic; - /** * @var boolean */ protected $escapeOutput = false; - protected static ?array $frontendGroupIds = null; - - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('path', 'string', 'Extension resource path', true); } /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * - * @return bool + * @throws InvalidFileException */ - public static function renderStatic( - array $arguments, - \Closure $renderChildrenClosure, - RenderingContextInterface $renderingContext - ) { - $path = (string)$arguments['path']; + public function renderStatic(): string + { + $path = (string)$this->arguments['path']; return PathUtility::getPublicResourceWebPath($path); } } diff --git a/Classes/ViewHelpers/ReplaceViewHelper.php b/Classes/ViewHelpers/ReplaceViewHelper.php old mode 100755 new mode 100644 index 77a9837..a9fe781 --- a/Classes/ViewHelpers/ReplaceViewHelper.php +++ b/Classes/ViewHelpers/ReplaceViewHelper.php @@ -1,6 +1,6 @@ registerArgument('value', 'string', 'String to replace in'); $this->registerArgument('search', 'string', 'Search string'); $this->registerArgument('replace', 'string', 'Replace value'); } - /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * @return null - */ - public static function renderStatic( - array $arguments, - \Closure $renderChildrenClosure, - RenderingContextInterface $renderingContext - ) { - $content = $arguments['value']; + public function render(): string + { + $content = $this->arguments['value']; if ($content === null) { - $content = $renderChildrenClosure(); + $content = $this->renderChildren(); } - return str_replace($arguments['search'], $arguments['replace'], $content); + return str_replace($this->arguments['search'], $this->arguments['replace'], $content); } } diff --git a/Classes/ViewHelpers/SvgViewHelper.php b/Classes/ViewHelpers/SvgViewHelper.php old mode 100755 new mode 100644 index 2290936..6dc418f --- a/Classes/ViewHelpers/SvgViewHelper.php +++ b/Classes/ViewHelpers/SvgViewHelper.php @@ -1,6 +1,6 @@ * */ -class SvgViewHelper extends \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper +class SvgViewHelper extends AbstractViewHelper { - use CompileWithRenderStatic; - /** * ViewHelper returns HTML, thus we need to disable output escaping * @@ -75,30 +74,25 @@ class SvgViewHelper extends \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper public function initializeArguments(): void { $this->registerArgument('identifier', 'string', 'Identifier of the icon as registered in the Icon Registry.', true); - $this->registerArgument('size', 'string', 'Desired size of the icon. All values of the Icons.sizes enum are allowed, these are: "small", "default", "large" and "overlay".', false, Icon::SIZE_SMALL); - $this->registerArgument('overlay', 'string', 'Identifier of an overlay icon as registered in the Icon Registry.', false); + $this->registerArgument('size', 'string', 'Desired size of the icon. All values of the Icons.sizes enum are allowed, these are: "small", "default", "large" and "overlay".', false, IconSize::SMALL); + $this->registerArgument('overlay', 'string', 'Identifier of an overlay icon as registered in the Icon Registry.'); $this->registerArgument('state', 'string', 'Sets the state of the icon. All values of the Icons.states enum are allowed, these are: "default" and "disabled".', false, IconState::STATE_DEFAULT); - $this->registerArgument('alternativeMarkupIdentifier', 'string', 'Alternative icon identifier. Takes precedence over the identifier if supported by the IconProvider.', false); + $this->registerArgument('alternativeMarkupIdentifier', 'string', 'Alternative icon identifier. Takes precedence over the identifier if supported by the IconProvider.'); } /** * Prints icon html for $identifier key * - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext * @return string */ - public static function renderStatic( - array $arguments, - \Closure $renderChildrenClosure, - RenderingContextInterface $renderingContext - ) { - $identifier = $arguments['identifier']; - $size = $arguments['size']; - $overlay = $arguments['overlay']; - $state = IconState::cast($arguments['state']); - $alternativeMarkupIdentifier = $arguments['alternativeMarkupIdentifier']; + public function render(): string + { + $identifier = $this->arguments['identifier']; + $size = $this->arguments['size']; + $overlay = $this->arguments['overlay']; + $state = IconState::tryFrom($this->arguments['state']); + $alternativeMarkupIdentifier = $this->arguments['alternativeMarkupIdentifier']; + /** @var IconFactory $iconFactory */ $iconFactory = GeneralUtility::makeInstance(IconFactory::class); return $iconFactory->getIcon($identifier, $size, $overlay, $state)->getMarkup($alternativeMarkupIdentifier); } diff --git a/Classes/ViewHelpers/TrimViewHelper.php b/Classes/ViewHelpers/TrimViewHelper.php old mode 100755 new mode 100644 index 68683ff..5d1db02 --- a/Classes/ViewHelpers/TrimViewHelper.php +++ b/Classes/ViewHelpers/TrimViewHelper.php @@ -1,6 +1,6 @@ registerArgument('content', 'string', 'Content to be trimmed'); - $this->registerArgument('characters', 'string', 'Characters to be removed', false); + $this->registerArgument('characters', 'string', 'Characters to be removed'); } - /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * - * @return string - */ - public static function renderStatic( - array $arguments, - \Closure $renderChildrenClosure, - RenderingContextInterface $renderingContext - ) { - $content = $arguments['content'] ? $arguments['content'] : $renderChildrenClosure(); - $characters = $arguments['characters'] ? $arguments['characters'] : null; + public function render(): string + { + $content = $this->arguments['content'] ?: $this->renderChildren(); + $characters = $this->arguments['characters'] ?: null; if ($characters !== null) { $content = trim($content, $characters); diff --git a/Classes/Xclass/SiteDatabaseEditRow.php b/Classes/Xclass/SiteDatabaseEditRow.php deleted file mode 100755 index cc4a4c5..0000000 --- a/Classes/Xclass/SiteDatabaseEditRow.php +++ /dev/null @@ -1,59 +0,0 @@ -siteConfiguration); - if ($tableName === 'site') { - $rootPageId = (int)$result['vanillaUid']; - $rowData = $this->getRawConfigurationForSiteWithRootPageId($siteFinder, $rootPageId); - $result['databaseRow']['uid'] = $rowData['rootPageId']; - $result['databaseRow']['identifier'] = $result['customData']['siteIdentifier']; - } elseif (in_array($tableName, ['site_errorhandling', 'site_language', 'site_route', 'site_base_variant'], true)) { - $rootPageId = (int)($result['inlineTopMostParentUid'] ?? $result['inlineParentUid']); - try { - $rowData = $this->getRawConfigurationForSiteWithRootPageId($siteFinder, $rootPageId); - $parentFieldName = $result['inlineParentFieldName']; - if (!isset($rowData[$parentFieldName])) { - throw new \RuntimeException('Field "' . $parentFieldName . '" not found', 1520886092); - } - $rowData = $rowData[$parentFieldName][$result['vanillaUid']]; - $result['databaseRow']['uid'] = $result['vanillaUid']; - } catch (SiteNotFoundException $e) { - $rowData = []; - } - } else { - return $result; - } - - foreach ($rowData as $fieldName => $value) { - // Flat values only - databaseRow has no "tree" - if (!is_array($value)) { - $result['databaseRow'][$fieldName] = $value; - } - } - // All "records" are always on pid 0 - $result['databaseRow']['pid'] = 0; - return $result; - } -} diff --git a/Classes/Xclass/SiteTcaInline.php b/Classes/Xclass/SiteTcaInline.php deleted file mode 100755 index 548f6f3..0000000 --- a/Classes/Xclass/SiteTcaInline.php +++ /dev/null @@ -1,35 +0,0 @@ -addInlineFirstPid($result); - foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) { - if (!$this->isInlineField($fieldConfig)) { - continue; - } - $childTableName = $fieldConfig['config']['foreign_table'] ?? ''; - if (!in_array($childTableName, ['site_errorhandling', 'site_route', 'site_base_variant'], true)) { - return $result; - } - $result['processedTca']['columns'][$fieldName]['children'] = []; - $result = $this->resolveSiteRelatedChildren($result, $fieldName); - if (!empty($result['processedTca']['columns'][$fieldName]['config']['selectorOrUniqueConfiguration'])) { - throw new \RuntimeException('selectorOrUniqueConfiguration not implemented in sites module', 1624313533); - } - } - - return $result; - } -} diff --git a/Configuration/Icons.php b/Configuration/Icons.php old mode 100755 new mode 100644 index 4220dea..8962136 --- a/Configuration/Icons.php +++ b/Configuration/Icons.php @@ -1,6 +1,5 @@ SvgIconProvider::class, 'source' => 'EXT:ew_base/Resources/Public/Icons/Extension_16.svg', ], - 'ew-usercentrics' => [ - 'provider' => BitmapIconProvider::class, - 'source' => 'EXT:ew_base/Resources/Public/Icons/usercentrics.svg', - ], ]; diff --git a/Configuration/JavaScriptModules.php b/Configuration/JavaScriptModules.php old mode 100755 new mode 100644 diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml old mode 100755 new mode 100644 index 8050c5b..3f7994e --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -16,29 +16,14 @@ services: Evoweb\EwBase\EventListener\CssMerger: tags: ['event.listener'] + public: true Evoweb\EwBase\EventListener\JsMerger: tags: ['event.listener'] + public: true - Evoweb\EwBase\EventListener\IsContentUsedOnPageLayout: + Evoweb\EwBase\EventListener\ReleaseToolbarItem: tags: ['event.listener'] - - Evoweb\EwBase\ToolbarItems\ReleaseToolbarItem: - tags: ['backend.toolbar.item'] - - Evoweb\EwBase\Hooks\UsercentricsPostRenderHook: - public: true - - Evoweb\EwBase\Updates\GridelementsToContainerMigration: - public: true - - Evoweb\EwBase\Updates\GridelementsToContainerService: - public: true - - Evoweb\EwBase\Updates\ParentChildToContainerMigration: - public: true - - Evoweb\EwBase\Updates\ParentChildToContainerService: public: true Evoweb\EwBase\Form\FormDataProvider\UsercentricsDatabaseEditRow: diff --git a/Configuration/Sets/EwBase/config.yaml b/Configuration/Sets/EwBase/config.yaml new file mode 100644 index 0000000..6969bf8 --- /dev/null +++ b/Configuration/Sets/EwBase/config.yaml @@ -0,0 +1,2 @@ +name: evoweb/ew-base +label: Site Base diff --git a/Configuration/Sets/EwBase/settings.definitions.yaml b/Configuration/Sets/EwBase/settings.definitions.yaml new file mode 100644 index 0000000..38aef6d --- /dev/null +++ b/Configuration/Sets/EwBase/settings.definitions.yaml @@ -0,0 +1,11 @@ +categories: + ew-base: + label: 'Evoweb Base' + +settings: + ew-base.inlineCssStyles: + default: false + label: 'Inline css' + type: bool + description: 'Weather the concatenated css should get added inline style tag or a css file' + category: ew-base diff --git a/Configuration/Sets/EwBase/settings.yaml b/Configuration/Sets/EwBase/settings.yaml new file mode 100644 index 0000000..b61c029 --- /dev/null +++ b/Configuration/Sets/EwBase/settings.yaml @@ -0,0 +1,2 @@ +ew-base: + inlineCssStyles: false diff --git a/Configuration/SiteConfiguration/Overrides/sites.php b/Configuration/SiteConfiguration/Overrides/sites.php deleted file mode 100755 index 3ec61c2..0000000 --- a/Configuration/SiteConfiguration/Overrides/sites.php +++ /dev/null @@ -1,22 +0,0 @@ - 'LLL:EXT:ew_base/Resources/Private/Language/locallang_siteconfiguration.xlf:site.usercentrics', - 'description' => 'LLL:EXT:ew_base/Resources/Private/Language/locallang_siteconfiguration.xlf:site.usercentrics.description', - 'config' => [ - 'type' => 'inline', - 'foreign_table' => 'site_usercentrics', - 'maxitems' => 1, - 'appearance' => [ - 'enabledControls' => [ - 'info' => false, - ], - ], - ], -]; - -$GLOBALS['SiteConfiguration']['site']['types']['0']['showitem'] = str_replace( - ' routes', - ' routes, usercentrics,', - $GLOBALS['SiteConfiguration']['site']['types']['0']['showitem'] -); diff --git a/Configuration/SiteConfiguration/site_usercentrics.php b/Configuration/SiteConfiguration/site_usercentrics.php deleted file mode 100755 index 67c84e0..0000000 --- a/Configuration/SiteConfiguration/site_usercentrics.php +++ /dev/null @@ -1,78 +0,0 @@ - [ - 'label' => 'id', - 'title' => 'LLL:EXT:ew_base/Resources/Private/Language/locallang_siteconfiguration.xlf:site_usercentrics.ctrl.title', - 'typeicon_classes' => [ - 'default' => 'ew-usercentrics', - ], - ], - 'columns' => [ - 'id' => [ - 'label' => 'LLL:EXT:ew_base/Resources/Private/Language/locallang_siteconfiguration.xlf:site_usercentrics.column.id', - 'config' => [ - 'type' => 'input', - 'size' => 12, - 'max' => 12, - ], - ], - 'version' => [ - 'label' => 'LLL:EXT:ew_base/Resources/Private/Language/locallang_siteconfiguration.xlf:site_usercentrics.column.version', - 'config' => [ - 'type' => 'select', - 'renderType' => 'selectSingle', - 'minitems' => 0, - 'maxitems' => 1, - 'size' => 1, - 'items' => [ - [ - 0 => 'CMP 2', - 1 => 'loader', - ], - [ - 0 => 'CMP 1', - 1 => 'main', - ], - ] - ], - ], - 'applicationContext' => [ - 'label' => 'LLL:EXT:ew_base/Resources/Private/Language/locallang_siteconfiguration.xlf:site_usercentrics.column.applicationContext', - 'description' => 'Values are separated by line breaks', - 'config' => [ - 'type' => 'text', - 'eval' => 'trim', - ], - ], - 'useBlocker' => [ - 'label' => 'LLL:EXT:ew_base/Resources/Private/Language/locallang_siteconfiguration.xlf:site_usercentrics.column.useBlocker', - 'config' => [ - 'type' => 'check', - 'renderType' => 'checkboxToggle', - 'default' => false, - 'items' => [ - [ - 0 => '', - 1 => true, - ], - ] - ], - ], - ], - 'types' => [ - '1' => [ - 'showitem' => ' - --palette--;;usercentrics, - ', - ], - ], - 'palettes' => [ - 'usercentrics' => [ - 'showitem' => ' - id, version, --linebreak--, - applicationContext, useBlocker, - ' - ] - ] -]; diff --git a/Configuration/TCA/Overrides/pages.php b/Configuration/TCA/Overrides/pages.php old mode 100755 new mode 100644 index 6a697aa..02ebb34 --- a/Configuration/TCA/Overrides/pages.php +++ b/Configuration/TCA/Overrides/pages.php @@ -10,15 +10,15 @@ $newColumns = [ 'type' => 'group', 'allowed' => 'tt_content', 'size' => 1, + 'relationship' => 'manyToOne', + 'default' => 0, 'maxitems' => 1, - 'minitems' => 0, 'suggestOptions' => [ 'default' => [ 'additionalSearchFields' => 'header, bodytext', 'searchWholePhrase' => false ] ], - 'default' => 0, 'behaviour' => [ 'allowLanguageSynchronization' => true ] diff --git a/Configuration/Yaml/Csp/Cookiebot.yaml b/Configuration/Yaml/Csp/Cookiebot.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleAdsConversions.yaml b/Configuration/Yaml/Csp/GoogleAdsConversions.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleAdsRemarketing.yaml b/Configuration/Yaml/Csp/GoogleAdsRemarketing.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleAnalytics4.yaml b/Configuration/Yaml/Csp/GoogleAnalytics4.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleAnalytics4Signals.yaml b/Configuration/Yaml/Csp/GoogleAnalytics4Signals.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleAnalytics4SignalsEu.yaml b/Configuration/Yaml/Csp/GoogleAnalytics4SignalsEu.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleOptimize.yaml b/Configuration/Yaml/Csp/GoogleOptimize.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleTagManagerPreview.yaml b/Configuration/Yaml/Csp/GoogleTagManagerPreview.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/MapBox.yaml b/Configuration/Yaml/Csp/MapBox.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/UniversalAnalytics.yaml b/Configuration/Yaml/Csp/UniversalAnalytics.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/Youtube.yaml b/Configuration/Yaml/Csp/Youtube.yaml old mode 100755 new mode 100644 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..95d36a7 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,345 @@ +Some icons used in the TYPO3 project are retrieved from the "Silk" icon set of +Mark James, which can be found at http://famfamfam.com/lab/icons/silk/. This +set is distributed under a Creative Commons Attribution 2.5 License. The +license can be found at http://creativecommons.org/licenses/by/2.5/. +--------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md old mode 100755 new mode 100644 index 99d4154..adc44fa --- a/README.md +++ b/README.md @@ -1,19 +1 @@ # ew-base -## Usercentrics Hook Guide - -Add this snippet to your config/sites to enable the hook -```yaml -usercentrics: - - - id: LNxzTaK8j - version: loader - useBlocker: false - applicationContext: "Production/Staging\r\nProduction\r\nDevelopment\r\n" -``` - -``` -id - Usercentrics Id -version (loader | main) - loader is the v2 and main is v1 in usercentrics context -useBlocker - if the content blocker script should be rendered -applicationContext - multiline string of application contexts that the snippets should be rendered in -``` diff --git a/Resources/Private/Language/locallang_core.xlf b/Resources/Private/Language/locallang_core.xlf old mode 100755 new mode 100644 index ef33b70..f226a6f --- a/Resources/Private/Language/locallang_core.xlf +++ b/Resources/Private/Language/locallang_core.xlf @@ -3,9 +3,6 @@
- - Release information - Release tag diff --git a/Resources/Private/Language/locallang_db.xlf b/Resources/Private/Language/locallang_db.xlf old mode 100755 new mode 100644 diff --git a/Resources/Private/Language/locallang_siteconfiguration.xlf b/Resources/Private/Language/locallang_siteconfiguration.xlf deleted file mode 100755 index 682ac47..0000000 --- a/Resources/Private/Language/locallang_siteconfiguration.xlf +++ /dev/null @@ -1,33 +0,0 @@ - - - -
- - - UserCentrics - - - Max one configuration per site - - - - Usercentrics configuration - - - ID - - - ID defining what user centrics account to use - - - Version - - - Application Context - - - use Blocker - - - - diff --git a/Resources/Private/Templates/Form/ImageManipulationElement.html b/Resources/Private/Templates/Form/ImageManipulationElement.html old mode 100755 new mode 100644 diff --git a/Resources/Private/Templates/ToolbarItems/ShowReleaseDropDown.html b/Resources/Private/Templates/ToolbarItems/ShowReleaseDropDown.html deleted file mode 100755 index fea0895..0000000 --- a/Resources/Private/Templates/ToolbarItems/ShowReleaseDropDown.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/Resources/Private/Templates/ToolbarItems/ShowReleaseToolbarItem.html b/Resources/Private/Templates/ToolbarItems/ShowReleaseToolbarItem.html deleted file mode 100755 index 93c156f..0000000 --- a/Resources/Private/Templates/ToolbarItems/ShowReleaseToolbarItem.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/Resources/Public/Icons/Extension.svg b/Resources/Public/Icons/Extension.svg old mode 100755 new mode 100644 diff --git a/Resources/Public/Icons/Extension_16.svg b/Resources/Public/Icons/Extension_16.svg old mode 100755 new mode 100644 diff --git a/Resources/Public/Icons/usercentrics.svg b/Resources/Public/Icons/usercentrics.svg deleted file mode 100755 index 6d44c30..0000000 --- a/Resources/Public/Icons/usercentrics.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - diff --git a/Resources/Public/JavaScript/form-engine/element/pick-color-from-image.css b/Resources/Public/JavaScript/form-engine/element/pick-color-from-image.css old mode 100755 new mode 100644 diff --git a/Resources/Public/JavaScript/form-engine/element/pick-color-from-image.js b/Resources/Public/JavaScript/form-engine/element/pick-color-from-image.js old mode 100755 new mode 100644 diff --git a/composer.json b/composer.json old mode 100755 new mode 100644 index bc99678..e1292ee --- a/composer.json +++ b/composer.json @@ -1,14 +1,13 @@ { "name": "evoweb/ew-base", "type": "typo3-cms-extension", - "version": "1.0.0", "autoload": { "psr-4": { "Evoweb\\EwBase\\": "Classes/" } }, "require": { - "typo3/cms-core": "^11.5 || ^12.4 || dev-main", + "typo3/cms-core": "*", "typo3/cms-backend": "*", "typo3/cms-extbase": "*", diff --git a/ext_conf_template.txt b/ext_conf_template.txt deleted file mode 100644 index b51c2fa..0000000 --- a/ext_conf_template.txt +++ /dev/null @@ -1,2 +0,0 @@ -# cat=basic//10; type=boolean; label= Weather the content of css files gets inserted to the source code of the page as inline css -inlineCssStyles = 0 diff --git a/ext_localconf.php b/ext_localconf.php old mode 100755 new mode 100644 index 1f96207..9741611 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -1,67 +1,15 @@ executePostRenderHook'; - $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1681197508] = [ 'nodeName' => 'pick-color-from-image', - 'priority' => '70', + 'priority' => 70, 'class' => PickColorFromImage::class, ]; - - $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][BaseSiteDatabaseEditRow::class] = [ - 'className' => SiteDatabaseEditRow::class - ]; - $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][BaseSiteTcaInline::class] = [ - 'className' => SiteTcaInline::class - ]; - - $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['siteConfiguration'][ - UsercentricsDatabaseEditRow::class - ] = [ - 'depends' => [ BaseSiteDatabaseEditRow::class ], - 'before' => [ DatabaseParentPageRow::class ], - ]; - $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['siteConfiguration'][ - UsercentricsTcaInline::class - ] = [ - 'depends' => [ BaseSiteTcaInline::class ], - 'before' => [ TcaSiteLanguage::class ], - ]; - - if (empty($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['userCentrics'] ?? [])) { - $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['userCentrics']['loader']['preload'] = - ' - -'; - $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['userCentrics']['loader']['template'] = - ''; - $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['userCentrics']['main']['preload'] = - ' - - -'; - $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['userCentrics']['main']['template'] = - ''; - $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['userCentrics']['block']['preload'] = - ''; - $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['userCentrics']['block']['template'] = - ''; - } -}); +})(); diff --git a/ext_tables.sql b/ext_tables.sql deleted file mode 100755 index 232ee2c..0000000 --- a/ext_tables.sql +++ /dev/null @@ -1,7 +0,0 @@ -# -# Table structure for table 'pages' -# -CREATE TABLE pages -( - sectionIndex_uid int(11) unsigned DEFAULT '0' NOT NULL -);