diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index f3cba11..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,47 +0,0 @@ -# Adapted from https://github.com/TYPO3GmbH/blog/blob/master/.github/workflows/ci.yml -name: CI - -on: [ pull_request ] - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - typo3: [ ^11.5, ^12.3 ] - php: [ '8.1' ] - include: - - typo3: ^11.5 - php: '7.4' - - typo3: ^11.5 - php: '8.0' - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Set up PHP version ${{ matrix.php }} - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - - - name: Environment Check - run: | - php --version - composer --version - - - name: Validate composer.json and composer.lock - run: composer validate - - - name: Install dependencies with typo3/cms-core:${{ matrix.typo3 }} - run: | - composer require typo3/cms-core:${{ matrix.typo3 }} --no-progress - git checkout composer.json - - - name: Lint PHP - run: composer ci:php:lint - - - name: php-cs-fixer - run: composer ci:php:fixer diff --git a/.github/workflows/typo3_11.yml b/.github/workflows/typo3_11.yml new file mode 100644 index 0000000..1825c7f --- /dev/null +++ b/.github/workflows/typo3_11.yml @@ -0,0 +1,47 @@ +name: Test video_shariff against TYPO3 11 + +on: [pull_request] + +jobs: + CGL: + name: Coding Style + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Validate composer.json and composer.lock + run: Build/Scripts/runTests.sh -t 11 -p 8.1 -s composer -e 'validate' + + - name: Install testing system + run: Build/Scripts/runTests.sh -t 11 -p 8.1 -s composerInstall + + - name: Lint PHP + run: Build/Scripts/runTests.sh -t 11 -p 8.1 -s lint + + - name: Validate code against CGL + run: Build/Scripts/runTests.sh -t 11 -p 8.1 -s cgl -n + + testing: + name: PHP ${{ matrix.php }} Unit and Functional Tests + needs: CGL + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + php: [ '7.4', '8.0', '8.1' ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install testing system + run: Build/Scripts/runTests.sh -t 11 -p ${{ matrix.php }} -s composerInstall + + - name: Functional Tests with mariadb and mysqli + run: Build/Scripts/runTests.sh -t 11 -p ${{ matrix.php }} -d mariadb -a mysqli -s functional diff --git a/.github/workflows/typo3_12.yml b/.github/workflows/typo3_12.yml new file mode 100644 index 0000000..2215020 --- /dev/null +++ b/.github/workflows/typo3_12.yml @@ -0,0 +1,47 @@ +name: Test video_shariff against TYPO3 12 + +on: [pull_request] + +jobs: + CGL: + name: Coding Style + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Validate composer.json and composer.lock + run: Build/Scripts/runTests.sh -t 12 -p 8.1 -s composer -e 'validate' + + - name: Install testing system + run: Build/Scripts/runTests.sh -t 12 -p 8.1 -s composerInstall + + - name: Lint PHP + run: Build/Scripts/runTests.sh -t 12 -p 8.1 -s lint + + - name: Validate code against CGL + run: Build/Scripts/runTests.sh -t 12 -p 8.1 -s cgl -n + + testing: + name: PHP ${{ matrix.php }} Unit and Functional Tests + needs: CGL + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + php: [ '8.1', '8.2' ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install testing system + run: Build/Scripts/runTests.sh -t 12 -p ${{ matrix.php }} -s composerInstall + + - name: Functional Tests with mariadb and mysqli + run: Build/Scripts/runTests.sh -t 12 -p ${{ matrix.php }} -d mariadb -a mysqli -s functional diff --git a/.gitignore b/.gitignore index fc37fd3..9ff89ea 100644 --- a/.gitignore +++ b/.gitignore @@ -26,11 +26,11 @@ nbproject # Temporary files and folders /.cache +.phpunit.cache/ .php_cs.cache .php-cs-fixer.cache .sass-cache .session *.log - # Ignore composer stuff /composer.lock diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh new file mode 100755 index 0000000..cdb1cbc --- /dev/null +++ b/Build/Scripts/runTests.sh @@ -0,0 +1,488 @@ +#!/usr/bin/env bash + +# +# TYPO3 core test runner based on docker and docker-compose. +# + +# Function to write a .env file in Build/testing-docker +# This is read by docker-compose and vars defined here are +# used in Build/testing-docker/docker-compose.yml +setUpDockerComposeDotEnv() { + # Delete possibly existing local .env file if exists + [ -e .env ] && rm .env + # Set up a new .env file for docker-compose + { + echo "COMPOSE_PROJECT_NAME=local" + # To prevent access rights of files created by the testing, the docker image later + # runs with the same user that is currently executing the script. docker-compose can't + # use $UID directly itself since it is a shell variable and not an env variable, so + # we have to set it explicitly here. + echo "HOST_UID=`id -u`" + # Your local user + echo "ROOT_DIR=${ROOT_DIR}" + echo "HOST_USER=${USER}" + echo "TEST_FILE=${TEST_FILE}" + echo "TYPO3_VERSION=${TYPO3_VERSION}" + echo "PHP_XDEBUG_ON=${PHP_XDEBUG_ON}" + echo "PHP_XDEBUG_PORT=${PHP_XDEBUG_PORT}" + echo "DOCKER_PHP_IMAGE=${DOCKER_PHP_IMAGE}" + echo "EXTRA_TEST_OPTIONS=${EXTRA_TEST_OPTIONS}" + echo "SCRIPT_VERBOSE=${SCRIPT_VERBOSE}" + echo "CGLCHECK_DRY_RUN=${CGLCHECK_DRY_RUN}" + echo "DATABASE_DRIVER=${DATABASE_DRIVER}" + echo "MARIADB_VERSION=${MARIADB_VERSION}" + echo "MYSQL_VERSION=${MYSQL_VERSION}" + echo "POSTGRES_VERSION=${POSTGRES_VERSION}" + echo "USED_XDEBUG_MODES=${USED_XDEBUG_MODES}" + echo "IMAGE_PREFIX=${IMAGE_PREFIX}" + } > .env +} + +# Options -a and -d depend on each other. The function +# validates input combinations and sets defaults. +handleDbmsAndDriverOptions() { + case ${DBMS} in + mysql|mariadb) + [ -z "${DATABASE_DRIVER}" ] && DATABASE_DRIVER="mysqli" + if [ "${DATABASE_DRIVER}" != "mysqli" ] && [ "${DATABASE_DRIVER}" != "pdo_mysql" ]; then + echo "Invalid option -a ${DATABASE_DRIVER} with -d ${DBMS}" >&2 + echo >&2 + echo "call \"./Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 + fi + ;; + postgres|sqlite) + if [ -n "${DATABASE_DRIVER}" ]; then + echo "Invalid option -a ${DATABASE_DRIVER} with -d ${DBMS}" >&2 + echo >&2 + echo "call \"./Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 + fi + ;; + esac +} + +# Load help text into $HELP +read -r -d '' HELP <=20.10 for xdebug break pointing to work reliably, and +a recent docker-compose (tested >=1.21.2) is needed. + +Usage: $0 [options] [file] + +No arguments: Run all unit tests with PHP 7.4 + +Options: + -s <...> + Specifies which test suite to run + - cgl: cgl test and fix all php files + - clean: clean up build and testing related files + - composer: Execute "composer" command, using -e for command arguments pass-through, ex. -e "ci:php:stan" + - composerInstall: "composer update", handy if host has no PHP + - composerInstallLowest: "composer update", handy if host has no PHP + - composerInstallHighest: "composer update", handy if host has no PHP + - functional: functional tests + - lint: PHP linting + - phpstan: phpstan analyze + - phpstanGenerateBaseline: regenerate phpstan baseline, handy after phpstan updates + - unit: PHP unit tests + + -a + Only with -s acceptance,functional + Specifies to use another driver, following combinations are available: + - mysql + - mysqli (default) + - pdo_mysql + - mariadb + - mysqli (default) + - pdo_mysql + + -d + Only with -s acceptance,functional + Specifies on which DBMS tests are performed + - sqlite: (default) use sqlite + - mariadb: use mariadb + - mysql: use mysql + - postgres: use postgres + + -i <10.2|10.3|10.4|10.5|10.6|10.7> + Only with -d mariadb + Specifies on which version of mariadb tests are performed + - 10.2 (default) + - 10.3 + - 10.4 + - 10.5 + - 10.6 + - 10.7 + + -j <5.5|5.6|5.7|8.0> + Only with -d mysql + Specifies on which version of mysql tests are performed + - 5.5 (default) + - 5.6 + - 5.7 + - 8.0 + + -k <10|11|12|13|14> + Only with -d postgres + Specifies on which version of postgres tests are performed + - 10 (default) + - 11 + - 12 + - 13 + - 14 + + -p <7.4|8.0|8.1|8.2> + Specifies the PHP minor version to be used + - 7.4 (default): use PHP 7.4 + - 8.0: use PHP 8.0 + - 8.1: use PHP 8.1 + - 8.2: use PHP 8.2 + + -t <11|12> + Only with -s composerUpdate + Specifies the TYPO3 core major version to be used + - 11 (default): use TYPO3 core v11 + - 12: use TYPO3 core v12 + + -e "" + Only with -s functional|unit|composer + Additional options to send to phpunit (unit & functional tests) or codeception (acceptance + tests). For phpunit, options starting with "--" must be added after options starting with "-". + Example -e "-v --filter canRetrieveValueWithGP" to enable verbose output AND filter tests + named "canRetrieveValueWithGP" + + -x + Only with -s functional|unit|acceptance + Send information to host instance for test or system under test break points. This is especially + useful if a local PhpStorm instance is listening on default xdebug port 9003. A different port + can be selected with -y + + -y + Send xdebug information to a different port than default 9003 if an IDE like PhpStorm + is not listening on default port. + + -z + Only with -x and -s functional|unit|acceptance + This sets the used xdebug modes. Defaults to 'debug,develop' + + -n + Only with -s cgl + Activate dry-run in CGL check that does not actively change files and only prints broken ones. + + -u + Update existing typo3/core-testing-*:latest docker images. Maintenance call to docker pull latest + versions of the main php images. The images are updated once in a while and only the youngest + ones are supported by core testing. Use this if weird test errors occur. Also removes obsolete + image versions of typo3/core-testing-*. + -v + Enable verbose script output. Shows variables and docker commands. + + -h + Show this help. + +Examples: + # Run unit tests using PHP 7.4 + ./Build/Scripts/runTests.sh -s unit +EOF + +# Test if docker-compose exists, else exit out with error +if ! type "docker-compose" > /dev/null; then + echo "This script relies on docker and docker-compose. Please install" >&2 + exit 1 +fi + +# Go to the directory this script is located, so everything else is relative +# to this dir, no matter from where this script is called. +THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd "$THIS_SCRIPT_DIR" || exit 1 + +# Go to directory that contains the local docker-compose.yml file +cd ../testing-docker || exit 1 + +# Option defaults +if ! command -v realpath &> /dev/null; then + echo "This script works best with realpath installed" >&2 + ROOT_DIR="${PWD}/../../" +else + ROOT_DIR=`realpath ${PWD}/../../` +fi + +TEST_SUITE="" +DBMS="sqlite" +PHP_VERSION="7.4" +TYPO3_VERSION="11" +PHP_XDEBUG_ON=0 +PHP_XDEBUG_PORT=9003 +EXTRA_TEST_OPTIONS="" +SCRIPT_VERBOSE=0 +CGLCHECK_DRY_RUN="" +DATABASE_DRIVER="" +MARIADB_VERSION="10.2" +MYSQL_VERSION="5.5" +POSTGRES_VERSION="10" +USED_XDEBUG_MODES="debug,develop" +#@todo the $$ would add the current process id to the name, keeping as plan b +#PROJECT_NAME="runTests-$(basename $(dirname $ROOT_DIR))-$(basename $ROOT_DIR)-$$" +PROJECT_NAME="runTests-$(basename $(dirname $ROOT_DIR))-$(basename $ROOT_DIR)" +PROJECT_NAME="${PROJECT_NAME//[[:blank:]]/}" +echo $PROJECT_NAME + +# Option parsing +# Reset in case getopts has been used previously in the shell +OPTIND=1 +# Array for invalid options +INVALID_OPTIONS=(); +# Simple option parsing based on getopts (! not getopt) +while getopts ":s:a:d:i:j:k:p:t:e:xy:z:nhuv" OPT; do + case ${OPT} in + s) + TEST_SUITE=${OPTARG} + ;; + a) + DATABASE_DRIVER=${OPTARG} + ;; + d) + DBMS=${OPTARG} + ;; + i) + MARIADB_VERSION=${OPTARG} + if ! [[ ${MARIADB_VERSION} =~ ^(10.2|10.3|10.4|10.5|10.6|10.7)$ ]]; then + INVALID_OPTIONS+=("${OPTARG}") + fi + ;; + j) + MYSQL_VERSION=${OPTARG} + if ! [[ ${MYSQL_VERSION} =~ ^(5.5|5.6|5.7|8.0)$ ]]; then + INVALID_OPTIONS+=("${OPTARG}") + fi + ;; + k) + POSTGRES_VERSION=${OPTARG} + if ! [[ ${POSTGRES_VERSION} =~ ^(10|11|12|13|14)$ ]]; then + INVALID_OPTIONS+=("${OPTARG}") + fi + ;; + p) + PHP_VERSION=${OPTARG} + if ! [[ ${PHP_VERSION} =~ ^(7.4|8.0|8.1|8.2)$ ]]; then + INVALID_OPTIONS+=("p ${OPTARG}") + fi + ;; + t) + TYPO3_VERSION=${OPTARG} + if ! [[ ${TYPO3_VERSION} =~ ^(11|12)$ ]]; then + INVALID_OPTIONS+=("p ${OPTARG}") + fi + ;; + e) + EXTRA_TEST_OPTIONS=${OPTARG} + ;; + x) + PHP_XDEBUG_ON=1 + ;; + y) + PHP_XDEBUG_PORT=${OPTARG} + ;; + z) + USED_XDEBUG_MODES=${OPTARG} + ;; + h) + echo "${HELP}" + exit 0 + ;; + n) + CGLCHECK_DRY_RUN="-n" + ;; + u) + TEST_SUITE=update + ;; + v) + SCRIPT_VERBOSE=1 + ;; + \?) + INVALID_OPTIONS+=(${OPTARG}) + ;; + :) + INVALID_OPTIONS+=(${OPTARG}) + ;; + esac +done + +# Exit on invalid options +if [ ${#INVALID_OPTIONS[@]} -ne 0 ]; then + echo "Invalid option(s):" >&2 + for I in "${INVALID_OPTIONS[@]}"; do + echo "-"${I} >&2 + done + echo >&2 + echo "${HELP}" >&2 + exit 1 +fi + +# Move "7.2" to "php72", the latter is the docker container name +DOCKER_PHP_IMAGE=`echo "php${PHP_VERSION}" | sed -e 's/\.//'` + +# Set $1 to first mass argument, this is the optional test file or test directory to execute +shift $((OPTIND - 1)) +TEST_FILE=${1} +if [ -n "${1}" ]; then + TEST_FILE="Web/typo3conf/ext/video_shariff/${1}" + if [ "${TYPO3_VERSION}" == "12" ]; then + TEST_FILE="./${1}" + fi +fi + +if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x +fi + +if [ -z ${TEST_SUITE} ]; then + echo "${HELP}" + exit 0 +fi + +# Suite execution +case ${TEST_SUITE} in + cgl) + # Active dry-run for cgl needs not "-n" but specific options + if [[ ! -z ${CGLCHECK_DRY_RUN} ]]; then + CGLCHECK_DRY_RUN="--dry-run --diff" + fi + setUpDockerComposeDotEnv + docker-compose run cgl + SUITE_EXIT_CODE=$? + docker-compose down + ;; + clean) + rm -rf \ + ../../var/ \ + ../../.cache \ + ../../composer.lock \ + ../../.Build/ \ + ../../Tests/Acceptance/Support/_generated/ \ + ../../composer.json.testing + ;; + composer) + setUpDockerComposeDotEnv + docker-compose run composer + SUITE_EXIT_CODE=$? + docker-compose down + ;; + composerInstall) + setUpDockerComposeDotEnv + cp ../../composer.json ../../composer.json.orig + if [ -f "../../composer.json.testing" ]; then + cp ../../composer.json ../../composer.json.orig + fi + docker-compose run composer_install + cp ../../composer.json ../../composer.json.testing + mv ../../composer.json.orig ../../composer.json + SUITE_EXIT_CODE=$? + docker-compose down + ;; + composerInstallLowest) + setUpDockerComposeDotEnv + cp ../../composer.json ../../composer.json.orig + if [ -f "../../composer.json.testing" ]; then + cp ../../composer.json ../../composer.json.orig + fi + docker-compose run composer_install_lowest + cp ../../composer.json ../../composer.json.testing + mv ../../composer.json.orig ../../composer.json + SUITE_EXIT_CODE=$? + docker-compose down + ;; + composerInstallHighest) + setUpDockerComposeDotEnv + cp ../../composer.json ../../composer.json.orig + if [ -f "../../composer.json.testing" ]; then + cp ../../composer.json ../../composer.json.orig + fi + docker-compose run composer_install_highest + cp ../../composer.json ../../composer.json.testing + mv ../../composer.json.orig ../../composer.json + SUITE_EXIT_CODE=$? + docker-compose down + ;; + coveralls) + setUpDockerComposeDotEnv + docker-compose run coveralls + SUITE_EXIT_CODE=$? + docker-compose down + ;; + functional) + handleDbmsAndDriverOptions + setUpDockerComposeDotEnv + case ${DBMS} in + mariadb) + echo "Using driver: ${DATABASE_DRIVER}" + docker-compose run functional_mariadb + SUITE_EXIT_CODE=$? + ;; + mysql) + echo "Using driver: ${DATABASE_DRIVER}" + docker-compose run functional_mysql + SUITE_EXIT_CODE=$? + ;; + postgres) + docker-compose run functional_postgres + SUITE_EXIT_CODE=$? + ;; + sqlite) + # sqlite has a tmpfs as Web/typo3temp/var/tests/functional-sqlite-dbs/ + # Since docker is executed as root (yay!), the path to this dir is owned by + # root if docker creates it. Thank you, docker. We create the path beforehand + # to avoid permission issues. + mkdir -p ${ROOT_DIR}/Web/typo3temp/var/tests/functional-sqlite-dbs/ + docker-compose run functional_sqlite + SUITE_EXIT_CODE=$? + ;; + *) + echo "Invalid -d option argument ${DBMS}" >&2 + echo >&2 + echo "${HELP}" >&2 + exit 1 + esac + docker-compose down + ;; + lint) + setUpDockerComposeDotEnv + docker-compose run lint + SUITE_EXIT_CODE=$? + docker-compose down + ;; + phpstan) + setUpDockerComposeDotEnv + docker-compose run phpstan + SUITE_EXIT_CODE=$? + docker-compose down + ;; + phpstanGenerateBaseline) + setUpDockerComposeDotEnv + docker-compose run phpstan_generate_baseline + SUITE_EXIT_CODE=$? + docker-compose down + ;; + unit) + setUpDockerComposeDotEnv + docker-compose run unit + SUITE_EXIT_CODE=$? + docker-compose down + ;; + update) + # pull typo3/core-testing-*:latest versions of those ones that exist locally + docker images typo3/core-testing-*:latest --format "{{.Repository}}:latest" | xargs -I {} docker pull {} + # remove "dangling" typo3/core-testing-* images (those tagged as ) + docker images typo3/core-testing-* --filter "dangling=true" --format "{{.ID}}" | xargs -I {} docker rmi {} + ;; + *) + echo "Invalid -s option argument ${TEST_SUITE}" >&2 + echo >&2 + echo "${HELP}" >&2 + exit 1 +esac + +exit $SUITE_EXIT_CODE diff --git a/Build/php-cs-fixer/config.php b/Build/php-cs-fixer/php-cs-fixer.php similarity index 74% rename from Build/php-cs-fixer/config.php rename to Build/php-cs-fixer/php-cs-fixer.php index 53e342b..73f8818 100644 --- a/Build/php-cs-fixer/config.php +++ b/Build/php-cs-fixer/php-cs-fixer.php @@ -1,39 +1,28 @@ setHeader( + 'This file is part of the package jweiland/video-shariff. For the full copyright and license information, please read the -LICENSE file that was distributed with this source code. -COMMENT; - -$finder = PhpCsFixer\Finder::create() - ->name('*.php') - ->exclude('.build') - ->in(__DIR__); - -return (new \PhpCsFixer\Config()) - ->setFinder($finder) +LICENSE file that was distributed with this source code.', + true +); +$config->setFinder( + (new PhpCsFixer\Finder()) + ->in(realpath(__DIR__ . '/../../')) + ->ignoreVCSIgnored(true) + ->notPath('/^.Build\//') + ->notPath('/^Build\/php-cs-fixer\/php-cs-fixer.php/') + ->notPath('/^Build\/phpunit\/(UnitTestsBootstrap|FunctionalTestsBootstrap).php/') + ->notPath('/^Configuration\//') + ->notPath('/^Documentation\//') + ->notName('/^ext_(emconf|localconf|tables).php/') +) ->setRiskyAllowed(true) ->setRules([ '@DoctrineAnnotation' => true, '@PER' => true, - 'header_comment' => [ - 'header' => $headerComment, - ], 'array_syntax' => ['syntax' => 'short'], 'blank_line_after_opening_tag' => true, 'braces' => ['allow_single_line_closure' => true], @@ -42,11 +31,9 @@ 'concat_space' => ['spacing' => 'one'], 'declare_equal_normalize' => ['space' => 'none'], 'dir_constant' => true, - 'function_to_constant' => ['functions' => ['get_called_class', 'get_class', 'get_class_this', 'php_sapi_name', 'phpversion', 'pi']], 'function_typehint_space' => true, 'lowercase_cast' => true, 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], - 'modernize_strpos' => false, 'modernize_types_casting' => true, 'native_function_casing' => true, 'new_with_braces' => true, @@ -86,3 +73,4 @@ 'whitespace_after_comma_in_array' => ['ensure_single_space' => true], 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], ]); +return $config; diff --git a/Build/phpunit/FunctionalTests11.xml b/Build/phpunit/FunctionalTests11.xml new file mode 100644 index 0000000..cee2ea4 --- /dev/null +++ b/Build/phpunit/FunctionalTests11.xml @@ -0,0 +1,47 @@ + + + + + ../../Tests/Functional/ + + + + + + + + + diff --git a/Build/phpunit/FunctionalTests12.xml b/Build/phpunit/FunctionalTests12.xml new file mode 100644 index 0000000..cc83787 --- /dev/null +++ b/Build/phpunit/FunctionalTests12.xml @@ -0,0 +1,44 @@ + + + + + + ../../Tests/Functional/ + + + + + + + + + diff --git a/Build/phpunit/FunctionalTestsBootstrap.php b/Build/phpunit/FunctionalTestsBootstrap.php new file mode 100644 index 0000000..a95bc52 --- /dev/null +++ b/Build/phpunit/FunctionalTestsBootstrap.php @@ -0,0 +1,30 @@ +defineOriginalRootPath(); + $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests'); + $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient'); +})(); diff --git a/Build/phpunit/UnitTests11.xml b/Build/phpunit/UnitTests11.xml new file mode 100644 index 0000000..8f1bf61 --- /dev/null +++ b/Build/phpunit/UnitTests11.xml @@ -0,0 +1,47 @@ + + + + + ../../Tests/Unit/ + + + + + + + + diff --git a/Build/phpunit/UnitTests12.xml b/Build/phpunit/UnitTests12.xml new file mode 100644 index 0000000..f43f9a3 --- /dev/null +++ b/Build/phpunit/UnitTests12.xml @@ -0,0 +1,46 @@ + + + + + + ../../Tests/Unit/ + + + + + + + + diff --git a/Build/phpunit/UnitTestsBootstrap.php b/Build/phpunit/UnitTestsBootstrap.php new file mode 100644 index 0000000..a4ca910 --- /dev/null +++ b/Build/phpunit/UnitTestsBootstrap.php @@ -0,0 +1,67 @@ +getWebRoot(), '/')); + } + if (!getenv('TYPO3_PATH_WEB')) { + putenv('TYPO3_PATH_WEB=' . rtrim($testbase->getWebRoot(), '/')); + } + + $testbase->defineSitePath(); + + $composerMode = defined('TYPO3_COMPOSER_MODE') && TYPO3_COMPOSER_MODE === true; + $requestType = \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_BE | \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_CLI; + \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::run(0, $requestType, $composerMode); + + $testbase->createDirectory(\TYPO3\CMS\Core\Core\Environment::getPublicPath() . '/typo3conf/ext'); + $testbase->createDirectory(\TYPO3\CMS\Core\Core\Environment::getPublicPath() . '/typo3temp/assets'); + $testbase->createDirectory(\TYPO3\CMS\Core\Core\Environment::getPublicPath() . '/typo3temp/var/tests'); + $testbase->createDirectory(\TYPO3\CMS\Core\Core\Environment::getPublicPath() . '/typo3temp/var/transient'); + + // Retrieve an instance of class loader and inject to core bootstrap + $classLoader = require $testbase->getPackagesPath() . '/autoload.php'; + \TYPO3\CMS\Core\Core\Bootstrap::initializeClassLoader($classLoader); + + // Initialize default TYPO3_CONF_VARS + $configurationManager = new \TYPO3\CMS\Core\Configuration\ConfigurationManager(); + $GLOBALS['TYPO3_CONF_VARS'] = $configurationManager->getDefaultConfiguration(); + + $cache = new \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend( + 'core', + new \TYPO3\CMS\Core\Cache\Backend\NullBackend('production', []), + ); + // Set all packages to active + $packageManager = \TYPO3\CMS\Core\Core\Bootstrap::createPackageManager( + \TYPO3\CMS\Core\Package\UnitTestPackageManager::class, + \TYPO3\CMS\Core\Core\Bootstrap::createPackageCache($cache) + ); + + \TYPO3\CMS\Core\Utility\GeneralUtility::setSingletonInstance(\TYPO3\CMS\Core\Package\PackageManager::class, $packageManager); + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::setPackageManager($packageManager); + + $testbase->dumpClassLoadingInformation(); + + \TYPO3\CMS\Core\Utility\GeneralUtility::purgeInstances(); +}); diff --git a/Build/testing-docker/docker-compose.yml b/Build/testing-docker/docker-compose.yml new file mode 100644 index 0000000..11095c7 --- /dev/null +++ b/Build/testing-docker/docker-compose.yml @@ -0,0 +1,370 @@ +version: '2.3' +services: + #--------------------------------------------------------------------------------------------------------------------- + # additional services needed for functional tests to be linked, e.g. databases + #--------------------------------------------------------------------------------------------------------------------- + mysql: + image: mysql:${MYSQL_VERSION} + environment: + MYSQL_ROOT_PASSWORD: funcp + tmpfs: + - /var/lib/mysql/:rw,noexec,nosuid + + mariadb: + image: mariadb:${MARIADB_VERSION} + environment: + MYSQL_ROOT_PASSWORD: funcp + tmpfs: + - /var/lib/mysql/:rw,noexec,nosuid + + postgres: + image: postgres:${POSTGRES_VERSION}-alpine + environment: + POSTGRES_PASSWORD: funcp + POSTGRES_USER: ${HOST_USER} + tmpfs: + - /var/lib/postgresql/data:rw,noexec,nosuid + + #--------------------------------------------------------------------------------------------------------------------- + # composer related services + #--------------------------------------------------------------------------------------------------------------------- + composer: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + working_dir: ${ROOT_DIR} + environment: + COMPOSER_HOME: ".cache/composer-home" + COMPOSER_CACHE_DIR: ".cache/composer" + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + php -v | grep '^PHP'; + composer ${EXTRA_TEST_OPTIONS}; + " + + composer_install: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + working_dir: ${ROOT_DIR} + environment: + COMPOSER_HOME: ".cache/composer-home" + COMPOSER_CACHE_DIR: ".cache/composer" + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + php -v | grep '^PHP'; + if [ ${TYPO3_VERSION} -eq 11 ]; then + composer require --no-ansi --no-interaction --no-progress --no-install typo3/cms-core:^11.5.24 + fi + if [ ${TYPO3_VERSION} -eq 12 ]; then + composer require --no-ansi --no-interaction --no-progress --no-install typo3/cms-core:^12.2 + fi + composer install --no-progress; + " + + composer_install_lowest: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + working_dir: ${ROOT_DIR} + environment: + COMPOSER_HOME: ".cache/composer-home" + COMPOSER_CACHE_DIR: ".cache/composer" + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + php -v | grep '^PHP'; + if [ ${TYPO3_VERSION} -eq 11 ]; then + composer require --no-ansi --no-interaction --no-progress --no-install typo3/cms-core:^11.5.24 + fi + if [ ${TYPO3_VERSION} -eq 12 ]; then + composer require --no-ansi --no-interaction --no-progress --no-install typo3/cms-core:^12.0 + fi + composer update --no-ansi --no-interaction --no-progress --with-dependencies --prefer-lowest; + composer show; + " + + composer_install_highest: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + working_dir: ${ROOT_DIR} + environment: + COMPOSER_HOME: ".cache/composer-home" + COMPOSER_CACHE_DIR: ".cache/composer" + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + if [ ${TYPO3_VERSION} -eq 11 ]; then + composer require --no-ansi --no-interaction --no-progress --no-install typo3/cms-core:^11.5.24 + fi + if [ ${TYPO3_VERSION} -eq 12 ]; then + composer require --no-ansi --no-interaction --no-progress --no-install typo3/cms-core:^12.2 + fi + composer update --no-progress --no-interaction; + composer show; + " + + #--------------------------------------------------------------------------------------------------------------------- + # Unit tests + #--------------------------------------------------------------------------------------------------------------------- + unit: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + working_dir: ${ROOT_DIR} + extra_hosts: + - "host.docker.internal:host-gateway" + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + php -v | grep '^PHP'; + if [ ${PHP_XDEBUG_ON} -eq 0 ]; then + XDEBUG_MODE=\"off\" .Build/bin/phpunit -c Build/phpunit/UnitTests${TYPO3_VERSION}.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; + else + XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" .Build/bin/phpunit -c Build/phpunit/UnitTests${TYPO3_VERSION}.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; + fi + " + + lint: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + working_dir: ${ROOT_DIR} + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + php -v | grep '^PHP'; + find . -name \\*.php ! -path "./.Build/\\*" -print0 | xargs -0 -n1 -P4 php -dxdebug.mode=off -l >/dev/null + " + + #--------------------------------------------------------------------------------------------------------------------- + # functional tests against different dbms + #--------------------------------------------------------------------------------------------------------------------- + functional_sqlite: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + tmpfs: + - ${ROOT_DIR}/Web/typo3temp/var/tests/functional-sqlite-dbs/:rw,noexec,nosuid,uid=${HOST_UID} + environment: + typo3DatabaseDriver: pdo_sqlite + working_dir: ${ROOT_DIR} + extra_hosts: + - "host.docker.internal:host-gateway" + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + php -v | grep '^PHP'; + if [ ${PHP_XDEBUG_ON} -eq 0 ]; then + XDEBUG_MODE=\"off\" .Build/bin/phpunit -c Build/phpunit/FunctionalTests${TYPO3_VERSION}.xml ${EXTRA_TEST_OPTIONS} --exclude-group not-sqlite ${TEST_FILE}; + else + XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" .Build/bin/phpunit -c Build/phpunit/FunctionalTests${TYPO3_VERSION}.xml ${EXTRA_TEST_OPTIONS} --exclude-group not-sqlite ${TEST_FILE}; + fi + " + + functional_postgres: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + links: + - postgres + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + environment: + typo3DatabaseDriver: pdo_pgsql + typo3DatabaseName: bamboo + typo3DatabaseUsername: ${HOST_USER} + typo3DatabaseHost: postgres + typo3DatabasePassword: funcp + working_dir: ${ROOT_DIR} + extra_hosts: + - "host.docker.internal:host-gateway" + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + echo Waiting for database start...; + while ! nc -z postgres 5432; do + sleep 1; + done; + echo Database is up; + php -v | grep '^PHP'; + if [ ${PHP_XDEBUG_ON} -eq 0 ]; then + XDEBUG_MODE=\"off\" .Build/bin/phpunit -c Build/phpunit/FunctionalTests${TYPO3_VERSION}.xml ${EXTRA_TEST_OPTIONS} --exclude-group not-postgres ${TEST_FILE}; + else + XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" .Build/bin/phpunit -c Build/phpunit/FunctionalTests${TYPO3_VERSION}.xml ${EXTRA_TEST_OPTIONS} --exclude-group not-postgres ${TEST_FILE}; + fi + " + + functional_mysql: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + links: + - mysql + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + environment: + typo3DatabaseDriver: "${DATABASE_DRIVER}" + typo3DatabaseName: func_test + typo3DatabaseUsername: root + typo3DatabasePassword: funcp + typo3DatabaseHost: mysql + working_dir: ${ROOT_DIR} + extra_hosts: + - "host.docker.internal:host-gateway" + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + echo Waiting for database start...; + while ! nc -z mysql 3306; do + sleep 1; + done; + echo Database is up; + php -v | grep '^PHP'; + if [ ${PHP_XDEBUG_ON} -eq 0 ]; then + XDEBUG_MODE=\"off\" .Build/bin/phpunit -c Build/phpunit/FunctionalTests${TYPO3_VERSION}.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; + else + XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" .Build/bin/phpunit -c Build/phpunit/FunctionalTests${TYPO3_VERSION}.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; + fi + " + + functional_mariadb: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + links: + - mariadb + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + environment: + typo3DatabaseDriver: "${DATABASE_DRIVER}" + typo3DatabaseName: func_test + typo3DatabaseUsername: root + typo3DatabasePassword: funcp + typo3DatabaseHost: mariadb + working_dir: ${ROOT_DIR}/ + extra_hosts: + - "host.docker.internal:host-gateway" + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + echo Waiting for database start...; + while ! nc -z mariadb 3306; do + sleep 1; + done; + echo Database is up; + php -v | grep '^PHP'; + if [ ${PHP_XDEBUG_ON} -eq 0 ]; then + XDEBUG_MODE=\"off\" .Build/bin/phpunit -c Build/phpunit/FunctionalTests${TYPO3_VERSION}.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; + else + XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" .Build/bin/phpunit -c Build/phpunit/FunctionalTests${TYPO3_VERSION}.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; + fi + " + + #--------------------------------------------------------------------------------------------------------------------- + # code quality tools + #--------------------------------------------------------------------------------------------------------------------- + cgl: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + working_dir: ${ROOT_DIR} + extra_hosts: + - "host.docker.internal:host-gateway" + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + php -v | grep '^PHP'; + if [ ${PHP_XDEBUG_ON} -eq 0 ]; then + php -dxdebug.mode=off .Build/bin/php-cs-fixer fix -v ${CGLCHECK_DRY_RUN} --config=Build/php-cs-fixer/php-cs-fixer.php --using-cache=no . + else + XDEBUG_MODE=\"debug,develop\" XDEBUG_TRIGGER=\"foo\" XDEBUG_CONFIG=\"client_host=host.docker.internal\" PHP_CS_FIXER_ALLOW_XDEBUG=1 .Build/bin/php-cs-fixer fix -v ${CGLCHECK_DRY_RUN} --config=Build/php-cs-fixer/php-cs-fixer.php --using-cache=no . + fi + " + + phpstan: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + working_dir: ${ROOT_DIR} + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + mkdir -p .Build/.cache + php -v | grep '^PHP'; + php -dxdebug.mode=off .Build/bin/phpstan analyze -c Build/phpstan/phpstan.neon --no-progress + " + + phpstan_generate_baseline: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + working_dir: ${ROOT_DIR} + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + mkdir -p .Build/.cache + php -v | grep '^PHP'; + php -dxdebug.mode=off .Build/bin/phpstan analyze -c Build/phpstan/phpstan.neon --generate-baseline=Build/phpstan/phpstan-baseline.neon + " + + composer_update: + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + user: "${HOST_UID}" + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + working_dir: ${ROOT_DIR} + environment: + COMPOSER_CACHE_DIR: ".cache/composer" + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + php -v | grep '^PHP'; + if [ ${TYPO3_VERSION} -eq 11 ]; then + composer req --dev --no-update typo3/cms-composer-installers:^3.0 typo3/cms-backend:^11.5 typo3/cms-recordlist:^11.5 typo3/cms-frontend:^11.5 typo3/cms-extbase:^11.5 typo3/cms-fluid:^11.5 typo3/cms-install:^11.5 phpunit/phpunit:^9.6.4 + composer req typo3/cms-core:^11.5 --no-update + fi + if [ ${TYPO3_VERSION} -eq 12 ]; then + composer req --dev --no-update typo3/cms-composer-installers:^5.0 typo3/cms-backend:~12.4 typo3/cms-recordlist:~12.4 typo3/cms-frontend:~12.4 typo3/cms-extbase:~12.4 typo3/cms-fluid:~12.4 typo3/cms-install:~12.4 phpunit/phpunit:^10 + composer req typo3/cms-core:~12.4 -W --no-update + fi + composer update --no-progress --no-interaction; + " diff --git a/Classes/Traits/GetCoreFileReferenceTrait.php b/Classes/Traits/GetCoreFileReferenceTrait.php new file mode 100644 index 0000000..a362069 --- /dev/null +++ b/Classes/Traits/GetCoreFileReferenceTrait.php @@ -0,0 +1,30 @@ +getOriginalResource(); + } + + return $fileReference; + } +} diff --git a/Classes/Traits/GetOnlineMediaHelperTrait.php b/Classes/Traits/GetOnlineMediaHelperTrait.php new file mode 100644 index 0000000..0c912eb --- /dev/null +++ b/Classes/Traits/GetOnlineMediaHelperTrait.php @@ -0,0 +1,32 @@ +getOnlineMediaHelper($file); + + return $onlineMediaHelper ?: null; + } + + protected static function getOnlineMediaHelperRegistry(): OnlineMediaHelperRegistry + { + return GeneralUtility::makeInstance(OnlineMediaHelperRegistry::class); + } +} diff --git a/Classes/Traits/GetTypoScriptSetupTrait.php b/Classes/Traits/GetTypoScriptSetupTrait.php new file mode 100644 index 0000000..fa22d6f --- /dev/null +++ b/Classes/Traits/GetTypoScriptSetupTrait.php @@ -0,0 +1,42 @@ +getBranch(), '12.1', '>')) { + $frontendTypoScript = $GLOBALS['TYPO3_REQUEST']->getAttribute('frontend.typoscript'); + if ($frontendTypoScript instanceof FrontendTypoScript) { + $typoScript = $frontendTypoScript->getSetupArray(); + } + } else { + $typoScript = self::getTypoScriptFrontendController()->tmpl->setup; + } + + return $typoScript; + } + + protected static function getTypoScriptFrontendController(): TypoScriptFrontendController + { + return $GLOBALS['TSFE']; + } +} diff --git a/Classes/ViewHelpers/Format/SecondsToISO8601ViewHelper.php b/Classes/ViewHelpers/Format/SecondsToISO8601ViewHelper.php index 84e3a3a..3246b04 100644 --- a/Classes/ViewHelpers/Format/SecondsToISO8601ViewHelper.php +++ b/Classes/ViewHelpers/Format/SecondsToISO8601ViewHelper.php @@ -15,17 +15,13 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; /** - * Class SecondsToISO8601ViewHelper + * ViewHelper to convert seconds into ISO 8601 date */ class SecondsToISO8601ViewHelper extends AbstractViewHelper { /** - * Returns the absolute web path to the preview image. + * Convert seconds into ISO 8601 date * - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * @return string * @throws \UnexpectedValueException */ public static function renderStatic( @@ -34,29 +30,27 @@ public static function renderStatic( RenderingContextInterface $renderingContext ): string { $seconds = (int)$renderChildrenClosure(); - $intervals = [ - 'D' => 60 * 60 * 24, - 'H' => 60 * 60, - 'M' => 60, - 'S' => 1, - ]; - - $pt = 'P'; - $result = ''; - foreach ($intervals as $tag => $divisor) { - $qty = floor($seconds / $divisor); - if (!$qty && $result === '') { - $pt = 'T'; - continue; - } - $seconds -= $qty * $divisor; - $result .= $qty . $tag; + if ($seconds === 0) { + return 'P0S'; } - if ($result === '') { - $result = '0S'; - } + return self::formatSecondsIntoISO8601($seconds); + } - return $pt . $result; + protected static function formatSecondsIntoISO8601(int $seconds): string + { + $days = floor($seconds / (3600 * 24)); + $hours = floor(($seconds % (3600 * 24)) / 3600); + $minutes = floor(($seconds % 3600) / 60); + $seconds = $seconds % 60; + + return sprintf( + 'P%s%s%s%s%s', + $days ? $days . 'D' : '', + $hours || $minutes || $seconds ? 'T' : '', + $hours ? $hours . 'H' : '', + $minutes ? $minutes . 'M' : '', + $seconds ? $seconds . 'S' : '' + ); } } diff --git a/Classes/ViewHelpers/VideoCreationDateViewHelper.php b/Classes/ViewHelpers/VideoCreationDateViewHelper.php index f0cfee4..e8ec933 100644 --- a/Classes/ViewHelpers/VideoCreationDateViewHelper.php +++ b/Classes/ViewHelpers/VideoCreationDateViewHelper.php @@ -11,21 +11,19 @@ namespace JWeiland\VideoShariff\ViewHelpers; -use TYPO3\CMS\Core\Resource\FileInterface; +use JWeiland\VideoShariff\Traits\GetCoreFileReferenceTrait; use TYPO3\CMS\Core\Resource\FileReference; -use TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder; use TYPO3\CMS\Extbase\Domain\Model\FileReference as ExtbaseFileReference; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; /** - * Class VideoCreationDateViewHelper + * ViewHelper to extract date values from given FileReference */ class VideoCreationDateViewHelper extends AbstractViewHelper { - /** - * Initialize arguments - */ + use GetCoreFileReferenceTrait; + public function initializeArguments(): void { $this->registerArgument( @@ -36,12 +34,8 @@ public function initializeArguments(): void } /** - * Returns the absolute web path to the preview image. + * Returns date of given FileReference * - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * @return int * @throws \UnexpectedValueException */ public static function renderStatic( @@ -49,27 +43,29 @@ public static function renderStatic( \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext ): int { - /** @var FileReference|ExtbaseFileReference $fileReference */ - $fileReference = $arguments['fileReference']; - - // get Resource Object (non ExtBase version) - if (is_callable([$fileReference, 'getOriginalResource'])) { - // We have a domain model, so we need to fetch the FAL resource object from there - $fileReference = $fileReference->getOriginalResource(); - } - if (!($fileReference instanceof FileInterface || $fileReference instanceof AbstractFileFolder)) { - throw new \UnexpectedValueException('Supplied file object type ' . get_class($fileReference) . ' must be FileInterface or AbstractFileFolder.', 1454252193); + // Early return, if object is not allowed + if ( + $arguments['fileReference'] instanceof FileReference + || $arguments['fileReference'] instanceof ExtbaseFileReference + ) { + $fileReference = self::getCoreFileReference($arguments['fileReference']); + } else { + return 0; } + $file = $fileReference->getOriginalFile(); if ($file->getProperty('content_creation_date')) { return $file->getProperty('content_creation_date'); } + if ($file->getProperty('creation_date')) { return $file->getProperty('creation_date'); } + if ($file->getProperty('crdate')) { return $file->getProperty('crdate'); } + return 0; } } diff --git a/Classes/ViewHelpers/VideoPreviewImageViewHelper.php b/Classes/ViewHelpers/VideoPreviewImageViewHelper.php index 5c09f53..8e1b3b8 100644 --- a/Classes/ViewHelpers/VideoPreviewImageViewHelper.php +++ b/Classes/ViewHelpers/VideoPreviewImageViewHelper.php @@ -11,23 +11,27 @@ namespace JWeiland\VideoShariff\ViewHelpers; +use JWeiland\VideoShariff\Traits\GetCoreFileReferenceTrait; +use JWeiland\VideoShariff\Traits\GetOnlineMediaHelperTrait; +use JWeiland\VideoShariff\Traits\GetTypoScriptSetupTrait; use TYPO3\CMS\Core\Core\Environment; -use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\FileReference; -use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry; +use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Core\Utility\PathUtility; -use TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder; use TYPO3\CMS\Extbase\Domain\Model\FileReference as ExtbaseFileReference; -use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; /** - * Class VideoPreviewImageViewHelper + * ViewHelper to get preview image for video. If video is unavailable or private + * return a fallback image configured in lib.video_shariff.defaultThumbnail */ class VideoPreviewImageViewHelper extends AbstractViewHelper { + use GetCoreFileReferenceTrait; + use GetOnlineMediaHelperTrait; + use GetTypoScriptSetupTrait; + public function initializeArguments(): void { $this->registerArgument( @@ -47,35 +51,33 @@ public static function renderStatic( \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext ): string { - $publicDirectory = Environment::getPublicPath() . '/typo3temp/assets/tx_videoshariff/'; - /** @var FileReference|ExtbaseFileReference $fileReference */ - $fileReference = $arguments['fileReference']; - - // get Resource Object (non ExtBase version) - if (is_callable([$fileReference, 'getOriginalResource'])) { - // We have a domain model, so we need to fetch the FAL resource object from there - $fileReference = $fileReference->getOriginalResource(); - } + $publicFile = ''; - if (!($fileReference instanceof FileInterface || $fileReference instanceof AbstractFileFolder)) { - throw new \UnexpectedValueException( - 'Supplied file object type ' . get_class($fileReference) . ' must be FileInterface or AbstractFileFolder.', - 1454252193 - ); + // Early return, if object is not allowed + if ( + $arguments['fileReference'] instanceof FileReference + || $arguments['fileReference'] instanceof ExtbaseFileReference + ) { + $fileReference = self::getCoreFileReference($arguments['fileReference']); + } else { + return $publicFile; } $file = $fileReference->getOriginalFile(); - $helper = self::getOnlineMediaHelperRegistry()->getOnlineMediaHelper($file); - if ($helper) { + $helper = self::getOnlineMediaHelper($file); + if ($helper instanceof OnlineMediaHelperInterface) { $privateFile = $helper->getPreviewImage($file); + $publicDirectory = Environment::getPublicPath() . '/typo3temp/assets/tx_videoshariff/'; $publicFile = $publicDirectory . substr($privateFile, strrpos($privateFile, '/') + 1); + // check if file has already been copied if (!is_file($publicFile)) { // check if public directory exists if (!is_dir($publicDirectory)) { GeneralUtility::mkdir_deep($publicDirectory); } + if (is_file($privateFile)) { copy($privateFile, $publicFile); } else { @@ -83,32 +85,15 @@ public static function renderStatic( $publicFile = static::getDefaultThumbnailFile(); } } - } else { - $publicFile = ''; } return $publicFile; } - protected static function getTypoScriptFrontendController(): TypoScriptFrontendController - { - return $GLOBALS['TSFE']; - } - - protected static function getOnlineMediaHelperRegistry(): OnlineMediaHelperRegistry - { - return GeneralUtility::makeInstance(OnlineMediaHelperRegistry::class); - } - protected static function getDefaultThumbnailFile(): string { - // ToDo: Change while removing TYPO3 11 compatibility - $filename = static::getTypoScriptFrontendController()->tmpl->setup['lib.']['video_shariff.']['defaultThumbnail']; - if (strpos($filename, 'EXT:') === 0) { - $file = GeneralUtility::getFileAbsFileName($filename); - } else { - $file = PathUtility::getAbsoluteWebPath($filename); - } - return $file; + $filename = self::getTypoScriptSetup()['lib.']['video_shariff.']['defaultThumbnail'] ?? ''; + + return $filename ? GeneralUtility::getFileAbsFileName($filename) : ''; } } diff --git a/Classes/ViewHelpers/VideoPublicUrlViewHelper.php b/Classes/ViewHelpers/VideoPublicUrlViewHelper.php index bfa6abd..6d0d4da 100644 --- a/Classes/ViewHelpers/VideoPublicUrlViewHelper.php +++ b/Classes/ViewHelpers/VideoPublicUrlViewHelper.php @@ -11,10 +11,10 @@ namespace JWeiland\VideoShariff\ViewHelpers; -use TYPO3\CMS\Core\Resource\FileInterface; +use JWeiland\VideoShariff\Traits\GetCoreFileReferenceTrait; +use JWeiland\VideoShariff\Traits\GetOnlineMediaHelperTrait; use TYPO3\CMS\Core\Resource\FileReference; -use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry; -use TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder; +use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperInterface; use TYPO3\CMS\Extbase\Domain\Model\FileReference as ExtbaseFileReference; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; @@ -24,9 +24,9 @@ */ class VideoPublicUrlViewHelper extends AbstractViewHelper { - /** - * Initialize arguments - */ + use GetCoreFileReferenceTrait; + use GetOnlineMediaHelperTrait; + public function initializeArguments(): void { $this->registerArgument( @@ -39,10 +39,6 @@ public function initializeArguments(): void /** * Returns the absolute web path to the preview image. * - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * @return string * @throws \UnexpectedValueException */ public static function renderStatic( @@ -50,24 +46,24 @@ public static function renderStatic( \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext ): string { - /** @var FileReference|ExtbaseFileReference $fileReference */ - $fileReference = $arguments['fileReference']; + $publicUrl = ''; - // get Resource Object (non ExtBase version) - if (is_callable([$fileReference, 'getOriginalResource'])) { - // We have a domain model, so we need to fetch the FAL resource object from there - $fileReference = $fileReference->getOriginalResource(); - } - if (!($fileReference instanceof FileInterface || $fileReference instanceof AbstractFileFolder)) { - throw new \UnexpectedValueException('Supplied file object type ' . get_class($fileReference) . ' must be FileInterface or AbstractFileFolder.', 1454252193); + // Early return, if object is not allowed + if ( + $arguments['fileReference'] instanceof FileReference + || $arguments['fileReference'] instanceof ExtbaseFileReference + ) { + $fileReference = self::getCoreFileReference($arguments['fileReference']); + } else { + return $publicUrl; } + $file = $fileReference->getOriginalFile(); - $helper = OnlineMediaHelperRegistry::getInstance()->getOnlineMediaHelper($file); - if ($helper) { + $helper = self::getOnlineMediaHelper($file); + if ($helper instanceof OnlineMediaHelperInterface) { $publicUrl = $helper->getPublicUrl($file); - } else { - $publicUrl = ''; } + return $publicUrl; } } diff --git a/Tests/Functional/ViewHelpers/Format/SecondsToISO8601ViewHelperTest.php b/Tests/Functional/ViewHelpers/Format/SecondsToISO8601ViewHelperTest.php new file mode 100644 index 0000000..006e0f0 --- /dev/null +++ b/Tests/Functional/ViewHelpers/Format/SecondsToISO8601ViewHelperTest.php @@ -0,0 +1,76 @@ + [0, 'P0S'], + '14 seconds will return PT14S' => [14, 'PT14S'], + '59 seconds will return PT59S' => [59, 'PT59S'], + '60 seconds will return PT1M' => [60, 'PT1M'], + '61 seconds will return PT1M1S' => [61, 'PT1M1S'], + '250 seconds will return PT4M10S' => [250, 'PT4M10S'], + '3600 seconds will return PT1H' => [3600, 'PT1H'], + '3700 seconds will return PT1H1M40S' => [3700, 'PT1H1M40S'], + '43200 seconds will return PT12H' => [43200, 'PT12H'], + '86400 seconds will return P1D' => [86400, 'P1D'], + '86600 seconds will return P1DT3M20S' => [86600, 'P1DT3M20S'], + ]; + } + + /** + * @test + * + * @dataProvider secondsDataProvider + */ + public function formatSecondsToISO8601(int $seconds, string $expected) + { + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setTemplateSource(implode(' ', [ + '', + '{seconds -> jw:format.secondsToISO8601()}', + '', + ])); + + $view->assign('seconds', $seconds); + + self::assertSame( + $expected, + trim($view->render()) + ); + } +} diff --git a/Tests/Functional/ViewHelpers/VideoCreationDateViewHelperTest.php b/Tests/Functional/ViewHelpers/VideoCreationDateViewHelperTest.php new file mode 100644 index 0000000..1d8b4ef --- /dev/null +++ b/Tests/Functional/ViewHelpers/VideoCreationDateViewHelperTest.php @@ -0,0 +1,188 @@ +createMock(File::class); + $file + ->expects(self::atLeastOnce()) + ->method('getUid') + ->willReturn(1); + $file + ->expects(self::atLeastOnce()) + ->method('getProperty') + ->willReturnMap([ + ['content_creation_date', 1683100800], + ['creation_date', 0], + ['crdate', 0], + ]); + + $coreFileReference = $this->createMock(FileReference::class); + $coreFileReference + ->expects(self::atLeastOnce()) + ->method('getOriginalFile') + ->willReturn($file); + + $extbaseFileReference = new ExtbaseFileReference(); + $extbaseFileReference->setOriginalResource($coreFileReference); + + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setTemplateSource(implode(' ', [ + '', + '{jw:videoCreationDate(fileReference: \'{file}\')}', + '', + ])); + + $view->assign('file', $coreFileReference); + self::assertSame( + '1683100800', + trim($view->render()) + ); + + $view->assign('file', $extbaseFileReference); + self::assertSame( + '1683100800', + trim($view->render()) + ); + } + + /** + * @test + */ + public function videoCreateDateReturnCreationDate(): void + { + $file = $this->createMock(File::class); + $file + ->expects(self::atLeastOnce()) + ->method('getUid') + ->willReturn(1); + $file + ->expects(self::atLeastOnce()) + ->method('getProperty') + ->willReturnMap([ + ['content_creation_date', 0], + ['creation_date', 1682928000], + ['crdate', 0], + ]); + + $coreFileReference = $this->createMock(FileReference::class); + $coreFileReference + ->expects(self::atLeastOnce()) + ->method('getOriginalFile') + ->willReturn($file); + + $extbaseFileReference = new ExtbaseFileReference(); + $extbaseFileReference->setOriginalResource($coreFileReference); + + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setTemplateSource(implode(' ', [ + '', + '{jw:videoCreationDate(fileReference: \'{file}\')}', + '', + ])); + + $view->assign('file', $coreFileReference); + self::assertSame( + '1682928000', + trim($view->render()) + ); + + $view->assign('file', $extbaseFileReference); + self::assertSame( + '1682928000', + trim($view->render()) + ); + } + + /** + * @test + */ + public function videoCreateDateReturnCrdate(): void + { + $file = $this->createMock(File::class); + $file + ->expects(self::atLeastOnce()) + ->method('getUid') + ->willReturn(1); + $file + ->expects(self::atLeastOnce()) + ->method('getProperty') + ->willReturnMap([ + ['content_creation_date', 0], + ['creation_date', 0], + ['crdate', 1683014400], + ]); + + $coreFileReference = $this->createMock(FileReference::class); + $coreFileReference + ->expects(self::atLeastOnce()) + ->method('getOriginalFile') + ->willReturn($file); + + $extbaseFileReference = new ExtbaseFileReference(); + $extbaseFileReference->setOriginalResource($coreFileReference); + + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setTemplateSource(implode(' ', [ + '', + '{jw:videoCreationDate(fileReference: \'{file}\')}', + '', + ])); + + $view->assign('file', $coreFileReference); + self::assertSame( + '1683014400', + trim($view->render()) + ); + + $view->assign('file', $extbaseFileReference); + self::assertSame( + '1683014400', + trim($view->render()) + ); + } +} diff --git a/composer.json b/composer.json index ae795d8..30f17aa 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,11 @@ "typo3/cms-core": "^11.5.23 || ^12.3.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "3.14" + "sbuerk/typo3-cmscomposerinstallers-testingframework-bridge": "^0.1", + "typo3/testing-framework": "^7.0", + "phpunit/phpunit": "^9.6", + "typo3/coding-standards": "^0.6", + "friendsofphp/php-cs-fixer": "^3.14" }, "autoload": { "psr-4": { @@ -29,7 +33,8 @@ } }, "config": { - "vendor-dir": ".build/vendor", + "vendor-dir": ".Build/vendor", + "bin-dir": ".Build/bin", "allow-plugins": { "typo3/cms-composer-installers": true, "typo3/class-alias-loader": true @@ -38,20 +43,8 @@ "extra": { "typo3/cms": { "extension-key": "video_shariff", - "app-dir": ".build", - "web-dir": ".build/public" + "app-dir": ".Build", + "web-dir": ".Build/public" } - }, - "scripts": { - "php:fix": ".build/vendor/bin/php-cs-fixer --config=Build/php-cs-fixer/config.php fix Classes", - "ci:php:lint": "find *.php Classes Configuration -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l", - "ci:php:fixer": ".build/vendor/bin/php-cs-fixer --config=Build/php-cs-fixer/config.php fix --dry-run -v --show-progress=dots --diff Classes", - "link-extension": [ - "@php -r 'is_dir($extFolder=__DIR__.\"/.build/public/typo3conf/ext/\") || mkdir($extFolder, 0777, true);'", - "@php -r 'file_exists($extFolder=__DIR__.\"/.build/public/typo3conf/ext/video_shariff\") || symlink(__DIR__,$extFolder);'" - ], - "post-autoload-dump": [ - "@link-extension" - ] } } diff --git a/ext_emconf.php b/ext_emconf.php index 3a7442a..e20a19f 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -1,4 +1,5 @@ 'Video Shariff', 'description' => 'This extension provides more privacy when embedding videos in frontend.', @@ -11,9 +12,9 @@ 'constraints' => [ 'depends' => [ 'typo3' => '11.5.23-12.4.99', - 'fluid_styled_content' => '11.5.23-0.0.0' + 'fluid_styled_content' => '11.5.23-0.0.0', ], 'conflicts' => [], 'suggests' => [], - ] + ], ];